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 (r < 0) {
+ if (r != -ENOENT)
+ 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 */
+ /* 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 (action_flags & UPDATE_ACTION_INSTALL && r > 0) /* update needed */
- r = context_install(context, version, &applied);
- if (r < 0)
- return r;
+ 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 (r > 0 && 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.");
- 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();
- }
-
- log_info("Booted version is newer or identical to newly installed version, not rebooting.");
}
- return 0;
+ 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.");
+ }
+ }
+
+ 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