summaryrefslogtreecommitdiff
diff options
authorPaweł Stołowski <stolowski@gmail.com>2022-01-10 13:50:05 +0100
committerPaweł Stołowski <stolowski@gmail.com>2022-01-10 13:50:05 +0100
commit7812114ccfcb061f2d2e410b87e9a3e865da56f7 (patch)
tree6f98bbef012df5e3a18617164797f91c9fcd975f
parent6aee3a20e50b8a55eedea270907534e3c16f33ef (diff)
parent1c39a66605eee95e759d5cfe08b889b2ed21a039 (diff)
Merge branch 'master' into notifications/close-notificationnotifications/close-notification
-rw-r--r--.github/workflows/test.yaml15
-rw-r--r--asserts/sysdb/staging.go1
-rw-r--r--asserts/sysdb/testkeys.go1
-rw-r--r--bootloader/assets/assetstesting.go1
-rw-r--r--bootloader/assets/grub_cfg_asset.go2
-rw-r--r--bootloader/assets/grub_recovery_cfg_asset.go2
-rw-r--r--bootloader/withbootassettesting.go1
-rw-r--r--bootloader/withbootassettesting_test.go1
-rw-r--r--build-aux/snap/snapcraft.yaml20
-rwxr-xr-xc-vendor/vendor.sh5
-rw-r--r--cmd/.clangd2
-rw-r--r--cmd/snap-bootstrap/cmd_initramfs_mounts.go11
-rw-r--r--cmd/snap-bootstrap/cmd_initramfs_mounts_nosecboot.go1
-rw-r--r--cmd/snap-bootstrap/cmd_initramfs_mounts_secboot.go1
-rw-r--r--cmd/snap-bootstrap/cmd_initramfs_mounts_test.go18
-rw-r--r--cmd/snap-bootstrap/initramfs_systemd_mount.go5
-rw-r--r--cmd/snap-bootstrap/initramfs_systemd_mount_test.go44
-rw-r--r--cmd/snap-device-helper/snap-device-helper-test.c110
-rw-r--r--cmd/snap-device-helper/snap-device-helper.c26
-rw-r--r--cmd/snap-failure/cmd_snapd.go5
-rw-r--r--cmd/snap-failure/cmd_snapd_test.go35
-rw-r--r--cmd/snap-mgmt/snap-mgmt.sh.in6
-rw-r--r--cmd/snap-preseed/preseed_linux.go8
-rw-r--r--cmd/snap-preseed/preseed_other.go1
-rw-r--r--cmd/snap-repair/staging.go1
-rw-r--r--cmd/snap-repair/testkeys.go1
-rw-r--r--cmd/snap-seccomp/main_nonriscv64.go1
-rw-r--r--cmd/snap-seccomp/main_ppc64le.go1
-rw-r--r--cmd/snap-seccomp/main_riscv64.go1
-rw-r--r--cmd/snap-seccomp/old_seccomp.go1
-rw-r--r--cmd/snap-update-ns/bootstrap_ppc64le.go1
-rw-r--r--cmd/snap/cmd_list.go10
-rw-r--r--cmd/snap/cmd_list_test.go4
-rw-r--r--cmd/snap/cmd_userd.go1
-rw-r--r--cmd/snap/cmd_userd_test.go1
-rw-r--r--cmd/snap/cmd_version_other.go1
-rw-r--r--cmd/snap/cmd_warnings_test.go2
-rw-r--r--cmd/snap/last.go13
-rw-r--r--daemon/api.go4
-rw-r--r--daemon/api_sideload_n_try.go212
-rw-r--r--daemon/api_sideload_n_try_test.go290
-rw-r--r--daemon/api_snaps.go7
-rw-r--r--daemon/api_snaps_test.go29
-rw-r--r--daemon/export_api_snaps_test.go9
-rw-r--r--daemon/export_test.go8
-rw-r--r--data/env/snapd.fish.in4
-rw-r--r--data/selinux/snappy.te9
l---------debian2
-rw-r--r--dirs/dirs.go8
-rw-r--r--gadget/gadget.go6
-rw-r--r--gadget/gadget_test.go13
-rw-r--r--gadget/install/encrypt.go1
-rw-r--r--gadget/install/encrypt_test.go1
-rw-r--r--gadget/install/export_secboot_test.go1
-rw-r--r--gadget/install/install.go1
-rw-r--r--gadget/install/install_dummy.go1
-rw-r--r--gadget/install/install_test.go1
-rw-r--r--gadget/install/mount_other.go1
-rw-r--r--gadget/install/partition.go56
-rw-r--r--gadget/install/partition_test.go199
-rw-r--r--gadget/ondisk.go15
-rw-r--r--gadget/ondisk_test.go87
-rw-r--r--interfaces/builtin/kernel_module_load.go229
-rw-r--r--interfaces/builtin/kernel_module_load_test.go193
-rw-r--r--interfaces/builtin/mount_control.go490
-rw-r--r--interfaces/builtin/mount_control_test.go316
-rw-r--r--interfaces/builtin/opengl.go1
-rw-r--r--interfaces/builtin/shared_memory.go236
-rw-r--r--interfaces/builtin/shared_memory_test.go304
-rw-r--r--interfaces/kmod/backend.go108
-rw-r--r--interfaces/kmod/backend_test.go57
-rw-r--r--interfaces/kmod/spec.go43
-rw-r--r--interfaces/policy/basedeclaration_test.go9
-rw-r--r--interfaces/utils/export_test.go26
-rw-r--r--interfaces/utils/path_patterns.go177
-rw-r--r--interfaces/utils/path_patterns_test.go133
-rwxr-xr-xmkversion.sh1
-rw-r--r--osutil/chattr_32.go1
-rw-r--r--osutil/chattr_64.go1
-rw-r--r--osutil/cp_other.go1
-rw-r--r--osutil/export_fault_test.go1
-rw-r--r--osutil/faultinject.go1
-rw-r--r--osutil/faultinject_dummy.go1
-rw-r--r--osutil/faultinject_dummy_test.go1
-rw-r--r--osutil/faultinject_test.go1
-rw-r--r--osutil/group_cgo.go1
-rw-r--r--osutil/group_no_cgo.go1
-rw-r--r--osutil/settime_32bit.go1
-rw-r--r--osutil/settime_64bit.go5
-rw-r--r--osutil/sys/sysnum_16_linux.go1
-rw-r--r--osutil/sys/sysnum_32_linux.go1
-rw-r--r--osutil/udev/netlink/rawsockstop_other.go2
-rw-r--r--overlord/assertstate/assertstate.go4
-rw-r--r--overlord/assertstate/validation_set_tracking.go16
-rw-r--r--overlord/assertstate/validation_set_tracking_test.go53
-rw-r--r--overlord/configstate/configcore/certs.go1
-rw-r--r--overlord/configstate/configcore/cloud.go1
-rw-r--r--overlord/configstate/configcore/handlers.go3
-rw-r--r--overlord/configstate/configcore/proxy.go1
-rw-r--r--overlord/configstate/configcore/refresh.go1
-rw-r--r--overlord/configstate/configcore/runwithstate.go1
-rw-r--r--overlord/configstate/configcore/snapshots.go1
-rw-r--r--overlord/configstate/configcore/tmp.go145
-rw-r--r--overlord/configstate/configcore/tmp_test.go224
-rw-r--r--overlord/configstate/configcore/vitality.go1
-rw-r--r--overlord/devicestate/devicemgr.go7
-rw-r--r--overlord/devicestate/devicestate.go37
-rw-r--r--overlord/devicestate/devicestate_remodel_test.go66
-rw-r--r--overlord/devicestate/handlers_test.go2
-rw-r--r--overlord/managers_test.go293
-rw-r--r--overlord/snapshotstate/backend/export_test.go8
-rw-r--r--overlord/snapshotstate/backend/helpers.go57
-rw-r--r--overlord/snapstate/backend.go6
-rw-r--r--overlord/snapstate/backend/copydata.go135
-rw-r--r--overlord/snapstate/backend/copydata_test.go270
-rw-r--r--overlord/snapstate/backend/export_test.go21
-rw-r--r--overlord/snapstate/backend_test.go12
-rw-r--r--overlord/snapstate/export_test.go26
-rw-r--r--overlord/snapstate/handlers.go142
-rw-r--r--overlord/snapstate/handlers_rerefresh_test.go230
-rw-r--r--overlord/snapstate/snapstate.go75
-rw-r--r--overlord/snapstate/snapstate_install_test.go83
-rw-r--r--overlord/snapstate/snapstate_test.go5
-rw-r--r--overlord/snapstate/snapstate_update_test.go153
-rw-r--r--overlord/snapstate/storehelpers.go6
-rw-r--r--overlord/snapstate/storehelpers_test.go132
-rw-r--r--packaging/arch/PKGBUILD2
-rw-r--r--packaging/debian-sid/changelog380
-rw-r--r--packaging/debian-sid/snapd.postrm1
l---------packaging/fedora-351
-rw-r--r--packaging/fedora/snapd.spec370
-rw-r--r--packaging/opensuse/snapd.changes20
-rw-r--r--packaging/opensuse/snapd.spec4
-rw-r--r--packaging/ubuntu-14.04/changelog380
-rw-r--r--packaging/ubuntu-14.04/snapd.postrm1
l---------packaging/ubuntu-14.04/source2
-rw-r--r--packaging/ubuntu-16.04/changelog380
-rwxr-xr-xpackaging/ubuntu-16.04/rules3
-rw-r--r--packaging/ubuntu-16.04/snapd.postrm1
-rw-r--r--polkit/pid_start_time.go1
-rw-r--r--polkit/pid_start_time_test.go1
-rw-r--r--secboot/encrypt_dummy.go1
-rw-r--r--secboot/encrypt_sb.go1
-rw-r--r--secboot/encrypt_sb_test.go1
-rw-r--r--secboot/export_sb_test.go1
-rw-r--r--secboot/secboot_dummy.go1
-rw-r--r--secboot/secboot_hooks.go1
-rw-r--r--secboot/secboot_sb.go1
-rw-r--r--secboot/secboot_sb_test.go1
-rw-r--r--secboot/secboot_tpm.go1
-rw-r--r--snap/helpers.go65
-rw-r--r--snap/info.go2
-rw-r--r--snapdenv/withtestkeys.go1
-rw-r--r--snapdtool/info_file.go61
-rw-r--r--snapdtool/info_file_test.go23
-rw-r--r--snapdtool/tool_linux.go4
-rw-r--r--snapdtool/tool_other.go1
-rw-r--r--spread.yaml6
-rw-r--r--strutil/ctrl16.go1
-rw-r--r--strutil/ctrl17.go1
-rw-r--r--systemd/emulation.go49
-rw-r--r--tests/completion/data/twisted/this is a file with spaces in it.doc0
-rw-r--r--tests/completion/data/twisted/this isn't.innit0
-rw-r--r--tests/completion/data/twisted/twisted.tarbin0 -> 158 bytes
-rw-r--r--tests/completion/twisted.sh1
-rw-r--r--tests/core/basic20/task.yaml8
-rw-r--r--tests/core/failover/task.yaml46
-rw-r--r--tests/core/fsck-on-boot/task.yaml2
-rw-r--r--tests/core/kernel-and-base-single-reboot-failover/task.yaml117
-rw-r--r--tests/core/remodel-base/task.yaml30
-rw-r--r--tests/core/remodel-kernel/task.yaml14
-rw-r--r--tests/core/tmp/task.yaml46
-rw-r--r--tests/lib/fakestore/cmd/fakestore/cmd_make_refreshable.go13
-rw-r--r--tests/lib/fakestore/cmd/fakestore/main.go2
-rw-r--r--tests/lib/fakestore/refresh/refresh.go90
-rw-r--r--tests/lib/nested.sh20
-rwxr-xr-xtests/lib/pkgdb.sh3
-rwxr-xr-xtests/lib/prepare.sh26
l---------tests/lib/snaps/store/test-snapd-daemon-user/src/setregid32.c2
-rwxr-xr-xtests/lib/snaps/test-snapd-mount-control/bin/cmd6
-rw-r--r--tests/lib/snaps/test-snapd-mount-control/meta/snap.yaml23
-rw-r--r--tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml20
-rwxr-xr-xtests/lib/tools/tests.invariant23
-rw-r--r--tests/main/cgroup-devices-v1/task.yaml2
-rw-r--r--tests/main/cgroup-freezer/task.yaml2
-rw-r--r--tests/main/generic-unregister/task.yaml27
-rw-r--r--tests/main/interfaces-calendar-service/task.yaml1
-rw-r--r--tests/main/interfaces-contacts-service/task.yaml1
-rw-r--r--tests/main/interfaces-kernel-module-load/task.yaml58
-rw-r--r--tests/main/interfaces-kernel-module-load/test-snapd-kernel-module-load/meta/snap.yaml17
-rw-r--r--tests/main/interfaces-many-core-provided/task.yaml6
-rw-r--r--tests/main/interfaces-mount-control/task.yaml97
-rwxr-xr-xtests/main/interfaces-mount-control/test-mount-control-invalid/bin/cmd3
-rw-r--r--tests/main/interfaces-mount-control/test-mount-control-invalid/meta/snap.yaml16
-rwxr-xr-xtests/main/interfaces-shared-memory/shm-plug/bin/cmd2
-rw-r--r--tests/main/interfaces-shared-memory/shm-plug/meta/snap.yaml10
-rwxr-xr-xtests/main/interfaces-shared-memory/shm-slot/bin/cmd2
-rw-r--r--tests/main/interfaces-shared-memory/shm-slot/meta/snap.yaml12
-rw-r--r--tests/main/interfaces-shared-memory/task.yaml74
-rw-r--r--tests/main/microk8s-smoke/task.yaml2
-rw-r--r--tests/main/security-device-cgroups-helper/task.yaml14
-rw-r--r--tests/main/security-udev-input-subsystem/task.yaml4
l---------tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/bar1
l---------tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/bar -> baz1
l---------tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/bar -> baz -> qux1
l---------tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/bar -> qux1
l---------tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/baz1
l---------tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/baz -> qux1
l---------tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo1
l---------tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo -> bar1
l---------tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo -> bar -> baz1
l---------tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo -> bar -> qux1
l---------tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo -> baz1
l---------tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo -> baz -> qux1
l---------tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo -> qux1
l---------tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/qux1
-rw-r--r--tests/main/validate-container-happy/task.yaml38
-rwxr-xr-x[-rw-r--r--]tests/main/validate-container-happy/test-snapd-validate-container-happy/bin/validate-container (renamed from tests/completion/data/twisted/.just a hidden file)0
-rw-r--r--tests/main/validate-container-happy/test-snapd-validate-container-happy/hell/hell.tarbin0 -> 254 bytes
-rw-r--r--tests/main/validate-container-happy/test-snapd-validate-container-happy/meta/snap.yaml5
-rw-r--r--tests/nested/core/core20-reinstall-partitions/task.yaml43
-rw-r--r--tests/nested/manual/core20-initramfs-time-moves-forward/task.yaml2
-rw-r--r--tests/nested/manual/core20-remodel/task.yaml3
-rw-r--r--tests/nested/manual/core20-to-core22/task.yaml3
-rw-r--r--timeutil/synchronized.go2
225 files changed, 7973 insertions, 636 deletions
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index fc4bf47dfa..f828bd95bc 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -44,9 +44,19 @@ jobs:
uses: snapcore/action-build@v1
with:
snapcraft-channel: 4.x/candidate
- - name: Cache built artifact
+ - name: Cache and check built artifact
run: |
mkdir -p $(dirname "$CACHE_RESULT_STAMP")
+ unsquashfs snapd*.snap meta/snap.yaml usr/lib/snapd/info
+ if cat squashfs-root/meta/snap.yaml | grep -q "version:.*dirty.*"; then
+ echo "PR produces dirty snapd snap version"
+ cat squashfs-root/usr/lib/snapd/dirty-git-tree-info.txt
+ exit 1
+ elif cat squashfs-root/usr/lib/snapd/info | grep -q "VERSION=.*dirty.*"; then
+ echo "PR produces dirty internal snapd info version"
+ cat squashfs-root/usr/lib/snapd/info
+ exit 1
+ fi
cp -v *.snap "$(dirname $CACHE_RESULT_STAMP)/"
- name: Uploading snapd snap artifact
uses: actions/upload-artifact@v2
@@ -255,9 +265,8 @@ jobs:
- debian-10-64
- debian-11-64
- debian-sid-64
- - fedora-33-64
- fedora-34-64
- - opensuse-15.2-64
+ - fedora-35-64
- opensuse-15.3-64
- opensuse-tumbleweed-64
- ubuntu-14.04-64
diff --git a/asserts/sysdb/staging.go b/asserts/sysdb/staging.go
index c26a48a466..78e9472d78 100644
--- a/asserts/sysdb/staging.go
+++ b/asserts/sysdb/staging.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build withtestkeys || withstagingkeys
// +build withtestkeys withstagingkeys
/*
diff --git a/asserts/sysdb/testkeys.go b/asserts/sysdb/testkeys.go
index 7b61564597..09a0f49793 100644
--- a/asserts/sysdb/testkeys.go
+++ b/asserts/sysdb/testkeys.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build withtestkeys
// +build withtestkeys
/*
diff --git a/bootloader/assets/assetstesting.go b/bootloader/assets/assetstesting.go
index 260092db3d..ea77045d43 100644
--- a/bootloader/assets/assetstesting.go
+++ b/bootloader/assets/assetstesting.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build withbootassetstesting
// +build withbootassetstesting
/*
diff --git a/bootloader/assets/grub_cfg_asset.go b/bootloader/assets/grub_cfg_asset.go
index 8b0ce2a34b..b8c4b14673 100644
--- a/bootloader/assets/grub_cfg_asset.go
+++ b/bootloader/assets/grub_cfg_asset.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2021 Canonical Ltd
+ * Copyright (C) 2022 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
diff --git a/bootloader/assets/grub_recovery_cfg_asset.go b/bootloader/assets/grub_recovery_cfg_asset.go
index 0b31ebb017..79448a1145 100644
--- a/bootloader/assets/grub_recovery_cfg_asset.go
+++ b/bootloader/assets/grub_recovery_cfg_asset.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2021 Canonical Ltd
+ * Copyright (C) 2022 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
diff --git a/bootloader/withbootassettesting.go b/bootloader/withbootassettesting.go
index 2f737d669d..3d20f87b71 100644
--- a/bootloader/withbootassettesting.go
+++ b/bootloader/withbootassettesting.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build withbootassetstesting
// +build withbootassetstesting
/*
diff --git a/bootloader/withbootassettesting_test.go b/bootloader/withbootassettesting_test.go
index 1e547019d1..587c9646ff 100644
--- a/bootloader/withbootassettesting_test.go
+++ b/bootloader/withbootassettesting_test.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build withbootassetstesting
// +build withbootassetstesting
/*
diff --git a/build-aux/snap/snapcraft.yaml b/build-aux/snap/snapcraft.yaml
index ac8ec3267b..7bfe654156 100644
--- a/build-aux/snap/snapcraft.yaml
+++ b/build-aux/snap/snapcraft.yaml
@@ -30,8 +30,15 @@ parts:
plugin: nil
source: .
build-snaps: [go/1.13/stable]
+ # these packages are needed to call mkversion.sh in override-pull, all other
+ # dependencies are installed using apt-get build-dep
+ build-packages:
+ - git
+ - dpkg-dev
override-pull: |
snapcraftctl pull
+ # set version, this needs dpkg-parsechangelog (from dpkg-dev) and git
+ snapcraftctl set-version "$(./mkversion.sh --output-only)"
# Ensure that ./debian/ packaging which we are about to use
# matches the current `build-base` release. I.e. ubuntu-16.04
# for build-base:core, etc.
@@ -41,9 +48,18 @@ parts:
export DEBCONF_NONINTERACTIVE_SEEN=true
sudo -E apt-get build-dep -y ./
./get-deps.sh --skip-unused-check
- # set version after installing dependencies so we have all the tools here
- snapcraftctl set-version "$(./mkversion.sh --output-only)"
override-build: |
+ # TODO: when something like "craftctl get-version" is ready, then we can
+ # use that, but until then, we have to re-run mkversion.sh to check if the
+ # version number was set as "dirty" from the override-pull step
+ if sh -x ./mkversion.sh --output-only | grep "dirty"; then
+ mkdir -p $SNAPCRAFT_PART_INSTALL/usr/lib/snapd
+ (
+ echo "dirty git tree during build detected:"
+ git status
+ git diff
+ ) > $SNAPCRAFT_PART_INSTALL/usr/lib/snapd/dirty-git-tree-info.txt
+ fi
# unset the LD_FLAGS and LD_LIBRARY_PATH vars that snapcraft sets for us
# as those will point to the $SNAPCRAFT_STAGE which on re-builds will
# contain things like libc and friends that confuse the debian package
diff --git a/c-vendor/vendor.sh b/c-vendor/vendor.sh
index 507461587c..f9804a35e8 100755
--- a/c-vendor/vendor.sh
+++ b/c-vendor/vendor.sh
@@ -7,9 +7,12 @@ set -e
if [ ! -d ./squashfuse ]; then
git clone https://github.com/vasi/squashfuse
fi
+
# This is just tip/master as of Aug 30th 2021, there is no other
# specific reason to use this. It works with both "libfuse-dev" and
# "libfuse3-dev" which is important as 16.04 only have libfuse-dev
# and 21.10 only has libfuse3-dev
-(cd squashfuse && git checkout 74f4fe86ebd47a2fb7df5cb60d452354f977c72e)
+if [ -d ./squashfuse/.git ]; then
+ (cd squashfuse && git checkout 74f4fe86ebd47a2fb7df5cb60d452354f977c72e)
+fi
diff --git a/cmd/.clangd b/cmd/.clangd
index 617bbe62c5..6d8b27ae1f 100644
--- a/cmd/.clangd
+++ b/cmd/.clangd
@@ -1,2 +1,2 @@
CompileFlags:
- Add: -I/usr/include/glib-2.0 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wno-missing-field-initializers -Wno-unused-parameter \ No newline at end of file
+ Add: [-I/usr/include/glib-2.0, -Wall, -Wextra, -Wmissing-prototypes, -Wstrict-prototypes, -Wno-missing-field-initializers, -Wno-unused-parameter] \ No newline at end of file
diff --git a/cmd/snap-bootstrap/cmd_initramfs_mounts.go b/cmd/snap-bootstrap/cmd_initramfs_mounts.go
index 7c50984b4d..fefd893191 100644
--- a/cmd/snap-bootstrap/cmd_initramfs_mounts.go
+++ b/cmd/snap-bootstrap/cmd_initramfs_mounts.go
@@ -86,6 +86,10 @@ var (
secbootLockSealedKeys func() error
bootFindPartitionUUIDForBootedKernelDisk = boot.FindPartitionUUIDForBootedKernelDisk
+
+ mountReadOnlyOptions = &systemdMountOptions{
+ ReadOnly: true,
+ }
)
func stampedAction(stamp string, action func() error) error {
@@ -1309,7 +1313,7 @@ func generateMountsCommonInstallRecover(mst *initramfsMountsState) (model *asser
dir := snapTypeToMountDir[essentialSnap.EssentialType]
// TODO:UC20: we need to cross-check the kernel path with snapd_recovery_kernel used by grub
- if err := doSystemdMount(essentialSnap.Path, filepath.Join(boot.InitramfsRunMntDir, dir), nil); err != nil {
+ if err := doSystemdMount(essentialSnap.Path, filepath.Join(boot.InitramfsRunMntDir, dir), mountReadOnlyOptions); err != nil {
return nil, nil, err
}
}
@@ -1557,7 +1561,7 @@ func generateMountsModeRun(mst *initramfsMountsState) error {
if sn, ok := mounts[typ]; ok {
dir := snapTypeToMountDir[typ]
snapPath := filepath.Join(dirs.SnapBlobDirUnder(boot.InitramfsWritableDir), sn.Filename())
- if err := doSystemdMount(snapPath, filepath.Join(boot.InitramfsRunMntDir, dir), nil); err != nil {
+ if err := doSystemdMount(snapPath, filepath.Join(boot.InitramfsRunMntDir, dir), mountReadOnlyOptions); err != nil {
return err
}
}
@@ -1570,8 +1574,7 @@ func generateMountsModeRun(mst *initramfsMountsState) error {
if err != nil {
return fmt.Errorf("cannot load metadata and verify snapd snap: %v", err)
}
-
- return doSystemdMount(essSnaps[0].Path, filepath.Join(boot.InitramfsRunMntDir, "snapd"), nil)
+ return doSystemdMount(essSnaps[0].Path, filepath.Join(boot.InitramfsRunMntDir, "snapd"), mountReadOnlyOptions)
}
return nil
diff --git a/cmd/snap-bootstrap/cmd_initramfs_mounts_nosecboot.go b/cmd/snap-bootstrap/cmd_initramfs_mounts_nosecboot.go
index 31ee416424..b87922daf4 100644
--- a/cmd/snap-bootstrap/cmd_initramfs_mounts_nosecboot.go
+++ b/cmd/snap-bootstrap/cmd_initramfs_mounts_nosecboot.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build nosecboot
// +build nosecboot
/*
diff --git a/cmd/snap-bootstrap/cmd_initramfs_mounts_secboot.go b/cmd/snap-bootstrap/cmd_initramfs_mounts_secboot.go
index b095363641..9002e040a2 100644
--- a/cmd/snap-bootstrap/cmd_initramfs_mounts_secboot.go
+++ b/cmd/snap-bootstrap/cmd_initramfs_mounts_secboot.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !nosecboot
// +build !nosecboot
/*
diff --git a/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go b/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go
index 7d2733a770..5f5eed98e6 100644
--- a/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go
+++ b/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go
@@ -92,6 +92,9 @@ var (
needsNoSuidDiskMountOpts = &main.SystemdMountOptions{
NoSuid: true,
}
+ snapMountOpts = &main.SystemdMountOptions{
+ ReadOnly: true,
+ }
seedPart = disks.Partition{
FilesystemLabel: "ubuntu-seed",
@@ -501,6 +504,7 @@ func (s *initramfsMountsSuite) makeSeedSnapSystemdMount(typ snap.Type) systemdMo
}
mnt.what = filepath.Join(s.seedDir, "snaps", name+"_1.snap")
mnt.where = filepath.Join(boot.InitramfsRunMntDir, dir)
+ mnt.opts = snapMountOpts
return mnt
}
@@ -519,6 +523,7 @@ func (s *initramfsMountsSuite) makeRunSnapSystemdMount(typ snap.Type, sn snap.Pl
mnt.what = filepath.Join(dirs.SnapBlobDirUnder(boot.InitramfsWritableDir), sn.Filename())
mnt.where = filepath.Join(boot.InitramfsRunMntDir, dir)
+ mnt.opts = snapMountOpts
return mnt
}
@@ -1247,6 +1252,7 @@ After=%[1]s
"--no-pager",
"--no-ask-password",
"--fsck=no",
+ "--options=ro",
}, {
"systemd-mount",
filepath.Join(s.seedDir, "snaps", s.kernel.Filename()),
@@ -1254,6 +1260,7 @@ After=%[1]s
"--no-pager",
"--no-ask-password",
"--fsck=no",
+ "--options=ro",
}, {
"systemd-mount",
filepath.Join(s.seedDir, "snaps", s.core20.Filename()),
@@ -1261,6 +1268,7 @@ After=%[1]s
"--no-pager",
"--no-ask-password",
"--fsck=no",
+ "--options=ro",
}, {
"systemd-mount",
"tmpfs",
@@ -1415,6 +1423,7 @@ After=%[1]s
"--no-pager",
"--no-ask-password",
"--fsck=no",
+ "--options=ro",
}, {
"systemd-mount",
filepath.Join(s.seedDir, "snaps", s.kernel.Filename()),
@@ -1422,6 +1431,7 @@ After=%[1]s
"--no-pager",
"--no-ask-password",
"--fsck=no",
+ "--options=ro",
}, {
"systemd-mount",
filepath.Join(s.seedDir, "snaps", s.core20.Filename()),
@@ -1429,6 +1439,7 @@ After=%[1]s
"--no-pager",
"--no-ask-password",
"--fsck=no",
+ "--options=ro",
}, {
"systemd-mount",
"tmpfs",
@@ -1561,6 +1572,7 @@ After=%[1]s
"--no-pager",
"--no-ask-password",
"--fsck=no",
+ "--options=ro",
}, {
"systemd-mount",
filepath.Join(s.seedDir, "snaps", s.kernel.Filename()),
@@ -1568,6 +1580,7 @@ After=%[1]s
"--no-pager",
"--no-ask-password",
"--fsck=no",
+ "--options=ro",
}, {
"systemd-mount",
filepath.Join(s.seedDir, "snaps", s.core20.Filename()),
@@ -1575,6 +1588,7 @@ After=%[1]s
"--no-pager",
"--no-ask-password",
"--fsck=no",
+ "--options=ro",
}, {
"systemd-mount",
"tmpfs",
@@ -1746,6 +1760,7 @@ After=%[1]s
"--no-pager",
"--no-ask-password",
"--fsck=no",
+ "--options=ro",
}, {
"systemd-mount",
filepath.Join(dirs.SnapBlobDirUnder(boot.InitramfsWritableDir), s.kernel.Filename()),
@@ -1753,6 +1768,7 @@ After=%[1]s
"--no-pager",
"--no-ask-password",
"--fsck=no",
+ "--options=ro",
},
})
}
@@ -1870,6 +1886,7 @@ After=%[1]s
"--no-pager",
"--no-ask-password",
"--fsck=no",
+ "--options=ro",
}, {
"systemd-mount",
filepath.Join(dirs.SnapBlobDirUnder(boot.InitramfsWritableDir), s.kernel.Filename()),
@@ -1877,6 +1894,7 @@ After=%[1]s
"--no-pager",
"--no-ask-password",
"--fsck=no",
+ "--options=ro",
},
})
}
diff --git a/cmd/snap-bootstrap/initramfs_systemd_mount.go b/cmd/snap-bootstrap/initramfs_systemd_mount.go
index 84ae97e8fe..d0fef732cb 100644
--- a/cmd/snap-bootstrap/initramfs_systemd_mount.go
+++ b/cmd/snap-bootstrap/initramfs_systemd_mount.go
@@ -72,6 +72,8 @@ type systemdMountOptions struct {
NoSuid bool
// Bind indicates a bind mount
Bind bool
+ // Read-only mount
+ ReadOnly bool
}
// doSystemdMount will mount "what" at "where" using systemd-mount(1) with
@@ -135,6 +137,9 @@ func doSystemdMountImpl(what, where string, opts *systemdMountOptions) error {
if opts.Bind {
options = append(options, "bind")
}
+ if opts.ReadOnly {
+ options = append(options, "ro")
+ }
if len(options) > 0 {
args = append(args, "--options="+strings.Join(options, ","))
}
diff --git a/cmd/snap-bootstrap/initramfs_systemd_mount_test.go b/cmd/snap-bootstrap/initramfs_systemd_mount_test.go
index 20a437a4ac..8349bffe54 100644
--- a/cmd/snap-bootstrap/initramfs_systemd_mount_test.go
+++ b/cmd/snap-bootstrap/initramfs_systemd_mount_test.go
@@ -22,6 +22,7 @@ package main_test
import (
"fmt"
"path/filepath"
+ "strings"
"time"
. "gopkg.in/check.v1"
@@ -161,6 +162,16 @@ func (s *doSystemdMountSuite) TestDoSystemdMount(c *C) {
isMountedReturns: []bool{true},
comment: "happy nosuid+bind",
},
+ {
+ what: "/run/mnt/data/some.snap",
+ where: "/run/mnt/base",
+ opts: &main.SystemdMountOptions{
+ ReadOnly: true,
+ },
+ timeNowTimes: []time.Time{testStart, testStart},
+ isMountedReturns: []bool{true},
+ comment: "happy ro",
+ },
}
for _, t := range tt {
@@ -221,6 +232,8 @@ func (s *doSystemdMountSuite) TestDoSystemdMount(c *C) {
} else {
c.Assert(err, IsNil)
+ c.Assert(len(cmd.Calls()), Equals, 1)
+ call := cmd.Calls()[0]
args := []string{
"systemd-mount", t.what, t.where, "--no-pager", "--no-ask-password",
}
@@ -235,15 +248,30 @@ func (s *doSystemdMountSuite) TestDoSystemdMount(c *C) {
if opts.NoWait {
args = append(args, "--no-block")
}
- if opts.Bind && opts.NoSuid {
- args = append(args, "--options=nosuid,bind")
- } else if opts.NoSuid {
- args = append(args, "--options=nosuid")
- } else if opts.Bind {
- args = append(args, "--options=bind")
+ c.Assert(call[:len(args)], DeepEquals, args)
+ foundNoSuid := false
+ foundBind := false
+ foundReadOnly := false
+ if len(call) != len(args) {
+ c.Assert(len(call), Equals, len(args)+1)
+ c.Assert(strings.HasPrefix(call[len(args)], "--options="), Equals, true)
+ for _, opt := range strings.Split(strings.TrimPrefix(call[len(args)], "--options="), ",") {
+ switch opt {
+ case "nosuid":
+ foundNoSuid = true
+ case "bind":
+ foundBind = true
+ case "ro":
+ foundReadOnly = true
+ default:
+ c.Logf("Option '%s' unexpected", opt)
+ c.Fail()
+ }
+ }
}
-
- c.Assert(cmd.Calls(), DeepEquals, [][]string{args})
+ c.Assert(foundNoSuid, Equals, opts.NoSuid)
+ c.Assert(foundBind, Equals, opts.Bind)
+ c.Assert(foundReadOnly, Equals, opts.ReadOnly)
// check that the overrides are present if opts.Ephemeral is false,
// or check the overrides are not present if opts.Ephemeral is true
diff --git a/cmd/snap-device-helper/snap-device-helper-test.c b/cmd/snap-device-helper/snap-device-helper-test.c
index bd8dd69877..fa40055dac 100644
--- a/cmd/snap-device-helper/snap-device-helper-test.c
+++ b/cmd/snap-device-helper/snap-device-helper-test.c
@@ -80,7 +80,7 @@ static void sdh_test_tear_down(sdh_test_fixture *fixture, gconstpointer user_dat
}
static struct mocks {
- size_t cgorup_new_calls;
+ size_t cgroup_new_calls;
void *new_ret;
char *new_tag;
int new_flags;
@@ -104,7 +104,7 @@ static void mocks_reset(void) {
/* mocked in test */
sc_device_cgroup *sc_device_cgroup_new(const char *security_tag, int flags) {
g_debug("cgroup new called");
- mocks.cgorup_new_calls++;
+ mocks.cgroup_new_calls++;
mocks.new_tag = g_strdup(security_tag);
mocks.new_flags = flags;
return (sc_device_cgroup *)mocks.new_ret;
@@ -153,7 +153,7 @@ static void test_sdh_action(sdh_test_fixture *fixture, gconstpointer test_data)
int ret = snap_device_helper_run(&inv_block);
g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(mocks.cgorup_new_calls, ==, 1);
+ g_assert_cmpint(mocks.cgroup_new_calls, ==, 1);
if (g_strcmp0(td->action, "add") == 0 || g_strcmp0(td->action, "change") == 0) {
g_assert_cmpint(mocks.cgroup_allow_calls, ==, 1);
g_assert_cmpint(mocks.cgroup_deny_calls, ==, 0);
@@ -184,7 +184,7 @@ static void test_sdh_action(sdh_test_fixture *fixture, gconstpointer test_data)
symlink_in_sysroot(fixture, "/sys/devices/foo/tty/ttyS0/subsystem", "../../../../class/other");
ret = snap_device_helper_run(&inv_serial);
g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(mocks.cgorup_new_calls, ==, 1);
+ g_assert_cmpint(mocks.cgroup_new_calls, ==, 1);
if (g_strcmp0(td->action, "add") == 0 || g_strcmp0(td->action, "change") == 0) {
g_assert_cmpint(mocks.cgroup_allow_calls, ==, 1);
g_assert_cmpint(mocks.cgroup_deny_calls, ==, 0);
@@ -271,7 +271,7 @@ static void test_sdh_action_nvme(sdh_test_fixture *fixture, gconstpointer test_d
};
int ret = snap_device_helper_run(&inv_block);
g_assert_cmpint(ret, ==, 0);
- g_assert_cmpint(mocks.cgorup_new_calls, ==, 1);
+ g_assert_cmpint(mocks.cgroup_new_calls, ==, 1);
g_assert_cmpint(mocks.cgroup_allow_calls, ==, 1);
g_assert_cmpint(mocks.cgroup_deny_calls, ==, 0);
g_assert_cmpint(mocks.device_major, ==, tcs[i].expected_maj);
@@ -282,6 +282,94 @@ static void test_sdh_action_nvme(sdh_test_fixture *fixture, gconstpointer test_d
}
}
+static void test_sdh_action_remove_fallback_devtype(sdh_test_fixture *fixture, gconstpointer test_data) {
+ /* check that fallback guessing of device type if applied during remove action */
+ mkdir_in_sysroot(fixture, "/sys/devices/pci0000:00/0000:00:01.1/0000:01:00.0/nvme/nvme0/nvme0n1");
+ mkdir_in_sysroot(fixture, "/sys/devices/pci0000:00/0000:00:01.1/0000:01:00.0/nvme/nvme0/nvme0n1p1");
+ mkdir_in_sysroot(fixture, "/sys/devices/pci0000:00/0000:00:01.1/0000:01:00.0/nvme/nvme0/ng0n1");
+ mkdir_in_sysroot(fixture, "/sys/devices/pci0000:00/0000:00:01.1/0000:01:00.0/nvme/nvme0/hwmon0");
+ mkdir_in_sysroot(fixture, "/sys/devices/foo/block/sda/sda4");
+ mkdir_in_sysroot(fixture, "/sys//devices/pnp0/00:04/tty/ttyS0");
+
+ struct {
+ const char *dev;
+ const char *majmin;
+ int expected_maj;
+ int expected_min;
+ int expected_type;
+ } tcs[] = {
+ /* these device paths match the fallback pattern of block devices */
+ {
+ .dev = "/devices/pci0000:00/0000:00:01.1/0000:01:00.0/nvme/nvme0/nvme0n1",
+ .majmin = "259:0",
+ .expected_maj = 259,
+ .expected_min = 0,
+ .expected_type = S_IFBLK,
+ },
+ {
+ .dev = "/devices/pci0000:00/0000:00:01.1/0000:01:00.0/nvme/nvme0/nvme0n1p1",
+ .majmin = "259:1",
+ .expected_maj = 259,
+ .expected_min = 1,
+ .expected_type = S_IFBLK,
+ },
+ {
+ .dev = "/devices/foo/block/sda/sda4",
+ .majmin = "8:0",
+ .expected_maj = 8,
+ .expected_min = 0,
+ .expected_type = S_IFBLK,
+ },
+ /* these are treated as char devices */
+ {
+ .dev = "/devices/pci0000:00/0000:00:01.1/0000:01:00.0/nvme/nvme0",
+ .majmin = "242:0",
+ .expected_maj = 242,
+ .expected_min = 0,
+ .expected_type = S_IFCHR,
+ },
+ {
+ .dev = "/devices/pci0000:00/0000:00:01.1/0000:01:00.0/nvme/nvme0/hwmon0",
+ .majmin = "241:0",
+ .expected_maj = 241,
+ .expected_min = 0,
+ .expected_type = S_IFCHR,
+ },
+ {
+ .dev = "/devices/pnp0/00:04/tty/ttyS0",
+ .majmin = "4:64",
+ .expected_maj = 4,
+ .expected_min = 64,
+ .expected_type = S_IFCHR,
+ },
+ };
+
+ int bogus = 0;
+
+ for (size_t i = 0; i < sizeof(tcs) / sizeof(tcs[0]); i++) {
+ mocks_reset();
+ /* make cgroup_device_new return a non-NULL */
+ mocks.new_ret = &bogus;
+
+ struct sdh_invocation inv_block = {
+ .action = "remove",
+ .tagname = "snap_foo_bar",
+ .devpath = tcs[i].dev,
+ .majmin = tcs[i].majmin,
+ };
+ int ret = snap_device_helper_run(&inv_block);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpint(mocks.cgroup_new_calls, ==, 1);
+ g_assert_cmpint(mocks.cgroup_allow_calls, ==, 0);
+ g_assert_cmpint(mocks.cgroup_deny_calls, ==, 1);
+ g_assert_cmpint(mocks.device_major, ==, tcs[i].expected_maj);
+ g_assert_cmpint(mocks.device_minor, ==, tcs[i].expected_min);
+ g_assert_cmpint(mocks.device_type, ==, tcs[i].expected_type);
+ g_assert_cmpint(mocks.new_flags, !=, 0);
+ g_assert_cmpint(mocks.new_flags, ==, SC_DEVICE_CGROUP_FROM_EXISTING);
+ }
+}
+
static void run_sdh_die(const char *action, const char *tagname, const char *devpath, const char *majmin,
const char *msg) {
struct sdh_invocation inv = {
@@ -355,12 +443,18 @@ static void test_sdh_err_badaction(sdh_test_fixture *fixture, gconstpointer test
"ERROR: unknown action \"badaction\"\n");
}
-static void test_sdh_err_nosymlink(sdh_test_fixture *fixture, gconstpointer test_data) {
+static void test_sdh_err_nosymlink_block(sdh_test_fixture *fixture, gconstpointer test_data) {
// missing symlink
run_sdh_die("add", "snap_foo_bar", "/devices/foo/block/sda/sda4", "8:4",
"cannot read symlink */sys//devices/foo/block/sda/sda4/subsystem*\n");
}
+static void test_sdh_err_nosymlink_char(sdh_test_fixture *fixture, gconstpointer test_data) {
+ // missing symlink
+ run_sdh_die("add", "snap_foo_bar", "/devices/pnp0/00:04/tty/ttyS0", "4:64",
+ "cannot read symlink */sys//devices/pnp0/00:04/tty/ttyS0/subsystem*\n");
+}
+
static void test_sdh_err_funtag1(sdh_test_fixture *fixture, gconstpointer test_data) {
run_sdh_die("add", "snap___bar", "/devices/foo/block/sda/sda4", "8:4",
"security tag \"snap._.bar\" for snap \"_\" is not valid\n");
@@ -427,6 +521,7 @@ static void __attribute__((constructor)) init(void) {
_test_add("/snap-device-helper/add", &add_data, test_sdh_action);
_test_add("/snap-device-helper/change", &change_data, test_sdh_action);
_test_add("/snap-device-helper/remove", &remove_data, test_sdh_action);
+ _test_add("/snap-device-helper/remove_fallback", NULL, test_sdh_action_remove_fallback_devtype);
_test_add("/snap-device-helper/err/no-appname", NULL, test_sdh_err_noappname);
_test_add("/snap-device-helper/err/bad-appname", NULL, test_sdh_err_badappname);
@@ -436,7 +531,8 @@ static void __attribute__((constructor)) init(void) {
_test_add("/snap-device-helper/err/wrong-devmajorminor_late1", NULL, test_sdh_err_wrongdevmajorminor_late1);
_test_add("/snap-device-helper/err/wrong-devmajorminor_late2", NULL, test_sdh_err_wrongdevmajorminor_late2);
_test_add("/snap-device-helper/err/bad-action", NULL, test_sdh_err_badaction);
- _test_add("/snap-device-helper/err/no-symlink", NULL, test_sdh_err_nosymlink);
+ _test_add("/snap-device-helper/err/no-symlink-block", NULL, test_sdh_err_nosymlink_block);
+ _test_add("/snap-device-helper/err/no-symlink-char", NULL, test_sdh_err_nosymlink_char);
_test_add("/snap-device-helper/err/funtag1", NULL, test_sdh_err_funtag1);
_test_add("/snap-device-helper/err/funtag2", NULL, test_sdh_err_funtag2);
_test_add("/snap-device-helper/err/funtag3", NULL, test_sdh_err_funtag3);
diff --git a/cmd/snap-device-helper/snap-device-helper.c b/cmd/snap-device-helper/snap-device-helper.c
index 26bd47aad7..563f3c006e 100644
--- a/cmd/snap-device-helper/snap-device-helper.c
+++ b/cmd/snap-device-helper/snap-device-helper.c
@@ -15,6 +15,7 @@
*
*/
#include <errno.h>
+#include <fnmatch.h>
#include <libgen.h>
#include <limits.h>
#include <stdbool.h>
@@ -175,11 +176,26 @@ int snap_device_helper_run(const struct sdh_invocation *inv) {
char fullsubsystem[PATH_MAX] = {0};
sc_must_snprintf(sysdevsubsystem, sizeof(sysdevsubsystem), "%s/sys/%s/subsystem", sysroot, devpath);
if (readlink(sysdevsubsystem, fullsubsystem, sizeof(fullsubsystem)) < 0) {
- die("cannot read symlink %s", sysdevsubsystem);
- }
- char *subsystem = basename(fullsubsystem);
- if (sc_streq(subsystem, "block")) {
- devtype = S_IFBLK;
+ if (errno == ENOENT && sc_streq(action, "remove")) {
+ // on removal the devices are going away, so it is possible that the
+ // symlink is already gone, in which case try guessing the type like
+ // the old shell-based snap-device-helper did:
+ //
+ // > char devices are .../nvme/nvme* but block devices are
+ // > .../nvme/nvme*/nvme*n* and .../nvme/nvme*/nvme*n*p* so if have a
+ // > device that has nvme/nvme*/nvme*n* in it, treat it as a block
+ // > device
+ if ((fnmatch("*/block/*", devpath, 0) == 0) || (fnmatch("*/nvme/nvme*/nvme*n*", devpath, 0) == 0)) {
+ devtype = S_IFBLK;
+ }
+ } else {
+ die("cannot read symlink %s", sysdevsubsystem);
+ }
+ } else {
+ char *subsystem = basename(fullsubsystem);
+ if (sc_streq(subsystem, "block")) {
+ devtype = S_IFBLK;
+ }
}
sc_device_cgroup *cgroup = sc_device_cgroup_new(security_tag, SC_DEVICE_CGROUP_FROM_EXISTING);
if (!cgroup) {
diff --git a/cmd/snap-failure/cmd_snapd.go b/cmd/snap-failure/cmd_snapd.go
index 625860b1b3..75088cacc1 100644
--- a/cmd/snap-failure/cmd_snapd.go
+++ b/cmd/snap-failure/cmd_snapd.go
@@ -126,6 +126,11 @@ func (c *cmdSnapd) Execute(args []string) error {
// system, either a remodel or a plain snapd installation, call
// the snapd from the core snap
snapdPath = filepath.Join(dirs.SnapMountDir, "core", "current", "/usr/lib/snapd/snapd")
+ if !osutil.FileExists(snapdPath) {
+ // it is possible that the core snap is not installed at
+ // all, in which case we should try the snapd snap
+ snapdPath = filepath.Join(dirs.SnapMountDir, "snapd", "current", "/usr/lib/snapd/snapd")
+ }
prevRev = "0"
case nil:
// the snapd snap was installed before, use the previous revision
diff --git a/cmd/snap-failure/cmd_snapd_test.go b/cmd/snap-failure/cmd_snapd_test.go
index c7fbe62612..1c03647049 100644
--- a/cmd/snap-failure/cmd_snapd_test.go
+++ b/cmd/snap-failure/cmd_snapd_test.go
@@ -265,6 +265,41 @@ func (r *failureSuite) TestCallPrevSnapdFromCore(c *C) {
})
}
+func (r *failureSuite) TestCallPrevSnapdFromSnapdWhenNoCore(c *C) {
+ origArgs := os.Args
+ defer func() { os.Args = origArgs }()
+
+ // only one entry in sequence
+ writeSeqFile(c, "snapd", snap.R(123), []*snap.SideInfo{
+ {Revision: snap.R(123)},
+ })
+
+ // sanity
+ c.Assert(filepath.Join(dirs.SnapMountDir, "core", "current", "/usr/lib/snapd/snapd"), testutil.FileAbsent)
+ // mock snapd in the core snap
+ snapdCmd := testutil.MockCommand(c, filepath.Join(dirs.SnapMountDir, "snapd", "current", "/usr/lib/snapd/snapd"),
+ `test "$SNAPD_REVERT_TO_REV" = "0"`)
+ defer snapdCmd.Restore()
+
+ systemctlCmd := testutil.MockCommand(c, "systemctl", "")
+ defer systemctlCmd.Restore()
+
+ os.Args = []string{"snap-failure", "snapd"}
+ err := failure.Run()
+ c.Check(err, IsNil)
+ c.Check(r.Stderr(), HasLen, 0)
+
+ c.Check(snapdCmd.Calls(), DeepEquals, [][]string{
+ {"snapd"},
+ })
+ c.Check(systemctlCmd.Calls(), DeepEquals, [][]string{
+ {"systemctl", "stop", "snapd.socket"},
+ {"systemctl", "is-failed", "snapd.socket", "snapd.service"},
+ {"systemctl", "reset-failed", "snapd.socket", "snapd.service"},
+ {"systemctl", "restart", "snapd.socket"},
+ })
+}
+
func (r *failureSuite) TestCallPrevSnapdFail(c *C) {
origArgs := os.Args
defer func() { os.Args = origArgs }()
diff --git a/cmd/snap-mgmt/snap-mgmt.sh.in b/cmd/snap-mgmt/snap-mgmt.sh.in
index 47540ddbef..f9e4e6d7d5 100644
--- a/cmd/snap-mgmt/snap-mgmt.sh.in
+++ b/cmd/snap-mgmt/snap-mgmt.sh.in
@@ -45,7 +45,10 @@ purge() {
# Undo any bind mounts to ${SNAP_MOUNT_DIR} or /var/snap done by parallel
# installs or LP:#1668659
for mp in "$SNAP_MOUNT_DIR" /var/snap; do
- if grep -q " $mp $mp" /proc/self/mountinfo; then
+ # btrfs bind mounts actually include subvolume in the filesystem-path
+ # https://www.mail-archive.com/linux-btrfs@vger.kernel.org/msg51810.html
+ if grep -q " $mp $mp " /proc/self/mountinfo ||
+ grep -q -e "\(/.*\)$mp $mp .* btrfs .*\(subvol=\1\)\(,.*\)\?\$" /proc/self/mountinfo ; then
umount -l "$mp" || true
fi
done
@@ -110,6 +113,7 @@ purge() {
fi
# modules
rm -f "/etc/modules-load.d/snap.${snap}.conf"
+ rm -f "/etc/modprobe.d/snap.${snap}.conf"
# timer and socket units
find /etc/systemd/system -name "snap.${snap}.*.timer" -o -name "snap.${snap}.*.socket" | while read -r f; do
systemctl_stop "$(basename "$f")"
diff --git a/cmd/snap-preseed/preseed_linux.go b/cmd/snap-preseed/preseed_linux.go
index 6c6e819445..bfb201819f 100644
--- a/cmd/snap-preseed/preseed_linux.go
+++ b/cmd/snap-preseed/preseed_linux.go
@@ -158,16 +158,16 @@ type targetSnapdInfo struct {
// The function must be called after syscall.Chroot(..).
func chooseTargetSnapdVersion() (*targetSnapdInfo, error) {
// read snapd version from the mounted core/snapd snap
- infoPath := filepath.Join(snapdMountPath, dirs.CoreLibExecDir, "info")
- verFromSnap, err := snapdtool.SnapdVersionFromInfoFile(infoPath)
+ snapdInfoDir := filepath.Join(snapdMountPath, dirs.CoreLibExecDir)
+ verFromSnap, _, err := snapdtool.SnapdVersionFromInfoFile(snapdInfoDir)
if err != nil {
return nil, err
}
// read snapd version from the main fs under chroot (snapd from the deb);
// assumes running under chroot already.
- infoPath = filepath.Join(dirs.GlobalRootDir, dirs.CoreLibExecDir, "info")
- verFromDeb, err := snapdtool.SnapdVersionFromInfoFile(infoPath)
+ hostInfoDir := filepath.Join(dirs.GlobalRootDir, dirs.CoreLibExecDir)
+ verFromDeb, _, err := snapdtool.SnapdVersionFromInfoFile(hostInfoDir)
if err != nil {
return nil, err
}
diff --git a/cmd/snap-preseed/preseed_other.go b/cmd/snap-preseed/preseed_other.go
index 72c8c6079a..2de640b0c5 100644
--- a/cmd/snap-preseed/preseed_other.go
+++ b/cmd/snap-preseed/preseed_other.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !linux
// +build !linux
/*
diff --git a/cmd/snap-repair/staging.go b/cmd/snap-repair/staging.go
index dca15c1afd..6152240055 100644
--- a/cmd/snap-repair/staging.go
+++ b/cmd/snap-repair/staging.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build withtestkeys || withstagingkeys
// +build withtestkeys withstagingkeys
/*
diff --git a/cmd/snap-repair/testkeys.go b/cmd/snap-repair/testkeys.go
index e6b9158a7f..b598c42529 100644
--- a/cmd/snap-repair/testkeys.go
+++ b/cmd/snap-repair/testkeys.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build withtestkeys
// +build withtestkeys
/*
diff --git a/cmd/snap-seccomp/main_nonriscv64.go b/cmd/snap-seccomp/main_nonriscv64.go
index 2825561e38..e3690d079d 100644
--- a/cmd/snap-seccomp/main_nonriscv64.go
+++ b/cmd/snap-seccomp/main_nonriscv64.go
@@ -1,5 +1,6 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
//
+//go:build !riscv64
// +build !riscv64
/*
diff --git a/cmd/snap-seccomp/main_ppc64le.go b/cmd/snap-seccomp/main_ppc64le.go
index fdfc2543d0..d719b1402f 100644
--- a/cmd/snap-seccomp/main_ppc64le.go
+++ b/cmd/snap-seccomp/main_ppc64le.go
@@ -1,5 +1,6 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
//
+//go:build ppc64le && go1.7 && !go1.8
// +build ppc64le,go1.7,!go1.8
/*
diff --git a/cmd/snap-seccomp/main_riscv64.go b/cmd/snap-seccomp/main_riscv64.go
index 58003f9759..ca78eddf6f 100644
--- a/cmd/snap-seccomp/main_riscv64.go
+++ b/cmd/snap-seccomp/main_riscv64.go
@@ -1,5 +1,6 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
//
+//go:build riscv64
// +build riscv64
/*
diff --git a/cmd/snap-seccomp/old_seccomp.go b/cmd/snap-seccomp/old_seccomp.go
index 8b3b4bf9dd..a053dceb82 100644
--- a/cmd/snap-seccomp/old_seccomp.go
+++ b/cmd/snap-seccomp/old_seccomp.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build oldseccomp
// +build oldseccomp
/*
diff --git a/cmd/snap-update-ns/bootstrap_ppc64le.go b/cmd/snap-update-ns/bootstrap_ppc64le.go
index fdfc2543d0..d719b1402f 100644
--- a/cmd/snap-update-ns/bootstrap_ppc64le.go
+++ b/cmd/snap-update-ns/bootstrap_ppc64le.go
@@ -1,5 +1,6 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
//
+//go:build ppc64le && go1.7 && !go1.8
// +build ppc64le,go1.7,!go1.8
/*
diff --git a/cmd/snap/cmd_list.go b/cmd/snap/cmd_list.go
index d92f2fbf4a..2914a771c8 100644
--- a/cmd/snap/cmd_list.go
+++ b/cmd/snap/cmd_list.go
@@ -84,6 +84,14 @@ func fmtChannel(ch string) string {
return ch[:idx+1] + "…"
}
+func fmtVersion(v string) string {
+ if v == "" {
+ // most likely a broken snap, leave a placeholder
+ return "-"
+ }
+ return v
+}
+
func (x *cmdList) Execute(args []string) error {
if len(args) > 0 {
return ErrExtraArgs
@@ -116,7 +124,7 @@ func (x *cmdList) Execute(args []string) error {
// doing it this way because otherwise it's a sea of %s\t%s\t%s
line := []string{
snap.Name,
- snap.Version,
+ fmtVersion(snap.Version),
snap.Revision.String(),
fmtChannel(snap.TrackingChannel),
shortPublisher(esc, snap.Publisher),
diff --git a/cmd/snap/cmd_list_test.go b/cmd/snap/cmd_list_test.go
index 0cdd9efb31..b711e3755f 100644
--- a/cmd/snap/cmd_list_test.go
+++ b/cmd/snap/cmd_list_test.go
@@ -202,6 +202,8 @@ func (s *SnapSuite) TestListWithNotes(c *check.C) {
,{"name": "dm1", "status": "active", "version": "5", "revision":1, "devmode": true, "confinement": "devmode"}
,{"name": "dm2", "status": "active", "version": "5", "revision":1, "devmode": true, "confinement": "strict"}
,{"name": "cf1", "status": "active", "version": "6", "revision":2, "confinement": "devmode", "jailmode": true}
+,{"name": "br1", "status": "active", "version": "", "revision":2, "publisher": {"id": "bar-id", "username": "bar", "display-name": "Bar", "validation": "unproven"}, "confinement": "strict", "broken": "snap is broken"}
+,{"name": "dbr1", "status": "", "version": "", "revision":2, "publisher": {"id": "bar-id", "username": "bar", "display-name": "Bar", "validation": "unproven"}, "confinement": "strict", "broken": "snap is broken"}
]}`)
default:
c.Fatalf("expected to get 1 requests, now on %d", n+1)
@@ -217,6 +219,8 @@ func (s *SnapSuite) TestListWithNotes(c *check.C) {
c.Check(s.Stdout(), check.Matches, `(?ms).*^dm1 +.* +devmode$`)
c.Check(s.Stdout(), check.Matches, `(?ms).*^dm2 +.* +devmode$`)
c.Check(s.Stdout(), check.Matches, `(?ms).*^cf1 +.* +jailmode$`)
+ c.Check(s.Stdout(), check.Matches, `(?ms).*^br1 +- +2 +- +bar +broken$`)
+ c.Check(s.Stdout(), check.Matches, `(?ms).*^dbr1 +- +2 +- +bar +disabled,broken$`)
c.Check(s.Stderr(), check.Equals, "")
}
diff --git a/cmd/snap/cmd_userd.go b/cmd/snap/cmd_userd.go
index d1e114d4fa..4c9df875ea 100644
--- a/cmd/snap/cmd_userd.go
+++ b/cmd/snap/cmd_userd.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !darwin
// +build !darwin
/*
diff --git a/cmd/snap/cmd_userd_test.go b/cmd/snap/cmd_userd_test.go
index 8b9880ab70..b22c847d69 100644
--- a/cmd/snap/cmd_userd_test.go
+++ b/cmd/snap/cmd_userd_test.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !darwin
// +build !darwin
/*
diff --git a/cmd/snap/cmd_version_other.go b/cmd/snap/cmd_version_other.go
index 2de3d0b64b..2204cfdc25 100644
--- a/cmd/snap/cmd_version_other.go
+++ b/cmd/snap/cmd_version_other.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !linux
// +build !linux
/*
diff --git a/cmd/snap/cmd_warnings_test.go b/cmd/snap/cmd_warnings_test.go
index 9a85c0c747..0245fcd77a 100644
--- a/cmd/snap/cmd_warnings_test.go
+++ b/cmd/snap/cmd_warnings_test.go
@@ -222,7 +222,7 @@ func (s *warningSuite) TestListWithWarnings(c *check.C) {
c.Check(rest, check.HasLen, 0)
c.Check(s.Stdout(), check.Equals, `
Name Version Rev Tracking Publisher Notes
- unset - - disabled
+ - unset - - disabled
`[1:])
c.Check(s.Stderr(), check.Equals, "WARNING: There are 2 new warnings. See 'snap warnings'.\n")
diff --git a/cmd/snap/last.go b/cmd/snap/last.go
index 0abb383b53..4cd30104dd 100644
--- a/cmd/snap/last.go
+++ b/cmd/snap/last.go
@@ -25,6 +25,7 @@ import (
"github.com/snapcore/snapd/client"
"github.com/snapcore/snapd/i18n"
+ "github.com/snapcore/snapd/strutil"
)
type changeIDMixin struct {
@@ -72,7 +73,17 @@ func (l *changeIDMixin) GetChangeID() (string, error) {
kind = kind[:l]
}
// our internal change types use "-snap" postfix but let user skip it and use short form.
- if kind == "refresh" || kind == "install" || kind == "remove" || kind == "connect" || kind == "disconnect" || kind == "configure" || kind == "try" {
+ shortForms := []string{
+ // see api_snaps.go:snapInstructionDispTable
+ "install", "refresh", "remove", "revert", "enable", "disable", "switch",
+ // see api_interfaces.go:changeInterfaces
+ "connect", "disconnect",
+ // see api_snap_conf.go:setSnapConf
+ "configure",
+ // see api_sideload_n_try.go:trySnap
+ "try",
+ }
+ if strutil.ListContains(shortForms, kind) {
kind += "-snap"
}
changes, err := queryChanges(cli, &client.ChangesOptions{Selector: client.ChangesAll})
diff --git a/daemon/api.go b/daemon/api.go
index 7a8f972890..5aebe1ea21 100644
--- a/daemon/api.go
+++ b/daemon/api.go
@@ -132,6 +132,7 @@ func storeFrom(d *Daemon) snapstate.StoreService {
var (
snapstateInstall = snapstate.Install
snapstateInstallPath = snapstate.InstallPath
+ snapstateInstallPathMany = snapstate.InstallPathMany
snapstateRefreshCandidates = snapstate.RefreshCandidates
snapstateTryPath = snapstate.TryPath
snapstateUpdate = snapstate.Update
@@ -142,7 +143,8 @@ var (
snapstateRevertToRevision = snapstate.RevertToRevision
snapstateSwitch = snapstate.Switch
- assertstateRefreshSnapAssertions = assertstate.RefreshSnapAssertions
+ assertstateRefreshSnapAssertions = assertstate.RefreshSnapAssertions
+ assertstateRestoreValidationSetsTracking = assertstate.RestoreValidationSetsTracking
)
func ensureStateSoonImpl(st *state.State) {
diff --git a/daemon/api_sideload_n_try.go b/daemon/api_sideload_n_try.go
index a8a260bf13..bec514d91d 100644
--- a/daemon/api_sideload_n_try.go
+++ b/daemon/api_sideload_n_try.go
@@ -21,6 +21,7 @@ package daemon
import (
"bytes"
+ "context"
"errors"
"fmt"
"io"
@@ -37,6 +38,7 @@ import (
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/overlord/assertstate"
+ "github.com/snapcore/snapd/overlord/auth"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/snap"
@@ -75,24 +77,53 @@ func (f *Form) RemoveAllExcept(paths []string) {
}
}
-// SnapFileNameAndTempPath returns the original file path/name and the path to
-// where the temp file is written.
-func (f *Form) SnapFileNameAndTempPath() (srcFilename, path string, apiErr *apiError) {
+type uploadedSnap struct {
+ // filename is the original name/path of the snap file.
+ filename string
+ // tmpPath is the location where the temp snap file is stored.
+ tmpPath string
+ // instanceName is optional and can only be set if only one snap was uploaded.
+ instanceName string
+}
+
+// GetSnapFiles returns the original name and temp path for each snap file in
+// the form. Optionally, it might include a requested instance name, but only
+// if the was only one file in the form.
+func (f *Form) GetSnapFiles() ([]*uploadedSnap, *apiError) {
if len(f.FileRefs["snap"]) == 0 {
- return "", "", BadRequest(`cannot find "snap" file field in provided multipart/form-data payload`)
+ return nil, BadRequest(`cannot find "snap" file field in provided multipart/form-data payload`)
}
- snapFile := f.FileRefs["snap"][0]
- srcFilename, path = snapFile.Filename, snapFile.TmpPath
+ refs := f.FileRefs["snap"]
+ if len(refs) == 1 && len(f.Values["snap-path"]) > 0 {
+ uploaded := &uploadedSnap{
+ filename: f.Values["snap-path"][0],
+ tmpPath: refs[0].TmpPath,
+ }
- if len(f.Values["snap-path"]) > 0 {
- srcFilename = f.Values["snap-path"][0]
+ if len(f.Values["name"]) > 0 {
+ uploaded.instanceName = f.Values["name"][0]
+ }
+ return []*uploadedSnap{uploaded}, nil
}
- return srcFilename, path, nil
+ snapFiles := make([]*uploadedSnap, len(refs))
+ for i, ref := range refs {
+ snapFiles[i] = &uploadedSnap{
+ filename: ref.Filename,
+ tmpPath: ref.TmpPath,
+ }
+ }
+
+ return snapFiles, nil
+}
+
+type sideloadFlags struct {
+ snapstate.Flags
+ dangerousOK bool
}
-func sideloadOrTrySnap(c *Command, body io.ReadCloser, boundary string) Response {
+func sideloadOrTrySnap(c *Command, body io.ReadCloser, boundary string, user *auth.UserState) Response {
route := c.d.router.Get(stateChangeCmd.Path)
if route == nil {
return InternalError("cannot find route for change")
@@ -111,7 +142,6 @@ func sideloadOrTrySnap(c *Command, body io.ReadCloser, boundary string) Response
form.RemoveAllExcept(pathsToNotRemove)
}()
- dangerousOK := isTrue(form, "dangerous")
flags, err := modeFlags(isTrue(form, "devmode"), isTrue(form, "jailmode"), isTrue(form, "classic"))
if err != nil {
return BadRequest(err.Error())
@@ -123,97 +153,153 @@ func sideloadOrTrySnap(c *Command, body io.ReadCloser, boundary string) Response
}
return trySnap(c.d.overlord.State(), form.Values["snap-path"][0], flags)
}
- flags.RemoveSnapPath = true
+ flags.RemoveSnapPath = true
flags.Unaliased = isTrue(form, "unaliased")
flags.IgnoreRunning = isTrue(form, "ignore-running")
- systemRestartImmediate := isTrue(form, "system-restart-immediate")
- origPath, tempPath, errRsp := form.SnapFileNameAndTempPath()
+ sideloadFlags := sideloadFlags{
+ Flags: flags,
+ dangerousOK: isTrue(form, "dangerous"),
+ }
+
+ snapFiles, errRsp := form.GetSnapFiles()
+ if errRsp != nil {
+ return errRsp
+ }
+
+ st := c.d.overlord.State()
+ st.Lock()
+ defer st.Unlock()
+
+ var chg *state.Change
+ if len(snapFiles) > 1 {
+ chg, errRsp = sideloadManySnaps(st, snapFiles, sideloadFlags, user)
+ } else {
+ chg, errRsp = sideloadSnap(st, snapFiles[0], sideloadFlags)
+ }
if errRsp != nil {
return errRsp
}
+ chg.Set("system-restart-immediate", isTrue(form, "system-restart-immediate"))
+
+ ensureStateSoon(st)
+
+ // the handoff is only done when the unlock succeeds (instead of panicking)
+ // but this is good enough
+ pathsToNotRemove = make([]string, len(snapFiles))
+ for i, snapFile := range snapFiles {
+ pathsToNotRemove[i] = snapFile.tmpPath
+ }
+
+ return AsyncResponse(nil, chg.ID())
+}
+
+func sideloadManySnaps(st *state.State, snapFiles []*uploadedSnap, flags sideloadFlags, user *auth.UserState) (*state.Change, *apiError) {
+ sideInfos := make([]*snap.SideInfo, len(snapFiles))
+ names := make([]string, len(snapFiles))
+ tempPaths := make([]string, len(snapFiles))
+ origPaths := make([]string, len(snapFiles))
+
+ for i, snapFile := range snapFiles {
+ si, apiError := readSideInfo(st, snapFile.tmpPath, snapFile.filename, flags)
+ if apiError != nil {
+ return nil, apiError
+ }
+
+ sideInfos[i] = si
+ names[i] = si.RealName
+ tempPaths[i] = snapFile.tmpPath
+ origPaths[i] = snapFile.filename
+ }
+
+ var userID int
+ if user != nil {
+ userID = user.ID
+ }
+
+ tss, err := snapstateInstallPathMany(context.TODO(), st, sideInfos, tempPaths, userID, &flags.Flags)
+ if err != nil {
+ return nil, errToResponse(err, tempPaths, InternalError, "cannot install snap files: %v")
+ }
+
+ msg := fmt.Sprintf(i18n.G("Install snaps %s from files %s"), strutil.Quoted(names), strutil.Quoted(origPaths))
+ chg := newChange(st, "install-snap", msg, tss, names)
+ chg.Set("api-data", map[string][]string{"snap-names": names})
+
+ return chg, nil
+}
+
+func sideloadSnap(st *state.State, snapFile *uploadedSnap, flags sideloadFlags) (*state.Change, *apiError) {
var instanceName string
- if len(form.Values["name"]) > 0 {
+ if snapFile.instanceName != "" {
// caller has specified desired instance name
- instanceName = form.Values["name"][0]
+ instanceName = snapFile.instanceName
if err := snap.ValidateInstanceName(instanceName); err != nil {
- return BadRequest(err.Error())
+ return nil, BadRequest(err.Error())
}
}
- st := c.d.overlord.State()
- st.Lock()
- defer st.Unlock()
+ sideInfo, apiErr := readSideInfo(st, snapFile.tmpPath, snapFile.filename, flags)
+ if apiErr != nil {
+ return nil, apiErr
+ }
+
+ if instanceName != "" {
+ requestedSnapName := snap.InstanceSnap(instanceName)
+ if requestedSnapName != sideInfo.RealName {
+ return nil, BadRequest(fmt.Sprintf("instance name %q does not match snap name %q", instanceName, sideInfo.RealName))
+ }
+ } else {
+ instanceName = sideInfo.RealName
+ }
+
+ tset, _, err := snapstateInstallPath(st, sideInfo, snapFile.tmpPath, instanceName, "", flags.Flags)
+ if err != nil {
+ return nil, errToResponse(err, []string{sideInfo.RealName}, InternalError, "cannot install snap file: %v")
+ }
+
+ msg := fmt.Sprintf(i18n.G("Install %q snap from file %q"), instanceName, snapFile.filename)
+ chg := newChange(st, "install-snap", msg, []*state.TaskSet{tset}, []string{instanceName})
+ chg.Set("api-data", map[string]string{"snap-name": instanceName})
+
+ return chg, nil
+}
- var snapName string
+func readSideInfo(st *state.State, tempPath string, origPath string, flags sideloadFlags) (*snap.SideInfo, *apiError) {
var sideInfo *snap.SideInfo
- if !dangerousOK {
+ if !flags.dangerousOK {
si, err := snapasserts.DeriveSideInfo(tempPath, assertstate.DB(st))
switch {
case err == nil:
- snapName = si.RealName
sideInfo = si
case asserts.IsNotFound(err):
// with devmode we try to find assertions but it's ok
// if they are not there (implies --dangerous)
- if !isTrue(form, "devmode") {
+ if !flags.DevMode {
msg := "cannot find signatures with metadata for snap"
if origPath != "" {
msg = fmt.Sprintf("%s %q", msg, origPath)
}
- return BadRequest(msg)
+ return nil, BadRequest(msg)
}
// TODO: set a warning if devmode
default:
- return BadRequest(err.Error())
+ return nil, BadRequest(err.Error())
}
}
- if snapName == "" {
+ if sideInfo == nil {
// potentially dangerous but dangerous or devmode params were set
info, err := unsafeReadSnapInfo(tempPath)
if err != nil {
- return BadRequest("cannot read snap file: %v", err)
+ return nil, BadRequest("cannot read snap file: %v", err)
}
- snapName = info.SnapName()
- sideInfo = &snap.SideInfo{RealName: snapName}
+ sideInfo = &snap.SideInfo{RealName: info.SnapName()}
}
-
- if instanceName != "" {
- requestedSnapName := snap.InstanceSnap(instanceName)
- if requestedSnapName != snapName {
- return BadRequest(fmt.Sprintf("instance name %q does not match snap name %q", instanceName, snapName))
- }
- } else {
- instanceName = snapName
- }
-
- msg := fmt.Sprintf(i18n.G("Install %q snap from file"), instanceName)
- if origPath != "" {
- msg = fmt.Sprintf(i18n.G("Install %q snap from file %q"), instanceName, origPath)
- }
-
- tset, _, err := snapstateInstallPath(st, sideInfo, tempPath, instanceName, "", flags)
- if err != nil {
- return errToResponse(err, []string{snapName}, InternalError, "cannot install snap file: %v")
- }
-
- chg := newChange(st, "install-snap", msg, []*state.TaskSet{tset}, []string{instanceName})
- if systemRestartImmediate {
- chg.Set("system-restart-immediate", true)
- }
- chg.Set("api-data", map[string]string{"snap-name": instanceName})
-
- ensureStateSoon(st)
-
- // only when the unlock succeeds (as opposed to panicing) is the handoff done
- // but this is good enough
- pathsToNotRemove = append(pathsToNotRemove, tempPath)
-
- return AsyncResponse(nil, chg.ID())
+ return sideInfo, nil
}
// maxReadBuflen is the maximum buffer size for reading the non-file parts in the snap upload form
diff --git a/daemon/api_sideload_n_try_test.go b/daemon/api_sideload_n_try_test.go
index bcdb02c6f4..618dba66bd 100644
--- a/daemon/api_sideload_n_try_test.go
+++ b/daemon/api_sideload_n_try_test.go
@@ -22,13 +22,16 @@ package daemon_test
import (
"bytes"
"context"
+ "crypto"
"crypto/rand"
+ "errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"regexp"
+ "strconv"
"time"
"gopkg.in/check.v1"
@@ -43,6 +46,7 @@ import (
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/sandbox"
"github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/strutil"
"github.com/snapcore/snapd/testutil"
)
@@ -583,7 +587,7 @@ func (s *sideloadSuite) TestSideloadUsePreciselyAllMemory(c *check.C) {
c.Check(apiErr.Message, check.Equals, `cannot find "snap" file field in provided multipart/form-data payload`)
}
-func (s *sideloadSuite) TestCleanUpTempFilesIfRequestFailed(c *check.C) {
+func (s *sideloadSuite) TestSideloadCleanUpTempFilesIfRequestFailed(c *check.C) {
s.daemonWithOverlordMockAndStore()
// write file parts
@@ -618,7 +622,7 @@ func (s *sideloadSuite) TestCleanUpTempFilesIfRequestFailed(c *check.C) {
c.Check(matches, check.HasLen, 0)
}
-func (s *sideloadSuite) TestCleanUpUnusedTempSnapFiles(c *check.C) {
+func (s *sideloadSuite) TestSideloadCleanUpUnusedTempSnapFiles(c *check.C) {
body := "----hello--\r\n" +
"Content-Disposition: form-data; name=\"devmode\"\r\n" +
"\r\n" +
@@ -628,10 +632,9 @@ func (s *sideloadSuite) TestCleanUpUnusedTempSnapFiles(c *check.C) {
"\r\n" +
"xyzzy\r\n" +
"----hello--\r\n" +
- "Content-Disposition: form-data; name=\"snap\"; filename=\"two\"\r\n" +
+ // only files with the name 'snap' are used
+ "Content-Disposition: form-data; name=\"not-snap\"; filename=\"two\"\r\n" +
"\r\n" +
- // sideloadCheck checks that the snap file passed to the change has contents "xyzzy" so
- // having a different body here tests that the second file isn't passed to change
"bla\r\n" +
"----hello--\r\n"
@@ -645,6 +648,283 @@ func (s *sideloadSuite) TestCleanUpUnusedTempSnapFiles(c *check.C) {
c.Check(matches, check.HasLen, 1)
}
+func (s *sideloadSuite) TestSideloadManySnaps(c *check.C) {
+ d := s.daemonWithFakeSnapManager(c)
+ expectedFlags := &snapstate.Flags{RemoveSnapPath: true, DevMode: true}
+
+ restore := daemon.MockSnapstateInstallPathMany(func(_ context.Context, s *state.State, infos []*snap.SideInfo, paths []string, userID int, flags *snapstate.Flags) ([]*state.TaskSet, error) {
+ c.Check(flags, check.DeepEquals, expectedFlags)
+ c.Check(userID, check.Not(check.Equals), 0)
+
+ var tss []*state.TaskSet
+ for i, path := range paths {
+ si := infos[i]
+ c.Check(path, testutil.FileEquals, si.RealName)
+
+ ts := state.NewTaskSet(s.NewTask("fake-install-snap", fmt.Sprintf("Doing a fake install of %q", si.RealName)))
+ tss = append(tss, ts)
+ }
+
+ return tss, nil
+ })
+ defer restore()
+
+ snaps := []string{"one", "two"}
+ var i int
+ readRest := daemon.MockUnsafeReadSnapInfo(func(string) (*snap.Info, error) {
+ info := &snap.Info{SuggestedName: snaps[i]}
+ i++
+ return info, nil
+ })
+ defer readRest()
+
+ body := "----hello--\r\n" +
+ "Content-Disposition: form-data; name=\"devmode\"\r\n" +
+ "\r\n" +
+ "true\r\n" +
+ "----hello--\r\n"
+ prefixed := make([]string, len(snaps))
+ for i, snap := range snaps {
+ prefixed[i] = "file-" + snap
+ body += "Content-Disposition: form-data; name=\"snap\"; filename=\"" + prefixed[i] + "\"\r\n" +
+ "\r\n" +
+ snap + "\r\n" +
+ "----hello--\r\n"
+ }
+
+ req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body))
+ c.Assert(err, check.IsNil)
+ req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--")
+ s.asUserAuth(c, req)
+ rsp := s.asyncReq(c, req, s.authUser)
+
+ st := d.Overlord().State()
+ st.Lock()
+ defer st.Unlock()
+
+ chg := st.Change(rsp.Change)
+ c.Assert(chg, check.NotNil)
+ c.Check(chg.Summary(), check.Equals, fmt.Sprintf(`Install snaps %s from files %s`, strutil.Quoted(snaps), strutil.Quoted(prefixed)))
+
+ var data map[string][]string
+ c.Assert(chg.Get("api-data", &data), check.IsNil)
+ c.Check(data["snap-names"], check.DeepEquals, snaps)
+}
+
+func (s *sideloadSuite) TestSideloadManyFailInstallPathMany(c *check.C) {
+ s.daemon(c)
+ restore := daemon.MockSnapstateInstallPathMany(func(_ context.Context, s *state.State, infos []*snap.SideInfo, paths []string, userID int, flags *snapstate.Flags) ([]*state.TaskSet, error) {
+ return nil, errors.New("expected")
+ })
+ defer restore()
+
+ readRest := daemon.MockUnsafeReadSnapInfo(func(string) (*snap.Info, error) {
+ return &snap.Info{SuggestedName: "name"}, nil
+ })
+ defer readRest()
+
+ body := "----hello--\r\n" +
+ "Content-Disposition: form-data; name=\"devmode\"\r\n" +
+ "\r\n" +
+ "true\r\n" +
+ "----hello--\r\n"
+ for _, snap := range []string{"one", "two"} {
+ body += "Content-Disposition: form-data; name=\"snap\"; filename=\"file-" + snap + "\"\r\n" +
+ "\r\n" +
+ "xyzzy \r\n" +
+ "----hello--\r\n"
+ }
+
+ req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body))
+ c.Assert(err, check.IsNil)
+ req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--")
+ apiErr := s.errorReq(c, req, nil)
+
+ c.Check(apiErr.JSON().Status, check.Equals, 500)
+ c.Check(apiErr.Message, check.Equals, `cannot install snap files: expected`)
+}
+
+func (s *sideloadSuite) TestSideloadManyFailUnsafeReadInfo(c *check.C) {
+ s.daemon(c)
+ restore := daemon.MockUnsafeReadSnapInfo(func(string) (*snap.Info, error) {
+ return nil, errors.New("expected")
+ })
+ defer restore()
+
+ body := "----hello--\r\n" +
+ "Content-Disposition: form-data; name=\"devmode\"\r\n" +
+ "\r\n" +
+ "true\r\n" +
+ "----hello--\r\n"
+ for _, snap := range []string{"one", "two"} {
+ body += "Content-Disposition: form-data; name=\"snap\"; filename=\"file-" + snap + "\"\r\n" +
+ "\r\n" +
+ "xyzzy \r\n" +
+ "----hello--\r\n"
+ }
+
+ req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body))
+ c.Assert(err, check.IsNil)
+ req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--")
+ apiErr := s.errorReq(c, req, nil)
+
+ c.Check(apiErr.JSON().Status, check.Equals, 400)
+ c.Check(apiErr.Message, check.Equals, `cannot read snap file: expected`)
+}
+
+func (s *sideloadSuite) TestSideloadManySnapsDevmode(c *check.C) {
+ body := "----hello--\r\n" +
+ "Content-Disposition: form-data; name=\"devmode\"\r\n" +
+ "\r\n" +
+ "true\r\n" +
+ "----hello--\r\n"
+
+ s.errReadInfo(c, body)
+}
+
+func (s *sideloadSuite) TestSideloadManySnapsDangerous(c *check.C) {
+ body := "----hello--\r\n" +
+ "Content-Disposition: form-data; name=\"dangerous\"\r\n" +
+ "\r\n" +
+ "true\r\n" +
+ "----hello--\r\n"
+
+ s.errReadInfo(c, body)
+}
+
+func (s *sideloadSuite) errReadInfo(c *check.C, body string) {
+ s.daemon(c)
+
+ for _, snap := range []string{"one", "two"} {
+ body += "Content-Disposition: form-data; name=\"snap\"; filename=\"" + snap + "\"\r\n" +
+ "\r\n" +
+ snap + "\r\n" +
+ "----hello--\r\n"
+ }
+
+ req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body))
+ c.Assert(err, check.IsNil)
+ req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--")
+ rsp := s.errorReq(c, req, nil)
+
+ c.Assert(rsp.Status, check.Equals, 400)
+ // gets as far as reading the file to get the SideInfo
+ c.Assert(rsp.Message, check.Matches, "cannot read snap file:.*")
+}
+
+func (s *sideloadSuite) TestSideloadManySnapsAsserted(c *check.C) {
+ d := s.daemonWithOverlordMockAndStore()
+ st := d.Overlord().State()
+ snaps := []string{"one", "two"}
+ s.mockAssertions(c, st, snaps)
+
+ body := "----hello--\r\n"
+ expectedFlags := snapstate.Flags{RemoveSnapPath: true}
+ s.testSideloadManySnaps(c, st, body, snaps, expectedFlags)
+}
+
+func (s *sideloadSuite) TestSideloadManySnapsOneNotAsserted(c *check.C) {
+ d := s.daemonWithOverlordMockAndStore()
+ st := d.Overlord().State()
+ snaps := []string{"one", "two"}
+ s.mockAssertions(c, st, []string{"one"})
+
+ body := "----hello--\r\n"
+
+ fileSnaps := make([]string, len(snaps))
+ for i, snap := range snaps {
+ fileSnaps[i] = "file-" + snap
+ body += "Content-Disposition: form-data; name=\"snap\"; filename=\"" + fileSnaps[i] + "\"\r\n" +
+ "\r\n" +
+ snap + "\r\n" +
+ "----hello--\r\n"
+ }
+
+ req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body))
+ c.Assert(err, check.IsNil)
+ req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--")
+ rsp := s.errorReq(c, req, nil)
+
+ c.Check(rsp.Status, check.Equals, 400)
+ c.Check(rsp.Message, check.Matches, "cannot find signatures with metadata for snap \"file-two\"")
+}
+
+func (s *sideloadSuite) mockAssertions(c *check.C, st *state.State, snaps []string) {
+ for _, snap := range snaps {
+ hash := crypto.SHA3_384.New()
+ data := []byte(snap)
+ hash.Write(data)
+ digest := hash.Sum(nil)
+
+ base64Digest, err := asserts.EncodeDigest(crypto.SHA3_384, digest)
+ c.Assert(err, check.IsNil)
+ dev1Acct := assertstest.NewAccount(s.StoreSigning, "devel1", nil, "")
+ snapDecl, err := s.StoreSigning.Sign(asserts.SnapDeclarationType, map[string]interface{}{
+ "series": "16",
+ "snap-id": snap + "-id",
+ "snap-name": snap,
+ "publisher-id": dev1Acct.AccountID(),
+ "timestamp": time.Now().Format(time.RFC3339),
+ }, nil, "")
+ c.Assert(err, check.IsNil)
+ snapRev, err := s.StoreSigning.Sign(asserts.SnapRevisionType, map[string]interface{}{
+ "snap-sha3-384": base64Digest,
+ "snap-size": strconv.Itoa(len(data)),
+ "snap-id": snap + "-id",
+ "snap-revision": "41",
+ "developer-id": dev1Acct.AccountID(),
+ "timestamp": time.Now().Format(time.RFC3339),
+ }, nil, "")
+ c.Assert(err, check.IsNil)
+
+ st.Lock()
+ assertstatetest.AddMany(st, s.StoreSigning.StoreAccountKey(""), dev1Acct, snapDecl, snapRev)
+ st.Unlock()
+ }
+}
+
+func (s *sideloadSuite) testSideloadManySnaps(c *check.C, st *state.State, body string, snaps []string, expectedFlags snapstate.Flags) {
+ restore := daemon.MockSnapstateInstallPathMany(func(_ context.Context, s *state.State, infos []*snap.SideInfo, paths []string, userID int, flags *snapstate.Flags) ([]*state.TaskSet, error) {
+ c.Check(*flags, check.DeepEquals, expectedFlags)
+
+ var tss []*state.TaskSet
+ for i, si := range infos {
+ c.Check(si, check.DeepEquals, &snap.SideInfo{
+ RealName: snaps[i],
+ SnapID: snaps[i] + "-id",
+ Revision: snap.R(41),
+ })
+
+ ts := state.NewTaskSet(s.NewTask("fake-install-snap", fmt.Sprintf("Doing a fake install of %q", si.RealName)))
+ tss = append(tss, ts)
+ }
+
+ return tss, nil
+ })
+ defer restore()
+
+ fileSnaps := make([]string, len(snaps))
+ for i, snap := range snaps {
+ fileSnaps[i] = "file-" + snap
+ body += "Content-Disposition: form-data; name=\"snap\"; filename=\"" + fileSnaps[i] + "\"\r\n" +
+ "\r\n" +
+ snap + "\r\n" +
+ "----hello--\r\n"
+ }
+
+ req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body))
+ c.Assert(err, check.IsNil)
+ req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--")
+ rsp := s.asyncReq(c, req, nil)
+
+ c.Check(rsp.Status, check.Equals, 202)
+ st.Lock()
+ defer st.Unlock()
+ chg := st.Change(rsp.Change)
+ c.Assert(chg, check.NotNil)
+ c.Check(chg.Summary(), check.Equals, fmt.Sprintf(`Install snaps %s from files %s`, strutil.Quoted(snaps), strutil.Quoted(fileSnaps)))
+}
+
type trySuite struct {
apiBaseSuite
}
diff --git a/daemon/api_snaps.go b/daemon/api_snaps.go
index 0089d14ae8..f84ce706d6 100644
--- a/daemon/api_snaps.go
+++ b/daemon/api_snaps.go
@@ -487,7 +487,7 @@ func postSnaps(c *Command, r *http.Request, user *auth.UserState) Response {
return BadRequest("unknown content type: %s", contentType)
}
- return sideloadOrTrySnap(c, r.Body, params["boundary"])
+ return sideloadOrTrySnap(c, r.Body, params["boundary"], user)
}
func snapOpMany(c *Command, r *http.Request, user *auth.UserState) Response {
@@ -607,6 +607,11 @@ func snapUpdateMany(inst *snapInstruction, st *state.State) (*snapInstructionRes
// TODO: use a per-request context
updated, tasksets, err := snapstateUpdateMany(context.TODO(), st, inst.Snaps, inst.userID, nil)
if err != nil {
+ if opts.IsRefreshOfAllSnaps {
+ if err := assertstateRestoreValidationSetsTracking(st); err != nil && !errors.Is(err, state.ErrNoState) {
+ return nil, err
+ }
+ }
return nil, err
}
diff --git a/daemon/api_snaps_test.go b/daemon/api_snaps_test.go
index a9c12444b9..9c4131befa 100644
--- a/daemon/api_snaps_test.go
+++ b/daemon/api_snaps_test.go
@@ -596,6 +596,35 @@ func (s *snapsSuite) TestRefreshAllNoChanges(c *check.C) {
c.Check(refreshSnapAssertions, check.Equals, true)
}
+func (s *snapsSuite) TestRefreshAllRestoresValidationSets(c *check.C) {
+ refreshSnapAssertions := false
+ var refreshAssertionsOpts *assertstate.RefreshAssertionsOptions
+ defer daemon.MockAssertstateRefreshSnapAssertions(func(s *state.State, userID int, opts *assertstate.RefreshAssertionsOptions) error {
+ refreshSnapAssertions = true
+ refreshAssertionsOpts = opts
+ return nil
+ })()
+
+ defer daemon.MockAssertstateRestoreValidationSetsTracking(func(s *state.State) error {
+ return nil
+ })()
+
+ defer daemon.MockSnapstateUpdateMany(func(_ context.Context, s *state.State, names []string, userID int, flags *snapstate.Flags) ([]string, []*state.TaskSet, error) {
+ return nil, nil, fmt.Errorf("boom")
+ })()
+
+ d := s.daemon(c)
+ inst := &daemon.SnapInstruction{Action: "refresh"}
+ st := d.Overlord().State()
+ st.Lock()
+ _, err := inst.DispatchForMany()(inst, st)
+ st.Unlock()
+ c.Assert(err, check.ErrorMatches, "boom")
+ c.Check(refreshSnapAssertions, check.Equals, true)
+ c.Assert(refreshAssertionsOpts, check.NotNil)
+ c.Check(refreshAssertionsOpts.IsRefreshOfAllSnaps, check.Equals, true)
+}
+
func (s *snapsSuite) TestRefreshMany(c *check.C) {
refreshSnapAssertions := false
var refreshAssertionsOpts *assertstate.RefreshAssertionsOptions
diff --git a/daemon/export_api_snaps_test.go b/daemon/export_api_snaps_test.go
index 61173ecf87..1a4322d90d 100644
--- a/daemon/export_api_snaps_test.go
+++ b/daemon/export_api_snaps_test.go
@@ -21,6 +21,7 @@ package daemon
import (
"github.com/snapcore/snapd/overlord/snapstate"
+ "github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/snap"
)
@@ -31,3 +32,11 @@ func MakeAboutSnap(info *snap.Info, snapst *snapstate.SnapState) aboutSnap {
var (
MapLocal = mapLocal
)
+
+func MockAssertstateRestoreValidationSetsTracking(f func(*state.State) error) (restore func()) {
+ old := assertstateRestoreValidationSetsTracking
+ assertstateRestoreValidationSetsTracking = f
+ return func() {
+ assertstateRestoreValidationSetsTracking = old
+ }
+}
diff --git a/daemon/export_test.go b/daemon/export_test.go
index 051aa8e8f5..fcd53c5e2d 100644
--- a/daemon/export_test.go
+++ b/daemon/export_test.go
@@ -195,6 +195,14 @@ func MockSnapstateRemoveMany(mock func(*state.State, []string) ([]string, []*sta
}
}
+func MockSnapstateInstallPathMany(f func(context.Context, *state.State, []*snap.SideInfo, []string, int, *snapstate.Flags) ([]*state.TaskSet, error)) func() {
+ old := snapstateInstallPathMany
+ snapstateInstallPathMany = f
+ return func() {
+ snapstateInstallPathMany = old
+ }
+}
+
type (
RespJSON = respJSON
FileResponse = fileResponse
diff --git a/data/env/snapd.fish.in b/data/env/snapd.fish.in
index ca7c8358ad..f2a6591249 100644
--- a/data/env/snapd.fish.in
+++ b/data/env/snapd.fish.in
@@ -1,8 +1,6 @@
# Expand $PATH to include the directory where snappy applications go.
set -u snap_bin_path "@SNAP_MOUNT_DIR@/bin"
-if ! contains $snap_bin_path $PATH
- set PATH $PATH $snap_bin_path
-end
+fish_add_path -aP $snap_bin_path
# Desktop files (used by desktop environments within both X11 and Wayland) are
# looked for in XDG_DATA_DIRS; make sure it includes the relevant directory for
diff --git a/data/selinux/snappy.te b/data/selinux/snappy.te
index f463baba4c..948280ae7d 100644
--- a/data/selinux/snappy.te
+++ b/data/selinux/snappy.te
@@ -350,6 +350,11 @@ ifndef(`distro_rhel7',`
timedatex_dbus_chat(snappy_t)
')
+# kernel-module-load interface may inspect or write files under /etc/modprobe.d
+optional_policy(`
+ modutils_manage_module_config(snappy_t)
+')
+
# only pops up in cloud images where cloud-init.target is incorrectly labeled
allow snappy_t init_var_run_t:lnk_file read;
@@ -432,6 +437,10 @@ ifndef(`distro_rhel7',`
allow snappy_t snappy_cli_t:process { getpgid sigkill };
allow snappy_t unconfined_service_t:process { getpgid sigkill };
+# Snapd invokes systemd-detect-virt, which may make poke /proc/xen/, but does
+# not transition to a separate type and has no interface policy
+kernel_read_xen_state(snappy_t)
+
########################################
#
# snap-update-ns, snap-dicsard-ns local policy
diff --git a/debian b/debian
index d79cbbd542..937dd5365b 120000
--- a/debian
+++ b/debian
@@ -1 +1 @@
-packaging/ubuntu-16.04/ \ No newline at end of file
+packaging/ubuntu-16.04 \ No newline at end of file
diff --git a/dirs/dirs.go b/dirs/dirs.go
index d25ef34e08..676ce36373 100644
--- a/dirs/dirs.go
+++ b/dirs/dirs.go
@@ -51,6 +51,7 @@ var (
SnapMountPolicyDir string
SnapUdevRulesDir string
SnapKModModulesDir string
+ SnapKModModprobeDir string
LocaleDir string
SnapMetaDir string
SnapdSocket string
@@ -266,6 +267,12 @@ func SnapSystemdConfDirUnder(rootdir string) string {
return filepath.Join(rootdir, "/etc/systemd/system.conf.d")
}
+// SnapSystemdConfDirUnder returns the path to the systemd conf dir under
+// rootdir.
+func SnapServicesDirUnder(rootdir string) string {
+ return filepath.Join(rootdir, "/etc/systemd/system")
+}
+
// SnapBootAssetsDirUnder returns the path to boot assets directory under a
// rootdir.
func SnapBootAssetsDirUnder(rootdir string) string {
@@ -407,6 +414,7 @@ func SetRootDir(rootdir string) {
SnapUdevRulesDir = filepath.Join(rootdir, "/etc/udev/rules.d")
SnapKModModulesDir = filepath.Join(rootdir, "/etc/modules-load.d/")
+ SnapKModModprobeDir = filepath.Join(rootdir, "/etc/modprobe.d/")
LocaleDir = filepath.Join(rootdir, "/usr/share/locale")
ClassicDir = filepath.Join(rootdir, "/writable/classic")
diff --git a/gadget/gadget.go b/gadget/gadget.go
index 54df9b4ba8..fcaaaecd90 100644
--- a/gadget/gadget.go
+++ b/gadget/gadget.go
@@ -462,7 +462,11 @@ func InfoFromGadgetYaml(gadgetYaml []byte, model Model) (*Info, error) {
// basic validation
var bootloadersFound int
knownFsLabelsPerVolume := make(map[string]map[string]bool, len(gi.Volumes))
- for name, v := range gi.Volumes {
+ for name := range gi.Volumes {
+ v := gi.Volumes[name]
+ if v == nil {
+ return nil, fmt.Errorf("volume %q stanza is empty", name)
+ }
// set the VolumeName for the volume
v.Name = name
if err := validateVolume(v, knownFsLabelsPerVolume); err != nil {
diff --git a/gadget/gadget_test.go b/gadget/gadget_test.go
index 0a2fea054e..fb61376fc7 100644
--- a/gadget/gadget_test.go
+++ b/gadget/gadget_test.go
@@ -621,6 +621,19 @@ func (s *gadgetYamlTestSuite) TestCoreConfigDefaults(c *C) {
})
}
+var mockGadgetWithEmptyVolumes = `device-tree-origin: kernel
+volumes:
+ lun-0:
+`
+
+func (s *gadgetYamlTestSuite) TestRegressionGadgetWithEmptyVolume(c *C) {
+ err := ioutil.WriteFile(s.gadgetYamlPath, []byte(mockGadgetWithEmptyVolumes), 0644)
+ c.Assert(err, IsNil)
+
+ _, err = gadget.ReadInfo(s.dir, nil)
+ c.Assert(err, ErrorMatches, `volume "lun-0" stanza is empty`)
+}
+
func (s *gadgetYamlTestSuite) TestReadGadgetDefaultsMultiline(c *C) {
err := ioutil.WriteFile(s.gadgetYamlPath, mockClassicGadgetMultilineDefaultsYaml, 0644)
c.Assert(err, IsNil)
diff --git a/gadget/install/encrypt.go b/gadget/install/encrypt.go
index b3a44b77b7..ac10571680 100644
--- a/gadget/install/encrypt.go
+++ b/gadget/install/encrypt.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !nosecboot
// +build !nosecboot
/*
diff --git a/gadget/install/encrypt_test.go b/gadget/install/encrypt_test.go
index 290a20b2b3..a43e8b1145 100644
--- a/gadget/install/encrypt_test.go
+++ b/gadget/install/encrypt_test.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !nosecboot
// +build !nosecboot
/*
diff --git a/gadget/install/export_secboot_test.go b/gadget/install/export_secboot_test.go
index 257c2e7cf2..b21824f5c0 100644
--- a/gadget/install/export_secboot_test.go
+++ b/gadget/install/export_secboot_test.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !nosecboot
// +build !nosecboot
/*
diff --git a/gadget/install/install.go b/gadget/install/install.go
index b44599c4f8..f8424e35ca 100644
--- a/gadget/install/install.go
+++ b/gadget/install/install.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !nosecboot
// +build !nosecboot
/*
diff --git a/gadget/install/install_dummy.go b/gadget/install/install_dummy.go
index 6149b916f8..2698d5ee47 100644
--- a/gadget/install/install_dummy.go
+++ b/gadget/install/install_dummy.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build nosecboot
// +build nosecboot
/*
diff --git a/gadget/install/install_test.go b/gadget/install/install_test.go
index 4dc479d0c6..8acfeba9f2 100644
--- a/gadget/install/install_test.go
+++ b/gadget/install/install_test.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !nosecboot
// +build !nosecboot
/*
diff --git a/gadget/install/mount_other.go b/gadget/install/mount_other.go
index fedfdca6ce..c3ec6022c8 100644
--- a/gadget/install/mount_other.go
+++ b/gadget/install/mount_other.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !linux
// +build !linux
/*
diff --git a/gadget/install/partition.go b/gadget/install/partition.go
index 4fc6719df3..a13b1ec318 100644
--- a/gadget/install/partition.go
+++ b/gadget/install/partition.go
@@ -66,6 +66,13 @@ func createMissingPartitions(dl *gadget.OnDiskVolume, pv *gadget.LaidOutVolume)
return nil, err
}
+ // run udevadm settle to wait for udev events that may have been triggered
+ // by reloading the partition table to be processed, as we need the udev
+ // database to be freshly updated
+ if out, err := exec.Command("udevadm", "settle", "--timeout=180").CombinedOutput(); err != nil {
+ return nil, fmt.Errorf("cannot wait for udev to settle after reloading partition table: %v", osutil.OutputErr(out, err))
+ }
+
// Make sure the devices for the partitions we created are available
if err := ensureNodesExist(created, 5*time.Second); err != nil {
return nil, fmt.Errorf("partition not available: %v", err)
@@ -177,42 +184,57 @@ func deviceName(name string, index int) string {
// removeCreatedPartitions removes partitions added during a previous install.
func removeCreatedPartitions(lv *gadget.LaidOutVolume, dl *gadget.OnDiskVolume) error {
- indexes := make([]string, 0, len(dl.Structure))
+ sfdiskIndexes := make([]string, 0, len(dl.Structure))
+ // up to 3 possible partitions are creatable and thus removable:
+ // ubuntu-data, ubuntu-boot, and ubuntu-save
+ deletedIndexes := make(map[int]bool, 3)
for i, s := range dl.Structure {
if wasCreatedDuringInstall(lv, s) {
logger.Noticef("partition %s was created during previous install", s.Node)
- indexes = append(indexes, strconv.Itoa(i+1))
+ sfdiskIndexes = append(sfdiskIndexes, strconv.Itoa(i+1))
+ deletedIndexes[i] = true
}
}
- if len(indexes) == 0 {
+ if len(sfdiskIndexes) == 0 {
return nil
}
// Delete disk partitions
- logger.Debugf("delete disk partitions %v", indexes)
- cmd := exec.Command("sfdisk", append([]string{"--no-reread", "--delete", dl.Device}, indexes...)...)
+ logger.Debugf("delete disk partitions %v", sfdiskIndexes)
+ cmd := exec.Command("sfdisk", append([]string{"--no-reread", "--delete", dl.Device}, sfdiskIndexes...)...)
if output, err := cmd.CombinedOutput(); err != nil {
return osutil.OutputErr(output, err)
}
- // Reload the partition table
+ // Reload the partition table - note that this specifically does not trigger
+ // udev events to remove the deleted devices, see the doc-comment in
+ // reloadPartitionTable for more details
if err := reloadPartitionTable(dl.Device); err != nil {
return err
}
- // run udevadm settle to wait for udev events that may have been triggered
- // by reloading the partition table to be processed, as we need the udev
- // database to be freshly updated and complete before updating the partition
- // information for the OnDiskVolume
- // TODO: is 3 minute timeout reasonable for this?
- if out, err := exec.Command("udevadm", "settle", "--timeout=180").CombinedOutput(); err != nil {
- return fmt.Errorf("cannot wait for udev to settle after reloading partition table: %v", osutil.OutputErr(out, err))
+ // Remove the partitions we deleted from the OnDiskVolume - note that we
+ // specifically don't try to just re-build the OnDiskVolume since doing
+ // so correctly requires using only information from the partition table
+ // we just updated with sfdisk (since we used --no-reread above, and we can't
+ // really tell the kernel to re-read the partition table without hitting
+ // EBUSY as the disk is still mounted even though the deleted partitions
+ // were deleted), but to do so would essentially just be testing that sfdisk
+ // updated the partition table in a way we expect. The partition parsing
+ // code we use to build the OnDiskVolume also must not be reliant on using
+ // sfdisk (since it has to work in the initrd where we don't have sfdisk),
+ // so either that code would just be a duplication of what sfdisk is doing
+ // or that code would fail to update the deleted partitions anyways since
+ // at this point the only thing that knows about the deleted partitions is
+ // the physical partition table on the disk.
+ newStructure := make([]gadget.OnDiskStructure, 0, len(dl.Structure)-len(deletedIndexes))
+ for i, structure := range dl.Structure {
+ if !deletedIndexes[i] {
+ newStructure = append(newStructure, structure)
+ }
}
- // Re-read the partition table from the device to update our partition list
- if err := gadget.UpdatePartitionList(dl); err != nil {
- return err
- }
+ dl.Structure = newStructure
// Ensure all created partitions were removed
if remaining := createdDuringInstall(lv, dl); len(remaining) > 0 {
diff --git a/gadget/install/partition_test.go b/gadget/install/partition_test.go
index 7d007002d0..92733a1862 100644
--- a/gadget/install/partition_test.go
+++ b/gadget/install/partition_test.go
@@ -264,6 +264,9 @@ func (s *partitionTestSuite) TestCreatePartitions(c *C) {
restore := disks.MockDeviceNameToDiskMapping(m)
defer restore()
+ cmdUdevadm := testutil.MockCommand(c, "udevadm", "")
+ defer cmdUdevadm.Restore()
+
calls := 0
restore = install.MockEnsureNodesExist(func(ds []gadget.OnDiskStructure, timeout time.Duration) error {
calls++
@@ -294,6 +297,10 @@ func (s *partitionTestSuite) TestCreatePartitions(c *C) {
c.Assert(s.cmdPartx.Calls(), DeepEquals, [][]string{
{"partx", "-u", "/dev/node"},
})
+
+ c.Assert(cmdUdevadm.Calls(), DeepEquals, [][]string{
+ {"udevadm", "settle", "--timeout=180"},
+ })
}
func (s *partitionTestSuite) TestRemovePartitionsTrivial(c *C) {
@@ -320,12 +327,8 @@ func (s *partitionTestSuite) TestRemovePartitionsTrivial(c *C) {
func (s *partitionTestSuite) TestRemovePartitions(c *C) {
m := map[string]*disks.MockDiskMapping{
"/dev/node": {
- DevNum: "42:0",
- // this is so that the updated version will be found after we delete
- // the partitions and reload the partition table
- // XXX: this is a bit of a hack but is easier than mocking every
- // individual call to find a disk in order
- DevNode: "/dev/updated-node",
+ DevNum: "42:0",
+ DevNode: "/dev/node",
// assume GPT backup header section is 34 sectors long
DiskSizeInBytes: (8388574 + 34) * 512,
DiskUsableSectorEnd: 8388574 + 1,
@@ -375,29 +378,6 @@ func (s *partitionTestSuite) TestRemovePartitions(c *C) {
},
},
},
- "/dev/updated-node": {
- DevNum: "42:0",
- DevNode: "/dev/updated-node",
- DiskSizeInBytes: (8388574 + 34) * 512,
- DiskUsableSectorEnd: 8388574 + 1,
- DiskSchema: "gpt",
- ID: "9151F25B-CDF0-48F1-9EDE-68CBD616E2CA",
- SectorSizeBytes: 512,
- Structure: []disks.Partition{
- // only the first partition
- {
- KernelDeviceNode: "/dev/node1",
- StartInBytes: 2048 * 512,
- SizeInBytes: 2048 * 512,
- PartitionType: "21686148-6449-6E6F-744E-656564454649",
- PartitionUUID: "2E59D969-52AB-430B-88AC-F83873519F6F",
- PartitionLabel: "BIOS Boot",
- Major: 42,
- Minor: 1,
- StructureIndex: 1,
- },
- },
- },
}
restore := disks.MockDeviceNameToDiskMapping(m)
@@ -421,22 +401,84 @@ func (s *partitionTestSuite) TestRemovePartitions(c *C) {
c.Assert(err, IsNil)
c.Assert(cmdSfdisk.Calls(), DeepEquals, [][]string{
- {"sfdisk", "--no-reread", "--delete", "/dev/updated-node", "3"},
+ {"sfdisk", "--no-reread", "--delete", "/dev/node", "3"},
})
- c.Assert(cmdUdevadm.Calls(), DeepEquals, [][]string{
- {"udevadm", "settle", "--timeout=180"},
+ // check that the OnDiskVolume was updated as expected
+ c.Assert(dl.Structure, DeepEquals, []gadget.OnDiskStructure{
+ {
+ LaidOutStructure: gadget.LaidOutStructure{
+ VolumeStructure: &gadget.VolumeStructure{
+ Name: "BIOS Boot",
+ Size: 1024 * 1024,
+ Type: "21686148-6449-6E6F-744E-656564454649",
+ ID: "2E59D969-52AB-430B-88AC-F83873519F6F",
+ },
+ StartOffset: 1024 * 1024,
+ Index: 1,
+ },
+ Node: "/dev/node1",
+ Size: 1024 * 1024,
+ },
+ {
+ LaidOutStructure: gadget.LaidOutStructure{
+ VolumeStructure: &gadget.VolumeStructure{
+ Label: "ubuntu-seed",
+ Name: "Recovery",
+ Size: 2457600 * 512,
+ Type: "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
+ ID: "44C3D5C3-CAE1-4306-83E8-DF437ACDB32F",
+ Filesystem: "vfat",
+ },
+
+ StartOffset: 1024*1024 + 1024*1024,
+ Index: 2,
+ },
+
+ Node: "/dev/node2",
+ Size: 2457600 * 512,
+ },
})
}
-func (s *partitionTestSuite) TestRemovePartitionsDoesNotRemoveError(c *C) {
- cmdSfdisk := testutil.MockCommand(c, "sfdisk", "")
- defer cmdSfdisk.Restore()
+const gadgetContentDifferentOrder = `volumes:
+ pc:
+ bootloader: grub
+ structure:
+ - name: mbr
+ type: mbr
+ size: 440
+ content:
+ - image: pc-boot.img
+ - name: BIOS Boot
+ type: DA,21686148-6449-6E6F-744E-656564454649
+ size: 1M
+ offset: 1M
+ offset-write: mbr+92
+ content:
+ - image: pc-core.img
+ - name: Writable
+ role: system-data
+ filesystem: ext4
+ type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
+ size: 1200M
+ - name: Recovery
+ role: system-seed
+ filesystem: vfat
+ # UEFI will boot the ESP partition by default first
+ type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B
+ size: 1200M
+ content:
+ - source: grubx64.efi
+ target: EFI/boot/grubx64.efi
+`
+func (s *partitionTestSuite) TestRemovePartitionsNonAdjacent(c *C) {
m := map[string]*disks.MockDiskMapping{
"/dev/node": {
- DevNum: "42:0",
- DevNode: "/dev/node",
+ DevNum: "42:0",
+ DevNode: "/dev/node",
+ // assume GPT backup header section is 34 sectors long
DiskSizeInBytes: (8388574 + 34) * 512,
DiskUsableSectorEnd: 8388574 + 1,
DiskSchema: "gpt",
@@ -446,7 +488,7 @@ func (s *partitionTestSuite) TestRemovePartitionsDoesNotRemoveError(c *C) {
// all 3 partitions present
{
KernelDeviceNode: "/dev/node1",
- StartInBytes: 2048 * 512,
+ StartInBytes: 1024 * 1024,
SizeInBytes: 2048 * 512,
PartitionType: "21686148-6449-6E6F-744E-656564454649",
PartitionUUID: "2E59D969-52AB-430B-88AC-F83873519F6F",
@@ -457,31 +499,31 @@ func (s *partitionTestSuite) TestRemovePartitionsDoesNotRemoveError(c *C) {
},
{
KernelDeviceNode: "/dev/node2",
- StartInBytes: 4096 * 512,
+ StartInBytes: 1024*1024 + 1024*1024,
SizeInBytes: 2457600 * 512,
- PartitionType: "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
- PartitionUUID: "44C3D5C3-CAE1-4306-83E8-DF437ACDB32F",
- PartitionLabel: "Recovery",
+ PartitionType: "0FC63DAF-8483-4772-8E79-3D69D8477DE4",
+ PartitionUUID: "F940029D-BFBB-4887-9D44-321E85C63866",
+ PartitionLabel: "Writable",
Major: 42,
Minor: 2,
StructureIndex: 2,
- FilesystemType: "vfat",
- FilesystemUUID: "A644-B807",
- FilesystemLabel: "ubuntu-seed",
+ FilesystemType: "ext4",
+ FilesystemUUID: "8781-433a",
+ FilesystemLabel: "ubuntu-data",
},
{
KernelDeviceNode: "/dev/node3",
- StartInBytes: 2461696 * 512,
+ StartInBytes: 1024*1024 + 1024*1024 + 2457600*512,
SizeInBytes: 2457600 * 512,
- PartitionType: "0FC63DAF-8483-4772-8E79-3D69D8477DE4",
- PartitionUUID: "F940029D-BFBB-4887-9D44-321E85C63866",
- PartitionLabel: "Writable",
+ PartitionType: "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
+ PartitionUUID: "44C3D5C3-CAE1-4306-83E8-DF437ACDB32F",
+ PartitionLabel: "Recovery",
Major: 42,
Minor: 3,
StructureIndex: 3,
- FilesystemType: "ext4",
- FilesystemUUID: "8781-433a",
- FilesystemLabel: "ubuntu-data",
+ FilesystemType: "vfat",
+ FilesystemUUID: "A644-B807",
+ FilesystemLabel: "ubuntu-seed",
},
},
},
@@ -490,22 +532,61 @@ func (s *partitionTestSuite) TestRemovePartitionsDoesNotRemoveError(c *C) {
restore := disks.MockDeviceNameToDiskMapping(m)
defer restore()
+ cmdSfdisk := testutil.MockCommand(c, "sfdisk", "")
+ defer cmdSfdisk.Restore()
+
+ cmdUdevadm := testutil.MockCommand(c, "udevadm", "")
+ defer cmdUdevadm.Restore()
+
dl, err := gadget.OnDiskVolumeFromDevice("/dev/node")
c.Assert(err, IsNil)
- err = makeMockGadget(s.gadgetRoot, gadgetContent)
+ err = makeMockGadget(s.gadgetRoot, gadgetContentDifferentOrder)
c.Assert(err, IsNil)
pv, err := gadgettest.MustLayOutSingleVolumeFromGadget(s.gadgetRoot, "", uc20Mod)
c.Assert(err, IsNil)
- cmdUdevadm := testutil.MockCommand(c, "udevadm", "")
- defer cmdUdevadm.Restore()
-
err = install.RemoveCreatedPartitions(pv, dl)
- c.Assert(err, ErrorMatches, "cannot remove partitions: /dev/node3")
+ c.Assert(err, IsNil)
- c.Assert(cmdUdevadm.Calls(), DeepEquals, [][]string{
- {"udevadm", "settle", "--timeout=180"},
+ c.Assert(cmdSfdisk.Calls(), DeepEquals, [][]string{
+ {"sfdisk", "--no-reread", "--delete", "/dev/node", "2"},
+ })
+
+ // check that the OnDiskVolume was updated as expected
+ c.Assert(dl.Structure, DeepEquals, []gadget.OnDiskStructure{
+ {
+ LaidOutStructure: gadget.LaidOutStructure{
+ VolumeStructure: &gadget.VolumeStructure{
+ Name: "BIOS Boot",
+ Size: 1024 * 1024,
+ Type: "21686148-6449-6E6F-744E-656564454649",
+ ID: "2E59D969-52AB-430B-88AC-F83873519F6F",
+ },
+ StartOffset: 1024 * 1024,
+ Index: 1,
+ },
+ Node: "/dev/node1",
+ Size: 1024 * 1024,
+ },
+ {
+ LaidOutStructure: gadget.LaidOutStructure{
+ VolumeStructure: &gadget.VolumeStructure{
+ Label: "ubuntu-seed",
+ Name: "Recovery",
+ Size: 2457600 * 512,
+ Type: "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
+ ID: "44C3D5C3-CAE1-4306-83E8-DF437ACDB32F",
+ Filesystem: "vfat",
+ },
+
+ StartOffset: 1024*1024 + 1024*1024 + 2457600*512,
+ Index: 3,
+ },
+
+ Node: "/dev/node3",
+ Size: 2457600 * 512,
+ },
})
}
diff --git a/gadget/ondisk.go b/gadget/ondisk.go
index 52a0ead5b5..88ff3e6121 100644
--- a/gadget/ondisk.go
+++ b/gadget/ondisk.go
@@ -157,18 +157,3 @@ func OnDiskVolumeFromDisk(disk disks.Disk) (*OnDiskVolume, error) {
return dl, nil
}
-
-// UpdatePartitionList re-reads the partitioning data from the device and
-// updates the volume structures in the specified volume.
-func UpdatePartitionList(dl *OnDiskVolume) error {
- layout, err := OnDiskVolumeFromDevice(dl.Device)
- if err != nil {
- return fmt.Errorf("cannot read disk layout: %v", err)
- }
- if dl.ID != layout.ID {
- return fmt.Errorf("partition table IDs don't match")
- }
-
- dl.Structure = layout.Structure
- return nil
-}
diff --git a/gadget/ondisk_test.go b/gadget/ondisk_test.go
index 94c9783653..2d3e302311 100644
--- a/gadget/ondisk_test.go
+++ b/gadget/ondisk_test.go
@@ -432,90 +432,3 @@ func (s *ondiskTestSuite) TestDeviceInfoMBR(c *C) {
},
})
}
-
-func (s *ondiskTestSuite) TestUpdatePartitionList(c *C) {
- // start with a single partition
- m := map[string]*disks.MockDiskMapping{
- "/dev/node": {
- DevNum: "42:0",
- DevNode: "/dev/node",
- DiskSizeInBytes: (8388574 + 1) * 512,
- DiskSchema: "gpt",
- ID: "9151F25B-CDF0-48F1-9EDE-68CBD616E2CA",
- SectorSizeBytes: 512,
- Structure: []disks.Partition{
- {
- KernelDeviceNode: "/dev/node1",
- StartInBytes: 2048 * 512,
- SizeInBytes: 2048 * 512,
- PartitionType: "21686148-6449-6E6F-744E-656564454649",
- PartitionUUID: "2E59D969-52AB-430B-88AC-F83873519F6F",
- PartitionLabel: "BIOS Boot",
- Major: 42,
- Minor: 1,
- StructureIndex: 1,
- },
- },
- },
- }
-
- restore := disks.MockDeviceNameToDiskMapping(m)
- defer restore()
-
- dl, err := gadget.OnDiskVolumeFromDevice("/dev/node")
- c.Assert(err, IsNil)
-
- c.Assert(len(dl.Structure), Equals, 1)
- c.Assert(dl.Structure[0].Node, Equals, "/dev/node1")
-
- // add a partition
- m2 := map[string]*disks.MockDiskMapping{
- "/dev/node": {
- DevNum: "42:0",
- DevNode: "/dev/node",
- DiskSizeInBytes: (8388574 + 1) * 512,
- DiskSchema: "gpt",
- ID: "9151F25B-CDF0-48F1-9EDE-68CBD616E2CA",
- SectorSizeBytes: 512,
- Structure: []disks.Partition{
- {
- KernelDeviceNode: "/dev/node1",
- StartInBytes: 2048 * 512,
- SizeInBytes: 2048 * 512,
- PartitionType: "21686148-6449-6E6F-744E-656564454649",
- PartitionUUID: "2E59D969-52AB-430B-88AC-F83873519F6F",
- PartitionLabel: "BIOS Boot",
- Major: 42,
- Minor: 1,
- StructureIndex: 1,
- },
- {
- KernelDeviceNode: "/dev/node2",
- StartInBytes: 4096 * 512,
- SizeInBytes: 2457600 * 512,
- PartitionType: "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
- PartitionUUID: "44C3D5C3-CAE1-4306-83E8-DF437ACDB32F",
- PartitionLabel: "ubuntu-seed",
- Major: 42,
- Minor: 2,
- StructureIndex: 2,
- FilesystemType: "vfat",
- FilesystemUUID: "A644-B807",
- FilesystemLabel: "ubuntu-seed",
- },
- },
- },
- }
-
- restore = disks.MockDeviceNameToDiskMapping(m2)
- defer restore()
-
- // update the partition list
- err = gadget.UpdatePartitionList(dl)
- c.Assert(err, IsNil)
-
- // check if the partition list was updated
- c.Assert(len(dl.Structure), Equals, 2)
- c.Assert(dl.Structure[0].Node, Equals, "/dev/node1")
- c.Assert(dl.Structure[1].Node, Equals, "/dev/node2")
-}
diff --git a/interfaces/builtin/kernel_module_load.go b/interfaces/builtin/kernel_module_load.go
new file mode 100644
index 0000000000..541804f045
--- /dev/null
+++ b/interfaces/builtin/kernel_module_load.go
@@ -0,0 +1,229 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2021 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package builtin
+
+import (
+ "errors"
+ "fmt"
+ "regexp"
+
+ "github.com/snapcore/snapd/interfaces"
+ "github.com/snapcore/snapd/interfaces/kmod"
+ "github.com/snapcore/snapd/snap"
+)
+
+const kernelModuleLoadSummary = `allows constrained control over kernel module loading`
+
+const kernelModuleLoadBaseDeclarationPlugs = `
+ kernel-module-load:
+ allow-installation: false
+ deny-auto-connection: true
+`
+
+const kernelModuleLoadBaseDeclarationSlots = `
+ kernel-module-load:
+ allow-installation:
+ slot-snap-type:
+ - core
+ deny-connection: true
+`
+
+var modulesAttrTypeError = errors.New(`kernel-module-load "modules" attribute must be a list of dictionaries`)
+
+// kernelModuleLoadInterface allows creating transient and persistent modules
+type kernelModuleLoadInterface struct {
+ commonInterface
+}
+
+type loadOption int
+
+const (
+ loadNone loadOption = iota
+ loadDenied
+ loadOnBoot
+)
+
+type ModuleInfo struct {
+ name string
+ load loadOption
+ options string
+}
+
+var kernelModuleNameRegexp = regexp.MustCompile(`^[-a-zA-Z0-9_]+$`)
+var kernelModuleOptionsRegexp = regexp.MustCompile(`^([a-zA-Z][a-zA-Z0-9_]*(=[[:graph:]]+)? *)+$`)
+
+func enumerateModules(plug interfaces.Attrer, handleModule func(moduleInfo *ModuleInfo) error) error {
+ modulesAttr, ok := plug.Lookup("modules")
+ if !ok {
+ return nil
+ }
+ modules, ok := modulesAttr.([]interface{})
+ if !ok {
+ return modulesAttrTypeError
+ }
+
+ for _, m := range modules {
+ module, ok := m.(map[string]interface{})
+ if !ok {
+ return modulesAttrTypeError
+ }
+
+ name, ok := module["name"].(string)
+ if !ok {
+ return errors.New(`kernel-module-load "name" must be a string`)
+ }
+
+ var load loadOption
+ if loadAttr, found := module["load"]; found {
+ loadString, ok := loadAttr.(string)
+ if !ok {
+ return errors.New(`kernel-module-load "load" must be a string`)
+ }
+
+ switch loadString {
+ case "denied":
+ load = loadDenied
+ case "on-boot":
+ load = loadOnBoot
+ default:
+ return fmt.Errorf(`kernel-module-load "load" value is unrecognized: %q`, loadString)
+ }
+ }
+
+ var options string
+ if optionsAttr, found := module["options"]; found {
+ options, ok = optionsAttr.(string)
+ if !ok {
+ return errors.New(`kernel-module-load "options" must be a string`)
+ }
+ }
+
+ moduleInfo := &ModuleInfo{
+ name: name,
+ load: load,
+ options: options,
+ }
+
+ if err := handleModule(moduleInfo); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func validateNameAttr(name string) error {
+ if !kernelModuleNameRegexp.MatchString(name) {
+ return errors.New(`kernel-module-load "name" attribute is not a valid module name`)
+ }
+
+ return nil
+}
+
+func validateOptionsAttr(moduleInfo *ModuleInfo) error {
+ if moduleInfo.options == "" {
+ return nil
+ }
+
+ if moduleInfo.load == loadDenied {
+ return errors.New(`kernel-module-load "options" attribute incompatible with "load: denied"`)
+ }
+
+ if !kernelModuleOptionsRegexp.MatchString(moduleInfo.options) {
+ return fmt.Errorf(`kernel-module-load "options" attribute contains invalid characters: %q`, moduleInfo.options)
+ }
+
+ return nil
+}
+
+func validateModuleInfo(moduleInfo *ModuleInfo) error {
+ if err := validateNameAttr(moduleInfo.name); err != nil {
+ return err
+ }
+
+ if err := validateOptionsAttr(moduleInfo); err != nil {
+ return err
+ }
+
+ if moduleInfo.options == "" && moduleInfo.load == loadNone {
+ return errors.New(`kernel-module-load: must specify at least "load" or "options"`)
+ }
+
+ return nil
+}
+
+func (iface *kernelModuleLoadInterface) BeforeConnectPlug(plug *interfaces.ConnectedPlug) error {
+ numModulesEntries := 0
+ err := enumerateModules(plug, func(moduleInfo *ModuleInfo) error {
+ numModulesEntries++
+ return validateModuleInfo(moduleInfo)
+ })
+ if err != nil {
+ return err
+ }
+
+ if numModulesEntries == 0 {
+ return modulesAttrTypeError
+ }
+
+ return nil
+}
+
+func (iface *kernelModuleLoadInterface) KModConnectedPlug(spec *kmod.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
+ err := enumerateModules(plug, func(moduleInfo *ModuleInfo) error {
+ var err error
+ switch moduleInfo.load {
+ case loadDenied:
+ err = spec.DisallowModule(moduleInfo.name)
+ case loadOnBoot:
+ err = spec.AddModule(moduleInfo.name)
+ if err != nil {
+ break
+ }
+ fallthrough
+ case loadNone:
+ if len(moduleInfo.options) > 0 {
+ err = spec.SetModuleOptions(moduleInfo.name, moduleInfo.options)
+ }
+ default:
+ // we can panic, this will be catched on validation
+ panic("Unsupported module load option")
+ }
+ return err
+ })
+ return err
+}
+
+func (iface *kernelModuleLoadInterface) AutoConnect(*snap.PlugInfo, *snap.SlotInfo) bool {
+ return true
+}
+
+func init() {
+ registerIface(&kernelModuleLoadInterface{
+ commonInterface: commonInterface{
+ name: "kernel-module-load",
+ summary: kernelModuleLoadSummary,
+ baseDeclarationPlugs: kernelModuleLoadBaseDeclarationPlugs,
+ baseDeclarationSlots: kernelModuleLoadBaseDeclarationSlots,
+ implicitOnCore: true,
+ implicitOnClassic: true,
+ },
+ })
+}
diff --git a/interfaces/builtin/kernel_module_load_test.go b/interfaces/builtin/kernel_module_load_test.go
new file mode 100644
index 0000000000..8689fa6c2d
--- /dev/null
+++ b/interfaces/builtin/kernel_module_load_test.go
@@ -0,0 +1,193 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2021 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package builtin_test
+
+import (
+ "fmt"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/interfaces"
+ "github.com/snapcore/snapd/interfaces/builtin"
+ "github.com/snapcore/snapd/interfaces/kmod"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/testutil"
+)
+
+type KernelModuleLoadInterfaceSuite struct {
+ testutil.BaseTest
+
+ iface interfaces.Interface
+ slotInfo *snap.SlotInfo
+ slot *interfaces.ConnectedSlot
+ plugInfo *snap.PlugInfo
+ plug *interfaces.ConnectedPlug
+}
+
+var _ = Suite(&KernelModuleLoadInterfaceSuite{
+ iface: builtin.MustInterface("kernel-module-load"),
+})
+
+const kernelModuleLoadConsumerYaml = `name: consumer
+version: 0
+plugs:
+ kmod:
+ interface: kernel-module-load
+ modules:
+ - name: forbidden
+ load: denied
+ - name: mymodule1
+ load: on-boot
+ options: p1=3 p2=true p3
+ - name: mymodule2
+ options: param_1=ok param_2=false
+apps:
+ app:
+ plugs: [kmod]
+`
+
+const kernelModuleLoadCoreYaml = `name: core
+version: 0
+type: os
+slots:
+ kernel-module-load:
+`
+
+func (s *KernelModuleLoadInterfaceSuite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
+
+ s.plug, s.plugInfo = MockConnectedPlug(c, kernelModuleLoadConsumerYaml, nil, "kmod")
+ s.slot, s.slotInfo = MockConnectedSlot(c, kernelModuleLoadCoreYaml, nil, "kernel-module-load")
+}
+
+func (s *KernelModuleLoadInterfaceSuite) TestName(c *C) {
+ c.Assert(s.iface.Name(), Equals, "kernel-module-load")
+}
+
+func (s *KernelModuleLoadInterfaceSuite) TestSanitizeSlot(c *C) {
+ c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil)
+}
+
+func (s *KernelModuleLoadInterfaceSuite) TestSanitizePlug(c *C) {
+ c.Check(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil)
+ c.Check(interfaces.BeforeConnectPlug(s.iface, s.plug), IsNil)
+}
+
+func (s *KernelModuleLoadInterfaceSuite) TestSanitizePlugUnhappy(c *C) {
+ var kernelModuleLoadYaml = `name: consumer
+version: 0
+plugs:
+ kmod:
+ interface: kernel-module-load
+ %s
+apps:
+ app:
+ plugs: [kmod]
+`
+ data := []struct {
+ plugYaml string
+ expectedError string
+ }{
+ {
+ "", // missing "modules" attribute
+ `kernel-module-load "modules" attribute must be a list of dictionaries`,
+ },
+ {
+ "modules: a string",
+ `kernel-module-load "modules" attribute must be a list of dictionaries`,
+ },
+ {
+ "modules: [this, is, a, list]",
+ `kernel-module-load "modules" attribute must be a list of dictionaries`,
+ },
+ {
+ "modules:\n - name: [this, is, a, list]",
+ `kernel-module-load "name" must be a string`,
+ },
+ {
+ "modules:\n - name: pcspkr",
+ `kernel-module-load: must specify at least "load" or "options"`,
+ },
+ {
+ "modules:\n - name: pcspkr\n load: [yes, no]",
+ `kernel-module-load "load" must be a string`,
+ },
+ {
+ "modules:\n - name: pcspkr\n load: maybe",
+ `kernel-module-load "load" value is unrecognized: "maybe"`,
+ },
+ {
+ "modules:\n - name: pcspkr\n options: [one, two]",
+ `kernel-module-load "options" must be a string`,
+ },
+ {
+ "modules:\n - name: pcspkr\n options: \"a\\nnewline\"",
+ `kernel-module-load "options" attribute contains invalid characters: "a\\nnewline"`,
+ },
+ {
+ "modules:\n - name: pcspkr\n options: \"5tartWithNumber=1\"",
+ `kernel-module-load "options" attribute contains invalid characters: "5tartWithNumber=1"`,
+ },
+ {
+ "modules:\n - name: pcspkr\n options: \"no-dashes\"",
+ `kernel-module-load "options" attribute contains invalid characters: "no-dashes"`,
+ },
+ {
+ "modules:\n - name: pcspkr\n load: denied\n options: p1=true",
+ `kernel-module-load "options" attribute incompatible with "load: denied"`,
+ },
+ }
+
+ for _, testData := range data {
+ snapYaml := fmt.Sprintf(kernelModuleLoadYaml, testData.plugYaml)
+ plug, _ := MockConnectedPlug(c, snapYaml, nil, "kmod")
+ err := interfaces.BeforeConnectPlug(s.iface, plug)
+ c.Check(err, ErrorMatches, testData.expectedError, Commentf("yaml: %s", testData.plugYaml))
+ }
+}
+
+func (s *KernelModuleLoadInterfaceSuite) TestKModSpec(c *C) {
+ spec := &kmod.Specification{}
+ c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil)
+ c.Check(spec.Modules(), DeepEquals, map[string]bool{
+ "mymodule1": true,
+ })
+ c.Check(spec.ModuleOptions(), DeepEquals, map[string]string{
+ "mymodule1": "p1=3 p2=true p3",
+ "mymodule2": "param_1=ok param_2=false",
+ })
+ c.Check(spec.DisallowedModules(), DeepEquals, []string{"forbidden"})
+}
+
+func (s *KernelModuleLoadInterfaceSuite) TestStaticInfo(c *C) {
+ si := interfaces.StaticInfoOf(s.iface)
+ c.Assert(si.ImplicitOnCore, Equals, true)
+ c.Assert(si.ImplicitOnClassic, Equals, true)
+ c.Assert(si.Summary, Equals, `allows constrained control over kernel module loading`)
+ c.Assert(si.BaseDeclarationSlots, testutil.Contains, "kernel-module-load")
+}
+
+func (s *KernelModuleLoadInterfaceSuite) TestAutoConnect(c *C) {
+ c.Assert(s.iface.AutoConnect(s.plugInfo, s.slotInfo), Equals, true)
+}
+
+func (s *KernelModuleLoadInterfaceSuite) TestInterfaces(c *C) {
+ c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface)
+}
diff --git a/interfaces/builtin/mount_control.go b/interfaces/builtin/mount_control.go
new file mode 100644
index 0000000000..4a0b82af35
--- /dev/null
+++ b/interfaces/builtin/mount_control.go
@@ -0,0 +1,490 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2021 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package builtin
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "regexp"
+ "strings"
+
+ "github.com/snapcore/snapd/interfaces"
+ "github.com/snapcore/snapd/interfaces/apparmor"
+ "github.com/snapcore/snapd/interfaces/utils"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/strutil"
+ "github.com/snapcore/snapd/systemd"
+)
+
+const mountControlSummary = `allows creating transient and persistent mounts`
+
+const mountControlBaseDeclarationPlugs = `
+ mount-control:
+ allow-installation: false
+ deny-auto-connection: true
+`
+
+const mountControlBaseDeclarationSlots = `
+ mount-control:
+ allow-installation:
+ slot-snap-type:
+ - core
+ deny-connection: true
+`
+
+var mountAttrTypeError = errors.New(`mount-control "mount" attribute must be a list of dictionaries`)
+
+const mountControlConnectedPlugSecComp = `
+# Description: Allow mount and umount syscall access. No filtering here, as we
+# rely on AppArmor to filter the mount operations.
+mount
+umount
+umount2
+`
+
+// The reason why this list is not shared with osutil.MountOptsToCommonFlags or
+// other parts of the codebase is that this one only contains the options which
+// have been deemed safe and have been vetted by the security team.
+var allowedMountOptions = []string{
+ "async",
+ "atime",
+ "bind",
+ "diratime",
+ "dirsync",
+ "iversion",
+ "lazytime",
+ "nofail",
+ "noiversion",
+ "nomand",
+ "noatime",
+ "nodev",
+ "nodiratime",
+ "noexec",
+ "nolazytime",
+ "norelatime",
+ "nosuid",
+ "nostrictatime",
+ "nouser",
+ "relatime",
+ "strictatime",
+ "sync",
+ "ro",
+ "rw",
+}
+
+// A few mount flags are special in that if they are specified, the filesystem
+// type is ignored. We list them here, and we will ensure that the plug
+// declaration does not specify a type, if any of them is present among the
+// options.
+var optionsWithoutFsType = []string{
+ "bind",
+ // Note: the following flags should also fall into this list, but we are
+ // not currently allowing them (and don't plan to):
+ // - "make-private"
+ // - "make-shared"
+ // - "make-slave"
+ // - "make-unbindable"
+ // - "move"
+ // - "remount"
+}
+
+// List of allowed filesystem types. This can be extended, keeping in mind that
+// the filesystems in the following list were considered either dangerous or
+// not relevant for this interface:
+// bpf
+// cgroup
+// cgroup2
+// debugfs
+// devpts
+// ecryptfs
+// hugetlbfs
+// overlayfs
+// proc
+// securityfs
+// sysfs
+// tracefs
+var allowedFSTypes = []string{
+ "aufs",
+ "autofs",
+ "btrfs",
+ "ext2",
+ "ext3",
+ "ext4",
+ "hfs",
+ "iso9660",
+ "jfs",
+ "msdos",
+ "ntfs",
+ "ramfs",
+ "reiserfs",
+ "squashfs",
+ "tmpfs",
+ "ubifs",
+ "udf",
+ "ufs",
+ "vfat",
+ "zfs",
+ "xfs",
+}
+
+// mountControlInterface allows creating transient and persistent mounts
+type mountControlInterface struct {
+ commonInterface
+}
+
+// The "what" and "where" attributes end up in the AppArmor profile, surrounded
+// by double quotes; to ensure that a malicious snap cannot inject arbitrary
+// rules by specifying something like
+// where: $SNAP_DATA/foo", /** rw, #
+// which would generate a profile line like:
+// mount options=() "$SNAP_DATA/foo", /** rw, #"
+// (which would grant read-write access to the whole filesystem), it's enough
+// to exclude the `"` character: without it, whatever is written in the
+// attribute will not be able to escape being treated like a pattern.
+//
+// To be safe, there's more to be done: the pattern also needs to be valid, as
+// a malformed one (for example, a pattern having an unmatched `}`) would cause
+// apparmor_parser to fail loading the profile. For this situation, we use the
+// PathPattern interface to validate the pattern.
+//
+// Besides that, we are also excluding the `@` character, which is used to mark
+// AppArmor variables (tunables): when generating the profile we lack the
+// knowledge of which variables have been defined, so it's safer to exclude
+// them.
+// The what attribute regular expression here is intentionally permissive of
+// nearly any path, and due to the super-privileged nature of this interface it
+// is expected that sensible values of what are enforced by the store manual
+// review queue and security teams.
+var (
+ whatRegexp = regexp.MustCompile(`^(none|/[^"@]*)$`)
+ whereRegexp = regexp.MustCompile(`^(\$SNAP_COMMON|\$SNAP_DATA)?/[^\$"@]+$`)
+)
+
+// Excluding spaces and other characters which might allow constructing a
+// malicious string like
+// auto) options=() /malicious/content /var/lib/snapd/hostfs/...,\n mount fstype=(
+var typeRegexp = regexp.MustCompile(`^[a-z0-9]+$`)
+
+type MountInfo struct {
+ what string
+ where string
+ persistent bool
+ types []string
+ options []string
+}
+
+func parseStringList(mountEntry map[string]interface{}, fieldName string) ([]string, error) {
+ var list []string
+ value, ok := mountEntry[fieldName]
+ if ok {
+ interfaceList, ok := value.([]interface{})
+ if !ok {
+ return nil, fmt.Errorf(`mount-control "%s" must be an array of strings (got %q)`, fieldName, value)
+ }
+ for i, iface := range interfaceList {
+ valueString, ok := iface.(string)
+ if !ok {
+ return nil, fmt.Errorf(`mount-control "%s" element %d not a string (%q)`, fieldName, i+1, iface)
+ }
+ list = append(list, valueString)
+ }
+ }
+ return list, nil
+}
+
+func enumerateMounts(plug interfaces.Attrer, fn func(mountInfo *MountInfo) error) error {
+ mountAttr, ok := plug.Lookup("mount")
+ if !ok {
+ return nil
+ }
+ mounts, ok := mountAttr.([]interface{})
+ if !ok {
+ return mountAttrTypeError
+ }
+
+ for _, m := range mounts {
+ mount, ok := m.(map[string]interface{})
+ if !ok {
+ return mountAttrTypeError
+ }
+
+ what, ok := mount["what"].(string)
+ if !ok {
+ return fmt.Errorf(`mount-control "what" must be a string`)
+ }
+
+ where, ok := mount["where"].(string)
+ if !ok {
+ return fmt.Errorf(`mount-control "where" must be a string`)
+ }
+
+ persistent := false
+ persistentValue, ok := mount["persistent"]
+ if ok {
+ if persistent, ok = persistentValue.(bool); !ok {
+ return fmt.Errorf(`mount-control "persistent" must be a boolean`)
+ }
+ }
+
+ types, err := parseStringList(mount, "type")
+ if err != nil {
+ return err
+ }
+
+ options, err := parseStringList(mount, "options")
+ if err != nil {
+ return err
+ }
+
+ mountInfo := &MountInfo{
+ what: what,
+ where: where,
+ persistent: persistent,
+ types: types,
+ options: options,
+ }
+
+ if err := fn(mountInfo); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func validateWhatAttr(what string) error {
+ if !whatRegexp.MatchString(what) {
+ return fmt.Errorf(`mount-control "what" attribute is invalid: must start with / and not contain special characters`)
+ }
+
+ if !cleanSubPath(what) {
+ return fmt.Errorf(`mount-control "what" pattern is not clean: %q`, what)
+ }
+
+ if _, err := utils.NewPathPattern(what); err != nil {
+ return fmt.Errorf(`mount-control "what" setting cannot be used: %v`, err)
+ }
+
+ return nil
+}
+
+func validateWhereAttr(where string) error {
+ if !whereRegexp.MatchString(where) {
+ return fmt.Errorf(`mount-control "where" attribute must start with $SNAP_COMMON, $SNAP_DATA or / and not contain special characters`)
+ }
+
+ if !cleanSubPath(where) {
+ return fmt.Errorf(`mount-control "where" pattern is not clean: %q`, where)
+ }
+
+ if _, err := utils.NewPathPattern(where); err != nil {
+ return fmt.Errorf(`mount-control "where" setting cannot be used: %v`, err)
+ }
+
+ return nil
+}
+
+func validateMountTypes(types []string) error {
+ includesTmpfs := false
+ for _, t := range types {
+ if !typeRegexp.MatchString(t) {
+ return fmt.Errorf(`mount-control filesystem type invalid: %q`, t)
+ }
+ if !strutil.ListContains(allowedFSTypes, t) {
+ return fmt.Errorf(`mount-control forbidden filesystem type: %q`, t)
+ }
+ if t == "tmpfs" {
+ includesTmpfs = true
+ }
+ }
+
+ if includesTmpfs && len(types) > 1 {
+ return errors.New(`mount-control filesystem type "tmpfs" cannot be listed with other types`)
+ }
+ return nil
+}
+
+func validateMountOptions(options []string) error {
+ if len(options) == 0 {
+ return errors.New(`mount-control "options" cannot be empty`)
+ }
+ for _, o := range options {
+ if !strutil.ListContains(allowedMountOptions, o) {
+ return fmt.Errorf(`mount-control option unrecognized or forbidden: %q`, o)
+ }
+ }
+ return nil
+}
+
+// Find the first option which is incompatible with a FS type declaration
+func optionIncompatibleWithFsType(options []string) string {
+ for _, o := range options {
+ if strutil.ListContains(optionsWithoutFsType, o) {
+ return o
+ }
+ }
+ return ""
+}
+
+func validateMountInfo(mountInfo *MountInfo) error {
+ if err := validateWhatAttr(mountInfo.what); err != nil {
+ return err
+ }
+
+ if err := validateWhereAttr(mountInfo.where); err != nil {
+ return err
+ }
+
+ if err := validateMountTypes(mountInfo.types); err != nil {
+ return err
+ }
+
+ if err := validateMountOptions(mountInfo.options); err != nil {
+ return err
+ }
+
+ // Check if any options are incompatible with specifying a FS type
+ fsExclusiveOption := optionIncompatibleWithFsType(mountInfo.options)
+ if fsExclusiveOption != "" && len(mountInfo.types) > 0 {
+ return fmt.Errorf(`mount-control option %q is incompatible with specifying filesystem type`, fsExclusiveOption)
+ }
+
+ // "what" must be set to "none" iff the type is "tmpfs"
+ isTmpfs := len(mountInfo.types) == 1 && mountInfo.types[0] == "tmpfs"
+ if mountInfo.what == "none" {
+ if !isTmpfs {
+ return errors.New(`mount-control "what" attribute can be "none" only with "tmpfs"`)
+ }
+ } else if isTmpfs {
+ return fmt.Errorf(`mount-control "what" attribute must be "none" with "tmpfs"; found %q instead`, mountInfo.what)
+ }
+
+ return nil
+}
+
+func (iface *mountControlInterface) BeforeConnectPlug(plug *interfaces.ConnectedPlug) error {
+ // The systemd.ListMountUnits() method works by issuing the command
+ // "systemctl show *.mount", but globbing was only added in systemd v209.
+ if err := systemd.EnsureAtLeast(209); err != nil {
+ return err
+ }
+
+ hasMountEntries := false
+ err := enumerateMounts(plug, func(mountInfo *MountInfo) error {
+ hasMountEntries = true
+ return validateMountInfo(mountInfo)
+ })
+ if err != nil {
+ return err
+ }
+
+ if !hasMountEntries {
+ return mountAttrTypeError
+ }
+
+ return nil
+}
+
+func (iface *mountControlInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
+ mountControlSnippet := bytes.NewBuffer(nil)
+ emit := func(f string, args ...interface{}) {
+ fmt.Fprintf(mountControlSnippet, f, args...)
+ }
+ snapInfo := plug.Snap()
+
+ emit(`
+ # Rules added by the mount-control interface
+ capability sys_admin, # for mount
+
+ owner @{PROC}/@{pid}/mounts r,
+ owner @{PROC}/@{pid}/mountinfo r,
+ owner @{PROC}/self/mountinfo r,
+
+ /{,usr/}bin/mount ixr,
+ /{,usr/}bin/umount ixr,
+ # mount/umount (via libmount) track some mount info in these files
+ /run/mount/utab* wrlk,
+`)
+
+ // No validation is occurring here, as it was already performed in
+ // BeforeConnectPlug()
+ enumerateMounts(plug, func(mountInfo *MountInfo) error {
+
+ source := mountInfo.what
+ target := mountInfo.where
+ if target[0] == '$' {
+ matches := whereRegexp.FindStringSubmatchIndex(target)
+ if matches == nil || len(matches) < 4 {
+ // This cannot really happen, as the string wouldn't pass the validation
+ return fmt.Errorf(`internal error: "where" fails to match regexp: %q`, mountInfo.where)
+ }
+ // the first two elements in "matches" are the boundaries of the whole
+ // string; the next two are the boundaries of the first match, which is
+ // what we care about as it contains the environment variable we want
+ // to expand:
+ variableStart, variableEnd := matches[2], matches[3]
+ variable := target[variableStart:variableEnd]
+ expanded := snapInfo.ExpandSnapVariables(variable)
+ target = expanded + target[variableEnd:]
+ }
+
+ var typeRule string
+ if optionIncompatibleWithFsType(mountInfo.options) != "" {
+ // In this rule the FS type will not match unless it's empty
+ typeRule = ""
+ } else {
+ var types []string
+ if len(mountInfo.types) > 0 {
+ types = mountInfo.types
+ } else {
+ types = allowedFSTypes
+ }
+ typeRule = "fstype=(" + strings.Join(types, ",") + ")"
+ }
+
+ options := strings.Join(mountInfo.options, ",")
+
+ emit(" mount %s options=(%s) \"%s\" -> \"%s\",\n", typeRule, options, source, target)
+ emit(" umount \"%s\",\n", target)
+ return nil
+ })
+
+ spec.AddSnippet(mountControlSnippet.String())
+ return nil
+}
+
+func (iface *mountControlInterface) AutoConnect(*snap.PlugInfo, *snap.SlotInfo) bool {
+ return true
+}
+
+func init() {
+ registerIface(&mountControlInterface{
+ commonInterface: commonInterface{
+ name: "mount-control",
+ summary: mountControlSummary,
+ baseDeclarationPlugs: mountControlBaseDeclarationPlugs,
+ baseDeclarationSlots: mountControlBaseDeclarationSlots,
+ implicitOnCore: true,
+ implicitOnClassic: true,
+ connectedPlugSecComp: mountControlConnectedPlugSecComp,
+ },
+ })
+}
diff --git a/interfaces/builtin/mount_control_test.go b/interfaces/builtin/mount_control_test.go
new file mode 100644
index 0000000000..2cb6a46e81
--- /dev/null
+++ b/interfaces/builtin/mount_control_test.go
@@ -0,0 +1,316 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2021 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package builtin_test
+
+import (
+ "fmt"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/interfaces"
+ "github.com/snapcore/snapd/interfaces/apparmor"
+ "github.com/snapcore/snapd/interfaces/builtin"
+ "github.com/snapcore/snapd/interfaces/seccomp"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/systemd"
+ "github.com/snapcore/snapd/testutil"
+)
+
+type MountControlInterfaceSuite struct {
+ testutil.BaseTest
+
+ iface interfaces.Interface
+ slotInfo *snap.SlotInfo
+ slot *interfaces.ConnectedSlot
+ plugInfo *snap.PlugInfo
+ plug *interfaces.ConnectedPlug
+}
+
+var _ = Suite(&MountControlInterfaceSuite{
+ iface: builtin.MustInterface("mount-control"),
+})
+
+const mountControlConsumerYaml = `name: consumer
+version: 0
+plugs:
+ mntctl:
+ interface: mount-control
+ mount:
+ - what: /dev/sd*
+ where: /media/**
+ type: [ext2, ext3, ext4]
+ options: [rw, sync]
+ - what: /usr/**
+ where: $SNAP_COMMON/**
+ options: [bind]
+ - what: /dev/sda{0,1}
+ where: $SNAP_COMMON/**
+ options: [ro]
+ - what: /dev/sda[0-1]
+ where: $SNAP_COMMON/{foo,other,**}
+ options: [sync]
+apps:
+ app:
+ plugs: [mntctl]
+`
+
+const mountControlCoreYaml = `name: core
+version: 0
+type: os
+slots:
+ mount-control:
+`
+
+func (s *MountControlInterfaceSuite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
+
+ s.plug, s.plugInfo = MockConnectedPlug(c, mountControlConsumerYaml, nil, "mntctl")
+ s.slot, s.slotInfo = MockConnectedSlot(c, mountControlCoreYaml, nil, "mount-control")
+
+ s.AddCleanup(systemd.MockSystemdVersion(210, nil))
+}
+
+func (s *MountControlInterfaceSuite) TestName(c *C) {
+ c.Assert(s.iface.Name(), Equals, "mount-control")
+}
+
+func (s *MountControlInterfaceSuite) TestSanitizeSlot(c *C) {
+ c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil)
+}
+
+func (s *MountControlInterfaceSuite) TestSanitizePlug(c *C) {
+ c.Check(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil)
+ c.Check(interfaces.BeforeConnectPlug(s.iface, s.plug), IsNil)
+}
+
+func (s *MountControlInterfaceSuite) TestSanitizePlugOldSystemd(c *C) {
+ restore := systemd.MockSystemdVersion(208, nil)
+ defer restore()
+ err := interfaces.BeforeConnectPlug(s.iface, s.plug)
+ c.Assert(err, ErrorMatches, `systemd version 208 is too old \(expected at least 209\)`)
+}
+
+func (s *MountControlInterfaceSuite) TestSanitizePlugUnhappy(c *C) {
+ var mountControlYaml = `name: consumer
+version: 0
+plugs:
+ mntctl:
+ interface: mount-control
+ %s
+apps:
+ app:
+ plugs: [mntctl]
+`
+ data := []struct {
+ plugYaml string
+ expectedError string
+ }{
+ {
+ "", // missing "mount" attribute
+ `mount-control "mount" attribute must be a list of dictionaries`,
+ },
+ {
+ "mount: a string",
+ `mount-control "mount" attribute must be a list of dictionaries`,
+ },
+ {
+ "mount: [this, is, a, list]",
+ `mount-control "mount" attribute must be a list of dictionaries`,
+ },
+ {
+ "mount:\n - what: [this, is, a, list]\n where: /media/**",
+ `mount-control "what" must be a string`,
+ },
+ {
+ "mount:\n - what: /path/\n where: [this, is, a, list]",
+ `mount-control "where" must be a string`,
+ },
+ {
+ "mount:\n - what: /\n where: /\n persistent: string",
+ `mount-control "persistent" must be a boolean`,
+ },
+ {
+ "mount:\n - what: /\n where: /\n type: string",
+ `mount-control "type" must be an array of strings.*`,
+ },
+ {
+ "mount:\n - what: /\n where: /\n type: [true, false]",
+ `mount-control "type" element 1 not a string.*`,
+ },
+ {
+ "mount:\n - what: /\n where: /media/*\n type: [auto)]",
+ `mount-control filesystem type invalid.*`,
+ },
+ {
+ "mount:\n - what: /\n where: /media/*\n type: [upperCase]",
+ `mount-control filesystem type invalid.*`,
+ },
+ {
+ "mount:\n - what: /\n where: /media/*\n type: [two words]",
+ `mount-control filesystem type invalid.*`,
+ },
+ {
+ "mount:\n - what: /\n where: /media/*\n",
+ `mount-control "options" cannot be empty`,
+ },
+ {
+ "mount:\n - what: /\n where: /\n options: string",
+ `mount-control "options" must be an array of strings.*`,
+ },
+ {
+ "mount:\n - what: /\n where: /media/*\n options: []",
+ `mount-control "options" cannot be empty`,
+ },
+ {
+ "mount:\n - what: here\n where: /mnt",
+ `mount-control "what" attribute is invalid: must start with / and not contain special characters`,
+ },
+ {
+ "mount:\n - what: /double\"quote\n where: /mnt",
+ `mount-control "what" attribute is invalid: must start with / and not contain special characters`,
+ },
+ {
+ "mount:\n - what: /variables/are/not/@{allowed}\n where: /mnt",
+ `mount-control "what" attribute is invalid: must start with / and not contain special characters`,
+ },
+ {
+ "mount:\n - what: /invalid}pattern\n where: /mnt",
+ `mount-control "what" setting cannot be used: invalid closing brace, no matching open.*`,
+ },
+ {
+ "mount:\n - what: /\n where: /\n options: [ro]",
+ `mount-control "where" attribute must start with \$SNAP_COMMON, \$SNAP_DATA or / and not contain special characters`,
+ },
+ {
+ "mount:\n - what: /\n where: /media/no\"quotes",
+ `mount-control "where" attribute must start with \$SNAP_COMMON, \$SNAP_DATA or / and not contain special characters`,
+ },
+ {
+ "mount:\n - what: /\n where: /media/no@{variables}",
+ `mount-control "where" attribute must start with \$SNAP_COMMON, \$SNAP_DATA or / and not contain special characters`,
+ },
+ {
+ "mount:\n - what: /\n where: $SNAP_DATA/$SNAP_DATA",
+ `mount-control "where" attribute must start with \$SNAP_COMMON, \$SNAP_DATA or / and not contain special characters`,
+ },
+ {
+ "mount:\n - what: /\n where: /$SNAP_DATA",
+ `mount-control "where" attribute must start with \$SNAP_COMMON, \$SNAP_DATA or / and not contain special characters`,
+ },
+ {
+ "mount:\n - what: /\n where: /media/invalid[path",
+ `mount-control "where" setting cannot be used: missing closing bracket ']'.*`,
+ },
+ {
+ "mount:\n - what: /\n where: /media/*\n options: [sync,invalid]",
+ `mount-control option unrecognized or forbidden: "invalid"`,
+ },
+ {
+ "mount:\n - what: /\n where: /media/*\n type: [ext4,debugfs]",
+ `mount-control forbidden filesystem type: "debugfs"`,
+ },
+ {
+ "mount:\n - what: /\n where: /media/*\n type: [ext4]\n options: [rw,bind]",
+ `mount-control option "bind" is incompatible with specifying filesystem type`,
+ },
+ {
+ "mount:\n - what: /tmp/..\n where: /media/*",
+ `mount-control "what" pattern is not clean:.*`,
+ },
+ {
+ "mount:\n - what: /\n where: /media/../etc",
+ `mount-control "where" pattern is not clean:.*`,
+ },
+ {
+ "mount:\n - what: none\n where: /media/*\n options: [rw]",
+ `mount-control "what" attribute can be "none" only with "tmpfs"`,
+ },
+ {
+ "mount:\n - what: none\n where: /media/*\n options: [rw]\n type: [ext4,ntfs]",
+ `mount-control "what" attribute can be "none" only with "tmpfs"`,
+ },
+ {
+ "mount:\n - what: none\n where: /media/*\n options: [rw]\n type: [tmpfs,ext4]",
+ `mount-control filesystem type "tmpfs" cannot be listed with other types`,
+ },
+ {
+ "mount:\n - what: /\n where: /media/*\n options: [rw]\n type: [tmpfs]",
+ `mount-control "what" attribute must be "none" with "tmpfs"; found "/" instead`,
+ },
+ }
+
+ for _, testData := range data {
+ snapYaml := fmt.Sprintf(mountControlYaml, testData.plugYaml)
+ plug, _ := MockConnectedPlug(c, snapYaml, nil, "mntctl")
+ err := interfaces.BeforeConnectPlug(s.iface, plug)
+ c.Check(err, ErrorMatches, testData.expectedError, Commentf("Yaml: %s", testData.plugYaml))
+ }
+}
+
+func (s *MountControlInterfaceSuite) TestSecCompSpec(c *C) {
+ spec := &seccomp.Specification{}
+ c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil)
+ c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"})
+ c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "mount\n")
+ c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "umount\n")
+ c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "umount2\n")
+}
+
+func (s *MountControlInterfaceSuite) TestAppArmorSpec(c *C) {
+ spec := &apparmor.Specification{}
+ c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil)
+ c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"})
+ c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `capability sys_admin,`)
+ c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `/{,usr/}bin/mount ixr,`)
+
+ expectedMountLine1 := `mount fstype=(ext2,ext3,ext4) options=(rw,sync) "/dev/sd*" -> "/media/**",`
+ c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, expectedMountLine1)
+
+ expectedMountLine2 := `mount options=(bind) "/usr/**" -> "/var/snap/consumer/common/**",`
+ c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, expectedMountLine2)
+
+ expectedMountLine3 := `mount fstype=(` +
+ `aufs,autofs,btrfs,ext2,ext3,ext4,hfs,iso9660,jfs,msdos,ntfs,ramfs,` +
+ `reiserfs,squashfs,tmpfs,ubifs,udf,ufs,vfat,zfs,xfs` +
+ `) options=(ro) "/dev/sda{0,1}" -> "/var/snap/consumer/common/**",`
+ c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, expectedMountLine3)
+
+ expectedMountLine4 := `mount fstype=(` +
+ `aufs,autofs,btrfs,ext2,ext3,ext4,hfs,iso9660,jfs,msdos,ntfs,ramfs,` +
+ `reiserfs,squashfs,tmpfs,ubifs,udf,ufs,vfat,zfs,xfs` +
+ `) options=(sync) "/dev/sda[0-1]" -> "/var/snap/consumer/common/{foo,other,**}",`
+ c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, expectedMountLine4)
+}
+
+func (s *MountControlInterfaceSuite) TestStaticInfo(c *C) {
+ si := interfaces.StaticInfoOf(s.iface)
+ c.Assert(si.ImplicitOnCore, Equals, true)
+ c.Assert(si.ImplicitOnClassic, Equals, true)
+ c.Assert(si.Summary, Equals, `allows creating transient and persistent mounts`)
+ c.Assert(si.BaseDeclarationSlots, testutil.Contains, "mount-control")
+}
+
+func (s *MountControlInterfaceSuite) TestAutoConnect(c *C) {
+ c.Assert(s.iface.AutoConnect(s.plugInfo, s.slotInfo), Equals, true)
+}
+
+func (s *MountControlInterfaceSuite) TestInterfaces(c *C) {
+ c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface)
+}
diff --git a/interfaces/builtin/opengl.go b/interfaces/builtin/opengl.go
index fe1217d533..c41719fd4c 100644
--- a/interfaces/builtin/opengl.go
+++ b/interfaces/builtin/opengl.go
@@ -127,6 +127,7 @@ unix (bind,listen) type=seqpacket addr="@cuda-uvmfd-[0-9a-f]*",
# /sys/devices
/sys/devices/{,*pcie-controller/}pci[0-9a-f]*/**/config r,
/sys/devices/{,*pcie-controller/}pci[0-9a-f]*/**/revision r,
+/sys/devices/{,*pcie-controller/}pci[0-9a-f]*/**/boot_vga r,
/sys/devices/{,*pcie-controller/}pci[0-9a-f]*/**/{,subsystem_}class r,
/sys/devices/{,*pcie-controller/}pci[0-9a-f]*/**/{,subsystem_}device r,
/sys/devices/{,*pcie-controller/}pci[0-9a-f]*/**/{,subsystem_}vendor r,
diff --git a/interfaces/builtin/shared_memory.go b/interfaces/builtin/shared_memory.go
new file mode 100644
index 0000000000..b398d5f8a7
--- /dev/null
+++ b/interfaces/builtin/shared_memory.go
@@ -0,0 +1,236 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2021 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package builtin
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "strings"
+
+ "github.com/snapcore/snapd/interfaces"
+ "github.com/snapcore/snapd/interfaces/apparmor"
+ "github.com/snapcore/snapd/snap"
+)
+
+const sharedMemorySummary = `allows two snaps to use predefined shared memory objects`
+
+const sharedMemoryBaseDeclarationPlugs = `
+ shared-memory:
+ allow-installation: true
+ allow-connection:
+ slot-attributes:
+ shared-memory: $PLUG(shared-memory)
+ allow-auto-connection:
+ slot-publisher-id:
+ - $PLUG_PUBLISHER_ID
+ slot-attributes:
+ shared-memory: $PLUG(shared-memory)
+`
+
+const sharedMemoryBaseDeclarationSlots = `
+ shared-memory:
+ allow-installation: false
+ deny-connection: true
+ deny-auto-connection: true
+`
+
+func validateSharedMemoryPath(path string) error {
+ if len(path) == 0 {
+ return fmt.Errorf("shared-memory interface path is empty")
+ }
+
+ if strings.TrimSpace(path) != path {
+ return fmt.Errorf("shared-memory interface path has leading or trailing spaces: %q", path)
+ }
+
+ // TODO: allow "*" as a globbing character; figure out if more AARE should be allowed
+ if err := apparmor.ValidateNoAppArmorRegexp(path); err != nil {
+ return fmt.Errorf("shared-memory interface path is invalid: %v", err)
+ }
+
+ // TODO: consider whether we should remove this check and allow full SHM path
+ if strings.Contains(path, "/") {
+ return fmt.Errorf("shared-memory interface path should not contain '/': %q", path)
+ }
+
+ // The check above protects from most unclean paths, but one could still specify ".."
+ if !cleanSubPath(path) {
+ return fmt.Errorf("shared-memory interface path is not clean: %q", path)
+ }
+
+ return nil
+}
+
+func stringListAttribute(attrer interfaces.Attrer, key string) ([]string, error) {
+ parseError := func(key string, value interface{}) error {
+ return fmt.Errorf(`shared-memory %q attribute must be a list of strings, not "%v"`, key, value)
+ }
+ attr, ok := attrer.Lookup(key)
+ if !ok {
+ return nil, nil
+ }
+
+ attrList, ok := attr.([]interface{})
+ if !ok || len(attrList) == 0 {
+ return nil, parseError(key, attr)
+ }
+
+ stringList := make([]string, 0, len(attrList))
+ for _, value := range attrList {
+ s, ok := value.(string)
+ if !ok {
+ return nil, parseError(key, attrList)
+ }
+ stringList = append(stringList, s)
+ }
+ return stringList, nil
+}
+
+// sharedMemoryInterface allows sharing sharedMemory between snaps
+type sharedMemoryInterface struct{}
+
+func (iface *sharedMemoryInterface) Name() string {
+ return "shared-memory"
+}
+
+func (iface *sharedMemoryInterface) StaticInfo() interfaces.StaticInfo {
+ return interfaces.StaticInfo{
+ Summary: sharedMemorySummary,
+ BaseDeclarationPlugs: sharedMemoryBaseDeclarationPlugs,
+ BaseDeclarationSlots: sharedMemoryBaseDeclarationSlots,
+ AffectsPlugOnRefresh: true,
+ }
+}
+
+func (iface *sharedMemoryInterface) BeforePrepareSlot(slot *snap.SlotInfo) error {
+ sharedMemoryAttr, isSet := slot.Attrs["shared-memory"]
+ sharedMemory, ok := sharedMemoryAttr.(string)
+ if isSet && !ok {
+ return fmt.Errorf(`shared-memory "shared-memory" attribute must be a string, not %v`,
+ slot.Attrs["shared-memory"])
+ }
+ if sharedMemory == "" {
+ if slot.Attrs == nil {
+ slot.Attrs = make(map[string]interface{})
+ }
+ // shared-memory defaults to "slot" name if unspecified
+ slot.Attrs["shared-memory"] = slot.Name
+ }
+
+ readPaths, err := stringListAttribute(slot, "read")
+ if err != nil {
+ return err
+ }
+
+ writePaths, err := stringListAttribute(slot, "write")
+ if err != nil {
+ return err
+ }
+
+ // We perform the same validation for read-only and writable paths, so
+ // let's just put them all in the same array
+ allPaths := append(readPaths, writePaths...)
+ if len(allPaths) == 0 {
+ return errors.New(`shared memory interface requires at least a valid "read" or "write" attribute`)
+ }
+
+ for _, path := range allPaths {
+ if err := validateSharedMemoryPath(path); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+type sharedMemorySnippetType int
+
+const (
+ snippetForSlot sharedMemorySnippetType = iota
+ snippetForPlug
+)
+
+func writeSharedMemoryPaths(w io.Writer, slot *interfaces.ConnectedSlot,
+ snippetType sharedMemorySnippetType) {
+ emitWritableRule := func(path string) {
+ // Ubuntu 14.04 uses /run/shm instead of the most common /dev/shm
+ fmt.Fprintf(w, "\"/{dev,run}/shm/%s\" rwk,\n", path)
+ }
+
+ // All checks were already done in BeforePrepare{Plug,Slot}
+ writePaths, _ := stringListAttribute(slot, "write")
+ for _, path := range writePaths {
+ emitWritableRule(path)
+ }
+ readPaths, _ := stringListAttribute(slot, "read")
+ for _, path := range readPaths {
+ if snippetType == snippetForPlug {
+ // grant read-only access
+ fmt.Fprintf(w, "\"/{dev,run}/shm/%s\" r,\n", path)
+ } else {
+ // the slot must still be granted write access, because the "read"
+ // and "write" attributes are meant to affect the plug only
+ emitWritableRule(path)
+ }
+ }
+}
+
+func (iface *sharedMemoryInterface) BeforePreparePlug(plug *snap.PlugInfo) error {
+ sharedMemoryAttr, isSet := plug.Attrs["shared-memory"]
+ sharedMemory, ok := sharedMemoryAttr.(string)
+ if isSet && !ok {
+ return fmt.Errorf(`shared-memory "shared-memory" attribute must be a string, not %v`,
+ plug.Attrs["shared-memory"])
+ }
+ if sharedMemory == "" {
+ if plug.Attrs == nil {
+ plug.Attrs = make(map[string]interface{})
+ }
+ // shared-memory defaults to "plug" name if unspecified
+ plug.Attrs["shared-memory"] = plug.Name
+ }
+
+ return nil
+}
+
+func (iface *sharedMemoryInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
+ sharedMemorySnippet := &bytes.Buffer{}
+ writeSharedMemoryPaths(sharedMemorySnippet, slot, snippetForPlug)
+ spec.AddSnippet(sharedMemorySnippet.String())
+ return nil
+}
+
+func (iface *sharedMemoryInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
+ sharedMemorySnippet := &bytes.Buffer{}
+ writeSharedMemoryPaths(sharedMemorySnippet, slot, snippetForSlot)
+ spec.AddSnippet(sharedMemorySnippet.String())
+ return nil
+}
+
+func (iface *sharedMemoryInterface) AutoConnect(plug *snap.PlugInfo, slot *snap.SlotInfo) bool {
+ // allow what declarations allowed
+ return true
+}
+
+func init() {
+ registerIface(&sharedMemoryInterface{})
+}
diff --git a/interfaces/builtin/shared_memory_test.go b/interfaces/builtin/shared_memory_test.go
new file mode 100644
index 0000000000..a67012407f
--- /dev/null
+++ b/interfaces/builtin/shared_memory_test.go
@@ -0,0 +1,304 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2021 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package builtin_test
+
+import (
+ "fmt"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/interfaces"
+ "github.com/snapcore/snapd/interfaces/apparmor"
+ "github.com/snapcore/snapd/interfaces/builtin"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/testutil"
+)
+
+type SharedMemoryInterfaceSuite struct {
+ testutil.BaseTest
+
+ iface interfaces.Interface
+ slotInfo *snap.SlotInfo
+ slot *interfaces.ConnectedSlot
+ plugInfo *snap.PlugInfo
+ plug *interfaces.ConnectedPlug
+}
+
+var _ = Suite(&SharedMemoryInterfaceSuite{
+ iface: builtin.MustInterface("shared-memory"),
+})
+
+const sharedMemoryConsumerYaml = `name: consumer
+version: 0
+plugs:
+ shmem:
+ interface: shared-memory
+ shared-memory: foo
+apps:
+ app:
+ plugs: [shmem]
+`
+
+const sharedMemoryProviderYaml = `name: provider
+version: 0
+slots:
+ shmem:
+ interface: shared-memory
+ shared-memory: foo
+ write: [ bar ]
+ read: [ bar-ro ]
+apps:
+ app:
+ slots: [shmem]
+`
+
+func (s *SharedMemoryInterfaceSuite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
+
+ s.plug, s.plugInfo = MockConnectedPlug(c, sharedMemoryConsumerYaml, nil, "shmem")
+ s.slot, s.slotInfo = MockConnectedSlot(c, sharedMemoryProviderYaml, nil, "shmem")
+}
+
+func (s *SharedMemoryInterfaceSuite) TestName(c *C) {
+ c.Assert(s.iface.Name(), Equals, "shared-memory")
+}
+
+func (s *SharedMemoryInterfaceSuite) TestSanitizePlug(c *C) {
+ c.Check(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil)
+ c.Check(interfaces.BeforeConnectPlug(s.iface, s.plug), IsNil)
+}
+
+func (s *SharedMemoryInterfaceSuite) TestSanitizePlugUnhappy(c *C) {
+ var sharedMemoryYaml = `name: consumer
+version: 0
+plugs:
+ shmem:
+ interface: shared-memory
+ %s
+apps:
+ app:
+ plugs: [shmem]
+`
+ data := []struct {
+ plugYaml string
+ expectedError string
+ }{
+ {
+ "shared-memory: [one two]",
+ `shared-memory "shared-memory" attribute must be a string, not \[one two\]`,
+ },
+ }
+
+ for _, testData := range data {
+ snapYaml := fmt.Sprintf(sharedMemoryYaml, testData.plugYaml)
+ _, plug := MockConnectedPlug(c, snapYaml, nil, "shmem")
+ err := interfaces.BeforePreparePlug(s.iface, plug)
+ c.Check(err, ErrorMatches, testData.expectedError, Commentf("yaml: %s", testData.plugYaml))
+ }
+}
+
+func (s *SharedMemoryInterfaceSuite) TestPlugShmAttribute(c *C) {
+ var plugYamlTemplate = `name: consumer
+version: 0
+plugs:
+ shmem:
+ interface: shared-memory
+ %s
+apps:
+ app:
+ plugs: [shmem]
+`
+
+ data := []struct {
+ plugYaml string
+ expectedName string
+ }{
+ {
+ "", // missing "shared-memory" attribute
+ "shmem", // use the name of the plug
+ },
+ {
+ "shared-memory: shmemFoo",
+ "shmemFoo",
+ },
+ }
+
+ for _, testData := range data {
+ snapYaml := fmt.Sprintf(plugYamlTemplate, testData.plugYaml)
+ _, plug := MockConnectedPlug(c, snapYaml, nil, "shmem")
+ err := interfaces.BeforePreparePlug(s.iface, plug)
+ c.Assert(err, IsNil)
+ c.Check(plug.Attrs["shared-memory"], Equals, testData.expectedName,
+ Commentf(`yaml: %q`, testData.plugYaml))
+ }
+}
+
+func (s *SharedMemoryInterfaceSuite) TestSanitizeSlot(c *C) {
+ c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil)
+}
+
+func (s *SharedMemoryInterfaceSuite) TestSanitizeSlotUnhappy(c *C) {
+ var sharedMemoryYaml = `name: provider
+version: 0
+slots:
+ shmem:
+ interface: shared-memory
+ %s
+apps:
+ app:
+ slots: [shmem]
+`
+ data := []struct {
+ slotYaml string
+ expectedError string
+ }{
+ {
+ "shared-memory: 12",
+ `shared-memory "shared-memory" attribute must be a string, not 12`,
+ },
+ {
+ "", // missing "write" attribute
+ `shared memory interface requires at least a valid "read" or "write" attribute`,
+ },
+ {
+ "write: a string",
+ `shared-memory "write" attribute must be a list of strings, not "a string"`,
+ },
+ {
+ "read: [Mixed, 12, False, list]",
+ `shared-memory "read" attribute must be a list of strings, not "\[Mixed 12 false list\]"`,
+ },
+ {
+ `read: ["ok", "trailing-space "]`,
+ `shared-memory interface path has leading or trailing spaces: "trailing-space "`,
+ },
+ {
+ `write: [" leading-space"]`,
+ `shared-memory interface path has leading or trailing spaces: " leading-space"`,
+ },
+ {
+ `write: [""]`,
+ `shared-memory interface path is empty`,
+ },
+ {
+ `write: [mem/**]`,
+ `shared-memory interface path is invalid: "mem/\*\*" contains a reserved apparmor char.*`,
+ },
+ {
+ `read: [..]`,
+ `shared-memory interface path is not clean: ".."`,
+ },
+ {
+ `write: [/dev/shm/bar]`,
+ `shared-memory interface path should not contain '/': "/dev/shm/bar"`,
+ },
+ {
+ `write: [mem/../etc]`,
+ `shared-memory interface path should not contain '/': "mem/../etc"`,
+ },
+ {
+ "write: [valid]\n read: [../invalid]",
+ `shared-memory interface path should not contain '/': "../invalid"`,
+ },
+ {
+ "read: [valid]\n write: [../invalid]",
+ `shared-memory interface path should not contain '/': "../invalid"`,
+ },
+ }
+
+ for _, testData := range data {
+ snapYaml := fmt.Sprintf(sharedMemoryYaml, testData.slotYaml)
+ _, slot := MockConnectedSlot(c, snapYaml, nil, "shmem")
+ err := interfaces.BeforePrepareSlot(s.iface, slot)
+ c.Check(err, ErrorMatches, testData.expectedError, Commentf("yaml: %s", testData.slotYaml))
+ }
+}
+
+func (s *SharedMemoryInterfaceSuite) TestSlotShmAttribute(c *C) {
+ var slotYamlTemplate = `name: consumer
+version: 0
+slots:
+ shmem:
+ interface: shared-memory
+ write: [foo]
+ %s
+apps:
+ app:
+ slots: [shmem]
+`
+
+ data := []struct {
+ slotYaml string
+ expectedName string
+ }{
+ {
+ "", // missing "shared-memory" attribute
+ "shmem", // use the name of the slot
+ },
+ {
+ "shared-memory: shmemBar",
+ "shmemBar",
+ },
+ }
+
+ for _, testData := range data {
+ snapYaml := fmt.Sprintf(slotYamlTemplate, testData.slotYaml)
+ _, slot := MockConnectedSlot(c, snapYaml, nil, "shmem")
+ err := interfaces.BeforePrepareSlot(s.iface, slot)
+ c.Assert(err, IsNil)
+ c.Check(slot.Attrs["shared-memory"], Equals, testData.expectedName,
+ Commentf(`yaml: %q`, testData.slotYaml))
+ }
+}
+
+func (s *SharedMemoryInterfaceSuite) TestStaticInfo(c *C) {
+ si := interfaces.StaticInfoOf(s.iface)
+ c.Check(si.ImplicitOnCore, Equals, false)
+ c.Check(si.ImplicitOnClassic, Equals, false)
+ c.Check(si.Summary, Equals, `allows two snaps to use predefined shared memory objects`)
+ c.Check(si.BaseDeclarationSlots, testutil.Contains, "shared-memory")
+}
+
+func (s *SharedMemoryInterfaceSuite) TestAppArmorSpec(c *C) {
+ spec := &apparmor.Specification{}
+
+ c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil)
+ plugSnippet := spec.SnippetForTag("snap.consumer.app")
+
+ c.Assert(spec.AddConnectedSlot(s.iface, s.plug, s.slot), IsNil)
+ slotSnippet := spec.SnippetForTag("snap.provider.app")
+
+ c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app", "snap.provider.app"})
+
+ c.Check(plugSnippet, testutil.Contains, `"/{dev,run}/shm/bar" rwk,`)
+ c.Check(plugSnippet, testutil.Contains, `"/{dev,run}/shm/bar-ro" r,`)
+
+ // Slot has read-write permissions to all paths
+ c.Check(slotSnippet, testutil.Contains, `"/{dev,run}/shm/bar" rwk,`)
+ c.Check(slotSnippet, testutil.Contains, `"/{dev,run}/shm/bar-ro" rwk,`)
+}
+
+func (s *SharedMemoryInterfaceSuite) TestAutoConnect(c *C) {
+ c.Assert(s.iface.AutoConnect(s.plugInfo, s.slotInfo), Equals, true)
+}
+
+func (s *SharedMemoryInterfaceSuite) TestInterfaces(c *C) {
+ c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface)
+}
diff --git a/interfaces/kmod/backend.go b/interfaces/kmod/backend.go
index 0690ba06b4..75e2b9a011 100644
--- a/interfaces/kmod/backend.go
+++ b/interfaces/kmod/backend.go
@@ -67,9 +67,56 @@ func (b *Backend) Name() interfaces.SecuritySystem {
return "kmod"
}
-// Setup creates a conf file with list of kernel modules required by given snap,
-// writes it in /etc/modules-load.d/ directory and immediately loads the modules
-// using /sbin/modprobe. The devMode is ignored.
+// setupModules creates a conf file with list of kernel modules required by
+// given snap, writes it in /etc/modules-load.d/ directory and immediately
+// loads the modules using /sbin/modprobe. The devMode is ignored.
+func (b *Backend) setupModules(snapInfo *snap.Info, spec *Specification) error {
+ content, modules := deriveContent(spec, snapInfo)
+ // synchronize the content with the filesystem
+ glob := interfaces.SecurityTagGlob(snapInfo.InstanceName())
+ dir := dirs.SnapKModModulesDir
+ if err := os.MkdirAll(dir, 0755); err != nil {
+ return fmt.Errorf("cannot create directory for kmod files %q: %s", dir, err)
+ }
+
+ changed, _, err := osutil.EnsureDirState(dirs.SnapKModModulesDir, glob, content)
+ if err != nil {
+ return err
+ }
+
+ if len(changed) > 0 {
+ b.loadModules(modules)
+ }
+ return nil
+}
+
+// setupModprobe creates a configuration file under /etc/modprobe.d/ according
+// to the specification: this allows to either specify the load parameters for
+// a module, or prevent it from being loaded.
+// TODO: consider whether
+// - a newly blocklisted module should get unloaded
+// - a module whose option change should get reloaded
+func (b *Backend) setupModprobe(snapInfo *snap.Info, spec *Specification) error {
+ dir := dirs.SnapKModModprobeDir
+ if err := os.MkdirAll(dir, 0755); err != nil {
+ return fmt.Errorf("cannot create directory for kmod files %q: %s", dir, err)
+ }
+
+ glob := interfaces.SecurityTagGlob(snapInfo.InstanceName())
+ dirContents := prepareModprobeDirContents(spec, snapInfo)
+ _, _, err := osutil.EnsureDirState(dirs.SnapKModModprobeDir, glob, dirContents)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// Setup will make the kmod backend generate the needed system files (such as
+// those under /etc/modules-load.d/ and /etc/modprobe.d/) and call the
+// appropriate system commands so that the desired kernel module configuration
+// will be applied.
+// The devMode is ignored.
//
// If the method fails it should be re-tried (with a sensible strategy) by the caller.
func (b *Backend) Setup(snapInfo *snap.Info, confinement interfaces.ConfinementOptions, repo *interfaces.Repository, tm timings.Measurer) error {
@@ -80,22 +127,16 @@ func (b *Backend) Setup(snapInfo *snap.Info, confinement interfaces.ConfinementO
return fmt.Errorf("cannot obtain kmod specification for snap %q: %s", snapName, err)
}
- content, modules := deriveContent(spec.(*Specification), snapInfo)
- // synchronize the content with the filesystem
- glob := interfaces.SecurityTagGlob(snapName)
- dir := dirs.SnapKModModulesDir
- if err := os.MkdirAll(dir, 0755); err != nil {
- return fmt.Errorf("cannot create directory for kmod files %q: %s", dir, err)
+ err = b.setupModprobe(snapInfo, spec.(*Specification))
+ if err != nil {
+ return err
}
- changed, _, err := osutil.EnsureDirState(dirs.SnapKModModulesDir, glob, content)
+ err = b.setupModules(snapInfo, spec.(*Specification))
if err != nil {
return err
}
- if len(changed) > 0 {
- b.loadModules(modules)
- }
return nil
}
@@ -106,8 +147,20 @@ func (b *Backend) Setup(snapInfo *snap.Info, confinement interfaces.ConfinementO
// If the method fails it should be re-tried (with a sensible strategy) by the caller.
func (b *Backend) Remove(snapName string) error {
glob := interfaces.SecurityTagGlob(snapName)
- _, _, err := osutil.EnsureDirState(dirs.SnapKModModulesDir, glob, nil)
- return err
+ var errors []error
+ if _, _, err := osutil.EnsureDirState(dirs.SnapKModModulesDir, glob, nil); err != nil {
+ errors = append(errors, err)
+ }
+
+ if _, _, err := osutil.EnsureDirState(dirs.SnapKModModprobeDir, glob, nil); err != nil {
+ errors = append(errors, err)
+ }
+
+ if len(errors) > 0 {
+ return fmt.Errorf("cannot remove kernel modules config files: %v", errors)
+ }
+
+ return nil
}
func deriveContent(spec *Specification, snapInfo *snap.Info) (map[string]osutil.FileState, []string) {
@@ -134,6 +187,31 @@ func deriveContent(spec *Specification, snapInfo *snap.Info) (map[string]osutil.
return content, modules
}
+func prepareModprobeDirContents(spec *Specification, snapInfo *snap.Info) map[string]osutil.FileState {
+ disallowedModules := spec.DisallowedModules()
+ if len(disallowedModules) == 0 && len(spec.moduleOptions) == 0 {
+ return nil
+ }
+
+ contents := "# Generated by snapd. Do not edit\n\n"
+ // First, write down the list of disallowed modules
+ for _, module := range disallowedModules {
+ contents += fmt.Sprintf("blacklist %s\n", module)
+ }
+ // Then, write down the module options
+ for module, options := range spec.moduleOptions {
+ contents += fmt.Sprintf("options %s %s\n", module, options)
+ }
+
+ fileName := fmt.Sprintf("%s.conf", snap.SecurityTag(snapInfo.InstanceName()))
+ return map[string]osutil.FileState{
+ fileName: &osutil.MemoryFileState{
+ Content: []byte(contents),
+ Mode: 0644,
+ },
+ }
+}
+
func (b *Backend) NewSpecification() interfaces.Specification {
return &Specification{}
}
diff --git a/interfaces/kmod/backend_test.go b/interfaces/kmod/backend_test.go
index 92d6dfed70..ab8b5b3f27 100644
--- a/interfaces/kmod/backend_test.go
+++ b/interfaces/kmod/backend_test.go
@@ -118,6 +118,63 @@ func (s *backendSuite) TestRemovingSnapRemovesModulesConf(c *C) {
}
}
+func (s *backendSuite) TestInstallingSnapCreatesModprobeConf(c *C) {
+ s.Iface.KModPermanentSlotCallback = func(spec *kmod.Specification, slot *snap.SlotInfo) error {
+ spec.AddModule("module1")
+ spec.SetModuleOptions("module1", "opt1=true opt2=2")
+ spec.DisallowModule("module2")
+ return nil
+ }
+
+ modulesPath := filepath.Join(dirs.SnapKModModulesDir, "snap.samba.conf")
+ c.Assert(osutil.FileExists(modulesPath), Equals, false)
+ modprobePath := filepath.Join(dirs.SnapKModModprobeDir, "snap.samba.conf")
+ c.Assert(osutil.FileExists(modprobePath), Equals, false)
+
+ for _, opts := range testedConfinementOpts {
+ s.modprobeCmd.ForgetCalls()
+ snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 0)
+
+ c.Assert(osutil.FileExists(modulesPath), Equals, true)
+ c.Assert(modulesPath, testutil.FileEquals, "# This file is automatically generated.\nmodule1\n")
+
+ c.Assert(osutil.FileExists(modprobePath), Equals, true)
+ c.Assert(modprobePath, testutil.FileEquals, `# Generated by snapd. Do not edit
+
+blacklist module2
+options module1 opt1=true opt2=2
+`)
+
+ c.Assert(s.modprobeCmd.Calls(), DeepEquals, [][]string{
+ {"modprobe", "--syslog", "module1"},
+ })
+ s.RemoveSnap(c, snapInfo)
+ }
+}
+
+func (s *backendSuite) TestRemovingSnapRemovesModprobeConf(c *C) {
+ s.Iface.KModPermanentSlotCallback = func(spec *kmod.Specification, slot *snap.SlotInfo) error {
+ spec.AddModule("module1")
+ spec.SetModuleOptions("module1", "opt1=true opt2=2")
+ spec.DisallowModule("module2")
+ return nil
+ }
+
+ modulesPath := filepath.Join(dirs.SnapKModModulesDir, "snap.samba.conf")
+ c.Assert(osutil.FileExists(modulesPath), Equals, false)
+ modprobePath := filepath.Join(dirs.SnapKModModprobeDir, "snap.samba.conf")
+ c.Assert(osutil.FileExists(modprobePath), Equals, false)
+
+ for _, opts := range testedConfinementOpts {
+ snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 0)
+ c.Assert(osutil.FileExists(modulesPath), Equals, true)
+ c.Assert(osutil.FileExists(modprobePath), Equals, true)
+ s.RemoveSnap(c, snapInfo)
+ c.Assert(osutil.FileExists(modulesPath), Equals, false)
+ c.Assert(osutil.FileExists(modprobePath), Equals, false)
+ }
+}
+
func (s *backendSuite) TestSecurityIsStable(c *C) {
// NOTE: Hand out a permanent snippet so that .conf file is generated.
s.Iface.KModPermanentSlotCallback = func(spec *kmod.Specification, slot *snap.SlotInfo) error {
diff --git a/interfaces/kmod/spec.go b/interfaces/kmod/spec.go
index 8e6db0d7e4..d2851b1c5b 100644
--- a/interfaces/kmod/spec.go
+++ b/interfaces/kmod/spec.go
@@ -20,6 +20,7 @@
package kmod
import (
+ "sort"
"strings"
"github.com/snapcore/snapd/interfaces"
@@ -33,6 +34,9 @@ import (
// setup process.
type Specification struct {
modules map[string]bool
+
+ moduleOptions map[string]string
+ disallowedModules map[string]bool
}
// AddModule adds a kernel module, trimming spaces and ignoring duplicated modules.
@@ -57,6 +61,45 @@ func (spec *Specification) Modules() map[string]bool {
return result
}
+// SetModuleOptions specifies which options to use when loading the given kernel module.
+func (spec *Specification) SetModuleOptions(module, options string) error {
+ if spec.moduleOptions == nil {
+ spec.moduleOptions = make(map[string]string)
+ }
+ spec.moduleOptions[module] = options
+ return nil
+}
+
+// moduleOptions returns the load options for each kernel module
+func (spec *Specification) ModuleOptions() map[string]string {
+ return spec.moduleOptions
+}
+
+// DisallowModule adds a kernel module to the list of disallowed modules.
+func (spec *Specification) DisallowModule(module string) error {
+ m := strings.TrimSpace(module)
+ if m == "" {
+ return nil
+ }
+ if spec.disallowedModules == nil {
+ spec.disallowedModules = make(map[string]bool)
+ }
+ spec.disallowedModules[m] = true
+ return nil
+}
+
+// DisallowedModules returns the list of disallowed modules.
+func (spec *Specification) DisallowedModules() []string {
+ result := make([]string, 0, len(spec.disallowedModules))
+ for k, v := range spec.disallowedModules {
+ if v {
+ result = append(result, k)
+ }
+ }
+ sort.Strings(result)
+ return result
+}
+
// Implementation of methods required by interfaces.Specification
// AddConnectedPlug records kmod-specific side-effects of having a connected plug.
diff --git a/interfaces/policy/basedeclaration_test.go b/interfaces/policy/basedeclaration_test.go
index e4a8ea6379..c3a33c873c 100644
--- a/interfaces/policy/basedeclaration_test.go
+++ b/interfaces/policy/basedeclaration_test.go
@@ -627,6 +627,7 @@ var (
"hidraw": {"core", "gadget"},
"i2c": {"core", "gadget"},
"iio": {"core", "gadget"},
+ "kernel-module-load": {"core"},
"kubernetes-support": {"core"},
"location-control": {"app"},
"location-observe": {"app"},
@@ -636,6 +637,7 @@ var (
"mir": {"app"},
"microstack-support": {"core"},
"modem-manager": {"app", "core"},
+ "mount-control": {"core"},
"mpris": {"app"},
"netlink-driver": {"core", "gadget"},
"network-manager": {"app", "core"},
@@ -669,6 +671,7 @@ var (
"classic-support": nil,
"docker": nil,
"lxd": nil,
+ "shared-memory": nil,
}
restrictedPlugInstallation = map[string][]string{
@@ -735,9 +738,11 @@ func (s *baseDeclSuite) TestPlugInstallation(c *C) {
"gpio-control": true,
"ion-memory-control": true,
"kernel-module-control": true,
+ "kernel-module-load": true,
"kubernetes-support": true,
"lxd-support": true,
"microstack-support": true,
+ "mount-control": true,
"multipass-support": true,
"packagekit-control": true,
"personal-files": true,
@@ -798,6 +803,7 @@ func (s *baseDeclSuite) TestConnection(c *C) {
"mir": true,
"online-accounts-service": true,
"raw-volume": true,
+ "shared-memory": true,
"storage-framework-service": true,
"thumbnailer-service": true,
"ubuntu-download-manager": true,
@@ -975,13 +981,16 @@ func (s *baseDeclSuite) TestSanity(c *C) {
"gpio-control": true,
"ion-memory-control": true,
"kernel-module-control": true,
+ "kernel-module-load": true,
"kubernetes-support": true,
"lxd-support": true,
"microstack-support": true,
+ "mount-control": true,
"multipass-support": true,
"packagekit-control": true,
"personal-files": true,
"sd-control": true,
+ "shared-memory": true,
"snap-refresh-control": true,
"snap-themes-control": true,
"snapd-control": true,
diff --git a/interfaces/utils/export_test.go b/interfaces/utils/export_test.go
new file mode 100644
index 0000000000..9f2ee6076c
--- /dev/null
+++ b/interfaces/utils/export_test.go
@@ -0,0 +1,26 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2021 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package utils
+
+var (
+ CreateRegex = createRegex
+ GlobDefault = globDefault
+ GlobNull = globNull
+)
diff --git a/interfaces/utils/path_patterns.go b/interfaces/utils/path_patterns.go
new file mode 100644
index 0000000000..098e6f2954
--- /dev/null
+++ b/interfaces/utils/path_patterns.go
@@ -0,0 +1,177 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2021 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package utils
+
+import (
+ "fmt"
+ "regexp"
+)
+
+type PathPattern struct {
+ pattern string
+ regex *regexp.Regexp
+}
+
+const maxGroupDepth = 50
+
+type GlobFlags int
+
+const (
+ globDefault GlobFlags = 1 << iota
+ globNull
+)
+
+// createRegex converts the apparmor-like glob sequence into a regex. Loosely
+// using this as reference:
+// https://gitlab.com/apparmor/apparmor/-/blob/master/parser/parser_regex.c#L107
+func createRegex(pattern string, glob GlobFlags) (string, error) {
+ regex := "^"
+
+ appendGlob := func(defaultGlob, nullGlob string) {
+ var pattern string
+ switch glob {
+ case globDefault:
+ pattern = defaultGlob
+ case globNull:
+ pattern = nullGlob
+ }
+ regex += pattern
+ }
+
+ const (
+ noSlashOrNull = `[^/\x00]`
+ noSlash = `[^/]`
+ )
+
+ escapeNext := false
+ currentGroupLevel := 0
+ inCharClass := false
+ skipNext := false
+ itemCountInGroup := new([maxGroupDepth + 1]int)
+ for i, ch := range pattern {
+ if escapeNext {
+ regex += regexp.QuoteMeta(string(ch))
+ escapeNext = false
+ continue
+ }
+ if skipNext {
+ skipNext = false
+ continue
+ }
+ if inCharClass && ch != '\\' && ch != ']' {
+ // no characters are special other than '\' and ']'
+ regex += string(ch)
+ continue
+ }
+ switch ch {
+ case '\\':
+ escapeNext = true
+ case '*':
+ if regex[len(regex)-1] == '/' {
+ // if the * is at the end of the pattern or is followed by a
+ // '/' we don't want it to match an empty string:
+ // /foo/* -> should not match /foo/
+ // /foo/*bar -> should match /foo/bar
+ // /*/foo -> should not match //foo
+ pos := i + 1
+ for len(pattern) > pos && pattern[pos] == '*' {
+ pos++
+ }
+ if len(pattern) <= pos || pattern[pos] == '/' {
+ appendGlob(noSlashOrNull, noSlash)
+ }
+ }
+
+ if len(pattern) > i+1 && pattern[i+1] == '*' {
+ // Handle **
+ appendGlob("[^\\x00]*", ".*")
+ skipNext = true
+ } else {
+ appendGlob(noSlashOrNull+"*", noSlash+"*")
+ }
+ case '?':
+ appendGlob(noSlashOrNull, noSlash)
+ case '[':
+ inCharClass = true
+ regex += string(ch)
+ case ']':
+ if !inCharClass {
+ return "", fmt.Errorf("pattern contains unmatching ']': %q", pattern)
+ }
+ inCharClass = false
+ regex += string(ch)
+ case '{':
+ currentGroupLevel++
+ if currentGroupLevel > maxGroupDepth {
+ return "", fmt.Errorf("maximum group depth exceeded: %q", pattern)
+ }
+ itemCountInGroup[currentGroupLevel] = 0
+ regex += "("
+ case '}':
+ if currentGroupLevel <= 0 {
+ return "", fmt.Errorf("invalid closing brace, no matching open { found: %q", pattern)
+ }
+ if itemCountInGroup[currentGroupLevel] == 0 {
+ return "", fmt.Errorf("invalid number of items between {}: %q", pattern)
+ }
+ currentGroupLevel--
+ regex += ")"
+ case ',':
+ if currentGroupLevel > 0 {
+ itemCountInGroup[currentGroupLevel]++
+ regex += "|"
+ } else {
+ return "", fmt.Errorf("cannot use ',' outside of group or character class")
+ }
+ default:
+ // take literal character (with quoting if needed)
+ regex += regexp.QuoteMeta(string(ch))
+ }
+ }
+
+ if currentGroupLevel > 0 {
+ return "", fmt.Errorf("missing %d closing brace(s): %q", currentGroupLevel, pattern)
+ }
+ if inCharClass {
+ return "", fmt.Errorf("missing closing bracket ']': %q", pattern)
+ }
+ if escapeNext {
+ return "", fmt.Errorf("expected character after '\\': %q", pattern)
+ }
+
+ regex += "$"
+ return regex, nil
+}
+
+func NewPathPattern(pattern string) (*PathPattern, error) {
+ regexPattern, err := createRegex(pattern, globDefault)
+ if err != nil {
+ return nil, err
+ }
+
+ regex := regexp.MustCompile(regexPattern)
+
+ pp := &PathPattern{pattern, regex}
+ return pp, nil
+}
+
+func (pp *PathPattern) Matches(path string) bool {
+ return pp.regex.MatchString(path)
+}
diff --git a/interfaces/utils/path_patterns_test.go b/interfaces/utils/path_patterns_test.go
new file mode 100644
index 0000000000..c453dd88a1
--- /dev/null
+++ b/interfaces/utils/path_patterns_test.go
@@ -0,0 +1,133 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2021 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package utils_test
+
+import (
+ "regexp"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/interfaces/utils"
+)
+
+type pathPatternsSuite struct{}
+
+var _ = Suite(&pathPatternsSuite{})
+
+func (s *pathPatternsSuite) TestRegexCreationHappy(c *C) {
+ // to save some typing:
+ d := utils.GlobDefault
+ n := utils.GlobNull
+
+ data := []struct {
+ pattern string
+ glob utils.GlobFlags
+ expectedRegex string
+ }{
+ {`/media/user/`, d, `^/media/user/$`},
+ {`/dev/sd*`, d, `^/dev/sd[^/\x00]*$`},
+ {`/dev/sd*`, n, `^/dev/sd[^/]*$`},
+ {`/dev/sd?`, d, `^/dev/sd[^/\x00]$`},
+ {`/dev/sd?`, n, `^/dev/sd[^/]$`},
+ {`/etc/**`, d, `^/etc/[^/\x00][^\x00]*$`},
+ {`/home/*/.bashrc`, d, `^/home/[^/\x00][^/\x00]*/\.bashrc$`},
+ {`/home/*/.bashrc`, n, `^/home/[^/][^/]*/\.bashrc$`},
+ {`/media/{user,loser}/`, d, `^/media/(user|loser)/$`},
+ {`/nested/{a,b{c,d}}/`, d, `^/nested/(a|b(c|d))/$`},
+ {`/media/\{in-braces\}/`, d, `^/media/\{in-braces\}/$`},
+ {`/media/\[in-brackets\]/`, d, `^/media/\[in-brackets\]/$`},
+ {`/dev/sd[abc][0-9]`, d, `^/dev/sd[abc][0-9]$`},
+ {`/quoted/bracket/[ab\]c]`, d, `^/quoted/bracket/[ab\]c]$`},
+ {`{[,],}`, d, `^([,]|)$`},
+ {`/path/with/comma[,]`, d, `^/path/with/comma[,]$`},
+ {`/$pecial/c^aracters`, d, `^/\$pecial/c\^aracters$`},
+ {`/in/char/class[^$]`, d, `^/in/char/class[^$]$`},
+ }
+
+ for _, testData := range data {
+ pattern := testData.pattern
+ expectedRegex := testData.expectedRegex
+ regex, err := utils.CreateRegex(pattern, testData.glob)
+ c.Assert(err, IsNil, Commentf("%s", pattern))
+ c.Assert(regex, Equals, expectedRegex, Commentf("%s", pattern))
+ // Also, make sure that the obtained regex is valid
+ _, err = regexp.Compile(regex)
+ c.Assert(err, IsNil, Commentf("%s", pattern))
+ }
+}
+
+func (s *pathPatternsSuite) TestRegexCreationUnhappy(c *C) {
+ data := []struct {
+ pattern string
+ expectedError string
+ }{
+ {`/media/{}/`, `invalid number of items between {}:.*`},
+ {`/media/{some/things`, `missing 1 closing brace\(s\):.*`},
+ {`/media/}`, `invalid closing brace, no matching open { found:.*`},
+ {`/media/[abc`, `missing closing bracket ']':.*`},
+ {`/media/]`, `pattern contains unmatching ']':.*`},
+ {`/media\`, `expected character after '\\':.*`},
+ // 123456789012345678901234567890123456789012345678901, 51 of them
+ {`/{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{`, `maximum group depth exceeded:.*`},
+ {`/comma/not/in/group/a,b`, `cannot use ',' outside of group or character class`},
+ }
+
+ for _, testData := range data {
+ pattern := testData.pattern
+ expectedError := testData.expectedError
+ pathPattern, err := utils.NewPathPattern(pattern)
+ c.Assert(pathPattern, IsNil, Commentf("%s", pattern))
+ c.Assert(err, ErrorMatches, expectedError, Commentf("%s", pattern))
+ }
+}
+
+func (s *pathPatternsSuite) TestPatternMatches(c *C) {
+ data := []struct {
+ pattern string
+ testPath string
+ expectedMatch bool
+ }{
+ {`/same/path/`, `/same/path/`, true},
+ {`/path/*`, `/path/here`, true},
+ {`/path/*`, `/path/too/deep`, false},
+ {`/path/**`, `/path/here`, true},
+ {`/path/**`, `/path/here/too`, true},
+ {`/dev/sd?`, `/dev/sda`, true},
+ {`/dev/sd?`, `/dev/sdb1`, false},
+ {`/media/{user,loser}/`, `/media/user/`, true},
+ {`/media/{user,loser}/`, `/media/other/`, false},
+ {`/nested/{a,b{c,d}}/`, `/nested/a/`, true},
+ {`/nested/{a,b{c,d}}/`, `/nested/bd/`, true},
+ {`/nested/{a,b{c,d}}/`, `/nested/ad/`, false},
+ {`/dev/sd[abc][0-9]`, `/dev/sda0`, true},
+ {`/dev/sd[abc][0-9]`, `/dev/sdb4`, true},
+ {`/dev/sd[abc][0-9]`, `/dev/sda10`, false},
+ {`/dev/sd[abc][0-9]`, `/dev/sdd0`, false},
+ }
+
+ for _, testData := range data {
+ pattern := testData.pattern
+ testPath := testData.testPath
+ expectedMatch := testData.expectedMatch
+ pathPattern, err := utils.NewPathPattern(pattern)
+ c.Assert(err, IsNil, Commentf("%s", pattern))
+ c.Assert(pathPattern.Matches(testPath), Equals, expectedMatch, Commentf("%s", pattern))
+ }
+}
diff --git a/mkversion.sh b/mkversion.sh
index 2fe1edf51a..7d6d108d21 100755
--- a/mkversion.sh
+++ b/mkversion.sh
@@ -130,4 +130,5 @@ EOF
cat <<EOF > "$PKG_BUILDDIR/data/info"
VERSION=$v
+SNAPD_APPARMOR_REEXEC=0
EOF
diff --git a/osutil/chattr_32.go b/osutil/chattr_32.go
index 92e509b8fb..96b9ee2560 100644
--- a/osutil/chattr_32.go
+++ b/osutil/chattr_32.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build arm || 386 || ppc
// +build arm 386 ppc
/*
diff --git a/osutil/chattr_64.go b/osutil/chattr_64.go
index acd7c2738f..e9f5a8a675 100644
--- a/osutil/chattr_64.go
+++ b/osutil/chattr_64.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build amd64 || arm64 || ppc64le || riscv64 || s390x
// +build amd64 arm64 ppc64le riscv64 s390x
/*
diff --git a/osutil/cp_other.go b/osutil/cp_other.go
index 5cc206ba2c..b36d08de44 100644
--- a/osutil/cp_other.go
+++ b/osutil/cp_other.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !linux
// +build !linux
/*
diff --git a/osutil/export_fault_test.go b/osutil/export_fault_test.go
index 6097f244d9..fd12be7759 100644
--- a/osutil/export_fault_test.go
+++ b/osutil/export_fault_test.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build faultinject
// +build faultinject
/*
diff --git a/osutil/faultinject.go b/osutil/faultinject.go
index 05305865be..691e1d48e5 100644
--- a/osutil/faultinject.go
+++ b/osutil/faultinject.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build faultinject
// +build faultinject
/*
diff --git a/osutil/faultinject_dummy.go b/osutil/faultinject_dummy.go
index 674915edf7..c68e597215 100644
--- a/osutil/faultinject_dummy.go
+++ b/osutil/faultinject_dummy.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !faultinject
// +build !faultinject
/*
diff --git a/osutil/faultinject_dummy_test.go b/osutil/faultinject_dummy_test.go
index 310e882e67..6c5bc8121f 100644
--- a/osutil/faultinject_dummy_test.go
+++ b/osutil/faultinject_dummy_test.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !faultinject
// +build !faultinject
/*
diff --git a/osutil/faultinject_test.go b/osutil/faultinject_test.go
index deff115e5c..ff299a936b 100644
--- a/osutil/faultinject_test.go
+++ b/osutil/faultinject_test.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build faultinject
// +build faultinject
/*
diff --git a/osutil/group_cgo.go b/osutil/group_cgo.go
index 8a70e1b901..1d00b454c5 100644
--- a/osutil/group_cgo.go
+++ b/osutil/group_cgo.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build cgo
// +build cgo
/*
diff --git a/osutil/group_no_cgo.go b/osutil/group_no_cgo.go
index 752cae6561..8d18cf2acd 100644
--- a/osutil/group_no_cgo.go
+++ b/osutil/group_no_cgo.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !cgo
// +build !cgo
package osutil
diff --git a/osutil/settime_32bit.go b/osutil/settime_32bit.go
index 983556a955..42ee0cf6e9 100644
--- a/osutil/settime_32bit.go
+++ b/osutil/settime_32bit.go
@@ -1,5 +1,6 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build (386 || arm) && linux
// +build 386 arm
// +build linux
diff --git a/osutil/settime_64bit.go b/osutil/settime_64bit.go
index d0587271b0..665b785b22 100644
--- a/osutil/settime_64bit.go
+++ b/osutil/settime_64bit.go
@@ -1,8 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
-// +build !386
-// +build !arm
-// +build linux
+//go:build !386 && !arm && linux
+// +build !386,!arm,linux
/*
* Copyright (C) 2021 Canonical Ltd
diff --git a/osutil/sys/sysnum_16_linux.go b/osutil/sys/sysnum_16_linux.go
index fe3783cdca..6d01a57abf 100644
--- a/osutil/sys/sysnum_16_linux.go
+++ b/osutil/sys/sysnum_16_linux.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build arm || 386
// +build arm 386
/*
diff --git a/osutil/sys/sysnum_32_linux.go b/osutil/sys/sysnum_32_linux.go
index 0449aa5129..36f67fc48a 100644
--- a/osutil/sys/sysnum_32_linux.go
+++ b/osutil/sys/sysnum_32_linux.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build amd64 || arm64 || ppc || ppc64le || riscv64 || s390x
// +build amd64 arm64 ppc ppc64le riscv64 s390x
/*
diff --git a/osutil/udev/netlink/rawsockstop_other.go b/osutil/udev/netlink/rawsockstop_other.go
index 9eaacc6339..8d9ce41579 100644
--- a/osutil/udev/netlink/rawsockstop_other.go
+++ b/osutil/udev/netlink/rawsockstop_other.go
@@ -1,4 +1,6 @@
+//go:build !arm64
// +build !arm64
+
// don't remove the newline between the above statement and the package statement
// or else the build constraint will be ignored and assumed to be part of the package comment!
diff --git a/overlord/assertstate/assertstate.go b/overlord/assertstate/assertstate.go
index bfd85cf612..d0b63a0687 100644
--- a/overlord/assertstate/assertstate.go
+++ b/overlord/assertstate/assertstate.go
@@ -344,6 +344,10 @@ func delayedCrossMgrInit() {
snapstate.AutoAliases = AutoAliases
// hook the helper for getting enforced validation sets
snapstate.EnforcedValidationSets = EnforcedValidationSets
+ // hook the helper for saving current validation sets to the stack
+ snapstate.AddCurrentTrackingToValidationSetsStack = addCurrentTrackingToValidationSetsHistory
+ // hook the helper for restoring validation sets tracking from the stack
+ snapstate.RestoreValidationSetsTracking = RestoreValidationSetsTracking
}
// AutoRefreshAssertions tries to refresh all assertions
diff --git a/overlord/assertstate/validation_set_tracking.go b/overlord/assertstate/validation_set_tracking.go
index 5a85d26b45..c037158960 100644
--- a/overlord/assertstate/validation_set_tracking.go
+++ b/overlord/assertstate/validation_set_tracking.go
@@ -261,3 +261,19 @@ func ValidationSetsHistory(st *state.State) ([]map[string]*ValidationSetTracking
}
return vshist, nil
}
+
+// RestoreValidationSetsTracking restores validation-sets state to the last state
+// stored in the validation-sets-stack. It should only be called when the stack
+// is not empty, otherwise an error is returned.
+func RestoreValidationSetsTracking(st *state.State) error {
+ trackingState, err := validationSetsHistoryTop(st)
+ if err != nil {
+ return err
+ }
+ if len(trackingState) == 0 {
+ // we should never be called when there is nothing in the stack
+ return state.ErrNoState
+ }
+ st.Set("validation-sets", trackingState)
+ return nil
+}
diff --git a/overlord/assertstate/validation_set_tracking_test.go b/overlord/assertstate/validation_set_tracking_test.go
index 718ed79a13..11facaa4d4 100644
--- a/overlord/assertstate/validation_set_tracking_test.go
+++ b/overlord/assertstate/validation_set_tracking_test.go
@@ -409,3 +409,56 @@ func (s *validationSetTrackingSuite) TestAddToValidationSetsHistoryRemovesOldEnt
},
})
}
+
+func (s *validationSetTrackingSuite) TestRestoreValidationSetsTrackingNoHistory(c *C) {
+ s.st.Lock()
+ defer s.st.Unlock()
+
+ c.Assert(assertstate.RestoreValidationSetsTracking(s.st), Equals, state.ErrNoState)
+}
+
+func (s *validationSetTrackingSuite) TestRestoreValidationSetsTracking(c *C) {
+ s.st.Lock()
+ defer s.st.Unlock()
+
+ tr1 := assertstate.ValidationSetTracking{
+ AccountID: "foo",
+ Name: "bar",
+ Mode: assertstate.Enforce,
+ PinnedAt: 1,
+ Current: 2,
+ }
+ assertstate.UpdateValidationSet(s.st, &tr1)
+
+ c.Assert(assertstate.AddCurrentTrackingToValidationSetsHistory(s.st), IsNil)
+
+ all, err := assertstate.ValidationSets(s.st)
+ c.Assert(err, IsNil)
+ c.Assert(all, HasLen, 1)
+
+ tr2 := assertstate.ValidationSetTracking{
+ AccountID: "foo",
+ Name: "baz",
+ Mode: assertstate.Enforce,
+ Current: 5,
+ }
+ assertstate.UpdateValidationSet(s.st, &tr2)
+
+ all, err = assertstate.ValidationSets(s.st)
+ c.Assert(err, IsNil)
+ // two validation sets are now tracked
+ c.Check(all, DeepEquals, map[string]*assertstate.ValidationSetTracking{
+ "foo/bar": &tr1,
+ "foo/baz": &tr2,
+ })
+
+ // restore
+ c.Assert(assertstate.RestoreValidationSetsTracking(s.st), IsNil)
+
+ // and we're back at one validation set being tracked
+ all, err = assertstate.ValidationSets(s.st)
+ c.Assert(err, IsNil)
+ c.Check(all, DeepEquals, map[string]*assertstate.ValidationSetTracking{
+ "foo/bar": &tr1,
+ })
+}
diff --git a/overlord/configstate/configcore/certs.go b/overlord/configstate/configcore/certs.go
index 6e482bbd5c..f45727aafa 100644
--- a/overlord/configstate/configcore/certs.go
+++ b/overlord/configstate/configcore/certs.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !nomanagers
// +build !nomanagers
/*
diff --git a/overlord/configstate/configcore/cloud.go b/overlord/configstate/configcore/cloud.go
index a0cc2a901e..9bf050ef30 100644
--- a/overlord/configstate/configcore/cloud.go
+++ b/overlord/configstate/configcore/cloud.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !nomanagers
// +build !nomanagers
/*
diff --git a/overlord/configstate/configcore/handlers.go b/overlord/configstate/configcore/handlers.go
index b773fd125b..16ac8b28bd 100644
--- a/overlord/configstate/configcore/handlers.go
+++ b/overlord/configstate/configcore/handlers.go
@@ -101,6 +101,9 @@ func init() {
// when applying so there is no validation handler, see LP:1952740
addFSOnlyHandler(nil, handleHostnameConfiguration, coreOnly)
+ // tmpfs.size
+ addFSOnlyHandler(validateTmpfsSettings, handleTmpfsConfiguration, coreOnly)
+
sysconfig.ApplyFilesystemOnlyDefaultsImpl = filesystemOnlyApply
}
diff --git a/overlord/configstate/configcore/proxy.go b/overlord/configstate/configcore/proxy.go
index f6724c12e7..9c199f1fb9 100644
--- a/overlord/configstate/configcore/proxy.go
+++ b/overlord/configstate/configcore/proxy.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !nomanagers
// +build !nomanagers
/*
diff --git a/overlord/configstate/configcore/refresh.go b/overlord/configstate/configcore/refresh.go
index acf465f6d7..7b1bd8e878 100644
--- a/overlord/configstate/configcore/refresh.go
+++ b/overlord/configstate/configcore/refresh.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !nomanagers
// +build !nomanagers
/*
diff --git a/overlord/configstate/configcore/runwithstate.go b/overlord/configstate/configcore/runwithstate.go
index 7246fd5550..7dee3f6950 100644
--- a/overlord/configstate/configcore/runwithstate.go
+++ b/overlord/configstate/configcore/runwithstate.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !nomanagers
// +build !nomanagers
/*
diff --git a/overlord/configstate/configcore/snapshots.go b/overlord/configstate/configcore/snapshots.go
index 80b0a824d1..5e88f26287 100644
--- a/overlord/configstate/configcore/snapshots.go
+++ b/overlord/configstate/configcore/snapshots.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !nomanagers
// +build !nomanagers
/*
diff --git a/overlord/configstate/configcore/tmp.go b/overlord/configstate/configcore/tmp.go
new file mode 100644
index 0000000000..cc66fa797f
--- /dev/null
+++ b/overlord/configstate/configcore/tmp.go
@@ -0,0 +1,145 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2021 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package configcore
+
+import (
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/gadget/quantity"
+ "github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/overlord/configstate/config"
+ "github.com/snapcore/snapd/sysconfig"
+)
+
+const (
+ mntStaticOptions = "mode=1777,strictatime,nosuid,nodev"
+ tmpfsMountPoint = "/tmp"
+ tmpMntServOverrideSubDir = "tmp.mount.d"
+ tmpMntServOverrideFile = "override.conf"
+)
+
+func init() {
+ // add supported configuration of this module
+ supportedConfigurations["core.tmp.size"] = true
+}
+
+func validTmpfsSize(sizeStr string) error {
+ if sizeStr == "" {
+ return nil
+ }
+
+ // TODO allow also percentages. That is allowed for CPU quotas so
+ // it is probably fine to allow that for tmp.size too.
+ size, err := quantity.ParseSize(sizeStr)
+ if err != nil {
+ return err
+ }
+
+ // Do not allow less than 16mb
+ // 0 is special and means unlimited
+ if size > 0 && size < 16*quantity.SizeMiB {
+ return fmt.Errorf("size is less than 16Mb")
+ }
+
+ return nil
+}
+
+func validateTmpfsSettings(tr config.ConfGetter) error {
+ tmpfsSz, err := coreCfg(tr, "tmp.size")
+ if err != nil {
+ return err
+ }
+
+ return validTmpfsSize(tmpfsSz)
+}
+
+func handleTmpfsConfiguration(_ sysconfig.Device, tr config.ConfGetter, opts *fsOnlyContext) error {
+ tmpfsSz, err := coreCfg(tr, "tmp.size")
+ if err != nil {
+ return err
+ }
+
+ // Create override configuration file for tmp.mount service
+
+ // Create /etc/systemd/system/tmp.mount.d if needed
+ var overrDir string
+ if opts == nil {
+ // runtime system
+ overrDir = dirs.SnapServicesDir
+ } else {
+ overrDir = dirs.SnapServicesDirUnder(opts.RootDir)
+ }
+ overrDir = filepath.Join(overrDir, tmpMntServOverrideSubDir)
+
+ // Write service config override if needed
+ options := mntStaticOptions
+ dirContent := make(map[string]osutil.FileState, 1)
+ cfgFilePath := filepath.Join(overrDir, tmpMntServOverrideFile)
+ modify := true
+ if tmpfsSz != "" {
+ if err := os.MkdirAll(overrDir, 0755); err != nil {
+ return err
+ }
+ options = fmt.Sprintf("%s,size=%s", options, tmpfsSz)
+ content := fmt.Sprintf("[Mount]\nOptions=%s\n", options)
+ dirContent[tmpMntServOverrideFile] = &osutil.MemoryFileState{
+ Content: []byte(content),
+ Mode: 0644,
+ }
+ oldContent, err := ioutil.ReadFile(cfgFilePath)
+ if err == nil && content == string(oldContent) {
+ modify = false
+ }
+ } else {
+ // Use default tmpfs size if empty setting (50%, see tmpfs(5))
+ options = fmt.Sprintf("%s,size=50%%", options)
+ // In this case, we are removing the file, so we will
+ // not do anything if the file is not there alreay.
+ if _, err := os.Stat(cfgFilePath); errors.Is(err, os.ErrNotExist) {
+ modify = false
+ }
+ }
+
+ // Re-starting the tmp.mount service will fail if some process
+ // is using a file in /tmp, so instead of doing that we use
+ // the remount option for the mount command, which will not
+ // fail in that case. There is however the possibility of a
+ // failure in case we are reducing the size to something
+ // smaller than the currently used space in the mount. We
+ // return an error in that case.
+ if opts == nil && modify {
+ if output, err := exec.Command("mount", "-o", "remount,"+options, tmpfsMountPoint).CombinedOutput(); err != nil {
+ return fmt.Errorf("cannot remount tmpfs with new size: %s (%s)", err.Error(), output)
+ }
+ }
+
+ glob := tmpMntServOverrideFile
+ if _, _, err = osutil.EnsureDirState(overrDir, glob, dirContent); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/overlord/configstate/configcore/tmp_test.go b/overlord/configstate/configcore/tmp_test.go
new file mode 100644
index 0000000000..fba5711a0d
--- /dev/null
+++ b/overlord/configstate/configcore/tmp_test.go
@@ -0,0 +1,224 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2021 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package configcore_test
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "time"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/overlord/configstate/configcore"
+ "github.com/snapcore/snapd/testutil"
+)
+
+type tmpfsSuite struct {
+ configcoreSuite
+
+ servOverridePath string
+ servOverrideDir string
+}
+
+var _ = Suite(&tmpfsSuite{})
+
+func (s *tmpfsSuite) SetUpTest(c *C) {
+ s.configcoreSuite.SetUpTest(c)
+
+ s.servOverrideDir = filepath.Join(dirs.SnapServicesDir, "tmp.mount.d")
+ s.servOverridePath = filepath.Join(s.servOverrideDir, "override.conf")
+}
+
+// Configure with different valid values
+func (s *tmpfsSuite) TestConfigureTmpfsGoodVals(c *C) {
+ expectedMountCalls := [][]string{}
+ mountCmd := testutil.MockCommand(c, "mount", "")
+ defer mountCmd.Restore()
+
+ for _, size := range []string{"104857600", "16M", "7G", "0"} {
+
+ err := configcore.Run(coreDev, &mockConf{
+ state: s.state,
+ conf: map[string]interface{}{
+ "tmp.size": size,
+ },
+ })
+ c.Assert(err, IsNil)
+
+ c.Check(s.servOverridePath, testutil.FileEquals,
+ fmt.Sprintf("[Mount]\nOptions=mode=1777,strictatime,nosuid,nodev,size=%s\n", size))
+ mntOpts := fmt.Sprintf("remount,mode=1777,strictatime,nosuid,nodev,size=%s", size)
+ expectedMountCalls = append(expectedMountCalls, []string{"mount", "-o", mntOpts, "/tmp"})
+ }
+
+ c.Check(s.systemctlArgs, HasLen, 0)
+ c.Check(mountCmd.Calls(), DeepEquals, expectedMountCalls)
+}
+
+// Configure with different invalid values
+func (s *tmpfsSuite) TestConfigureTmpfsBadVals(c *C) {
+ for _, size := range []string{"100p", "0x123", "10485f7600", "20%%",
+ "20%", "100m", "10k", "10K", "10g"} {
+
+ err := configcore.Run(coreDev, &mockConf{
+ state: s.state,
+ conf: map[string]interface{}{
+ "tmp.size": size,
+ },
+ })
+ c.Assert(err, ErrorMatches, `invalid suffix .*`)
+
+ _, err = os.Stat(s.servOverridePath)
+ c.Assert(os.IsNotExist(err), Equals, true)
+ }
+
+ c.Assert(s.systemctlArgs, IsNil)
+}
+
+func (s *tmpfsSuite) TestConfigureTmpfsTooSmall(c *C) {
+ for _, size := range []string{"1", "16777215"} {
+
+ err := configcore.Run(coreDev, &mockConf{
+ state: s.state,
+ conf: map[string]interface{}{
+ "tmp.size": size,
+ },
+ })
+ c.Assert(err, ErrorMatches, `size is less than 16Mb`)
+
+ _, err = os.Stat(s.servOverridePath)
+ c.Assert(os.IsNotExist(err), Equals, true)
+ }
+
+ c.Assert(s.systemctlArgs, IsNil)
+}
+
+// Ensure things are fine if destination folder already existed
+func (s *tmpfsSuite) TestConfigureTmpfsAllConfDirExistsAlready(c *C) {
+ mountCmd := testutil.MockCommand(c, "mount", "")
+ defer mountCmd.Restore()
+
+ // make tmp.mount.d directory already
+ err := os.MkdirAll(s.servOverrideDir, 0755)
+ c.Assert(err, IsNil)
+
+ size := "100M"
+ err = configcore.Run(coreDev, &mockConf{
+ state: s.state,
+ conf: map[string]interface{}{
+ "tmp.size": size,
+ },
+ })
+ c.Assert(err, IsNil)
+ c.Check(s.servOverridePath, testutil.FileEquals,
+ fmt.Sprintf("[Mount]\nOptions=mode=1777,strictatime,nosuid,nodev,size=%s\n", size))
+
+ c.Check(s.systemctlArgs, HasLen, 0)
+ c.Check(mountCmd.Calls(), DeepEquals,
+ [][]string{{"mount", "-o", "remount,mode=1777,strictatime,nosuid,nodev,size=100M", "/tmp"}})
+}
+
+// Test cfg file is not updated if we set the same size that is already set
+func (s *tmpfsSuite) TestConfigureTmpfsNoFileUpdate(c *C) {
+ err := os.MkdirAll(s.servOverrideDir, 0755)
+ c.Assert(err, IsNil)
+ size := "100M"
+ content := "[Mount]\nOptions=mode=1777,strictatime,nosuid,nodev,size=" + size + "\n"
+ err = ioutil.WriteFile(s.servOverridePath, []byte(content), 0644)
+ c.Assert(err, IsNil)
+
+ info, err := os.Stat(s.servOverridePath)
+ c.Assert(err, IsNil)
+
+ fileModTime := info.ModTime()
+
+ // To make sure the times will differ if the file is newly written
+ time.Sleep(100 * time.Millisecond)
+
+ err = configcore.Run(coreDev, &mockConf{
+ state: s.state,
+ conf: map[string]interface{}{
+ "tmp.size": size,
+ },
+ })
+ c.Assert(err, IsNil)
+ c.Check(s.servOverridePath, testutil.FileEquals, content)
+
+ info, err = os.Stat(s.servOverridePath)
+ c.Assert(err, IsNil)
+ c.Assert(info.ModTime(), Equals, fileModTime)
+
+ c.Check(s.systemctlArgs, HasLen, 0)
+}
+
+// Test that config file is removed when unsetting
+func (s *tmpfsSuite) TestConfigureTmpfsRemovesIfUnset(c *C) {
+ mountCmd := testutil.MockCommand(c, "mount", "")
+ defer mountCmd.Restore()
+
+ err := os.MkdirAll(s.servOverrideDir, 0755)
+ c.Assert(err, IsNil)
+
+ // add canary to ensure we don't touch other files
+ canary := filepath.Join(s.servOverrideDir, "05-canary.conf")
+ err = ioutil.WriteFile(canary, nil, 0644)
+ c.Assert(err, IsNil)
+
+ content := "[Mount]\nOptions=mode=1777,strictatime,nosuid,nodev,size=1G\n"
+ err = ioutil.WriteFile(s.servOverridePath, []byte(content), 0644)
+ c.Assert(err, IsNil)
+
+ err = configcore.Run(coreDev, &mockConf{
+ state: s.state,
+ conf: map[string]interface{}{
+ "tmp.size": "",
+ },
+ })
+ c.Assert(err, IsNil)
+
+ // ensure the file got deleted
+ c.Check(osutil.FileExists(s.servOverridePath), Equals, false)
+ // but the canary is still here
+ c.Check(osutil.FileExists(canary), Equals, true)
+
+ // the default was applied
+ c.Check(s.systemctlArgs, HasLen, 0)
+ c.Check(mountCmd.Calls(), DeepEquals,
+ [][]string{{"mount", "-o", "remount,mode=1777,strictatime,nosuid,nodev,size=50%", "/tmp"}})
+}
+
+// Test applying on image preparation
+func (s *tmpfsSuite) TestFilesystemOnlyApply(c *C) {
+ conf := configcore.PlainCoreConfig(map[string]interface{}{
+ "tmp.size": "16777216",
+ })
+
+ tmpDir := c.MkDir()
+ c.Assert(configcore.FilesystemOnlyApply(coreDev, tmpDir, conf), IsNil)
+
+ tmpfsOverrCfg := filepath.Join(tmpDir,
+ "/etc/systemd/system/tmp.mount.d/override.conf")
+ c.Check(tmpfsOverrCfg, testutil.FileEquals,
+ "[Mount]\nOptions=mode=1777,strictatime,nosuid,nodev,size=16777216\n")
+}
diff --git a/overlord/configstate/configcore/vitality.go b/overlord/configstate/configcore/vitality.go
index 63b7f95ee4..733775b0fb 100644
--- a/overlord/configstate/configcore/vitality.go
+++ b/overlord/configstate/configcore/vitality.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !nomanagers
// +build !nomanagers
/*
diff --git a/overlord/devicestate/devicemgr.go b/overlord/devicestate/devicemgr.go
index de97e10139..453452eba2 100644
--- a/overlord/devicestate/devicemgr.go
+++ b/overlord/devicestate/devicemgr.go
@@ -1754,7 +1754,12 @@ func (m *DeviceManager) StoreContextBackend() storecontext.Backend {
var timeutilIsNTPSynchronized = timeutil.IsNTPSynchronized
func (m *DeviceManager) ntpSyncedOrWaitedLongerThan(maxWait time.Duration) bool {
- if m.ntpSyncedOrTimedOut || time.Now().After(startTime.Add(maxWait)) {
+ if m.ntpSyncedOrTimedOut {
+ return true
+ }
+ if time.Now().After(startTime.Add(maxWait)) {
+ logger.Noticef("no NTP sync after %v, trying auto-refresh anyway", maxWait)
+ m.ntpSyncedOrTimedOut = true
return true
}
diff --git a/overlord/devicestate/devicestate.go b/overlord/devicestate/devicestate.go
index d40b32bcbd..847a3b6c55 100644
--- a/overlord/devicestate/devicestate.go
+++ b/overlord/devicestate/devicestate.go
@@ -328,14 +328,14 @@ func getAllRequiredSnapsForModel(model *asserts.Model) *naming.SnapSet {
return naming.NewSnapSet(reqSnaps)
}
-var errNoDownloadInstallEdge = fmt.Errorf("download and checks edge not found")
+var errNoBeforeLocalModificationsEdge = fmt.Errorf("before-local-modifications edge not found")
-// extractDownloadInstallEdgesFromTs extracts the first, last download
+// extractBeforeLocalModificationsEdgesTs extracts the first, last download
// phase and install phase tasks from a TaskSet
-func extractDownloadInstallEdgesFromTs(ts *state.TaskSet) (firstDl, lastDl, firstInst, lastInst *state.Task, err error) {
- edgeTask := ts.MaybeEdge(snapstate.DownloadAndChecksDoneEdge)
+func extractBeforeLocalModificationsEdgesTs(ts *state.TaskSet) (firstDl, lastDl, firstInst, lastInst *state.Task, err error) {
+ edgeTask := ts.MaybeEdge(snapstate.LastBeforeLocalModificationsEdge)
if edgeTask == nil {
- return nil, nil, nil, nil, errNoDownloadInstallEdge
+ return nil, nil, nil, nil, errNoBeforeLocalModificationsEdge
}
tasks := ts.Tasks()
// we know we always start with downloads
@@ -479,9 +479,11 @@ func remodelEssentialSnapTasks(ctx context.Context, st *state.State, ms modelSna
return nil, err
}
if ts != nil {
- if edgeTask := ts.MaybeEdge(snapstate.DownloadAndChecksDoneEdge); edgeTask != nil {
- // we have downloads and checks done edge, so
- // the update is not a simple
+ if edgeTask := ts.MaybeEdge(snapstate.LastBeforeLocalModificationsEdge); edgeTask != nil {
+ // no task is marked as being last
+ // before local modifications are
+ // introduced, indicating that the
+ // update is a simple
// switch-snap-channel
return ts, nil
} else {
@@ -632,7 +634,9 @@ func remodelTasks(ctx context.Context, st *state.State, current, new *asserts.Mo
// Terminology
// A <- B means B waits for A
// "download,verify" are part of the "Download" phase
- // "link,start" is part of "Install" phase
+ // "link,start" is part of "Install" phase which introduces
+ // system modifications. The last task of the "Download" phase
+ // is marked with LastBeforeLocalModificationsEdge.
//
// - all tasks inside ts{Download,Install} already wait for
// each other so the chains look something like this:
@@ -649,15 +653,16 @@ func remodelTasks(ctx context.Context, st *state.State, current, new *asserts.Mo
// verify2 <- download3 (added)
// install1 <- install2 (added)
// install2 <- install3 (added)
- downloadStart, downloadLast, installFirst, installLast, err := extractDownloadInstallEdgesFromTs(ts)
+ downloadStart, downloadLast, installFirst, installLast, err := extractBeforeLocalModificationsEdgesTs(ts)
if err != nil {
- if err == errNoDownloadInstallEdge {
+ if err == errNoBeforeLocalModificationsEdge {
// there is no task in the task set marked with
- // download edges, which can happen when there
- // is a simple channel switch if the snap which
- // is part of remodel has the same revision in
- // the current channel and one that will be used
- // after remodel
+ // as being last before system modification
+ // edge, which can happen when there is a simple
+ // channel switch if the snap which is part of
+ // remodel has the same revision in the current
+ // channel and one that will be used after
+ // remodel
continue
}
return nil, fmt.Errorf("cannot remodel: %v", err)
diff --git a/overlord/devicestate/devicestate_remodel_test.go b/overlord/devicestate/devicestate_remodel_test.go
index 0f029356e9..094e4aebec 100644
--- a/overlord/devicestate/devicestate_remodel_test.go
+++ b/overlord/devicestate/devicestate_remodel_test.go
@@ -264,7 +264,7 @@ func (s *deviceMgrRemodelSuite) testRemodelTasksSwitchTrack(c *C, whatRefreshes
tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
tInstall.WaitFor(tValidate)
ts := state.NewTaskSet(tDownload, tValidate, tInstall)
- ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
+ ts.MarkEdge(tValidate, snapstate.LastBeforeLocalModificationsEdge)
return ts, nil
})
defer restore()
@@ -283,7 +283,7 @@ func (s *deviceMgrRemodelSuite) testRemodelTasksSwitchTrack(c *C, whatRefreshes
tUpdate := s.state.NewTask("fake-update", fmt.Sprintf("Update %s to track %s", name, opts.Channel))
tUpdate.WaitFor(tValidate)
ts := state.NewTaskSet(tDownload, tValidate, tUpdate)
- ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
+ ts.MarkEdge(tValidate, snapstate.LastBeforeLocalModificationsEdge)
return ts, nil
})
defer restore()
@@ -359,7 +359,7 @@ func (s *deviceMgrRemodelSuite) testRemodelSwitchTasks(c *C, whatsNew, whatNewTr
tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
tInstall.WaitFor(tValidate)
ts := state.NewTaskSet(tDownload, tValidate, tInstall)
- ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
+ ts.MarkEdge(tValidate, snapstate.LastBeforeLocalModificationsEdge)
return ts, nil
})
defer restore()
@@ -417,7 +417,7 @@ func (s *deviceMgrRemodelSuite) TestRemodelRequiredSnaps(c *C) {
tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
tInstall.WaitFor(tValidate)
ts := state.NewTaskSet(tDownload, tValidate, tInstall)
- ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
+ ts.MarkEdge(tValidate, snapstate.LastBeforeLocalModificationsEdge)
return ts, nil
})
defer restore()
@@ -529,7 +529,7 @@ func (s *deviceMgrRemodelSuite) TestRemodelSwitchKernelTrack(c *C) {
tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
tInstall.WaitFor(tValidate)
ts := state.NewTaskSet(tDownload, tValidate, tInstall)
- ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
+ ts.MarkEdge(tValidate, snapstate.LastBeforeLocalModificationsEdge)
return ts, nil
})
defer restore()
@@ -546,7 +546,7 @@ func (s *deviceMgrRemodelSuite) TestRemodelSwitchKernelTrack(c *C) {
tUpdate := s.state.NewTask("fake-update", fmt.Sprintf("Update %s to track %s", name, opts.Channel))
tUpdate.WaitFor(tValidate)
ts := state.NewTaskSet(tDownload, tValidate, tUpdate)
- ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
+ ts.MarkEdge(tValidate, snapstate.LastBeforeLocalModificationsEdge)
return ts, nil
})
defer restore()
@@ -694,7 +694,7 @@ func (s *deviceMgrRemodelSuite) TestRemodelStoreSwitch(c *C) {
tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
tInstall.WaitFor(tValidate)
ts := state.NewTaskSet(tDownload, tValidate, tInstall)
- ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
+ ts.MarkEdge(tValidate, snapstate.LastBeforeLocalModificationsEdge)
return ts, nil
})
defer restore()
@@ -838,7 +838,7 @@ func (s *deviceMgrRemodelSuite) TestRemodelClash(c *C) {
tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
tInstall.WaitFor(tValidate)
ts := state.NewTaskSet(tDownload, tValidate, tInstall)
- ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
+ ts.MarkEdge(tValidate, snapstate.LastBeforeLocalModificationsEdge)
return ts, nil
})
defer restore()
@@ -909,7 +909,7 @@ func (s *deviceMgrRemodelSuite) TestRemodelClashInProgress(c *C) {
tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
tInstall.WaitFor(tValidate)
ts := state.NewTaskSet(tDownload, tValidate, tInstall)
- ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
+ ts.MarkEdge(tValidate, snapstate.LastBeforeLocalModificationsEdge)
return ts, nil
})
defer restore()
@@ -1408,7 +1408,7 @@ volumes:
})
tGadgetUpdate.WaitFor(tValidate)
ts := state.NewTaskSet(tDownload, tValidate, tGadgetUpdate)
- ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
+ ts.MarkEdge(tValidate, snapstate.LastBeforeLocalModificationsEdge)
return ts, nil
})
defer restore()
@@ -1558,7 +1558,7 @@ func (s *deviceMgrRemodelSuite) TestRemodelGadgetAssetsParanoidCheck(c *C) {
})
tGadgetUpdate.WaitFor(tValidate)
ts := state.NewTaskSet(tDownload, tValidate, tGadgetUpdate)
- ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
+ ts.MarkEdge(tValidate, snapstate.LastBeforeLocalModificationsEdge)
return ts, nil
})
defer restore()
@@ -1604,7 +1604,7 @@ func (s *deviceMgrSuite) TestRemodelSwitchBase(c *C) {
tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
tInstall.WaitFor(tValidate)
ts := state.NewTaskSet(tDownload, tValidate, tInstall)
- ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
+ ts.MarkEdge(tValidate, snapstate.LastBeforeLocalModificationsEdge)
return ts, nil
})
defer restore()
@@ -1658,7 +1658,7 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20RequiredSnapsAndRecoverySystem(c
tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
tInstall.WaitFor(tValidate)
ts := state.NewTaskSet(tDownload, tValidate, tInstall)
- ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
+ ts.MarkEdge(tValidate, snapstate.LastBeforeLocalModificationsEdge)
return ts, nil
})
defer restore()
@@ -1879,7 +1879,7 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelGadgetBaseSnaps(c *C)
tUpdate := s.state.NewTask("fake-update", fmt.Sprintf("Update %s to track %s", name, opts.Channel))
tUpdate.WaitFor(tValidate)
ts := state.NewTaskSet(tDownload, tValidate, tUpdate)
- ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
+ ts.MarkEdge(tValidate, snapstate.LastBeforeLocalModificationsEdge)
return ts, nil
})
defer restore()
@@ -2549,12 +2549,10 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseGadgetSnapsInstal
c.Assert(tLinkKernel.Summary(), Equals, `Make snap "pc-kernel-new" (222) available to the system during 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")
@@ -2568,19 +2566,29 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseGadgetSnapsInstal
c.Assert(tSetModel.Summary(), Equals, "Set new model assertion")
// check the ordering, prepare/link are part of download edge and come first
c.Assert(tSwitchChannelKernel.WaitTasks(), HasLen, 0)
- c.Check(tUpdateAssetsFromKernel.WaitTasks(), DeepEquals, []*state.Task{
+ c.Assert(tSwitchChannelBase.WaitTasks(), DeepEquals, []*state.Task{
tSwitchChannelKernel,
})
- c.Check(tLinkKernel.WaitTasks(), DeepEquals, []*state.Task{
- tUpdateAssetsFromKernel,
- })
- c.Assert(tLinkBase.WaitTasks(), DeepEquals, []*state.Task{
+ c.Assert(tSwitchChannelGadget.WaitTasks(), DeepEquals, []*state.Task{
tSwitchChannelBase,
})
- c.Assert(tCreateRecovery.WaitTasks(), DeepEquals, []*state.Task{})
+ c.Assert(tCreateRecovery.WaitTasks(), DeepEquals, []*state.Task{
+ tSwitchChannelGadget,
+ })
c.Assert(tFinalizeRecovery.WaitTasks(), DeepEquals, []*state.Task{
// recovery system being created
tCreateRecovery,
+ tSwitchChannelGadget,
+ })
+ c.Check(tUpdateAssetsFromKernel.WaitTasks(), DeepEquals, []*state.Task{
+ tSwitchChannelKernel, tSwitchChannelGadget,
+ tCreateRecovery, tFinalizeRecovery,
+ })
+ c.Check(tLinkKernel.WaitTasks(), DeepEquals, []*state.Task{
+ tUpdateAssetsFromKernel,
+ })
+ c.Assert(tLinkBase.WaitTasks(), DeepEquals, []*state.Task{
+ tSwitchChannelBase, tLinkKernel,
})
// setModel waits for everything in the change
c.Assert(tSetModel.WaitTasks(), DeepEquals, []*state.Task{
@@ -2596,8 +2604,12 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseGadgetSnapsInstal
c.Assert(systemSetupData, DeepEquals, map[string]interface{}{
"label": expectedLabel,
"directory": filepath.Join(boot.InitramfsUbuntuSeedDir, "systems", expectedLabel),
- // none of the tasks are downloads so they were not tracked
- "snap-setup-tasks": nil,
+ // tasks carrying snap-setup are tracked
+ "snap-setup-tasks": []interface{}{
+ tSwitchChannelKernel.ID(),
+ tSwitchChannelBase.ID(),
+ tSwitchChannelGadget.ID(),
+ },
})
}
@@ -2618,7 +2630,7 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna
tUpdate := s.state.NewTask("fake-update", fmt.Sprintf("Update %s to track %s", name, opts.Channel))
tUpdate.WaitFor(tValidate)
ts := state.NewTaskSet(tDownload, tValidate, tUpdate)
- ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
+ ts.MarkEdge(tValidate, snapstate.LastBeforeLocalModificationsEdge)
return ts, nil
})
defer restore()
@@ -3675,7 +3687,7 @@ func (s *deviceMgrRemodelSuite) testUC20RemodelSetModel(c *C, tc uc20RemodelSetM
tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
tInstall.WaitFor(tValidate)
ts := state.NewTaskSet(tDownload, tValidate, tInstall)
- ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
+ ts.MarkEdge(tValidate, snapstate.LastBeforeLocalModificationsEdge)
return ts, nil
})
defer restore()
@@ -3954,7 +3966,7 @@ func (s *deviceMgrRemodelSuite) TestUC20RemodelSetModelWithReboot(c *C) {
tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
tInstall.WaitFor(tValidate)
ts := state.NewTaskSet(tDownload, tValidate, tInstall)
- ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
+ ts.MarkEdge(tValidate, snapstate.LastBeforeLocalModificationsEdge)
return ts, nil
})
defer restore()
diff --git a/overlord/devicestate/handlers_test.go b/overlord/devicestate/handlers_test.go
index ec1794ed05..01b6663d23 100644
--- a/overlord/devicestate/handlers_test.go
+++ b/overlord/devicestate/handlers_test.go
@@ -332,7 +332,7 @@ func (s *deviceMgrSuite) TestDoPrepareRemodeling(c *C) {
tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
tInstall.WaitFor(tValidate)
ts := state.NewTaskSet(tDownload, tValidate, tInstall)
- ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
+ ts.MarkEdge(tValidate, snapstate.LastBeforeLocalModificationsEdge)
return ts, nil
})
defer restore()
diff --git a/overlord/managers_test.go b/overlord/managers_test.go
index 5ae037ba23..759259bcd7 100644
--- a/overlord/managers_test.go
+++ b/overlord/managers_test.go
@@ -1953,7 +1953,7 @@ type: os
st.Lock()
c.Assert(err, IsNil)
- // final steps will are post poned until we are in the restarted snapd
+ // final steps will are postponed until we are in the restarted snapd
ok, rst := restart.Pending(st)
c.Assert(ok, Equals, true)
c.Assert(rst, Equals, restart.RestartSystem)
@@ -1970,10 +1970,9 @@ type: os
"snap_mode": boot.TryStatus,
})
- // simulate successful restart happened
- restart.MockPending(st, restart.RestartUnset)
- bloader.BootVars["snap_mode"] = boot.DefaultStatus
- bloader.SetBootBase("core_x1.snap")
+ // simulate successful restart happened, technically "core" is of type
+ // "os", but for the purpose of the mock it is handled like a base
+ s.mockSuccessfulReboot(c, bloader, []snap.Type{snap.TypeBase})
st.Unlock()
err = s.o.Settle(settleTimeout)
@@ -1981,6 +1980,99 @@ type: os
c.Assert(err, IsNil)
c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err()))
+
+ c.Assert(bloader.BootVars, DeepEquals, map[string]string{
+ "snap_core": "core_x1.snap",
+ "snap_try_core": "",
+ "snap_try_kernel": "",
+ "snap_mode": "",
+ })
+}
+
+func (s *mgrsSuite) TestInstallCoreSnapUpdatesBootloaderEnvAndFailWithRollback(c *C) {
+ bloader := boottest.MockUC16Bootenv(bootloadertest.Mock("mock", c.MkDir()))
+ bootloader.Force(bloader)
+ defer bootloader.Force(nil)
+ bloader.SetBootBase("core_99.snap")
+
+ restore := release.MockOnClassic(false)
+ defer restore()
+
+ model := s.brands.Model("my-brand", "my-model", modelDefaults)
+
+ const packageOS = `
+name: core
+version: 16.04-1
+type: os
+`
+ snapPath := makeTestSnap(c, packageOS)
+
+ st := s.o.State()
+ st.Lock()
+ defer st.Unlock()
+
+ // setup model assertion
+ assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...)
+ devicestatetest.SetDevice(st, &auth.DeviceState{
+ Brand: "my-brand",
+ Model: "my-model",
+ Serial: "serialserialserial",
+ })
+ err := assertstate.Add(st, model)
+ c.Assert(err, IsNil)
+
+ ts, _, err := snapstate.InstallPath(st, &snap.SideInfo{RealName: "core"}, snapPath, "", "", snapstate.Flags{})
+ c.Assert(err, IsNil)
+ chg := st.NewChange("install-snap", "...")
+ chg.AddAll(ts)
+
+ st.Unlock()
+ err = s.o.Settle(settleTimeout)
+ st.Lock()
+ c.Assert(err, IsNil)
+
+ // final steps will be postponed until we are in the restarted snapd
+ ok, rst := restart.Pending(st)
+ c.Assert(ok, Equals, true)
+ c.Assert(rst, Equals, restart.RestartSystem)
+
+ t := findKind(chg, "auto-connect")
+ c.Assert(t, NotNil)
+ c.Assert(t.Status(), Equals, state.DoingStatus, Commentf("install-snap change failed with: %v", chg.Err()))
+
+ // this is already set
+ c.Assert(bloader.BootVars, DeepEquals, map[string]string{
+ "snap_core": "core_99.snap",
+ "snap_try_core": "core_x1.snap",
+ "snap_try_kernel": "",
+ "snap_mode": boot.TryStatus,
+ })
+
+ // simulate a reboot in which bootloader updates the env
+ s.mockRollbackAcrossReboot(c, bloader, []snap.Type{snap.TypeBase})
+
+ 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)
+
+ c.Assert(chg.Status(), Equals, state.ErrorStatus, Commentf("install-snap change did not fail"))
+ tLink := findKind(chg, "link-snap")
+ c.Assert(tLink, NotNil)
+ c.Assert(tLink.Status(), Equals, state.UndoneStatus)
+
+ c.Assert(bloader.BootVars, DeepEquals, map[string]string{
+ "snap_core": "core_99.snap",
+ "snap_try_core": "",
+ "snap_try_kernel": "",
+ "snap_mode": "",
+ })
}
type rebootEnv interface {
@@ -2110,6 +2202,14 @@ type: kernel`
c.Assert(err, IsNil)
c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err()))
+
+ c.Assert(bloader.BootVars, DeepEquals, map[string]string{
+ "snap_core": "core18_2.snap",
+ "snap_try_core": "",
+ "snap_kernel": "pc-kernel_x1.snap",
+ "snap_try_kernel": "",
+ "snap_mode": "",
+ })
}
func (s *mgrsSuite) TestInstallKernelSnapUndoUpdatesBootloaderEnv(c *C) {
@@ -2219,6 +2319,23 @@ type: kernel`
})
restarting, _ = restart.Pending(st)
c.Check(restarting, Equals, true)
+
+ // pretend we restarted back to the old kernel
+ s.mockSuccessfulReboot(c, bloader, []snap.Type{snap.TypeKernel})
+
+ st.Unlock()
+ err = s.o.Settle(settleTimeout)
+ st.Lock()
+ c.Assert(err, IsNil)
+
+ // and we undo the bootvars and trigger a reboot
+ c.Check(bloader.BootVars, DeepEquals, map[string]string{
+ "snap_core": "core18_2.snap",
+ "snap_try_core": "",
+ "snap_try_kernel": "",
+ "snap_kernel": "pc-kernel_123.snap",
+ "snap_mode": "",
+ })
}
func (s *mgrsSuite) TestInstallKernelSnap20UpdatesBootloaderEnv(c *C) {
@@ -6207,7 +6324,7 @@ base: core22
const oldPcGadgetSnapYaml = `
version: 1.0
-name: pc
+name: old-pc
type: gadget
base: core20
`
@@ -7481,6 +7598,170 @@ func (s *mgrsSuite) TestRemodelUC20BackToPreviousGadget(c *C) {
c.Check(i, Equals, len(tasks))
}
+func (s *mgrsSuite) TestRemodelUC20ExistingGadgetSnapDifferentChannel(c *C) {
+ // a remodel where the target model uses a gadget that is already
+ // present (possibly due to being used by one of the previous models)
+ // but tracks a different channel than what the new model ordains
+ 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)
+
+ snapInfo := s.makeInstalledSnapInStateForRemodel(c, "old-pc", snap.R(1), "20/beta")
+ // there already is a snap revision assertion for this snap, just serve
+ // it in the mock store
+ s.serveSnap(snapInfo.MountFile(), "1")
+
+ 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(`Switch snap "old-pc" from channel "20/beta" to "20/edge"`))
+ 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/snapshotstate/backend/export_test.go b/overlord/snapshotstate/backend/export_test.go
index 03ab11ffb5..aa33050d1b 100644
--- a/overlord/snapshotstate/backend/export_test.go
+++ b/overlord/snapshotstate/backend/export_test.go
@@ -48,14 +48,6 @@ func MockIsTesting(newIsTesting bool) func() {
}
}
-func MockUserLookupId(newLookupId func(string) (*user.User, error)) func() {
- oldLookupId := userLookupId
- userLookupId = newLookupId
- return func() {
- userLookupId = oldLookupId
- }
-}
-
func MockOsOpen(newOsOpen func(string) (*os.File, error)) func() {
oldOsOpen := osOpen
osOpen = newOsOpen
diff --git a/overlord/snapshotstate/backend/helpers.go b/overlord/snapshotstate/backend/helpers.go
index 0099e7d9b0..95eab16e6a 100644
--- a/overlord/snapshotstate/backend/helpers.go
+++ b/overlord/snapshotstate/backend/helpers.go
@@ -27,9 +27,7 @@ import (
"os/exec"
"os/user"
"path/filepath"
- "strconv"
"strings"
- "syscall"
"github.com/snapcore/snapd/client"
"github.com/snapcore/snapd/dirs"
@@ -97,7 +95,7 @@ var (
func usersForUsernamesImpl(usernames []string, opts *dirs.SnapDirOptions) ([]*user.User, error) {
if len(usernames) == 0 {
- return allUsers(opts)
+ return snap.AllUsers(opts)
}
users := make([]*user.User, 0, len(usernames))
for _, username := range usernames {
@@ -133,59 +131,6 @@ func usersForUsernamesImpl(usernames []string, opts *dirs.SnapDirOptions) ([]*us
return users, nil
}
-func allUsers(opts *dirs.SnapDirOptions) ([]*user.User, error) {
- ds, err := filepath.Glob(snap.DataHomeGlob(opts))
- if err != nil {
- // can't happen?
- return nil, err
- }
-
- users := make([]*user.User, 1, len(ds)+1)
- root, err := user.LookupId("0")
- if err != nil {
- return nil, err
- }
- users[0] = root
- seen := make(map[uint32]bool, len(ds)+1)
- seen[0] = true
- var st syscall.Stat_t
- for _, d := range ds {
- err := syscall.Stat(d, &st)
- if err != nil {
- continue
- }
- if seen[st.Uid] {
- continue
- }
- seen[st.Uid] = true
- usr, err := userLookupId(strconv.FormatUint(uint64(st.Uid), 10))
- if err != nil {
- // Treat all non-nil errors as user.Unknown{User,Group}Error's, as
- // currently Go's handling of returned errno from get{pw,gr}nam_r
- // in the cgo implementation of user.Lookup is lacking, and thus
- // user.Unknown{User,Group}Error is returned only when errno is 0
- // and the list of users/groups is empty, but as per the man page
- // for get{pw,gr}nam_r, there are many other errno's that typical
- // systems could return to indicate that the user/group wasn't
- // found, however unfortunately the POSIX standard does not actually
- // dictate what errno should be used to indicate "user/group not
- // found", and so even if Go is more robust, it may not ever be
- // fully robust. See from the man page:
- //
- // > It [POSIX.1-2001] does not call "not found" an error, hence
- // > does not specify what value errno might have in this situation.
- // > But that makes it impossible to recognize errors.
- //
- // See upstream Go issue: https://github.com/golang/go/issues/40334
- continue
- } else {
- users = append(users, usr)
- }
- }
-
- return users, nil
-}
-
var (
sysGeteuid = sys.Geteuid
execLookPath = exec.LookPath
diff --git a/overlord/snapstate/backend.go b/overlord/snapstate/backend.go
index d70e02cdc0..b35c62859d 100644
--- a/overlord/snapstate/backend.go
+++ b/overlord/snapstate/backend.go
@@ -83,7 +83,7 @@ type managerBackend interface {
UndoSetupSnap(s snap.PlaceInfo, typ snap.Type, installRecord *backend.InstallRecord, dev boot.Device, meter progress.Meter) error
UndoCopySnapData(newSnap, oldSnap *snap.Info, meter progress.Meter, opts *dirs.SnapDirOptions) error
// cleanup
- ClearTrashedData(oldSnap *snap.Info, opts *dirs.SnapDirOptions)
+ ClearTrashedData(oldSnap *snap.Info)
// remove related
UnlinkSnap(info *snap.Info, linkCtx backend.LinkContext, meter progress.Meter) error
@@ -108,4 +108,8 @@ type managerBackend interface {
RunInhibitSnapForUnlink(info *snap.Info, hint runinhibit.Hint, decision func() error) (*osutil.FileLock, error)
// (not a backend method because doInstall cannot access the backend)
// WithSnapLock(info *snap.Info, action func() error) error
+
+ // ~/.snap/data migration related
+ HideSnapData(snapName string) error
+ UndoHideSnapData(snapName string) error
}
diff --git a/overlord/snapstate/backend/copydata.go b/overlord/snapstate/backend/copydata.go
index 657f520afa..36baf5cb2a 100644
--- a/overlord/snapstate/backend/copydata.go
+++ b/overlord/snapstate/backend/copydata.go
@@ -20,18 +20,24 @@
package backend
import (
+ "errors"
+ "fmt"
+ "io/ioutil"
"os"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/logger"
+ "github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/progress"
"github.com/snapcore/snapd/snap"
)
+var allUsers = snap.AllUsers
+
// CopySnapData makes a copy of oldSnap data for newSnap in its data directories.
func (b Backend) CopySnapData(newSnap, oldSnap *snap.Info, meter progress.Meter, opts *dirs.SnapDirOptions) error {
// deal with the old data or
- // otherwise just create a empty data dir
+ // otherwise just create an empty data dir
// Make sure the base data directory exists for instance snaps
if newSnap.InstanceKey != "" {
@@ -86,16 +92,137 @@ func (b Backend) UndoCopySnapData(newInfo, oldInfo *snap.Info, _ progress.Meter,
}
// ClearTrashedData removes the trash. It returns no errors on the assumption that it is called very late in the game.
-func (b Backend) ClearTrashedData(oldSnap *snap.Info, opts *dirs.SnapDirOptions) {
- dirs, err := snapDataDirs(oldSnap, opts)
+func (b Backend) ClearTrashedData(oldSnap *snap.Info) {
+ dataDirs, err := snapDataDirs(oldSnap, nil)
if err != nil {
logger.Noticef("Cannot remove previous data for %q: %v", oldSnap.InstanceName(), err)
return
}
- for _, d := range dirs {
+ opts := &dirs.SnapDirOptions{HiddenSnapDataDir: true}
+ hiddenDirs, err := snapDataDirs(oldSnap, opts)
+ if err != nil {
+ logger.Noticef("Cannot remove previous data for %q: %v", oldSnap.InstanceName(), err)
+ return
+ }
+
+ // this will have duplicates but the second remove will just be ignored
+ dataDirs = append(dataDirs, hiddenDirs...)
+ for _, d := range dataDirs {
if err := clearTrash(d); err != nil {
logger.Noticef("Cannot remove %s: %v", d, err)
}
}
}
+
+func (b Backend) HideSnapData(snapName string) error {
+ postMigrationOpts := &dirs.SnapDirOptions{HiddenSnapDataDir: true}
+
+ users, err := allUsers(nil)
+ if err != nil {
+ return err
+ }
+
+ for _, usr := range users {
+ uid, gid, err := osutil.UidGid(usr)
+ if err != nil {
+ return err
+ }
+
+ // nothing to migrate
+ oldSnapDir := snap.UserSnapDir(usr.HomeDir, snapName, nil)
+ if _, err := os.Stat(oldSnapDir); errors.Is(err, os.ErrNotExist) {
+ continue
+ } else if err != nil {
+ return fmt.Errorf("cannot stat snap dir %q: %w", oldSnapDir, err)
+ }
+
+ // create the new hidden snap dir
+ hiddenSnapDir := snap.SnapDir(usr.HomeDir, postMigrationOpts)
+ if err := osutil.MkdirAllChown(hiddenSnapDir, 0700, uid, gid); err != nil {
+ return fmt.Errorf("cannot create snap dir %q: %w", hiddenSnapDir, err)
+ }
+
+ // move the snap's dir
+ newSnapDir := snap.UserSnapDir(usr.HomeDir, snapName, postMigrationOpts)
+ if err := osutil.AtomicRename(oldSnapDir, newSnapDir); err != nil {
+ return fmt.Errorf("cannot move %q to %q: %w", oldSnapDir, newSnapDir, err)
+ }
+
+ // remove ~/snap if it's empty
+ if err := removeIfEmpty(snap.SnapDir(usr.HomeDir, nil)); err != nil {
+ return fmt.Errorf("failed to remove old snap dir: %w", err)
+ }
+ }
+
+ return nil
+}
+
+func (b Backend) UndoHideSnapData(snapName string) error {
+ postMigrationOpts := &dirs.SnapDirOptions{HiddenSnapDataDir: true}
+
+ users, err := allUsers(postMigrationOpts)
+ if err != nil {
+ return err
+ }
+
+ var firstErr error
+ handle := func(err error) {
+ // keep going, restore previous state as much as possible
+ if firstErr == nil {
+ firstErr = err
+ } else {
+ logger.Noticef(err.Error())
+ }
+ }
+
+ for _, usr := range users {
+ uid, gid, err := osutil.UidGid(usr)
+ if err != nil {
+ handle(err)
+ continue
+ }
+
+ // skip it if wasn't migrated
+ hiddenSnapDir := snap.UserSnapDir(usr.HomeDir, snapName, postMigrationOpts)
+ if _, err := os.Stat(hiddenSnapDir); err != nil {
+ if !errors.Is(err, os.ErrNotExist) {
+ handle(fmt.Errorf("cannot read files in %q: %w", hiddenSnapDir, err))
+ }
+ continue
+ }
+
+ // ensure parent dirs exist
+ exposedDir := snap.SnapDir(usr.HomeDir, nil)
+ if err := osutil.MkdirAllChown(exposedDir, 0700, uid, gid); err != nil {
+ handle(fmt.Errorf("cannot create snap dir %q: %w", exposedDir, err))
+ continue
+ }
+
+ exposedSnapDir := snap.UserSnapDir(usr.HomeDir, snapName, nil)
+ if err := osutil.AtomicRename(hiddenSnapDir, exposedSnapDir); err != nil {
+ handle(fmt.Errorf("cannot move %q to %q: %w", hiddenSnapDir, exposedSnapDir, err))
+ }
+
+ // remove ~/.snap/data dir if empty
+ hiddenDir := snap.SnapDir(usr.HomeDir, postMigrationOpts)
+ if err := removeIfEmpty(hiddenDir); err != nil {
+ handle(fmt.Errorf("cannot remove dir %q: %w", hiddenDir, err))
+ }
+ }
+
+ return firstErr
+}
+
+var removeIfEmpty = func(dir string) error {
+ files, err := ioutil.ReadDir(dir)
+ if err != nil {
+ return err
+ }
+
+ if len(files) > 0 {
+ return nil
+ }
+
+ return os.Remove(dir)
+}
diff --git a/overlord/snapstate/backend/copydata_test.go b/overlord/snapstate/backend/copydata_test.go
index 5727899a36..422057a630 100644
--- a/overlord/snapstate/backend/copydata_test.go
+++ b/overlord/snapstate/backend/copydata_test.go
@@ -20,9 +20,11 @@
package backend_test
import (
+ "errors"
"fmt"
"io/ioutil"
"os"
+ "os/user"
"path/filepath"
"regexp"
"strconv"
@@ -30,6 +32,7 @@ import (
. "gopkg.in/check.v1"
"github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/overlord/snapstate/backend"
"github.com/snapcore/snapd/progress"
@@ -325,7 +328,7 @@ func (s *copydataSuite) testCopyDataDoABA(c *C, opts *dirs.SnapDirOptions) {
c.Check(s.populatedData("10.old"), Equals, "10\n")
// but cleanup cleans it up, huzzah
- s.be.ClearTrashedData(v1, opts)
+ s.be.ClearTrashedData(v1)
c.Check(s.populatedData("10.old"), Equals, "")
}
@@ -576,5 +579,270 @@ func (s *copydataSuite) TestUndoCopyDataSameRevision(c *C) {
} {
c.Check(osutil.FileExists(fn), Equals, true, Commentf(fn))
}
+}
+
+func (s *copydataSuite) TestHideSnapData(c *C) {
+ info := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)})
+
+ // mock user home
+ homedir := filepath.Join(s.tempdir, "home", "user")
+ usr, err := user.Current()
+ c.Assert(err, IsNil)
+ usr.HomeDir = homedir
+
+ restore := backend.MockAllUsers(func(*dirs.SnapDirOptions) ([]*user.User, error) {
+ return []*user.User{usr}, nil
+ })
+ defer restore()
+
+ // writes a file canary.home file to the rev dir of the "hello" snap
+ s.populateHomeData(c, "user", snap.R(10))
+
+ // write file in common
+ err = os.MkdirAll(info.UserCommonDataDir(homedir, nil), 0770)
+ c.Assert(err, IsNil)
+
+ commonFilePath := filepath.Join(info.UserCommonDataDir(homedir, nil), "file.txt")
+ err = ioutil.WriteFile(commonFilePath, []byte("some content"), 0640)
+ c.Assert(err, IsNil)
+
+ // make 'current' symlink
+ revDir := snap.UserDataDir(homedir, "hello", snap.R(10), nil)
+ // path must be relative, otherwise move would make it dangling
+ err = os.Symlink(filepath.Base(revDir), filepath.Join(revDir, "..", "current"))
+ c.Assert(err, IsNil)
+
+ err = s.be.HideSnapData("hello")
+ c.Assert(err, IsNil)
+
+ // check versioned file was moved
+ opts := &dirs.SnapDirOptions{HiddenSnapDataDir: true}
+ revFile := filepath.Join(info.UserDataDir(homedir, opts), "canary.home")
+ data, err := ioutil.ReadFile(revFile)
+ c.Assert(err, IsNil)
+ c.Assert(data, DeepEquals, []byte("10\n"))
+
+ // check common file was moved
+ commonFile := filepath.Join(info.UserCommonDataDir(homedir, opts), "file.txt")
+ data, err = ioutil.ReadFile(commonFile)
+ c.Assert(err, IsNil)
+ c.Assert(data, DeepEquals, []byte("some content"))
+
+ // check 'current' symlink has correct attributes and target
+ link := filepath.Join(homedir, dirs.HiddenSnapDataHomeDir, "hello", "current")
+ linkInfo, err := os.Lstat(link)
+ c.Assert(err, IsNil)
+ c.Assert(linkInfo.Mode()&os.ModeSymlink, Equals, os.ModeSymlink)
+
+ target, err := os.Readlink(link)
+ c.Assert(err, IsNil)
+ c.Assert(target, Equals, "10")
+
+ // check old '~/snap' folder was removed
+ _, err = os.Stat(snap.SnapDir(homedir, nil))
+ c.Assert(errors.Is(err, os.ErrNotExist), Equals, true)
+}
+
+func (s *copydataSuite) TestHideSnapDataSkipNoData(c *C) {
+ info := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)})
+
+ // mock user home
+ homedir := filepath.Join(s.tempdir, "home", "user")
+ usr, err := user.Current()
+ c.Assert(err, IsNil)
+ usr.HomeDir = homedir
+
+ // create user without snap dir (to be skipped)
+ usrNoSnapDir := &user.User{
+ HomeDir: filepath.Join(s.tempdir, "home", "other-user"),
+ Name: "other-user",
+ Uid: "1001",
+ Gid: "1001",
+ }
+ restore := backend.MockAllUsers(func(_ *dirs.SnapDirOptions) ([]*user.User, error) {
+ return []*user.User{usr, usrNoSnapDir}, nil
+ })
+ defer restore()
+
+ s.populateHomeData(c, "user", snap.R(10))
+
+ // make 'current' symlink
+ revDir := info.UserDataDir(homedir, nil)
+ linkPath := filepath.Join(revDir, "..", "current")
+ err = os.Symlink(revDir, linkPath)
+ c.Assert(err, IsNil)
+
+ // empty user dir is skipped
+ err = s.be.HideSnapData("hello")
+ c.Assert(err, IsNil)
+
+ // only the user with snap data was migrated
+ newSnapDir := filepath.Join(homedir, dirs.HiddenSnapDataHomeDir)
+ matches, err := filepath.Glob(dirs.HiddenSnapDataHomeGlob)
+ c.Assert(err, IsNil)
+ c.Assert(matches, HasLen, 1)
+ c.Assert(matches[0], Equals, newSnapDir)
+}
+
+func (s *copydataSuite) TestUndoHideSnapData(c *C) {
+ info := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)})
+
+ // mock user home dir
+ homedir := filepath.Join(s.tempdir, "home", "user")
+ usr, err := user.Current()
+ c.Assert(err, IsNil)
+ usr.HomeDir = homedir
+
+ restore := backend.MockAllUsers(func(_ *dirs.SnapDirOptions) ([]*user.User, error) {
+ return []*user.User{usr}, nil
+ })
+ defer restore()
+
+ // write file in revisioned dir
+ opts := &dirs.SnapDirOptions{HiddenSnapDataDir: true}
+ err = os.MkdirAll(info.UserDataDir(homedir, opts), 0770)
+ c.Assert(err, IsNil)
+
+ hiddenRevFile := filepath.Join(info.UserDataDir(homedir, opts), "file.txt")
+ err = ioutil.WriteFile(hiddenRevFile, []byte("some content"), 0640)
+ c.Assert(err, IsNil)
+
+ // write file in common
+ err = os.MkdirAll(info.UserCommonDataDir(homedir, opts), 0770)
+ c.Assert(err, IsNil)
+
+ hiddenCommonFile := filepath.Join(info.UserCommonDataDir(homedir, opts), "file.txt")
+ err = ioutil.WriteFile(hiddenCommonFile, []byte("other content"), 0640)
+ c.Assert(err, IsNil)
+
+ // make 'current' symlink
+ revDir := info.UserDataDir(homedir, opts)
+ // path must be relative otherwise the move would make it dangling
+ err = os.Symlink(filepath.Base(revDir), filepath.Join(revDir, "..", "current"))
+ c.Assert(err, IsNil)
+
+ // undo migration
+ err = s.be.UndoHideSnapData("hello")
+ c.Assert(err, IsNil)
+
+ // check versioned file was restored
+ revFile := filepath.Join(info.UserDataDir(homedir, nil), "file.txt")
+ data, err := ioutil.ReadFile(revFile)
+ c.Assert(err, IsNil)
+ c.Assert(data, DeepEquals, []byte("some content"))
+
+ // check common file was restored
+ commonFile := filepath.Join(info.UserCommonDataDir(homedir, nil), "file.txt")
+ data, err = ioutil.ReadFile(commonFile)
+ c.Assert(err, IsNil)
+ c.Assert(data, DeepEquals, []byte("other content"))
+
+ // check symlink points to revisioned dir
+ exposedDir := filepath.Join(homedir, dirs.UserHomeSnapDir)
+ target, err := os.Readlink(filepath.Join(exposedDir, "hello", "current"))
+ c.Assert(err, IsNil)
+ c.Assert(target, Equals, "10")
+
+ // ~/.snap/data was removed
+ _, err = os.Stat(snap.SnapDir(homedir, opts))
+ c.Assert(errors.Is(err, os.ErrNotExist), Equals, true)
+}
+
+func (s *copydataSuite) TestCleanupAfterCopyAndMigration(c *C) {
+ homedir := filepath.Join(s.tempdir, "home", "user")
+ usr, err := user.Current()
+ c.Assert(err, IsNil)
+ usr.HomeDir = homedir
+
+ restore := backend.MockAllUsers(func(_ *dirs.SnapDirOptions) ([]*user.User, error) {
+ return []*user.User{usr}, nil
+ })
+ defer restore()
+
+ // add trashed data in exposed dir
+ s.populateHomeData(c, "user", snap.R(10))
+ v1 := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)})
+ exposedTrash := filepath.Join(homedir, "snap", "hello", "10.old")
+ c.Assert(os.MkdirAll(exposedTrash, 0770), IsNil)
+
+ // add trashed data in hidden dir
+ s.populateHomeDataWithSnapDir(c, "user", dirs.HiddenSnapDataHomeDir, snap.R(10))
+ hiddenTrash := filepath.Join(homedir, ".snap", "data", "hello", "10.old")
+ c.Assert(os.MkdirAll(exposedTrash, 0770), IsNil)
+
+ s.be.ClearTrashedData(v1)
+
+ // clear should remove both
+ exists, _, err := osutil.DirExists(exposedTrash)
+ c.Assert(err, IsNil)
+ c.Assert(exists, Equals, false)
+
+ exists, _, err = osutil.DirExists(hiddenTrash)
+ c.Assert(err, IsNil)
+ c.Assert(exists, Equals, false)
+}
+
+func (s *copydataSuite) TestRemoveIfEmpty(c *C) {
+ file := filepath.Join(s.tempdir, "random")
+ c.Assert(ioutil.WriteFile(file, []byte("stuff"), 0664), IsNil)
+
+ // dir contains a file, shouldn't do anything
+ c.Assert(backend.RemoveIfEmpty(s.tempdir), IsNil)
+ files, err := ioutil.ReadDir(s.tempdir)
+ c.Assert(err, IsNil)
+ c.Check(files, HasLen, 1)
+ c.Check(filepath.Join(s.tempdir, files[0].Name()), testutil.FileEquals, "stuff")
+
+ c.Assert(os.Remove(file), IsNil)
+
+ // dir is empty, should be removed
+ c.Assert(backend.RemoveIfEmpty(s.tempdir), IsNil)
+ c.Assert(osutil.FileExists(file), Equals, false)
+}
+
+func (s *copydataSuite) TestUndoHideKeepGoingPreserveFirstErr(c *C) {
+ firstTime := true
+ restore := backend.MockRemoveIfEmpty(func(dir string) error {
+ var err error
+ if firstTime {
+ err = errors.New("first error")
+ firstTime = false
+ } else {
+ err = errors.New("other error")
+ }
+
+ return err
+ })
+ defer restore()
+
+ info := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)})
+
+ // mock two users so that the undo is done twice
+ var usrs []*user.User
+ for _, usrName := range []string{"usr1", "usr2"} {
+ homedir := filepath.Join(s.tempdir, "home", usrName)
+ usr, err := user.Current()
+ c.Assert(err, IsNil)
+ usr.HomeDir = homedir
+
+ opts := &dirs.SnapDirOptions{HiddenSnapDataDir: true}
+ err = os.MkdirAll(info.UserDataDir(homedir, opts), 0770)
+ c.Assert(err, IsNil)
+
+ usrs = append(usrs, usr)
+ }
+
+ restUsers := backend.MockAllUsers(func(_ *dirs.SnapDirOptions) ([]*user.User, error) {
+ return usrs, nil
+ })
+ defer restUsers()
+
+ buf, restLogger := logger.MockLogger()
+ defer restLogger()
+ err := s.be.UndoHideSnapData("hello")
+ // the first error is returned
+ c.Assert(err, ErrorMatches, `cannot remove dir ".*": first error`)
+ // the undo keeps going and logs the next error
+ c.Assert(buf, Matches, `.*cannot remove dir ".*": other error\n`)
}
diff --git a/overlord/snapstate/backend/export_test.go b/overlord/snapstate/backend/export_test.go
index d11379922d..53f54323cd 100644
--- a/overlord/snapstate/backend/export_test.go
+++ b/overlord/snapstate/backend/export_test.go
@@ -21,11 +21,15 @@ package backend
import (
"os/exec"
+ "os/user"
+
+ "github.com/snapcore/snapd/dirs"
)
var (
AddMountUnit = addMountUnit
RemoveMountUnit = removeMountUnit
+ RemoveIfEmpty = removeIfEmpty
)
func MockUpdateFontconfigCaches(f func() error) (restore func()) {
@@ -43,3 +47,20 @@ func MockCommandFromSystemSnap(f func(string, ...string) (*exec.Cmd, error)) (re
commandFromSystemSnap = old
}
}
+
+func MockAllUsers(f func(options *dirs.SnapDirOptions) ([]*user.User, error)) func() {
+ old := allUsers
+ allUsers = f
+ return func() {
+ allUsers = old
+ }
+
+}
+
+func MockRemoveIfEmpty(f func(dir string) error) func() {
+ old := removeIfEmpty
+ removeIfEmpty = f
+ return func() {
+ removeIfEmpty = old
+ }
+}
diff --git a/overlord/snapstate/backend_test.go b/overlord/snapstate/backend_test.go
index 9eda7b741c..f9511b906d 100644
--- a/overlord/snapstate/backend_test.go
+++ b/overlord/snapstate/backend_test.go
@@ -905,7 +905,7 @@ apps:
return info, nil
}
-func (f *fakeSnappyBackend) ClearTrashedData(si *snap.Info, opts *dirs.SnapDirOptions) {
+func (f *fakeSnappyBackend) ClearTrashedData(si *snap.Info) {
f.appendOp(&fakeOp{
op: "cleanup-trash",
name: si.InstanceName(),
@@ -1239,6 +1239,16 @@ func (f *fakeSnappyBackend) RunInhibitSnapForUnlink(info *snap.Info, hint runinh
return osutil.NewFileLock(filepath.Join(f.lockDir, info.InstanceName()+".lock"))
}
+func (f *fakeSnappyBackend) HideSnapData(snapName string) error {
+ f.appendOp(&fakeOp{op: "hide-snap-data", name: snapName})
+ return f.maybeErrForLastOp()
+}
+
+func (f *fakeSnappyBackend) UndoHideSnapData(snapName string) error {
+ f.appendOp(&fakeOp{op: "undo-hide-snap-data", name: snapName})
+ return f.maybeErrForLastOp()
+}
+
func (f *fakeSnappyBackend) appendOp(op *fakeOp) {
f.mu.Lock()
defer f.mu.Unlock()
diff --git a/overlord/snapstate/export_test.go b/overlord/snapstate/export_test.go
index c2b309c739..d68604f0ee 100644
--- a/overlord/snapstate/export_test.go
+++ b/overlord/snapstate/export_test.go
@@ -212,6 +212,8 @@ func MockAsyncPendingRefreshNotification(fn func(context.Context, *userclient.Cl
var (
RefreshedSnaps = refreshedSnaps
ReRefreshFilter = reRefreshFilter
+
+ MaybeRestoreValidationSetsAndRevertSnaps = maybeRestoreValidationSetsAndRevertSnaps
)
type UpdateFilter = updateFilter
@@ -366,3 +368,27 @@ func MockSnapsToRefresh(f func(gatingTask *state.Task) ([]*refreshCandidate, err
snapsToRefresh = old
}
}
+
+func MockAddCurrentTrackingToValidationSetsStack(f func(st *state.State) error) (restore func()) {
+ old := AddCurrentTrackingToValidationSetsStack
+ AddCurrentTrackingToValidationSetsStack = f
+ return func() {
+ AddCurrentTrackingToValidationSetsStack = old
+ }
+}
+
+func MockRestoreValidationSetsTracking(f func(*state.State) error) (restore func()) {
+ old := RestoreValidationSetsTracking
+ RestoreValidationSetsTracking = f
+ return func() {
+ RestoreValidationSetsTracking = old
+ }
+}
+
+func MockMaybeRestoreValidationSetsAndRevertSnaps(f func(st *state.State, refreshedSnaps []string) ([]*state.TaskSet, error)) (restore func()) {
+ old := maybeRestoreValidationSetsAndRevertSnaps
+ maybeRestoreValidationSetsAndRevertSnaps = f
+ return func() {
+ maybeRestoreValidationSetsAndRevertSnaps = old
+ }
+}
diff --git a/overlord/snapstate/handlers.go b/overlord/snapstate/handlers.go
index 3e206e0910..b463e80a86 100644
--- a/overlord/snapstate/handlers.go
+++ b/overlord/snapstate/handlers.go
@@ -34,6 +34,7 @@ import (
"gopkg.in/tomb.v2"
+ "github.com/snapcore/snapd/asserts/snapasserts"
"github.com/snapcore/snapd/boot"
"github.com/snapcore/snapd/cmd/snaplock/runinhibit"
"github.com/snapcore/snapd/dirs"
@@ -268,7 +269,7 @@ func (m *SnapManager) doPrerequisites(t *state.Task, _ *tomb.Tomb) error {
}
// if a previous version of snapd persisted Prereq only, fill the contentAttrs.
- // There will be not content attrs, so it will not update an outdated default provider
+ // There will be no content attrs, so it will not update an outdated default provider
if len(snapsup.PrereqContentAttrs) == 0 && len(snapsup.Prereq) != 0 {
snapsup.PrereqContentAttrs = make(map[string][]string, len(snapsup.Prereq))
@@ -1221,12 +1222,7 @@ func (m *SnapManager) cleanupCopySnapData(t *state.Task, _ *tomb.Tomb) error {
return err
}
- opts, err := GetSnapDirOptions(st)
- if err != nil {
- return err
- }
-
- m.backend.ClearTrashedData(info, opts)
+ m.backend.ClearTrashedData(info)
return nil
}
@@ -3149,13 +3145,14 @@ func changeReadyUpToTask(task *state.Task) bool {
}
// refreshedSnaps returns the instance names of the snaps successfully refreshed
-// in the last batch of refreshes before the given (re-refresh) task.
+// in the last batch of refreshes before the given (re-refresh) task; failed is
+// true if any of the snaps failed to refresh.
//
// It does this by advancing through the given task's change's tasks, keeping
// track of the instance names from the first SnapSetup in every lane, stopping
// when finding the given task, and resetting things when finding a different
// re-refresh task (that indicates the end of a batch that isn't the given one).
-func refreshedSnaps(reTask *state.Task) []string {
+func refreshedSnaps(reTask *state.Task) (snapNames []string, failed bool) {
// NOTE nothing requires reTask to be a check-rerefresh task, nor even to be in
// a refresh-ish change, but it doesn't make much sense to call this otherwise.
tid := reTask.ID()
@@ -3197,15 +3194,16 @@ func refreshedSnaps(reTask *state.Task) []string {
laneSnaps[lane] = snapsup.InstanceName()
}
- snapNames := make([]string, 0, len(laneSnaps))
+ snapNames = make([]string, 0, len(laneSnaps))
for _, name := range laneSnaps {
if name == "" {
// the lane was unsuccessful
+ failed = true
continue
}
snapNames = append(snapNames, name)
}
- return snapNames
+ return snapNames, failed
}
// reRefreshSetup holds the necessary details to re-refresh snaps that need it
@@ -3241,14 +3239,50 @@ func (m *SnapManager) doCheckReRefresh(t *state.Task, tomb *tomb.Tomb) error {
if !changeReadyUpToTask(t) {
return &state.Retry{After: reRefreshRetryTimeout, Reason: "pending refreshes"}
}
- snaps := refreshedSnaps(t)
+
+ snaps, failed := refreshedSnaps(t)
+ if len(snaps) > 0 {
+ if err := pruneRefreshCandidates(st, snaps...); err != nil {
+ return err
+ }
+ }
+
+ // if any snap failed to refresh, reconsider validation set tracking
+ if failed {
+ tasksets, err := maybeRestoreValidationSetsAndRevertSnaps(st, snaps)
+ if err != nil {
+ return err
+ }
+ if len(tasksets) > 0 {
+ chg := t.Change()
+ for _, taskset := range tasksets {
+ chg.AddAll(taskset)
+ }
+ st.EnsureBefore(0)
+ t.SetStatus(state.DoneStatus)
+ return nil
+ }
+ // else - validation sets tracking got restored or wasn't affected, carry on
+ }
+
if len(snaps) == 0 {
// nothing to do (maybe everything failed)
return nil
}
- if err := pruneRefreshCandidates(st, snaps...); err != nil {
- return err
+ // update validation sets stack: there are two possibilities
+ // - if maybeRestoreValidationSetsAndRevertSnaps restored previous tracking
+ // or refresh succeeded and it hasn't changed then this is a noop
+ // (AddCurrentTrackingToValidationSetsStack ignores tracking if identical
+ // to the topmost stack entry);
+ // - if maybeRestoreValidationSetsAndRevertSnaps kept new tracking
+ // because its constraints were met even after partial failure or
+ // refresh succeeded and tracking got updated, then
+ // this creates a new copy of validation-sets tracking data.
+ if AddCurrentTrackingToValidationSetsStack != nil {
+ if err := AddCurrentTrackingToValidationSetsStack(st); err != nil {
+ return err
+ }
}
var re reRefreshSetup
@@ -3317,6 +3351,86 @@ func (m *SnapManager) doConditionalAutoRefresh(t *state.Task, tomb *tomb.Tomb) e
return nil
}
+// maybeRestoreValidationSetsAndRevertSnaps restores validation-sets to their
+// previous state using validation sets stack if there are any enforced
+// validation sets and - if necessary - creates tasksets to revert some or all
+// of the refreshed snaps to their previous revisions to satisfy the restored
+// validation sets tracking.
+var maybeRestoreValidationSetsAndRevertSnaps = func(st *state.State, refreshedSnaps []string) ([]*state.TaskSet, error) {
+ enforcedSets, err := EnforcedValidationSets(st)
+ if err != nil {
+ return nil, err
+ }
+ if enforcedSets == nil {
+ // no enforced validation sets, nothing to do
+ return nil, nil
+ }
+
+ installedSnaps, ignoreValidation, err := InstalledSnaps(st)
+ if err != nil {
+ return nil, err
+ }
+ if err := enforcedSets.CheckInstalledSnaps(installedSnaps, ignoreValidation); err == nil {
+ // validation sets are still correct, nothing to do
+ return nil, nil
+ }
+
+ // restore previous validation sets tracking state
+ if err := RestoreValidationSetsTracking(st); err != nil {
+ return nil, fmt.Errorf("cannot restore validation sets: %v", err)
+ }
+
+ // no snaps were refreshed, after restoring validation sets tracking
+ // there is nothing else to do
+ if len(refreshedSnaps) == 0 {
+ return nil, nil
+ }
+
+ // check installed snaps again against restored validation-sets.
+ // this may fail which is fine, but it tells us which snaps are
+ // at invalid revisions and need reverting.
+ // note: we need to fetch enforced sets again because of RestoreValidationSetsTracking.
+ enforcedSets, err = EnforcedValidationSets(st)
+ if err != nil {
+ return nil, err
+ }
+ if enforcedSets == nil {
+ return nil, fmt.Errorf("internal error: no enforced validation sets after restoring from the stack")
+ }
+ err = enforcedSets.CheckInstalledSnaps(installedSnaps, ignoreValidation)
+ if err == nil {
+ // all fine after restoring validation sets: this can happen if previous
+ // validation sets only required a snap (regardless of its revision), then
+ // after update they require a specific snap revision, so after restoring
+ // we are back with the good state.
+ return nil, nil
+ }
+ verr, ok := err.(*snapasserts.ValidationSetsValidationError)
+ if !ok {
+ return nil, err
+ }
+ if len(verr.WrongRevisionSnaps) == 0 {
+ // if we hit ValidationSetsValidationError but it's not about wrong revisions,
+ // then something is really broken (we shouldn't have invalid or missing required
+ // snaps at this point).
+ return nil, fmt.Errorf("internal error: unexpected validation error of installed snaps after unsuccesfull refresh: %v", verr)
+ }
+ // revert some or all snaps
+ var tss []*state.TaskSet
+ for _, snapName := range refreshedSnaps {
+ if verr.WrongRevisionSnaps[snapName] != nil {
+ // XXX: should we be extra paranoid and use RevertToRevision with
+ // the specific revision from verr.WrongRevisionSnaps?
+ ts, err := Revert(st, snapName, Flags{RevertStatus: NotBlocked})
+ if err != nil {
+ return nil, err
+ }
+ tss = append(tss, ts)
+ }
+ }
+ return tss, nil
+}
+
// InjectTasks makes all the halt tasks of the mainTask wait for extraTasks;
// extraTasks join the same lane and change as the mainTask.
func InjectTasks(mainTask *state.Task, extraTasks *state.TaskSet) {
diff --git a/overlord/snapstate/handlers_rerefresh_test.go b/overlord/snapstate/handlers_rerefresh_test.go
index e35f2d8253..05f714181d 100644
--- a/overlord/snapstate/handlers_rerefresh_test.go
+++ b/overlord/snapstate/handlers_rerefresh_test.go
@@ -28,9 +28,12 @@ import (
. "gopkg.in/check.v1"
+ "github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/asserts/snapasserts"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/snaptest"
. "github.com/snapcore/snapd/testutil"
)
@@ -204,7 +207,7 @@ func (s *reRefreshSuite) TestDoCheckReRefreshAddsNewTasks(c *C) {
// wrapper around snapstate.RefreshedSnaps for easier testing
func refreshedSnaps(task *state.Task) string {
- snaps := snapstate.RefreshedSnaps(task)
+ snaps, _ := snapstate.RefreshedSnaps(task)
sort.Strings(snaps)
return strings.Join(snaps, ",")
}
@@ -369,3 +372,228 @@ func (s *reRefreshSuite) TestFilterReturnsFalseIfEpochEqualZero(c *C) {
c.Check(snapstate.ReRefreshFilter(&snap.Info{Epoch: snap.E("0")}, snapst), Equals, false)
c.Check(snapstate.ReRefreshFilter(&snap.Info{Epoch: snap.Epoch{}}, snapst), Equals, false)
}
+
+func (s *refreshSuite) TestMaybeRestoreValidationSetsAndRevertSnaps(c *C) {
+ restore := snapstate.MockEnforcedValidationSets(func(st *state.State) (*snapasserts.ValidationSets, error) {
+ return nil, nil
+ })
+ defer restore()
+
+ st := s.state
+ st.Lock()
+ defer st.Unlock()
+
+ refreshedSnaps := []string{"foo", "bar"}
+ // nothing to do with no enforced validation sets
+ ts, err := snapstate.MaybeRestoreValidationSetsAndRevertSnaps(st, refreshedSnaps)
+ c.Assert(err, IsNil)
+ c.Check(ts, IsNil)
+}
+
+func (s *validationSetsSuite) TestMaybeRestoreValidationSetsAndRevertSnapsOneRevert(c *C) {
+ var enforcedValidationSetsCalled int
+ restore := snapstate.MockEnforcedValidationSets(func(st *state.State) (*snapasserts.ValidationSets, error) {
+ enforcedValidationSetsCalled++
+
+ vs := snapasserts.NewValidationSets()
+ var snap1, snap2, snap3 map[string]interface{}
+ snap3 = map[string]interface{}{
+ "id": "abcKhntON3vR7kwEbVPsILm7bUViPDzx",
+ "name": "some-snap3",
+ "presence": "required",
+ }
+
+ switch enforcedValidationSetsCalled {
+ case 1:
+ // refreshed validation sets
+ snap1 = map[string]interface{}{
+ "id": "aaqKhntON3vR7kwEbVPsILm7bUViPDzx",
+ "name": "some-snap1",
+ "presence": "required",
+ "revision": "3",
+ }
+ // require snap2 at revision 5 (if snap refresh succeeded, but it didn't, so
+ // current revision of the snap is wrong)
+ snap2 = map[string]interface{}{
+ "id": "bgtKhntON3vR7kwEbVPsILm7bUViPDzx",
+ "name": "some-snap2",
+ "presence": "required",
+ "revision": "5",
+ }
+ case 2:
+ // validation sets restored from history
+ snap1 = map[string]interface{}{
+ "id": "aaqKhntON3vR7kwEbVPsILm7bUViPDzx",
+ "name": "some-snap1",
+ "presence": "required",
+ "revision": "1",
+ }
+ snap2 = map[string]interface{}{
+ "id": "bgtKhntON3vR7kwEbVPsILm7bUViPDzx",
+ "name": "some-snap2",
+ "presence": "required",
+ "revision": "2",
+ }
+ default:
+ c.Fatalf("unexpected call to EnforcedValidatioSets")
+ }
+ vsa1 := s.mockValidationSetAssert(c, "bar", "2", snap1, snap2, snap3)
+ vs.Add(vsa1.(*asserts.ValidationSet))
+ return vs, nil
+ })
+ defer restore()
+
+ var restoreValidationSetsTrackingCalled int
+ restoreRestoreValidationSetsTracking := snapstate.MockRestoreValidationSetsTracking(func(*state.State) error {
+ restoreValidationSetsTrackingCalled++
+ return nil
+ })
+ defer restoreRestoreValidationSetsTracking()
+
+ st := s.state
+ st.Lock()
+ defer st.Unlock()
+
+ // snaps installed after partial refresh
+ si1 := &snap.SideInfo{RealName: "some-snap1", SnapID: "aaqKhntON3vR7kwEbVPsILm7bUViPDzx", Revision: snap.R(3)}
+ si11 := &snap.SideInfo{RealName: "some-snap1", SnapID: "aaqKhntON3vR7kwEbVPsILm7bUViPDzx", Revision: snap.R(1)}
+ snapstate.Set(s.state, "some-snap1", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{si11, si1},
+ Current: snap.R(3),
+ SnapType: "app",
+ })
+ snaptest.MockSnap(c, `name: some-snap1`, si1)
+
+ // some-snap2 failed to refresh and remains at revision 2
+ si2 := &snap.SideInfo{RealName: "some-snap2", SnapID: "bgtKhntON3vR7kwEbVPsILm7bUViPDzx", Revision: snap.R(2)}
+ snapstate.Set(s.state, "some-snap2", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{si2},
+ Current: snap.R(2),
+ SnapType: "app",
+ })
+ snaptest.MockSnap(c, `name: some-snap2`, si2)
+
+ si3 := &snap.SideInfo{RealName: "some-snap3", SnapID: "abcKhntON3vR7kwEbVPsILm7bUViPDzx", Revision: snap.R(3)}
+ snapstate.Set(s.state, "some-snap3", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{si3},
+ Current: snap.R(3),
+ SnapType: "app",
+ })
+ snaptest.MockSnap(c, `name: some-snap3`, si3)
+
+ // some-snap2 failed to refresh
+ refreshedSnaps := []string{"some-snap1", "some-snap3"}
+ ts, err := snapstate.MaybeRestoreValidationSetsAndRevertSnaps(st, refreshedSnaps)
+ c.Assert(err, IsNil)
+
+ // we expect revert of snap1
+ c.Assert(ts, HasLen, 1)
+ revertTasks := ts[0].Tasks()
+ c.Assert(taskKinds(revertTasks), DeepEquals, []string{
+ "prerequisites",
+ "prepare-snap",
+ "stop-snap-services",
+ "remove-aliases",
+ "unlink-current-snap",
+ "setup-profiles",
+ "link-snap",
+ "auto-connect",
+ "set-auto-aliases",
+ "setup-aliases",
+ "start-snap-services",
+ "run-hook[configure]",
+ "run-hook[check-health]",
+ })
+
+ snapsup, err := snapstate.TaskSnapSetup(revertTasks[0])
+ c.Assert(err, IsNil)
+ c.Check(snapsup.Flags, Equals, snapstate.Flags{Revert: true, RevertStatus: snapstate.NotBlocked})
+ c.Check(snapsup.InstanceName(), Equals, "some-snap1")
+ c.Check(snapsup.Revision(), Equals, snap.R(1))
+
+ c.Check(restoreValidationSetsTrackingCalled, Equals, 1)
+ c.Check(enforcedValidationSetsCalled, Equals, 2)
+}
+
+func (s *validationSetsSuite) TestMaybeRestoreValidationSetsAndRevertJustValidationSetsRestore(c *C) {
+ var enforcedValidationSetsCalled int
+ restore := snapstate.MockEnforcedValidationSets(func(st *state.State) (*snapasserts.ValidationSets, error) {
+ enforcedValidationSetsCalled++
+
+ vs := snapasserts.NewValidationSets()
+ var snap1, snap2 map[string]interface{}
+ snap2 = map[string]interface{}{
+ "id": "abcKhntON3vR7kwEbVPsILm7bUViPDzx",
+ "name": "some-snap2",
+ "presence": "required",
+ }
+
+ switch enforcedValidationSetsCalled {
+ case 1:
+ // refreshed validation sets
+ // snap1 revision 3 is now required (but snap wasn't refreshed)
+ snap1 = map[string]interface{}{
+ "id": "aaqKhntON3vR7kwEbVPsILm7bUViPDzx",
+ "name": "some-snap1",
+ "presence": "required",
+ "revision": "3",
+ }
+ case 2:
+ // validation sets restored from history
+ snap1 = map[string]interface{}{
+ "id": "aaqKhntON3vR7kwEbVPsILm7bUViPDzx",
+ "name": "some-snap1",
+ "presence": "required",
+ "revision": "1",
+ }
+ default:
+ c.Fatalf("unexpected call to EnforcedValidatioSets")
+ }
+ vsa1 := s.mockValidationSetAssert(c, "bar", "2", snap1, snap2)
+ vs.Add(vsa1.(*asserts.ValidationSet))
+ return vs, nil
+ })
+ defer restore()
+
+ var restoreValidationSetsTrackingCalled int
+ restoreRestoreValidationSetsTracking := snapstate.MockRestoreValidationSetsTracking(func(*state.State) error {
+ restoreValidationSetsTrackingCalled++
+ return nil
+ })
+ defer restoreRestoreValidationSetsTracking()
+
+ st := s.state
+ st.Lock()
+ defer st.Unlock()
+
+ // snaps in the system after partial refresh
+ si1 := &snap.SideInfo{RealName: "some-snap1", SnapID: "aaqKhntON3vR7kwEbVPsILm7bUViPDzx", Revision: snap.R(1)}
+ snapstate.Set(s.state, "some-snap1", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{si1},
+ Current: snap.R(1),
+ SnapType: "app",
+ })
+ snaptest.MockSnap(c, `name: some-snap1`, si1)
+
+ si3 := &snap.SideInfo{RealName: "some-snap2", SnapID: "abcKhntON3vR7kwEbVPsILm7bUViPDzx", Revision: snap.R(3)}
+ snapstate.Set(s.state, "some-snap2", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{si3},
+ Current: snap.R(3),
+ SnapType: "app",
+ })
+ snaptest.MockSnap(c, `name: some-snap2`, si3)
+
+ refreshedSnaps := []string{"some-snap2"}
+ ts, err := snapstate.MaybeRestoreValidationSetsAndRevertSnaps(st, refreshedSnaps)
+ c.Assert(err, IsNil)
+
+ // we expect no snap reverts
+ c.Assert(ts, HasLen, 0)
+ c.Check(restoreValidationSetsTrackingCalled, Equals, 1)
+ c.Check(enforcedValidationSetsCalled, Equals, 2)
+}
diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go
index 7276af01bf..56ff8c8f85 100644
--- a/overlord/snapstate/snapstate.go
+++ b/overlord/snapstate/snapstate.go
@@ -66,14 +66,14 @@ const (
)
const (
- DownloadAndChecksDoneEdge = state.TaskSetEdge("download-and-checks-done")
- BeginEdge = state.TaskSetEdge("begin")
- BeforeHooksEdge = state.TaskSetEdge("before-hooks")
- HooksEdge = state.TaskSetEdge("hooks")
- BeforeMaybeRebootEdge = state.TaskSetEdge("before-maybe-reboot")
- MaybeRebootEdge = state.TaskSetEdge("maybe-reboot")
- MaybeRebootWaitEdge = state.TaskSetEdge("maybe-reboot-wait")
- AfterMaybeRebootWaitEdge = state.TaskSetEdge("after-maybe-reboot-wait")
+ BeginEdge = state.TaskSetEdge("begin")
+ BeforeHooksEdge = state.TaskSetEdge("before-hooks")
+ HooksEdge = state.TaskSetEdge("hooks")
+ BeforeMaybeRebootEdge = state.TaskSetEdge("before-maybe-reboot")
+ MaybeRebootEdge = state.TaskSetEdge("maybe-reboot")
+ MaybeRebootWaitEdge = state.TaskSetEdge("maybe-reboot-wait")
+ AfterMaybeRebootWaitEdge = state.TaskSetEdge("after-maybe-reboot-wait")
+ LastBeforeLocalModificationsEdge = state.TaskSetEdge("last-before-local-modifications")
)
var ErrNothingToDo = errors.New("nothing to do")
@@ -173,7 +173,7 @@ type pathInfo struct {
}
func (i pathInfo) DownloadSize() int64 {
- return i.DownloadInfo.Size
+ return i.Size
}
// SnapBase returns the base snap of the snap.
@@ -566,15 +566,20 @@ func doInstall(st *state.State, snapst *SnapState, snapsup *SnapSetup, flags int
if installHook != nil {
installSet.MarkEdge(installHook, HooksEdge)
}
- ts.AddAllWithEdges(installSet)
+ // if snap is being installed from the store, then the last task before
+ // any system modifications are done is check validate-snap, otherwise
+ // it's the prepare-snap
if checkAsserts != nil {
- ts.MarkEdge(checkAsserts, DownloadAndChecksDoneEdge)
+ installSet.MarkEdge(checkAsserts, LastBeforeLocalModificationsEdge)
+ } else {
+ installSet.MarkEdge(prepare, LastBeforeLocalModificationsEdge)
}
-
if flags&skipConfigure != 0 {
return installSet, nil
}
+ ts.AddAllWithEdges(installSet)
+
// we do not support configuration for bases or the "snapd" snap yet
if snapsup.Type != snap.TypeBase && snapsup.Type != snap.TypeSnapd {
confFlags := 0
@@ -1003,7 +1008,9 @@ func TryPath(st *state.State, name, path string, flags Flags) (*state.TaskSet, e
// Install returns a set of tasks for installing a snap.
// Note that the state must be locked by the caller.
//
-// The returned TaskSet will contain a DownloadAndChecksDoneEdge.
+// The returned TaskSet will contain a LastBeforeLocalModificationsEdge
+// identifying the last task before the first task that introduces system
+// modifications.
func Install(ctx context.Context, st *state.State, name string, opts *RevisionOptions, userID int, flags Flags) (*state.TaskSet, error) {
return InstallWithDeviceContext(ctx, st, name, opts, userID, flags, nil, "")
}
@@ -1012,7 +1019,9 @@ func Install(ctx context.Context, st *state.State, name string, opts *RevisionOp
// It will query for the snap with the given deviceCtx.
// Note that the state must be locked by the caller.
//
-// The returned TaskSet will contain a DownloadAndChecksDoneEdge.
+// The returned TaskSet will contain a LastBeforeLocalModificationsEdge
+// identifying the last task before the first task that introduces system
+// modifications.
func InstallWithDeviceContext(ctx context.Context, st *state.State, name string, opts *RevisionOptions, userID int, flags Flags, deviceCtx DeviceContext, fromChange string) (*state.TaskSet, error) {
if opts == nil {
opts = &RevisionOptions{}
@@ -1879,7 +1888,10 @@ type RevisionOptions struct {
// Update initiates a change updating a snap.
// Note that the state must be locked by the caller.
//
-// The returned TaskSet will contain a DownloadAndChecksDoneEdge.
+// The returned TaskSet can contain a LastBeforeLocalModificationsEdge
+// identifying the last task before the first task that introduces system
+// modifications. If no such edge is set, then none of the tasks introduce
+// system modifications.
func Update(st *state.State, name string, opts *RevisionOptions, userID int, flags Flags) (*state.TaskSet, error) {
return UpdateWithDeviceContext(st, name, opts, userID, flags, nil, "")
}
@@ -1888,7 +1900,10 @@ func Update(st *state.State, name string, opts *RevisionOptions, userID int, fla
// It will query for the snap with the given deviceCtx.
// Note that the state must be locked by the caller.
//
-// The returned TaskSet will contain a DownloadAndChecksDoneEdge.
+// The returned TaskSet can contain a LastBeforeLocalModificationsEdge
+// identifying the last task before the first task that introduces system
+// modifications. If no such edge is set, then none of the tasks introduce
+// system modifications.
func UpdateWithDeviceContext(st *state.State, name string, opts *RevisionOptions, userID int, flags Flags, deviceCtx DeviceContext, fromChange string) (*state.TaskSet, error) {
if opts == nil {
opts = &RevisionOptions{}
@@ -2069,6 +2084,10 @@ func infoForUpdate(st *state.State, snapst *SnapState, name string, opts *Revisi
// into the Autorefresh function.
var AutoRefreshAssertions func(st *state.State, userID int) error
+var AddCurrentTrackingToValidationSetsStack func(st *state.State) error
+
+var RestoreValidationSetsTracking func(st *state.State) error
+
// AutoRefresh is the wrapper that will do a refresh of all the installed
// snaps on the system. In addition to that it will also refresh important
// assertions.
@@ -2377,8 +2396,8 @@ func LinkNewBaseOrKernel(st *state.State, name string) (*state.TaskSet, error) {
linkSnap.Set("snap-setup-task", prepareSnap.ID())
linkSnap.WaitFor(prev)
ts.AddTask(linkSnap)
- // we need this for remodel
- ts.MarkEdge(prepareSnap, DownloadAndChecksDoneEdge)
+ // prepare-snap is the last task that carries no system modifications
+ ts.MarkEdge(prepareSnap, LastBeforeLocalModificationsEdge)
return ts, nil
}
@@ -2423,6 +2442,14 @@ func AddLinkNewBaseOrKernel(st *state.State, ts *state.TaskSet) (*state.TaskSet,
linkSnap.Set("snap-setup-task", snapSetupTask.ID())
linkSnap.WaitFor(prev)
ts.AddTask(linkSnap)
+ // make sure that remodel can identify which tasks introduce actual
+ // changes to the system and order them correctly
+ if edgeTask := ts.MaybeEdge(LastBeforeLocalModificationsEdge); edgeTask == nil {
+ // no task in the task set is marked as last before system
+ // modifications are introduced, so we need to mark the last
+ // task in the set, as tasks introduced here modify system state
+ ts.MarkEdge(allTasks[len(allTasks)-1], LastBeforeLocalModificationsEdge)
+ }
return ts, nil
}
@@ -2470,9 +2497,9 @@ func SwitchToNewGadget(st *state.State, name string) (*state.TaskSet, error) {
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)
+ // prepare-snap is the last task that carries no system modifications
+ ts.MarkEdge(prepareSnap, LastBeforeLocalModificationsEdge)
return ts, nil
}
@@ -2497,6 +2524,14 @@ func AddGadgetAssetsTasks(st *state.State, ts *state.TaskSet) (*state.TaskSet, e
gadgetCmdline.Set("snap-setup-task", snapSetupTask.ID())
gadgetCmdline.WaitFor(gadgetUpdate)
ts.AddTask(gadgetCmdline)
+ // make sure that remodel can identify which tasks introduce actual
+ // changes to the system and order them correctly
+ if edgeTask := ts.MaybeEdge(LastBeforeLocalModificationsEdge); edgeTask == nil {
+ // no task in the task set is marked as last before system
+ // modifications are introduced, so we need to mark the last
+ // task in the set, as tasks introduced here modify system state
+ ts.MarkEdge(allTasks[len(allTasks)-1], LastBeforeLocalModificationsEdge)
+ }
return ts, nil
}
diff --git a/overlord/snapstate/snapstate_install_test.go b/overlord/snapstate/snapstate_install_test.go
index 34eb41635d..536d4d2a2e 100644
--- a/overlord/snapstate/snapstate_install_test.go
+++ b/overlord/snapstate/snapstate_install_test.go
@@ -148,6 +148,12 @@ func verifyInstallTasks(c *C, typ snap.Type, opts, discards int, ts *state.TaskS
expected := expectedDoInstallTasks(typ, opts, discards, nil, nil)
c.Assert(kinds, DeepEquals, expected)
+
+ if opts&noLastBeforeModificationsEdge == 0 {
+ te := ts.MaybeEdge(snapstate.LastBeforeLocalModificationsEdge)
+ c.Assert(te, NotNil)
+ c.Assert(te.Kind(), Equals, "validate-snap")
+ }
}
func (s *snapmgrTestSuite) TestInstallDevModeConfinementFiltering(c *C) {
@@ -4594,7 +4600,12 @@ epoch: 42
}
func (s *snapmgrTestSuite) TestInstallPathManyClassicAsUpdate(c *C) {
- restore := snapstate.MockSnapReadInfo(func(name string, si *snap.SideInfo) (*snap.Info, error) {
+ restore := release.MockReleaseInfo(&release.OS{ID: "ubuntu"})
+ defer restore()
+ // this needs doing because dirs depends on the release info
+ dirs.SetRootDir(dirs.GlobalRootDir)
+
+ restore = snapstate.MockSnapReadInfo(func(name string, si *snap.SideInfo) (*snap.Info, error) {
return &snap.Info{SuggestedName: name, Confinement: "classic"}, nil
})
defer restore()
@@ -4701,3 +4712,73 @@ version: 1
return brokenSnap, si
}
+
+func (s *snapmgrTestSuite) TestInstallPathManyWithLocalPrereqAndBaseNoStore(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ tr := config.NewTransaction(s.state)
+ c.Assert(tr.Set("core", "experimental.check-disk-space-install", true), IsNil)
+ tr.Commit()
+
+ // use the real disk check since it also includes store checks
+ restore := snapstate.MockInstallSize(snapstate.InstallSize)
+ defer restore()
+
+ // no core, we'll install it as well
+ snapstate.Set(s.state, "core", nil)
+
+ var paths []string
+ var sideInfos []*snap.SideInfo
+
+ snapNames := []string{"some-snap", "prereq-snap", "core"}
+ yamls := []string{
+ `name: some-snap
+version: 1.0
+base: core
+plugs:
+ myplug:
+ interface: content
+ content: mycontent
+ default-provider: prereq-snap
+`,
+ `name: prereq-snap
+version: 1.0
+base: core
+slots:
+ myslot:
+ interface: content
+ content: mycontent`,
+ `name: core
+version: 1.0
+type: base
+`,
+ }
+
+ for i, name := range snapNames {
+ paths = append(paths, makeTestSnap(c, yamls[i]))
+ si := &snap.SideInfo{
+ RealName: name,
+ Revision: snap.R("1"),
+ }
+ sideInfos = append(sideInfos, si)
+ }
+
+ tss, err := snapstate.InstallPathMany(context.Background(), s.state, sideInfos, paths, 0, nil)
+ c.Assert(err, IsNil)
+ c.Assert(tss, HasLen, 3)
+
+ chg := s.state.NewChange("install", "install local snaps")
+ for _, ts := range tss {
+ chg.AddAll(ts)
+ }
+
+ defer s.se.Stop()
+ s.settle(c)
+
+ c.Assert(chg.Err(), IsNil)
+ c.Assert(chg.IsReady(), Equals, true)
+
+ op := s.fakeBackend.ops.First("storesvc-snap-action")
+ c.Assert(op, IsNil)
+}
diff --git a/overlord/snapstate/snapstate_test.go b/overlord/snapstate/snapstate_test.go
index 15eb0568e9..7d5c01ef1f 100644
--- a/overlord/snapstate/snapstate_test.go
+++ b/overlord/snapstate/snapstate_test.go
@@ -430,6 +430,7 @@ const (
updatesGadgetAssets
updatesBootConfig
noConfigure
+ noLastBeforeModificationsEdge
)
func taskKinds(tasks []*state.Task) []string {
@@ -4950,7 +4951,9 @@ func (s *snapmgrTestSuite) TestTransitionSnapdSnapWithCoreRunthrough(c *C) {
c.Assert(chg.IsReady(), Equals, true)
c.Check(s.fakeStore.downloads, HasLen, 1)
ts := state.NewTaskSet(chg.Tasks()...)
- verifyInstallTasks(c, snap.TypeSnapd, noConfigure, 0, ts)
+ // task set was reconstituted from change tasks, so edges information is
+ // lost
+ verifyInstallTasks(c, snap.TypeSnapd, noConfigure|noLastBeforeModificationsEdge, 0, ts)
// ensure preferences from the core snap got transferred over
var snapst snapstate.SnapState
diff --git a/overlord/snapstate/snapstate_update_test.go b/overlord/snapstate/snapstate_update_test.go
index e8bdd74dde..2d4d6c3869 100644
--- a/overlord/snapstate/snapstate_update_test.go
+++ b/overlord/snapstate/snapstate_update_test.go
@@ -37,6 +37,7 @@ import (
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/interfaces/ifacetest"
+ "github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/overlord/assertstate"
"github.com/snapcore/snapd/overlord/auth"
@@ -65,6 +66,10 @@ func verifyUpdateTasks(c *C, typ snap.Type, opts, discards int, ts *state.TaskSe
}
c.Assert(kinds, DeepEquals, expected)
+
+ te := ts.MaybeEdge(snapstate.LastBeforeLocalModificationsEdge)
+ c.Assert(te, NotNil)
+ c.Assert(te.Kind(), Equals, "validate-snap")
}
func (s *snapmgrTestSuite) TestUpdateDoesGC(c *C) {
@@ -815,6 +820,11 @@ func (s *snapmgrTestSuite) TestUpdateRunThrough(c *C) {
c.Assert(err, IsNil)
chg.AddAll(ts)
+ // local modifications, edge must be set
+ te := ts.MaybeEdge(snapstate.LastBeforeLocalModificationsEdge)
+ c.Assert(te, NotNil)
+ c.Assert(te.Kind(), Equals, "validate-snap")
+
defer s.se.Stop()
s.settle(c)
@@ -2705,6 +2715,10 @@ func (s *snapmgrTestSuite) TestUpdateSameRevisionSwitchChannelRunThrough(c *C) {
chg := s.state.NewChange("refresh", "refresh a snap")
chg.AddAll(ts)
+ // no local modifications, hence no edge
+ te := ts.MaybeEdge(snapstate.LastBeforeLocalModificationsEdge)
+ c.Assert(te, IsNil)
+
defer s.se.Stop()
s.settle(c)
@@ -4413,7 +4427,7 @@ func (s *snapmgrTestSuite) testUpdateCreatesGCTasks(c *C, expectedDiscards int)
c.Assert(err, IsNil)
// ensure edges information is still there
- te, err := ts.Edge(snapstate.DownloadAndChecksDoneEdge)
+ te, err := ts.Edge(snapstate.LastBeforeLocalModificationsEdge)
c.Assert(te, NotNil)
c.Assert(err, IsNil)
@@ -4494,7 +4508,7 @@ func (s *snapmgrTestSuite) TestUpdateMany(c *C) {
c.Assert(s.state.TaskCount(), Equals, len(ts.Tasks())+1) // 1==rerefresh
// ensure edges information is still there
- te, err := ts.Edge(snapstate.DownloadAndChecksDoneEdge)
+ te, err := ts.Edge(snapstate.LastBeforeLocalModificationsEdge)
c.Assert(te, NotNil)
c.Assert(err, IsNil)
@@ -6844,6 +6858,141 @@ func (s *snapmgrTestSuite) TestUpdatePrerequisiteWithSameDeviceContext(c *C) {
})
}
+func (s *validationSetsSuite) testUpdateManyValidationSetsPartialFailure(c *C) *state.Change {
+ logbuf, rest := logger.MockLogger()
+ defer rest()
+
+ restore := snapstate.MockEnforcedValidationSets(func(st *state.State) (*snapasserts.ValidationSets, error) {
+ vs := snapasserts.NewValidationSets()
+ snap1 := map[string]interface{}{
+ "id": "aaqKhntON3vR7kwEbVPsILm7bUViPDzx",
+ "name": "some-snap",
+ "presence": "required",
+ }
+ snap2 := map[string]interface{}{
+ "id": "bgtKhntON3vR7kwEbVPsILm7bUViPDzx",
+ "name": "some-other-snap",
+ "presence": "required",
+ }
+ vsa1 := s.mockValidationSetAssert(c, "bar", "2", snap1, snap2)
+ vs.Add(vsa1.(*asserts.ValidationSet))
+ return vs, nil
+ })
+ defer restore()
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ tr := assertstate.ValidationSetTracking{
+ AccountID: "foo",
+ Name: "bar",
+ Mode: assertstate.Enforce,
+ Current: 2,
+ }
+ assertstate.UpdateValidationSet(s.state, &tr)
+
+ si1 := &snap.SideInfo{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}
+ snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{si1},
+ Current: snap.R(1),
+ SnapType: "app",
+ })
+ snaptest.MockSnap(c, `name: some-snap`, si1)
+
+ si2 := &snap.SideInfo{RealName: "some-other-snap", SnapID: "some-other-snap-id", Revision: snap.R(1)}
+ snapstate.Set(s.state, "some-other-snap", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{si2},
+ Current: snap.R(1),
+ SnapType: "app",
+ })
+ snaptest.MockSnap(c, `name: some-other-snap`, si2)
+
+ s.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.SnapMountDir, "/some-other-snap/11")
+
+ names, tss, err := snapstate.UpdateMany(context.Background(), s.state, nil, s.user.ID, &snapstate.Flags{})
+ c.Assert(err, IsNil)
+ c.Check(names, DeepEquals, []string{"some-other-snap", "some-snap"})
+ c.Check(logbuf.String(), Equals, "")
+ chg := s.state.NewChange("update", "")
+ for _, ts := range tss {
+ chg.AddAll(ts)
+ }
+
+ s.settle(c)
+
+ return chg
+}
+
+func (s *validationSetsSuite) TestUpdateManyValidationSetsPartialFailureNothingToRestore(c *C) {
+ var refreshed []string
+ restoreMaybeRestoreValidationSetsAndRevertSnaps := snapstate.MockMaybeRestoreValidationSetsAndRevertSnaps(func(st *state.State, refreshedSnaps []string) ([]*state.TaskSet, error) {
+ refreshed = refreshedSnaps
+ // nothing to restore
+ return nil, nil
+ })
+ defer restoreMaybeRestoreValidationSetsAndRevertSnaps()
+
+ var addCurrentTrackingToValidationSetsStackCalled int
+ restoreAddCurrentTrackingToValidationSetsStack := snapstate.MockAddCurrentTrackingToValidationSetsStack(func(st *state.State) error {
+ addCurrentTrackingToValidationSetsStackCalled++
+ return nil
+ })
+ defer restoreAddCurrentTrackingToValidationSetsStack()
+
+ s.testUpdateManyValidationSetsPartialFailure(c)
+
+ // only some-snap was successfully refreshed, this also confirms that
+ // mockMaybeRestoreValidationSetsAndRevertSnaps was called.
+ c.Check(refreshed, DeepEquals, []string{"some-snap"})
+
+ // validation sets history update was attempted (could be a no-op if
+ // maybeRestoreValidationSetsAndRevertSnaps restored last tracking
+ // data).
+ c.Check(addCurrentTrackingToValidationSetsStackCalled, Equals, 1)
+}
+
+func (s *validationSetsSuite) TestUpdateManyValidationSetsPartialFailureRevertTasks(c *C) {
+ var refreshed []string
+ restoreMaybeRestoreValidationSetsAndRevertSnaps := snapstate.MockMaybeRestoreValidationSetsAndRevertSnaps(func(st *state.State, refreshedSnaps []string) ([]*state.TaskSet, error) {
+ refreshed = refreshedSnaps
+ ts := state.NewTaskSet(st.NewTask("fake-revert-task", ""))
+ return []*state.TaskSet{ts}, nil
+ })
+ defer restoreMaybeRestoreValidationSetsAndRevertSnaps()
+
+ var addCurrentTrackingToValidationSetsStackCalled int
+ restoreAddCurrentTrackingToValidationSetsStack := snapstate.MockAddCurrentTrackingToValidationSetsStack(func(st *state.State) error {
+ addCurrentTrackingToValidationSetsStackCalled++
+ return nil
+ })
+ defer restoreAddCurrentTrackingToValidationSetsStack()
+
+ chg := s.testUpdateManyValidationSetsPartialFailure(c)
+
+ // only some-snap was successfully refreshed, this also confirms that
+ // mockMaybeRestoreValidationSetsAndRevertSnaps was called.
+ c.Check(refreshed, DeepEquals, []string{"some-snap"})
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ // check that a fake revert task returned by maybeRestoreValidationSetsAndRevertSnaps
+ // got injected into the refresh change.
+ var seen bool
+ for _, t := range chg.Tasks() {
+ if t.Kind() == "fake-revert-task" {
+ seen = true
+ break
+ }
+ }
+ c.Check(seen, Equals, true)
+
+ // we haven't updated validation sets history
+ c.Check(addCurrentTrackingToValidationSetsStackCalled, Equals, 0)
+}
+
func (s *snapmgrTestSuite) TestUpdatePrerequisiteBackwardsCompat(c *C) {
s.state.Lock()
defer s.state.Unlock()
diff --git a/overlord/snapstate/storehelpers.go b/overlord/snapstate/storehelpers.go
index 0840c1109f..15de87e557 100644
--- a/overlord/snapstate/storehelpers.go
+++ b/overlord/snapstate/storehelpers.go
@@ -113,6 +113,12 @@ var installSize = func(st *state.State, snaps []minimalInstallInfo, userID int)
accountedSnaps[snap.InstanceName] = true
}
+ // if the prerequisites are included in the install, don't query the store
+ // for info on them
+ for _, snap := range snaps {
+ accountedSnaps[snap.InstanceName()] = true
+ }
+
var prereqs []string
resolveBaseAndContentProviders := func(inst minimalInstallInfo) {
diff --git a/overlord/snapstate/storehelpers_test.go b/overlord/snapstate/storehelpers_test.go
index f12b38f8ee..223dd0c660 100644
--- a/overlord/snapstate/storehelpers_test.go
+++ b/overlord/snapstate/storehelpers_test.go
@@ -351,3 +351,135 @@ func (s *snapmgrTestSuite) TestInstallSizeErrorNoDownloadInfo(c *C) {
_, err := snapstate.InstallSize(st, []snapstate.MinimalInstallInfo{snapstate.InstallSnapInfo{Info: snap1}}, 0)
c.Assert(err, ErrorMatches, `internal error: download info missing.*`)
}
+
+func (s *snapmgrTestSuite) TestInstallSizeWithPrereqNoStore(c *C) {
+ st := s.state
+ st.Lock()
+ defer st.Unlock()
+
+ repo := interfaces.NewRepository()
+ ifacerepo.Replace(st, repo)
+
+ s.setupInstallSizeStore()
+
+ snap1 := snaptest.MockSnap(c, `name: some-snap
+version: 1.0
+epoch: 1
+base: core
+plugs:
+ myplug:
+ interface: content
+ content: mycontent
+ content-provider: some-snap2`, &snap.SideInfo{
+ RealName: "some-snap",
+ Revision: snap.R(1),
+ })
+ snap1.Size = snap1Size
+
+ snap2 := snaptest.MockSnap(c, `name: some-snap2
+version: 1.0
+epoch: 1
+base: core
+slots:
+ myslot:
+ interface: content
+ content: mycontent`, &snap.SideInfo{
+ RealName: "some-snap2",
+ Revision: snap.R(1),
+ })
+ snap2.Size = snap2Size
+
+ // core is already installed
+ s.mockCoreSnap(c)
+
+ sz, err := snapstate.InstallSize(st, []snapstate.MinimalInstallInfo{
+ snapstate.InstallSnapInfo{Info: snap1}, snapstate.InstallSnapInfo{Info: snap2}}, 0)
+ c.Assert(err, IsNil)
+ c.Check(sz, Equals, uint64(snap1Size+snap2Size))
+
+ // no call to the store is made
+ c.Assert(s.fakeStore.fakeBackend.ops, HasLen, 0)
+}
+
+func (s *snapmgrTestSuite) TestInstallSizeWithPrereqAndCoreNoStore(c *C) {
+ st := s.state
+ st.Lock()
+ defer st.Unlock()
+
+ repo := interfaces.NewRepository()
+ ifacerepo.Replace(st, repo)
+
+ s.setupInstallSizeStore()
+
+ snap1 := snaptest.MockSnap(c, `name: some-snap
+version: 1.0
+epoch: 1
+base: core
+plugs:
+ myplug:
+ interface: content
+ content: mycontent
+ content-provider: some-snap2`, &snap.SideInfo{
+ RealName: "some-snap",
+ Revision: snap.R(1),
+ })
+ snap1.Size = snap1Size
+
+ snap2 := snaptest.MockSnap(c, `name: some-snap2
+version: 1.0
+epoch: 1
+base: core
+slots:
+ myslot:
+ interface: content
+ content: mycontent`, &snap.SideInfo{
+ RealName: "some-snap2",
+ Revision: snap.R(1),
+ })
+ snap2.Size = snap2Size
+
+ core := snaptest.MockSnap(c, `name: core
+version: 1.0
+epoch: 1
+type: os`, &snap.SideInfo{
+ RealName: "core",
+ Revision: snap.R(1),
+ })
+ core.Size = someBaseSize
+
+ sz, err := snapstate.InstallSize(st, []snapstate.MinimalInstallInfo{
+ snapstate.InstallSnapInfo{Info: snap1}, snapstate.InstallSnapInfo{Info: snap2}, snapstate.InstallSnapInfo{Info: core}}, 0)
+ c.Assert(err, IsNil)
+ c.Check(sz, Equals, uint64(snap1Size+snap2Size+someBaseSize))
+
+ // no call to the store is made
+ c.Assert(s.fakeStore.fakeBackend.ops, HasLen, 0)
+}
+
+func (s *snapmgrTestSuite) TestInstallSizeRemotePrereq(c *C) {
+ st := s.state
+ st.Lock()
+ defer st.Unlock()
+
+ repo := interfaces.NewRepository()
+ ifacerepo.Replace(st, repo)
+
+ s.setupInstallSizeStore()
+
+ snap1 := snaptest.MockSnap(c, snapYamlWithContentPlug1, &snap.SideInfo{
+ RealName: "some-snap",
+ Revision: snap.R(1),
+ })
+ snap1.Size = snap1Size
+
+ s.mockCoreSnap(c)
+
+ sz, err := snapstate.InstallSize(st, []snapstate.MinimalInstallInfo{
+ snapstate.InstallSnapInfo{Info: snap1}}, 0)
+ c.Assert(err, IsNil)
+ c.Check(sz, Equals, uint64(snap1Size+snapContentSlotSize+someBaseSize))
+
+ // the prereq's size info is fetched from the store
+ op := s.fakeStore.fakeBackend.ops.MustFindOp(c, "storesvc-snap-action:action")
+ c.Assert(op.action.InstanceName, Equals, "snap-content-slot")
+}
diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD
index 4afc3b7a8e..dd769a9c5c 100644
--- a/packaging/arch/PKGBUILD
+++ b/packaging/arch/PKGBUILD
@@ -11,7 +11,7 @@ pkgdesc="Service and tools for management of snap packages."
depends=('squashfs-tools' 'libseccomp' 'libsystemd' 'apparmor')
optdepends=('bash-completion: bash completion support'
'xdg-desktop-portal: desktop integration')
-pkgver=2.53.2
+pkgver=2.54.1
pkgrel=1
arch=('x86_64' 'i686' 'armv7h' 'aarch64')
url="https://github.com/snapcore/snapd"
diff --git a/packaging/debian-sid/changelog b/packaging/debian-sid/changelog
index fc1fdefee0..f0f698019f 100644
--- a/packaging/debian-sid/changelog
+++ b/packaging/debian-sid/changelog
@@ -1,3 +1,383 @@
+snapd (2.54.1-1) unstable; urgency=medium
+
+ * New upstream release, LP: #1955137
+ - buid-aux: set version before calling ./generate-packaging-dir
+ This fixes the "dirty" suffix in the auto-generated version
+
+ -- Michael Vogt <michael.vogt@ubuntu.com> Mon, 20 Dec 2021 10:06:09 +0100
+
+snapd (2.54-1) unstable; urgency=medium
+
+ * New upstream release, LP: #1955137
+ - interfaces/builtin/opengl.go: add boot_vga sys/devices file
+ - o/configstate/configcore: add tmpfs.size option
+ - tests: moving to manual opensuse 15.2
+ - cmd/snap-device-helper: bring back the device type identification
+ behavior, but for remove action fallback only
+ - cmd/snap-failure: use snapd from the snapd snap if core is not
+ present
+ - tests/core/failover: enable the test on core18
+ - o/devicestate: ensure proper order when remodel does a simple
+ switch-snap-channel
+ - builtin/interfaces: add shared memory interface
+ - overlord: extend kernel/base success and failover with bootenv
+ checks
+ - o/snapstate: check disk space w/o store if possible
+ - snap-bootstrap: Mount snaps read only
+ - gadget/install: do not re-create partitions using OnDiskVolume
+ after deletion
+ - many: fix formatting w/ latest go version
+ - devicestate,timeutil: improve logging of NTP sync
+ - tests/main/security-device-cgroups-helper: more debugs
+ - cmd/snap: print a placeholder for version of broken snaps
+ - o/snapstate: mock system with classic confinement support
+ - cmd: Fixup .clangd to use correct syntax
+ - tests: run spread tests in fedora-35
+ - data/selinux: allow snapd to access /etc/modprobe.d
+ - mount-control: step 2
+ - daemon: add multiple snap sideload to API
+ - tests/lib/pkgdb: install dbus-user-session during prepare, drop
+ dbus-x11
+ - systemd: provide more detailed errors for unimplemented method in
+ emulation mode
+ - tests: avoid checking TRUST_TEST_KEYS on restore on remodel-base
+ test
+ - tests: retry umounting /var/lib/snapd/seed on uc20 on fsck-on-boot
+ test
+ - o/snapstate: add hide/expose snap data to backend
+ - interfaces: kernel-module-load
+ - snap: add support for `snap watch
+ --last={revert,enable,disable,switch}`
+ - tests/main/security-udev-input-subsystem: drop info from udev
+ - tests/core/kernel-and-base-single-reboot-failover,
+ tests/lib/fakestore: verify failover scenario
+ - tests/main/security-device-cgroups-helper: collect some debug info
+ when the test fails
+ - tests/nested/manual/core20-remodel: wait for device to have a
+ serial before starting a remodel
+ - tests/main/generic-unregister: test re-registration if not blocked
+ - o/snapstate, assertsate: validation sets/undo on partial failure
+ - tests: ensure snapd can be downloaded as a module
+ - snapdtool, many: support additional key/value flags in info file
+ - data/env: improve fish shell env setup
+ - usersession/client: provide a way for client to send messages to a
+ subset of users
+ - tests: verify that simultaneous refresh of kernel and base
+ triggers a single reboot only
+ - devicestate: Unregister deletes the device key pair as well
+ - daemon,tests: support forgetting device serial via API
+ - asserts: change behavior of alternative attribute matcher
+ - configcore: relax validation rules for hostname
+ - cmd/snap-confine: do not include libglvnd libraries from the host
+ system
+ - overlord, tests: add managers and a spread test for UC20 to UC22
+ remodel
+ - HACKING.md: adjust again for building the snapd snap
+ - systemd: add support for systemd unit alias names
+ - o/snapstate: add InstallPathMany
+ - gadget: allow EnsureLayoutCompatibility to ensure disk has all
+ laid out structsnow reject/fail:
+ - packaging/ubuntu, packaging/debian: depend on dbus-session-bus
+ provider (#11111)
+ - interfaces/interfaces/scsi_generic: add interface for scsi generic
+ de… (#10936)
+ - osutil/disks/mockdisk.go: add MockDevicePathToDiskMapping
+ - interfaces/microstack-support: set controlsDeviceCgroup to true
+ - network-setup-control: add netplan generate D-Bus rules
+ - interface/builtin/log_observe: allow to access /dev/kmsg
+ - .github/workflows/test.yaml: restore failing of spread tests on
+ errors (nested)
+ - gadget: tweaks to DiskStructureDeviceTraits + expand test cases
+ - tests/lib/nested.sh: allow tests to use their own core18 in extra-
+ snaps-path
+ - interfaces/browser-support: Update rules for Edge
+ - o/devicestate: during remodel first check pending download tasks
+ for snaps
+ - polkit: add a package to validate polkit policy files
+ - HACKING.md: document building the snapd snap and splicing it into
+ the core snap
+ - interfaces/udev: fix installing snaps inside lxd in 21.10
+ - o/snapstate: refactor disk space checks
+ - tests: add (strict) microk8s smoke test
+ - osutil/strace: try to enable strace on more arches
+ - cmd/libsnap-confine-private: fix snap-device-helper device allow
+ list modification on cgroup v2
+ - tests/main/snapd-reexec-snapd-snap: improve debugging
+ - daemon: write formdata file parts to snaps dir
+ - systemd: add support for .target units
+ - tests: run snap-disconnect on uc16
+ - many: add experimental setting to allow using ~/.snap/data instead
+ of ~/snap
+ - overlord/snapstate: perform a single reboot when updating boot
+ base and kernel
+ - kernel/fde: add DeviceUnlockKernelHookDeviceMapperBackResolver,
+ use w/ disks pkg
+ - o/devicestate: introduce DeviceManager.Unregister
+ - interfaces: allow receiving PropertiesChanged on the mpris plug
+ - tests: new tool used to retrieve data from mongo db
+ - daemon: amend ssh keys coming from the store
+ - tests: Include the tools from snapd-testing-tools project in
+ "$TESTSTOOLS"
+ - tests: new workflow step used to report spread error to mongodb
+ - interfaces/builtin/dsp: update proc files for ambarella flavor
+ - gadget: replace ondisk implementation with disks package, refactor
+ part calcs
+ - tests: Revert "tests: disable flaky uc18 tests until systemd is
+ fixed"
+ - Revert: "many: Vendor apparmor-3.0.3 into the snapd snap"
+ - asserts: rename "white box" to "clear box" (woke checker)
+ - many: Vendor apparmor-3.0.3 into the snapd snap
+ - tests: reorganize the debug-each on the spread.yaml
+ - packaging: sync with downstream packaging in Fedora and openSUSE
+ - tests: disable flaky uc18 tests until systemd is fixed
+ - data/env: provide profile setup for fish shell
+ - tests: use ubuntu-image 1.11 from stable channel
+ - gadget/gadget.go: include disk schema in the disk device volume
+ traits too
+ - tests/main/security-device-cgroups-strict-enforced: extend the
+ comments
+ - README.md: point at bugs.launchpad.net/snapd instead of snappy
+ project
+ - osutil/disks: introduce RegisterDeviceMapperBackResolver + use for
+ crypt-luks2
+ - packaging: make postrm script robust against `rm` failures
+ - tests: print extra debug on auto-refresh-gating test failure
+ - o/assertstate, api: move enforcing/monitoring from api to
+ assertstate, save history
+ - tests: skip the test-snapd-timedate-control-consumer.date to avoid
+ NTP sync error
+ - gadget/install: use disks functions to implement deviceFromRole,
+ also rename
+ - tests: the `lxd` test is failing right now on 21.10
+ - o/snapstate: account for deleted revs when undoing install
+ - interfaces/builtin/block_devices: allow blkid to print block
+ device attributes
+ - gadget: include size + sector-size in DiskVolumeDeviceTraits
+ - cmd/libsnap-confine-private: do not deny all devices when reusing
+ the device cgroup
+ - interfaces/builtin/time-control: allow pps access
+ - o/snapstate/handlers: propagate read errors on "copy-snap-data"
+ - osutil/disks: add more fields to Partition, populate them during
+ discovery
+ - interfaces/u2f-devices: add Trezor and Trezor v2 keys
+ - interfaces: timezone-control, add permission for ListTimezones
+ DBus call
+ - o/snapstate: remove repeated test assertions
+ - tests: skip `snap advise-command` test if the store is overloaded
+ - cmd: create ~/snap dir with 0700 perms
+ - interfaces/apparmor/template.go: allow udevadm from merged usr
+ systems
+ - github: leave a comment documenting reasons for pipefail
+ - github: enable pipefail when running spread
+ - osutil/disks: add DiskFromPartitionDeviceNode
+ - gadget, many: add model param to Update()
+ - cmd/snap-seccomp: add riscv64 support
+ - o/snapstate: maintain a RevertStatus map in SnapState
+ - tests: enable lxd tests on impish system
+ - tests: (partially) revert the memory limits PR#r10241
+ - o/assertstate: functions for handling validation sets tracking
+ history
+ - tests: some improvements for the spread log parser
+ - interfaces/network-manager-observe: Update for libnm / dart
+ clients
+ - tests: add ntp related debug around "auto-refresh" test
+ - boot: expand on the fact that reseal taking modeenv is very
+ intentional
+ - cmd/snap-seccomp/syscalls: update syscalls to match libseccomp
+ abad8a8f4
+ - data/selinux: update the policy to allow snapd to talk to
+ org.freedesktop.timedate1
+ - o/snapstate: keep old revision if install doesn't add new one
+ - overlord/state: add a unit test for a kernel+base refresh like
+ sequence
+ - desktop, usersession: observe notifications
+ - osutil/disks: add AllPhysicalDisks()
+ - timeutil,deviceutil: fix unit tests on systems without dbus or
+ without ntp-sync
+ - cmd/snap-bootstrap/README: explain all the things (well most of
+ them anyways)
+ - docs: add run-checks dependency install instruction
+ - o/snapstate: do not prune refresh-candidates if gate-auto-refresh-
+ hook feature is not enabled
+ - o/snapstate: test relink remodel helpers do a proper subset of
+ doInstall and rework the verify*Tasks helpers
+ - tests/main/mount-ns: make the test run early
+ - tests: add `--debug` to netplan apply
+ - many: wait for up to 10min for NTP synchronization before
+ autorefresh
+ - tests: initialize CHANGE_ID in _wait_autorefresh
+ - sandbox/cgroup: freeze and thaw cgroups related to services and
+ scopes only
+ - tests: add more debug around qemu-nbd
+ - o/hookstate: print cohort with snapctl refresh --pending (#10985)
+ - tests: misc robustness changes
+ - o/snapstate: improve install/update tests (#10850)
+ - tests: clean up test tools
+ - spread.yaml: show `journalctl -e` for all suites on debug
+ - tests: give interfaces-udisks2 more time for the loop device to
+ appear
+ - tests: set memory limit for snapd
+ - tests: increase timeout/add debug around nbd0 mounting (up, see
+ LP:#1949513)
+ - snapstate: add debug message where a snap is mounted
+ - tests: give nbd0 more time to show up in preseed-lxd
+ - interfaces/dsp: add more ambarella things
+ - cmd/snap: improve snap disconnect arg parsing and err msg
+ - tests: disable nested lxd snapd testing
+ - tests: disable flaky "interfaces-udisks2" on ubuntu-18.04-32
+ - o/snapstate: avoid validationSetsSuite repeating snapmgrTestSuite
+ - sandbox/cgroup: wait for start transient unit job to finish
+ - o/snapstate: fix task order, tweak errors, add unit tests for
+ remodel helpers
+ - osutil/disks: re-org methods for end of usable region, size
+ information
+ - build-aux: ensure that debian packaging matches build-base
+ - docs: update HACKING.md instructions for snapd 2.52 and later
+ - spread: run lxd tests with version from latest/edge
+ - interfaces: suppress denial of sys_module capability
+ - osutil/disks: add methods to replace gadget/ondisk functions
+ - tests: split test tools - part 1
+ - tests: fix nested tests on uc20
+ - data/selinux: allow snap-confine to read udev's database
+ - i/b/common_test: refactor AppArmor features test
+ - tests: run spread tests on debian 11
+ - o/devicestate: copy timesyncd clock timestamp during install
+ - interfaces/builtin: do not probe parser features when apparmor
+ isn't available
+ - interface/modem-manager: allow connecting to the mbim/qmi proxy
+ - tests: fix error message in run-checks
+ - tests: spread test for validation sets enforcing
+ - cmd/snap-confine: lazy set up of device cgroup, only when devices
+ were assigned
+ - o/snapstate: deduplicate snap names in remove/install/update
+ - tests/main/selinux-data-context: use session when performing
+ actions as test user
+ - packaging/opensuse: sync with openSUSE packaging, enable AppArmor
+ on 15.3+
+ - interfaces: skip connection of netlink interface on older
+ systems
+ - asserts, o/snapstate: honor IgnoreValidation flag when checking
+ installed snaps
+ - tests/main/apparmor-batch-reload: fix fake apparmor_parser to
+ handle --preprocess
+ - sandbox/apparmor, interfaces/apparmor: detect bpf capability,
+ generate snippet for s-c
+ - release-tools/repack-debian-tarball.sh: fix c-vendor dir
+ - tests: test for enforcing with prerequisites
+ - tests/main/snapd-sigterm: fix race conditions
+ - spread: run lxd tests with version from latest/stable
+ - run-checks: remove --spread from help message
+ - secboot: use latest secboot with tpm legacy platform and v2 fully
+ optional
+ - tests/lib/pkgdb: install strace on Debian 11 and Sid
+ - tests: ensure systemd-timesyncd is installed on debian
+ - interfaces/u2f-devices: add Nitrokey 3
+ - tests: update the ubuntu-image channel to candidate
+ - osutil/disks/labels: simplify decoding algorithm
+ - tests: not testing lxd snap anymore on i386 architecture
+ - o/snapstate, hookstate: print remaining hold time on snapctl
+ --hold
+ - cmd/snap: support --ignore-validation with snap install client
+ command
+ - tests/snapd-sigterm: be more robust against service restart
+ - tests: simplify mock script for apparmor_parser
+ - o/devicestate, o/servicestate: update gadget assets and cmdline
+ when remodeling
+ - tests/nested/manual/refresh-revert-fundamentals: re-enable
+ encryption
+ - osutil/disks: fix bug in BlkIDEncodeLabel, add BlkIDDecodeLabel
+ - gadget, osutil/disks: fix some bugs from prior PR'sin the dir.
+ - secboot: revert move to new version (revert #10715)
+ - cmd/snap-confine: die when snap process is outside of snap
+ specific cgroup
+ - many: mv MockDeviceNameDisksToPartitionMapping ->
+ MockDeviceNameToDiskMapping
+ - interfaces/builtin: Add '/com/canonical/dbusmenu' path access to
+ 'unity7' interface
+ - interfaces/builtin/hardware-observer: add /proc/bus/input/devices
+ too
+ - osutil/disks, many: switch to defining Partitions directly for
+ MockDiskMapping
+ - tests: remove extra-snaps-assertions test
+ - interface/modem-manager: add accept for MBIM/QMI proxy clients
+ - tests/nested/core/core20-create-recovery: fix passing of data to
+ curl
+ - daemon: allow enabling enforce mode
+ - daemon: use the syscall connection to get the socket credentials
+ - i/builtin/kubernetes_support: add access to Calico lock file
+ - osutil: ensure parent dir is opened and sync'd
+ - tests: using test-snapd-curl snap instead of http snap
+ - overlord: add managers unit test demonstrating cyclic dependency
+ between gadget and kernel updates
+ - gadget/ondisk.go: include the filesystem UUID in the returned
+ OnDiskVolume
+ - packaging: fixes for building on openSUSE
+ - o/configcore: allow hostnames up to 253 characters, with dot-
+ delimited elements
+ - gadget/ondisk.go: add listBlockDevices() to get all block devices
+ on a system
+ - gadget: add mapping trait types + functions to save/load
+ - interfaces: add polkit security backend
+ - cmd/snap-confine/snap-confine.apparmor.in: update ld rule for
+ s390x impish
+ - tests: merge coverage results
+ - tests: remove "features" from fde-setup.go example
+ - fde: add new device-setup support to fde-setup
+ - gadget: add `encryptedDevice` and add encryptedDeviceLUKS
+ - spread: use `bios: uefi` for uc20
+ - client: fail fast on non-retryable errors
+ - tests: support running all spread tests with experimental features
+ - tests: check that a snap that doesn't have gate-auto-refresh hook
+ can call --proceed
+ - o/snapstate: support ignore-validation flag when updating to a
+ specific snap revision
+ - o/snapstate: test prereq update if started by old version
+ - tests/main: disable cgroup-devices-v1 and freezer tests on 21.10
+ - tests/main/interfaces-many: run both variants on all possible
+ Ubuntu systems
+ - gadget: mv ensureLayoutCompatibility to gadget proper, add
+ gadgettest pkg
+ - many: replace state.State restart support with overlord/restart
+ - overlord: fix generated snap-revision assertions in remodel unit
+ tests
+
+ -- Michael Vogt <michael.vogt@ubuntu.com> Fri, 17 Dec 2021 15:49:18 +0100
+
+snapd (2.53.4-1) unstable; urgency=medium
+
+ * New upstream release, LP: #1929842
+ - devicestate: mock devicestate.MockTimeutilIsNTPSynchronized to
+ avoid host env leaking into tests
+ - timeutil: return NoTimedate1Error if it can't connect to the
+ system bus
+
+ -- Ian Johnson <ian.johnson@canonical.com> Thu, 02 Dec 2021 17:16:48 -0600
+
+snapd (2.53.3-1) unstable; urgency=medium
+
+ * New upstream release, LP: #1929842
+ - devicestate: Unregister deletes the device key pair as well
+ - daemon,tests: support forgetting device serial via API
+ - configcore: relax validation rules for hostname
+ - o/devicestate: introduce DeviceManager.Unregister
+ - packaging/ubuntu, packaging/debian: depend on dbus-session-bus
+ provider
+ - many: wait for up to 10min for NTP synchronization before
+ autorefresh
+ - interfaces/interfaces/scsi_generic: add interface for scsi generic
+ devices
+ - interfaces/microstack-support: set controlsDeviceCgroup to true
+ - interface/builtin/log_observe: allow to access /dev/kmsg
+ - daemon: write formdata file parts to snaps dir
+ - spread: run lxd tests with version from latest/edge
+ - cmd/libsnap-confine-private: fix snap-device-helper device allow
+ list modification on cgroup v2
+ - interfaces/builtin/dsp: add proc files for monitoring Ambarella
+ DSP firmware
+ - interfaces/builtin/dsp: update proc file accordingly
+
+ -- Ian Johnson <ian.johnson@canonical.com> Thu, 02 Dec 2021 11:42:15 -0600
+
snapd (2.53.2-1) unstable; urgency=medium
* New upstream release, LP: #1946127
diff --git a/packaging/debian-sid/snapd.postrm b/packaging/debian-sid/snapd.postrm
index ab41eca635..009b4d4add 100644
--- a/packaging/debian-sid/snapd.postrm
+++ b/packaging/debian-sid/snapd.postrm
@@ -79,6 +79,7 @@ if [ "$1" = "purge" ]; then
fi
# modules
rm -f "/etc/modules-load.d/snap.${snap}.conf"
+ rm -f "/etc/modprobe.d/snap.${snap}.conf"
# timer and socket units
find /etc/systemd/system -name "snap.${snap}.*.timer" -o -name "snap.${snap}.*.socket" | while read -r f; do
systemctl_stop "$(basename "$f")"
diff --git a/packaging/fedora-35 b/packaging/fedora-35
new file mode 120000
index 0000000000..100fe0cd7b
--- /dev/null
+++ b/packaging/fedora-35
@@ -0,0 +1 @@
+fedora \ No newline at end of file
diff --git a/packaging/fedora/snapd.spec b/packaging/fedora/snapd.spec
index be2e8db87c..5eb68777b5 100644
--- a/packaging/fedora/snapd.spec
+++ b/packaging/fedora/snapd.spec
@@ -102,7 +102,7 @@
%endif
Name: snapd
-Version: 2.53.2
+Version: 2.54.1
Release: 0%{?dist}
Summary: A transactional software package manager
License: GPLv3
@@ -989,6 +989,374 @@ fi
%changelog
+* Mon Dec 20 2021 Michael Vogt <michael.vogt@ubuntu.com>
+- New upstream release 2.54.1
+ - buid-aux: set version before calling ./generate-packaging-dir
+ This fixes the "dirty" suffix in the auto-generated version
+
+* Fri Dec 17 2021 Michael Vogt <michael.vogt@ubuntu.com>
+- New upstream release 2.54
+ - interfaces/builtin/opengl.go: add boot_vga sys/devices file
+ - o/configstate/configcore: add tmpfs.size option
+ - tests: moving to manual opensuse 15.2
+ - cmd/snap-device-helper: bring back the device type identification
+ behavior, but for remove action fallback only
+ - cmd/snap-failure: use snapd from the snapd snap if core is not
+ present
+ - tests/core/failover: enable the test on core18
+ - o/devicestate: ensure proper order when remodel does a simple
+ switch-snap-channel
+ - builtin/interfaces: add shared memory interface
+ - overlord: extend kernel/base success and failover with bootenv
+ checks
+ - o/snapstate: check disk space w/o store if possible
+ - snap-bootstrap: Mount snaps read only
+ - gadget/install: do not re-create partitions using OnDiskVolume
+ after deletion
+ - many: fix formatting w/ latest go version
+ - devicestate,timeutil: improve logging of NTP sync
+ - tests/main/security-device-cgroups-helper: more debugs
+ - cmd/snap: print a placeholder for version of broken snaps
+ - o/snapstate: mock system with classic confinement support
+ - cmd: Fixup .clangd to use correct syntax
+ - tests: run spread tests in fedora-35
+ - data/selinux: allow snapd to access /etc/modprobe.d
+ - mount-control: step 2
+ - daemon: add multiple snap sideload to API
+ - tests/lib/pkgdb: install dbus-user-session during prepare, drop
+ dbus-x11
+ - systemd: provide more detailed errors for unimplemented method in
+ emulation mode
+ - tests: avoid checking TRUST_TEST_KEYS on restore on remodel-base
+ test
+ - tests: retry umounting /var/lib/snapd/seed on uc20 on fsck-on-boot
+ test
+ - o/snapstate: add hide/expose snap data to backend
+ - interfaces: kernel-module-load
+ - snap: add support for `snap watch
+ --last={revert,enable,disable,switch}`
+ - tests/main/security-udev-input-subsystem: drop info from udev
+ - tests/core/kernel-and-base-single-reboot-failover,
+ tests/lib/fakestore: verify failover scenario
+ - tests/main/security-device-cgroups-helper: collect some debug info
+ when the test fails
+ - tests/nested/manual/core20-remodel: wait for device to have a
+ serial before starting a remodel
+ - tests/main/generic-unregister: test re-registration if not blocked
+ - o/snapstate, assertsate: validation sets/undo on partial failure
+ - tests: ensure snapd can be downloaded as a module
+ - snapdtool, many: support additional key/value flags in info file
+ - data/env: improve fish shell env setup
+ - usersession/client: provide a way for client to send messages to a
+ subset of users
+ - tests: verify that simultaneous refresh of kernel and base
+ triggers a single reboot only
+ - devicestate: Unregister deletes the device key pair as well
+ - daemon,tests: support forgetting device serial via API
+ - asserts: change behavior of alternative attribute matcher
+ - configcore: relax validation rules for hostname
+ - cmd/snap-confine: do not include libglvnd libraries from the host
+ system
+ - overlord, tests: add managers and a spread test for UC20 to UC22
+ remodel
+ - HACKING.md: adjust again for building the snapd snap
+ - systemd: add support for systemd unit alias names
+ - o/snapstate: add InstallPathMany
+ - gadget: allow EnsureLayoutCompatibility to ensure disk has all
+ laid out structsnow reject/fail:
+ - packaging/ubuntu, packaging/debian: depend on dbus-session-bus
+ provider (#11111)
+ - interfaces/interfaces/scsi_generic: add interface for scsi generic
+ de… (#10936)
+ - osutil/disks/mockdisk.go: add MockDevicePathToDiskMapping
+ - interfaces/microstack-support: set controlsDeviceCgroup to true
+ - network-setup-control: add netplan generate D-Bus rules
+ - interface/builtin/log_observe: allow to access /dev/kmsg
+ - .github/workflows/test.yaml: restore failing of spread tests on
+ errors (nested)
+ - gadget: tweaks to DiskStructureDeviceTraits + expand test cases
+ - tests/lib/nested.sh: allow tests to use their own core18 in extra-
+ snaps-path
+ - interfaces/browser-support: Update rules for Edge
+ - o/devicestate: during remodel first check pending download tasks
+ for snaps
+ - polkit: add a package to validate polkit policy files
+ - HACKING.md: document building the snapd snap and splicing it into
+ the core snap
+ - interfaces/udev: fix installing snaps inside lxd in 21.10
+ - o/snapstate: refactor disk space checks
+ - tests: add (strict) microk8s smoke test
+ - osutil/strace: try to enable strace on more arches
+ - cmd/libsnap-confine-private: fix snap-device-helper device allow
+ list modification on cgroup v2
+ - tests/main/snapd-reexec-snapd-snap: improve debugging
+ - daemon: write formdata file parts to snaps dir
+ - systemd: add support for .target units
+ - tests: run snap-disconnect on uc16
+ - many: add experimental setting to allow using ~/.snap/data instead
+ of ~/snap
+ - overlord/snapstate: perform a single reboot when updating boot
+ base and kernel
+ - kernel/fde: add DeviceUnlockKernelHookDeviceMapperBackResolver,
+ use w/ disks pkg
+ - o/devicestate: introduce DeviceManager.Unregister
+ - interfaces: allow receiving PropertiesChanged on the mpris plug
+ - tests: new tool used to retrieve data from mongo db
+ - daemon: amend ssh keys coming from the store
+ - tests: Include the tools from snapd-testing-tools project in
+ "$TESTSTOOLS"
+ - tests: new workflow step used to report spread error to mongodb
+ - interfaces/builtin/dsp: update proc files for ambarella flavor
+ - gadget: replace ondisk implementation with disks package, refactor
+ part calcs
+ - tests: Revert "tests: disable flaky uc18 tests until systemd is
+ fixed"
+ - Revert: "many: Vendor apparmor-3.0.3 into the snapd snap"
+ - asserts: rename "white box" to "clear box" (woke checker)
+ - many: Vendor apparmor-3.0.3 into the snapd snap
+ - tests: reorganize the debug-each on the spread.yaml
+ - packaging: sync with downstream packaging in Fedora and openSUSE
+ - tests: disable flaky uc18 tests until systemd is fixed
+ - data/env: provide profile setup for fish shell
+ - tests: use ubuntu-image 1.11 from stable channel
+ - gadget/gadget.go: include disk schema in the disk device volume
+ traits too
+ - tests/main/security-device-cgroups-strict-enforced: extend the
+ comments
+ - README.md: point at bugs.launchpad.net/snapd instead of snappy
+ project
+ - osutil/disks: introduce RegisterDeviceMapperBackResolver + use for
+ crypt-luks2
+ - packaging: make postrm script robust against `rm` failures
+ - tests: print extra debug on auto-refresh-gating test failure
+ - o/assertstate, api: move enforcing/monitoring from api to
+ assertstate, save history
+ - tests: skip the test-snapd-timedate-control-consumer.date to avoid
+ NTP sync error
+ - gadget/install: use disks functions to implement deviceFromRole,
+ also rename
+ - tests: the `lxd` test is failing right now on 21.10
+ - o/snapstate: account for deleted revs when undoing install
+ - interfaces/builtin/block_devices: allow blkid to print block
+ device attributes
+ - gadget: include size + sector-size in DiskVolumeDeviceTraits
+ - cmd/libsnap-confine-private: do not deny all devices when reusing
+ the device cgroup
+ - interfaces/builtin/time-control: allow pps access
+ - o/snapstate/handlers: propagate read errors on "copy-snap-data"
+ - osutil/disks: add more fields to Partition, populate them during
+ discovery
+ - interfaces/u2f-devices: add Trezor and Trezor v2 keys
+ - interfaces: timezone-control, add permission for ListTimezones
+ DBus call
+ - o/snapstate: remove repeated test assertions
+ - tests: skip `snap advise-command` test if the store is overloaded
+ - cmd: create ~/snap dir with 0700 perms
+ - interfaces/apparmor/template.go: allow udevadm from merged usr
+ systems
+ - github: leave a comment documenting reasons for pipefail
+ - github: enable pipefail when running spread
+ - osutil/disks: add DiskFromPartitionDeviceNode
+ - gadget, many: add model param to Update()
+ - cmd/snap-seccomp: add riscv64 support
+ - o/snapstate: maintain a RevertStatus map in SnapState
+ - tests: enable lxd tests on impish system
+ - tests: (partially) revert the memory limits PR#r10241
+ - o/assertstate: functions for handling validation sets tracking
+ history
+ - tests: some improvements for the spread log parser
+ - interfaces/network-manager-observe: Update for libnm / dart
+ clients
+ - tests: add ntp related debug around "auto-refresh" test
+ - boot: expand on the fact that reseal taking modeenv is very
+ intentional
+ - cmd/snap-seccomp/syscalls: update syscalls to match libseccomp
+ abad8a8f4
+ - data/selinux: update the policy to allow snapd to talk to
+ org.freedesktop.timedate1
+ - o/snapstate: keep old revision if install doesn't add new one
+ - overlord/state: add a unit test for a kernel+base refresh like
+ sequence
+ - desktop, usersession: observe notifications
+ - osutil/disks: add AllPhysicalDisks()
+ - timeutil,deviceutil: fix unit tests on systems without dbus or
+ without ntp-sync
+ - cmd/snap-bootstrap/README: explain all the things (well most of
+ them anyways)
+ - docs: add run-checks dependency install instruction
+ - o/snapstate: do not prune refresh-candidates if gate-auto-refresh-
+ hook feature is not enabled
+ - o/snapstate: test relink remodel helpers do a proper subset of
+ doInstall and rework the verify*Tasks helpers
+ - tests/main/mount-ns: make the test run early
+ - tests: add `--debug` to netplan apply
+ - many: wait for up to 10min for NTP synchronization before
+ autorefresh
+ - tests: initialize CHANGE_ID in _wait_autorefresh
+ - sandbox/cgroup: freeze and thaw cgroups related to services and
+ scopes only
+ - tests: add more debug around qemu-nbd
+ - o/hookstate: print cohort with snapctl refresh --pending (#10985)
+ - tests: misc robustness changes
+ - o/snapstate: improve install/update tests (#10850)
+ - tests: clean up test tools
+ - spread.yaml: show `journalctl -e` for all suites on debug
+ - tests: give interfaces-udisks2 more time for the loop device to
+ appear
+ - tests: set memory limit for snapd
+ - tests: increase timeout/add debug around nbd0 mounting (up, see
+ LP:#1949513)
+ - snapstate: add debug message where a snap is mounted
+ - tests: give nbd0 more time to show up in preseed-lxd
+ - interfaces/dsp: add more ambarella things
+ - cmd/snap: improve snap disconnect arg parsing and err msg
+ - tests: disable nested lxd snapd testing
+ - tests: disable flaky "interfaces-udisks2" on ubuntu-18.04-32
+ - o/snapstate: avoid validationSetsSuite repeating snapmgrTestSuite
+ - sandbox/cgroup: wait for start transient unit job to finish
+ - o/snapstate: fix task order, tweak errors, add unit tests for
+ remodel helpers
+ - osutil/disks: re-org methods for end of usable region, size
+ information
+ - build-aux: ensure that debian packaging matches build-base
+ - docs: update HACKING.md instructions for snapd 2.52 and later
+ - spread: run lxd tests with version from latest/edge
+ - interfaces: suppress denial of sys_module capability
+ - osutil/disks: add methods to replace gadget/ondisk functions
+ - tests: split test tools - part 1
+ - tests: fix nested tests on uc20
+ - data/selinux: allow snap-confine to read udev's database
+ - i/b/common_test: refactor AppArmor features test
+ - tests: run spread tests on debian 11
+ - o/devicestate: copy timesyncd clock timestamp during install
+ - interfaces/builtin: do not probe parser features when apparmor
+ isn't available
+ - interface/modem-manager: allow connecting to the mbim/qmi proxy
+ - tests: fix error message in run-checks
+ - tests: spread test for validation sets enforcing
+ - cmd/snap-confine: lazy set up of device cgroup, only when devices
+ were assigned
+ - o/snapstate: deduplicate snap names in remove/install/update
+ - tests/main/selinux-data-context: use session when performing
+ actions as test user
+ - packaging/opensuse: sync with openSUSE packaging, enable AppArmor
+ on 15.3+
+ - interfaces: skip connection of netlink interface on older
+ systems
+ - asserts, o/snapstate: honor IgnoreValidation flag when checking
+ installed snaps
+ - tests/main/apparmor-batch-reload: fix fake apparmor_parser to
+ handle --preprocess
+ - sandbox/apparmor, interfaces/apparmor: detect bpf capability,
+ generate snippet for s-c
+ - release-tools/repack-debian-tarball.sh: fix c-vendor dir
+ - tests: test for enforcing with prerequisites
+ - tests/main/snapd-sigterm: fix race conditions
+ - spread: run lxd tests with version from latest/stable
+ - run-checks: remove --spread from help message
+ - secboot: use latest secboot with tpm legacy platform and v2 fully
+ optional
+ - tests/lib/pkgdb: install strace on Debian 11 and Sid
+ - tests: ensure systemd-timesyncd is installed on debian
+ - interfaces/u2f-devices: add Nitrokey 3
+ - tests: update the ubuntu-image channel to candidate
+ - osutil/disks/labels: simplify decoding algorithm
+ - tests: not testing lxd snap anymore on i386 architecture
+ - o/snapstate, hookstate: print remaining hold time on snapctl
+ --hold
+ - cmd/snap: support --ignore-validation with snap install client
+ command
+ - tests/snapd-sigterm: be more robust against service restart
+ - tests: simplify mock script for apparmor_parser
+ - o/devicestate, o/servicestate: update gadget assets and cmdline
+ when remodeling
+ - tests/nested/manual/refresh-revert-fundamentals: re-enable
+ encryption
+ - osutil/disks: fix bug in BlkIDEncodeLabel, add BlkIDDecodeLabel
+ - gadget, osutil/disks: fix some bugs from prior PR'sin the dir.
+ - secboot: revert move to new version (revert #10715)
+ - cmd/snap-confine: die when snap process is outside of snap
+ specific cgroup
+ - many: mv MockDeviceNameDisksToPartitionMapping ->
+ MockDeviceNameToDiskMapping
+ - interfaces/builtin: Add '/com/canonical/dbusmenu' path access to
+ 'unity7' interface
+ - interfaces/builtin/hardware-observer: add /proc/bus/input/devices
+ too
+ - osutil/disks, many: switch to defining Partitions directly for
+ MockDiskMapping
+ - tests: remove extra-snaps-assertions test
+ - interface/modem-manager: add accept for MBIM/QMI proxy clients
+ - tests/nested/core/core20-create-recovery: fix passing of data to
+ curl
+ - daemon: allow enabling enforce mode
+ - daemon: use the syscall connection to get the socket credentials
+ - i/builtin/kubernetes_support: add access to Calico lock file
+ - osutil: ensure parent dir is opened and sync'd
+ - tests: using test-snapd-curl snap instead of http snap
+ - overlord: add managers unit test demonstrating cyclic dependency
+ between gadget and kernel updates
+ - gadget/ondisk.go: include the filesystem UUID in the returned
+ OnDiskVolume
+ - packaging: fixes for building on openSUSE
+ - o/configcore: allow hostnames up to 253 characters, with dot-
+ delimited elements
+ - gadget/ondisk.go: add listBlockDevices() to get all block devices
+ on a system
+ - gadget: add mapping trait types + functions to save/load
+ - interfaces: add polkit security backend
+ - cmd/snap-confine/snap-confine.apparmor.in: update ld rule for
+ s390x impish
+ - tests: merge coverage results
+ - tests: remove "features" from fde-setup.go example
+ - fde: add new device-setup support to fde-setup
+ - gadget: add `encryptedDevice` and add encryptedDeviceLUKS
+ - spread: use `bios: uefi` for uc20
+ - client: fail fast on non-retryable errors
+ - tests: support running all spread tests with experimental features
+ - tests: check that a snap that doesn't have gate-auto-refresh hook
+ can call --proceed
+ - o/snapstate: support ignore-validation flag when updating to a
+ specific snap revision
+ - o/snapstate: test prereq update if started by old version
+ - tests/main: disable cgroup-devices-v1 and freezer tests on 21.10
+ - tests/main/interfaces-many: run both variants on all possible
+ Ubuntu systems
+ - gadget: mv ensureLayoutCompatibility to gadget proper, add
+ gadgettest pkg
+ - many: replace state.State restart support with overlord/restart
+ - overlord: fix generated snap-revision assertions in remodel unit
+ tests
+
+* Thu Dec 02 2021 Ian Johnson <ian.johnson@canonical.com>
+- New upstream release 2.53.4
+ - devicestate: mock devicestate.MockTimeutilIsNTPSynchronized to
+ avoid host env leaking into tests
+ - timeutil: return NoTimedate1Error if it can't connect to the
+ system bus
+
+* Thu Dec 02 2021 Ian Johnson <ian.johnson@canonical.com>
+- New upstream release 2.53.3
+ - devicestate: Unregister deletes the device key pair as well
+ - daemon,tests: support forgetting device serial via API
+ - configcore: relax validation rules for hostname
+ - o/devicestate: introduce DeviceManager.Unregister
+ - packaging/ubuntu, packaging/debian: depend on dbus-session-bus
+ provider
+ - many: wait for up to 10min for NTP synchronization before
+ autorefresh
+ - interfaces/interfaces/scsi_generic: add interface for scsi generic
+ devices
+ - interfaces/microstack-support: set controlsDeviceCgroup to true
+ - interface/builtin/log_observe: allow to access /dev/kmsg
+ - daemon: write formdata file parts to snaps dir
+ - spread: run lxd tests with version from latest/edge
+ - cmd/libsnap-confine-private: fix snap-device-helper device allow
+ list modification on cgroup v2
+ - interfaces/builtin/dsp: add proc files for monitoring Ambarella
+ DSP firmware
+ - interfaces/builtin/dsp: update proc file accordingly
+
* Mon Nov 15 2021 Ian Johnson <ian.johnson@canonical.com>
- New upstream release 2.53.2
- interfaces/builtin/block_devices: allow blkid to print block
diff --git a/packaging/opensuse/snapd.changes b/packaging/opensuse/snapd.changes
index ddff24a8cf..c047c3c8bd 100644
--- a/packaging/opensuse/snapd.changes
+++ b/packaging/opensuse/snapd.changes
@@ -1,4 +1,24 @@
-------------------------------------------------------------------
+Mon Dec 20 09:06:09 UTC 2021 - michael.vogt@ubuntu.com
+
+- Update to upstream release 2.54.1
+
+-------------------------------------------------------------------
+Fri Dec 17 14:49:18 UTC 2021 - michael.vogt@ubuntu.com
+
+- Update to upstream release 2.54
+
+-------------------------------------------------------------------
+Thu Dec 02 23:16:48 UTC 2021 - ian.johnson@canonical.com
+
+- Update to upstream release 2.53.4
+
+-------------------------------------------------------------------
+Thu Dec 02 17:42:15 UTC 2021 - ian.johnson@canonical.com
+
+- Update to upstream release 2.53.3
+
+-------------------------------------------------------------------
Mon Nov 15 22:09:09 UTC 2021 - ian.johnson@canonical.com
- Update to upstream release 2.53.2
diff --git a/packaging/opensuse/snapd.spec b/packaging/opensuse/snapd.spec
index 61b2a5af3f..5f3a2a12c6 100644
--- a/packaging/opensuse/snapd.spec
+++ b/packaging/opensuse/snapd.spec
@@ -81,7 +81,7 @@
Name: snapd
-Version: 2.53.2
+Version: 2.54.1
Release: 0
Summary: Tools enabling systems to work with .snap files
License: GPL-3.0
@@ -125,6 +125,7 @@ BuildRequires: ca-certificates-mozilla
%if %{with apparmor}
BuildRequires: libapparmor-devel
BuildRequires: apparmor-rpm-macros
+BuildRequires: apparmor-parser
%endif
PreReq: permissions
@@ -418,6 +419,7 @@ fi
%dir %{_datadir}/zsh
%dir %{_datadir}/zsh/site-functions
# similar case for fish
+%dir %{_datadir}/fish
%dir %{_datadir}/fish/vendor_conf.d
# Ghost entries for things created at runtime
diff --git a/packaging/ubuntu-14.04/changelog b/packaging/ubuntu-14.04/changelog
index e6c705b37b..aa0f8635a6 100644
--- a/packaging/ubuntu-14.04/changelog
+++ b/packaging/ubuntu-14.04/changelog
@@ -1,3 +1,383 @@
+snapd (2.54.1~14.04) trusty; urgency=medium
+
+ * New upstream release, LP: #1955137
+ - buid-aux: set version before calling ./generate-packaging-dir
+ This fixes the "dirty" suffix in the auto-generated version
+
+ -- Michael Vogt <michael.vogt@ubuntu.com> Mon, 20 Dec 2021 10:06:09 +0100
+
+snapd (2.54~14.04) trusty; urgency=medium
+
+ * New upstream release, LP: #1955137
+ - interfaces/builtin/opengl.go: add boot_vga sys/devices file
+ - o/configstate/configcore: add tmpfs.size option
+ - tests: moving to manual opensuse 15.2
+ - cmd/snap-device-helper: bring back the device type identification
+ behavior, but for remove action fallback only
+ - cmd/snap-failure: use snapd from the snapd snap if core is not
+ present
+ - tests/core/failover: enable the test on core18
+ - o/devicestate: ensure proper order when remodel does a simple
+ switch-snap-channel
+ - builtin/interfaces: add shared memory interface
+ - overlord: extend kernel/base success and failover with bootenv
+ checks
+ - o/snapstate: check disk space w/o store if possible
+ - snap-bootstrap: Mount snaps read only
+ - gadget/install: do not re-create partitions using OnDiskVolume
+ after deletion
+ - many: fix formatting w/ latest go version
+ - devicestate,timeutil: improve logging of NTP sync
+ - tests/main/security-device-cgroups-helper: more debugs
+ - cmd/snap: print a placeholder for version of broken snaps
+ - o/snapstate: mock system with classic confinement support
+ - cmd: Fixup .clangd to use correct syntax
+ - tests: run spread tests in fedora-35
+ - data/selinux: allow snapd to access /etc/modprobe.d
+ - mount-control: step 2
+ - daemon: add multiple snap sideload to API
+ - tests/lib/pkgdb: install dbus-user-session during prepare, drop
+ dbus-x11
+ - systemd: provide more detailed errors for unimplemented method in
+ emulation mode
+ - tests: avoid checking TRUST_TEST_KEYS on restore on remodel-base
+ test
+ - tests: retry umounting /var/lib/snapd/seed on uc20 on fsck-on-boot
+ test
+ - o/snapstate: add hide/expose snap data to backend
+ - interfaces: kernel-module-load
+ - snap: add support for `snap watch
+ --last={revert,enable,disable,switch}`
+ - tests/main/security-udev-input-subsystem: drop info from udev
+ - tests/core/kernel-and-base-single-reboot-failover,
+ tests/lib/fakestore: verify failover scenario
+ - tests/main/security-device-cgroups-helper: collect some debug info
+ when the test fails
+ - tests/nested/manual/core20-remodel: wait for device to have a
+ serial before starting a remodel
+ - tests/main/generic-unregister: test re-registration if not blocked
+ - o/snapstate, assertsate: validation sets/undo on partial failure
+ - tests: ensure snapd can be downloaded as a module
+ - snapdtool, many: support additional key/value flags in info file
+ - data/env: improve fish shell env setup
+ - usersession/client: provide a way for client to send messages to a
+ subset of users
+ - tests: verify that simultaneous refresh of kernel and base
+ triggers a single reboot only
+ - devicestate: Unregister deletes the device key pair as well
+ - daemon,tests: support forgetting device serial via API
+ - asserts: change behavior of alternative attribute matcher
+ - configcore: relax validation rules for hostname
+ - cmd/snap-confine: do not include libglvnd libraries from the host
+ system
+ - overlord, tests: add managers and a spread test for UC20 to UC22
+ remodel
+ - HACKING.md: adjust again for building the snapd snap
+ - systemd: add support for systemd unit alias names
+ - o/snapstate: add InstallPathMany
+ - gadget: allow EnsureLayoutCompatibility to ensure disk has all
+ laid out structsnow reject/fail:
+ - packaging/ubuntu, packaging/debian: depend on dbus-session-bus
+ provider (#11111)
+ - interfaces/interfaces/scsi_generic: add interface for scsi generic
+ de… (#10936)
+ - osutil/disks/mockdisk.go: add MockDevicePathToDiskMapping
+ - interfaces/microstack-support: set controlsDeviceCgroup to true
+ - network-setup-control: add netplan generate D-Bus rules
+ - interface/builtin/log_observe: allow to access /dev/kmsg
+ - .github/workflows/test.yaml: restore failing of spread tests on
+ errors (nested)
+ - gadget: tweaks to DiskStructureDeviceTraits + expand test cases
+ - tests/lib/nested.sh: allow tests to use their own core18 in extra-
+ snaps-path
+ - interfaces/browser-support: Update rules for Edge
+ - o/devicestate: during remodel first check pending download tasks
+ for snaps
+ - polkit: add a package to validate polkit policy files
+ - HACKING.md: document building the snapd snap and splicing it into
+ the core snap
+ - interfaces/udev: fix installing snaps inside lxd in 21.10
+ - o/snapstate: refactor disk space checks
+ - tests: add (strict) microk8s smoke test
+ - osutil/strace: try to enable strace on more arches
+ - cmd/libsnap-confine-private: fix snap-device-helper device allow
+ list modification on cgroup v2
+ - tests/main/snapd-reexec-snapd-snap: improve debugging
+ - daemon: write formdata file parts to snaps dir
+ - systemd: add support for .target units
+ - tests: run snap-disconnect on uc16
+ - many: add experimental setting to allow using ~/.snap/data instead
+ of ~/snap
+ - overlord/snapstate: perform a single reboot when updating boot
+ base and kernel
+ - kernel/fde: add DeviceUnlockKernelHookDeviceMapperBackResolver,
+ use w/ disks pkg
+ - o/devicestate: introduce DeviceManager.Unregister
+ - interfaces: allow receiving PropertiesChanged on the mpris plug
+ - tests: new tool used to retrieve data from mongo db
+ - daemon: amend ssh keys coming from the store
+ - tests: Include the tools from snapd-testing-tools project in
+ "$TESTSTOOLS"
+ - tests: new workflow step used to report spread error to mongodb
+ - interfaces/builtin/dsp: update proc files for ambarella flavor
+ - gadget: replace ondisk implementation with disks package, refactor
+ part calcs
+ - tests: Revert "tests: disable flaky uc18 tests until systemd is
+ fixed"
+ - Revert: "many: Vendor apparmor-3.0.3 into the snapd snap"
+ - asserts: rename "white box" to "clear box" (woke checker)
+ - many: Vendor apparmor-3.0.3 into the snapd snap
+ - tests: reorganize the debug-each on the spread.yaml
+ - packaging: sync with downstream packaging in Fedora and openSUSE
+ - tests: disable flaky uc18 tests until systemd is fixed
+ - data/env: provide profile setup for fish shell
+ - tests: use ubuntu-image 1.11 from stable channel
+ - gadget/gadget.go: include disk schema in the disk device volume
+ traits too
+ - tests/main/security-device-cgroups-strict-enforced: extend the
+ comments
+ - README.md: point at bugs.launchpad.net/snapd instead of snappy
+ project
+ - osutil/disks: introduce RegisterDeviceMapperBackResolver + use for
+ crypt-luks2
+ - packaging: make postrm script robust against `rm` failures
+ - tests: print extra debug on auto-refresh-gating test failure
+ - o/assertstate, api: move enforcing/monitoring from api to
+ assertstate, save history
+ - tests: skip the test-snapd-timedate-control-consumer.date to avoid
+ NTP sync error
+ - gadget/install: use disks functions to implement deviceFromRole,
+ also rename
+ - tests: the `lxd` test is failing right now on 21.10
+ - o/snapstate: account for deleted revs when undoing install
+ - interfaces/builtin/block_devices: allow blkid to print block
+ device attributes
+ - gadget: include size + sector-size in DiskVolumeDeviceTraits
+ - cmd/libsnap-confine-private: do not deny all devices when reusing
+ the device cgroup
+ - interfaces/builtin/time-control: allow pps access
+ - o/snapstate/handlers: propagate read errors on "copy-snap-data"
+ - osutil/disks: add more fields to Partition, populate them during
+ discovery
+ - interfaces/u2f-devices: add Trezor and Trezor v2 keys
+ - interfaces: timezone-control, add permission for ListTimezones
+ DBus call
+ - o/snapstate: remove repeated test assertions
+ - tests: skip `snap advise-command` test if the store is overloaded
+ - cmd: create ~/snap dir with 0700 perms
+ - interfaces/apparmor/template.go: allow udevadm from merged usr
+ systems
+ - github: leave a comment documenting reasons for pipefail
+ - github: enable pipefail when running spread
+ - osutil/disks: add DiskFromPartitionDeviceNode
+ - gadget, many: add model param to Update()
+ - cmd/snap-seccomp: add riscv64 support
+ - o/snapstate: maintain a RevertStatus map in SnapState
+ - tests: enable lxd tests on impish system
+ - tests: (partially) revert the memory limits PR#r10241
+ - o/assertstate: functions for handling validation sets tracking
+ history
+ - tests: some improvements for the spread log parser
+ - interfaces/network-manager-observe: Update for libnm / dart
+ clients
+ - tests: add ntp related debug around "auto-refresh" test
+ - boot: expand on the fact that reseal taking modeenv is very
+ intentional
+ - cmd/snap-seccomp/syscalls: update syscalls to match libseccomp
+ abad8a8f4
+ - data/selinux: update the policy to allow snapd to talk to
+ org.freedesktop.timedate1
+ - o/snapstate: keep old revision if install doesn't add new one
+ - overlord/state: add a unit test for a kernel+base refresh like
+ sequence
+ - desktop, usersession: observe notifications
+ - osutil/disks: add AllPhysicalDisks()
+ - timeutil,deviceutil: fix unit tests on systems without dbus or
+ without ntp-sync
+ - cmd/snap-bootstrap/README: explain all the things (well most of
+ them anyways)
+ - docs: add run-checks dependency install instruction
+ - o/snapstate: do not prune refresh-candidates if gate-auto-refresh-
+ hook feature is not enabled
+ - o/snapstate: test relink remodel helpers do a proper subset of
+ doInstall and rework the verify*Tasks helpers
+ - tests/main/mount-ns: make the test run early
+ - tests: add `--debug` to netplan apply
+ - many: wait for up to 10min for NTP synchronization before
+ autorefresh
+ - tests: initialize CHANGE_ID in _wait_autorefresh
+ - sandbox/cgroup: freeze and thaw cgroups related to services and
+ scopes only
+ - tests: add more debug around qemu-nbd
+ - o/hookstate: print cohort with snapctl refresh --pending (#10985)
+ - tests: misc robustness changes
+ - o/snapstate: improve install/update tests (#10850)
+ - tests: clean up test tools
+ - spread.yaml: show `journalctl -e` for all suites on debug
+ - tests: give interfaces-udisks2 more time for the loop device to
+ appear
+ - tests: set memory limit for snapd
+ - tests: increase timeout/add debug around nbd0 mounting (up, see
+ LP:#1949513)
+ - snapstate: add debug message where a snap is mounted
+ - tests: give nbd0 more time to show up in preseed-lxd
+ - interfaces/dsp: add more ambarella things
+ - cmd/snap: improve snap disconnect arg parsing and err msg
+ - tests: disable nested lxd snapd testing
+ - tests: disable flaky "interfaces-udisks2" on ubuntu-18.04-32
+ - o/snapstate: avoid validationSetsSuite repeating snapmgrTestSuite
+ - sandbox/cgroup: wait for start transient unit job to finish
+ - o/snapstate: fix task order, tweak errors, add unit tests for
+ remodel helpers
+ - osutil/disks: re-org methods for end of usable region, size
+ information
+ - build-aux: ensure that debian packaging matches build-base
+ - docs: update HACKING.md instructions for snapd 2.52 and later
+ - spread: run lxd tests with version from latest/edge
+ - interfaces: suppress denial of sys_module capability
+ - osutil/disks: add methods to replace gadget/ondisk functions
+ - tests: split test tools - part 1
+ - tests: fix nested tests on uc20
+ - data/selinux: allow snap-confine to read udev's database
+ - i/b/common_test: refactor AppArmor features test
+ - tests: run spread tests on debian 11
+ - o/devicestate: copy timesyncd clock timestamp during install
+ - interfaces/builtin: do not probe parser features when apparmor
+ isn't available
+ - interface/modem-manager: allow connecting to the mbim/qmi proxy
+ - tests: fix error message in run-checks
+ - tests: spread test for validation sets enforcing
+ - cmd/snap-confine: lazy set up of device cgroup, only when devices
+ were assigned
+ - o/snapstate: deduplicate snap names in remove/install/update
+ - tests/main/selinux-data-context: use session when performing
+ actions as test user
+ - packaging/opensuse: sync with openSUSE packaging, enable AppArmor
+ on 15.3+
+ - interfaces: skip connection of netlink interface on older
+ systems
+ - asserts, o/snapstate: honor IgnoreValidation flag when checking
+ installed snaps
+ - tests/main/apparmor-batch-reload: fix fake apparmor_parser to
+ handle --preprocess
+ - sandbox/apparmor, interfaces/apparmor: detect bpf capability,
+ generate snippet for s-c
+ - release-tools/repack-debian-tarball.sh: fix c-vendor dir
+ - tests: test for enforcing with prerequisites
+ - tests/main/snapd-sigterm: fix race conditions
+ - spread: run lxd tests with version from latest/stable
+ - run-checks: remove --spread from help message
+ - secboot: use latest secboot with tpm legacy platform and v2 fully
+ optional
+ - tests/lib/pkgdb: install strace on Debian 11 and Sid
+ - tests: ensure systemd-timesyncd is installed on debian
+ - interfaces/u2f-devices: add Nitrokey 3
+ - tests: update the ubuntu-image channel to candidate
+ - osutil/disks/labels: simplify decoding algorithm
+ - tests: not testing lxd snap anymore on i386 architecture
+ - o/snapstate, hookstate: print remaining hold time on snapctl
+ --hold
+ - cmd/snap: support --ignore-validation with snap install client
+ command
+ - tests/snapd-sigterm: be more robust against service restart
+ - tests: simplify mock script for apparmor_parser
+ - o/devicestate, o/servicestate: update gadget assets and cmdline
+ when remodeling
+ - tests/nested/manual/refresh-revert-fundamentals: re-enable
+ encryption
+ - osutil/disks: fix bug in BlkIDEncodeLabel, add BlkIDDecodeLabel
+ - gadget, osutil/disks: fix some bugs from prior PR'sin the dir.
+ - secboot: revert move to new version (revert #10715)
+ - cmd/snap-confine: die when snap process is outside of snap
+ specific cgroup
+ - many: mv MockDeviceNameDisksToPartitionMapping ->
+ MockDeviceNameToDiskMapping
+ - interfaces/builtin: Add '/com/canonical/dbusmenu' path access to
+ 'unity7' interface
+ - interfaces/builtin/hardware-observer: add /proc/bus/input/devices
+ too
+ - osutil/disks, many: switch to defining Partitions directly for
+ MockDiskMapping
+ - tests: remove extra-snaps-assertions test
+ - interface/modem-manager: add accept for MBIM/QMI proxy clients
+ - tests/nested/core/core20-create-recovery: fix passing of data to
+ curl
+ - daemon: allow enabling enforce mode
+ - daemon: use the syscall connection to get the socket credentials
+ - i/builtin/kubernetes_support: add access to Calico lock file
+ - osutil: ensure parent dir is opened and sync'd
+ - tests: using test-snapd-curl snap instead of http snap
+ - overlord: add managers unit test demonstrating cyclic dependency
+ between gadget and kernel updates
+ - gadget/ondisk.go: include the filesystem UUID in the returned
+ OnDiskVolume
+ - packaging: fixes for building on openSUSE
+ - o/configcore: allow hostnames up to 253 characters, with dot-
+ delimited elements
+ - gadget/ondisk.go: add listBlockDevices() to get all block devices
+ on a system
+ - gadget: add mapping trait types + functions to save/load
+ - interfaces: add polkit security backend
+ - cmd/snap-confine/snap-confine.apparmor.in: update ld rule for
+ s390x impish
+ - tests: merge coverage results
+ - tests: remove "features" from fde-setup.go example
+ - fde: add new device-setup support to fde-setup
+ - gadget: add `encryptedDevice` and add encryptedDeviceLUKS
+ - spread: use `bios: uefi` for uc20
+ - client: fail fast on non-retryable errors
+ - tests: support running all spread tests with experimental features
+ - tests: check that a snap that doesn't have gate-auto-refresh hook
+ can call --proceed
+ - o/snapstate: support ignore-validation flag when updating to a
+ specific snap revision
+ - o/snapstate: test prereq update if started by old version
+ - tests/main: disable cgroup-devices-v1 and freezer tests on 21.10
+ - tests/main/interfaces-many: run both variants on all possible
+ Ubuntu systems
+ - gadget: mv ensureLayoutCompatibility to gadget proper, add
+ gadgettest pkg
+ - many: replace state.State restart support with overlord/restart
+ - overlord: fix generated snap-revision assertions in remodel unit
+ tests
+
+ -- Michael Vogt <michael.vogt@ubuntu.com> Fri, 17 Dec 2021 15:49:18 +0100
+
+snapd (2.53.4~14.04) trusty; urgency=medium
+
+ * New upstream release, LP: #1929842
+ - devicestate: mock devicestate.MockTimeutilIsNTPSynchronized to
+ avoid host env leaking into tests
+ - timeutil: return NoTimedate1Error if it can't connect to the
+ system bus
+
+ -- Ian Johnson <ian.johnson@canonical.com> Thu, 02 Dec 2021 17:16:48 -0600
+
+snapd (2.53.3~14.04) trusty; urgency=medium
+
+ * New upstream release, LP: #1929842
+ - devicestate: Unregister deletes the device key pair as well
+ - daemon,tests: support forgetting device serial via API
+ - configcore: relax validation rules for hostname
+ - o/devicestate: introduce DeviceManager.Unregister
+ - packaging/ubuntu, packaging/debian: depend on dbus-session-bus
+ provider
+ - many: wait for up to 10min for NTP synchronization before
+ autorefresh
+ - interfaces/interfaces/scsi_generic: add interface for scsi generic
+ devices
+ - interfaces/microstack-support: set controlsDeviceCgroup to true
+ - interface/builtin/log_observe: allow to access /dev/kmsg
+ - daemon: write formdata file parts to snaps dir
+ - spread: run lxd tests with version from latest/edge
+ - cmd/libsnap-confine-private: fix snap-device-helper device allow
+ list modification on cgroup v2
+ - interfaces/builtin/dsp: add proc files for monitoring Ambarella
+ DSP firmware
+ - interfaces/builtin/dsp: update proc file accordingly
+
+ -- Ian Johnson <ian.johnson@canonical.com> Thu, 02 Dec 2021 11:42:15 -0600
+
snapd (2.53.2~14.04) trusty; urgency=medium
* New upstream release, LP: #1946127
diff --git a/packaging/ubuntu-14.04/snapd.postrm b/packaging/ubuntu-14.04/snapd.postrm
index befa073ae4..4626e71f60 100644
--- a/packaging/ubuntu-14.04/snapd.postrm
+++ b/packaging/ubuntu-14.04/snapd.postrm
@@ -73,6 +73,7 @@ if [ "$1" = "purge" ]; then
done
# modules
rm -f "/etc/modules-load.d/snap.${snap}.conf"
+ rm -f "/etc/modprobe.d/snap.${snap}.conf"
# udev rules
find /etc/udev/rules.d -name "*-snap.${snap}.rules" -execdir rm -f "{}" \;
# dbus policy files
diff --git a/packaging/ubuntu-14.04/source b/packaging/ubuntu-14.04/source
index d4219c032f..05552630c6 120000
--- a/packaging/ubuntu-14.04/source
+++ b/packaging/ubuntu-14.04/source
@@ -1 +1 @@
-../ubuntu-16.04/source/ \ No newline at end of file
+../ubuntu-16.04/source \ No newline at end of file
diff --git a/packaging/ubuntu-16.04/changelog b/packaging/ubuntu-16.04/changelog
index 924a7f1d66..7fe71f481c 100644
--- a/packaging/ubuntu-16.04/changelog
+++ b/packaging/ubuntu-16.04/changelog
@@ -1,3 +1,383 @@
+snapd (2.54.1) xenial; urgency=medium
+
+ * New upstream release, LP: #1955137
+ - buid-aux: set version before calling ./generate-packaging-dir
+ This fixes the "dirty" suffix in the auto-generated version
+
+ -- Michael Vogt <michael.vogt@ubuntu.com> Mon, 20 Dec 2021 10:06:09 +0100
+
+snapd (2.54) xenial; urgency=medium
+
+ * New upstream release, LP: #1955137
+ - interfaces/builtin/opengl.go: add boot_vga sys/devices file
+ - o/configstate/configcore: add tmpfs.size option
+ - tests: moving to manual opensuse 15.2
+ - cmd/snap-device-helper: bring back the device type identification
+ behavior, but for remove action fallback only
+ - cmd/snap-failure: use snapd from the snapd snap if core is not
+ present
+ - tests/core/failover: enable the test on core18
+ - o/devicestate: ensure proper order when remodel does a simple
+ switch-snap-channel
+ - builtin/interfaces: add shared memory interface
+ - overlord: extend kernel/base success and failover with bootenv
+ checks
+ - o/snapstate: check disk space w/o store if possible
+ - snap-bootstrap: Mount snaps read only
+ - gadget/install: do not re-create partitions using OnDiskVolume
+ after deletion
+ - many: fix formatting w/ latest go version
+ - devicestate,timeutil: improve logging of NTP sync
+ - tests/main/security-device-cgroups-helper: more debugs
+ - cmd/snap: print a placeholder for version of broken snaps
+ - o/snapstate: mock system with classic confinement support
+ - cmd: Fixup .clangd to use correct syntax
+ - tests: run spread tests in fedora-35
+ - data/selinux: allow snapd to access /etc/modprobe.d
+ - mount-control: step 2
+ - daemon: add multiple snap sideload to API
+ - tests/lib/pkgdb: install dbus-user-session during prepare, drop
+ dbus-x11
+ - systemd: provide more detailed errors for unimplemented method in
+ emulation mode
+ - tests: avoid checking TRUST_TEST_KEYS on restore on remodel-base
+ test
+ - tests: retry umounting /var/lib/snapd/seed on uc20 on fsck-on-boot
+ test
+ - o/snapstate: add hide/expose snap data to backend
+ - interfaces: kernel-module-load
+ - snap: add support for `snap watch
+ --last={revert,enable,disable,switch}`
+ - tests/main/security-udev-input-subsystem: drop info from udev
+ - tests/core/kernel-and-base-single-reboot-failover,
+ tests/lib/fakestore: verify failover scenario
+ - tests/main/security-device-cgroups-helper: collect some debug info
+ when the test fails
+ - tests/nested/manual/core20-remodel: wait for device to have a
+ serial before starting a remodel
+ - tests/main/generic-unregister: test re-registration if not blocked
+ - o/snapstate, assertsate: validation sets/undo on partial failure
+ - tests: ensure snapd can be downloaded as a module
+ - snapdtool, many: support additional key/value flags in info file
+ - data/env: improve fish shell env setup
+ - usersession/client: provide a way for client to send messages to a
+ subset of users
+ - tests: verify that simultaneous refresh of kernel and base
+ triggers a single reboot only
+ - devicestate: Unregister deletes the device key pair as well
+ - daemon,tests: support forgetting device serial via API
+ - asserts: change behavior of alternative attribute matcher
+ - configcore: relax validation rules for hostname
+ - cmd/snap-confine: do not include libglvnd libraries from the host
+ system
+ - overlord, tests: add managers and a spread test for UC20 to UC22
+ remodel
+ - HACKING.md: adjust again for building the snapd snap
+ - systemd: add support for systemd unit alias names
+ - o/snapstate: add InstallPathMany
+ - gadget: allow EnsureLayoutCompatibility to ensure disk has all
+ laid out structsnow reject/fail:
+ - packaging/ubuntu, packaging/debian: depend on dbus-session-bus
+ provider (#11111)
+ - interfaces/interfaces/scsi_generic: add interface for scsi generic
+ de… (#10936)
+ - osutil/disks/mockdisk.go: add MockDevicePathToDiskMapping
+ - interfaces/microstack-support: set controlsDeviceCgroup to true
+ - network-setup-control: add netplan generate D-Bus rules
+ - interface/builtin/log_observe: allow to access /dev/kmsg
+ - .github/workflows/test.yaml: restore failing of spread tests on
+ errors (nested)
+ - gadget: tweaks to DiskStructureDeviceTraits + expand test cases
+ - tests/lib/nested.sh: allow tests to use their own core18 in extra-
+ snaps-path
+ - interfaces/browser-support: Update rules for Edge
+ - o/devicestate: during remodel first check pending download tasks
+ for snaps
+ - polkit: add a package to validate polkit policy files
+ - HACKING.md: document building the snapd snap and splicing it into
+ the core snap
+ - interfaces/udev: fix installing snaps inside lxd in 21.10
+ - o/snapstate: refactor disk space checks
+ - tests: add (strict) microk8s smoke test
+ - osutil/strace: try to enable strace on more arches
+ - cmd/libsnap-confine-private: fix snap-device-helper device allow
+ list modification on cgroup v2
+ - tests/main/snapd-reexec-snapd-snap: improve debugging
+ - daemon: write formdata file parts to snaps dir
+ - systemd: add support for .target units
+ - tests: run snap-disconnect on uc16
+ - many: add experimental setting to allow using ~/.snap/data instead
+ of ~/snap
+ - overlord/snapstate: perform a single reboot when updating boot
+ base and kernel
+ - kernel/fde: add DeviceUnlockKernelHookDeviceMapperBackResolver,
+ use w/ disks pkg
+ - o/devicestate: introduce DeviceManager.Unregister
+ - interfaces: allow receiving PropertiesChanged on the mpris plug
+ - tests: new tool used to retrieve data from mongo db
+ - daemon: amend ssh keys coming from the store
+ - tests: Include the tools from snapd-testing-tools project in
+ "$TESTSTOOLS"
+ - tests: new workflow step used to report spread error to mongodb
+ - interfaces/builtin/dsp: update proc files for ambarella flavor
+ - gadget: replace ondisk implementation with disks package, refactor
+ part calcs
+ - tests: Revert "tests: disable flaky uc18 tests until systemd is
+ fixed"
+ - Revert: "many: Vendor apparmor-3.0.3 into the snapd snap"
+ - asserts: rename "white box" to "clear box" (woke checker)
+ - many: Vendor apparmor-3.0.3 into the snapd snap
+ - tests: reorganize the debug-each on the spread.yaml
+ - packaging: sync with downstream packaging in Fedora and openSUSE
+ - tests: disable flaky uc18 tests until systemd is fixed
+ - data/env: provide profile setup for fish shell
+ - tests: use ubuntu-image 1.11 from stable channel
+ - gadget/gadget.go: include disk schema in the disk device volume
+ traits too
+ - tests/main/security-device-cgroups-strict-enforced: extend the
+ comments
+ - README.md: point at bugs.launchpad.net/snapd instead of snappy
+ project
+ - osutil/disks: introduce RegisterDeviceMapperBackResolver + use for
+ crypt-luks2
+ - packaging: make postrm script robust against `rm` failures
+ - tests: print extra debug on auto-refresh-gating test failure
+ - o/assertstate, api: move enforcing/monitoring from api to
+ assertstate, save history
+ - tests: skip the test-snapd-timedate-control-consumer.date to avoid
+ NTP sync error
+ - gadget/install: use disks functions to implement deviceFromRole,
+ also rename
+ - tests: the `lxd` test is failing right now on 21.10
+ - o/snapstate: account for deleted revs when undoing install
+ - interfaces/builtin/block_devices: allow blkid to print block
+ device attributes
+ - gadget: include size + sector-size in DiskVolumeDeviceTraits
+ - cmd/libsnap-confine-private: do not deny all devices when reusing
+ the device cgroup
+ - interfaces/builtin/time-control: allow pps access
+ - o/snapstate/handlers: propagate read errors on "copy-snap-data"
+ - osutil/disks: add more fields to Partition, populate them during
+ discovery
+ - interfaces/u2f-devices: add Trezor and Trezor v2 keys
+ - interfaces: timezone-control, add permission for ListTimezones
+ DBus call
+ - o/snapstate: remove repeated test assertions
+ - tests: skip `snap advise-command` test if the store is overloaded
+ - cmd: create ~/snap dir with 0700 perms
+ - interfaces/apparmor/template.go: allow udevadm from merged usr
+ systems
+ - github: leave a comment documenting reasons for pipefail
+ - github: enable pipefail when running spread
+ - osutil/disks: add DiskFromPartitionDeviceNode
+ - gadget, many: add model param to Update()
+ - cmd/snap-seccomp: add riscv64 support
+ - o/snapstate: maintain a RevertStatus map in SnapState
+ - tests: enable lxd tests on impish system
+ - tests: (partially) revert the memory limits PR#r10241
+ - o/assertstate: functions for handling validation sets tracking
+ history
+ - tests: some improvements for the spread log parser
+ - interfaces/network-manager-observe: Update for libnm / dart
+ clients
+ - tests: add ntp related debug around "auto-refresh" test
+ - boot: expand on the fact that reseal taking modeenv is very
+ intentional
+ - cmd/snap-seccomp/syscalls: update syscalls to match libseccomp
+ abad8a8f4
+ - data/selinux: update the policy to allow snapd to talk to
+ org.freedesktop.timedate1
+ - o/snapstate: keep old revision if install doesn't add new one
+ - overlord/state: add a unit test for a kernel+base refresh like
+ sequence
+ - desktop, usersession: observe notifications
+ - osutil/disks: add AllPhysicalDisks()
+ - timeutil,deviceutil: fix unit tests on systems without dbus or
+ without ntp-sync
+ - cmd/snap-bootstrap/README: explain all the things (well most of
+ them anyways)
+ - docs: add run-checks dependency install instruction
+ - o/snapstate: do not prune refresh-candidates if gate-auto-refresh-
+ hook feature is not enabled
+ - o/snapstate: test relink remodel helpers do a proper subset of
+ doInstall and rework the verify*Tasks helpers
+ - tests/main/mount-ns: make the test run early
+ - tests: add `--debug` to netplan apply
+ - many: wait for up to 10min for NTP synchronization before
+ autorefresh
+ - tests: initialize CHANGE_ID in _wait_autorefresh
+ - sandbox/cgroup: freeze and thaw cgroups related to services and
+ scopes only
+ - tests: add more debug around qemu-nbd
+ - o/hookstate: print cohort with snapctl refresh --pending (#10985)
+ - tests: misc robustness changes
+ - o/snapstate: improve install/update tests (#10850)
+ - tests: clean up test tools
+ - spread.yaml: show `journalctl -e` for all suites on debug
+ - tests: give interfaces-udisks2 more time for the loop device to
+ appear
+ - tests: set memory limit for snapd
+ - tests: increase timeout/add debug around nbd0 mounting (up, see
+ LP:#1949513)
+ - snapstate: add debug message where a snap is mounted
+ - tests: give nbd0 more time to show up in preseed-lxd
+ - interfaces/dsp: add more ambarella things
+ - cmd/snap: improve snap disconnect arg parsing and err msg
+ - tests: disable nested lxd snapd testing
+ - tests: disable flaky "interfaces-udisks2" on ubuntu-18.04-32
+ - o/snapstate: avoid validationSetsSuite repeating snapmgrTestSuite
+ - sandbox/cgroup: wait for start transient unit job to finish
+ - o/snapstate: fix task order, tweak errors, add unit tests for
+ remodel helpers
+ - osutil/disks: re-org methods for end of usable region, size
+ information
+ - build-aux: ensure that debian packaging matches build-base
+ - docs: update HACKING.md instructions for snapd 2.52 and later
+ - spread: run lxd tests with version from latest/edge
+ - interfaces: suppress denial of sys_module capability
+ - osutil/disks: add methods to replace gadget/ondisk functions
+ - tests: split test tools - part 1
+ - tests: fix nested tests on uc20
+ - data/selinux: allow snap-confine to read udev's database
+ - i/b/common_test: refactor AppArmor features test
+ - tests: run spread tests on debian 11
+ - o/devicestate: copy timesyncd clock timestamp during install
+ - interfaces/builtin: do not probe parser features when apparmor
+ isn't available
+ - interface/modem-manager: allow connecting to the mbim/qmi proxy
+ - tests: fix error message in run-checks
+ - tests: spread test for validation sets enforcing
+ - cmd/snap-confine: lazy set up of device cgroup, only when devices
+ were assigned
+ - o/snapstate: deduplicate snap names in remove/install/update
+ - tests/main/selinux-data-context: use session when performing
+ actions as test user
+ - packaging/opensuse: sync with openSUSE packaging, enable AppArmor
+ on 15.3+
+ - interfaces: skip connection of netlink interface on older
+ systems
+ - asserts, o/snapstate: honor IgnoreValidation flag when checking
+ installed snaps
+ - tests/main/apparmor-batch-reload: fix fake apparmor_parser to
+ handle --preprocess
+ - sandbox/apparmor, interfaces/apparmor: detect bpf capability,
+ generate snippet for s-c
+ - release-tools/repack-debian-tarball.sh: fix c-vendor dir
+ - tests: test for enforcing with prerequisites
+ - tests/main/snapd-sigterm: fix race conditions
+ - spread: run lxd tests with version from latest/stable
+ - run-checks: remove --spread from help message
+ - secboot: use latest secboot with tpm legacy platform and v2 fully
+ optional
+ - tests/lib/pkgdb: install strace on Debian 11 and Sid
+ - tests: ensure systemd-timesyncd is installed on debian
+ - interfaces/u2f-devices: add Nitrokey 3
+ - tests: update the ubuntu-image channel to candidate
+ - osutil/disks/labels: simplify decoding algorithm
+ - tests: not testing lxd snap anymore on i386 architecture
+ - o/snapstate, hookstate: print remaining hold time on snapctl
+ --hold
+ - cmd/snap: support --ignore-validation with snap install client
+ command
+ - tests/snapd-sigterm: be more robust against service restart
+ - tests: simplify mock script for apparmor_parser
+ - o/devicestate, o/servicestate: update gadget assets and cmdline
+ when remodeling
+ - tests/nested/manual/refresh-revert-fundamentals: re-enable
+ encryption
+ - osutil/disks: fix bug in BlkIDEncodeLabel, add BlkIDDecodeLabel
+ - gadget, osutil/disks: fix some bugs from prior PR'sin the dir.
+ - secboot: revert move to new version (revert #10715)
+ - cmd/snap-confine: die when snap process is outside of snap
+ specific cgroup
+ - many: mv MockDeviceNameDisksToPartitionMapping ->
+ MockDeviceNameToDiskMapping
+ - interfaces/builtin: Add '/com/canonical/dbusmenu' path access to
+ 'unity7' interface
+ - interfaces/builtin/hardware-observer: add /proc/bus/input/devices
+ too
+ - osutil/disks, many: switch to defining Partitions directly for
+ MockDiskMapping
+ - tests: remove extra-snaps-assertions test
+ - interface/modem-manager: add accept for MBIM/QMI proxy clients
+ - tests/nested/core/core20-create-recovery: fix passing of data to
+ curl
+ - daemon: allow enabling enforce mode
+ - daemon: use the syscall connection to get the socket credentials
+ - i/builtin/kubernetes_support: add access to Calico lock file
+ - osutil: ensure parent dir is opened and sync'd
+ - tests: using test-snapd-curl snap instead of http snap
+ - overlord: add managers unit test demonstrating cyclic dependency
+ between gadget and kernel updates
+ - gadget/ondisk.go: include the filesystem UUID in the returned
+ OnDiskVolume
+ - packaging: fixes for building on openSUSE
+ - o/configcore: allow hostnames up to 253 characters, with dot-
+ delimited elements
+ - gadget/ondisk.go: add listBlockDevices() to get all block devices
+ on a system
+ - gadget: add mapping trait types + functions to save/load
+ - interfaces: add polkit security backend
+ - cmd/snap-confine/snap-confine.apparmor.in: update ld rule for
+ s390x impish
+ - tests: merge coverage results
+ - tests: remove "features" from fde-setup.go example
+ - fde: add new device-setup support to fde-setup
+ - gadget: add `encryptedDevice` and add encryptedDeviceLUKS
+ - spread: use `bios: uefi` for uc20
+ - client: fail fast on non-retryable errors
+ - tests: support running all spread tests with experimental features
+ - tests: check that a snap that doesn't have gate-auto-refresh hook
+ can call --proceed
+ - o/snapstate: support ignore-validation flag when updating to a
+ specific snap revision
+ - o/snapstate: test prereq update if started by old version
+ - tests/main: disable cgroup-devices-v1 and freezer tests on 21.10
+ - tests/main/interfaces-many: run both variants on all possible
+ Ubuntu systems
+ - gadget: mv ensureLayoutCompatibility to gadget proper, add
+ gadgettest pkg
+ - many: replace state.State restart support with overlord/restart
+ - overlord: fix generated snap-revision assertions in remodel unit
+ tests
+
+ -- Michael Vogt <michael.vogt@ubuntu.com> Fri, 17 Dec 2021 15:49:18 +0100
+
+snapd (2.53.4) xenial; urgency=medium
+
+ * New upstream release, LP: #1929842
+ - devicestate: mock devicestate.MockTimeutilIsNTPSynchronized to
+ avoid host env leaking into tests
+ - timeutil: return NoTimedate1Error if it can't connect to the
+ system bus
+
+ -- Ian Johnson <ian.johnson@canonical.com> Thu, 02 Dec 2021 17:16:48 -0600
+
+snapd (2.53.3) xenial; urgency=medium
+
+ * New upstream release, LP: #1929842
+ - devicestate: Unregister deletes the device key pair as well
+ - daemon,tests: support forgetting device serial via API
+ - configcore: relax validation rules for hostname
+ - o/devicestate: introduce DeviceManager.Unregister
+ - packaging/ubuntu, packaging/debian: depend on dbus-session-bus
+ provider
+ - many: wait for up to 10min for NTP synchronization before
+ autorefresh
+ - interfaces/interfaces/scsi_generic: add interface for scsi generic
+ devices
+ - interfaces/microstack-support: set controlsDeviceCgroup to true
+ - interface/builtin/log_observe: allow to access /dev/kmsg
+ - daemon: write formdata file parts to snaps dir
+ - spread: run lxd tests with version from latest/edge
+ - cmd/libsnap-confine-private: fix snap-device-helper device allow
+ list modification on cgroup v2
+ - interfaces/builtin/dsp: add proc files for monitoring Ambarella
+ DSP firmware
+ - interfaces/builtin/dsp: update proc file accordingly
+
+ -- Ian Johnson <ian.johnson@canonical.com> Thu, 02 Dec 2021 11:42:15 -0600
+
snapd (2.53.2) xenial; urgency=medium
* New upstream release, LP: #1946127
diff --git a/packaging/ubuntu-16.04/rules b/packaging/ubuntu-16.04/rules
index 885ae31379..2ba72706b4 100755
--- a/packaging/ubuntu-16.04/rules
+++ b/packaging/ubuntu-16.04/rules
@@ -164,9 +164,6 @@ override_dh_clean:
$(MAKE) -C cmd distclean || true
# XXX: hacky^2
(cd c-vendor/squashfuse && rm -f snapfuse && make distclean || true )
- # XXX: drop old mvo5/libseccomp
- rm -f ./cmd/snap-seccomp/old_seccomp.go
- sed '/mvo5\/libseccomp-golang/d' -i go.mod go.sum vendor/modules.txt || true
override_dh_auto_build:
# usually done via `go generate` but that is not supported on powerpc
diff --git a/packaging/ubuntu-16.04/snapd.postrm b/packaging/ubuntu-16.04/snapd.postrm
index 0dba123050..163be54fbb 100644
--- a/packaging/ubuntu-16.04/snapd.postrm
+++ b/packaging/ubuntu-16.04/snapd.postrm
@@ -85,6 +85,7 @@ if [ "$1" = "purge" ]; then
fi
# modules
rm -f "/etc/modules-load.d/snap.${snap}.conf"
+ rm -f "/etc/modprobe.d/snap.${snap}.conf"
# timer and socket units
find /etc/systemd/system -name "snap.${snap}.*.timer" -o -name "snap.${snap}.*.socket" | while read -r f; do
systemctl_stop "$(basename "$f")"
diff --git a/polkit/pid_start_time.go b/polkit/pid_start_time.go
index 93aca0fffb..f1870a233f 100644
--- a/polkit/pid_start_time.go
+++ b/polkit/pid_start_time.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build linux
// +build linux
/*
diff --git a/polkit/pid_start_time_test.go b/polkit/pid_start_time_test.go
index 7bc1107d1b..dad807bc7a 100644
--- a/polkit/pid_start_time_test.go
+++ b/polkit/pid_start_time_test.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build linux
// +build linux
/*
diff --git a/secboot/encrypt_dummy.go b/secboot/encrypt_dummy.go
index 60c9cdd661..6f08781ec4 100644
--- a/secboot/encrypt_dummy.go
+++ b/secboot/encrypt_dummy.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build nosecboot
// +build nosecboot
/*
diff --git a/secboot/encrypt_sb.go b/secboot/encrypt_sb.go
index 0082b9be67..28136ded12 100644
--- a/secboot/encrypt_sb.go
+++ b/secboot/encrypt_sb.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !nosecboot
// +build !nosecboot
/*
diff --git a/secboot/encrypt_sb_test.go b/secboot/encrypt_sb_test.go
index 105a1da722..3841786736 100644
--- a/secboot/encrypt_sb_test.go
+++ b/secboot/encrypt_sb_test.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !nosecboot
// +build !nosecboot
/*
diff --git a/secboot/export_sb_test.go b/secboot/export_sb_test.go
index 1d8d8c005a..03a9c4c2e3 100644
--- a/secboot/export_sb_test.go
+++ b/secboot/export_sb_test.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !nosecboot
// +build !nosecboot
/*
diff --git a/secboot/secboot_dummy.go b/secboot/secboot_dummy.go
index 2036a52d73..013ad6c609 100644
--- a/secboot/secboot_dummy.go
+++ b/secboot/secboot_dummy.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build nosecboot
// +build nosecboot
/*
diff --git a/secboot/secboot_hooks.go b/secboot/secboot_hooks.go
index 9c7298a8e2..baaa39eee5 100644
--- a/secboot/secboot_hooks.go
+++ b/secboot/secboot_hooks.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !nosecboot
// +build !nosecboot
/*
diff --git a/secboot/secboot_sb.go b/secboot/secboot_sb.go
index 7f68d7a12e..3085275c8f 100644
--- a/secboot/secboot_sb.go
+++ b/secboot/secboot_sb.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !nosecboot
// +build !nosecboot
/*
diff --git a/secboot/secboot_sb_test.go b/secboot/secboot_sb_test.go
index 5e60b6220c..3b0ac83146 100644
--- a/secboot/secboot_sb_test.go
+++ b/secboot/secboot_sb_test.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !nosecboot
// +build !nosecboot
/*
diff --git a/secboot/secboot_tpm.go b/secboot/secboot_tpm.go
index d7fc0c5fee..fb074430bf 100644
--- a/secboot/secboot_tpm.go
+++ b/secboot/secboot_tpm.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !nosecboot
// +build !nosecboot
/*
diff --git a/snap/helpers.go b/snap/helpers.go
index 0d24abdff4..cacb3074e7 100644
--- a/snap/helpers.go
+++ b/snap/helpers.go
@@ -20,9 +20,74 @@
package snap
import (
+ "os/user"
+ "path/filepath"
+ "strconv"
+ "syscall"
+
+ "github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/snap/naming"
)
+var (
+ userLookupId = user.LookupId
+)
+
func IsSnapd(snapID string) bool {
return snapID == naming.WellKnownSnapID("snapd")
}
+
+// AllUsers returns a list of users, including the root user and all users that
+// can be found under /home with a snap directory.
+func AllUsers(opts *dirs.SnapDirOptions) ([]*user.User, error) {
+ ds, err := filepath.Glob(DataHomeGlob(opts))
+ if err != nil {
+ // can't happen?
+ return nil, err
+ }
+
+ users := make([]*user.User, 1, len(ds)+1)
+ root, err := user.LookupId("0")
+ if err != nil {
+ return nil, err
+ }
+ users[0] = root
+ seen := make(map[uint32]bool, len(ds)+1)
+ seen[0] = true
+ var st syscall.Stat_t
+ for _, d := range ds {
+ err := syscall.Stat(d, &st)
+ if err != nil {
+ continue
+ }
+ if seen[st.Uid] {
+ continue
+ }
+ seen[st.Uid] = true
+ usr, err := userLookupId(strconv.FormatUint(uint64(st.Uid), 10))
+ if err != nil {
+ // Treat all non-nil errors as user.Unknown{User,Group}Error's, as
+ // currently Go's handling of returned errno from get{pw,gr}nam_r
+ // in the cgo implementation of user.Lookup is lacking, and thus
+ // user.Unknown{User,Group}Error is returned only when errno is 0
+ // and the list of users/groups is empty, but as per the man page
+ // for get{pw,gr}nam_r, there are many other errno's that typical
+ // systems could return to indicate that the user/group wasn't
+ // found, however unfortunately the POSIX standard does not actually
+ // dictate what errno should be used to indicate "user/group not
+ // found", and so even if Go is more robust, it may not ever be
+ // fully robust. See from the man page:
+ //
+ // > It [POSIX.1-2001] does not call "not found" an error, hence
+ // > does not specify what value errno might have in this situation.
+ // > But that makes it impossible to recognize errors.
+ //
+ // See upstream Go issue: https://github.com/golang/go/issues/40334
+ continue
+ } else {
+ users = append(users, usr)
+ }
+ }
+
+ return users, nil
+}
diff --git a/snap/info.go b/snap/info.go
index 6bf218ab73..590cd1638f 100644
--- a/snap/info.go
+++ b/snap/info.go
@@ -303,7 +303,7 @@ type Info struct {
Broken string
// The information in these fields is ephemeral, available only from the
- // store.
+ // store or when read from a snap file.
DownloadInfo
Prices map[string]float64
diff --git a/snapdenv/withtestkeys.go b/snapdenv/withtestkeys.go
index f02f0a1246..7614ed9efb 100644
--- a/snapdenv/withtestkeys.go
+++ b/snapdenv/withtestkeys.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build withtestkeys
// +build withtestkeys
/*
diff --git a/snapdtool/info_file.go b/snapdtool/info_file.go
index 2780334501..93c5e0730d 100644
--- a/snapdtool/info_file.go
+++ b/snapdtool/info_file.go
@@ -20,34 +20,55 @@
package snapdtool
import (
- "bytes"
+ "bufio"
"fmt"
- "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
)
-// SnapdVersionFromInfoFile returns snapd version read for the
-// given info" file, pointed by infoPath.
-// The format of the "info" file is a single line with "VERSION=..."
-// in it. The file is produced by mkversion.sh and normally installed
-// along snapd binary in /usr/lib/snapd.
-func SnapdVersionFromInfoFile(infoPath string) (string, error) {
- content, err := ioutil.ReadFile(infoPath)
+// SnapdVersionFromInfoFile returns the snapd version read from the info file in
+// the given dir, as well as any other key/value pairs/flags in the file.
+// The format of the "info" file are lines with "KEY=VALUE" with the typical key
+// being just VERSION. The file is produced by mkversion.sh and normally
+// installed along snapd binary in /usr/lib/snapd.
+// Other typical keys in this file include SNAPD_APPARMOR_REEXEC, which
+// indicates whether or not the snapd-apparmor binary installed via the
+// traditional linux package of snapd supports re-exec into the version in the
+// snapd or core snaps.
+func SnapdVersionFromInfoFile(dir string) (version string, flags map[string]string, err error) {
+ infoPath := filepath.Join(dir, "info")
+ f, err := os.Open(infoPath)
if err != nil {
- return "", fmt.Errorf("cannot open snapd info file %q: %s", infoPath, err)
+ return "", nil, fmt.Errorf("cannot open snapd info file %q: %s", infoPath, err)
}
+ defer f.Close()
- if !bytes.HasPrefix(content, []byte("VERSION=")) {
- idx := bytes.Index(content, []byte("\nVERSION="))
- if idx < 0 {
- return "", fmt.Errorf("cannot find snapd version information in %q", content)
+ flags = map[string]string{}
+
+ scanner := bufio.NewScanner(f)
+ for scanner.Scan() {
+ line := scanner.Text()
+ if strings.HasPrefix(line, "VERSION=") {
+ version = strings.TrimPrefix(line, "VERSION=")
+ } else {
+ keyVal := strings.SplitN(line, "=", 2)
+ if len(keyVal) != 2 {
+ // potentially malformed line, just skip it
+ continue
+ }
+
+ flags[keyVal[0]] = keyVal[1]
}
- content = content[idx+1:]
}
- content = content[8:]
- idx := bytes.IndexByte(content, '\n')
- if idx > -1 {
- content = content[:idx]
+
+ if err := scanner.Err(); err != nil {
+ return "", nil, fmt.Errorf("error reading snapd info file %q: %v", infoPath, err)
+ }
+
+ if version == "" {
+ return "", nil, fmt.Errorf("cannot find snapd version information in file %q", infoPath)
}
- return string(content), nil
+ return version, flags, nil
}
diff --git a/snapdtool/info_file_test.go b/snapdtool/info_file_test.go
index 1ce9a5dd23..0f311f79c8 100644
--- a/snapdtool/info_file_test.go
+++ b/snapdtool/info_file_test.go
@@ -20,6 +20,7 @@
package snapdtool_test
import (
+ "fmt"
"io/ioutil"
"path/filepath"
@@ -33,8 +34,8 @@ type infoFileSuite struct{}
var _ = Suite(&infoFileSuite{})
func (s *infoFileSuite) TestNoVersionFile(c *C) {
- _, err := snapdtool.SnapdVersionFromInfoFile("/non-existing-file")
- c.Assert(err, ErrorMatches, `cannot open snapd info file "/non-existing-file":.*`)
+ _, _, err := snapdtool.SnapdVersionFromInfoFile("/non-existing-dir")
+ c.Assert(err, ErrorMatches, `cannot open snapd info file "/non-existing-dir/info":.*`)
}
func (s *infoFileSuite) TestNoVersionData(c *C) {
@@ -42,8 +43,8 @@ func (s *infoFileSuite) TestNoVersionData(c *C) {
infoFile := filepath.Join(top, "info")
c.Assert(ioutil.WriteFile(infoFile, []byte("foo"), 0644), IsNil)
- _, err := snapdtool.SnapdVersionFromInfoFile(infoFile)
- c.Assert(err, ErrorMatches, `cannot find snapd version information in "foo"`)
+ _, _, err := snapdtool.SnapdVersionFromInfoFile(top)
+ c.Assert(err, ErrorMatches, fmt.Sprintf(`cannot find snapd version information in file %q`, infoFile))
}
func (s *infoFileSuite) TestVersionHappy(c *C) {
@@ -51,7 +52,19 @@ func (s *infoFileSuite) TestVersionHappy(c *C) {
infoFile := filepath.Join(top, "info")
c.Assert(ioutil.WriteFile(infoFile, []byte("VERSION=1.2.3"), 0644), IsNil)
- ver, err := snapdtool.SnapdVersionFromInfoFile(infoFile)
+ ver, flags, err := snapdtool.SnapdVersionFromInfoFile(top)
c.Assert(err, IsNil)
c.Check(ver, Equals, "1.2.3")
+ c.Assert(flags, HasLen, 0)
+}
+
+func (s *infoFileSuite) TestInfoVersionFlags(c *C) {
+ top := c.MkDir()
+ infoFile := filepath.Join(top, "info")
+ c.Assert(ioutil.WriteFile(infoFile, []byte("VERSION=1.2.3\nFOO=BAR"), 0644), IsNil)
+
+ ver, flags, err := snapdtool.SnapdVersionFromInfoFile(top)
+ c.Assert(err, IsNil)
+ c.Check(ver, Equals, "1.2.3")
+ c.Assert(flags, DeepEquals, map[string]string{"FOO": "BAR"})
}
diff --git a/snapdtool/tool_linux.go b/snapdtool/tool_linux.go
index 986b106bdd..5e8e554d7f 100644
--- a/snapdtool/tool_linux.go
+++ b/snapdtool/tool_linux.go
@@ -76,8 +76,8 @@ func distroSupportsReExec() bool {
// Ensure we do not use older version of snapd, look for info file and ignore
// version of core that do not yet have it.
func coreSupportsReExec(coreOrSnapdPath string) bool {
- infoPath := filepath.Join(coreOrSnapdPath, filepath.Join(dirs.CoreLibExecDir, "info"))
- ver, err := SnapdVersionFromInfoFile(infoPath)
+ infoDir := filepath.Join(coreOrSnapdPath, filepath.Join(dirs.CoreLibExecDir))
+ ver, _, err := SnapdVersionFromInfoFile(infoDir)
if err != nil {
logger.Noticef("%v", err)
return false
diff --git a/snapdtool/tool_other.go b/snapdtool/tool_other.go
index 67c6b0a1b8..454093e380 100644
--- a/snapdtool/tool_other.go
+++ b/snapdtool/tool_other.go
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+//go:build !linux
// +build !linux
/*
diff --git a/spread.yaml b/spread.yaml
index daf110af91..944cd29b3d 100644
--- a/spread.yaml
+++ b/spread.yaml
@@ -128,8 +128,11 @@ backends:
- fedora-33-64:
workers: 6
+ manual: true
- fedora-34-64:
workers: 6
+ - fedora-35-64:
+ workers: 6
- arch-linux-64:
workers: 6
@@ -144,6 +147,7 @@ backends:
# unstable systems below
- opensuse-15.2-64:
workers: 6
+ manual: true
- opensuse-15.3-64:
workers: 6
- opensuse-tumbleweed-64:
@@ -785,7 +789,7 @@ suites:
_/hosts: _
_/hosts_n_dirs: _
# twisted fails in travis (but not regular spread).
- # _/twisted: _
+ #_/twisted: _
_/func: _
_/funkyfunc: _
_/funcarg: _
diff --git a/strutil/ctrl16.go b/strutil/ctrl16.go
index 4d8a8c1854..425c8913d3 100644
--- a/strutil/ctrl16.go
+++ b/strutil/ctrl16.go
@@ -1,3 +1,4 @@
+//go:build !go1.7
// +build !go1.7
package strutil
diff --git a/strutil/ctrl17.go b/strutil/ctrl17.go
index be3c09abcc..e52dec6a4f 100644
--- a/strutil/ctrl17.go
+++ b/strutil/ctrl17.go
@@ -1,3 +1,4 @@
+//go:build go1.7
// +build go1.7
package strutil
diff --git a/systemd/emulation.go b/systemd/emulation.go
index 486490f608..9f623a070c 100644
--- a/systemd/emulation.go
+++ b/systemd/emulation.go
@@ -20,7 +20,6 @@
package systemd
import (
- "errors"
"fmt"
"io"
"os"
@@ -39,18 +38,24 @@ type emulation struct {
rootDir string
}
-var errNotImplemented = errors.New("not implemented in emulation mode")
+type notImplementedError struct {
+ op string
+}
+
+func (e *notImplementedError) Error() string {
+ return fmt.Sprintf("%q is not implemented in emulation mode", e.op)
+}
func (s *emulation) Backend() Backend {
return EmulationModeBackend
}
func (s *emulation) DaemonReload() error {
- return errNotImplemented
+ return &notImplementedError{"DaemonReload"}
}
func (s *emulation) DaemonReexec() error {
- return errNotImplemented
+ return &notImplementedError{"DaemonReexec"}
}
func (s *emulation) Enable(service string) error {
@@ -64,59 +69,59 @@ func (s *emulation) Disable(service string) error {
}
func (s *emulation) Start(service ...string) error {
- return errNotImplemented
+ return &notImplementedError{"Start"}
}
func (s *emulation) StartNoBlock(service ...string) error {
- return errNotImplemented
+ return &notImplementedError{"StartNoBlock"}
}
func (s *emulation) Stop(service string, timeout time.Duration) error {
- return errNotImplemented
+ return &notImplementedError{"Stop"}
}
func (s *emulation) Kill(service, signal, who string) error {
- return errNotImplemented
+ return &notImplementedError{"Kill"}
}
func (s *emulation) Restart(service string, timeout time.Duration) error {
- return errNotImplemented
+ return &notImplementedError{"Restart"}
}
func (s *emulation) ReloadOrRestart(service string) error {
- return errNotImplemented
+ return &notImplementedError{"ReloadOrRestart"}
}
func (s *emulation) RestartAll(service string) error {
- return errNotImplemented
+ return &notImplementedError{"RestartAll"}
}
func (s *emulation) Status(units ...string) ([]*UnitStatus, error) {
- return nil, errNotImplemented
+ return nil, &notImplementedError{"Status"}
}
func (s *emulation) InactiveEnterTimestamp(unit string) (time.Time, error) {
- return time.Time{}, errNotImplemented
+ return time.Time{}, &notImplementedError{"InactiveEnterTimestamp"}
}
func (s *emulation) CurrentMemoryUsage(unit string) (quantity.Size, error) {
- return 0, errNotImplemented
+ return 0, &notImplementedError{"CurrentMemoryUsage"}
}
func (s *emulation) CurrentTasksCount(unit string) (uint64, error) {
- return 0, errNotImplemented
+ return 0, &notImplementedError{"CurrentTasksCount"}
}
func (s *emulation) IsEnabled(service string) (bool, error) {
- return false, errNotImplemented
+ return false, &notImplementedError{"IsEnabled"}
}
func (s *emulation) IsActive(service string) (bool, error) {
- return false, errNotImplemented
+ return false, &notImplementedError{"IsActive"}
}
func (s *emulation) LogReader(services []string, n int, follow bool) (io.ReadCloser, error) {
- return nil, errNotImplemented
+ return nil, fmt.Errorf("LogReader")
}
func (s *emulation) AddMountUnitFile(snapName, revision, what, where, fstype string) (string, error) {
@@ -163,7 +168,7 @@ func (s *emulation) AddMountUnitFile(snapName, revision, what, where, fstype str
}
func (s *emulation) AddMountUnitFileWithOptions(unitOptions *MountUnitOptions) (string, error) {
- return "", errNotImplemented
+ return "", &notImplementedError{"AddMountUnitFileWithOptions"}
}
func (s *emulation) RemoveMountUnitFile(mountedDir string) error {
@@ -197,7 +202,7 @@ func (s *emulation) RemoveMountUnitFile(mountedDir string) error {
}
func (s *emulation) ListMountUnits(snapName, origin string) ([]string, error) {
- return nil, errNotImplemented
+ return nil, &notImplementedError{"ListMountUnits"}
}
func (s *emulation) Mask(service string) error {
@@ -211,9 +216,9 @@ func (s *emulation) Unmask(service string) error {
}
func (s *emulation) Mount(what, where string, options ...string) error {
- return errNotImplemented
+ return &notImplementedError{"Mount"}
}
func (s *emulation) Umount(whatOrWhere string) error {
- return errNotImplemented
+ return &notImplementedError{"Umount"}
}
diff --git a/tests/completion/data/twisted/this is a file with spaces in it.doc b/tests/completion/data/twisted/this is a file with spaces in it.doc
deleted file mode 100644
index e69de29bb2..0000000000
--- a/tests/completion/data/twisted/this is a file with spaces in it.doc
+++ /dev/null
diff --git a/tests/completion/data/twisted/this isn't.innit b/tests/completion/data/twisted/this isn't.innit
deleted file mode 100644
index e69de29bb2..0000000000
--- a/tests/completion/data/twisted/this isn't.innit
+++ /dev/null
diff --git a/tests/completion/data/twisted/twisted.tar b/tests/completion/data/twisted/twisted.tar
new file mode 100644
index 0000000000..62081e2926
--- /dev/null
+++ b/tests/completion/data/twisted/twisted.tar
Binary files differ
diff --git a/tests/completion/twisted.sh b/tests/completion/twisted.sh
index 5d52812ce1..4f738f16b9 100644
--- a/tests/completion/twisted.sh
+++ b/tests/completion/twisted.sh
@@ -1 +1,2 @@
cd "$SPREAD_PATH/$SPREAD_SUITE/data/twisted"
+tar -xvf $SPREAD_PATH/$SPREAD_SUITE/data/twisted/twisted.tar
diff --git a/tests/core/basic20/task.yaml b/tests/core/basic20/task.yaml
index dabbb3e8cd..976946a78b 100644
--- a/tests/core/basic20/task.yaml
+++ b/tests/core/basic20/task.yaml
@@ -81,3 +81,11 @@ execute: |
# check that we have a boot-flags file
test -f /run/snapd/boot-flags
+
+ # make sure that loop devices created by snap-bootstrap initramfs-mounts for snaps are readonly
+ for mount in /run/mnt/base /run/mnt/kernel; do
+ mountpoint "${mount}"
+ loop="$(findmnt -o source "${mount}" -n)"
+ echo "${loop}" | MATCH "/dev/loop[0-9]+"
+ losetup -O ro -n --raw "${loop}" | MATCH "1"
+ done
diff --git a/tests/core/failover/task.yaml b/tests/core/failover/task.yaml
index 13e531cbcb..85af86b635 100644
--- a/tests/core/failover/task.yaml
+++ b/tests/core/failover/task.yaml
@@ -10,8 +10,8 @@ details: |
reboots, one for trying the upgrade and another for rolling back) the installed
fundamental snap is the good one and the boot environment variables are correctly set.
-# TODO: enable for UC18 ?
-systems: [ubuntu-core-16-*]
+# TODO: enable for UC20 ?
+systems: [ubuntu-core-16-*, ubuntu-core-18-*]
# Start early as it takes a long time.
priority: 100
@@ -40,7 +40,7 @@ restore: |
"$TESTSTOOLS"/boot-state bootenv unset snap_try_kernel
debug: |
- "$TESTSTOOLS"/boot-state bootenv show
+ snap debug boot-vars
snap list
snap changes
@@ -61,15 +61,28 @@ execute: |
truncate -s 0 "$BUILD_DIR/unpack/initrd.img"
}
+ if os.query is-core18 && [[ "$SPREAD_VARIANT" = "rclocalcrash" ]]; then
+ # there is no /etc/rc.local on core18
+ echo "scenario isn't supported on core18"
+ exit 0
+ fi
+
#shellcheck source=tests/lib/names.sh
. "$TESTSLIB"/names.sh
#shellcheck source=tests/lib/snaps.sh
. "$TESTSLIB"/snaps.sh
+ core_name="core"
+ if os.query is-core18; then
+ core_name="core18"
+ elif os.query is-core20; then
+ core_name="core20"
+ fi
+
if [ "$TARGET_SNAP" = kernel ]; then
TARGET_SNAP_NAME=$kernel_name
else
- TARGET_SNAP_NAME=core
+ TARGET_SNAP_NAME="$core_name"
fi
if [ "$SPREAD_REBOOT" = 0 ]; then
@@ -109,9 +122,14 @@ execute: |
fi
# check boot env vars
- readlink /snap/core/current > failBoot
- test "$("$TESTSTOOLS"/boot-state bootenv show snap_"${TARGET_SNAP}")" = "${TARGET_SNAP_NAME}_$(cat prevBoot).snap"
- test "$("$TESTSTOOLS"/boot-state bootenv show snap_try_"${TARGET_SNAP}")" = "${TARGET_SNAP_NAME}_$(cat failBoot).snap"
+ readlink "/snap/$core_name/current" > failBoot
+ if [ "$TARGET_SNAP" = kernel ]; then
+ snap debug boot-vars | MATCH "snap_try_kernel=${TARGET_SNAP_NAME}_$(cat failBoot).snap\$"
+ snap debug boot-vars | MATCH "snap_kernel=${TARGET_SNAP_NAME}_$(cat prevBoot).snap\$"
+ else
+ snap debug boot-vars | MATCH "snap_try_core=${TARGET_SNAP_NAME}_$(cat failBoot).snap\$"
+ snap debug boot-vars | MATCH "snap_core=${TARGET_SNAP_NAME}_$(cat prevBoot).snap\$"
+ fi
REBOOT
fi
@@ -122,15 +140,19 @@ execute: |
retry -n 60 --wait 1 --env TARGET_SNAP_NAME="$TARGET_SNAP_NAME" sh -c 'test $(snap list | awk "/^${TARGET_SNAP_NAME} / {print(\$3)}") = $(cat prevBoot)'
# ensure the last install change failed as expected
- snap change --last=install | MATCH "cannot finish core installation, there was a rollback across reboot"
+ snap change --last=install | MATCH "cannot finish $TARGET_SNAP_NAME installation, there was a rollback across reboot"
snap change --last=install | MATCH "^Error.*Automatically connect"
# and the boot env vars are correctly set
echo "Waiting for snapd to clean snap_mode"
#shellcheck disable=SC2148
#shellcheck disable=SC2016
- retry -n 200 --wait 1 sh -c 'test -z "$("$TESTSTOOLS"/boot-state bootenv show snap_mode)"'
+ retry -n 200 --wait 1 sh -c 'snap debug boot-vars | MATCH "snap_mode=\$"'
- test "$("$TESTSTOOLS"/boot-state bootenv show snap_"${TARGET_SNAP}")" = "${TARGET_SNAP_NAME}_$(cat prevBoot).snap"
- # FIXME: reenable the last check when we reset properly snap_try_{core,kernel} on rollback
- # test "$("$TESTSTOOLS"/boot-state bootenv show snap_try_${TARGET_SNAP})" = ""
+ if [ "$TARGET_SNAP" = kernel ]; then
+ snap debug boot-vars | MATCH 'snap_try_kernel=$'
+ snap debug boot-vars | MATCH "snap_kernel=${TARGET_SNAP_NAME}_$(cat prevBoot).snap\$"
+ else
+ snap debug boot-vars | MATCH 'snap_try_core=$'
+ snap debug boot-vars | MATCH "snap_core=${TARGET_SNAP_NAME}_$(cat prevBoot).snap\$"
+ fi
diff --git a/tests/core/fsck-on-boot/task.yaml b/tests/core/fsck-on-boot/task.yaml
index 5f0b32fed7..7d351ea7b3 100644
--- a/tests/core/fsck-on-boot/task.yaml
+++ b/tests/core/fsck-on-boot/task.yaml
@@ -44,7 +44,7 @@ execute: |
if mountpoint /run/mnt/snapd >/dev/null; then
umount /run/mnt/snapd
fi
- umount /var/lib/snapd/seed
+ retry -n 20 --wait 2 sh -c 'umount /var/lib/snapd/seed'
umount /run/mnt/ubuntu-seed
# Refer to the core 20 gadgets for details:
diff --git a/tests/core/kernel-and-base-single-reboot-failover/task.yaml b/tests/core/kernel-and-base-single-reboot-failover/task.yaml
new file mode 100644
index 0000000000..13441aaf87
--- /dev/null
+++ b/tests/core/kernel-and-base-single-reboot-failover/task.yaml
@@ -0,0 +1,117 @@
+summary: Exercises a simultaneous kernel and base refresh with a single reboot
+
+# TODO make the test work with ubuntu-core-20
+systems: [ubuntu-core-18-*]
+
+environment:
+ BLOB_DIR: $(pwd)/fake-store-blobdir
+
+prepare: |
+ if [ "$TRUST_TEST_KEYS" = "false" ]; then
+ echo "This test needs test keys to be trusted"
+ exit
+ fi
+ snap ack "$TESTSLIB/assertions/testrootorg-store.account-key"
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+
+ setup_fake_store "$BLOB_DIR"
+
+ readlink /snap/pc-kernel/current > pc-kernel.rev
+ readlink "/snap/core18/current" > core.rev
+
+restore: |
+ if [ "$TRUST_TEST_KEYS" = "false" ]; then
+ echo "This test needs test keys to be trusted"
+ exit
+ fi
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+ teardown_fake_store "$BLOB_DIR"
+
+execute: |
+ if [ "$TRUST_TEST_KEYS" = "false" ]; then
+ echo "This test needs test keys to be trusted"
+ exit
+ fi
+
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+
+ if [ "$SPREAD_REBOOT" = 0 ]; then
+ # break the pc-kernel snap
+ unsquashfs -d pc-kernel-snap /var/lib/snapd/snaps/pc-kernel_*.snap
+ truncate -s 0 pc-kernel-snap/initrd.img
+
+ init_fake_refreshes "$BLOB_DIR" pc-kernel --snap-blob "$PWD/pc-kernel-snap"
+ init_fake_refreshes "$BLOB_DIR" core18
+
+ # taken from transition_to_recover_mode()
+ cp /bin/systemctl /tmp/orig-systemctl
+ mount -o bind "$TESTSLIB/mock-shutdown" /bin/systemctl
+ tests.cleanup defer umount /bin/systemctl
+
+ snap refresh --no-wait core18 pc-kernel > refresh-change-id
+ test -n "$(cat refresh-change-id)"
+ change_id="$(cat refresh-change-id)"
+ # wait until we observe reboots
+ # shellcheck disable=SC2016
+ retry -n 100 --wait 5 sh -c 'test "$(wc -l < /tmp/mock-shutdown.calls)" -gt "1"'
+ # stop snapd now to avoid snapd waiting for too long and deciding to
+ # error out assuming a rollback across reboot
+ systemctl stop snapd.service snapd.socket
+
+ # both link snaps should be done now, snapd was stopped, so we cannot
+ # use 'snap change' and we need to inspect the state directly (even if
+ # snapd was up, it would not respond to API requests as it would be busy
+ # retrying auto-connect)
+ snap debug state --change "$change_id" /var/lib/snapd/state.json > tasks.state
+ # both link snaps are done
+ MATCH ' Done\s+.*Make snap "pc-kernel" .* available' < tasks.state
+ MATCH ' Done\s+.*Make snap "core18" .* available' < tasks.state
+ # auto-connect of the base is in doing and waiting for reboot
+ MATCH ' Doing\s+.*Automatically connect eligible plugs and slots of snap "core18"' < tasks.state
+ # auto-connect of the kernel is still queued
+ MATCH ' Do\s+.*Automatically connect eligible plugs and slots of snap "pc-kernel"' < tasks.state
+
+ snap debug boot-vars > boot-vars.dump
+ MATCH 'snap_mode=try' < boot-vars.dump
+ MATCH 'snap_try_core=core18_.*.snap' < boot-vars.dump
+ MATCH 'snap_try_kernel=pc-kernel_.*.snap' < boot-vars.dump
+
+ # restore shutdown so that spread can reboot the host
+ tests.cleanup pop
+
+ REBOOT
+ elif [ "$SPREAD_REBOOT" = 1 ]; then
+ change_id="$(cat refresh-change-id)"
+ # we expect the change to have failed due to the kernel not booting
+ # properly
+ snap watch "$change_id" || true
+ snap changes | MATCH "$change_id\s+Error"
+ snap change "$change_id" > tasks.done
+ # both link snaps were undone
+ MATCH 'Undone\s+.*Make snap "pc-kernel" .* available' < tasks.done
+ MATCH 'Undone\s+.*Make snap "core18" .* available' < tasks.done
+
+ # boot variables should have been cleared
+ snap debug boot-vars > boot-vars.dump
+ MATCH 'snap_mode=$' < boot-vars.dump
+ MATCH 'snap_try_core=$' < boot-vars.dump
+ MATCH 'snap_try_kernel=$' < boot-vars.dump
+
+ # make sure the system is in stable state, no pending reboots
+ # XXX systemctl exits with non-0 when in degraded state
+ (systemctl is-system-running || true) | MATCH '(running|degraded)'
+
+ # we're expecting the old revisions to be back
+ expecting_kernel="$(cat pc-kernel.rev)"
+ expecting_core="$(cat core.rev)"
+
+ # verify that current points to old revisions
+ test "$(readlink /snap/pc-kernel/current)" = "$expecting_kernel"
+ test "$(readlink /snap/core18/current)" = "$expecting_core"
+ else
+ echo "unexpected reboot"
+ exit 1
+ fi
diff --git a/tests/core/remodel-base/task.yaml b/tests/core/remodel-base/task.yaml
index 9ec5ddafcc..0a4861d727 100644
--- a/tests/core/remodel-base/task.yaml
+++ b/tests/core/remodel-base/task.yaml
@@ -28,27 +28,35 @@ prepare: |
wait_for_device_initialized_change
restore: |
- if [ "$TRUST_TEST_KEYS" = "false" ]; then
- echo "This test needs test keys to be trusted"
- exit
- fi
#shellcheck source=tests/lib/core-config.sh
. "$TESTSLIB"/core-config.sh
#shellcheck source=tests/lib/systemd.sh
. "$TESTSLIB"/systemd.sh
- systemctl stop snapd.service snapd.socket
+ if [ "$SPREAD_REBOOT" = 0 ]; then
+ systemctl stop snapd.service snapd.socket
- clean_snapd_lib
- restore_test_account valid-for-testing
- restore_test_model valid-for-testing-pc
- restore_core_model
+ clean_snapd_lib
+ restore_test_account valid-for-testing
+ restore_test_model valid-for-testing-pc
+ restore_core_model
- # kick first boot again
- systemctl start snapd.service snapd.socket
+ # kick first boot again
+ systemctl start snapd.service snapd.socket
+
+ echo "reboot when the system is ready"
+ for _ in $(seq 30); do
+ if "$TESTSTOOLS"/journal-state match-log "Waiting for system reboot"; then
+ break
+ fi
+ sleep 1
+ done
+ REBOOT
+ fi
# wait for first boot to be done
wait_for_first_boot_change
+
# extra paranoia because failure to cleanup earlier took us a long time
# to find
if [ -e /var/snap/$NEW_BASE/current ]; then
diff --git a/tests/core/remodel-kernel/task.yaml b/tests/core/remodel-kernel/task.yaml
index e40ca24de9..88d50a4b82 100644
--- a/tests/core/remodel-kernel/task.yaml
+++ b/tests/core/remodel-kernel/task.yaml
@@ -16,7 +16,9 @@ prepare: |
. "$TESTSLIB"/systemd.sh
# Save the revision of the pc-kernel snap.
- readlink /snap/pc-kernel/current > original-revision.txt
+ readlink /snap/"$OLD_KERNEL"/current > original-revision.txt
+ # Save the original tracking channel
+ snap info "$OLD_KERNEL" | awk '/^tracking:/ {print $2}' > original-channel.txt
systemctl stop snapd.service snapd.socket
@@ -39,16 +41,16 @@ restore: |
#shellcheck source=tests/lib/systemd.sh
. "$TESTSLIB"/systemd.sh
- # Wait for the final refresh to complete.
- snap watch --last=refresh
+ # Wait for the final refresh to complete (if needed).
+ snap watch --last=refresh?
# Remove all the revisions of pc-kernel that should not be there.
- for revno_path in /snap/pc-kernel/*; do
+ for revno_path in /snap/"$OLD_KERNEL"/*; do
revno="$(basename "$revno_path")"
if [ "$revno" == current ] || [ "$revno" == "$(cat original-revision.txt)" ]; then
continue;
fi
- snap remove pc-kernel --revision="$revno"
+ snap remove "$OLD_KERNEL" --revision="$revno"
done
systemctl stop snapd.service snapd.socket
@@ -149,6 +151,6 @@ execute: |
snap remove --purge "$NEW_KERNEL"
echo "Ensure we are back to the original kernel channel and kernel"
- snap refresh --channel="$KERNEL_CHANNEL" "$OLD_KERNEL"
+ snap refresh --channel="$(cat original-channel.txt)" "$OLD_KERNEL"
REBOOT
fi
diff --git a/tests/core/tmp/task.yaml b/tests/core/tmp/task.yaml
new file mode 100644
index 0000000000..72ade5f26b
--- /dev/null
+++ b/tests/core/tmp/task.yaml
@@ -0,0 +1,46 @@
+summary: Check that the tmp.size settings work
+
+environment:
+ MOUNTCFG_FILE: /etc/systemd/system/tmp.mount.d/override.conf
+
+prepare: |
+ if [ -f "$MOUNTCFG_FILE" ]; then
+ echo "tmpfs configuration file already present, testbed not clean"
+ exit 1
+ fi
+
+restore: |
+ rm -f "$MOUNTCFG_FILE"
+
+execute: |
+ echo "Ensure tmp.size is not set initially"
+ test ! -f "$MOUNTCFG_FILE"
+ if snap get system tmp.size; then
+ echo "Error: tmp.size is unexpectedly set"
+ exit 1
+ fi
+ def_size=$(df --output=size /tmp | tail -1)
+
+ echo "Ensure setting tmp.size works"
+ for size in 100 200; do
+ snap set system tmp.size="$size"M
+ snap get system tmp.size | MATCH "$size"M
+ df -h --output=size /tmp | MATCH "$size"M
+ grep '^tmpfs /tmp' /proc/mounts | MATCH nosuid,nodev
+ MATCH "Options=mode=1777,strictatime,nosuid,nodev,size=${size}M" "$MOUNTCFG_FILE"
+ # Check that systemd is happy with the generated override.conf
+ systemctl daemon-reload
+ done
+
+ echo "Unsetting gets things back to defaults"
+ snap unset system tmp.size
+ if snap get system tmp.size; then
+ echo "Error: tmp.size is unexpectedly set"
+ exit 1
+ fi
+ test ! -f "$MOUNTCFG_FILE"
+ cur_size=$(df --output=size /tmp | tail -1)
+ # For some odd reason, resizing to the default can have a difference with the
+ # old one of one page (4k), at least in GCE, so we take that into account.
+ test "$cur_size" -le $((def_size + 4)) && test "$cur_size" -ge $((def_size - 4))
+ systemctl daemon-reload
diff --git a/tests/lib/fakestore/cmd/fakestore/cmd_make_refreshable.go b/tests/lib/fakestore/cmd/fakestore/cmd_make_refreshable.go
index 41a64eee12..97e3deb422 100644
--- a/tests/lib/fakestore/cmd/fakestore/cmd_make_refreshable.go
+++ b/tests/lib/fakestore/cmd/fakestore/cmd_make_refreshable.go
@@ -20,16 +20,25 @@
package main
import (
+ "fmt"
+
"github.com/snapcore/snapd/tests/lib/fakestore/refresh"
)
type cmdMakeRefreshable struct {
- TopDir string `long:"dir" description:"Directory to be used by the store to keep and serve snaps, <dir>/asserts is used for assertions"`
+ TopDir string `long:"dir" description:"Directory to be used by the store to keep and serve snaps, <dir>/asserts is used for assertions"`
+ SnapBlob string `long:"snap-blob" description:"File or directory with new snap revision contents"`
+ Positional struct {
+ SnapName string `description:"snap name" positional-arg-name:"snap-name"`
+ } `positional-args:"yes" required:"1"`
}
func (x *cmdMakeRefreshable) Execute(args []string) error {
+ if len(args) > 0 {
+ return fmt.Errorf("unexpected additional arguments %v", args)
+ }
// setup fake new revisions of snaps for refresh
- return refresh.MakeFakeRefreshForSnaps(args, x.TopDir)
+ return refresh.MakeFakeRefreshForSnaps(x.Positional.SnapName, x.TopDir, x.SnapBlob)
}
var shortMakeRefreshableHelp = "Makes new versions of the given snaps"
diff --git a/tests/lib/fakestore/cmd/fakestore/main.go b/tests/lib/fakestore/cmd/fakestore/main.go
index 4e73ea8379..b0b604d526 100644
--- a/tests/lib/fakestore/cmd/fakestore/main.go
+++ b/tests/lib/fakestore/cmd/fakestore/main.go
@@ -30,7 +30,7 @@ import (
type Options struct{}
-var parser = flags.NewParser(&Options{}, flags.Default)
+var parser = flags.NewParser(&Options{}, flags.HelpFlag|flags.PassDoubleDash)
func main() {
if err := logger.SimpleSetup(); err != nil {
diff --git a/tests/lib/fakestore/refresh/refresh.go b/tests/lib/fakestore/refresh/refresh.go
index e6ee51053d..f4d08e93a9 100644
--- a/tests/lib/fakestore/refresh/refresh.go
+++ b/tests/lib/fakestore/refresh/refresh.go
@@ -54,7 +54,7 @@ func newAssertsDB(signingPrivKey string) (*asserts.Database, error) {
return db, nil
}
-func MakeFakeRefreshForSnaps(snaps []string, blobDir string) error {
+func MakeFakeRefreshForSnaps(snap string, blobDir string, snapBlob string) error {
db, err := newAssertsDB(systestkeys.TestStorePrivKey)
if err != nil {
return err
@@ -94,10 +94,8 @@ func MakeFakeRefreshForSnaps(snaps []string, blobDir string) error {
f := asserts.NewFetcher(db, retrieve, save)
- for _, snap := range snaps {
- if err := makeFakeRefreshForSnap(snap, blobDir, db, f); err != nil {
- return err
- }
+ if err := makeFakeRefreshForSnap(snap, blobDir, snapBlob, db, f); err != nil {
+ return err
}
return nil
}
@@ -113,7 +111,7 @@ func writeAssert(a asserts.Assertion, targetDir string) (string, error) {
return p, err
}
-func makeFakeRefreshForSnap(snap, targetDir string, db *asserts.Database, f asserts.Fetcher) error {
+func makeFakeRefreshForSnap(snap, targetDir, snapBlob string, db *asserts.Database, f asserts.Fetcher) error {
// make a fake update snap in /var/tmp (which is not a tempfs)
fakeUpdateDir, err := ioutil.TempDir("/var/tmp", "snap-build-")
if err != nil {
@@ -130,9 +128,28 @@ func makeFakeRefreshForSnap(snap, targetDir string, db *asserts.Database, f asse
}
defer exec.Command("sudo", "rm", "-rf", fakeUpdateDir)
- origInfo, err := copySnap(snap, fakeUpdateDir)
+ origInfo, err := getOrigInfo(snap)
if err != nil {
- return fmt.Errorf("copying snap: %v", err)
+ return err
+ }
+ if snapBlob != "" {
+ fi, err := os.Stat(snapBlob)
+ if err != nil {
+ return err
+ }
+ if fi.IsDir() {
+ if err := copyDir(snapBlob, fakeUpdateDir); err != nil {
+ return fmt.Errorf("copying snap blob dir: %v", err)
+ }
+ } else {
+ if err := unpackSnap(snapBlob, fakeUpdateDir); err != nil {
+ return fmt.Errorf("unpacking snap blob: %v", err)
+ }
+ }
+ } else {
+ if err := copySnap(snap, fakeUpdateDir); err != nil {
+ return fmt.Errorf("copying snap: %v", err)
+ }
}
err = copySnapAsserts(origInfo, f)
@@ -172,42 +189,65 @@ type info struct {
size uint64
}
-func copySnap(snapName, targetDir string) (*info, error) {
- baseDir := filepath.Join(dirs.SnapMountDir, snapName)
- if _, err := os.Stat(baseDir); err != nil {
+func getOrigInfo(snapName string) (*info, error) {
+ origRevision, err := currentRevision(snapName)
+ if err != nil {
return nil, err
}
- sourceDir := filepath.Join(baseDir, "current")
- files, err := filepath.Glob(filepath.Join(sourceDir, "*"))
+ rev, err := snap.ParseRevision(origRevision)
if err != nil {
return nil, err
}
- revnoDir, err := filepath.EvalSymlinks(sourceDir)
+ place := snap.MinimalPlaceInfo(snapName, rev)
+ origDigest, origSize, err := asserts.SnapFileSHA3_384(place.MountFile())
if err != nil {
return nil, err
}
+
+ return &info{revision: origRevision, size: origSize, digest: origDigest}, nil
+}
+
+func currentRevision(snapName string) (string, error) {
+ baseDir := filepath.Join(dirs.SnapMountDir, snapName)
+ if _, err := os.Stat(baseDir); err != nil {
+ return "", err
+ }
+ sourceDir := filepath.Join(baseDir, "current")
+ revnoDir, err := filepath.EvalSymlinks(sourceDir)
+ if err != nil {
+ return "", err
+ }
origRevision := filepath.Base(revnoDir)
+ return origRevision, nil
+}
+
+func copyDir(sourceDir, targetDir string) error {
+ files, err := filepath.Glob(filepath.Join(sourceDir, "*"))
+ if err != nil {
+ return err
+ }
for _, m := range files {
if err = exec.Command("sudo", "cp", "-a", m, targetDir).Run(); err != nil {
- return nil, err
+ return err
}
}
+ return nil
+}
- rev, err := snap.ParseRevision(origRevision)
- if err != nil {
- return nil, err
- }
-
- place := snap.MinimalPlaceInfo(snapName, rev)
- origDigest, origSize, err := asserts.SnapFileSHA3_384(place.MountFile())
- if err != nil {
- return nil, err
+func copySnap(snapName, targetDir string) error {
+ baseDir := filepath.Join(dirs.SnapMountDir, snapName)
+ if _, err := os.Stat(baseDir); err != nil {
+ return err
}
+ sourceDir := filepath.Join(baseDir, "current")
+ return copyDir(sourceDir, targetDir)
+}
- return &info{revision: origRevision, size: origSize, digest: origDigest}, nil
+func unpackSnap(snapBlob, targetDir string) error {
+ return exec.Command("sudo", "unsquashfs", "-d", targetDir, "-f", snapBlob).Run()
}
func buildSnap(snapDir, targetDir string) (*info, error) {
diff --git a/tests/lib/nested.sh b/tests/lib/nested.sh
index 3a6a81cd7d..db8faeea75 100644
--- a/tests/lib/nested.sh
+++ b/tests/lib/nested.sh
@@ -1437,7 +1437,7 @@ nested_fetch_spread() {
mkdir -p "$NESTED_WORK_DIR"
curl -s https://storage.googleapis.com/snapd-spread-tests/spread/spread-amd64.tar.gz | tar -xz -C "$NESTED_WORK_DIR"
# make sure spread really exists
- test -x "$NESTED_WORK_DIR/spread"
+ test -x "$NESTED_WORK_DIR/spread"
fi
echo "$NESTED_WORK_DIR/spread"
}
@@ -1451,7 +1451,21 @@ nested_build_seed_cdrom() {
local ORIG_DIR=$PWD
- pushd "$SEED_DIR" || return 1
+ pushd "$SEED_DIR" || return 1
genisoimage -output "$ORIG_DIR/$SEED_NAME" -volid "$LABEL" -joliet -rock "$@"
- popd || return 1
+ popd || return 1
+}
+
+nested_wait_for_device_initialized_change() {
+ local retry=60
+ local wait=1
+
+ while ! nested_exec "snap changes" | MATCH "Done.*Initialize device"; do
+ retry=$(( retry - 1 ))
+ if [ $retry -le 0 ]; then
+ echo "Timed out waiting for device to be fully initialized. Aborting!"
+ return 1
+ fi
+ sleep "$wait"
+ done
}
diff --git a/tests/lib/pkgdb.sh b/tests/lib/pkgdb.sh
index 44ed797b45..b7048ab509 100755
--- a/tests/lib/pkgdb.sh
+++ b/tests/lib/pkgdb.sh
@@ -530,7 +530,7 @@ pkg_dependencies_ubuntu_classic(){
echo "
avahi-daemon
cups
- dbus-x11
+ dbus-user-session
fontconfig
gnome-keyring
jq
@@ -553,7 +553,6 @@ pkg_dependencies_ubuntu_classic(){
;;
ubuntu-16.04-64)
echo "
- dbus-user-session
evolution-data-server
fwupd
gccgo-6
diff --git a/tests/lib/prepare.sh b/tests/lib/prepare.sh
index 37ea84bb7b..1f5ae355c0 100755
--- a/tests/lib/prepare.sh
+++ b/tests/lib/prepare.sh
@@ -579,18 +579,18 @@ uc20_build_initramfs_kernel_snap() {
sed -i -e 's/set -e/set -ex/' "$skeletondir/main/usr/lib/the-tool"
# also save the time before snap-bootstrap runs
sed -i -e "s@/usr/lib/snapd/snap-bootstrap@beforeDate=\$(date --utc \'+%s\'); /usr/lib/snapd/snap-bootstrap@" "$skeletondir/main/usr/lib/the-tool"
- {
- echo ""
- echo "if test -d /run/mnt/data/system-data; then touch /run/mnt/data/system-data/the-tool-ran; fi"
- # also copy the time for the clock-epoch to system-data, this is
- # used by a specific test but doesn't hurt anything to do this for
- # all tests
- echo "mode=\$(grep -Eo 'snapd_recovery_mode=([a-z]+)' /proc/cmdline)"
- echo "mode=\${mode##snapd_recovery_mode=}"
- echo "stat -c '%Y' /usr/lib/clock-epoch >> /run/mnt/ubuntu-seed/\${mode}-clock-epoch"
- echo "echo \"\$beforeDate\" > /run/mnt/ubuntu-seed/\${mode}-before-snap-bootstrap-date"
- echo "date --utc '+%s' > /run/mnt/ubuntu-seed/\${mode}-after-snap-bootstrap-date"
- } >> "$skeletondir/main/usr/lib/the-tool"
+ cat >> "$skeletondir/main/usr/lib/the-tool" <<'EOF'
+ if test -d /run/mnt/data/system-data; then touch /run/mnt/data/system-data/the-tool-ran; fi
+ # also copy the time for the clock-epoch to system-data, this is
+ # used by a specific test but doesn't hurt anything to do this for
+ # all tests
+ mode=$(grep -Eo 'snapd_recovery_mode=([a-z]+)' /proc/cmdline)
+ mode=${mode##snapd_recovery_mode=}
+ mkdir -p /run/mnt/ubuntu-seed/test
+ stat -c '%Y' /usr/lib/clock-epoch >> /run/mnt/ubuntu-seed/test/${mode}-clock-epoch
+ echo "$beforeDate" > /run/mnt/ubuntu-seed/test/${mode}-before-snap-bootstrap-date
+ date --utc '+%s' > /run/mnt/ubuntu-seed/test/${mode}-after-snap-bootstrap-date
+EOF
if [ "$injectKernelPanic" = "true" ]; then
# add a kernel panic to the end of the-tool execution
@@ -1022,13 +1022,13 @@ EOF
# mount it so we can use it now
mount "/dev/mapper/${dev}p${LOOP_PARTITION}" /mnt
- mkdir -p /mnt/user-data/
# copy over everything from gopath to user-data, exclude:
# - VCS files
# - built debs
# - golang archive files and built packages dir
# - govendor .cache directory and the binary,
if os.query is-core16 || os.query is-core18; then
+ mkdir -p /mnt/user-data/
# we need to include "core" here because -C option says to ignore
# files the way CVS(?!) does, so it ignores files named "core" which
# are core dumps, but we have a test suite named "core", so including
diff --git a/tests/lib/snaps/store/test-snapd-daemon-user/src/setregid32.c b/tests/lib/snaps/store/test-snapd-daemon-user/src/setregid32.c
index d6bcd517c5..da2cc09bae 120000
--- a/tests/lib/snaps/store/test-snapd-daemon-user/src/setregid32.c
+++ b/tests/lib/snaps/store/test-snapd-daemon-user/src/setregid32.c
@@ -1 +1 @@
-./setregid.c \ No newline at end of file
+setregid.c \ No newline at end of file
diff --git a/tests/lib/snaps/test-snapd-mount-control/bin/cmd b/tests/lib/snaps/test-snapd-mount-control/bin/cmd
new file mode 100755
index 0000000000..e55f49a5a0
--- /dev/null
+++ b/tests/lib/snaps/test-snapd-mount-control/bin/cmd
@@ -0,0 +1,6 @@
+#!/bin/sh
+PS1='$ '
+command="$1"
+shift
+
+exec "$command" "$@"
diff --git a/tests/lib/snaps/test-snapd-mount-control/meta/snap.yaml b/tests/lib/snaps/test-snapd-mount-control/meta/snap.yaml
new file mode 100644
index 0000000000..554ce36cd5
--- /dev/null
+++ b/tests/lib/snaps/test-snapd-mount-control/meta/snap.yaml
@@ -0,0 +1,23 @@
+name: test-snapd-mount-control
+version: 1.0
+apps:
+ cmd:
+ command: bin/cmd
+plugs:
+ mntctl:
+ interface: mount-control
+ mount:
+ - what: /usr/**
+ where: $SNAP_COMMON/**
+ options: [rw, bind]
+ - what: /var/tmp/**
+ where: $SNAP_COMMON/**
+ options: [rw, bind]
+ - what: /dev/sd*
+ where: /media/**
+ type: [ext2, ext3, ext4]
+ options: [rw, sync]
+ - what: none
+ where: $SNAP_COMMON/**
+ type: [tmpfs]
+ options: [rw]
diff --git a/tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml b/tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml
index 073394df15..7a087a8207 100644
--- a/tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml
+++ b/tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml
@@ -212,6 +212,9 @@ apps:
kernel-module-control:
command: bin/run
plugs: [ kernel-module-control ]
+ kernel-module-load:
+ command: bin/run
+ plugs: [ kernel-module-load ]
kernel-module-observe:
command: bin/run
plugs: [ kernel-module-observe ]
@@ -266,6 +269,9 @@ apps:
modem-manager:
command: bin/run
plugs: [ modem-manager ]
+ mount-control:
+ command: bin/run
+ plugs: [ mount-control ]
mount-observe:
command: bin/run
plugs: [ mount-observe ]
@@ -398,6 +404,9 @@ apps:
sd-control:
command: bin/run
plugs: [ sd-control ]
+ shared-memory:
+ command: bin/run
+ plugs: [ shared-memory ]
ssh-keys:
command: bin/run
plugs: [ ssh-keys ]
@@ -496,6 +505,17 @@ plugs:
interface: dbus
bus: system
name: test.system
+ kernel-module-load:
+ interface: kernel-module-load
+ modules:
+ - name: mymodule
+ load: denied
+ mount-control:
+ interface: mount-control
+ mount:
+ - what: /dev/sda1
+ where: /media/myfiles
+ options: [rw]
system-files:
interface: system-files
read: [/file1]
diff --git a/tests/lib/tools/tests.invariant b/tests/lib/tools/tests.invariant
index 56fa1945f3..846e1b5e6e 100755
--- a/tests/lib/tools/tests.invariant
+++ b/tests/lib/tools/tests.invariant
@@ -9,6 +9,7 @@ show_help() {
echo " lxcfs-mounted: /var/lib/lxcfs is a mount point"
echo " stray-dbus-daemon: at most one dbus-daemon is running"
echo " leftover-defer-sh: defer.sh must not be left over by tests"
+ echo " broken-snaps: snaps must not be left around that are in a broken state"
}
if [ $# -eq 0 ]; then
@@ -81,7 +82,7 @@ check_stray_dbus_daemon() {
(
skipped_system=0
skipped_root_session=0
- for pid in $(pgrep dbus-daemon); do
+ for pid in $(pgrep -x dbus-daemon); do
cmdline="$(tr '\0' ' ' < "/proc/$pid/cmdline")"
# Ignore one dbus-daemon responsible for the system bus.
if echo "$cmdline" | grep -q 'dbus-daemon --system' && [ "$skipped_system" -eq 0 ]; then
@@ -122,6 +123,21 @@ check_leftover_defer_sh() {
fi
}
+check_broken_snaps() {
+ n="$1" # invariant name
+ (
+ # fist column is the snap name, revision is 3rd
+ snap list --all | awk '/,?broken,?/ {print $1,$3}' | while read -r name rev; do
+ echo "snap $name ($rev) is broken"
+ done
+ ) > "$TESTSTMP/tests.invariant.$n"
+ if [ -s "$TESTSTMP/tests.invariant.$n" ]; then
+ echo "tests.invariant: broken snaps" >&2
+ cat "$TESTSTMP/tests.invariant.$n" >&2
+ return 1
+ fi
+}
+
check_invariant() {
case "$1" in
root-files-in-home)
@@ -139,6 +155,9 @@ check_invariant() {
leftover-defer-sh)
check_leftover_defer_sh "$1"
;;
+ broken-snaps)
+ check_broken_snaps "$1"
+ ;;
*)
echo "tests.invariant: unknown invariant $1" >&2
exit 1
@@ -147,7 +166,7 @@ check_invariant() {
}
main() {
- ALL_INVARIANTS="root-files-in-home crashed-snap-confine lxcfs-mounted stray-dbus-daemon leftover-defer-sh"
+ ALL_INVARIANTS="root-files-in-home crashed-snap-confine lxcfs-mounted stray-dbus-daemon leftover-defer-sh broken-snaps"
case "$action" in
check)
diff --git a/tests/main/cgroup-devices-v1/task.yaml b/tests/main/cgroup-devices-v1/task.yaml
index 88976c9824..e316ea56dc 100644
--- a/tests/main/cgroup-devices-v1/task.yaml
+++ b/tests/main/cgroup-devices-v1/task.yaml
@@ -1,6 +1,6 @@
summary: measuring basic properties of device cgroup
# Disable the test on all systems that boot with cgroup v2
-systems: [ -fedora-33-*, -fedora-34-*, -debian-11-*, -debian-sid-*, -arch-*, -opensuse-tumbleweed-*, -ubuntu-21.10-*]
+systems: [ -fedora-33-*, -fedora-34-*, -fedora-35-*, -debian-11-*, -debian-sid-*, -arch-*, -opensuse-tumbleweed-*, -ubuntu-21.10-*]
execute: ./task.sh
diff --git a/tests/main/cgroup-freezer/task.yaml b/tests/main/cgroup-freezer/task.yaml
index 904c702dd2..7d1c88fee3 100644
--- a/tests/main/cgroup-freezer/task.yaml
+++ b/tests/main/cgroup-freezer/task.yaml
@@ -5,7 +5,7 @@ details: |
placed into the appropriate hierarchy under the freezer cgroup.
# Disable the test on all systems that boot with cgroup v2
-systems: [ -fedora-33-*, -fedora-34-*, -debian-11-*, -debian-sid-*, -arch-*, -opensuse-tumbleweed-*, -ubuntu-21.10-*]
+systems: [ -fedora-33-*, -fedora-34-*, -fedora-35-*, -debian-11-*, -debian-sid-*, -arch-*, -opensuse-tumbleweed-*, -ubuntu-21.10-*]
prepare: |
"$TESTSTOOLS"/snaps-state install-local test-snapd-sh
diff --git a/tests/main/generic-unregister/task.yaml b/tests/main/generic-unregister/task.yaml
index 4b44bf394c..25913dfc62 100644
--- a/tests/main/generic-unregister/task.yaml
+++ b/tests/main/generic-unregister/task.yaml
@@ -4,6 +4,10 @@ summary: |
# ubuntu-14.04: curl does not have --unix-socket option
systems: [-ubuntu-core-*, -ubuntu-14.04-*]
+environment:
+ UNTIL_REBOOT/rereg: false
+ UNTIL_REBOOT/until_reboot: true
+
prepare: |
systemctl stop snapd.service snapd.socket
cp /var/lib/snapd/state.json state.json.bak
@@ -13,6 +17,7 @@ prepare: |
restore: |
systemctl stop snapd.service snapd.socket
+ rm -f /var/lib/snapd/device/private-keys-v1/*
cp key/* /var/lib/snapd/device/private-keys-v1/
cp state.json.bak /var/lib/snapd/state.json
rm -f /run/snapd/noregister
@@ -34,16 +39,24 @@ execute: |
keyfile=(/var/lib/snapd/device/private-keys-v1/*)
test -f "${keyfile[0]}"
- curl --data '{"action":"forget","no-registration-until-reboot":true}' --unix-socket /run/snapd.socket http://localhost/v2/model/serial
-
- test -f /run/snapd/noregister
+ curl --data '{"action":"forget","no-registration-until-reboot":'${UNTIL_REBOOT}'}' --unix-socket /run/snapd.socket http://localhost/v2/model/serial
snap model --serial 2>&1|MATCH "error: device not registered yet"
-
not test -e "${keyfile[0]}"
- systemctl restart snapd.service
+ if [ "${UNTIL_REBOOT}" = "true" ] ; then
+ test -f /run/snapd/noregister
+ systemctl restart snapd.service
+ snap model --serial 2>&1|MATCH "error: device not registered yet"
+ else
+ not test -e /run/snapd/noregister
+ snap debug ensure-state-soon
+ retry --wait 2 -n 120 sh -c 'snap model --serial 2>&1|NOMATCH "error: device not registered yet"'
+ fi
- snap model --serial 2>&1|MATCH "error: device not registered yet"
snap find pc
- NOMATCH '"session-macaroon":"[^"]' < /var/lib/snapd/state.json
+ if [ "${UNTIL_REBOOT}" = "true" ] ; then
+ NOMATCH '"session-macaroon":"[^"]' < /var/lib/snapd/state.json
+ else
+ MATCH '"session-macaroon":"[^"]' < /var/lib/snapd/state.json
+ fi
diff --git a/tests/main/interfaces-calendar-service/task.yaml b/tests/main/interfaces-calendar-service/task.yaml
index 252a12d988..c2907dc569 100644
--- a/tests/main/interfaces-calendar-service/task.yaml
+++ b/tests/main/interfaces-calendar-service/task.yaml
@@ -25,6 +25,7 @@ systems:
- -debian-sid-*
- -fedora-33-* # test-snapd-eds is incompatible with eds version shipped with the distro
- -fedora-34-* # test-snapd-eds is incompatible with eds version shipped with the distro
+ - -fedora-35-* # test-snapd-eds is incompatible with eds version shipped with the distro
- -opensuse-15.2-* # test-snapd-eds is incompatible with eds version shipped with the distro
- -opensuse-15.3-* # test-snapd-eds is incompatible with eds version shipped with the distro
- -opensuse-tumbleweed-* # test-snapd-eds is incompatible with eds version shipped with the distro
diff --git a/tests/main/interfaces-contacts-service/task.yaml b/tests/main/interfaces-contacts-service/task.yaml
index 3482985236..b65c876164 100644
--- a/tests/main/interfaces-contacts-service/task.yaml
+++ b/tests/main/interfaces-contacts-service/task.yaml
@@ -19,6 +19,7 @@ systems:
- -debian-sid-*
- -fedora-33-* # test-snapd-eds is incompatible with eds version shipped with the distro
- -fedora-34-* # test-snapd-eds is incompatible with eds version shipped with the distro
+ - -fedora-35-* # test-snapd-eds is incompatible with eds version shipped with the distro
- -opensuse-15.2-* # test-snapd-eds is incompatible with eds version shipped with the distro
- -opensuse-15.3-* # test-snapd-eds is incompatible with eds version shipped with the distro
- -opensuse-tumbleweed-* # test-snapd-eds is incompatible with eds version shipped with the distro
diff --git a/tests/main/interfaces-kernel-module-load/task.yaml b/tests/main/interfaces-kernel-module-load/task.yaml
new file mode 100644
index 0000000000..5c83e773c6
--- /dev/null
+++ b/tests/main/interfaces-kernel-module-load/task.yaml
@@ -0,0 +1,58 @@
+summary: Ensure that the kernel-module-load interface works.
+
+details: |
+ The kernel-module-load interface allows to statically control kernel module
+ loading in a way that can be constrained via snap-declaration.
+
+systems:
+ - ubuntu-core-*-arm-* # XXX: fails with a kill-timeout
+
+environment:
+ SNAP_NAME: test-snapd-kernel-module-load
+
+prepare: |
+ "$TESTSTOOLS"/snaps-state install-local $SNAP_NAME
+
+restore: |
+ echo "Ensure snap is removed even if something goes wrong"
+ snap remove "$SNAP_NAME"
+
+execute: |
+ echo "When the interface is connected"
+ snap connect "$SNAP_NAME:kernel-module-load"
+
+ echo "Then the kernel modules are configured"
+ MODPROBE_CONF="/etc/modprobe.d/snap.$SNAP_NAME.conf"
+ MATCH "blacklist mymodule" < "$MODPROBE_CONF"
+ MATCH "blacklist other_module" < "$MODPROBE_CONF"
+ MATCH "options parport_pc io=0x3bc,0x278 irq=none" < "$MODPROBE_CONF"
+ NOMATCH "blacklist parport_pc" < "$MODPROBE_CONF"
+ NOMATCH "pcspkr" < "$MODPROBE_CONF"
+
+ echo "And modules are configured to be auto-loaded"
+ MODULES_LOAD_CONF="/etc/modules-load.d/snap.$SNAP_NAME.conf"
+ MATCH "parport_pc" < "$MODULES_LOAD_CONF"
+ MATCH "pcspkr" < "$MODULES_LOAD_CONF"
+ NOMATCH "mymodule" < "$MODULES_LOAD_CONF"
+
+ echo "Disconnect the interface"
+ snap disconnect "$SNAP_NAME:kernel-module-load"
+
+ echo "and verify that module configuration files are gone"
+ test ! -f "$MODPROBE_CONF"
+ test ! -f "$MODULES_LOAD_CONF"
+
+ # Now we want to verify that removing the snap does not leave any leftovers
+ echo "Reconnect the interface"
+ snap connect "$SNAP_NAME:kernel-module-load"
+
+ echo "Configuration files have been recreated"
+ test -f "$MODPROBE_CONF"
+ test -f "$MODULES_LOAD_CONF"
+
+ echo "Uninstall the snap"
+ snap remove "$SNAP_NAME"
+
+ echo "verify that module configuration files are gone"
+ test ! -f "$MODPROBE_CONF"
+ test ! -f "$MODULES_LOAD_CONF"
diff --git a/tests/main/interfaces-kernel-module-load/test-snapd-kernel-module-load/meta/snap.yaml b/tests/main/interfaces-kernel-module-load/test-snapd-kernel-module-load/meta/snap.yaml
new file mode 100644
index 0000000000..fea755ad61
--- /dev/null
+++ b/tests/main/interfaces-kernel-module-load/test-snapd-kernel-module-load/meta/snap.yaml
@@ -0,0 +1,17 @@
+name: test-snapd-kernel-module-load
+summary: A no-strings-attached, no-fuss shell for writing tests
+version: 1.0
+
+plugs:
+ kernel-module-load:
+ interface: kernel-module-load
+ modules:
+ - name: mymodule
+ load: denied
+ - name: parport_pc
+ load: on-boot
+ options: io=0x3bc,0x278 irq=none
+ - name: other_module
+ load: denied
+ - name: pcspkr
+ load: on-boot
diff --git a/tests/main/interfaces-many-core-provided/task.yaml b/tests/main/interfaces-many-core-provided/task.yaml
index cf5a26d586..42588055e9 100644
--- a/tests/main/interfaces-many-core-provided/task.yaml
+++ b/tests/main/interfaces-many-core-provided/task.yaml
@@ -90,6 +90,12 @@ execute: |
continue
fi
+ if [ "$plug_iface" = "$CONSUMER_SNAP:mount-control" ] && os.query is-trusty ; then
+ # systemd version is too old, skipping
+ snap connect "$plug_iface" "$slot_iface" 2>&1 | MATCH "systemd version 204 is too old \\(expected at least 209\\)"
+ continue
+ fi
+
# The netlink-audit interface adds the `audit_read` capability to the
# AppArmor profile, but that's not supported on some older systems
if [ "$plug_iface" = "$CONSUMER_SNAP:netlink-audit" ] && os.query is-trusty; then
diff --git a/tests/main/interfaces-mount-control/task.yaml b/tests/main/interfaces-mount-control/task.yaml
new file mode 100644
index 0000000000..9f831bc70e
--- /dev/null
+++ b/tests/main/interfaces-mount-control/task.yaml
@@ -0,0 +1,97 @@
+summary: Test for the mount-control interface
+
+environment:
+ MOUNT_SRC: /var/tmp/test-snapd-mount-control
+ SNAP_COMMON: /var/snap/test-snapd-mount-control/common
+ SNAP_NAME: test-snapd-mount-control
+ MOUNT_DEST: $SNAP_COMMON/target
+
+prepare: |
+ mkdir -p "$MOUNT_SRC/dir1"
+ echo "Something" > "$MOUNT_SRC/file1"
+
+restore: |
+ rm connect_error.log
+ rm -rf "$MOUNT_SRC"
+
+execute: |
+ echo "First verify that a snap with a malicious manifest cannot be connected"
+ "$TESTSTOOLS"/snaps-state install-local test-mount-control-invalid
+ snap connect test-mount-control-invalid:mntctl 2> connect_error.log || true
+ if os.query is-trusty; then
+ echo "On Trusty, we should fail anyway due to systemd being too old"
+ MATCH "systemd version 204 is too old" < connect_error.log
+ exit 0
+ fi
+
+ MATCH 'mount-control "where" pattern is not clean' < connect_error.log
+
+ echo "Installing the test snap"
+
+ "$TESTSTOOLS"/snaps-state install-local "${SNAP_NAME}"
+
+ echo "Connecting the mount-control interface"
+ snap connect "${SNAP_NAME}":mntctl
+
+ echo "Verify that the snap can perform a mount"
+ mkdir -p "$MOUNT_DEST"
+ "${SNAP_NAME}".cmd mount -o bind,rw "$MOUNT_SRC" "$MOUNT_DEST"
+
+ echo "Verify that the mount has been performed"
+ "${SNAP_NAME}".cmd grep "$MOUNT_DEST" /proc/self/mountinfo
+
+ echo "and that it's only in the snap's namespace"
+ NOMATCH "$MOUNT_DEST" < /proc/self/mountinfo
+
+ echo "Ensure that the mounted files are visible"
+ "${SNAP_NAME}".cmd test -e "$MOUNT_DEST/file1"
+
+ echo "Unmount via the system command umount(8)"
+ "${SNAP_NAME}".cmd umount "$MOUNT_DEST"
+ if "${SNAP_NAME}".cmd grep "$MOUNT_DEST" /proc/self/mountinfo; then
+ echo "Unmount failed"
+ exit 1
+ fi
+ "${SNAP_NAME}".cmd test "!" -e "$MOUNT_DEST/file1"
+
+ echo "Verify that a mount with a specific FS type can be created"
+ "${SNAP_NAME}".cmd mount -o rw -t tmpfs none "$MOUNT_DEST"
+ "${SNAP_NAME}".cmd grep "$MOUNT_DEST.*tmpfs" /proc/self/mountinfo
+ "${SNAP_NAME}".cmd umount "$MOUNT_DEST"
+
+ if [ "$(snap debug confinement)" = partial ] ; then
+ echo "Early exit on systems where strict confinement does not work"
+ exit 0
+ fi
+
+ if os.query is-opensuse && ! os.query is-opensuse-tumbleweed; then
+ echo "Early exit in OpenSUSE as confinement is disabled"
+ exit 0
+ fi
+
+ echo "Verify that a mount not matching the allowed pattern will fail"
+ if "${SNAP_NAME}".cmd mount -o bind,rw "$MOUNT_SRC" "/tmp/"; then
+ echo "Mount succeeded despite not matching the allowed pattern"
+ exit 1
+ fi
+
+ echo "Verify that a mount not matching the allowed options will fail"
+ if "${SNAP_NAME}".cmd mount -o sync "$MOUNT_SRC" "$MOUNT_DEST"; then
+ echo "Mount succeeded despite not matching the allowed options"
+ exit 1
+ fi
+
+ echo "Verify that a mount not matching the allowed FS type will fail"
+ mkdir -p /media/somedir
+ if "${SNAP_NAME}".cmd mount -t debugfs "/dev/sda" "/media/somedir"; then
+ echo "Mount succeeded despite not matching the allowed FS type"
+ exit 1
+ fi
+ journalctl -t audit | grep 'fstype="debugfs"' | MATCH 'info="failed type match"'
+ rmdir /media/somedir
+
+ echo "Verify that a maliciously crafted path cannot bypass the allowed pattern"
+ if "${SNAP_NAME}".cmd mount -o bind,rw "$MOUNT_SRC" "$SNAP_COMMON/.."; then
+ echo "Malicious pattern was not blocked"
+ exit 1
+ fi
diff --git a/tests/main/interfaces-mount-control/test-mount-control-invalid/bin/cmd b/tests/main/interfaces-mount-control/test-mount-control-invalid/bin/cmd
new file mode 100755
index 0000000000..214eb4c775
--- /dev/null
+++ b/tests/main/interfaces-mount-control/test-mount-control-invalid/bin/cmd
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+exec "$@"
diff --git a/tests/main/interfaces-mount-control/test-mount-control-invalid/meta/snap.yaml b/tests/main/interfaces-mount-control/test-mount-control-invalid/meta/snap.yaml
new file mode 100644
index 0000000000..7f7da300fe
--- /dev/null
+++ b/tests/main/interfaces-mount-control/test-mount-control-invalid/meta/snap.yaml
@@ -0,0 +1,16 @@
+name: test-mount-control-invalid
+version: 1.0
+apps:
+ cmd:
+ command: bin/cmd
+plugs:
+ mntctl:
+ interface: mount-control
+ mount:
+ - what: /usr/**
+ where: $SNAP_COMMON/**
+ options: [rw, bind]
+ - what: /var/tmp/**
+ where: /media/../**
+ options: [rw, bind]
+
diff --git a/tests/main/interfaces-shared-memory/shm-plug/bin/cmd b/tests/main/interfaces-shared-memory/shm-plug/bin/cmd
new file mode 100755
index 0000000000..ee708187ee
--- /dev/null
+++ b/tests/main/interfaces-shared-memory/shm-plug/bin/cmd
@@ -0,0 +1,2 @@
+#! /bin/sh
+exec "$@"
diff --git a/tests/main/interfaces-shared-memory/shm-plug/meta/snap.yaml b/tests/main/interfaces-shared-memory/shm-plug/meta/snap.yaml
new file mode 100644
index 0000000000..f648e7db77
--- /dev/null
+++ b/tests/main/interfaces-shared-memory/shm-plug/meta/snap.yaml
@@ -0,0 +1,10 @@
+name: shm-plug
+version: 1.0
+apps:
+ cmd:
+ command: bin/cmd
+ plugs: [shmem]
+plugs:
+ shmem:
+ interface: shared-memory
+ shared-memory: super-foo
diff --git a/tests/main/interfaces-shared-memory/shm-slot/bin/cmd b/tests/main/interfaces-shared-memory/shm-slot/bin/cmd
new file mode 100755
index 0000000000..ee708187ee
--- /dev/null
+++ b/tests/main/interfaces-shared-memory/shm-slot/bin/cmd
@@ -0,0 +1,2 @@
+#! /bin/sh
+exec "$@"
diff --git a/tests/main/interfaces-shared-memory/shm-slot/meta/snap.yaml b/tests/main/interfaces-shared-memory/shm-slot/meta/snap.yaml
new file mode 100644
index 0000000000..60d740122e
--- /dev/null
+++ b/tests/main/interfaces-shared-memory/shm-slot/meta/snap.yaml
@@ -0,0 +1,12 @@
+name: shm-slot
+version: 1.0
+apps:
+ cmd:
+ command: bin/cmd
+ slots: [shmem]
+slots:
+ shmem:
+ interface: shared-memory
+ shared-memory: super-foo
+ write: [writable-bar]
+ read: [readable-foo]
diff --git a/tests/main/interfaces-shared-memory/task.yaml b/tests/main/interfaces-shared-memory/task.yaml
new file mode 100644
index 0000000000..7a833c8e9e
--- /dev/null
+++ b/tests/main/interfaces-shared-memory/task.yaml
@@ -0,0 +1,74 @@
+summary: Ensure that the shared-memory interface works.
+
+details: |
+ The shared-memory interface allows two snaps to share a POSIX shared memory
+ object declared in the slot of the provider snap.
+
+prepare: |
+ "$TESTSTOOLS"/snaps-state install-local shm-slot
+ "$TESTSTOOLS"/snaps-state install-local shm-plug
+
+execute: |
+ echo "When the interface is connected"
+ snap connect shm-plug:shmem shm-slot:shmem
+
+ # Test writable SHM areas
+
+ echo "Verify that the slot snap can create a writable SHM, and plug can read it"
+ shm-slot.cmd sh -c 'echo "writable area" > /dev/shm/writable-bar'
+ shm-plug.cmd cat /dev/shm/writable-bar | MATCH "writable area"
+
+ echo "Plug can also write to it"
+ shm-plug.cmd sh -c 'echo "client can also write" > /dev/shm/writable-bar'
+ shm-slot.cmd cat /dev/shm/writable-bar | MATCH "client can also write"
+
+ echo "And vice-versa: plug creates, slot reads"
+ shm-slot.cmd rm /dev/shm/writable-bar
+ shm-plug.cmd sh -c 'echo "another test" > /dev/shm/writable-bar'
+ shm-slot.cmd cat /dev/shm/writable-bar | MATCH "another test"
+
+ # Test read-only SHM areas
+
+ echo "Verify that the slot snap can create a readable SHM, and plug can read it"
+ shm-slot.cmd sh -c 'echo "read-only area" > /dev/shm/readable-foo'
+ shm-plug.cmd cat /dev/shm/readable-foo | MATCH "read-only area"
+
+ if [ "$(snap debug confinement)" = strict ] ; then
+ echo "Plug cannot write to it"
+ if shm-plug.cmd sh -c 'echo "I cannot write this" > /dev/shm/readable-foo'; then
+ echo "Plug snap should not be able to write to read-only SHM area"
+ exit 1
+ fi
+ echo "Double-check that the data was not changed"
+ shm-slot.cmd cat /dev/shm/readable-foo | MATCH "read-only area"
+ else
+ echo "Skipping check on disallowed write, because of partial confinement"
+ fi
+
+ # cleanup
+ shm-slot.cmd rm /dev/shm/writable-bar /dev/shm/readable-foo
+
+ echo "Disconnect the interface"
+ snap disconnect shm-plug:shmem
+
+ if [ "$(snap debug confinement)" = partial ] ; then
+ echo "Do not execute checks with disconnected plug on systems where confinement doesn't work"
+ exit 0
+ fi
+
+ echo "Neither snap should be able to access the SHM now"
+ if shm-slot.cmd sh -c 'echo "test1" > /dev/shm/writable-bar'; then
+ exit 1
+ fi
+ if shm-plug.cmd sh -c 'echo "test2" > /dev/shm/writable-bar'; then
+ exit 1
+ fi
+ if shm-plug.cmd cat /dev/shm/writable-bar; then
+ exit 1
+ fi
+ if shm-slot.cmd sh -c 'echo "test3" > /dev/shm/readable-bar'; then
+ exit 1
+ fi
+ if shm-plug.cmd cat /dev/shm/readable-bar; then
+ exit 1
+ fi
diff --git a/tests/main/microk8s-smoke/task.yaml b/tests/main/microk8s-smoke/task.yaml
index c6afbe046b..764e36e3b8 100644
--- a/tests/main/microk8s-smoke/task.yaml
+++ b/tests/main/microk8s-smoke/task.yaml
@@ -6,10 +6,12 @@ systems:
- -centos-8-* # fails to start service daemon-containerd
- -fedora-33-* # fails to start service daemon-containerd
- -fedora-34-* # fails to start service daemon-containerd
+ - -fedora-35-* # fails to start service daemon-containerd
- -debian-10-* # doesn't have libseccomp >= 2.4
- -ubuntu-14.04-* # doesn't have libseccomp >= 2.4
- -ubuntu-18.04-32 # no microk8s snap for i386 pc systems
- -arch-linux-* # XXX: no curl to the pod for unknown reasons
+ - -ubuntu-*-arm* # not available on arm
environment:
CHANNEL/edge: latest/edge/strict
diff --git a/tests/main/security-device-cgroups-helper/task.yaml b/tests/main/security-device-cgroups-helper/task.yaml
index 3d8caaf031..88272c00ae 100644
--- a/tests/main/security-device-cgroups-helper/task.yaml
+++ b/tests/main/security-device-cgroups-helper/task.yaml
@@ -8,6 +8,13 @@ environment:
DEVICES_PATH_MEM_FULL: /devices/virtual/mem/full
# and /dev/kmsg has 1:11
DEVICES_PATH_MEM_KMSG: /devices/virtual/mem/kmsg
+ # enable debugs from s-c
+ SNAPD_DEBUG: "1"
+
+debug: |
+ udevadm info /dev/full || true
+ udevadm info /dev/kmsg || true
+ tests.device-cgroup test-strict-cgroup-helper.sh dump || true
execute: |
#shellcheck source=tests/lib/systems.sh
@@ -88,6 +95,13 @@ execute: |
NOMATCH 'Operation not permitted' < run.log
test -n "$(cat run.log)"
+ # remove action removes the device from the cgroup
+ "$libexecdir"/snapd/snap-device-helper remove snap_test-strict-cgroup-helper_sh "$DEVICES_PATH_MEM_KMSG" 1:11
+ # /dev/kmsg is not present anymore
+ tests.device-cgroup test-strict-cgroup-helper.sh dump | NOMATCH 'c 1:11 rwm'
+ # and it's not possible to read /dev/kmsg again
+ snap run test-strict-cgroup-helper.sh -c 'head -1 /dev/kmsg' 2>&1 | MATCH "Operation not permitted"
+
# now remove the cgroup
if is_cgroupv2; then
rm /sys/fs/bpf/snap/snap_test-strict-cgroup-helper_sh
diff --git a/tests/main/security-udev-input-subsystem/task.yaml b/tests/main/security-udev-input-subsystem/task.yaml
index 1cd3b09c86..f8b1931d33 100644
--- a/tests/main/security-udev-input-subsystem/task.yaml
+++ b/tests/main/security-udev-input-subsystem/task.yaml
@@ -14,6 +14,10 @@ prepare: |
echo "Given the test-snapd-udev-input-subsystem is installed"
"$TESTSTOOLS"/snaps-state install-local test-snapd-udev-input-subsystem
+debug: |
+ # shellcheck disable=SC2046
+ udevadm info $(find /dev/input/ -type c) | grep -e N: -e MAJOR= -e MINOR= -e TAGS= || true
+
execute: |
if [ -z "$(find /dev/input/by-path -name '*-event-kbd')" ]; then
if [ "$SPREAD_SYSTEM" = "ubuntu-16.04-64" ]; then
diff --git a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/bar b/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/bar
deleted file mode 120000
index 5c68478d09..0000000000
--- a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/bar
+++ /dev/null
@@ -1 +0,0 @@
-foo -> baz -> qux \ No newline at end of file
diff --git a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/bar -> baz b/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/bar -> baz
deleted file mode 120000
index 5a883c8397..0000000000
--- a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/bar -> baz
+++ /dev/null
@@ -1 +0,0 @@
-foo -> qux \ No newline at end of file
diff --git a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/bar -> baz -> qux b/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/bar -> baz -> qux
deleted file mode 120000
index 1910281566..0000000000
--- a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/bar -> baz -> qux
+++ /dev/null
@@ -1 +0,0 @@
-foo \ No newline at end of file
diff --git a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/bar -> qux b/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/bar -> qux
deleted file mode 120000
index 365c3e79f6..0000000000
--- a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/bar -> qux
+++ /dev/null
@@ -1 +0,0 @@
-foo -> baz \ No newline at end of file
diff --git a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/baz b/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/baz
deleted file mode 120000
index 6ea8eef97f..0000000000
--- a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/baz
+++ /dev/null
@@ -1 +0,0 @@
-foo -> bar -> qux \ No newline at end of file
diff --git a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/baz -> qux b/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/baz -> qux
deleted file mode 120000
index 14b8163083..0000000000
--- a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/baz -> qux
+++ /dev/null
@@ -1 +0,0 @@
-foo -> bar \ No newline at end of file
diff --git a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo b/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo
deleted file mode 120000
index 35bb28b82d..0000000000
--- a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo
+++ /dev/null
@@ -1 +0,0 @@
-bar -> baz -> qux \ No newline at end of file
diff --git a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo -> bar b/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo -> bar
deleted file mode 120000
index ec193c70de..0000000000
--- a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo -> bar
+++ /dev/null
@@ -1 +0,0 @@
-baz -> qux \ No newline at end of file
diff --git a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo -> bar -> baz b/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo -> bar -> baz
deleted file mode 120000
index 78df5b06bd..0000000000
--- a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo -> bar -> baz
+++ /dev/null
@@ -1 +0,0 @@
-qux \ No newline at end of file
diff --git a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo -> bar -> qux b/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo -> bar -> qux
deleted file mode 120000
index 3f95386662..0000000000
--- a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo -> bar -> qux
+++ /dev/null
@@ -1 +0,0 @@
-baz \ No newline at end of file
diff --git a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo -> baz b/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo -> baz
deleted file mode 120000
index 21f05bdff4..0000000000
--- a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo -> baz
+++ /dev/null
@@ -1 +0,0 @@
-bar -> qux \ No newline at end of file
diff --git a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo -> baz -> qux b/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo -> baz -> qux
deleted file mode 120000
index ba0e162e1c..0000000000
--- a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo -> baz -> qux
+++ /dev/null
@@ -1 +0,0 @@
-bar \ No newline at end of file
diff --git a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo -> qux b/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo -> qux
deleted file mode 120000
index 522e3d9299..0000000000
--- a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/foo -> qux
+++ /dev/null
@@ -1 +0,0 @@
-bar -> baz \ No newline at end of file
diff --git a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/qux b/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/qux
deleted file mode 120000
index af0f493f56..0000000000
--- a/tests/main/validate-container-failures/test-snapd-validate-container-failures/hell/qux
+++ /dev/null
@@ -1 +0,0 @@
-foo -> bar -> baz \ No newline at end of file
diff --git a/tests/main/validate-container-happy/task.yaml b/tests/main/validate-container-happy/task.yaml
new file mode 100644
index 0000000000..b33faa0112
--- /dev/null
+++ b/tests/main/validate-container-happy/task.yaml
@@ -0,0 +1,38 @@
+summary: check the symlinks following the right track
+
+environment:
+ SNAP: test-snapd-validate-container-happy
+
+prepare: |
+
+execute: |
+
+ SNAP_MOUNT_DIR="$(os.paths snap-mount-dir)"
+
+ # We shouldn't use relative symlinks in Github as they cannot be packed correctly.
+ # So here let's test whether we can still pack such symlinks within a snap and use if needed.
+ # First we "try" to unpack the snap structure and untar the symlinks
+ # Then we pack the snap with these symlinks and then install
+ # Finally we check to see if the symlinks actually support the intervined symlinks
+
+ # Untar the symlinks
+ tar -xvf "$SNAP"/hell/hell.tar -C "$SNAP"/hell
+
+ snap try "$SNAP"
+ # Check to see if the symlinks point to the right paths
+ readlink "$SNAP_MOUNT_DIR"/"$SNAP"/current/hell/bar | MATCH "foo -> baz -> qux"
+ readlink "$SNAP_MOUNT_DIR"/"$SNAP"/current/hell/baz | MATCH "foo -> bar -> qux"
+ readlink "$SNAP_MOUNT_DIR"/"$SNAP"/current/hell/foo | MATCH "bar -> baz -> qux"
+ readlink "$SNAP_MOUNT_DIR"/"$SNAP"/current/hell/qux | MATCH "foo -> bar -> baz"
+ snap remove "$SNAP"
+
+ # Create a new snap structure that includes the unpacked symlinks
+ snap pack "$SNAP"
+ snap install --dangerous test-snapd-validate-container-happy_1.0_all.snap
+ tests.cleanup defer snap remove --purge test-snapd-validate-container-happy
+
+ # Check to see if the symlinks retain their existing paths
+ readlink "$SNAP_MOUNT_DIR"/"$SNAP"/current/hell/bar | MATCH "foo -> baz -> qux"
+ readlink "$SNAP_MOUNT_DIR"/"$SNAP"/current/hell/baz | MATCH "foo -> bar -> qux"
+ readlink "$SNAP_MOUNT_DIR"/"$SNAP"/current/hell/foo | MATCH "bar -> baz -> qux"
+ readlink "$SNAP_MOUNT_DIR"/"$SNAP"/current/hell/qux | MATCH "foo -> bar -> baz"
diff --git a/tests/completion/data/twisted/.just a hidden file b/tests/main/validate-container-happy/test-snapd-validate-container-happy/bin/validate-container
index e69de29bb2..e69de29bb2 100644..100755
--- a/tests/completion/data/twisted/.just a hidden file
+++ b/tests/main/validate-container-happy/test-snapd-validate-container-happy/bin/validate-container
diff --git a/tests/main/validate-container-happy/test-snapd-validate-container-happy/hell/hell.tar b/tests/main/validate-container-happy/test-snapd-validate-container-happy/hell/hell.tar
new file mode 100644
index 0000000000..17c8f0c271
--- /dev/null
+++ b/tests/main/validate-container-happy/test-snapd-validate-container-happy/hell/hell.tar
Binary files differ
diff --git a/tests/main/validate-container-happy/test-snapd-validate-container-happy/meta/snap.yaml b/tests/main/validate-container-happy/test-snapd-validate-container-happy/meta/snap.yaml
new file mode 100644
index 0000000000..3f660211c7
--- /dev/null
+++ b/tests/main/validate-container-happy/test-snapd-validate-container-happy/meta/snap.yaml
@@ -0,0 +1,5 @@
+name: test-snapd-validate-container-happy
+version: 1.0
+apps:
+ validate-container:
+ command: bin/validate-container
diff --git a/tests/nested/core/core20-reinstall-partitions/task.yaml b/tests/nested/core/core20-reinstall-partitions/task.yaml
new file mode 100644
index 0000000000..b9a7297e7c
--- /dev/null
+++ b/tests/nested/core/core20-reinstall-partitions/task.yaml
@@ -0,0 +1,43 @@
+summary: Run a smoke test on UC20 with encryption enabled
+
+details: |
+ This test checks that UC20 can be reinstalled
+
+systems: [ubuntu-20.04-64]
+
+environment:
+ # TODO: figure out a way to do this test where we reset the swtpm after the
+ # shutdown to go into install mode, but before we actually reboot into the
+ # install mode
+ NESTED_ENABLE_SECURE_BOOT: false
+ NESTED_ENABLE_TPM: false
+
+execute: |
+ echo "Wait for the system to be seeded first"
+ tests.nested exec "sudo snap wait system seed.loaded"
+
+ INITIAL_SERIAL=$(tests.nested exec snap model --serial | grep -Po 'serial:\s+\K.*')
+
+ echo "Reinstall the system"
+ boot_id=$(tests.nested boot-id)
+ # add || true in case the SSH connection is broken while executing this
+ # since this command causes an immediate reboot
+ tests.nested exec "sudo snap reboot --install" || true
+
+ tests.nested wait-for reboot "${boot_id}"
+
+ # check that we are back in run mode
+ tests.nested exec cat /proc/cmdline | MATCH 'snapd_recovery_mode=run'
+
+ # wait for the system to get setup and finish seeding
+ tests.nested wait-for snap-command
+ tests.nested exec "sudo snap wait system seed.loaded"
+
+ # wait up to two minutes for serial registration
+ retry -n 60 --wait 2 tests.nested exec snap model --serial
+
+ END_SERIAL=$(tests.nested exec snap model --serial | grep -Po 'serial:\s+\K.*')
+ if [ "$INITIAL_SERIAL" = "$END_SERIAL" ]; then
+ echo "test failed, same serial assertion after reinstallation"
+ exit 1
+ fi
diff --git a/tests/nested/manual/core20-initramfs-time-moves-forward/task.yaml b/tests/nested/manual/core20-initramfs-time-moves-forward/task.yaml
index 32d0d51731..bf07472f4f 100644
--- a/tests/nested/manual/core20-initramfs-time-moves-forward/task.yaml
+++ b/tests/nested/manual/core20-initramfs-time-moves-forward/task.yaml
@@ -30,4 +30,4 @@ execute: |
test "$(tests.nested exec date --utc '+%s')" -ge "$MODEL_ASSERTION_SIGN_TIME"
echo "Verify that the timestamp from after snap-bootstrap ran is greater than the time from the model assertion"
- test "$(tests.nested exec "cat /run/mnt/ubuntu-seed/install-after-snap-bootstrap-date")" -ge "$MODEL_ASSERTION_SIGN_TIME"
+ test "$(tests.nested exec "cat /run/mnt/ubuntu-seed/test/install-after-snap-bootstrap-date")" -ge "$MODEL_ASSERTION_SIGN_TIME"
diff --git a/tests/nested/manual/core20-remodel/task.yaml b/tests/nested/manual/core20-remodel/task.yaml
index bbf9e91191..1a16c6dca2 100644
--- a/tests/nested/manual/core20-remodel/task.yaml
+++ b/tests/nested/manual/core20-remodel/task.yaml
@@ -23,6 +23,9 @@ execute: |
# conflict with an existing system label
label_base=$(tests.nested exec "date '+%Y%m%d'")
+ # wait until device is initialized and has a serial
+ nested_wait_for_device_initialized_change
+
echo "Refresh model assertion to revision 2"
nested_copy "$TESTSLIB/assertions/valid-for-testing-pc-revno-2-20.model"
REMOTE_CHG_ID="$(tests.nested exec sudo snap remodel --no-wait valid-for-testing-pc-revno-2-20.model)"
diff --git a/tests/nested/manual/core20-to-core22/task.yaml b/tests/nested/manual/core20-to-core22/task.yaml
index 5a66f65141..8250f9509a 100644
--- a/tests/nested/manual/core20-to-core22/task.yaml
+++ b/tests/nested/manual/core20-to-core22/task.yaml
@@ -33,6 +33,9 @@ execute: |
label_base=$(tests.nested exec "date '+%Y%m%d'")
label="${label_base}-1"
+ # wait until device is initialized and has a serial
+ nested_wait_for_device_initialized_change
+
echo "Remodel to UC22"
nested_copy "$TESTSLIB/assertions/valid-for-testing-pc-22-from-20.model"
REMOTE_CHG_ID="$(tests.nested exec sudo snap remodel --no-wait valid-for-testing-pc-22-from-20.model)"
diff --git a/timeutil/synchronized.go b/timeutil/synchronized.go
index aef2784ca2..27cd2cdaff 100644
--- a/timeutil/synchronized.go
+++ b/timeutil/synchronized.go
@@ -25,7 +25,6 @@ import (
"github.com/godbus/dbus"
"github.com/snapcore/snapd/dbusutil"
- "github.com/snapcore/snapd/logger"
)
func isNoServiceOrUnknownPropertyDbusErr(err error) bool {
@@ -69,7 +68,6 @@ func IsNTPSynchronized() (bool, error) {
if !ok {
return false, fmt.Errorf("timedate1 returned invalid value for NTPSynchronized property: %s", dbusV)
}
- logger.Debugf("NTPSynchronized state returned by timedate1: %s", dbusV)
return v, nil
}