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