| /* 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_se(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; |
| } |