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