systemctl: port "systemctl start --wait" logic to new bus-wait-for-units.c API

This commit is contained in:
Lennart Poettering 2019-03-29 20:23:33 +01:00
parent d81bc51fe3
commit a7056cde56

View file

@ -2793,158 +2793,6 @@ static const char *verb_to_job_type(const char *verb) {
return "start";
}
typedef struct {
sd_bus_slot *match;
sd_event *event;
Set *unit_paths;
bool any_failed;
} WaitContext;
static void wait_context_free(WaitContext *c) {
c->match = sd_bus_slot_unref(c->match);
c->event = sd_event_unref(c->event);
c->unit_paths = set_free_free(c->unit_paths);
}
static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
const char *path, *interface, *active_state = NULL, *job_path = NULL;
WaitContext *c = userdata;
bool is_failed;
int r;
/* Called whenever we get a PropertiesChanged signal. Checks if ActiveState changed to inactive/failed.
*
* Signal parameters: (s interface, a{sv} changed_properties, as invalidated_properties) */
path = sd_bus_message_get_path(m);
if (!set_contains(c->unit_paths, path))
return 0;
r = sd_bus_message_read(m, "s", &interface);
if (r < 0)
return bus_log_parse_error(r);
if (!streq(interface, "org.freedesktop.systemd1.Unit")) /* ActiveState is on the Unit interface */
return 0;
r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "{sv}");
if (r < 0)
return bus_log_parse_error(r);
for (;;) {
const char *s;
r = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, "sv");
if (r < 0)
return bus_log_parse_error(r);
if (r == 0) /* end of array */
break;
r = sd_bus_message_read(m, "s", &s); /* Property name */
if (r < 0)
return bus_log_parse_error(r);
if (streq(s, "ActiveState")) {
r = sd_bus_message_read(m, "v", "s", &active_state);
if (r < 0)
return bus_log_parse_error(r);
if (job_path) /* Found everything we need */
break;
} else if (streq(s, "Job")) {
uint32_t job_id;
r = sd_bus_message_read(m, "v", "(uo)", &job_id, &job_path);
if (r < 0)
return bus_log_parse_error(r);
/* There's still a job pending for this unit, let's ignore this for now, and return right-away. */
if (job_id != 0)
return 0;
if (active_state) /* Found everything we need */
break;
} else {
r = sd_bus_message_skip(m, "v"); /* Other property */
if (r < 0)
return bus_log_parse_error(r);
}
r = sd_bus_message_exit_container(m);
if (r < 0)
return bus_log_parse_error(r);
}
/* If this didn't contain the ActiveState property we can't do anything */
if (!active_state)
return 0;
is_failed = streq(active_state, "failed");
if (streq(active_state, "inactive") || is_failed) {
log_debug("%s became %s, dropping from --wait tracking", path, active_state);
free(set_remove(c->unit_paths, path));
c->any_failed = c->any_failed || is_failed;
} else
log_debug("ActiveState on %s changed to %s", path, active_state);
if (set_isempty(c->unit_paths))
sd_event_exit(c->event, EXIT_SUCCESS);
return 0;
}
static int wait_context_watch(
WaitContext *wait_context,
sd_bus *bus,
const char *name) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_free_ char *unit_path = NULL;
int r;
assert(wait_context);
assert(name);
log_debug("Watching for property changes of %s", name);
r = sd_bus_call_method(
bus,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
"RefUnit",
&error,
NULL,
"s", name);
if (r < 0)
return log_error_errno(r, "Failed to add reference to unit %s: %s", name, bus_error_message(&error, r));
unit_path = unit_dbus_path_from_name(name);
if (!unit_path)
return log_oom();
r = set_ensure_allocated(&wait_context->unit_paths, &string_hash_ops);
if (r < 0)
return log_oom();
r = set_put_strdup(wait_context->unit_paths, unit_path);
if (r < 0)
return log_error_errno(r, "Failed to add unit path %s to set: %m", unit_path);
r = sd_bus_match_signal_async(bus,
&wait_context->match,
NULL,
unit_path,
"org.freedesktop.DBus.Properties",
"PropertiesChanged",
on_properties_changed, NULL, wait_context);
if (r < 0)
return log_error_errno(r, "Failed to request match for PropertiesChanged signal: %m");
return 0;
}
static int start_unit_one(
sd_bus *bus,
const char *method, /* When using classic per-job bus methods */
@ -2953,7 +2801,7 @@ static int start_unit_one(
const char *mode,
sd_bus_error *error,
BusWaitForJobs *w,
WaitContext *wait_context) {
BusWaitForUnits *wu) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
const char *path;
@ -2965,12 +2813,6 @@ static int start_unit_one(
assert(mode);
assert(error);
if (wait_context) {
r = wait_context_watch(wait_context, bus, name);
if (r < 0)
return r;
}
log_debug("%s dbus call org.freedesktop.systemd1.Manager %s(%s, %s)",
arg_dry_run ? "Would execute" : "Executing",
method, name, mode);
@ -3059,6 +2901,12 @@ static int start_unit_one(
return log_error_errno(r, "Failed to watch job for %s: %m", name);
}
if (wu) {
r = bus_wait_for_units_add_unit(wu, name, BUS_WAIT_FOR_INACTIVE|BUS_WAIT_NO_JOB, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to watch unit %s: %m", name);
}
return 0;
fail:
@ -3190,8 +3038,8 @@ static const char** make_extra_args(const char *extra_args[static 4]) {
}
static int start_unit(int argc, char *argv[], void *userdata) {
_cleanup_(bus_wait_for_units_freep) BusWaitForUnits *wu = NULL;
_cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
_cleanup_(wait_context_free) WaitContext wait_context = {};
const char *method, *job_type, *mode, *one_name, *suffix = NULL;
_cleanup_free_ char **stopped_units = NULL; /* Do not use _cleanup_strv_free_ */
_cleanup_strv_free_ char **names = NULL;
@ -3279,19 +3127,15 @@ static int start_unit(int argc, char *argv[], void *userdata) {
if (r < 0)
return log_error_errno(r, "Failed to enable subscription: %m");
r = sd_event_default(&wait_context.event);
r = bus_wait_for_units_new(bus, &wu);
if (r < 0)
return log_error_errno(r, "Failed to allocate event loop: %m");
r = sd_bus_attach_event(bus, wait_context.event, 0);
if (r < 0)
return log_error_errno(r, "Failed to attach bus to event loop: %m");
return log_error_errno(r, "Failed to allocate unit watch context: %m");
}
STRV_FOREACH(name, names) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
r = start_unit_one(bus, method, job_type, *name, mode, &error, w, arg_wait ? &wait_context : NULL);
r = start_unit_one(bus, method, job_type, *name, mode, &error, w, wu);
if (ret == EXIT_SUCCESS && r < 0)
ret = translate_bus_error_to_exit_status(r, &error);
@ -3316,11 +3160,11 @@ static int start_unit(int argc, char *argv[], void *userdata) {
(void) check_triggering_units(bus, *name);
}
if (ret == EXIT_SUCCESS && arg_wait && !set_isempty(wait_context.unit_paths)) {
r = sd_event_loop(wait_context.event);
if (arg_wait) {
r = bus_wait_for_units_run(wu);
if (r < 0)
return log_error_errno(r, "Failed to run event loop: %m");
if (wait_context.any_failed)
return log_error_errno(r, "Failed to wait for units: %m");
if (r == BUS_WAIT_FAILURE && ret == EXIT_SUCCESS)
ret = EXIT_FAILURE;
}