From: Lennart Poettering Date: Tue, 23 Jun 2026 19:15:53 +0000 (+0200) Subject: sysupdate: automatically clean up orphaned files after auto-update X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fpull%2F42714%2Fhead;p=thirdparty%2Fsystemd.git sysupdate: automatically clean up orphaned files after auto-update This adds an operation equivalent to "systemd-sysupdate cleanup" after an update completed (regardless if that update was entirely successful or not). This ensures that any orphaned files are automatically cleaned up, if they are not referenced by any transfer file's patterns anymore. Follow-up for: d82e256bb9d151b185a8afec1fcacd8fbe80555c --- diff --git a/man/systemd-sysupdate.xml b/man/systemd-sysupdate.xml index 2fdf3c1f59b..7f4b0890315 100644 --- a/man/systemd-sysupdate.xml +++ b/man/systemd-sysupdate.xml @@ -360,6 +360,18 @@ + + + + Takes a boolean argument. When used in combination with the update + command, automatically performs the equivalent of the cleanup command afterwards, + removing any orphaned files that are no longer covered by the patterns of the currently defined + transfer files. This is useful to garbage-collect files that used to be owned by a transfer file that + has since been modified, disabled or removed. Defaults to off. + + + + diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index a1d292f0eb5..81ed4861e0f 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -47,6 +47,7 @@ static bool arg_legend = true; char *arg_root = NULL; static char *arg_image = NULL; static bool arg_reboot = false; +static int arg_cleanup = -1; static char *arg_component = NULL; static bool arg_component_all = false; static int arg_verify = -1; @@ -1595,44 +1596,60 @@ static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flag if (r < 0) return r; + const char *node = loop_device ? loop_device->node : NULL; + bool installed = false; + int ret = 0; + r = context_make_online( &context, - loop_device ? loop_device->node : NULL, + node, arg_component); - if (r < 0) - return r; - - if (action_flags & UPDATE_ACTION_ACQUIRE) - r = context_acquire(context, version); - else - r = context_process_partial_and_pending(context, version); - if (r < 0) - return r; /* error */ + if (r < 0) { + if (r != -ENOENT) + return r; - if (action_flags & UPDATE_ACTION_INSTALL && r > 0) /* update needed */ - r = context_install(context, version, &applied); - if (r < 0) - return r; + /* No transfer files found. In that case, still do the installdb cleanup below */ + RET_GATHER(ret, r); + } else { + if (action_flags & UPDATE_ACTION_ACQUIRE) + r = context_acquire(context, version); + else + r = context_process_partial_and_pending(context, version); + if (r < 0) + return r; - if (r > 0 && arg_reboot) { - assert(applied); - assert(booted_version); + if (FLAGS_SET(action_flags, UPDATE_ACTION_INSTALL) && r > 0) { /* installation of update indicated */ + r = context_install(context, version, &applied); + if (r < 0) + return r; - if (strverscmp_improved(applied->version, booted_version) > 0) { - log_notice("Newly installed version is newer than booted version, rebooting."); - return reboot_now(); + installed = r > 0; } + } - if (strverscmp_improved(applied->version, booted_version) == 0 && - FLAGS_SET(applied->flags, UPDATE_INCOMPLETE)) { - log_notice("Currently booted version was incomplete and has been repaired, rebooting."); - return reboot_now(); + if (arg_cleanup > 0) + RET_GATHER(ret, installdb_cleanup_component(node, arg_component)); + + if (installed) { + /* We installed something, yay */ + + if (arg_reboot) { + assert(applied); + assert(booted_version); + + if (strverscmp_improved(applied->version, booted_version) > 0) { + log_notice("Newly installed version is newer than booted version, rebooting."); + RET_GATHER(ret, reboot_now()); + } else if (strverscmp_improved(applied->version, booted_version) == 0 && + FLAGS_SET(applied->flags, UPDATE_INCOMPLETE)) { + log_notice("Currently booted version was incomplete and has been repaired, rebooting."); + RET_GATHER(ret, reboot_now()); + } else + log_info("Booted version is newer or identical to newly installed version, not rebooting."); } - - log_info("Booted version is newer or identical to newly installed version, not rebooting."); } - return 0; + return ret; } VERB(verb_update, "update", "[VERSION]", VERB_ANY, 2, 0, @@ -1822,6 +1839,9 @@ static int verb_cleanup(int argc, char *argv[], uintptr_t _data, void *userdata) assert(argc <= 1); + if (arg_cleanup == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invocation of 'cleanup' with --cleanup=no is contradictory, refusing."); + _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL; r = process_image(/* ro= */ false, &mounted_dir, &loop_device); @@ -2004,6 +2024,17 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { arg_offline = true; break; + OPTION_LONG("cleanup", "BOOL", "Clean up orphaned files after completing update"): { + bool b; + + r = parse_boolean_argument("--cleanup=", opts.arg, &b); + if (r < 0) + return r; + + arg_cleanup = b; + break; + } + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; diff --git a/test/units/TEST-72-SYSUPDATE.sh b/test/units/TEST-72-SYSUPDATE.sh index 10c0f335f68..4ac8b8a52d3 100755 --- a/test/units/TEST-72-SYSUPDATE.sh +++ b/test/units/TEST-72-SYSUPDATE.sh @@ -858,4 +858,93 @@ test ! -e "$COMPALL/target-b/comp-b-v1.bin" rm -rf "$COMPALL" /run/sysupdate.comp-a.d /run/sysupdate.comp-b.d \ /var/lib/systemd/sysupdate/installdb.comp-a /var/lib/systemd/sysupdate/installdb.comp-b +# Check the "--cleanup=" switch of the "update" verb. With "--cleanup=yes" a +# successful update must, after installing the new version, run the equivalent of +# the "cleanup" verb and remove any resources that are no longer owned by a +# currently defined transfer file. Reuse the "alpha"/"beta" helpers from above. +rm -rf "$CONFIGDIR" "$INSTALLDB" "$CLEANUP" +mkdir -p "$CONFIGDIR" "$CLEANUP/source" "$CLEANUP/target" + +cat >"$CONFIGDIR/01-alpha.transfer" <"$CONFIGDIR/02-beta.transfer" <"$CONFIGDIR/01-alpha.transfer" </dev/null + +# A plain "cleanup" must still remove the orphaned alpha files. +"$SYSUPDATE" cleanup +test ! -f "$CLEANUP/target/alpha-v1.bin" +test ! -f "$CLEANUP/target/alpha-v2.bin" +[[ "$(installdb_count)" -eq 0 ]] + +rm -rf "$CONFIGDIR" "$INSTALLDB" "$CLEANUP" + touch /testok diff --git a/units/systemd-sysupdate.service b/units/systemd-sysupdate.service index fc4d74a583f..92ec7266e8c 100644 --- a/units/systemd-sysupdate.service +++ b/units/systemd-sysupdate.service @@ -17,7 +17,7 @@ ConditionVirtualization=!container [Service] Type=simple NotifyAccess=main -ExecStart=systemd-sysupdate update +ExecStart=systemd-sysupdate update --cleanup=yes CapabilityBoundingSet=CAP_CHOWN CAP_FOWNER CAP_FSETID CAP_MKNOD CAP_SETFCAP CAP_SYS_ADMIN CAP_SETPCAP CAP_DAC_OVERRIDE CAP_LINUX_IMMUTABLE NoNewPrivileges=yes MemoryDenyWriteExecute=yes