File 0002-cmd-snap-mgmt-support-for-migrating-snap-mount-direc.patch of Package snapd
From 54bfff17095921bfb684d6d61bbb2b4d3c8d5402 Mon Sep 17 00:00:00 2001
Message-ID: <54bfff17095921bfb684d6d61bbb2b4d3c8d5402.1768215470.git.maciej.borzecki@canonical.com>
From: Maciej Borzecki <maciej.borzecki@canonical.com>
Date: Mon, 1 Dec 2025 07:59:26 +0100
Subject: [PATCH] cmd/snap-mgmt: support for migrating snap mount directory
(#16063)
* cmd: define path to snapd tooling directory at build time
Signed-off-by: Maciej Borzecki <maciej.borzecki@canonical.com>
* syscheck: account for distributions which support migration of /snap to /var/lib/snapd/snap
Account for distributions which support migration of /snap to
/var/lib/snapd/snap. Currently, this applies only to flavors of
openSUSE.
Signed-off-by: Maciej Borzecki <maciej.borzecki@canonical.com>
* syscheck/dirs: fix typo in license header
Signed-off-by: Maciej Borzecki <maciej.borzecki@canonical.com>
* cmd/snap-mgmt/snap-mgmt: add support for migration of snap mount directory
Add support for migrating snap mount directory.
Related: SNAPDENG-35406
Signed-off-by: Maciej Borzecki <maciej.borzecki@canonical.com>
* tests/main/migrate-snap-mount-dir: spread test
Signed-off-by: Maciej Borzecki <maciej.borzecki@canonical.com>
* tests/main/mount-dir-detect-check: account for openSUSE supporting both mount locations
Signed-off-by: Maciej Borzecki <maciej.borzecki@canonical.com>
* cmd/snap-mgmt/snap-mgmt: update --purge to not require snap mount directory
Signed-off-by: Maciej Borzecki <maciej.borzecki@canonical.com>
* tests/lib/reset: stop passing --snap-mount-directory to purge
Signed-off-by: Maciej Borzecki <maciej.borzecki@canonical.com>
* cmd/snap-mgmt/snap-mgmt: tweak help output
Signed-off-by: Maciej Borzecki <maciej.borzecki@canonical.com>
---------
Signed-off-by: Maciej Borzecki <maciej.borzecki@canonical.com>
---
cmd/Makefile.am | 3 +-
cmd/configure.ac | 4 +
cmd/snap-mgmt/snap-mgmt.sh.in | 430 ++++++++++++++++++--
syscheck/dirs.go | 11 +-
syscheck/dirs_test.go | 44 +-
tests/lib/reset.sh | 1 -
tests/main/migrate-snap-mount-dir/task.yaml | 208 ++++++++++
tests/main/mount-dir-detect-check/task.yaml | 21 +-
8 files changed, 669 insertions(+), 53 deletions(-)
create mode 100644 tests/main/migrate-snap-mount-dir/task.yaml
diff --git a/cmd/Makefile.am b/cmd/Makefile.am
index ad48c1558137221036978189f40028c38fbbd196..4c5e70dc4f9e94cbb07596e755130e6adad526d2 100644
--- a/cmd/Makefile.am
+++ b/cmd/Makefile.am
@@ -434,7 +434,8 @@ snap-mgmt/$(am__dirstamp):
EXTRA_DIST += snap-mgmt/snap-mgmt.sh.in
snap-mgmt/snap-mgmt: snap-mgmt/snap-mgmt.sh.in Makefile snap-mgmt/$(am__dirstamp)
- sed -e 's,[@]STATIC_SNAP_MOUNT_DIR[@],$(STATIC_SNAP_MOUNT_DIR),' <$< >$@
+ sed \
+ -e 's,[@]STATIC_SNAP_TOOLING_DIR[@],$(STATIC_SNAP_TOOLING_DIR),' <$< >$@
if SELINUX
##
diff --git a/cmd/configure.ac b/cmd/configure.ac
index 233976750f08bf12c7f60108c92b31aafc1e3ac2..f02ef7cd08ea79f90dd2a264df3a7b9174c21b00 100644
--- a/cmd/configure.ac
+++ b/cmd/configure.ac
@@ -160,6 +160,10 @@ AC_ARG_WITH([snap-mount-dir],
AC_SUBST(STATIC_SNAP_MOUNT_DIR)
AC_DEFINE_UNQUOTED([STATIC_SNAP_MOUNT_DIR], "${STATIC_SNAP_MOUNT_DIR}", [Static location of the snap mount points])
+STATIC_SNAP_TOOLING_DIR="${libexecdir%/snapd}/snapd"
+AC_SUBST(STATIC_SNAP_TOOLING_DIR)
+AC_DEFINE_UNQUOTED([STATIC_SNAP_TOOLING_DIR], "${STATIC_SNAP_TOOLING_DIR}", [Static location of the snapd tooling directory])
+
SNAP_MOUNT_DIR_SYSTEMD_UNIT="$(systemd-escape -p "$STATIC_SNAP_MOUNT_DIR")"
AC_SUBST([SNAP_MOUNT_DIR_SYSTEMD_UNIT])
AC_DEFINE_UNQUOTED([SNAP_MOUNT_DIR_SYSTEMD_UNIT], "${SNAP_MOUNT_DIR_SYSTEMD_UNIT}", [Systemd unit name for snap mount points location])
diff --git a/cmd/snap-mgmt/snap-mgmt.sh.in b/cmd/snap-mgmt/snap-mgmt.sh.in
index 4c9f4494bce7f85620bda8e4fdeaae36a8a1daeb..470676ae983afd78c69353faa370d855b2aee4b8 100755
--- a/cmd/snap-mgmt/snap-mgmt.sh.in
+++ b/cmd/snap-mgmt/snap-mgmt.sh.in
@@ -8,7 +8,7 @@
set -e
set +x
-STATIC_SNAP_MOUNT_DIR="@STATIC_SNAP_MOUNT_DIR@"
+STATIC_SNAP_TOOLING_DIR="@STATIC_SNAP_TOOLING_DIR@"
show_help() {
exec cat <<'EOF'
@@ -16,15 +16,18 @@ Usage: snap-mgmt.sh [OPTIONS]
A simple script to cleanup snap installations.
-optional arguments:
+Options:
--help Show this help message and exit
- --snap-mount-dir=<path> Provide a path to be used as $STATIC_SNAP_MOUNT_DIR
- --purge Purge all data from $STATIC_SNAP_MOUNT_DIR
+ --snap-tooling-dir=<path> Override default path to snapd tooling dir
+ --force Force operation (relevant for mount directory migration)
+
+Actions:
+ --purge Purge all snaps and their data
+ --migrate-mount-dir Migrate mount directory from /snap to /var/lib/snapd/snap
+ --check-mount-dir-migration Check system state required to run mount directory migration
EOF
}
-SNAP_UNIT_PREFIX="$(systemd-escape -p ${STATIC_SNAP_MOUNT_DIR})"
-
systemctl_stop() {
unit="$1"
@@ -49,7 +52,7 @@ systemctl_stop() {
}
is_component_mount_unit() {
- systemctl show "$1" -p Where | sed 's#Where=##' | grep -q "${SNAP_MOUNT_DIR}/"'[^/]*/components/mnt/[^/]*/[^/]*'
+ systemctl show "$1" -p Where | sed 's#Where=##' | grep -q '\(/snap\|/var/lib/snapd/snap\)/[^/]*/components/mnt/[^/]*/[^/]*'
}
purge() {
@@ -61,8 +64,8 @@ purge() {
systemctl_stop snap.mount.service
fi
- units=$(systemctl list-unit-files --no-legend --full | grep -vF snap.mount.service || true)
- mounts=$(echo "$units" | grep "^${SNAP_UNIT_PREFIX}[-.].*\\.mount" | cut -f1 -d ' ')
+ units=$(systemctl list-unit-files --no-legend --full 'snap.*' 'snap-*.mount' 'var-lib-snapd-snap-*.mount' | grep -vF snap.mount.service || true)
+ mounts=$(echo "$units" | grep -e "^snap-.*\\.mount" -e "^var-lib-snapd-snap-.*\\.mount" | cut -f1 -d ' ')
# *.snap and *.comp mount points
snap_mounts=""
@@ -94,36 +97,44 @@ purge() {
systemctl_stop "$unit"
if echo "$unit" | grep -q '.*\.mount' && ! is_component_mount_unit "$unit"; then
- # Transform ${STATIC_SNAP_MOUNT_DIR}/core/3440 -> core/3440 removing any
+ # Transform /var/lib/snapd/snap/core/3440 -> core/3440 removing any
# extra / preceding snap name, eg:
# /var/lib/snapd/snap/core/3440 -> core/3440
# /snap/core/3440 -> core/3440
# /snap/core//3440 -> core/3440
# NOTE: we could have used `systemctl show $unit -p Where --value`
# but systemd 204 shipped with Ubuntu 14.04 does not support this
- snap_rev=$(systemctl show "$unit" -p Where | sed -e 's#Where=##' -e "s#$STATIC_SNAP_MOUNT_DIR##" -e 's#^/*##')
- snap=$(echo "$snap_rev" |cut -f1 -d/)
- rev=$(echo "$snap_rev" |cut -f2 -d/)
+ systemctl_where="$(systemctl show "$unit" -p Where)"
+ # Where=/var/lib/snapd/snap/core/3440 -> core/3440
+ snap_rev="${systemctl_where#Where=*/snap/}"
+ # core/3440 -> core
+ snap="${snap_rev%/*}"
+ # core/3440 -> 3440
+ rev="${snap_rev#*/}"
+ # Transform:
+ # Where=/var/lib/snapd/snap/core/3440 -> /var/lib/snapd/snap
+ snap_mountpoint="${systemctl_where#Where=}"
+ global_snaps_mount_dir="$(dirname "$(dirname "$snap_mountpoint")")"
if [ -n "$snap" ]; then
echo "Removing snap $snap"
# aliases
- if [ -d "${STATIC_SNAP_MOUNT_DIR}/bin" ]; then
- find "${STATIC_SNAP_MOUNT_DIR}/bin" -maxdepth 1 -lname "$snap" -delete
- find "${STATIC_SNAP_MOUNT_DIR}/bin" -maxdepth 1 -lname "$snap.*" -delete
+ if [ -d "${global_snaps_mount_dir}/bin" ]; then
+ find "${global_snaps_mount_dir}/bin" -maxdepth 1 -lname "$snap" -delete
+ find "${global_snaps_mount_dir}/bin" -maxdepth 1 -lname "$snap.*" -delete
fi
# generated binaries
- rm -f "${STATIC_SNAP_MOUNT_DIR}/bin/$snap"
- rm -f "${STATIC_SNAP_MOUNT_DIR}/bin/$snap".*
+ rm -f "${global_snaps_mount_dir}/bin/$snap"
+ find "${global_snaps_mount_dir}/bin" -maxdepth 1 -name "$snap.*" -delete
# snap mount dir
- umount -l "${STATIC_SNAP_MOUNT_DIR}/$snap/$rev" 2> /dev/null || true
- rm -rf "${STATIC_SNAP_MOUNT_DIR:?}/$snap/$rev"
- rm -f "${STATIC_SNAP_MOUNT_DIR}/$snap/current"
+ umount -l "$snap_mountpoint" 2> /dev/null || true
+ rm -rf "$snap_mountpoint"
+ rm -f "${global_snaps_mount_dir}/$snap/current"
# snap data dir
rm -rf "/var/snap/$snap/$rev"
rm -rf "/var/snap/$snap/common"
rm -f "/var/snap/$snap/current"
# opportunistic remove (may fail if there are still revisions left)
- for d in "${STATIC_SNAP_MOUNT_DIR}/$snap" "/var/snap/$snap"; do
+ for d in "${global_snaps_mount_dir}/$snap" "/var/snap/$snap"; do
if [ -d "$d" ]; then
rmdir --ignore-fail-on-non-empty "$d"
fi
@@ -161,9 +172,9 @@ purge() {
# Units may have been removed do a reload
systemctl -q daemon-reload || true
- # Undo any bind mounts to ${STATIC_SNAP_MOUNT_DIR} or /var/snap done by parallel
+ # Undo any bind mounts to /var/lib/snapd/snap, /snap or /var/snap done by parallel
# installs or LP:#1668659
- for mp in "$STATIC_SNAP_MOUNT_DIR" /var/snap; do
+ for mp in /snap /var/lib/snapd/snap /var/snap; do
# 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 ||
@@ -207,7 +218,15 @@ purge() {
rm -rf /var/lib/snapd/features
echo "Final directory cleanup"
- rm -rf "${STATIC_SNAP_MOUNT_DIR}"
+ for snap_mount_dir in /snap /var/lib/snapd/snap; do
+ if [ -L "$snap_mount_dir" ]; then
+ continue
+ fi
+
+ if [ -d "$snap_mount_dir" ]; then
+ rm -rf "$snap_mount_dir"
+ fi
+ done
rm -rf /var/snap
echo "Removing leftover snap shared state data"
@@ -235,28 +254,377 @@ purge() {
# Remove auto-generated rules for snap-confine from the 'core' snap
echo "Removing extra snap-confine apparmor rules"
# shellcheck disable=SC2046
- rm -f /etc/apparmor.d/$(echo "$SNAP_UNIT_PREFIX" | tr '-' '.').core.*.usr.lib.snapd.snap-confine
+ for snap_unit_prefix in snap var-lib-snapd-snap; do
+ rm -f /etc/apparmor.d/$(echo "$snap_unit_prefix" | tr '-' '.').core.*.usr.lib.snapd.snap-confine
+ done
fi
}
+ensure_snap_apps_stopped() {
+ apps_or_services="$(find /sys/fs/cgroup/ -name 'snap.*.*.scope' -o -name 'snap.*.*.service')"
+ if [ -n "$apps_or_services" ]; then
+ (
+ echo "Found active snap services or applications in the following cgroups:"
+ for n in $apps_or_services; do
+ # transform snap.foo.bar.service into foo
+ sn="$(basename "$n")"
+ sn="${sn#snap.}"
+ sn="${sn%%.*}"
+ echo "- $n"
+ echo " likely owned by snap: '$sn'"
+ echo " PIDs: $(paste -s -d' ' "$n/cgroup.procs")"
+ done
+ ) >&2
+ return 1
+ fi
+ return 0
+}
+
+discard_mount_namespaces() {
+ if [ ! -d /run/snapd/ns ]; then
+ return
+ fi
+
+ tooldir="$1"
+ if [ -z "$tooldir" ]; then
+ tooldir="$STATIC_SNAP_TOOLING_DIR"
+ fi
+
+ echo "Discarding snap mount namespaces"
+ find /run/snapd/ns/ -name '*.mnt' | while read -r mntns; do
+ snname="$(basename "${mntns%.mnt}")"
+ echo " ..discarding mount namespace of snap $snname"
+ "$tooldir"/snap-discard-ns "$snname"
+ done
+}
+
+_patch_service_unit_mount_dependencies() {
+ mount_from_nopref="$1"
+ mount_to_nopref="$2"
+ svc_unit="$3"
+ tmpdir="$4"
+
+ # strip out snap. and <svc>.service, leaving the snap name only
+ snap_name=${svc_unit#snap.}
+ snap_name=${snap_name%%.*}
+
+ old_mount_p="$(systemd-escape -p "$mount_from_nopref/$snap_name/")"
+ new_mount_p="$(systemd-escape -p "$mount_to_nopref/$snap_name/")"
+
+ # given e.g. /snap/test-snapd-service/
+ # systemd-escape produces: snap-test\x2dsnapd\x2dservice
+ # but we need to convert to: snap-test\\x2dsnapd\\x2dservice
+ # match entries like:
+ # Requires=snap-foo-
+ # After=snap-foo
+ old_mount_p_escaped="${old_mount_p//\\/\\\\}"
+ new_mount_p_escaped="${new_mount_p//\\/\\\\}"
+ if ! grep -F -q "=${old_mount_p_escaped}" "/etc/systemd/system/$svc_unit" ; then
+ echo "Service unit $svc_unit already patched"
+ return
+ fi
+
+ echo "Updating service unit $svc_unit"
+ cp -v "/etc/systemd/system/$svc_unit" "$tmpdir/backup/etc/systemd/system/$svc_unit"
+
+ sed -i \
+ -e "s#^Requires=${old_mount_p_escaped}-#Requires=${new_mount_p_escaped}-#" \
+ -e "s#^After=${old_mount_p_escaped}-#After=${new_mount_p_escaped}-#" \
+ "/etc/systemd/system/$svc_unit"
+}
+
+_mount_dir_migrate() {
+ mount_from="$1"
+ mount_from_nopref="${mount_from#/}"
+ mount_to="$2"
+ mount_to_nopref="${mount_to#/}"
+
+ # TODO error when $mount_from is a prefix of $mount_to
+
+ set -x
+ mount_from_escaped="$(systemd-escape -p "${mount_from_nopref}")"
+ mount_to_escaped="$(systemd-escape -p "${mount_to_nopref}")"
+
+ echo "Attempting migration of snap mount location:"
+ echo " $mount_from -> $mount_to"
+
+ if ! ensure_snap_apps_stopped; then
+ echo "Please ensure all snap services or applications are stopped before attempting migration." >&2
+ exit 1
+ fi
+
+ service_units=$(systemctl list-unit-files --no-legend --full 'snap.*.service' | cut -f1 -d' '|| true)
+ mount_units=$(systemctl list-unit-files --no-legend --full "${mount_from_escaped}-*.mount" | cut -f1 -d' ' || true)
+
+ echo "-- services"
+ echo "$service_units"
+ echo "-- mounts"
+ echo "$mount_units"
+
+ tmpdir="$(mktemp -d -t snap-mgmt-migrate.XXXXX)"
+ mkdir -p "$tmpdir/backup/etc/systemd/system"
+ echo "Backup saved to $tmpdir"
+
+ # stop service units first, the services could theoretically reach out to
+ # snapd while stopping
+ echo "Stopping snap services..."
+ for unit in $service_units; do
+ echo "Stopping service $unit"
+ systemctl stop "$unit"
+ done
+
+ echo "Stopping snapd..."
+ systemctl stop snapd.socket snapd.service
+
+ echo "Checking for snap applications or services that are still alive"
+ if ! ensure_snap_apps_stopped; then
+ echo "Found running snap services or applications." >&2
+ exit 1
+ fi
+
+ discard_mount_namespaces "$STATIC_SNAP_TOOLING_DIR"
+
+ new_units=()
+ if [ -z "$mount_units" ]; then
+ echo "All mount units already migrated or no snap mounts"
+ else
+ echo "Stopping mount units..."
+ for unit in $mount_units; do
+ echo "Stopping mount unit $unit"
+ systemctl stop "$unit"
+ done
+
+ # make a backup copy of the whole snap mount dir, all units should be
+ # stopped now, so this should be a tree of empty directories and current
+ # symlinks
+ cp -av "/$mount_from_nopref" "$tmpdir/backup/"
+
+ echo "Updating mount units"
+ # note, we're only iterating over mount units which still have the old name
+ for unit in $mount_units; do
+ # e.g. /snap/foo/123
+ # assumes systemd 246+
+ where_mount="$(systemctl show -p Where --value "$unit")"
+ # e.g. /snap/foo
+ snap_where_mount="$(dirname "$where_mount")"
+
+ # some units may have been patched already
+ echo "Updating mount unit $unit"
+ # new unit name
+ nn="${mount_to_escaped}-${unit#"$mount_from_escaped"-}"
+ new_units+=("$nn")
+ echo " ..new unit name $nn"
+ # new mount point location
+ n_where_mount="$mount_to/${where_mount#"$mount_from"}"
+ # new snap mount location
+ n_snap_where_mount="$(dirname "$n_where_mount")"
+ echo " ..new mount path $n_where_mount"
+
+ # make a backup copy
+ mkdir -p "$tmpdir/backup/etc/systemd/system"
+ cp -v "/etc/systemd/system/$unit" "$tmpdir/backup/etc/systemd/system/"
+
+ echo " ..patching $unit"
+ # Where=/snap/foo/123 -> Where=/var/lib/snapd/snap/foo/123
+ sed -e "s#Where=/${mount_from_nopref}/#Where=/${mount_to_nopref}/#" \
+ "/etc/systemd/system/$unit" > "/etc/systemd/system/$unit.swp"
+ echo " ..renaming to $nn"
+ mv -v "/etc/systemd/system/$unit.swp" "/etc/systemd/system/$nn"
+ rm -v "/etc/systemd/system/$unit"
+
+ if [ -L "/etc/systemd/system/multi-user.target.wants/$unit" ]; then
+ # enabled units appear as symlinks
+ rm -v "/etc/systemd/system/multi-user.target.wants/$unit"
+ ln -sv "/etc/systemd/system/$nn" "/etc/systemd/system/multi-user.target.wants/$nn"
+ fi
+
+ # ensure mount point exists
+ mkdir -p "$n_where_mount"
+
+ # recreate current symlink
+ if [ -L "$snap_where_mount/current" ] && [ ! -L "$n_snap_where_mount/current" ]; then
+ cp -av "$snap_where_mount/current" "$n_snap_where_mount/current"
+ fi
+ done
+ fi
+
+ if [ -z "$service_units" ]; then
+ echo "No snap service units"
+ else
+ for svc_unit in $service_units; do
+ if ! [[ "$svc_unit" = *.service ]]; then
+ continue
+ fi
+ _patch_service_unit_mount_dependencies "$mount_from_nopref" "$mount_to_nopref" "$svc_unit" "$tmpdir"
+ done
+ fi
+
+ if [ -d "$mount_from/bin" ] && [ ! -L "$mount_from" ]; then
+ cp -av "$mount_from/bin" "$mount_to/bin"
+ fi
+ if [ -f "$mount_from/README" ] && [ ! -L "$mount_from" ]; then
+ cp -av "$mount_from/README" "$mount_to/README"
+ fi
+
+ # TODO: restore SELinux context of created files and directories?
+
+ echo "Reloading systemd"
+ systemctl daemon-reload
+
+ echo "Starting new mount units"
+ for new_unit in "${new_units[@]}"; do
+ systemctl start "$new_unit"
+ done
+
+ if [ -d "$mount_from" ]; then
+ echo "Renaming old snap mount directory $mount_from to $mount_from.old"
+ if mountpoint "$mount_from"; then
+ echo "Found $mount_from to be a mount point, likely from parallel instances"
+ umount -l "$mount_from"
+ fi
+ mv -v "$mount_from" "${mount_from}.old"
+ fi
+
+ if [ "$mount_from" = "/snap" ] && [ ! -L "$mount_from" ]; then
+ echo "Restoring ability to run classic snaps"
+ ln -sv "$mount_to" "$mount_from"
+ fi
+
+ systemctl start snapd.socket snapd.service
+}
+
+# check whether migration is needed, returns 0 when needed
+_mount_dir_paths_check() {
+ if [ -e /var/lib/snapd/snap ] && {
+ [ -L /snap ] || [ ! -e /snap ]
+ }; then
+ echo "Snap mount directory migration already completed or not needed" >&2
+ return 1
+ fi
+ return 0
+}
+
+mount_dir_migrate() {
+ force="$1"
+
+ if ! _mount_dir_paths_check; then
+ if [ "$force" = "yes" ]; then
+ echo "WARNING: Forcing mount directory migration" >&2
+ else
+ exit 0
+ fi
+ fi
+
+ echo "#############################################"
+ echo "####### snap mount directory migration ######"
+ echo "#############################################"
+ echo
+ echo "Please report any issues in the snapcraft forum at: https://forum.snapcraft.io/"
+ echo
+
+ _mount_dir_migrate "/snap" "/var/lib/snapd/snap"
+
+ echo "######################################################"
+ echo "####### snap mount directory migration complete ######"
+ echo "######################################################"
+ echo
+ echo "You may start using snaps again."
+ echo "System reboot is recommended."
+ echo
+ echo "A copy of old snap mount directory was saved as /snap.old"
+ echo "Remove it manually by executing:"
+ echo " rm -rf /snap.old"
+ echo
+}
+
+mount_dir_migrate_check() {
+ bad=0
+ not_needed=0
+ echo "Checking snap mount directories presence..."
+ if ! _mount_dir_paths_check ; then
+ not_needed=1
+ fi
+
+ echo
+ echo "Checking snap applications and services..."
+ if ! ensure_snap_apps_stopped; then
+ echo "Found running snap services or applications."
+ bad=1
+ fi
+
+ echo
+ if [ "$bad" != 0 ]; then
+ echo "Mount directory migration cannot continue."
+ exit 1
+ elif [ "$not_needed" = 0 ]; then
+ echo "System is ready for migration."
+ fi
+}
+
+
+force=no
+action=""
while [ -n "$1" ]; do
case "$1" in
--help)
show_help
exit
;;
- --snap-mount-dir=*)
- STATIC_SNAP_MOUNT_DIR=${1#*=}
- SNAP_UNIT_PREFIX=$(systemd-escape -p "$STATIC_SNAP_MOUNT_DIR")
+ --snap-tooling-dir=*)
+ STATIC_SNAP_TOOLING_DIR=${1#*=}
shift
;;
--purge)
- purge
+ if [ -n "$action" ]; then
+ echo "Cannot request purge when already doing '$action'" >&2
+ exit 1
+ fi
+ action=purge
+ shift
+ ;;
+ --migrate-mount-dir)
+ if [ -n "$action" ]; then
+ echo "Cannot request mount directory migration when already doing '$action'" >&2
+ exit 1
+ fi
+ action=migrate-mount-dir
+ shift
+ ;;
+ --check-mount-dir-migration)
+ if [ -n "$action" ]; then
+ echo "Cannot request mount directory migration check when already doing '$action'" >&2
+ exit 1
+ fi
+ action=check-mount-dir-migration
+ shift
+ ;;
+ --force)
+ force=yes
shift
;;
*)
- echo "Unknown command: $1"
+ echo "Unknown command: $1, see --help" >&2
exit 1
;;
esac
done
+
+case "$action" in
+ purge)
+ purge
+ ;;
+ migrate-mount-dir)
+ mount_dir_migrate "$force"
+ ;;
+ check-mount-dir-migration)
+ mount_dir_migrate_check
+ ;;
+ *)
+ if [ -z "$action" ]; then
+ echo "No action specified, see --help" >&2
+ else
+ echo "Unknown action '$action'" >&2
+ fi
+ exit 1
+ ;;
+esac
diff --git a/syscheck/dirs.go b/syscheck/dirs.go
index 017913cb7f14d53e682b51f3cdd512fbe9b17f28..70921897e072eb5ebce953a4dcb460d50e7db1e9 100644
--- a/syscheck/dirs.go
+++ b/syscheck/dirs.go
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- f* Copyright (C) 2025 Canonical Ltd
+ * Copyright (C) 2025 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
@@ -15,7 +15,7 @@
* 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 syscheck
@@ -53,6 +53,11 @@ var (
"manjaro",
"manjaro-arm",
}
+
+ // distributions which support migration from /snap to /var/lib/snapd/snap
+ migratedAltDirDistros = []string{
+ "opensuse", // openSUSE Tumbleweed, Slowroll, Leap, but not SLE
+ }
)
func checkSnapMountDir() error {
@@ -62,6 +67,8 @@ func checkSnapMountDir() error {
smd := dirs.StripRootDir(dirs.SnapMountDir)
switch {
+ case release.DistroLike(migratedAltDirDistros...) && smd == dirs.AltSnapMountDir:
+ // some distributions support migration from /snap -> /var/lib/snapd/snap/
case release.DistroLike(defaultDirDistros...) && smd != dirs.DefaultSnapMountDir:
fallthrough
case release.DistroLike(altDirDistros...) && smd != dirs.AltSnapMountDir:
diff --git a/syscheck/dirs_test.go b/syscheck/dirs_test.go
index d6a1c29502ce35935bccf921d5d64601ae95b1b2..597fc6ab9f54e2c711594d41e97c1b9b224b0217 100644
--- a/syscheck/dirs_test.go
+++ b/syscheck/dirs_test.go
@@ -72,20 +72,24 @@ func (s *dirsSuite) TestUndetermined(c *C) {
var (
known = []struct {
- ID string
- IDLike []string
- canonicalDir bool
+ ID string
+ IDLike []string
+ canonicalDir bool
+ supportsMigration bool
}{
- {"fedora", nil, false},
- {"rhel", []string{"fedora"}, false},
- {"centos", []string{"fedora"}, false},
- {"ubuntu", []string{"debian"}, true},
- {"debian", nil, true},
- {"suse", nil, true},
- {"yocto", nil, true},
- {"arch", []string{"archlinux"}, false},
- {"archlinux", nil, false},
- {"altlinux", nil, false},
+ {"fedora", nil, false, false},
+ {"rhel", []string{"fedora"}, false, false},
+ {"centos", []string{"fedora"}, false, false},
+ {"ubuntu", []string{"debian"}, true, false},
+ {"debian", nil, true, false},
+ {"suse", nil, true, false}, // SLE, not to be confused with openSUSE
+ {"opensuse-leap", []string{"opensuse"}, true, true},
+ {"opensuse-tumbleweed", []string{"opensuse"}, true, true},
+ {"opensuse-slowroll", []string{"opensuse"}, true, true},
+ {"yocto", nil, true, false},
+ {"arch", []string{"archlinux"}, false, false},
+ {"archlinux", nil, false, false},
+ {"altlinux", nil, false, false},
}
knownSpecial = []struct {
@@ -101,10 +105,10 @@ func (s *dirsSuite) TestMountDirKnownDistroHappy(c *C) {
for _, tc := range known {
c.Logf("happy case %+v", tc)
- func() {
+ tf := func(canonicalDir bool) {
defer release.MockReleaseInfo(&release.OS{ID: tc.ID, IDLike: tc.IDLike})()
d := c.MkDir()
- if tc.canonicalDir {
+ if canonicalDir {
dirstest.MustMockCanonicalSnapMountDir(d)
} else {
dirstest.MustMockAltSnapMountDir(d)
@@ -112,7 +116,11 @@ func (s *dirsSuite) TestMountDirKnownDistroHappy(c *C) {
dirs.SetRootDir(d)
c.Check(syscheck.CheckSnapMountDir(), IsNil)
- }()
+ }
+ tf(tc.canonicalDir)
+ if tc.supportsMigration {
+ tf(!tc.canonicalDir)
+ }
}
}
@@ -122,6 +130,10 @@ func (s *dirsSuite) TestMountDirKnownDistroMismatch(c *C) {
for _, tc := range known {
c.Logf("mismatch case %+v", tc)
func() {
+ if tc.supportsMigration {
+ c.Logf("distro supports migration skipping test case: %+v", tc)
+ return
+ }
defer release.MockReleaseInfo(&release.OS{ID: tc.ID, IDLike: tc.IDLike})()
d := c.MkDir()
// do the complete opposite so that the mount directory does not
diff --git a/tests/lib/reset.sh b/tests/lib/reset.sh
index eedc253d5d3c913280f9500b697ba4ec9e25e503..1ef87290d2c32a75d6d876bad7daf193728abef5 100755
--- a/tests/lib/reset.sh
+++ b/tests/lib/reset.sh
@@ -28,7 +28,6 @@ reset_classic() {
# We don't know if snap-mgmt was built, so call the *.in file
# directly and pass arguments that will override the placeholders
sh -x "${SPREAD_PATH}/cmd/snap-mgmt/snap-mgmt.sh.in" \
- --snap-mount-dir="$SNAP_MOUNT_DIR" \
--purge
# The script above doesn't remove the snapd directory as this
# is normally done by the rpm packaging system.
diff --git a/tests/main/migrate-snap-mount-dir/task.yaml b/tests/main/migrate-snap-mount-dir/task.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..35aad0a7b1886dbbd1f61cfc343fc57b774a756c
--- /dev/null
+++ b/tests/main/migrate-snap-mount-dir/task.yaml
@@ -0,0 +1,208 @@
+summary: Execute snap mount directory migration
+details: |
+ Execute migration of snap mount directory. Ensure that snaps continue to
+ work after.
+
+systems:
+ - opensuse-*
+
+environment:
+ PARALLEL_INSTANCES/parallel: true
+ PARALLEL_INSTANCES/no_parallel: false
+
+prepare: |
+ snap set system experimental.user-daemons=true
+ # although state reset should clean that up
+ tests.cleanup defer snap unset system experimental.user-daemons
+
+ "$TESTSTOOLS"/snaps-state install-local test-snapd-sh-core24
+ # install twice
+ "$TESTSTOOLS"/snaps-state install-local test-snapd-sh-core24
+
+ "$TESTSTOOLS"/snaps-state install-local test-snapd-service
+ "$TESTSTOOLS"/snaps-state install-local test-snapd-user-service
+ tests.cleanup defer snap remove --purge test-snapd-user-service
+
+ if [ "$PARALLEL_INSTANCES" = "true" ]; then
+ snap set system experimental.parallel-instances=true
+ tests.cleanup defer snap unset system experimental.parallel-instances
+
+ "$TESTSTOOLS"/snaps-state install-local-as test-snapd-service test-snapd-service_foo
+ tests.cleanup defer snap remove --purge test-snapd-service_foo
+ fi
+
+execute: |
+ if [ "$SPREAD_REBOOT" = 0 ]; then
+ test-snapd-sh-core24.sh -c true
+ systemctl is-active snap.test-snapd-service.test-snapd-service.service
+ not snapd.tool exec snap-mgmt --check-mount-dir-migration |& tee check-migrate.log
+ not snapd.tool exec snap-mgmt --migrate-mount-dir |& tee migrate.log
+
+ # we're showing the prompt
+ MATCH 'Please report any issues in the snapcraft forum' < migrate.log
+ # and also failing due to running applications
+ MATCH "Please ensure all snap services or applications are stopped" < migrate.log
+ MATCH "/system.slice/snap.test-snapd-service.test-snapd-service.service" < migrate.log
+ MATCH "likely owned by snap: 'test-snapd-service'" < migrate.log
+
+ MATCH "/system.slice/snap.test-snapd-service.test-snapd-service.service" < check-migrate.log
+ MATCH "likely owned by snap: 'test-snapd-service'" < check-migrate.log
+ if [ "$PARALLEL_INSTANCES" = "true" ]; then
+ MATCH "likely owned by snap: 'test-snapd-service_foo'" < check-migrate.log
+ fi
+ MATCH "Mount directory migration cannot continue." < check-migrate.log
+ NOMATCH "System is ready for migration." < check-migrate.log
+
+ # stop the services
+ snap stop test-snapd-service
+ if [ "$PARALLEL_INSTANCES" = "true" ]; then
+ snap stop test-snapd-service_foo
+ fi
+
+ # but start the application
+ test-snapd-sh-core24.sh -c 'exec sleep infinity' &
+
+ not snapd.tool exec snap-mgmt --check-mount-dir-migration |& tee check-migrate.log
+ not snapd.tool exec snap-mgmt --migrate-mount-dir |& tee migrate.log
+ MATCH "Please ensure all snap services or applications are stopped" < migrate.log
+ MATCH "/app.slice/snap.test-snapd-sh-core24.sh" < migrate.log
+ MATCH "likely owned by snap: 'test-snapd-sh-core24'" < migrate.log
+ NOMATCH "test-snapd-service" < migrate.log
+
+ MATCH "/app.slice/snap.test-snapd-sh-core24.sh" < check-migrate.log
+ MATCH "likely owned by snap: 'test-snapd-sh-core24'" < check-migrate.log
+ MATCH "Mount directory migration cannot continue." < check-migrate.log
+
+ # kill whole process group
+ kill -9 $!
+ wait $! || true
+
+ # still blocked by user services
+ not snapd.tool exec snap-mgmt --check-mount-dir-migration |& tee check-migrate.log
+ not snapd.tool exec snap-mgmt --migrate-mount-dir |& tee migrate.log
+ MATCH "Please ensure all snap services or applications are stopped" < migrate.log
+ MATCH "/app.slice/snap.test-snapd-user-service.test-snapd-user-service.service" < migrate.log
+ MATCH "likely owned by snap: 'test-snapd-user-service'" < migrate.log
+ # no matches for process which was blocking previously snap
+ NOMATCH "test-snapd-sh-core24" < migrate.log
+ MATCH "Mount directory migration cannot continue." < check-migrate.log
+
+ snap stop test-snapd-user-service
+
+ # now we're good
+ snapd.tool exec snap-mgmt --check-mount-dir-migration |& tee check-migrate.log
+ NOMATCH "Mount directory migration cannot continue." < check-migrate.log
+ MATCH "System is ready for migration." < check-migrate.log
+
+ # and can execute actual migration
+ snapd.tool exec snap-mgmt --migrate-mount-dir |& tee migrate.log
+
+ snap list | NOMATCH broken
+
+ test-snapd-sh-core24.sh -c true
+
+ # XXX should this be done automatically?
+ snap start test-snapd-service
+
+ snap services test-snapd-service | MATCH ' active'
+ if [ "$PARALLEL_INSTANCES" = "true" ]; then
+ snap start test-snapd-service_foo
+ snap services test-snapd-service_foo | MATCH ' active'
+ fi
+
+ # both revisions are present
+ test -f /var/lib/snapd/snap/test-snapd-sh-core24/x1/meta/snap.yaml
+ test -f /var/lib/snapd/snap/test-snapd-sh-core24/x2/meta/snap.yaml
+ # current is correct
+ test "$(readlink /var/lib/snapd/snap/test-snapd-sh-core24/current)" = "x2"
+ # mount units are active
+ systemctl is-active "$(systemd-escape -p var/lib/snapd/snap/test-snapd-sh-core24/x1).mount"
+ systemctl is-active "$(systemd-escape -p var/lib/snapd/snap/test-snapd-sh-core24/x2).mount"
+
+ # same for the service snap
+ test -f /var/lib/snapd/snap/test-snapd-service/x1/meta/snap.yaml
+ test "$(readlink /var/lib/snapd/snap/test-snapd-service/current)" = "x1"
+ # mount unit is active
+ systemctl is-active "$(systemd-escape -p var/lib/snapd/snap/test-snapd-service/x1).mount"
+
+ if [ "$PARALLEL_INSTANCES" = "true" ]; then
+ # and parallel instance of test-snapd-service
+ test -f /var/lib/snapd/snap/test-snapd-service_foo/x1/meta/snap.yaml
+ test "$(readlink /var/lib/snapd/snap/test-snapd-service_foo/current)" = "x1"
+ # mount unit is active
+ systemctl is-active "$(systemd-escape -p var/lib/snapd/snap/test-snapd-service_foo/x1).mount"
+ fi
+
+ # /snap is preserved as symlink
+ test -L /snap
+ test "$(readlink /snap)" = "/var/lib/snapd/snap"
+
+ echo "A backup copy of old mount directory exists"
+ test -d /snap.old
+
+ # we can install another revision of a snap
+ "$TESTSTOOLS"/snaps-state install-local test-snapd-sh-core24
+
+ test -f /var/lib/snapd/snap/test-snapd-sh-core24/x3/meta/snap.yaml
+ # and remove the old revision
+ snap remove test-snapd-sh-core24 --revision=x2
+
+ not test -f /var/lib/snapd/snap/test-snapd-sh-core24/x2/meta/snap.yaml
+
+ # path is correctly reported by the snapd tooling
+ snap debug paths | MATCH 'SNAPD_MOUNT=/var/lib/snapd/snap$'
+
+ # stop all services
+ snap stop test-snapd-service
+ echo "Attempting another migration should be a noop"
+ snapd.tool exec snap-mgmt --migrate-mount-dir |& tee migrate-already-done.log
+ MATCH "Snap mount directory migration already completed or not needed" < migrate-already-done.log
+ snapd.tool exec snap-mgmt --check-mount-dir-migration |& tee check-migrate.log
+ MATCH "Snap mount directory migration already completed or not needed" < check-migrate.log
+
+ echo "Reboot to assert post reboot state"
+ REBOOT
+
+ elif [ "$SPREAD_REBOOT" = 1 ]; then
+ echo "After reboot..."
+ echo "No snaps are broken"
+ snap list | NOMATCH broken
+ echo "Mount units are active"
+ systemctl is-active "$(systemd-escape -p var/lib/snapd/snap/test-snapd-sh-core24/x3).mount"
+ systemctl is-active "$(systemd-escape -p var/lib/snapd/snap/test-snapd-service/x1).mount"
+
+ echo "Snaps continue to work"
+ snap services test-snapd-service | MATCH ' active'
+
+ if [ "$PARALLEL_INSTANCES" = "true" ]; then
+ systemctl is-active "$(systemd-escape -p var/lib/snapd/snap/test-snapd-service_foo/x1).mount"
+ snap services test-snapd-service_foo | MATCH ' active'
+ fi
+
+ test-snapd-sh-core24.sh -c true
+
+ snapd.tool exec snap-mgmt --migrate-mount-dir |& tee migrate.log
+ MATCH "Snap mount directory migration already completed or not needed" < migrate.log
+ NOMATCH "Forcing mount directory migration" < migrate.log
+
+ echo "It is possible to remove old mount directory"
+ rm -rfv /snap.old
+
+ snap stop test-snapd-service
+ if [ "$PARALLEL_INSTANCES" = "true" ]; then
+ snap stop test-snapd-service_foo
+ fi
+ snap stop test-snapd-user-service
+
+ echo "Forcing migration does not modify files"
+ mod_time_before="$(stat --format '%Y' /etc/systemd/system/snap.test-snapd-service.test-snapd-service.service)"
+ snapd.tool exec snap-mgmt --migrate-mount-dir --force |& tee migrate.log
+ MATCH "Snap mount directory migration already completed or not needed" < migrate.log
+ MATCH "Forcing mount directory migration" < migrate.log
+
+ mod_time_after="$(stat --format '%Y' /etc/systemd/system/snap.test-snapd-service.test-snapd-service.service)"
+ test "$mod_time_after" = "$mod_time_before"
+
+ # snapd is automatically started at the end of the process
+ snap list | NOMATCH broken
+ fi
diff --git a/tests/main/mount-dir-detect-check/task.yaml b/tests/main/mount-dir-detect-check/task.yaml
index 414ac39112daa262e0ed48df584eb4acdf55dacd..712e32f13f397bf25b26dffa21494115f2ccfe7c 100644
--- a/tests/main/mount-dir-detect-check/task.yaml
+++ b/tests/main/mount-dir-detect-check/task.yaml
@@ -61,13 +61,30 @@ execute: |
SNAP_MOUNT_DIR="$(os.paths snap-mount-dir)"
baddir="$(cat mock-mount-dir)"
- echo "No snaps can be installed if detection found unexpected snap mount directory"
- "$TESTSTOOLS"/snaps-state install-local test-snapd-sh-core24 2>&1 | MATCH "unexpected snap mount directory"
+ if ! os.query is-opensuse; then
+ echo "No snaps can be installed if detection found unexpected snap mount directory"
+ "$TESTSTOOLS"/snaps-state install-local test-snapd-sh-core24 2>&1 | MATCH "unexpected snap mount directory"
+ else
+ # on openSUSE, we support mount dir migration so either location works
+ "$TESTSTOOLS"/snaps-state install-local test-snapd-sh-core24
+
+ tests.systemd stop-unit snapd.service
+ # purge all snaps so that the alt mount directory can be purged
+ snapd.tool exec snap-mgmt --purge
+ fi
echo "After restoring the correct snap mount directory"
rm -rf "$baddir"
mkdir -p "$SNAP_MOUNT_DIR"
systemctl restart snapd.service
+ # previous steps may have purged the state
+ snap wait system seed.loaded
echo "Snaps can be installed again"
"$TESTSTOOLS"/snaps-state install-local test-snapd-sh-core24
+
+ test -d "$SNAP_MOUNT_DIR"
+ test ! -d "$baddir"
+
+ echo "And still work"
+ test-snapd-sh-core24.sh -c true
--
2.52.0