portablectl: add --now and --enable to attach/detach

Add shortcuts to enable and start, or disable and stop, portable
services with a single portablectl command.
Allow to pass a filter on detach, as it's necessary to call
GetImageMetadata to get the unit names associated with an image.

Fixes #10232
This commit is contained in:
Luca Boccassi 2020-01-23 16:50:15 +00:00 committed by Zbigniew Jędrzejewski-Szmek
parent c3b41d8811
commit e2c1ddcc49
3 changed files with 246 additions and 4 deletions

View File

@ -133,11 +133,14 @@
<para>By default, after the unit files are attached the service manager's configuration is reloaded, except <para>By default, after the unit files are attached the service manager's configuration is reloaded, except
when <option>--no-reload</option> is specified (see above). This ensures that the new units made available to when <option>--no-reload</option> is specified (see above). This ensures that the new units made available to
the service manager are seen by it.</para> the service manager are seen by it.</para>
<para>If <option>--now</option> and/or <option>--enable</option> are passed, the portable service(s) are
immediately started and/or enabled after attaching the image.</para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
<term><command>detach</command> <replaceable>IMAGE</replaceable></term> <term><command>detach</command> <replaceable>IMAGE</replaceable> [<replaceable>PREFIX…</replaceable>]</term>
<listitem><para>Detaches a portable service image from the host. This undoes the operations executed by the <listitem><para>Detaches a portable service image from the host. This undoes the operations executed by the
<command>attach</command> command above, and removes the unit file copies, drop-ins and image symlink <command>attach</command> 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 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 <command>attach</command> also to files. This is a convencience feature to allow all arguments passed as <command>attach</command> also to
<command>detach</command>.</para></listitem> <command>detach</command>.</para></listitem>
<para>If <option>--now</option> and/or <option>--enable</option> 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 <command>attach</command>.</para>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
@ -311,6 +318,18 @@
contents of the image.</para></listitem> contents of the image.</para></listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><option>--enable</option></term>
<listitem><para>Immediately enable/disable the portable service after attaching/detaching.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--now</option></term>
<listitem><para>Immediately start/stop the portable service after attaching/before detaching.</para></listitem>
</varlistentry>
<xi:include href="user-system-options.xml" xpointer="host" /> <xi:include href="user-system-options.xml" xpointer="host" />
<xi:include href="user-system-options.xml" xpointer="machine" /> <xi:include href="user-system-options.xml" xpointer="machine" />

View File

@ -34,7 +34,7 @@ _portablectl() {
local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]}
local -A OPTS=( local -A OPTS=(
[STANDALONE]='-q --quiet --runtime --no-reload --cat --no-pager --no-legend [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' [ARG]='-p --profile --copy -H --host -M --machine'
) )

View File

@ -7,6 +7,7 @@
#include "alloc-util.h" #include "alloc-util.h"
#include "bus-error.h" #include "bus-error.h"
#include "bus-unit-util.h"
#include "bus-util.h" #include "bus-util.h"
#include "def.h" #include "def.h"
#include "dirent-util.h" #include "dirent-util.h"
@ -39,6 +40,8 @@ static bool arg_reload = true;
static bool arg_cat = false; static bool arg_cat = false;
static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
static const char *arg_host = NULL; 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) { static int determine_image(const char *image, bool permit_non_existing, char **ret) {
int r; int r;
@ -388,6 +391,204 @@ static int print_changes(sd_bus_message *m) {
return 0; 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) { 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_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_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); (void) maybe_reload(&bus);
print_changes(reply); print_changes(reply);
(void) maybe_enable_start(bus, reply);
return 0; 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) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
(void) maybe_stop_disable(bus, image, argv);
r = sd_bus_call_method( r = sd_bus_call_method(
bus, bus,
"org.freedesktop.portable1", "org.freedesktop.portable1",
@ -764,7 +970,8 @@ static int help(int argc, char *argv[], void *userdata) {
" list List available portable service images\n" " list List available portable service images\n"
" attach NAME|PATH [PREFIX...]\n" " attach NAME|PATH [PREFIX...]\n"
" Attach the specified portable service image\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" " inspect NAME|PATH [PREFIX...]\n"
" Show details of specified portable service image\n" " Show details of specified portable service image\n"
" is-attached NAME|PATH Query if portable service image is attached\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" " --no-reload Don't reload the system and service manager\n"
" --cat When inspecting include unit and os-release file\n" " --cat When inspecting include unit and os-release file\n"
" contents\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" "\nSee the %s for details.\n"
, program_invocation_short_name , program_invocation_short_name
, ansi_highlight() , ansi_highlight()
@ -807,6 +1018,8 @@ static int parse_argv(int argc, char *argv[]) {
ARG_RUNTIME, ARG_RUNTIME,
ARG_NO_RELOAD, ARG_NO_RELOAD,
ARG_CAT, ARG_CAT,
ARG_ENABLE,
ARG_NOW,
}; };
static const struct option options[] = { static const struct option options[] = {
@ -823,6 +1036,8 @@ static int parse_argv(int argc, char *argv[]) {
{ "runtime", no_argument, NULL, ARG_RUNTIME }, { "runtime", no_argument, NULL, ARG_RUNTIME },
{ "no-reload", no_argument, NULL, ARG_NO_RELOAD }, { "no-reload", no_argument, NULL, ARG_NO_RELOAD },
{ "cat", no_argument, NULL, ARG_CAT }, { "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; arg_cat = true;
break; break;
case ARG_ENABLE:
arg_enable = true;
break;
case ARG_NOW:
arg_now = true;
break;
case '?': case '?':
return -EINVAL; return -EINVAL;
@ -925,7 +1148,7 @@ static int run(int argc, char *argv[]) {
{ "help", VERB_ANY, VERB_ANY, 0, help }, { "help", VERB_ANY, VERB_ANY, 0, help },
{ "list", VERB_ANY, 1, VERB_DEFAULT, list_images }, { "list", VERB_ANY, 1, VERB_DEFAULT, list_images },
{ "attach", 2, VERB_ANY, 0, attach_image }, { "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 }, { "inspect", 2, VERB_ANY, 0, inspect_image },
{ "is-attached", 2, 2, 0, is_image_attached }, { "is-attached", 2, 2, 0, is_image_attached },
{ "read-only", 2, 3, 0, read_only_image }, { "read-only", 2, 3, 0, read_only_image },