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
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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(
|
||||
¶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)
|
||||
|
||||
Reference in New Issue
Block a user