From 5e422a9cea38bd5c7ce54c7bbac612c04418dc41 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 1 Apr 2019 18:54:59 +0200 Subject: [PATCH] shared: add generic logic for waiting for a unit to enter some state This is a generic implementation of a client-side logic of waiting until a unit enters or leaves some state. This is a more generic implementation of the WaitContext logic currently in systemctl.c, and is supposed to replace it (a later commit does this). It's similar to bus-wait-for-jobs.c and we probably should fold that one into it later on. This code is more powerful and cleaner than the WaitContext logic however. In addition to waiting for a unit to exit this also allows us to wait for a unit to leave the "maintainance" state. This commit only implements the generic logic, and adds no users of it yet. (cherry picked from commit 3572d3df8f822d4cf1601428401a837f723771cf) Related: #1830861 --- src/shared/bus-wait-for-units.c | 434 ++++++++++++++++++++++++++++++++ src/shared/bus-wait-for-units.h | 35 +++ src/shared/meson.build | 2 + 3 files changed, 471 insertions(+) create mode 100644 src/shared/bus-wait-for-units.c create mode 100644 src/shared/bus-wait-for-units.h diff --git a/src/shared/bus-wait-for-units.c b/src/shared/bus-wait-for-units.c new file mode 100644 index 0000000000..d07f491e93 --- /dev/null +++ b/src/shared/bus-wait-for-units.c @@ -0,0 +1,434 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "bus-util.h" +#include "bus-wait-for-units.h" +#include "hashmap.h" +#include "string-util.h" +#include "strv.h" +#include "unit-def.h" + +typedef struct WaitForItem { + BusWaitForUnits *parent; + + BusWaitForUnitsFlags flags; + + char *bus_path; + + sd_bus_slot *slot_get_all; + sd_bus_slot *slot_properties_changed; + + bus_wait_for_units_unit_callback unit_callback; + void *userdata; + + char *active_state; + uint32_t job_id; + char *clean_result; +} WaitForItem; + +typedef struct BusWaitForUnits { + sd_bus *bus; + sd_bus_slot *slot_disconnected; + + Hashmap *items; + + bus_wait_for_units_ready_callback ready_callback; + void *userdata; + + WaitForItem *current; + + BusWaitForUnitsState state; + bool has_failed:1; +} BusWaitForUnits; + +static WaitForItem *wait_for_item_free(WaitForItem *item) { + int r; + + if (!item) + return NULL; + + if (item->parent) { + if (FLAGS_SET(item->flags, BUS_WAIT_REFFED) && item->bus_path && item->parent->bus) { + r = sd_bus_call_method_async( + item->parent->bus, + NULL, + "org.freedesktop.systemd1", + item->bus_path, + "org.freedesktop.systemd1.Unit", + "Unref", + NULL, + NULL, + NULL); + if (r < 0) + log_debug_errno(r, "Failed to drop reference to unit %s, ignoring: %m", item->bus_path); + } + + assert_se(hashmap_remove(item->parent->items, item->bus_path) == item); + + if (item->parent->current == item) + item->parent->current = NULL; + } + + sd_bus_slot_unref(item->slot_properties_changed); + sd_bus_slot_unref(item->slot_get_all); + + free(item->bus_path); + free(item->active_state); + free(item->clean_result); + + return mfree(item); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(WaitForItem*, wait_for_item_free); + +static void bus_wait_for_units_clear(BusWaitForUnits *d) { + WaitForItem *item; + + assert(d); + + d->slot_disconnected = sd_bus_slot_unref(d->slot_disconnected); + d->bus = sd_bus_unref(d->bus); + + while ((item = hashmap_first(d->items))) { + d->current = item; + + item->unit_callback(d, item->bus_path, false, item->userdata); + wait_for_item_free(item); + } + + d->items = hashmap_free(d->items); +} + +static int match_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *error) { + BusWaitForUnits *d = userdata; + + assert(m); + assert(d); + + log_error("Warning! D-Bus connection terminated."); + + bus_wait_for_units_clear(d); + + if (d->ready_callback) + d->ready_callback(d, false, d->userdata); + else /* If no ready callback is specified close the connection so that the event loop exits */ + sd_bus_close(sd_bus_message_get_bus(m)); + + return 0; +} + +int bus_wait_for_units_new(sd_bus *bus, BusWaitForUnits **ret) { + _cleanup_(bus_wait_for_units_freep) BusWaitForUnits *d = NULL; + int r; + + assert(bus); + assert(ret); + + d = new(BusWaitForUnits, 1); + if (!d) + return -ENOMEM; + + *d = (BusWaitForUnits) { + .state = BUS_WAIT_SUCCESS, + .bus = sd_bus_ref(bus), + }; + + r = sd_bus_match_signal_async( + bus, + &d->slot_disconnected, + "org.freedesktop.DBus.Local", + NULL, + "org.freedesktop.DBus.Local", + "Disconnected", + match_disconnected, NULL, d); + if (r < 0) + return r; + + *ret = TAKE_PTR(d); + return 0; +} + +BusWaitForUnits* bus_wait_for_units_free(BusWaitForUnits *d) { + if (!d) + return NULL; + + bus_wait_for_units_clear(d); + sd_bus_slot_unref(d->slot_disconnected); + sd_bus_unref(d->bus); + + return mfree(d); +} + +static bool bus_wait_for_units_is_ready(BusWaitForUnits *d) { + assert(d); + + if (!d->bus) /* Disconnected? */ + return true; + + return hashmap_isempty(d->items); +} + +void bus_wait_for_units_set_ready_callback(BusWaitForUnits *d, bus_wait_for_units_ready_callback callback, void *userdata) { + assert(d); + + d->ready_callback = callback; + d->userdata = userdata; +} + +static void bus_wait_for_units_check_ready(BusWaitForUnits *d) { + assert(d); + + if (!bus_wait_for_units_is_ready(d)) + return; + + d->state = d->has_failed ? BUS_WAIT_FAILURE : BUS_WAIT_SUCCESS; + + if (d->ready_callback) + d->ready_callback(d, d->state, d->userdata); +} + +static void wait_for_item_check_ready(WaitForItem *item) { + BusWaitForUnits *d; + + assert(item); + assert(d = item->parent); + + if (FLAGS_SET(item->flags, BUS_WAIT_FOR_MAINTENANCE_END)) { + + if (item->clean_result && !streq(item->clean_result, "success")) + d->has_failed = true; + + if (!item->active_state || streq(item->active_state, "maintenance")) + return; + } + + if (FLAGS_SET(item->flags, BUS_WAIT_NO_JOB) && item->job_id != 0) + return; + + if (FLAGS_SET(item->flags, BUS_WAIT_FOR_INACTIVE)) { + + if (streq_ptr(item->active_state, "failed")) + d->has_failed = true; + else if (!streq_ptr(item->active_state, "inactive")) + return; + } + + if (item->unit_callback) { + d->current = item; + item->unit_callback(d, item->bus_path, true, item->userdata); + } + + wait_for_item_free(item); + + bus_wait_for_units_check_ready(d); +} + +static int property_map_job( + sd_bus *bus, + const char *member, + sd_bus_message *m, + sd_bus_error *error, + void *userdata) { + + WaitForItem *item = userdata; + const char *path; + uint32_t id; + int r; + + assert(item); + + r = sd_bus_message_read(m, "(uo)", &id, &path); + if (r < 0) + return r; + + item->job_id = id; + return 0; +} + +static int wait_for_item_parse_properties(WaitForItem *item, sd_bus_message *m) { + + static const struct bus_properties_map map[] = { + { "ActiveState", "s", NULL, offsetof(WaitForItem, active_state) }, + { "Job", "(uo)", property_map_job, 0 }, + { "CleanResult", "s", NULL, offsetof(WaitForItem, clean_result) }, + {} + }; + + int r; + + assert(item); + assert(m); + + r = bus_message_map_all_properties(m, map, BUS_MAP_STRDUP, NULL, item); + if (r < 0) + return r; + + wait_for_item_check_ready(item); + return 0; +} + +static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error *error) { + WaitForItem *item = userdata; + const char *interface; + int r; + + assert(item); + + r = sd_bus_message_read(m, "s", &interface); + if (r < 0) { + log_debug_errno(r, "Failed to parse PropertiesChanged signal: %m"); + return 0; + } + + if (!streq(interface, "org.freedesktop.systemd1.Unit")) + return 0; + + r = wait_for_item_parse_properties(item, m); + if (r < 0) + log_debug_errno(r, "Failed to process PropertiesChanged signal: %m"); + + return 0; +} + +static int on_get_all_properties(sd_bus_message *m, void *userdata, sd_bus_error *error) { + WaitForItem *item = userdata; + int r; + + assert(item); + + if (sd_bus_error_is_set(error)) { + BusWaitForUnits *d = item->parent; + + d->has_failed = true; + + log_debug_errno(sd_bus_error_get_errno(error), "GetAll() failed for %s: %s", + item->bus_path, error->message); + + d->current = item; + item->unit_callback(d, item->bus_path, false, item->userdata); + wait_for_item_free(item); + + bus_wait_for_units_check_ready(d); + return 0; + } + + r = wait_for_item_parse_properties(item, m); + if (r < 0) + log_debug_errno(r, "Failed to process GetAll method reply: %m"); + + return 0; +} + +int bus_wait_for_units_add_unit( + BusWaitForUnits *d, + const char *unit, + BusWaitForUnitsFlags flags, + bus_wait_for_units_unit_callback callback, + void *userdata) { + + _cleanup_(wait_for_item_freep) WaitForItem *item = NULL; + int r; + + assert(d); + assert(unit); + + assert(flags != 0); + + r = hashmap_ensure_allocated(&d->items, &string_hash_ops); + if (r < 0) + return r; + + item = new(WaitForItem, 1); + if (!item) + return -ENOMEM; + + *item = (WaitForItem) { + .flags = flags, + .bus_path = unit_dbus_path_from_name(unit), + .unit_callback = callback, + .userdata = userdata, + .job_id = UINT32_MAX, + }; + + if (!item->bus_path) + return -ENOMEM; + + if (!FLAGS_SET(item->flags, BUS_WAIT_REFFED)) { + r = sd_bus_call_method_async( + d->bus, + NULL, + "org.freedesktop.systemd1", + item->bus_path, + "org.freedesktop.systemd1.Unit", + "Ref", + NULL, + NULL, + NULL); + if (r < 0) + return log_debug_errno(r, "Failed to add reference to unit %s: %m", unit); + + + item->flags |= BUS_WAIT_REFFED; + } + + r = sd_bus_match_signal_async( + d->bus, + &item->slot_properties_changed, + "org.freedesktop.systemd1", + item->bus_path, + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + on_properties_changed, + NULL, + item); + if (r < 0) + return log_debug_errno(r, "Failed to request match for PropertiesChanged signal: %m"); + + r = sd_bus_call_method_async( + d->bus, + &item->slot_get_all, + "org.freedesktop.systemd1", + item->bus_path, + "org.freedesktop.DBus.Properties", + "GetAll", + on_get_all_properties, + item, + "s", FLAGS_SET(item->flags, BUS_WAIT_FOR_MAINTENANCE_END) ? NULL : "org.freedesktop.systemd1.Unit"); + if (r < 0) + return log_debug_errno(r, "Failed to request properties of unit %s: %m", unit); + + r = hashmap_put(d->items, item->bus_path, item); + if (r < 0) + return r; + + d->state = BUS_WAIT_RUNNING; + item->parent = d; + TAKE_PTR(item); + return 0; +} + +int bus_wait_for_units_run(BusWaitForUnits *d) { + int r; + + assert(d); + + while (d->state == BUS_WAIT_RUNNING) { + + r = sd_bus_process(d->bus, NULL); + if (r < 0) + return r; + if (r > 0) + continue; + + r = sd_bus_wait(d->bus, (uint64_t) -1); + if (r < 0) + return r; + } + + return d->state; +} + +BusWaitForUnitsState bus_wait_for_units_state(BusWaitForUnits *d) { + assert(d); + + return d->state; +} diff --git a/src/shared/bus-wait-for-units.h b/src/shared/bus-wait-for-units.h new file mode 100644 index 0000000000..a20f3d8fd7 --- /dev/null +++ b/src/shared/bus-wait-for-units.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include "macro.h" +#include "sd-bus.h" + +typedef struct BusWaitForUnits BusWaitForUnits; + +typedef enum BusWaitForUnitsState { + BUS_WAIT_SUCCESS, /* Nothing to wait for anymore and nothing failed */ + BUS_WAIT_FAILURE, /* dito, but something failed */ + BUS_WAIT_RUNNING, /* Still something to wait for */ + _BUS_WAIT_FOR_UNITS_STATE_MAX, + _BUS_WAIT_FOR_UNITS_STATE_INVALID = -1, +} BusWaitForUnitsState; + +typedef enum BusWaitForUnitsFlags { + BUS_WAIT_FOR_MAINTENANCE_END = 1 << 0, /* Wait until the unit is no longer in maintenance state */ + BUS_WAIT_FOR_INACTIVE = 1 << 1, /* Wait until the unit is back in inactive or dead state */ + BUS_WAIT_NO_JOB = 1 << 2, /* Wait until there's no more job pending */ + BUS_WAIT_REFFED = 1 << 3, /* The unit is already reffed with RefUnit() */ +} BusWaitForUnitsFlags; + +typedef void (*bus_wait_for_units_ready_callback)(BusWaitForUnits *d, BusWaitForUnitsState state, void *userdata); +typedef void (*bus_wait_for_units_unit_callback)(BusWaitForUnits *d, const char *unit_path, bool good, void *userdata); + +int bus_wait_for_units_new(sd_bus *bus, BusWaitForUnits **ret); +BusWaitForUnits* bus_wait_for_units_free(BusWaitForUnits *d); + +BusWaitForUnitsState bus_wait_for_units_state(BusWaitForUnits *d); +void bus_wait_for_units_set_ready_callback(BusWaitForUnits *d, bus_wait_for_units_ready_callback callback, void *userdata); +int bus_wait_for_units_add_unit(BusWaitForUnits *d, const char *unit, BusWaitForUnitsFlags flags, bus_wait_for_units_unit_callback callback, void *userdata); +int bus_wait_for_units_run(BusWaitForUnits *d); + +DEFINE_TRIVIAL_CLEANUP_FUNC(BusWaitForUnits*, bus_wait_for_units_free); diff --git a/src/shared/meson.build b/src/shared/meson.build index 54e77e9af6..d0a1bba4c6 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -18,6 +18,8 @@ shared_sources = files(''' bus-unit-util.h bus-util.c bus-util.h + bus-wait-for-units.c + bus-wait-for-units.h cgroup-show.c cgroup-show.h clean-ipc.c