It helps editing units by either creating a drop-in file, like /etc/systemd/system/my.service.d/amendments.conf, or by copying the original unit from /usr/lib/systemd/ to /etc/systemd/ if the --full option is specified. Then it invokes an editor to the related files and daemon-reload is invoked when the editor exited successfully.
See https://bugzilla.redhat.com/show_bug.cgi?id=906824 --- TODO | 2 - man/journalctl.xml | 6 +- man/less-variables.xml | 40 ++- man/localectl.xml | 6 +- man/loginctl.xml | 6 +- man/machinectl.xml | 6 +- man/systemctl.xml | 49 +++- man/systemd-analyze.xml | 6 +- man/timedatectl.xml | 6 +- src/systemctl/systemctl.c | 650 +++++++++++++++++++++++++++++++++------------- 10 files changed, 564 insertions(+), 213 deletions(-) diff --git a/TODO b/TODO index 3206420..cc8d8c4 100644 --- a/TODO +++ b/TODO @@ -66,8 +66,6 @@ Features: * systemctl: if it fails, show log output? -* maybe add "systemctl edit" that copies unit files from /usr/lib/systemd/system to /etc/systemd/system and invokes vim on them - * dbus: add new message hdr field for allowing interactive auth, write spec for it. update dbus spec to mandate that unknown flags *must* be ignored... * maybe introduce AssertXYZ= similar to ConditionXYZ= that causes a unit to fail (instead of skipping it) if some condition is not true... diff --git a/man/journalctl.xml b/man/journalctl.xml index 7fb6afc..d36889f 100644 --- a/man/journalctl.xml +++ b/man/journalctl.xml @@ -891,7 +891,11 @@ failure code is returned.</para> </refsect1> - <xi:include href="less-variables.xml" /> + <refsect1> + <title>Environment</title> + + <xi:include href="less-variables.xml" /> + </refsect1> <refsect1> <title>Examples</title> diff --git a/man/less-variables.xml b/man/less-variables.xml index 09cbd42..1b8aae0 100644 --- a/man/less-variables.xml +++ b/man/less-variables.xml @@ -2,28 +2,24 @@ <!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd"> -<refsect1> - <title>Environment</title> +<variablelist class='environment-variables'> + <varlistentry> + <term><varname>$SYSTEMD_PAGER</varname></term> - <variablelist class='environment-variables'> - <varlistentry> - <term><varname>$SYSTEMD_PAGER</varname></term> + <listitem><para>Pager to use when + <option>--no-pager</option> is not given; + overrides <varname>$PAGER</varname>. Setting + this to an empty string or the value + <literal>cat</literal> is equivalent to passing + <option>--no-pager</option>.</para></listitem> + </varlistentry> - <listitem><para>Pager to use when - <option>--no-pager</option> is not given; - overrides <varname>$PAGER</varname>. Setting - this to an empty string or the value - <literal>cat</literal> is equivalent to passing - <option>--no-pager</option>.</para></listitem> - </varlistentry> + <varlistentry> + <term><varname>$SYSTEMD_LESS</varname></term> - <varlistentry> - <term><varname>$SYSTEMD_LESS</varname></term> - - <listitem><para>Override the default - options passed to - <command>less</command> - (<literal>FRSXMK</literal>).</para></listitem> - </varlistentry> - </variablelist> -</refsect1> + <listitem><para>Override the default + options passed to + <command>less</command> + (<literal>FRSXMK</literal>).</para></listitem> + </varlistentry> +</variablelist> diff --git a/man/localectl.xml b/man/localectl.xml index 38e73c7..7ae6c60 100644 --- a/man/localectl.xml +++ b/man/localectl.xml @@ -223,7 +223,11 @@ code otherwise.</para> </refsect1> - <xi:include href="less-variables.xml" /> + <refsect1> + <title>Environment</title> + + <xi:include href="less-variables.xml" /> + </refsect1> <refsect1> <title>See Also</title> diff --git a/man/loginctl.xml b/man/loginctl.xml index 749db92..4754790 100644 --- a/man/loginctl.xml +++ b/man/loginctl.xml @@ -438,7 +438,11 @@ code otherwise.</para> </refsect1> - <xi:include href="less-variables.xml" /> + <refsect1> + <title>Environment</title> + + <xi:include href="less-variables.xml" /> + </refsect1> <refsect1> <title>See Also</title> diff --git a/man/machinectl.xml b/man/machinectl.xml index 2f2e257..b95b7fe 100644 --- a/man/machinectl.xml +++ b/man/machinectl.xml @@ -281,7 +281,11 @@ code otherwise.</para> </refsect1> - <xi:include href="less-variables.xml" /> + <refsect1> + <title>Environment</title> + + <xi:include href="less-variables.xml" /> + </refsect1> <refsect1> <title>See Also</title> diff --git a/man/systemctl.xml b/man/systemctl.xml index 61a23de..44dc7cb 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -1150,6 +1150,31 @@ kobject-uevent 1 systemd-udevd-kernel.socket systemd-udevd.service <filename>default.target</filename> to the given unit.</para> </listitem> </varlistentry> + + <varlistentry> + <term><command>edit <replaceable>NAME</replaceable>...</command></term> + + <listitem> + <para>Edit one or more unit files, as specified on the command + line.</para> + + <para>Depending on whether <option>--system</option> (the default), + <option>--user</option>, or <option>--global</option> is specified, + this create a drop-in file for each units either for the system, + for the calling user or for all futures logins of all users. Then + the editor is invoked on them (see section "Environment" below).</para> + + <para>If <option>--full</option> is specified, this will copy the original + units instead of creating drop-in files.</para> + + <para>After the units have been edited, the systemd configuration is + reloaded (in a way that is equivalent to <command>daemon-reload</command>), + but it does not restart or reload the units.</para> + + <para>Note that this command cannot be used with <option>--runtime</option> or + to remotely edit units.</para> + </listitem> + </varlistentry> </variablelist> </refsect2> @@ -1559,7 +1584,27 @@ kobject-uevent 1 systemd-udevd-kernel.socket systemd-udevd.service code otherwise.</para> </refsect1> - <xi:include href="less-variables.xml" /> + <refsect1> + <title>Environment</title> + + <variablelist class='environment-variables'> + <varlistentry> + <term><varname>$SYSTEMD_EDITOR</varname></term> + + <listitem><para>Editor to use when editing units; overrides + <varname>$EDITOR</varname> and <varname>$VISUAL</varname>. If neither + <varname>$SYSTEMD_EDITOR</varname> nor <varname>$EDITOR</varname> nor + <varname>$VISUAL</varname> are present or if it is set to an empty + string or if their execution failed, systemctl will try to execute well + known editors in this order: + <citerefentry><refentrytitle>nano</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry><refentrytitle>vim</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry><refentrytitle>vi</refentrytitle><manvolnum>1</manvolnum></citerefentry>. + </para></listitem> + </varlistentry> + </variablelist> + <xi:include href="less-variables.xml" /> + </refsect1> <refsect1> <title>See Also</title> @@ -1572,7 +1617,7 @@ kobject-uevent 1 systemd-udevd-kernel.socket systemd-udevd.service <citerefentry><refentrytitle>systemd.resource-management</refentrytitle><manvolnum>5</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>7</manvolnum></citerefentry>, <citerefentry project='man-pages'><refentrytitle>wall</refentrytitle><manvolnum>1</manvolnum></citerefentry>, - <citerefentry><refentrytitle>systemd.preset</refentrytitle><manvolnum>5</manvolnum></citerefentry> + <citerefentry><refentrytitle>systemd.preset</refentrytitle><manvolnum>5</manvolnum></citerefentry>, <citerefentry><refentrytitle>glob</refentrytitle><manvolnum>7</manvolnum></citerefentry> </para> </refsect1> diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml index 073e807..0dd21a5 100644 --- a/man/systemd-analyze.xml +++ b/man/systemd-analyze.xml @@ -383,7 +383,11 @@ Service b@0.service not loaded, b.socket cannot be started. </example> </refsect1> - <xi:include href="less-variables.xml" /> + <refsect1> + <title>Environment</title> + + <xi:include href="less-variables.xml" /> + </refsect1> <refsect1> <title>See Also</title> diff --git a/man/timedatectl.xml b/man/timedatectl.xml index f3edb8d..849cc06 100644 --- a/man/timedatectl.xml +++ b/man/timedatectl.xml @@ -197,7 +197,11 @@ code otherwise.</para> </refsect1> - <xi:include href="less-variables.xml" /> + <refsect1> + <title>Environment</title> + + <xi:include href="less-variables.xml" /> + </refsect1> <refsect1> <title>Examples</title> diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 28eaa6a..3721610 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -72,6 +72,8 @@ #include "bus-message.h" #include "bus-error.h" #include "bus-errors.h" +#include "copy.h" +#include "mkdir.h" static char **arg_types = NULL; static char **arg_states = NULL; @@ -1965,28 +1967,18 @@ static int set_default(sd_bus *bus, char **args) { r = 0; } else { - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL, *m = NULL; + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - r = sd_bus_message_new_method_call( + r = sd_bus_call_method( bus, - &m, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", - "SetDefaultTarget"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "sb", unit, 1); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, &reply); + "SetDefaultTarget", + &error, + &reply, + "sb", unit, true); if (r < 0) { log_error("Failed to set default target: %s", bus_error_message(&error, -r)); return r; @@ -2153,7 +2145,6 @@ static int list_jobs(sd_bus *bus, char **args) { static int cancel_job(sd_bus *bus, char **args) { _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; char **name; - int r = 0; assert(args); @@ -2161,43 +2152,31 @@ static int cancel_job(sd_bus *bus, char **args) { return daemon_reload(bus, args); STRV_FOREACH(name, args+1) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; uint32_t id; - int q; + int r; - q = safe_atou32(*name, &id); - if (q < 0) { - log_error("Failed to parse job id \"%s\": %s", *name, strerror(-q)); - return q; + r = safe_atou32(*name, &id); + if (r < 0) { + log_error("Failed to parse job id \"%s\": %s", *name, strerror(-r)); + return r; } - q = sd_bus_message_new_method_call( + r = sd_bus_call_method( bus, - &m, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", - "CancelJob"); - if (q < 0) - return bus_log_create_error(q); - - q = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); - if (q < 0) - return bus_log_create_error(1); - - q = sd_bus_message_append(m, "u", id); - if (q < 0) - return bus_log_create_error(q); - - q = sd_bus_call(bus, m, 0, &error, NULL); - if (q < 0) { - log_error("Failed to cancel job %"PRIu32": %s", id, bus_error_message(&error, q)); - if (r == 0) - r = q; + "CancelJob", + &error, + NULL, + "u", id); + if (r < 0) { + log_error("Failed to cancel job %"PRIu32": %s", id, bus_error_message(&error, r)); + return r; } } - return r; + return 0; } static int need_daemon_reload(sd_bus *bus, const char *unit) { @@ -2590,7 +2569,7 @@ static int start_unit_one( sd_bus_error *error, Set *s) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL; + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; const char *path; int r; @@ -2600,26 +2579,15 @@ static int start_unit_one( assert(error); log_debug("Calling manager for %s on %s, %s", method, name, mode); - - r = sd_bus_message_new_method_call( + r = sd_bus_call_method( bus, - &m, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", - method); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "ss", name, mode); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, error, &reply); + method, + error, + &reply, + "ss", name, mode); if (r < 0) { const char *verb; @@ -2861,7 +2829,7 @@ static int reboot_with_logind(sd_bus *bus, enum action a) { method, &error, NULL, - "b", arg_ask_password); + "b", true); if (r < 0) log_error("Failed to execute operation: %s", bus_error_message(&error, r)); @@ -3084,29 +3052,18 @@ static int kill_unit(sd_bus *bus, char **args) { log_error("Failed to expand names: %s", strerror(-r)); STRV_FOREACH(name, names) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - - q = sd_bus_message_new_method_call( + q = sd_bus_call_method( bus, - &m, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", - "KillUnit"); - if (q < 0) - return bus_log_create_error(q); - - q = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); - if (q < 0) - return bus_log_create_error(q); - - q = sd_bus_message_append(m, "ssi", *names, arg_kill_who, arg_signal); - if (q < 0) - return bus_log_create_error(q); - - q = sd_bus_call(bus, m, 0, &error, NULL); + "KillUnit", + &error, + NULL, + "ssi", *names, arg_kill_who, arg_signal); if (q < 0) { - log_error("Failed to kill unit %s: %s", *names, bus_error_message(&error, q)); + log_error("Failed to kill unit %s: %s", + *names, bus_error_message(&error, r)); if (r == 0) r = q; } @@ -4606,10 +4563,6 @@ static int set_property(sd_bus *bus, char **args) { if (r < 0) return bus_log_create_error(r); - r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); - if (r < 0) - return bus_log_create_error(r); - n = unit_name_mangle(args[1], MANGLE_NOGLOB); if (!n) return log_oom(); @@ -4651,7 +4604,7 @@ static int set_property(sd_bus *bus, char **args) { static int snapshot(sd_bus *bus, char **args) { _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL; + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; _cleanup_free_ char *n = NULL, *id = NULL; const char *path; int r; @@ -4663,25 +4616,15 @@ static int snapshot(sd_bus *bus, char **args) { if (!n) return log_oom(); - r = sd_bus_message_new_method_call( + r = sd_bus_call_method( bus, - &m, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", - "CreateSnapshot"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "sb", n, false); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, &reply); + "CreateSnapshot", + &error, + &reply, + "sb", n, false); if (r < 0) { log_error("Failed to create snapshot: %s", bus_error_message(&error, r)); return r; @@ -4714,7 +4657,7 @@ static int delete_snapshot(sd_bus *bus, char **args) { _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_strv_free_ char **names = NULL; char **name; - int r; + int r, q; assert(args); @@ -4723,30 +4666,18 @@ static int delete_snapshot(sd_bus *bus, char **args) { log_error("Failed to expand names: %s", strerror(-r)); STRV_FOREACH(name, names) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - int q; - - q = sd_bus_message_new_method_call( + q = sd_bus_call_method( bus, - &m, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", - "RemoveSnapshot"); - if (q < 0) - return bus_log_create_error(q); - - q = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); - if (q < 0) - return bus_log_create_error(q); - - q = sd_bus_message_append(m, "s", *name); - if (q < 0) - return bus_log_create_error(q); - - q = sd_bus_call(bus, m, 0, &error, NULL); + "RemoveSnapshot", + &error, + NULL, + "s", *name); if (q < 0) { - log_error("Failed to remove snapshot %s: %s", *name, bus_error_message(&error, q)); + log_error("Failed to remove snapshot %s: %s", + *name, bus_error_message(&error, r)); if (r == 0) r = q; } @@ -4757,7 +4688,6 @@ static int delete_snapshot(sd_bus *bus, char **args) { static int daemon_reload(sd_bus *bus, char **args) { _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; const char *method; int r; @@ -4781,21 +4711,16 @@ static int daemon_reload(sd_bus *bus, char **args) { /* "daemon-reload" */ "Reload"; } - r = sd_bus_message_new_method_call( + r = sd_bus_call_method( bus, - &m, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", - method); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); - if (r < 0) - return bus_log_create_error(r); + method, + &error, + NULL, + NULL); - r = sd_bus_call(bus, m, 0, &error, NULL); if (r == -ENOENT && arg_action != ACTION_SYSTEMCTL) /* There's always a fallback possible for * legacy actions. */ @@ -4824,29 +4749,18 @@ static int reset_failed(sd_bus *bus, char **args) { log_error("Failed to expand names: %s", strerror(-r)); STRV_FOREACH(name, names) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - - q = sd_bus_message_new_method_call( + q = sd_bus_call_method( bus, - &m, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", - "ResetFailedUnit"); - if (q < 0) - return bus_log_create_error(q); - - q = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); - if (q < 0) - return bus_log_create_error(q); - - q = sd_bus_message_append(m, "s", *name); - if (q < 0) - return bus_log_create_error(q); - - q = sd_bus_call(bus, m, 0, &error, NULL); + "ResetFailedUnit", + &error, + NULL, + "s", *name); if (q < 0) { - log_error("Failed to reset failed state of unit %s: %s", *name, bus_error_message(&error, q)); + log_error("Failed to reset failed state of unit %s: %s", + *name, bus_error_message(&error, r)); if (r == 0) r = q; } @@ -4977,10 +4891,6 @@ static int set_environment(sd_bus *bus, char **args) { if (r < 0) return bus_log_create_error(r); - r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); - if (r < 0) - return bus_log_create_error(r); - r = sd_bus_message_append_strv(m, args + 1); if (r < 0) return bus_log_create_error(r); @@ -5012,10 +4922,6 @@ static int import_environment(sd_bus *bus, char **args) { if (r < 0) return bus_log_create_error(r); - r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); - if (r < 0) - return bus_log_create_error(r); - if (strv_isempty(args + 1)) r = sd_bus_message_append_strv(m, environ); else { @@ -5327,10 +5233,6 @@ static int enable_unit(sd_bus *bus, char **args) { if (r < 0) return bus_log_create_error(r); - r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); - 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); @@ -5446,15 +5348,23 @@ static int add_dependency(sd_bus *bus, char **args) { if (r < 0) return bus_log_create_error(r); - r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); + r = sd_bus_message_append_strv(m, names); if (r < 0) return bus_log_create_error(r); - r = sd_bus_message_append_strv(m, names); + r = sd_bus_message_append(m, "s", target); if (r < 0) return bus_log_create_error(r); - r = sd_bus_message_append(m, "ssbb", target, unit_dependency_to_string(dep), arg_runtime, arg_force); + r = sd_bus_message_append(m, "s", unit_dependency_to_string(dep)); + 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); + + r = sd_bus_message_append(m, "b", arg_force); if (r < 0) return bus_log_create_error(r); @@ -5496,33 +5406,21 @@ static int preset_all(sd_bus *bus, char **args) { r = 0; } else { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL; + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - r = sd_bus_message_new_method_call( + r = sd_bus_call_method( bus, - &m, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", - "PresetAllUnitFiles"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, + "PresetAllUnitFiles", + &error, + &reply, "sbb", unit_file_preset_mode_to_string(arg_preset_mode), arg_runtime, arg_force); - 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 execute operation: %s", bus_error_message(&error, r)); return r; @@ -5642,6 +5540,393 @@ static int is_system_running(sd_bus *bus, char **args) { return streq(state, "running") ? EXIT_SUCCESS : EXIT_FAILURE; } +static int unit_file_find_path(LookupPaths *lp, const char *unit_name, char **unit_path) { + char **p; + + assert(lp); + assert(unit_name); + assert(unit_path); + + STRV_FOREACH(p, lp->unit_path) { + char *path; + + path = strjoin(*p, "/", unit_name, NULL); + if (!path) + return log_oom(); + + if (access(path, F_OK) == 0) { + *unit_path = path; + return 1; + } + + free(path); + } + + return 0; +} + +static int unit_file_drop_in(const char *unit_name, const char *config_home, char **new_path) { + char *tmp_path; + int r; + + assert(unit_name); + assert(new_path); + + switch (arg_scope) { + case UNIT_FILE_SYSTEM: + tmp_path = strjoin(SYSTEM_CONFIG_UNIT_PATH, "/", unit_name, ".d/amendments.conf", NULL); + break; + case UNIT_FILE_GLOBAL: + tmp_path = strjoin(USER_CONFIG_UNIT_PATH, "/", unit_name, ".d/amendments.conf", NULL); + break; + case UNIT_FILE_USER: + assert(config_home); + tmp_path = strjoin(config_home, "/", unit_name, ".d/amendments.conf", NULL); + break; + default: + assert_not_reached("Invalid scope"); + } + if (!tmp_path) + return log_oom(); + + r = mkdir_parents(tmp_path, 0755); + if (r < 0) { + log_error("Failed to create directories for %s: %s", tmp_path, strerror(-r)); + free(tmp_path); + return r; + } + + *new_path = tmp_path; + + return 0; +} + +static int unit_file_copy_if_needed(const char *unit_name, const char *fragment_path, char **new_path) { + char *tmp_path; + int r; + + assert(fragment_path); + assert(unit_name); + assert(new_path); + + /* If it's a unit for the --user scope there is no need to copy it, it's already in the right directory. + * Same if this is --system/--global scope and the file is in {SYSTEM,USER}_CONFIG_UNIT_PATH + */ + if (arg_scope == UNIT_FILE_USER + || startswith(fragment_path, SYSTEM_CONFIG_UNIT_PATH) + || startswith(fragment_path, USER_CONFIG_UNIT_PATH)) { + *new_path = strdup(fragment_path); + if (!*new_path) + return log_oom(); + return 0; + } + + switch (arg_scope) { + case UNIT_FILE_SYSTEM: + tmp_path = strjoin(SYSTEM_CONFIG_UNIT_PATH, "/", unit_name, NULL); + break; + case UNIT_FILE_GLOBAL: + tmp_path = strjoin(USER_CONFIG_UNIT_PATH, "/", unit_name, NULL); + break; + default: + assert_not_reached("Invalid scope"); + } + if (!tmp_path) + return log_oom(); + + if (access(tmp_path, F_OK) == 0) { + char response; + + r = ask_char(&response, "yn", "%s already exists, are you sure to overwrite it with %s? [(y)es, (n)o] ", tmp_path, fragment_path); + if (r < 0) { + free(tmp_path); + return r; + } + if (response != 'y') { + log_warning("%s ignored", unit_name); + free(tmp_path); + return -1; + } + } + + r = mkdir_parents(tmp_path, 0755); + if (r < 0) { + log_error("Failed to create directories for %s: %s", tmp_path, strerror(-r)); + free(tmp_path); + return r; + } + + r = copy_file(fragment_path, tmp_path, 0, 0644); + if (r < 0) { + log_error("Failed to copy %s to %s: %s", fragment_path, tmp_path, strerror(-r)); + free(tmp_path); + return r; + } + + *new_path = tmp_path; + + return 0; +} + +static int get_editors(char ***editors) { + char **tmp_editors = strv_new("nano", "vim", "vi", NULL); + char *editor; + + /* SYSTEMD_EDITOR takes precedence over EDITOR which takes precedence over VISUAL + * If neither SYSTEMD_EDITOR nor EDITOR nor VISUAL are present, + * we try to execute well known editors + */ + editor = getenv("SYSTEMD_EDITOR"); + if (!editor) + editor = getenv("EDITOR"); + if (!editor) + editor = getenv("VISUAL"); + + if (editor) { + int r; + + editor = strdup(editor); + if (!editor) + return log_oom(); + + r = strv_consume_prepend(&tmp_editors, editor); + if (r < 0) + return log_oom(); + } + + *editors = tmp_editors; + + return 0; +} + +static int run_editor(char **paths) { + pid_t pid; + siginfo_t status; + int r; + + assert(paths); + + pid = fork(); + if (pid < 0) { + log_error("Failed to fork: %m"); + return -errno; + } + + if (pid == 0) { + _cleanup_strv_free_ char **editors = NULL; + char *editor; + char **p; + + r = get_editors(&editors); + if (r < 0) { + _exit(EXIT_FAILURE); + } + + STRV_FOREACH(p, editors) { + _cleanup_strv_free_ char **args = NULL; + + editor = strdup(*p); + if (!editor) { + log_oom(); + _exit(EXIT_FAILURE); + } + + args = strv_copy(paths); + if (!args) { + log_oom(); + _exit(EXIT_FAILURE); + } + + r = strv_consume_prepend(&args, editor); + if (r < 0) { + log_oom(); + _exit(EXIT_FAILURE); + } + + execvp(editor, args); + /* We do not fail if the editor doesn't exist + * because we want to try each one of them before + * failing. + */ + if (errno != ENOENT) { + log_error("Failed to execute %s: %m", editor); + _exit(EXIT_FAILURE); + } + } + + log_error("Cannot edit unit(s): No editor available. Please set either SYSTEMD_EDITOR or EDITOR or VISUAL environment variable"); + _exit(EXIT_FAILURE); + } + + r = wait_for_terminate(pid, &status); + if (r < 0) { + log_error("Failed to wait for child: %s", strerror(-r)); + return r; + } + + return WIFEXITED(status) ? WEXITSTATUS(status) : -1; +} + +static int find_units_path(sd_bus *bus, char **names, char ***paths) { + _cleanup_free_ char *config_home = NULL; + _cleanup_free_ char *unit = NULL; + char **name; + int r; + + assert(names); + assert(paths); + + if (arg_scope == UNIT_FILE_USER) { + r = user_config_home(&config_home); + if (r < 0) + return log_oom(); + + if (r == 0) { + log_error("Cannot edit units for the user instance: home directory unknown"); + return -1; + } + } + + if (!bus || avoid_bus()) { + _cleanup_lookup_paths_free_ LookupPaths lp = {}; + + /* If there is no bus, we try to find the units by testing each available directory + * according to the scope. + */ + r = lookup_paths_init(&lp, + arg_scope == UNIT_FILE_SYSTEM ? SYSTEMD_SYSTEM : SYSTEMD_USER, + arg_scope == UNIT_FILE_USER, + arg_root, + NULL, NULL, NULL); + if (r < 0) { + log_error("Cannot get lookup paths: %s", strerror(-r)); + return r; + } + + STRV_FOREACH(name, names) { + _cleanup_free_ char *path = NULL; + char *new_path; + + r = unit_file_find_path(&lp, *name, &path); + if (r < 0) + return r; + if (r == 0) { + log_warning("%s ignored: not found", *name); + continue; + } + + if (arg_full) + r = unit_file_copy_if_needed(*name, path, &new_path); + else + r = unit_file_drop_in(*name, config_home, &new_path); + + if (r < 0) + continue; + + r = strv_push(paths, new_path); + if (r < 0) + return log_oom(); + } + } else { + STRV_FOREACH(name, names) { + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *fragment_path = NULL; + char *new_path; + + unit = unit_dbus_path_from_name(*name); + if (!unit) + return log_oom(); + + if (need_daemon_reload(bus, *name) > 0) { + log_warning("%s ignored: unit file changed on disk. Run 'systemctl%s daemon-reload'.", + *name, arg_scope == UNIT_FILE_SYSTEM ? "" : " --user"); + continue; + } + + r = sd_bus_get_property_string( + bus, + "org.freedesktop.systemd1", + unit, + "org.freedesktop.systemd1.Unit", + "FragmentPath", + &error, + &fragment_path); + if (r < 0) { + log_warning("Failed to get FragmentPath: %s", bus_error_message(&error, r)); + continue; + } + + if (isempty(fragment_path)) { + log_warning("%s ignored: not found", *name); + continue; + } + + if (arg_full) + r = unit_file_copy_if_needed(*name, fragment_path, &new_path); + else + r = unit_file_drop_in(*name, config_home, &new_path); + if (r < 0) + continue; + + r = strv_push(paths, new_path); + if (r < 0) + return log_oom(); + } + } + + return 0; +} + +static int edit(sd_bus *bus, char **args) { + _cleanup_strv_free_ char **names = NULL; + _cleanup_strv_free_ char **paths = NULL; + int r; + + assert(args); + + if (!on_tty()) + return 0; + + if (arg_transport != BUS_TRANSPORT_LOCAL) { + log_error("Cannot remotely edit units"); + return -EINVAL; + } + + if (arg_runtime) { + log_error("Cannot edit runtime units"); + return -EINVAL; + } + + r = expand_names(bus, args + 1, NULL, &names); + if (r < 0) { + log_error("Failed to expand names: %s", strerror(-r)); + return r; + } + + if (!names) { + log_error("No unit name found by expanding names"); + return -ENOENT; + } + + r = find_units_path(bus, names, &paths); + if (r < 0) + return r; + + if (strv_isempty(paths)) { + log_error("Cannot find any units to edit"); + return -ENOENT; + } + + r = run_editor(paths); + if (r < 0) + return r; + + if (!arg_no_reload) + r = daemon_reload(bus, args); + + return r; +} + static void systemctl_help(void) { pager_open_if_enabled(); @@ -5739,7 +6024,9 @@ static void systemctl_help(void) { " add-requires TARGET NAME... Add 'Requires' dependency for the target\n" " on specified one or more units\n" " get-default Get the name of the default target\n" - " set-default NAME Set the default target\n\n" + " set-default NAME Set the default target\n" + " edit NAME... Edit one or more unit files\n" + "\n" "Machine Commands:\n" " list-machines [PATTERN...] List local containers and host\n\n" "Job Commands:\n" @@ -6750,6 +7037,7 @@ static int systemctl_main(sd_bus *bus, int argc, char *argv[], int bus_error) { { "is-system-running", EQUAL, 1, is_system_running }, { "add-wants", MORE, 3, add_dependency, NOBUS }, { "add-requires", MORE, 3, add_dependency, NOBUS }, + { "edit", MORE, 2, edit, NOBUS }, {} }, *verb = verbs; -- 2.1.2 _______________________________________________ systemd-devel mailing list systemd-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/systemd-devel