mirror of
https://github.com/systemd/systemd
synced 2024-10-07 08:40:44 +00:00
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.
This commit is contained in:
parent
345f322185
commit
3572d3df8f
434
src/shared/bus-wait-for-units.c
Normal file
434
src/shared/bus-wait-for-units.c
Normal file
|
@ -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;
|
||||||
|
}
|
35
src/shared/bus-wait-for-units.h
Normal file
35
src/shared/bus-wait-for-units.h
Normal file
|
@ -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);
|
|
@ -29,6 +29,8 @@ shared_sources = files('''
|
||||||
bus-util.h
|
bus-util.h
|
||||||
bus-wait-for-jobs.c
|
bus-wait-for-jobs.c
|
||||||
bus-wait-for-jobs.h
|
bus-wait-for-jobs.h
|
||||||
|
bus-wait-for-units.c
|
||||||
|
bus-wait-for-units.h
|
||||||
calendarspec.c
|
calendarspec.c
|
||||||
calendarspec.h
|
calendarspec.h
|
||||||
cgroup-show.c
|
cgroup-show.c
|
||||||
|
|
Loading…
Reference in a new issue