diff --git a/man/portablectl.xml b/man/portablectl.xml index 88cf36ae25..d1790db2fa 100644 --- a/man/portablectl.xml +++ b/man/portablectl.xml @@ -133,11 +133,14 @@ By default, after the unit files are attached the service manager's configuration is reloaded, except when is specified (see above). This ensures that the new units made available to the service manager are seen by it. + + If and/or are passed, the portable service(s) are + immediately started and/or enabled after attaching the image. - detach IMAGE + detach IMAGE [PREFIX…] Detaches a portable service image from the host. This undoes the operations executed by the attach command above, and removes the unit file copies, drop-ins and image symlink @@ -145,6 +148,10 @@ component of it (i.e. the file or directory name itself, not the path to it) is used for finding matching unit files. This is a convencience feature to allow all arguments passed as attach also to detach. + + If and/or are passed, the portable service(s) are + immediately started and/or enabled before detaching the image. Prefix(es) are also accepted, to be used in + case the unit names do not match the image name as described in the attach. @@ -311,6 +318,18 @@ contents of the image. + + + + Immediately enable/disable the portable service after attaching/detaching. + + + + + + Immediately start/stop the portable service after attaching/before detaching. + + diff --git a/shell-completion/bash/portablectl b/shell-completion/bash/portablectl index b60d8c5c4b..0b84d5c834 100644 --- a/shell-completion/bash/portablectl +++ b/shell-completion/bash/portablectl @@ -34,7 +34,7 @@ _portablectl() { local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} local -A OPTS=( [STANDALONE]='-q --quiet --runtime --no-reload --cat --no-pager --no-legend - --no-ask-password -h --help --version' + --no-ask-password --enable --now -h --help --version' [ARG]='-p --profile --copy -H --host -M --machine' ) diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c index 1fa6dc48c1..deaad0a0b0 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -7,6 +7,7 @@ #include "alloc-util.h" #include "bus-error.h" +#include "bus-unit-util.h" #include "bus-util.h" #include "def.h" #include "dirent-util.h" @@ -39,6 +40,8 @@ static bool arg_reload = true; static bool arg_cat = false; static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; static const char *arg_host = NULL; +static bool arg_enable = false; +static bool arg_now = false; static int determine_image(const char *image, bool permit_non_existing, char **ret) { int r; @@ -388,6 +391,204 @@ static int print_changes(sd_bus_message *m) { return 0; } +static int maybe_enable_disable(sd_bus *bus, const char *path, bool enable) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_strv_free_ char **names = NULL; + UnitFileChange *changes = NULL; + size_t n_changes = 0; + int r; + + if (!arg_enable) + return 0; + + names = strv_new(path, NULL); + if (!names) + return log_oom(); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + enable ? "EnableUnitFiles" : "DisableUnitFiles"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, names); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "b", arg_runtime); + if (r < 0) + return bus_log_create_error(r); + + if (enable) { + r = sd_bus_message_append(m, "b", false); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to %s the portable service %s: %s", + enable ? "enable" : "disable", path, bus_error_message(&error, r)); + + if (enable) { + r = sd_bus_message_skip(reply, "b"); + if (r < 0) + return bus_log_parse_error(r); + } + (void) bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes); + + return 0; +} + +static int maybe_start_stop(sd_bus *bus, const char *path, bool start) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + char *name = (char *)basename(path), *job = NULL; + int r; + + if (!arg_now) + return 0; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + start ? "StartUnit" : "StopUnit", + &error, + &reply, + "ss", name, "replace"); + if (r < 0) + return log_error_errno(r, "Failed to %s the portable service %s: %s", + start ? "start" : "stop", + path, + bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "o", &job); + if (r < 0) + return bus_log_parse_error(r); + + if (!arg_quiet) + log_info("Queued %s to %s portable service %s.", job, start ? "start" : "stop", name); + + return 0; +} + +static int maybe_enable_start(sd_bus *bus, sd_bus_message *reply) { + int r; + + if (!arg_enable && !arg_now) + return 0; + + r = sd_bus_message_rewind(reply, true); + if (r < 0) + return r; + r = sd_bus_message_enter_container(reply, 'a', "(sss)"); + if (r < 0) + return bus_log_parse_error(r); + + for (;;) { + char *type, *path, *source; + + r = sd_bus_message_read(reply, "(sss)", &type, &path, &source); + if (r < 0) + return bus_log_parse_error(r); + if (r == 0) + break; + + if (STR_IN_SET(type, "symlink", "copy") && ENDSWITH_SET(path, ".service", ".target", ".socket")) { + (void) maybe_enable_disable(bus, path, true); + (void) maybe_start_stop(bus, path, true); + } + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return r; + + return 0; +} + +static int maybe_stop_disable(sd_bus *bus, char *image, char *argv[]) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_strv_free_ char **matches = NULL; + int r; + + if (!arg_enable && !arg_now) + return 0; + + r = determine_matches(argv[1], argv + 2, true, &matches); + if (r < 0) + return r; + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.portable1", + "/org/freedesktop/portable1", + "org.freedesktop.portable1.Manager", + "GetImageMetadata"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", image); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, matches); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to inspect image metadata: %s", bus_error_message(&error, r)); + + r = sd_bus_message_skip(reply, "say"); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_enter_container(reply, 'a', "{say}"); + if (r < 0) + return bus_log_parse_error(r); + + for (;;) { + const char *name; + + r = sd_bus_message_enter_container(reply, 'e', "say"); + if (r < 0) + return bus_log_parse_error(r); + if (r == 0) + break; + + r = sd_bus_message_read(reply, "s", &name); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_skip(reply, "ay"); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + (void) maybe_start_stop(bus, name, false); + (void) maybe_enable_disable(bus, name, false); + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + return 0; +} + static int attach_image(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -439,6 +640,9 @@ static int attach_image(int argc, char *argv[], void *userdata) { (void) maybe_reload(&bus); print_changes(reply); + + (void) maybe_enable_start(bus, reply); + return 0; } @@ -459,6 +663,8 @@ static int detach_image(int argc, char *argv[], void *userdata) { (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + (void) maybe_stop_disable(bus, image, argv); + r = sd_bus_call_method( bus, "org.freedesktop.portable1", @@ -764,7 +970,8 @@ static int help(int argc, char *argv[], void *userdata) { " list List available portable service images\n" " attach NAME|PATH [PREFIX...]\n" " Attach the specified portable service image\n" - " detach NAME|PATH Detach the specified portable service image\n" + " detach NAME|PATH [PREFIX...]\n" + " Detach the specified portable service image\n" " inspect NAME|PATH [PREFIX...]\n" " Show details of specified portable service image\n" " is-attached NAME|PATH Query if portable service image is attached\n" @@ -786,6 +993,10 @@ static int help(int argc, char *argv[], void *userdata) { " --no-reload Don't reload the system and service manager\n" " --cat When inspecting include unit and os-release file\n" " contents\n" + " --enable Immediately enable/disable the portable service\n" + " after attach/detach\n" + " --now Immediately start/stop the portable service after\n" + " attach/before detach\n" "\nSee the %s for details.\n" , program_invocation_short_name , ansi_highlight() @@ -807,6 +1018,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_RUNTIME, ARG_NO_RELOAD, ARG_CAT, + ARG_ENABLE, + ARG_NOW, }; static const struct option options[] = { @@ -823,6 +1036,8 @@ static int parse_argv(int argc, char *argv[]) { { "runtime", no_argument, NULL, ARG_RUNTIME }, { "no-reload", no_argument, NULL, ARG_NO_RELOAD }, { "cat", no_argument, NULL, ARG_CAT }, + { "enable", no_argument, NULL, ARG_ENABLE }, + { "now", no_argument, NULL, ARG_NOW }, {} }; @@ -909,6 +1124,14 @@ static int parse_argv(int argc, char *argv[]) { arg_cat = true; break; + case ARG_ENABLE: + arg_enable = true; + break; + + case ARG_NOW: + arg_now = true; + break; + case '?': return -EINVAL; @@ -925,7 +1148,7 @@ static int run(int argc, char *argv[]) { { "help", VERB_ANY, VERB_ANY, 0, help }, { "list", VERB_ANY, 1, VERB_DEFAULT, list_images }, { "attach", 2, VERB_ANY, 0, attach_image }, - { "detach", 2, 2, 0, detach_image }, + { "detach", 2, VERB_ANY, 0, detach_image }, { "inspect", 2, VERB_ANY, 0, inspect_image }, { "is-attached", 2, 2, 0, is_image_attached }, { "read-only", 2, 3, 0, read_only_image },