]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sysupdate: notify hook subscribers after a successful update
authorLennart Poettering <lennart@amutable.com>
Thu, 28 May 2026 09:40:42 +0000 (11:40 +0200)
committerLennart Poettering <lennart@amutable.com>
Wed, 24 Jun 2026 11:05:33 +0000 (13:05 +0200)
Define a new io.systemd.SysUpdate.Notify Varlink interface with a single
OnCompletedUpdate() method, and after sysupdate successfully installs an
update, invoke that method on every socket linked into
/run/systemd/sysupdate/notify/ via varlink_execute_directory(). This
gives other components a hook to react to applied updates (e.g. recompute
a TPM policy, link a freshly downloaded kernel, refresh extensions).

The notification carries the component name, the installed version and the
list of updated resources (transfer id + on-disk path). Subscribers are
free to ignore the parameters and just treat the call as a trigger.

Setting SYSTEMD_SYSUPDATE_FORCE_NOTIFY=1 forces the notification to be sent
even when no update was applied (in which case no resource list is included),
so follow-up work can be triggered unconditionally.

Fixes: #35988
docs/ENVIRONMENT.md
src/basic/constants.h
src/libsystemd/sd-varlink/test-varlink-idl.c
src/shared/meson.build
src/shared/varlink-io.systemd.SysUpdate.Notify.c [new file with mode: 0644]
src/shared/varlink-io.systemd.SysUpdate.Notify.h [new file with mode: 0644]
src/sysupdate/sysupdate.c

