chiark / gitweb /
shared: move unit-specific code from bus-util.h to bus-unit-util.h
authorLennart Poettering <lennart@poettering.net>
Wed, 20 Apr 2016 15:43:13 +0000 (17:43 +0200)
committerSven Eden <yamakuzure@gmx.net>
Fri, 16 Jun 2017 08:12:58 +0000 (10:12 +0200)
Previously we'd have generally useful sd-bus utilities in bust-util.h,
intermixed with code that is specifically for writing clients for PID 1,
wrapping job and unit handling. Let's split the latter out and move it into
bus-unit-util.c, to make the sources a bit short and easier to grok.

src/shared/bus-unit-util.c

index acff91f..07cdc93 100644 (file)
 #include "utf8.h"
 #include "util.h"
 
+int bus_parse_unit_info(sd_bus_message *message, UnitInfo *u) {
+        assert(message);
+        assert(u);
+
+        u->machine = NULL;
+
+        return sd_bus_message_read(
+                        message,
+                        "(ssssssouso)",
+                        &u->id,
+                        &u->description,
+                        &u->load_state,
+                        &u->active_state,
+                        &u->sub_state,
+                        &u->following,
+                        &u->unit_path,
+                        &u->job_id,
+                        &u->job_type,
+                        &u->job_path);
+}
+
+int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignment) {
+        const char *eq, *field;
+        int r, rl;
+
+        assert(m);
+        assert(assignment);
+
+        eq = strchr(assignment, '=');
+        if (!eq) {
+                log_error("Not an assignment: %s", assignment);
+                return -EINVAL;
+        }
+
+        r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv");
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        field = strndupa(assignment, eq - assignment);
+        eq++;
+
+        if (streq(field, "CPUQuota")) {
+
+                if (isempty(eq))
+                        r = sd_bus_message_append(m, "sv", "CPUQuotaPerSecUSec", "t", USEC_INFINITY);
+                else if (endswith(eq, "%")) {
+                        double percent;
+
+                        if (sscanf(eq, "%lf%%", &percent) != 1 || percent <= 0) {
+                                log_error("CPU quota '%s' invalid.", eq);
+                                return -EINVAL;
+                        }
+
+                        r = sd_bus_message_append(m, "sv", "CPUQuotaPerSecUSec", "t", (usec_t) percent * USEC_PER_SEC / 100);
+                } else {
+                        log_error("CPU quota needs to be in percent.");
+                        return -EINVAL;
+                }
+
+                goto finish;
+
+        } else if (streq(field, "EnvironmentFile")) {
+
+                r = sd_bus_message_append(m, "sv", "EnvironmentFiles", "a(sb)", 1,
+                                          eq[0] == '-' ? eq + 1 : eq,
+                                          eq[0] == '-');
+                goto finish;
+
+        } else if (STR_IN_SET(field, "AccuracySec", "RandomizedDelaySec", "RuntimeMaxSec")) {
+                char *n;
+                usec_t t;
+                size_t l;
+                r = parse_sec(eq, &t);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to parse %s= parameter: %s", field, eq);
+
+                l = strlen(field);
+                n = newa(char, l + 2);
+                if (!n)
+                        return log_oom();
+
+                /* Change suffix Sec → USec */
+                strcpy(mempcpy(n, field, l - 3), "USec");
+                r = sd_bus_message_append(m, "sv", n, "t", t);
+                goto finish;
+        }
+
+        r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, field);
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        rl = rlimit_from_string(field);
+        if (rl >= 0) {
+                const char *sn;
+                struct rlimit l;
+
+                r = rlimit_parse(rl, eq, &l);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to parse resource limit: %s", eq);
+
+                r = sd_bus_message_append(m, "v", "t", l.rlim_max);
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_close_container(m);
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                sn = strjoina(field, "Soft");
+                r = sd_bus_message_append(m, "sv", sn, "t", l.rlim_cur);
+
+        } else if (STR_IN_SET(field,
+                       "CPUAccounting", "MemoryAccounting", "BlockIOAccounting", "TasksAccounting",
+                       "SendSIGHUP", "SendSIGKILL", "WakeSystem", "DefaultDependencies",
+                       "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "RemainAfterExit",
+                       "PrivateTmp", "PrivateDevices", "PrivateNetwork", "NoNewPrivileges",
+                       "SyslogLevelPrefix", "Delegate", "RemainAfterElapse")) {
+
+                r = parse_boolean(eq);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to parse boolean assignment %s.", assignment);
+
+                r = sd_bus_message_append(m, "v", "b", r);
+
+        } else if (streq(field, "MemoryLimit")) {
+                uint64_t bytes;
+
+                if (isempty(eq) || streq(eq, "infinity"))
+                        bytes = (uint64_t) -1;
+                else {
+                        r = parse_size(eq, 1024, &bytes);
+                        if (r < 0) {
+                                log_error("Failed to parse bytes specification %s", assignment);
+                                return -EINVAL;
+                        }
+                }
+
+                r = sd_bus_message_append(m, "v", "t", bytes);
+
+        } else if (streq(field, "TasksMax")) {
+                uint64_t n;
+
+                if (isempty(eq) || streq(eq, "infinity"))
+                        n = (uint64_t) -1;
+                else {
+                        r = safe_atou64(eq, &n);
+                        if (r < 0) {
+                                log_error("Failed to parse maximum tasks specification %s", assignment);
+                                return -EINVAL;
+                        }
+                }
+
+                r = sd_bus_message_append(m, "v", "t", n);
+
+        } else if (STR_IN_SET(field, "CPUShares", "StartupCPUShares")) {
+                uint64_t u;
+
+                r = cg_cpu_shares_parse(eq, &u);
+                if (r < 0) {
+                        log_error("Failed to parse %s value %s.", field, eq);
+                        return -EINVAL;
+                }
+
+                r = sd_bus_message_append(m, "v", "t", u);
+
+        } else if (STR_IN_SET(field, "BlockIOWeight", "StartupBlockIOWeight")) {
+                uint64_t u;
+
+                r = cg_cpu_shares_parse(eq, &u);
+                if (r < 0) {
+                        log_error("Failed to parse %s value %s.", field, eq);
+                        return -EINVAL;
+                }
+
+                r = sd_bus_message_append(m, "v", "t", u);
+
+        } else if (STR_IN_SET(field,
+                              "User", "Group", "DevicePolicy", "KillMode",
+                              "UtmpIdentifier", "UtmpMode", "PAMName", "TTYPath",
+                              "StandardInput", "StandardOutput", "StandardError",
+                              "Description", "Slice", "Type", "WorkingDirectory",
+                              "RootDirectory", "SyslogIdentifier", "ProtectSystem",
+                              "ProtectHome"))
+                r = sd_bus_message_append(m, "v", "s", eq);
+
+        else if (streq(field, "SyslogLevel")) {
+                int level;
+
+                level = log_level_from_string(eq);
+                if (level < 0) {
+                        log_error("Failed to parse %s value %s.", field, eq);
+                        return -EINVAL;
+                }
+
+                r = sd_bus_message_append(m, "v", "i", level);
+
+        } else if (streq(field, "SyslogFacility")) {
+                int facility;
+
+                facility = log_facility_unshifted_from_string(eq);
+                if (facility < 0) {
+                        log_error("Failed to parse %s value %s.", field, eq);
+                        return -EINVAL;
+                }
+
+                r = sd_bus_message_append(m, "v", "i", facility);
+
+        } else if (streq(field, "DeviceAllow")) {
+
+                if (isempty(eq))
+                        r = sd_bus_message_append(m, "v", "a(ss)", 0);
+                else {
+                        const char *path, *rwm, *e;
+
+                        e = strchr(eq, ' ');
+                        if (e) {
+                                path = strndupa(eq, e - eq);
+                                rwm = e+1;
+                        } else {
+                                path = eq;
+                                rwm = "";
+                        }
+
+                        if (!path_startswith(path, "/dev")) {
+                                log_error("%s is not a device file in /dev.", path);
+                                return -EINVAL;
+                        }
+
+                        r = sd_bus_message_append(m, "v", "a(ss)", 1, path, rwm);
+                }
+
+        } else if (STR_IN_SET(field, "BlockIOReadBandwidth", "BlockIOWriteBandwidth")) {
+
+                if (isempty(eq))
+                        r = sd_bus_message_append(m, "v", "a(st)", 0);
+                else {
+                        const char *path, *bandwidth, *e;
+                        uint64_t bytes;
+
+                        e = strchr(eq, ' ');
+                        if (e) {
+                                path = strndupa(eq, e - eq);
+                                bandwidth = e+1;
+                        } else {
+                                log_error("Failed to parse %s value %s.", field, eq);
+                                return -EINVAL;
+                        }
+
+                        if (!path_startswith(path, "/dev")) {
+                                log_error("%s is not a device file in /dev.", path);
+                                return -EINVAL;
+                        }
+
+                        r = parse_size(bandwidth, 1000, &bytes);
+                        if (r < 0) {
+                                log_error("Failed to parse byte value %s.", bandwidth);
+                                return -EINVAL;
+                        }
+
+                        r = sd_bus_message_append(m, "v", "a(st)", 1, path, bytes);
+                }
+
+        } else if (streq(field, "BlockIODeviceWeight")) {
+
+                if (isempty(eq))
+                        r = sd_bus_message_append(m, "v", "a(st)", 0);
+                else {
+                        const char *path, *weight, *e;
+                        uint64_t u;
+
+                        e = strchr(eq, ' ');
+                        if (e) {
+                                path = strndupa(eq, e - eq);
+                                weight = e+1;
+                        } else {
+                                log_error("Failed to parse %s value %s.", field, eq);
+                                return -EINVAL;
+                        }
+
+                        if (!path_startswith(path, "/dev")) {
+                                log_error("%s is not a device file in /dev.", path);
+                                return -EINVAL;
+                        }
+
+                        r = safe_atou64(weight, &u);
+                        if (r < 0) {
+                                log_error("Failed to parse %s value %s.", field, weight);
+                                return -EINVAL;
+                        }
+                        r = sd_bus_message_append(m, "v", "a(st)", path, u);
+                }
+
+        } else if (streq(field, "Nice")) {
+                int32_t i;
+
+                r = safe_atoi32(eq, &i);
+                if (r < 0) {
+                        log_error("Failed to parse %s value %s.", field, eq);
+                        return -EINVAL;
+                }
+
+                r = sd_bus_message_append(m, "v", "i", i);
+
+        } else if (STR_IN_SET(field, "Environment", "PassEnvironment")) {
+                const char *p;
+
+                r = sd_bus_message_open_container(m, 'v', "as");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_open_container(m, 'a', "s");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                p = eq;
+
+                for (;;) {
+                        _cleanup_free_ char *word = NULL;
+
+                        r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE);
+                        if (r < 0) {
+                                log_error("Failed to parse Environment value %s", eq);
+                                return -EINVAL;
+                        }
+                        if (r == 0)
+                                break;
+
+                        if (streq(field, "Environment")) {
+                                if (!env_assignment_is_valid(word)) {
+                                        log_error("Invalid environment assignment: %s", word);
+                                        return -EINVAL;
+                                }
+                        } else {  /* PassEnvironment */
+                                if (!env_name_is_valid(word)) {
+                                        log_error("Invalid environment variable name: %s", word);
+                                        return -EINVAL;
+                                }
+                        }
+
+                        r = sd_bus_message_append_basic(m, 's', word);
+                        if (r < 0)
+                                return bus_log_create_error(r);
+                }
+
+                r = sd_bus_message_close_container(m);
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_close_container(m);
+
+        } else if (streq(field, "KillSignal")) {
+                int sig;
+
+                sig = signal_from_string_try_harder(eq);
+                if (sig < 0) {
+                        log_error("Failed to parse %s value %s.", field, eq);
+                        return -EINVAL;
+                }
+
+                r = sd_bus_message_append(m, "v", "i", sig);
+
+        } else if (streq(field, "TimerSlackNSec")) {
+                nsec_t n;
+
+                r = parse_nsec(eq, &n);
+                if (r < 0) {
+                        log_error("Failed to parse %s value %s", field, eq);
+                        return -EINVAL;
+                }
+
+                r = sd_bus_message_append(m, "v", "t", n);
+        } else if (streq(field, "OOMScoreAdjust")) {
+                int oa;
+
+                r = safe_atoi(eq, &oa);
+                if (r < 0) {
+                        log_error("Failed to parse %s value %s", field, eq);
+                        return -EINVAL;
+                }
+
+                if (!oom_score_adjust_is_valid(oa)) {
+                        log_error("OOM score adjust value out of range");
+                        return -EINVAL;
+                }
+
+                r = sd_bus_message_append(m, "v", "i", oa);
+        } else if (STR_IN_SET(field, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories")) {
+                const char *p;
+
+                r = sd_bus_message_open_container(m, 'v', "as");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_open_container(m, 'a', "s");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                p = eq;
+
+                for (;;) {
+                        _cleanup_free_ char *word = NULL;
+                        int offset;
+
+                        r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES);
+                        if (r < 0) {
+                                log_error("Failed to parse %s value %s", field, eq);
+                                return -EINVAL;
+                        }
+                        if (r == 0)
+                                break;
+
+                        if (!utf8_is_valid(word)) {
+                                log_error("Failed to parse %s value %s", field, eq);
+                                return -EINVAL;
+                        }
+
+                        offset = word[0] == '-';
+                        if (!path_is_absolute(word + offset)) {
+                                log_error("Failed to parse %s value %s", field, eq);
+                                return -EINVAL;
+                        }
+
+                        path_kill_slashes(word + offset);
+
+                        r = sd_bus_message_append_basic(m, 's', word);
+                        if (r < 0)
+                                return bus_log_create_error(r);
+                }
+
+                r = sd_bus_message_close_container(m);
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_close_container(m);
+
+        } else if (streq(field, "RuntimeDirectory")) {
+                const char *p;
+
+                r = sd_bus_message_open_container(m, 'v', "as");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_open_container(m, 'a', "s");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                p = eq;
+
+                for (;;) {
+                        _cleanup_free_ char *word = NULL;
+
+                        r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse %s value %s", field, eq);
+
+                        if (r == 0)
+                                break;
+
+                        r = sd_bus_message_append_basic(m, 's', word);
+                        if (r < 0)
+                                return bus_log_create_error(r);
+                }
+
+                r = sd_bus_message_close_container(m);
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_close_container(m);
+
+        } else {
+                log_error("Unknown assignment %s.", assignment);
+                return -EINVAL;
+        }
+
+finish:
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        r = sd_bus_message_close_container(m);
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        return 0;
+}
+
+typedef struct BusWaitForJobs {
+        sd_bus *bus;
+        Set *jobs;
+
+        char *name;
+        char *result;
+
+        sd_bus_slot *slot_job_removed;
+        sd_bus_slot *slot_disconnected;
+} BusWaitForJobs;
+
+static int match_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *error) {
+        assert(m);
+
+        log_error("Warning! D-Bus connection terminated.");
+        sd_bus_close(sd_bus_message_get_bus(m));
+
+        return 0;
+}
+
+static int match_job_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
+        const char *path, *unit, *result;
+        BusWaitForJobs *d = userdata;
+        uint32_t id;
+        char *found;
+        int r;
+
+        assert(m);
+        assert(d);
+
+        r = sd_bus_message_read(m, "uoss", &id, &path, &unit, &result);
+        if (r < 0) {
+                bus_log_parse_error(r);
+                return 0;
+        }
+
+        found = set_remove(d->jobs, (char*) path);
+        if (!found)
+                return 0;
+
+        free(found);
+
+        if (!isempty(result))
+                d->result = strdup(result);
+
+        if (!isempty(unit))
+                d->name = strdup(unit);
+
+        return 0;
+}
+
+void bus_wait_for_jobs_free(BusWaitForJobs *d) {
+        if (!d)
+                return;
+
+        set_free_free(d->jobs);
+
+        sd_bus_slot_unref(d->slot_disconnected);
+        sd_bus_slot_unref(d->slot_job_removed);
+
+        sd_bus_unref(d->bus);
+
+        free(d->name);
+        free(d->result);
+
+        free(d);
+}
+
+int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret) {
+        _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *d = NULL;
+        int r;
+
+        assert(bus);
+        assert(ret);
+
+        d = new0(BusWaitForJobs, 1);
+        if (!d)
+                return -ENOMEM;
+
+        d->bus = sd_bus_ref(bus);
+
+        /* When we are a bus client we match by sender. Direct
+         * connections OTOH have no initialized sender field, and
+         * hence we ignore the sender then */
+        r = sd_bus_add_match(
+                        bus,
+                        &d->slot_job_removed,
+                        bus->bus_client ?
+                        "type='signal',"
+                        "sender='org.freedesktop.elogind1',"
+                        "interface='org.freedesktop.elogind1.Manager',"
+                        "member='JobRemoved',"
+                        "path='/org/freedesktop/elogind1'" :
+                        "type='signal',"
+                        "interface='org.freedesktop.elogind1.Manager',"
+                        "member='JobRemoved',"
+                        "path='/org/freedesktop/elogind1'",
+                        match_job_removed, d);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_add_match(
+                        bus,
+                        &d->slot_disconnected,
+                        "type='signal',"
+                        "sender='org.freedesktop.DBus.Local',"
+                        "interface='org.freedesktop.DBus.Local',"
+                        "member='Disconnected'",
+                        match_disconnected, d);
+        if (r < 0)
+                return r;
+
+        *ret = d;
+        d = NULL;
+
+        return 0;
+}
+
+static int bus_process_wait(sd_bus *bus) {
+        int r;
+
+        for (;;) {
+                r = sd_bus_process(bus, NULL);
+                if (r < 0)
+                        return r;
+                if (r > 0)
+                        return 0;
+
+                r = sd_bus_wait(bus, (uint64_t) -1);
+                if (r < 0)
+                        return r;
+        }
+}
+
+static int bus_job_get_service_result(BusWaitForJobs *d, char **result) {
+        _cleanup_free_ char *dbus_path = NULL;
+
+        assert(d);
+        assert(d->name);
+        assert(result);
+
+        dbus_path = unit_dbus_path_from_name(d->name);
+        if (!dbus_path)
+                return -ENOMEM;
+
+        return sd_bus_get_property_string(d->bus,
+                                          "org.freedesktop.elogind1",
+                                          dbus_path,
+                                          "org.freedesktop.elogind1.Service",
+                                          "Result",
+                                          NULL,
+                                          result);
+}
+
+static const struct {
+        const char *result, *explanation;
+} explanations [] = {
+        { "resources",   "a configured resource limit was exceeded" },
+        { "timeout",     "a timeout was exceeded" },
+        { "exit-code",   "the control process exited with error code" },
+        { "signal",      "a fatal signal was delivered to the control process" },
+        { "core-dump",   "a fatal signal was delivered causing the control process to dump core" },
+        { "watchdog",    "the service failed to send watchdog ping" },
+        { "start-limit", "start of the service was attempted too often" }
+};
+
+static void log_job_error_with_service_result(const char* service, const char *result, const char* const* extra_args) {
+        _cleanup_free_ char *service_shell_quoted = NULL;
+        const char *systemctl = "systemctl", *journalctl = "journalctl";
+
+        assert(service);
+
+        service_shell_quoted = shell_maybe_quote(service);
+
+        if (extra_args && extra_args[1]) {
+                _cleanup_free_ char *t;
+
+                t = strv_join((char**) extra_args, " ");
+                systemctl = strjoina("systemctl ", t ? : "<args>");
+                journalctl = strjoina("journalctl ", t ? : "<args>");
+        }
+
+        if (!isempty(result)) {
+                unsigned i;
+
+                for (i = 0; i < ELEMENTSOF(explanations); ++i)
+                        if (streq(result, explanations[i].result))
+                                break;
+
+                if (i < ELEMENTSOF(explanations)) {
+                        log_error("Job for %s failed because %s.\n"
+                                  "See \"%s status %s\" and \"%s -xe\" for details.\n",
+                                  service,
+                                  explanations[i].explanation,
+                                  systemctl,
+                                  service_shell_quoted ?: "<service>",
+                                  journalctl);
+                        goto finish;
+                }
+        }
+
+        log_error("Job for %s failed.\n"
+                  "See \"%s status %s\" and \"%s -xe\" for details.\n",
+                  service,
+                  systemctl,
+                  service_shell_quoted ?: "<service>",
+                  journalctl);
+
+finish:
+        /* For some results maybe additional explanation is required */
+        if (streq_ptr(result, "start-limit"))
+                log_info("To force a start use \"%1$s reset-failed %2$s\"\n"
+                         "followed by \"%1$s start %2$s\" again.",
+                         systemctl,
+                         service_shell_quoted ?: "<service>");
+}
+
+static int check_wait_response(BusWaitForJobs *d, bool quiet, const char* const* extra_args) {
+        int r = 0;
+
+        assert(d->result);
+
+        if (!quiet) {
+                if (streq(d->result, "canceled"))
+                        log_error("Job for %s canceled.", strna(d->name));
+                else if (streq(d->result, "timeout"))
+                        log_error("Job for %s timed out.", strna(d->name));
+                else if (streq(d->result, "dependency"))
+                        log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", strna(d->name));
+                else if (streq(d->result, "invalid"))
+                        log_error("%s is not active, cannot reload.", strna(d->name));
+                else if (streq(d->result, "assert"))
+                        log_error("Assertion failed on job for %s.", strna(d->name));
+                else if (streq(d->result, "unsupported"))
+                        log_error("Operation on or unit type of %s not supported on this system.", strna(d->name));
+                else if (!streq(d->result, "done") && !streq(d->result, "skipped")) {
+                        if (d->name) {
+                                int q;
+                                _cleanup_free_ char *result = NULL;
+
+                                q = bus_job_get_service_result(d, &result);
+                                if (q < 0)
+                                        log_debug_errno(q, "Failed to get Result property of service %s: %m", d->name);
+
+                                log_job_error_with_service_result(d->name, result, extra_args);
+                        } else
+                                log_error("Job failed. See \"journalctl -xe\" for details.");
+                }
+        }
+
+        if (streq(d->result, "canceled"))
+                r = -ECANCELED;
+        else if (streq(d->result, "timeout"))
+                r = -ETIME;
+        else if (streq(d->result, "dependency"))
+                r = -EIO;
+        else if (streq(d->result, "invalid"))
+                r = -ENOEXEC;
+        else if (streq(d->result, "assert"))
+                r = -EPROTO;
+        else if (streq(d->result, "unsupported"))
+                r = -EOPNOTSUPP;
+        else if (!streq(d->result, "done") && !streq(d->result, "skipped"))
+                r = -EIO;
+
+        return r;
+}
+
+int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char* const* extra_args) {
+        int r = 0;
+
+        assert(d);
+
+        while (!set_isempty(d->jobs)) {
+                int q;
+
+                q = bus_process_wait(d->bus);
+                if (q < 0)
+                        return log_error_errno(q, "Failed to wait for response: %m");
+
+                if (d->result) {
+                        q = check_wait_response(d, quiet, extra_args);
+                        /* Return the first error as it is most likely to be
+                         * meaningful. */
+                        if (q < 0 && r == 0)
+                                r = q;
+
+                        log_debug_errno(q, "Got result %s/%m for job %s", strna(d->result), strna(d->name));
+                }
+
+                d->name = mfree(d->name);
+                d->result = mfree(d->result);
+        }
+
+        return r;
+}
+
+int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path) {
+        int r;
+
+        assert(d);
+
+        r = set_ensure_allocated(&d->jobs, &string_hash_ops);
+        if (r < 0)
+                return r;
+
+        return set_put_strdup(d->jobs, path);
+}
+
+int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet) {
+        int r;
+
+        r = bus_wait_for_jobs_add(d, path);
+        if (r < 0)
+                return log_oom();
+
+        return bus_wait_for_jobs(d, quiet, NULL);
+}
+
+int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet, UnitFileChange **changes, unsigned *n_changes) {
+        const char *type, *path, *source;
+        int r;
+
+        r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sss)");
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        while ((r = sd_bus_message_read(m, "(sss)", &type, &path, &source)) > 0) {
+                /* We expect only "success" changes to be sent over the bus.
+                   Hence, reject anything negative. */
+                UnitFileChangeType ch = unit_file_change_type_from_string(type);
+
+                if (ch < 0) {
+                        log_notice("Manager reported unknown change type \"%s\" for path \"%s\", ignoring.", type, path);
+                        continue;
+                }
+
+                r = unit_file_changes_add(changes, n_changes, ch, path, source);
+                if (r < 0)
+                        return r;
+        }
+        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);
+
+        unit_file_dump_changes(0, NULL, *changes, *n_changes, false);
+        return 0;
+}
+
 struct CGroupInfo {
         char *cgroup_path;
         bool is_const; /* If false, cgroup_path should be free()'d */