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