index b528fce37da4747ad97d97b68866597c8c445527..31f180beea973823f61c88f9ec61e29c8c96388e 100644 (file)
@@ -870,3 +870,10 @@ Tools using the Varlink protocol (such as `varlinkctl`) or sd-bus (such as
   'freshness' check via `BEST-BEFORE-YYYY-MM-DD` files in `SHA256SUMS` manifest
   files is disabled, and updating from outdated manifests will not result in an
   error.
+
+* `$SYSTEMD_SYSUPDATE_FORCE_NOTIFY` – takes a boolean. If true the notification
+  callouts in `/run/systemd/sysupdate/notify/` are invoked even when no update
+  was applied (i.e. the system was already up-to-date). In this case the
+  notification carries no list of updated resources. This is useful to
+  unconditionally trigger follow-up work such as relinking a kernel or
+  recomputing a TPM policy.
index 3dbffdee2633510bddb0483cef941f13a2031d75..475d8bf79a688f3d0f051e216d58e3f3637e14cc 100644 (file)
@@ -68,6 +68,8 @@
 #define VARLINK_PATH_MACHINED_RESOLVE_HOOK "/run/systemd/resolve.hook/io.systemd.Machine"
 /* Path where to connect to send varlink prekill events */
 #define VARLINK_DIR_OOMD_PREKILL_HOOK "/run/systemd/oomd.prekill.hook/"
+/* Directory whose sockets receive io.systemd.SysUpdate.Notify() after a successful sysupdate run */
+#define VARLINK_DIR_SYSUPDATE_NOTIFY_HOOK "/run/systemd/sysupdate/notify/"
 
 /* Recommended baseline - see README for details */
 #define KERNEL_BASELINE_VERSION "5.14"
index e026d4b07ed3193710d9e6e19439877e3cd9829b..0b3304a43c80a4f69e462d9bc4a26b71d22ace7e 100644 (file)
@@ -51,6 +51,7 @@
 #include "varlink-io.systemd.Resolve.Monitor.h"
 #include "varlink-io.systemd.Shutdown.h"
 #include "varlink-io.systemd.StorageProvider.h"
+#include "varlink-io.systemd.SysUpdate.Notify.h"
 #include "varlink-io.systemd.Udev.h"
 #include "varlink-io.systemd.Unit.h"
 #include "varlink-io.systemd.UserDatabase.h"
@@ -227,6 +228,7 @@ TEST(parse_format) {
                 &vl_interface_io_systemd_Resolve_Monitor,
                 &vl_interface_io_systemd_Shutdown,
                 &vl_interface_io_systemd_StorageProvider,
+                &vl_interface_io_systemd_SysUpdate_Notify,
                 &vl_interface_io_systemd_Udev,
                 &vl_interface_io_systemd_Unit,
                 &vl_interface_io_systemd_UserDatabase,
index 8e874cb99d1d673361af4b52cec9feed3c8b7bbc..52df34bb0f5326d821146d6a967fa003febe076b 100644 (file)
@@ -250,6 +250,7 @@ shared_sources = files(
         'varlink-io.systemd.Resolve.Monitor.c',
         'varlink-io.systemd.Shutdown.c',
         'varlink-io.systemd.StorageProvider.c',
+        'varlink-io.systemd.SysUpdate.Notify.c',
         'varlink-io.systemd.Udev.c',
         'varlink-io.systemd.Unit.c',
         'varlink-io.systemd.UserDatabase.c',
diff --git a/src/shared/varlink-io.systemd.SysUpdate.Notify.c b/src/shared/varlink-io.systemd.SysUpdate.Notify.c
new file mode 100644 (file)
index 0000000..2fecc60
--- /dev/null
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "varlink-io.systemd.SysUpdate.Notify.h"
+
+static SD_VARLINK_DEFINE_STRUCT_TYPE(
+                Resource,
+                SD_VARLINK_DEFINE_FIELD(transfer, SD_VARLINK_STRING, 0),
+                SD_VARLINK_DEFINE_FIELD(path, SD_VARLINK_STRING, SD_VARLINK_NULLABLE));
+
+static SD_VARLINK_DEFINE_METHOD(
+                OnCompletedUpdate,
+                SD_VARLINK_DEFINE_INPUT(component, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+                SD_VARLINK_DEFINE_INPUT(version, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+                SD_VARLINK_DEFINE_INPUT_BY_TYPE(resources, Resource, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE));
+
+SD_VARLINK_DEFINE_INTERFACE(
+                io_systemd_SysUpdate_Notify,
+                "io.systemd.SysUpdate.Notify",
+                &vl_type_Resource,
+                &vl_method_OnCompletedUpdate);
diff --git a/src/shared/varlink-io.systemd.SysUpdate.Notify.h b/src/shared/varlink-io.systemd.SysUpdate.Notify.h
new file mode 100644 (file)
index 0000000..72deccb
--- /dev/null
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-varlink-idl.h"
+
+extern const sd_varlink_interface vl_interface_io_systemd_SysUpdate_Notify;
index 81ed4861e0fe24284b727a2cc15fe710a59f5224..9f78ee78bf3cb31efa395b611a6cec8b44b1cefc 100644 (file)
@@ -3,11 +3,14 @@
 #include <unistd.h>
 
 #include "sd-daemon.h"
+#include "sd-json.h"
+#include "sd-varlink.h"
 
 #include "build.h"
 #include "conf-files.h"
 #include "constants.h"
 #include "dissect-image.h"
+#include "env-util.h"
 #include "errno-util.h"
 #include "fd-util.h"
 #include "format-table.h"
@@ -36,6 +39,7 @@
 #include "sysupdate-transfer.h"
 #include "sysupdate-update-set.h"
 #include "sysupdate-util.h"
+#include "varlink-util.h"
 #include "verbs.h"
 
 static char *arg_definitions = NULL;
@@ -1192,6 +1196,82 @@ static int context_process_partial_and_pending(
         return 1;
 }
 
+static int notify_subscribers_reply(
+                sd_varlink *link,
+                sd_json_variant *reply,
+                const char *error_id,
+                sd_varlink_reply_flags_t flags,
+                void *userdata) {
+
+        assert(link);
+
+        if (error_id)
+                log_warning("Notification subscriber '%s' returned error, ignoring: %s",
+                            strna(sd_varlink_get_description(link)), error_id);
+
+        return 0;
+}
+
+static int context_notify_subscribers(Context *c, UpdateSet *us) {
+        int r;
+
+        assert(c);
+
+        /* 'us' is NULL when we are forced to notify even though no update was applied (via
+         * SYSTEMD_SYSUPDATE_FORCE_NOTIFY=1). In that case we send neither a version nor a resource list. */
+
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *resources = NULL;
+        if (us)
+                for (size_t i = 0; i < c->n_transfers; i++) {
+                        Instance *inst = us->instances[i];
+                        Transfer *t = c->transfers[i];
+
+                        if (inst->resource == &t->target &&
+                            !inst->is_pending)
+                                continue;
+
+                        /* Report where the resource was installed *to* (not the source it came from): the
+                         * final on-disk path for filesystem targets, the partition device node for partition
+                         * targets. */
+                        const char *target_path =
+                                RESOURCE_IS_FILESYSTEM(t->target.type) ? t->final_path :
+                                t->target.type == RESOURCE_PARTITION ? t->partition_info.device :
+                                NULL;
+
+                        r = sd_json_variant_append_arraybo(
+                                        &resources,
+                                        SD_JSON_BUILD_PAIR_STRING("transfer", t->id),
+                                        SD_JSON_BUILD_PAIR_CONDITION(!!target_path, "path", SD_JSON_BUILD_STRING(target_path)));
+                        if (r < 0)
+                                return log_warning_errno(r, "Failed to build sysupdate notify resources list, skipping notification: %m");
+                }
+
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *params = NULL;
+        r = sd_json_buildo(
+                        &params,
+                        SD_JSON_BUILD_PAIR_CONDITION(!!arg_component, "component", SD_JSON_BUILD_STRING(arg_component)),
+                        SD_JSON_BUILD_PAIR_CONDITION(!!us, "version", SD_JSON_BUILD_STRING(us ? us->version : NULL)),
+                        SD_JSON_BUILD_PAIR_CONDITION(!!resources, "resources", SD_JSON_BUILD_VARIANT(resources)));
+        if (r < 0)
+                return log_warning_errno(r, "Failed to build sysupdate notify parameters, skipping notification: %m");
+
+        ssize_t n = varlink_execute_directory(
+                        VARLINK_DIR_SYSUPDATE_NOTIFY_HOOK,
+                        "io.systemd.SysUpdate.Notify.OnCompletedUpdate",
+                        params,
+                        /* more= */ false,
+                        /* timeout_usec= */ 5 * USEC_PER_MINUTE,
+                        notify_subscribers_reply,
+                        /* userdata= */ NULL);
+        if (n < 0)
+                log_debug_errno(n, "Failed to dispatch sysupdate notification to %s, ignoring: %m",
+                                VARLINK_DIR_SYSUPDATE_NOTIFY_HOOK);
+        else if (n > 0)
+                log_debug("Dispatched sysupdate notification to %zi subscribers in %s.", n, VARLINK_DIR_SYSUPDATE_NOTIFY_HOOK);
+
+        return 0;
+}
+
 static int context_install(
                 Context *c,
                 const char *version,
@@ -1236,6 +1316,9 @@ static int context_install(
 
         log_info("%s Successfully installed update '%s'.", glyph(GLYPH_SPARKLES), us->version);
 
+        if (!arg_root)
+                (void) context_notify_subscribers(c, us);
+
         (void) sd_notifyf(/* unset_environment= */ false,
                           "STATUS=Installed '%s'.", us->version);
 
@@ -1625,6 +1708,17 @@ static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flag
 
                         installed = r > 0;
                 }
+
+                /* context_install() returns > 0 (and emits a notification) only if it actually applied an update. If
+                 * nothing was applied but SYSTEMD_SYSUPDATE_FORCE_NOTIFY=1 is set, still notify subscribers (without a
+                 * resource list), so e.g. a kernel/policy refresh can be triggered unconditionally. */
+                if ((action_flags & UPDATE_ACTION_INSTALL) && !installed) {
+                        int f = secure_getenv_bool("SYSTEMD_SYSUPDATE_FORCE_NOTIFY");
+                        if (f < 0 && f != -ENXIO)
+                                log_debug_errno(f, "Failed to parse $SYSTEMD_SYSUPDATE_FORCE_NOTIFY, ignoring: %m");
+                        if (f > 0)
+                                (void) context_notify_subscribers(context, /* us= */ NULL);
+                }
         }
 
         if (arg_cleanup > 0)