[systemd-devel] [PATCH] systemctl: add edit verb

Zbigniew Jędrzejewski-Szmek zbyszek at in.waw.pl
Fri Nov 28 14:38:45 PST 2014


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


More information about the systemd-devel mailing list