From: Lennart Poettering Date: Mon, 29 Dec 2014 11:41:26 +0000 (+0100) Subject: machinectl: add new "start" verb to start a container as a service in nspawn X-Git-Tag: v219~765 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=commitdiff_plain;h=ebd011d95b61a86258dece9864f65b7c4af721c0 machinectl: add new "start" verb to start a container as a service in nspawn --- diff --git a/src/libsystemd/sd-bus/bus-util.c b/src/libsystemd/sd-bus/bus-util.c index 2c3da3ce9..c264f2d3f 100644 --- a/src/libsystemd/sd-bus/bus-util.c +++ b/src/libsystemd/sd-bus/bus-util.c @@ -21,16 +21,16 @@ #include -#include "systemd/sd-daemon.h" - +#include "sd-daemon.h" +#include "sd-event.h" #include "util.h" #include "strv.h" #include "macro.h" #include "def.h" #include "path-util.h" #include "missing.h" +#include "set.h" -#include "sd-event.h" #include "sd-bus.h" #include "bus-error.h" #include "bus-message.h" @@ -1554,3 +1554,225 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen 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 *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) { + assert(bus); + assert(m); + + log_error("Warning! D-Bus connection terminated."); + sd_bus_close(bus); + + return 0; +} + +static int match_job_removed(sd_bus *bus, 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(bus); + 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); + + r = sd_bus_add_match( + bus, + &d->slot_job_removed, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.systemd1.Manager'," + "member='JobRemoved'," + "path='/org/freedesktop/systemd1'", + 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 check_wait_response(BusWaitForJobs *d, bool quiet) { + 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("Job for %s invalid.", 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) { + bool quotes; + + quotes = chars_intersect(d->name, SHELL_NEED_QUOTES); + + log_error("Job for %s failed. See \"systemctl status %s%s%s\" and \"journalctl -xe\" for details.", + d->name, + quotes ? "'" : "", d->name, quotes ? "'" : ""); + } 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 = -ENOTSUP; + else if (!streq(d->result, "done") && !streq(d->result, "skipped")) + r = -EIO; + + return r; +} + +int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet) { + 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); + /* 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)); + } + + free(d->name); + d->name = NULL; + + free(d->result); + d->result = NULL; + } + + 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); +} diff --git a/src/libsystemd/sd-bus/bus-util.h b/src/libsystemd/sd-bus/bus-util.h index 060988229..31e7fadbb 100644 --- a/src/libsystemd/sd-bus/bus-util.h +++ b/src/libsystemd/sd-bus/bus-util.h @@ -201,3 +201,12 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(sd_bus_track*, sd_bus_track_unref); int bus_maybe_reply_error(sd_bus_message *m, int r, sd_bus_error *error); int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignment); + +typedef struct BusWaitForJobs BusWaitForJobs; + +int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret); +void bus_wait_for_jobs_free(BusWaitForJobs *d); +int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path); +int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet); + +DEFINE_TRIVIAL_CLEANUP_FUNC(BusWaitForJobs*, bus_wait_for_jobs_free); diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index 17fbe51f9..b0c153349 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -1417,6 +1417,76 @@ static int read_only_image(int argc, char *argv[], void *userdata) { return 0; } +static int start_machine(int argc, char *argv[], void *userdata) { + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_oom(); + + for (i = 1; i < argc; i++) { + _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL; + _cleanup_free_ char *e = NULL, *unit = NULL; + const char *object; + + if (!machine_name_is_valid(argv[i])) { + log_error("Invalid machine name %s.", argv[i]); + return -EINVAL; + } + + e = unit_name_escape(argv[i]); + if (!e) + return log_oom(); + + unit = unit_name_build("systemd-nspawn", e, ".service"); + if (!unit) + return log_oom(); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartUnit"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_set_allow_interactive_authorization(m, true); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "ss", unit, "fail"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed to start unit: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "o", &object); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_add(w, object); + if (r < 0) + return log_oom(); + } + + r = bus_wait_for_jobs(w, false); + if (r < 0) + return r; + + return 0; +} + static int help(int argc, char *argv[], void *userdata) { printf("%s [OPTIONS...] {COMMAND} ...\n\n" @@ -1440,6 +1510,7 @@ static int help(int argc, char *argv[], void *userdata) { " status NAME... Show VM/container details\n" " show NAME... Show properties of one or more VMs/containers\n" " login NAME Get a login prompt on a container\n" + " start NAME... Start container as a service\n" " poweroff NAME... Power off one or more containers\n" " reboot NAME... Reboot one or more containers\n" " terminate NAME... Terminate one or more VMs/containers\n" @@ -1594,6 +1665,7 @@ static int machinectl_main(int argc, char *argv[], sd_bus *bus) { { "rename", 3, 3, 0, rename_image }, { "clone", 3, 3, 0, clone_image }, { "read-only", 2, 3, 0, read_only_image }, + { "start", 2, VERB_ANY, 0, start_machine }, {} }; diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index abe23ec23..060bfe28e 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -2406,197 +2406,6 @@ static int unit_find_paths(sd_bus *bus, return r; } -typedef struct WaitData { - Set *set; - - char *name; - char *result; -} WaitData; - -static int wait_filter(sd_bus *bus, sd_bus_message *m, void *data, sd_bus_error *error) { - WaitData *d = data; - - assert(bus); - assert(m); - assert(d); - - log_debug("Got D-Bus request: %s.%s() on %s", - sd_bus_message_get_interface(m), - sd_bus_message_get_member(m), - sd_bus_message_get_path(m)); - - if (sd_bus_message_is_signal(m, "org.freedesktop.DBus.Local", "Disconnected")) { - log_error("Warning! D-Bus connection terminated."); - sd_bus_close(bus); - } else if (sd_bus_message_is_signal(m, "org.freedesktop.systemd1.Manager", "JobRemoved")) { - uint32_t id; - const char *path, *result, *unit; - char *ret; - int r; - - r = sd_bus_message_read(m, "uoss", &id, &path, &unit, &result); - if (r >= 0) { - ret = set_remove(d->set, (char*) path); - if (!ret) - return 0; - - free(ret); - - if (!isempty(result)) - d->result = strdup(result); - - if (!isempty(unit)) - d->name = strdup(unit); - - return 0; - } -#ifndef NOLEGACY - r = sd_bus_message_read(m, "uos", &id, &path, &result); - if (r >= 0) { - ret = set_remove(d->set, (char*) path); - if (!ret) - return 0; - - free(ret); - - if (*result) - d->result = strdup(result); - - return 0; - } -#endif - - bus_log_parse_error(r); - } - - return 0; -} - -static int enable_wait_for_jobs(sd_bus *bus) { - int r; - - assert(bus); - - r = sd_bus_add_match( - bus, - NULL, - "type='signal'," - "sender='org.freedesktop.systemd1'," - "interface='org.freedesktop.systemd1.Manager'," - "member='JobRemoved'," - "path='/org/freedesktop/systemd1'", - NULL, NULL); - if (r < 0) { - log_error("Failed to add match"); - return -EIO; - } - - /* This is slightly dirty, since we don't undo the match registrations. */ - 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 check_wait_response(WaitData *d) { - int r = 0; - - assert(d->result); - - if (!arg_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("Job for %s invalid.", 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) { - bool quotes; - - quotes = chars_intersect(d->name, SHELL_NEED_QUOTES); - - log_error("Job for %s failed. See \"systemctl status %s%s%s\" and \"journalctl -xe\" for details.", - d->name, - quotes ? "'" : "", d->name, quotes ? "'" : ""); - } 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 = -ENOTSUP; - else if (!streq(d->result, "done") && !streq(d->result, "skipped")) - r = -EIO; - - return r; -} - -static int wait_for_jobs(sd_bus *bus, Set *s) { - _cleanup_bus_slot_unref_ sd_bus_slot *slot = NULL; - WaitData d = { .set = s }; - int r = 0, q; - - assert(bus); - assert(s); - - q = sd_bus_add_filter(bus, &slot, wait_filter, &d); - if (q < 0) - return log_oom(); - - while (!set_isempty(s)) { - q = bus_process_wait(bus); - if (q < 0) - return log_error_errno(q, "Failed to wait for response: %m"); - - if (d.result) { - q = check_wait_response(&d); - /* Return the first error as it is most likely to be - * meaningful. */ - if (q < 0 && r == 0) - r = q; - log_debug("Got result %s/%s for job %s", - strna(d.result), strerror(-q), strna(d.name)); - } - - free(d.name); - d.name = NULL; - - free(d.result); - d.result = NULL; - } - - return r; -} - static int check_one_unit(sd_bus *bus, const char *name, const char *good_states, bool quiet) { _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; _cleanup_free_ char *n = NULL, *state = NULL; @@ -2761,7 +2570,7 @@ static int start_unit_one( const char *name, const char *mode, sd_bus_error *error, - Set *s) { + BusWaitForJobs *w) { _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL; const char *path; @@ -2814,15 +2623,9 @@ static int start_unit_one( if (need_daemon_reload(bus, name) > 0) warn_unit_file_changed(name); - if (s) { - char *p; - - p = strdup(path); - if (!p) - return log_oom(); - - log_debug("Adding %s to the set", p); - r = set_consume(s, p); + if (w) { + log_debug("Adding %s to the set", path); + r = bus_wait_for_jobs_add(w, path); if (r < 0) return log_oom(); } @@ -2911,9 +2714,9 @@ static enum action verb_to_action(const char *verb) { } static int start_unit(sd_bus *bus, char **args) { - _cleanup_set_free_free_ Set *s = NULL; - _cleanup_strv_free_ char **names = NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; const char *method, *mode, *one_name, *suffix = NULL; + _cleanup_strv_free_ char **names = NULL; char **name; int r = 0; @@ -2952,20 +2755,16 @@ static int start_unit(sd_bus *bus, char **args) { } if (!arg_no_block) { - r = enable_wait_for_jobs(bus); + r = bus_wait_for_jobs_new(bus, &w); if (r < 0) return log_error_errno(r, "Could not watch jobs: %m"); - - s = set_new(&string_hash_ops); - if (!s) - return log_oom(); } STRV_FOREACH(name, names) { _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; int q; - q = start_unit_one(bus, method, *name, mode, &error, s); + q = start_unit_one(bus, method, *name, mode, &error, w); if (r >= 0 && q < 0) r = translate_bus_error_to_exit_status(q, &error); } @@ -2973,7 +2772,7 @@ static int start_unit(sd_bus *bus, char **args) { if (!arg_no_block) { int q; - q = wait_for_jobs(bus, s); + q = bus_wait_for_jobs(w, arg_quiet); if (q < 0) return q;