On Wed, Oct 29, 2014 at 04:22:02PM +0100, Ronny Chevalier wrote: > It helps editing units by either creating a drop-in file, like > /etc/systemd/system/my.service.d/override.conf, or by copying the > original unit from /usr/lib/systemd/ to /etc/systemd/ if the --full > option is specified. > > It invokes an editor on temporary files related to the unit files and > if the editor exited successfully, then it renames the temporary files > to their original names (e.g. my.service or override.conf) and > daemon-reload is invoked. > > If the temporary file is empty the modification is canceled. > > See https://bugzilla.redhat.com/show_bug.cgi?id=906824 > --- > > lookup_paths_init does not concatenate root_dir, so I added a path_join with > arg_root > > TODO | 4 +- > man/less-variables.xml | 4 +- > man/systemctl.xml | 64 +++++- > src/systemctl/systemctl.c | 525 > +++++++++++++++++++++++++++++++++++++++++++++- > 4 files changed, 587 insertions(+), 10 deletions(-) > > diff --git a/TODO b/TODO > index abe89b7..1cbedd4 100644 > --- a/TODO > +++ b/TODO > @@ -84,7 +84,7 @@ 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 > +* systemctl edit: add commented help text to the end, like git commit > > * maybe introduce AssertXYZ= similar to ConditionXYZ= that causes a unit to > fail (instead of skipping it) if some condition is not true... > > @@ -776,7 +776,7 @@ External: > > * zsh shell completion: > - <command> <verb> -<TAB> should complete options, but currently does not > - - systemctl add-wants,add-requires > + - systemctl add-wants,add-requires, edit > > > Regularly: > diff --git a/man/less-variables.xml b/man/less-variables.xml > index 09cbd42..0fb4d7f 100644 > --- a/man/less-variables.xml > +++ b/man/less-variables.xml > @@ -6,7 +6,7 @@ > <title>Environment</title> > > <variablelist class='environment-variables'> > - <varlistentry> > + <varlistentry id='pager'> > <term><varname>$SYSTEMD_PAGER</varname></term> > > <listitem><para>Pager to use when > @@ -17,7 +17,7 @@ > <option>--no-pager</option>.</para></listitem> > </varlistentry> > > - <varlistentry> > + <varlistentry id='less'> > <term><varname>$SYSTEMD_LESS</varname></term> > > <listitem><para>Override the default > diff --git a/man/systemctl.xml b/man/systemctl.xml > index 7cbaa6c..26f5235 100644 > --- a/man/systemctl.xml > +++ b/man/systemctl.xml > @@ -465,7 +465,7 @@ along with systemd; If not, see > <http://www.gnu.org/licenses/>. > > <listitem> > <para>When used with <command>enable</command>, > - <command>disable</command>, > + <command>disable</command>, <command>edit</command>, > (and related commands), make changes only temporarily, so > that they are lost on the next reboot. This will have the > effect that changes are not made in subdirectories of > @@ -1150,6 +1150,43 @@ 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 a drop-in snippet or a whole replacement file if > + <option>--full</option> is specified, to extend or override the > + specified unit.</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, creates each unit
> + for the calling user or for all futures logins of all users. Then , > + the editor (see section "Environment" below) is invoked on > temporary see the "Environment" section below > + files which will be saved as their corresponding files if the > editor which will be written to the real location if the editor exits successfully. > + exited successfully.</para> > + > + <para>If <option>--full</option> is specified, this will copy the > + original units instead of creating drop-in files.</para> > + > + <para>If <option>--runtime</option> is specified, the changes > will > + be made temporarily in <filename>/run</filename> and they will be > + lost on the next reboot.</para> > + > + <para>If the temporary file is empty the modification of the > related empty upon exit > + unit is canceled</para> . > + > + <para>After the units have been edited, the systemd > configuration is drop the 'the' in 'the systemd configuration' > + reloaded (in a way that is equivalent to > <command>daemon-reload</command>), > + but it does not restart or reload the units.</para> This is confusing. I know that 'reload' refers to 'systemctl reload', but a reader might think that it refers re-reading units from disk. Maybe just skip everything after the last comma. > + > + <para>Note that this command cannot be used to remotely edit > units > + and that you cannot temporarily edit units which are in > + <filename>/etc</filename> since they take precedence over > + <filename>/run</filename>.</para> > + </listitem> > + </varlistentry> > </variablelist> > </refsect2> > > @@ -1608,7 +1645,28 @@ 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" xpointer="pager"/> > + <xi:include href="less-variables.xml" xpointer="less"/> > + </refsect1> > > <refsect1> > <title>See Also</title> > @@ -1621,7 +1679,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/src/systemctl/systemctl.c b/src/systemctl/systemctl.c > index 28eaa6a..8c3ed58 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; > @@ -5642,6 +5644,520 @@ 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 = path_join(arg_root, *p, unit_name); > + if (!path) > + return log_oom(); > + > + if (access(path, F_OK) == 0) { > + *unit_path = path; > + return 1; > + } > + > + free(path); > + } > + > + return 0; > +} > + > +static int create_edit_temp_file(const char *new_path, const char > *original_path, char **ret_tmp_fn) { > + _cleanup_close_ int fd = -1; > + int r; > + char *t; > + > + assert(new_path); > + assert(original_path); > + assert(ret_tmp_fn); > + > + t = tempfn_random(new_path); > + if (!t) > + return log_oom(); > + > + r = mkdir_parents(new_path, 0755); > + if (r < 0) { > + log_error("Failed to create directories for %s: %s", > new_path, strerror(-r)); > + return r; > + } > + > + r = copy_file(original_path, t, 0, 0644); > + if (r == -ENOENT) { > + r = touch(t); > + if (r < 0) { > + log_error("Failed to create temporary file %s: %s", > t, strerror(-r)); > + free(t); > + return r; > + } > + } else if (r < 0) { > + log_error("Failed to copy %s to %s: %s", original_path, t, > strerror(-r)); > + free(t); > + return r; > + } > + > + *ret_tmp_fn = t; > + > + return 0; > +} > + > +static int get_drop_in_to_edit(const char *unit_name, const char *user_home, > const char *user_runtime, char **ret_path) { > + _cleanup_free_ char *tmp = NULL; > + char *tmp_new_path; > + > + assert(unit_name); > + assert(ret_path); > + > + switch (arg_scope) { > + case UNIT_FILE_SYSTEM: > + if (arg_runtime) > + tmp = strjoin("/run/systemd/system/", > unit_name, ".d/override.conf", NULL); > + else > + tmp = strjoin(SYSTEM_CONFIG_UNIT_PATH "/", > unit_name, ".d/override.conf", NULL); > + break; I'd prefer to use the ternary operator: tmp = strappenda(arg_runtime ? "/run/systemd/system/" : SYSTEM_CONFIG_UNIT_PATH "/", unit_name, ".d/override.conf"); (and drop _cleanup_). > + case UNIT_FILE_GLOBAL: > + if (arg_runtime) > + tmp = strjoin("/run/systemd/user/", > unit_name, ".d/override.conf", NULL); > + else > + tmp = strjoin(USER_CONFIG_UNIT_PATH "/", > unit_name, ".d/override.conf", NULL); > + break; > + case UNIT_FILE_USER: > + assert(user_home); > + assert(user_runtime); > + > + if (arg_runtime) > + tmp = strjoin(user_runtime, "/", unit_name, > ".d/override.conf", NULL); > + else > + tmp = strjoin(user_home, "/", unit_name, > ".d/override.conf", NULL); > + break; > + default: > + assert_not_reached("Invalid scope"); > + } > + if (!tmp) > + return log_oom(); This would also become unnecessary. > + > + tmp_new_path = path_join(arg_root, tmp, NULL); > + if (!tmp_new_path) > + return log_oom(); > + > + *ret_path = tmp_new_path; > + > + return 0; > +} > + > +static int unit_file_create_drop_in(const char *unit_name, const char > *user_home, const char *user_runtime, char **ret_new_path, char > **ret_tmp_path) { > + char *tmp_new_path; > + char *tmp_tmp_path; > + int r; > + > + assert(unit_name); > + assert(ret_new_path); > + assert(ret_tmp_path); > + > + r = get_drop_in_to_edit(unit_name, user_home, user_runtime, > &tmp_new_path); > + if (r < 0) > + return r; > + > + r = create_edit_temp_file(tmp_new_path, tmp_new_path, &tmp_tmp_path); > + if (r < 0) { > + free(tmp_new_path); > + return r; > + } > + > + *ret_new_path = tmp_new_path; > + *ret_tmp_path = tmp_tmp_path; > + > + return 0; > +} > + > +static int get_copy_to_edit(const char *unit_name, const char > *fragment_path, const char *user_home, const char *user_runtime, char > **ret_path) { > + _cleanup_free_ char *tmp = NULL; > + char *tmp_new_path; > + > + assert(unit_name); > + assert(ret_path); > + > + switch (arg_scope) { > + case UNIT_FILE_SYSTEM: > + if (arg_runtime) { > + if (path_startswith(fragment_path, > "/etc/systemd/system") || > + path_startswith(fragment_path, > SYSTEM_CONFIG_UNIT_PATH)) { > + log_error("%s ignored: cannot > temporarily edit units from /etc/systemd/system/%s", > + unit_name, > + > path_equal("/etc/systemd/system/", SYSTEM_CONFIG_UNIT_PATH) ? "" : " or " > SYSTEM_CONFIG_UNIT_PATH); > + return -EINVAL; > + } > + tmp_new_path = path_join(arg_root, > "/run/systemd/system/", unit_name); > + } else > + tmp_new_path = path_join(arg_root, > SYSTEM_CONFIG_UNIT_PATH, unit_name); > + break; > + case UNIT_FILE_GLOBAL: > + if (arg_runtime) { > + if (path_startswith(fragment_path, > "/etc/systemd/user") || > + path_startswith(fragment_path, > USER_CONFIG_UNIT_PATH)) { > + log_error("%s ignored: cannot > temporarily edit units from /etc/systemd/user/%s", > + unit_name, > + > path_equal("/etc/systemd/user/", USER_CONFIG_UNIT_PATH) ? "" : " or " > USER_CONFIG_UNIT_PATH); > + return -EINVAL; > + } > + tmp_new_path = path_join(arg_root, > "/run/systemd/user/", unit_name); > + } else > + tmp_new_path = path_join(arg_root, > USER_CONFIG_UNIT_PATH, unit_name); > + break; > + case UNIT_FILE_USER: > + assert(user_home); > + assert(user_runtime); > + > + if (arg_runtime) { > + if (path_startswith(fragment_path, > "/etc/systemd/user") || > + path_startswith(fragment_path, > USER_CONFIG_UNIT_PATH) || > + path_startswith(fragment_path, > user_home)) { > + log_error("%s ignored: cannot > temporarily edit units from %s or /etc/systemd/user/%s", > + unit_name, > + user_home, > + > path_equal("/etc/systemd/user/", USER_CONFIG_UNIT_PATH) ? "" : " or " > USER_CONFIG_UNIT_PATH); > + return -EINVAL; > + } > + tmp = strjoin(user_runtime, "/", unit_name, > NULL); > + } > + else > + tmp = strjoin(user_home, "/", unit_name, > NULL); This part looks like it could use some refactoring... The same warning and the same test repeats in each case of the switch. strappenda is probably better than strjoin too. > + > + if (!tmp) > + return log_oom(); > + > + tmp_new_path = path_join(arg_root, tmp, NULL); > + break; > + default: > + assert_not_reached("Invalid scope"); > + } > + if (!tmp_new_path) > + return log_oom(); > + > + *ret_path = tmp_new_path; > + > + return 0; > +} > + > +static int unit_file_create_copy(const char *unit_name, > + const char *fragment_path, > + const char *user_home, > + const char *user_runtime, > + char **ret_new_path, > + char **ret_tmp_path) { > + char *tmp_new_path; > + char *tmp_tmp_path; > + int r; > + > + assert(fragment_path); > + assert(unit_name); > + assert(ret_new_path); > + assert(ret_tmp_path); > + > + r = get_copy_to_edit(unit_name, fragment_path, user_home, > user_runtime, &tmp_new_path); > + if (r < 0) > + return r; > + > + if (!path_equal(fragment_path, tmp_new_path) && access(tmp_new_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_new_path, fragment_path); > + if (r < 0) { > + free(tmp_new_path); > + return r; > + } > + if (response != 'y') { > + log_warning("%s ignored", unit_name); > + free(tmp_new_path); > + return -1; > + } > + } > + > + r = create_edit_temp_file(tmp_new_path, fragment_path, > &tmp_tmp_path); > + if (r < 0) { > + log_error("Failed to create temporary file for %s: %s", > tmp_new_path, strerror(-r)); > + free(tmp_new_path); > + return r; > + } > + > + *ret_new_path = tmp_new_path; > + *ret_tmp_path = tmp_tmp_path; > + > + return 0; > +} > + > +static int run_editor(char **paths) { > + pid_t pid; > + int r; > + > + assert(paths); > + > + pid = fork(); > + if (pid < 0) { > + log_error("Failed to fork: %m"); > + return -errno; > + } > + > + if (pid == 0) { > + const char **args; > + char **backup_editors = STRV_MAKE("nano", "vim", "vi"); > + char *editor; > + char **tmp_path, **original_path, **p; > + unsigned i = 1; > + size_t argc; > + > + argc = strv_length(paths)/2 + 1; > + args = newa(const char*, argc + 1); > + > + args[0] = NULL; > + STRV_FOREACH_PAIR(original_path, tmp_path, paths) { > + args[i] = *tmp_path; > + i++; > + } > + args[argc] = NULL; > + > + /* 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 (!isempty(editor)) { > + args[0] = editor; > + execvp(editor, (char* const*) args); > + } > + > + STRV_FOREACH(p, backup_editors) { > + args[0] = *p; > + execvp(*p, (char* const*) 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_and_warn("editor", pid); > + if (r < 0) { > + log_error("Failed to wait for child: %s", strerror(-r)); > + return r; > + } > + > + return r; > +} > + > +static int find_paths_to_edit(sd_bus *bus, char **names, char ***paths) { > + _cleanup_free_ char *user_home = NULL; > + _cleanup_free_ char *user_runtime = NULL; > + char **name; > + int r; > + > + assert(names); > + assert(paths); > + > + if (arg_scope == UNIT_FILE_USER) { > + r = user_config_home(&user_home); > + if (r < 0) > + return log_oom(); > + else if (r == 0) { > + log_error("Cannot edit units for the user instance: > home directory unknown"); > + return -1; > + } > + > + r = user_runtime_dir(&user_runtime); > + if (r < 0) > + return log_oom(); > + else if (r == 0) { > + log_error("Cannot edit units for the user instance: > runtime 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("Failed get lookup paths: %s", > strerror(-r)); > + return r; > + } > + > + STRV_FOREACH(name, names) { > + _cleanup_free_ char *path = NULL; > + char *new_path, *tmp_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_create_copy(*name, path, > user_home, user_runtime, &new_path, &tmp_path); > + else > + r = unit_file_create_drop_in(*name, > user_home, user_runtime, &new_path, &tmp_path); > + > + if (r < 0) > + continue; > + > + r = strv_push(paths, new_path); > + if (r < 0) > + return log_oom(); > + > + r = strv_push(paths, tmp_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; > + _cleanup_free_ char *unit = NULL; > + char *new_path, *tmp_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_create_copy(*name, > fragment_path, user_home, user_runtime, &new_path, &tmp_path); > + else > + r = unit_file_create_drop_in(*name, > user_home, user_runtime, &new_path, &tmp_path); > + if (r < 0) > + continue; > + > + r = strv_push(paths, new_path); > + if (r < 0) > + return log_oom(); > + > + r = strv_push(paths, tmp_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; > + char **original, **tmp; > + int r; > + > + assert(args); > + > + if (!on_tty()) { > + log_error("Cannot edit units if we are not on a tty"); > + return -EINVAL; > + } > + > + if (arg_transport != BUS_TRANSPORT_LOCAL) { > + log_error("Cannot remotely edit 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_paths_to_edit(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) > + goto end; > + > + STRV_FOREACH_PAIR(original, tmp, paths) { > + /* If the temporary file is empty we ignore it. > + * It's useful if the user wants to cancel its modification > + */ > + if (null_or_empty_path(*tmp)) { > + log_warning("Edition of %s canceled: temporary file > empty", *original); > + continue; > + } > + r = rename(*tmp, *original); > + if (r < 0) { > + log_error("Failed to rename %s to %s: %m", *tmp, > *original); > + r = -errno; > + goto end; > + } > + } > + > + if (!arg_no_reload && bus && !avoid_bus()) > + r = daemon_reload(bus, args); > + > +end: > + STRV_FOREACH_PAIR(original, tmp, paths) { > + unlink_noerrno(*tmp); > + } {} not necessary. > + return r; > +} > + > static void systemctl_help(void) { > > pager_open_if_enabled(); > @@ -5739,7 +6255,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" > @@ -6748,8 +7266,9 @@ static int systemctl_main(sd_bus *bus, int argc, char > *argv[], int bus_error) { > { "get-default", EQUAL, 1, get_default, NOBUS > }, > { "set-property", MORE, 3, set_property }, > { "is-system-running", EQUAL, 1, is_system_running }, > - { "add-wants", MORE, 3, add_dependency, > NOBUS }, > - { "add-requires", MORE, 3, add_dependency, > NOBUS }, > + { "add-wants", MORE, 3, add_dependency, NOBUS > }, > + { "add-requires", MORE, 3, add_dependency, NOBUS > }, > + { "edit", MORE, 2, edit, NOBUS > }, > {} > }, *verb = verbs; Zbyszek _______________________________________________ systemd-devel mailing list systemd-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/systemd-devel