]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sysupdate: automatically clean up orphaned files after auto-update 42714/head
authorLennart Poettering <lennart@amutable.com>
Tue, 23 Jun 2026 19:15:53 +0000 (21:15 +0200)
committerLennart Poettering <lennart@amutable.com>
Wed, 24 Jun 2026 08:42:16 +0000 (10:42 +0200)
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

man/systemd-sysupdate.xml
src/sysupdate/sysupdate.c
test/units/TEST-72-SYSUPDATE.sh
units/systemd-sysupdate.service

index 2fdf3c1f59b0fc788cfc110663ea4abfc91c113b..7f4b08903159da24d6174f767d39143e591f85d9 100644 (file)
         <xi:include href="version-info.xml" xpointer="v251"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--cleanup=</option></term>
+
+        <listitem><para>Takes a boolean argument. When used in combination with the <command>update</command>
+        command, automatically performs the equivalent of the <command>cleanup</command> 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.</para>
+
+        <xi:include href="version-info.xml" xpointer="v262"/></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>--offline</option></term>
 
index a1d292f0eb5f372c2ff39700ae47856e8a38f4e6..81ed4861e0fe24284b727a2cc15fe710a59f5224 100644 (file)
@@ -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;
index 10c0f335f683197a3d5ff83eb6db3d4a2a8b3df0..4ac8b8a52d3623e55a84c555d77512d3e74ee7f4 100755 (executable)
@@ -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" <<EOF
+[Source]
+Type=regular-file
+Path=$CLEANUP/source
+MatchPattern=alpha-@v.bin
+
+[Target]
+Type=regular-file
+Path=$CLEANUP/target
+MatchPattern=alpha-@v.bin
+InstancesMax=2
+EOF
+
+cat >"$CONFIGDIR/02-beta.transfer" <<EOF
+[Source]
+Type=directory
+Path=$CLEANUP/source
+MatchPattern=beta-@v
+
+[Target]
+Type=directory
+Path=$CLEANUP/target
+MatchPattern=beta-@v
+InstancesMax=2
+EOF
+
+# Install a first version with both transfers in place.
+cleanup_new_version v1
+"$SYSUPDATE" --verify=no update --cleanup=yes
+test -f "$CLEANUP/target/alpha-v1.bin"
+verify_beta_synced v1
+[[ "$(installdb_count)" -eq 2 ]]
+assert_installdb_covers_target
+
+# Now drop the "beta" transfer file and install a second version with
+# "--cleanup=yes". The new alpha resource must be installed, and the now-orphaned
+# beta directory (and its install database entry) must be removed as part of the
+# same invocation, without a separate "cleanup" call.
+rm "$CONFIGDIR/02-beta.transfer"
+cleanup_new_version v2
+"$SYSUPDATE" --verify=no update --cleanup=yes
+test -f "$CLEANUP/target/alpha-v1.bin"
+test -f "$CLEANUP/target/alpha-v2.bin"
+test ! -e "$CLEANUP/target/beta-v1"
+[[ "$(installdb_count)" -eq 1 ]]
+assert_installdb_covers_target
+
+# With "--cleanup=no" (the default) orphaned resources must be left in place.
+# Redefine the "alpha" transfer so its patterns no longer match the already
+# installed alpha files (turning them into orphans), while keeping a valid
+# transfer definition in place. Updating with "--cleanup=no" must then install
+# nothing new (there's no matching source) and leave the now-orphaned alpha files
+# and their install database entry untouched.
+cat >"$CONFIGDIR/01-alpha.transfer" <<EOF
+[Source]
+Type=regular-file
+Path=$CLEANUP/source
+MatchPattern=gamma-@v.bin
+
+[Target]
+Type=regular-file
+Path=$CLEANUP/target
+MatchPattern=gamma-@v.bin
+InstancesMax=2
+EOF
+"$SYSUPDATE" --verify=no update --cleanup=no
+test -f "$CLEANUP/target/alpha-v1.bin"
+test -f "$CLEANUP/target/alpha-v2.bin"
+[[ "$(installdb_count)" -eq 1 ]]
+
+# Invoking the "cleanup" verb with "--cleanup=no" is contradictory and must be
+# refused.
+(! "$SYSUPDATE" --cleanup=no cleanup) |& grep "contradictory" >/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
index fc4d74a583f08a148d507fae8aa6eaf787e281b4..92ec7266e8c1db7245d0a8b9b2670457b9efdbe4 100644 (file)
@@ -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