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