From: Lennart Poettering Date: Thu, 28 May 2026 09:40:42 +0000 (+0200) Subject: sysupdate: notify hook subscribers after a successful update X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d36bdc946738d0ac3bf059f9f3ef604820f2d461;p=thirdparty%2Fsystemd.git sysupdate: notify hook subscribers after a successful update 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 --- diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index b528fce37da..31f180beea9 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -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. diff --git a/src/basic/constants.h b/src/basic/constants.h index 3dbffdee263..475d8bf79a6 100644 --- a/src/basic/constants.h +++ b/src/basic/constants.h @@ -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" diff --git a/src/libsystemd/sd-varlink/test-varlink-idl.c b/src/libsystemd/sd-varlink/test-varlink-idl.c index e026d4b07ed..0b3304a43c8 100644 --- a/src/libsystemd/sd-varlink/test-varlink-idl.c +++ b/src/libsystemd/sd-varlink/test-varlink-idl.c @@ -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, diff --git a/src/shared/meson.build b/src/shared/meson.build index 8e874cb99d1..52df34bb0f5 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -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 index 00000000000..2fecc6005f1 --- /dev/null +++ b/src/shared/varlink-io.systemd.SysUpdate.Notify.c @@ -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 index 00000000000..72deccb380b --- /dev/null +++ b/src/shared/varlink-io.systemd.SysUpdate.Notify.h @@ -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; diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 81ed4861e0f..9f78ee78bf3 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -3,11 +3,14 @@ #include #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( + ¶ms, + 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)