From a01c4bc9af388a5a0602c06c947b59f8dcd2a322 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Sat, 25 Feb 2023 20:47:13 +0800 Subject: [PATCH 1/5] shared: extract edit-util from systemctl-edit --- src/shared/edit-util.c | 261 +++++++++++++++++++++++++++++++++ src/shared/edit-util.h | 25 ++++ src/shared/meson.build | 1 + src/systemctl/systemctl-edit.c | 258 +------------------------------- 4 files changed, 291 insertions(+), 254 deletions(-) create mode 100644 src/shared/edit-util.c create mode 100644 src/shared/edit-util.h diff --git a/src/shared/edit-util.c b/src/shared/edit-util.c new file mode 100644 index 0000000000..6ef6fa984f --- /dev/null +++ b/src/shared/edit-util.c @@ -0,0 +1,261 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "alloc-util.h" +#include "copy.h" +#include "edit-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "mkdir-label.h" +#include "path-util.h" +#include "process-util.h" +#include "selinux-util.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "tmpfile-util.h" + +void edit_file_free_all(EditFile **f) { + if (!f || !*f) + return; + + for (EditFile *i = *f; i->path; i++) { + free(i->path); + free(i->tmp); + } + + free(*f); +} + +int create_edit_temp_file( + const char *new_path, + const char *original_path, + char ** const original_unit_paths, + const char *marker_start, + const char *marker_end, + char **ret_tmp_fn, + unsigned *ret_edit_line) { + + _cleanup_free_ char *t = NULL; + unsigned ln = 1; + int r; + + assert(new_path); + assert(ret_tmp_fn); + assert(ret_edit_line); + + r = tempfn_random(new_path, NULL, &t); + if (r < 0) + return log_error_errno(r, "Failed to determine temporary filename for \"%s\": %m", new_path); + + r = mkdir_parents_label(new_path, 0755); + if (r < 0) + return log_error_errno(r, "Failed to create directories for \"%s\": %m", new_path); + + if (original_path) { + r = mac_selinux_create_file_prepare(new_path, S_IFREG); + if (r < 0) + return r; + + r = copy_file(original_path, t, 0, 0644, 0, 0, COPY_REFLINK); + if (r == -ENOENT) { + r = touch(t); + mac_selinux_create_file_clear(); + if (r < 0) + return log_error_errno(r, "Failed to create temporary file \"%s\": %m", t); + } else { + mac_selinux_create_file_clear(); + if (r < 0) + return log_error_errno(r, "Failed to create temporary file for \"%s\": %m", new_path); + } + } else if (original_unit_paths) { + _cleanup_free_ char *new_contents = NULL; + _cleanup_fclose_ FILE *f = NULL; + + r = mac_selinux_create_file_prepare(new_path, S_IFREG); + if (r < 0) + return r; + + f = fopen(t, "we"); + mac_selinux_create_file_clear(); + if (!f) + return log_error_errno(errno, "Failed to open \"%s\": %m", t); + + if (fchmod(fileno(f), 0644) < 0) + return log_error_errno(errno, "Failed to change mode of \"%s\": %m", t); + + r = read_full_file(new_path, &new_contents, NULL); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to read \"%s\": %m", new_path); + + fprintf(f, + "### Editing %s\n" + "%s\n" + "\n" + "%s%s" + "\n" + "%s\n", + new_path, + strempty(marker_start), + strempty(new_contents), + new_contents && endswith(new_contents, "\n") ? "" : "\n", + strempty(marker_end)); + + ln = 4; /* start editing at the contents */ + + /* Add a comment with the contents of the original unit files */ + STRV_FOREACH(path, original_unit_paths) { + _cleanup_free_ char *contents = NULL; + + /* Skip the file that's being edited */ + if (path_equal(*path, new_path)) + continue; + + r = read_full_file(*path, &contents, NULL); + if (r < 0) + return log_error_errno(r, "Failed to read \"%s\": %m", *path); + + fprintf(f, "\n\n### %s", *path); + if (!isempty(contents)) { + _cleanup_free_ char *commented_contents = NULL; + + commented_contents = strreplace(strstrip(contents), "\n", "\n# "); + if (!commented_contents) + return log_oom(); + fprintf(f, "\n# %s", commented_contents); + } + } + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to create temporary file \"%s\": %m", t); + } + + *ret_tmp_fn = TAKE_PTR(t); + *ret_edit_line = ln; + + return 0; +} + +int run_editor(const EditFile *files) { + int r; + + assert(files); + + r = safe_fork("(editor)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG|FORK_WAIT, NULL); + if (r < 0) + return r; + if (r == 0) { + size_t n_editor_args = 0, i = 1, argc; + char **editor_args = NULL, **args; + const 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 (isempty(editor)) + argc = 1; + else { + editor_args = strv_split(editor, WHITESPACE); + if (!editor_args) { + (void) log_oom(); + _exit(EXIT_FAILURE); + } + n_editor_args = strv_length(editor_args); + argc = n_editor_args; + } + + for (const EditFile *f = files; f->path; f++) + argc += 2; + + args = newa(char*, argc + 1); + + if (n_editor_args > 0) { + args[0] = editor_args[0]; + for (; i < n_editor_args; i++) + args[i] = editor_args[i]; + } + + if (files[0].path && files[0].line > 1 && !files[1].path) { + /* If editing a single file only, use the +LINE syntax to put cursor on the right line */ + if (asprintf(args + i, "+%u", files[0].line) < 0) { + (void) log_oom(); + _exit(EXIT_FAILURE); + } + + i++; + args[i++] = files[0].tmp; + } else + for (const EditFile *f = files; f->path; f++) + args[i++] = f->tmp; + + args[i] = NULL; + + if (n_editor_args > 0) + execvp(args[0], (char* const*) args); + + FOREACH_STRING(name, "editor", "nano", "vim", "vi") { + args[0] = (char*) name; + execvp(name, (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_errno(errno, "Failed to execute %s: %m", name); + _exit(EXIT_FAILURE); + } + } + + log_error("Cannot edit units, no editor available. Please set either $SYSTEMD_EDITOR, $EDITOR or $VISUAL."); + _exit(EXIT_FAILURE); + } + + return 0; +} + +int trim_edit_markers(const char *path, const char *marker_start, const char *marker_end) { + _cleanup_free_ char *old_contents = NULL, *new_contents = NULL; + char *contents_start, *contents_end; + const char *c = NULL; + int r; + + /* Trim out the lines between the two markers */ + r = read_full_file(path, &old_contents, NULL); + if (r < 0) + return log_error_errno(r, "Failed to read temporary file \"%s\": %m", path); + + contents_start = strstr(old_contents, marker_start); + if (contents_start) + contents_start += strlen(marker_start); + else + contents_start = old_contents; + + contents_end = strstr(contents_start, marker_end); + if (contents_end) + contents_end[0] = 0; + + c = strstrip(contents_start); + if (isempty(c)) + return 0; /* All gone now */ + + new_contents = strjoin(c, "\n"); /* Trim prefix and suffix, but ensure suffixed by single newline */ + if (!new_contents) + return log_oom(); + + if (streq(old_contents, new_contents)) /* Don't touch the file if the above didn't change a thing */ + return 1; /* Unchanged, but good */ + + r = write_string_file(path, new_contents, WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_TRUNCATE | WRITE_STRING_FILE_AVOID_NEWLINE); + if (r < 0) + return log_error_errno(r, "Failed to modify temporary file \"%s\": %m", path); + + return 1; /* Changed, but good */ +} diff --git a/src/shared/edit-util.h b/src/shared/edit-util.h new file mode 100644 index 0000000000..a761d54390 --- /dev/null +++ b/src/shared/edit-util.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "path-lookup.h" + +typedef struct EditFile { + char *path; + char *tmp; + unsigned line; +} EditFile; + +void edit_file_free_all(EditFile **ef); + +int create_edit_temp_file( + const char *new_path, + const char *original_path, + char ** const original_unit_paths, + const char *marker_start, + const char *marker_end, + char **ret_tmp_fn, + unsigned *ret_edit_line); + +int run_editor(const EditFile *files); + +int trim_edit_markers(const char *path, const char *marker_start, const char *marker_end); diff --git a/src/shared/meson.build b/src/shared/meson.build index e58e161713..51fd37d16d 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -54,6 +54,7 @@ shared_sources = files( 'dm-util.c', 'dns-domain.c', 'dropin.c', + 'edit-util.c', 'efi-api.c', 'efi-loader.c', 'elf-util.c', diff --git a/src/systemctl/systemctl-edit.c b/src/systemctl/systemctl-edit.c index c07112b764..88ac98d8a2 100644 --- a/src/systemctl/systemctl-edit.c +++ b/src/systemctl/systemctl-edit.c @@ -1,23 +1,17 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "bus-error.h" -#include "copy.h" -#include "fd-util.h" -#include "fileio.h" +#include "edit-util.h" #include "fs-util.h" -#include "mkdir-label.h" #include "pager.h" #include "path-util.h" #include "pretty-print.h" -#include "process-util.h" #include "selinux-util.h" -#include "stat-util.h" #include "systemctl-daemon-reload.h" #include "systemctl-edit.h" #include "systemctl-util.h" #include "systemctl.h" #include "terminal-util.h" -#include "tmpfile-util.h" #define EDIT_MARKER_START "### Anything between here and the comment below will become the contents of the drop-in file" #define EDIT_MARKER_END "### Edits below this comment will be discarded" @@ -110,112 +104,6 @@ int verb_cat(int argc, char *argv[], void *userdata) { return rc; } -static int create_edit_temp_file( - const char *new_path, - const char *original_path, - char ** const original_unit_paths, - char **ret_tmp_fn, - unsigned *ret_edit_line) { - - _cleanup_free_ char *t = NULL; - unsigned ln = 1; - int r; - - assert(new_path); - assert(ret_tmp_fn); - assert(ret_edit_line); - - r = tempfn_random(new_path, NULL, &t); - if (r < 0) - return log_error_errno(r, "Failed to determine temporary filename for \"%s\": %m", new_path); - - r = mkdir_parents_label(new_path, 0755); - if (r < 0) - return log_error_errno(r, "Failed to create directories for \"%s\": %m", new_path); - - if (original_path) { - r = mac_selinux_create_file_prepare(new_path, S_IFREG); - if (r < 0) - return r; - - r = copy_file(original_path, t, 0, 0644, 0, 0, COPY_REFLINK); - if (r == -ENOENT) { - r = touch(t); - mac_selinux_create_file_clear(); - if (r < 0) - return log_error_errno(r, "Failed to create temporary file \"%s\": %m", t); - } else { - mac_selinux_create_file_clear(); - if (r < 0) - return log_error_errno(r, "Failed to create temporary file for \"%s\": %m", new_path); - } - } else if (original_unit_paths) { - _cleanup_free_ char *new_contents = NULL; - _cleanup_fclose_ FILE *f = NULL; - - r = mac_selinux_create_file_prepare(new_path, S_IFREG); - if (r < 0) - return r; - - f = fopen(t, "we"); - mac_selinux_create_file_clear(); - if (!f) - return log_error_errno(errno, "Failed to open \"%s\": %m", t); - - if (fchmod(fileno(f), 0644) < 0) - return log_error_errno(errno, "Failed to change mode of \"%s\": %m", t); - - r = read_full_file(new_path, &new_contents, NULL); - if (r < 0 && r != -ENOENT) - return log_error_errno(r, "Failed to read \"%s\": %m", new_path); - - fprintf(f, - "### Editing %s\n" - EDIT_MARKER_START "\n" - "\n" - "%s%s" - "\n" - EDIT_MARKER_END, - new_path, - strempty(new_contents), - new_contents && endswith(new_contents, "\n") ? "" : "\n"); - - ln = 4; /* start editing at the contents */ - - /* Add a comment with the contents of the original unit files */ - STRV_FOREACH(path, original_unit_paths) { - _cleanup_free_ char *contents = NULL; - - /* Skip the file that's being edited */ - if (path_equal(*path, new_path)) - continue; - - r = read_full_file(*path, &contents, NULL); - if (r < 0) - return log_error_errno(r, "Failed to read \"%s\": %m", *path); - - fprintf(f, "\n\n### %s", *path); - if (!isempty(contents)) { - _cleanup_free_ char *commented_contents = NULL; - - commented_contents = strreplace(strstrip(contents), "\n", "\n# "); - if (!commented_contents) - return log_oom(); - fprintf(f, "\n# %s", commented_contents); - } - } - - r = fflush_and_check(f); - if (r < 0) - return log_error_errno(r, "Failed to create temporary file \"%s\": %m", t); - } - - *ret_tmp_fn = TAKE_PTR(t); - *ret_edit_line = ln; - - return 0; -} - static int get_file_to_edit( const LookupPaths *paths, const char *name, @@ -247,24 +135,6 @@ static int get_file_to_edit( return 0; } -typedef struct EditFile { - char *path; - char *tmp; - unsigned line; -} EditFile; - -static void edit_file_free_all(EditFile **f) { - if (!f || !*f) - return; - - for (EditFile *i = *f; i->path; i++) { - free(i->path); - free(i->tmp); - } - - free(*f); -} - static int unit_file_create_new( const LookupPaths *paths, const char *unit_name, @@ -285,7 +155,7 @@ static int unit_file_create_new( if (r < 0) return r; - r = create_edit_temp_file(new_path, NULL, original_unit_paths, &tmp_path, &edit_line); + r = create_edit_temp_file(new_path, NULL, original_unit_paths, EDIT_MARKER_START, EDIT_MARKER_END, &tmp_path, &edit_line); if (r < 0) return r; @@ -325,7 +195,7 @@ static int unit_file_create_copy( return log_warning_errno(SYNTHETIC_ERRNO(EKEYREJECTED), "%s skipped.", unit_name); } - r = create_edit_temp_file(new_path, fragment_path, NULL, &tmp_path, &edit_line); + r = create_edit_temp_file(new_path, fragment_path, NULL, EDIT_MARKER_START, EDIT_MARKER_END, &tmp_path, &edit_line); if (r < 0) return r; @@ -337,87 +207,6 @@ static int unit_file_create_copy( return 0; } -static int run_editor(const EditFile *files) { - int r; - - assert(files); - - r = safe_fork("(editor)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG|FORK_WAIT, NULL); - if (r < 0) - return r; - if (r == 0) { - size_t n_editor_args = 0, i = 1, argc; - char **editor_args = NULL, **args; - const 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 (isempty(editor)) - argc = 1; - else { - editor_args = strv_split(editor, WHITESPACE); - if (!editor_args) { - (void) log_oom(); - _exit(EXIT_FAILURE); - } - n_editor_args = strv_length(editor_args); - argc = n_editor_args; - } - - for (const EditFile *f = files; f->path; f++) - argc += 2; - - args = newa(char*, argc + 1); - - if (n_editor_args > 0) { - args[0] = editor_args[0]; - for (; i < n_editor_args; i++) - args[i] = editor_args[i]; - } - - if (files[0].path && files[0].line > 1 && !files[1].path) { - /* If editing a single file only, use the +LINE syntax to put cursor on the right line */ - if (asprintf(args + i, "+%u", files[0].line) < 0) { - (void) log_oom(); - _exit(EXIT_FAILURE); - } - - i++; - args[i++] = files[0].tmp; - } else - for (const EditFile *f = files; f->path; f++) - args[i++] = f->tmp; - - args[i] = NULL; - - if (n_editor_args > 0) - execvp(args[0], (char* const*) args); - - FOREACH_STRING(name, "editor", "nano", "vim", "vi") { - args[0] = (char*) name; - execvp(name, (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_errno(errno, "Failed to execute %s: %m", name); - _exit(EXIT_FAILURE); - } - } - - log_error("Cannot edit units, no editor available. Please set either $SYSTEMD_EDITOR, $EDITOR or $VISUAL."); - _exit(EXIT_FAILURE); - } - - return 0; -} - static int find_paths_to_edit( sd_bus *bus, char **names, @@ -543,45 +332,6 @@ static int find_paths_to_edit( return 0; } -static int trim_edit_markers(const char *path) { - _cleanup_free_ char *old_contents = NULL, *new_contents = NULL; - char *contents_start, *contents_end; - const char *c = NULL; - int r; - - /* Trim out the lines between the two markers */ - r = read_full_file(path, &old_contents, NULL); - if (r < 0) - return log_error_errno(r, "Failed to read temporary file \"%s\": %m", path); - - contents_start = strstr(old_contents, EDIT_MARKER_START); - if (contents_start) - contents_start += strlen(EDIT_MARKER_START); - else - contents_start = old_contents; - - contents_end = strstr(contents_start, EDIT_MARKER_END); - if (contents_end) - contents_end[0] = 0; - - c = strstrip(contents_start); - if (isempty(c)) - return 0; /* All gone now */ - - new_contents = strjoin(c, "\n"); /* Trim prefix and suffix, but ensure suffixed by single newline */ - if (!new_contents) - return log_oom(); - - if (streq(old_contents, new_contents)) /* Don't touch the file if the above didn't change a thing */ - return 1; /* Unchanged, but good */ - - r = write_string_file(path, new_contents, WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_TRUNCATE | WRITE_STRING_FILE_AVOID_NEWLINE); - if (r < 0) - return log_error_errno(r, "Failed to modify temporary file \"%s\": %m", path); - - return 1; /* Changed, but good */ -} - int verb_edit(int argc, char *argv[], void *userdata) { _cleanup_(edit_file_free_all) EditFile *edit_files = NULL; _cleanup_(lookup_paths_free) LookupPaths lp = {}; @@ -634,7 +384,7 @@ int verb_edit(int argc, char *argv[], void *userdata) { for (EditFile *f = edit_files; f->path; f++) { /* If the temporary file is empty we ignore it. This allows the user to cancel the * modification. */ - r = trim_edit_markers(f->tmp); + r = trim_edit_markers(f->tmp, EDIT_MARKER_START, EDIT_MARKER_END); if (r < 0) goto end; if (r == 0) /* has no actual contents? then ignore it */ From fa2413dd57bb594b846ee4cfb97d338e59ec5639 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Sat, 25 Feb 2023 21:11:02 +0800 Subject: [PATCH 2/5] edit-util: several cleanups to create_edit_temp_file original_path and comment_paths can now be used together. Removes reference to "unit" --- src/shared/edit-util.c | 87 +++++++++++++++++++++++------------------- src/shared/edit-util.h | 6 +-- 2 files changed, 50 insertions(+), 43 deletions(-) diff --git a/src/shared/edit-util.c b/src/shared/edit-util.c index 6ef6fa984f..2ad81e7e09 100644 --- a/src/shared/edit-util.c +++ b/src/shared/edit-util.c @@ -31,65 +31,67 @@ void edit_file_free_all(EditFile **f) { } int create_edit_temp_file( - const char *new_path, + const char *target_path, const char *original_path, - char ** const original_unit_paths, + char * const *comment_paths, const char *marker_start, const char *marker_end, - char **ret_tmp_fn, + char **ret_temp_filename, unsigned *ret_edit_line) { - _cleanup_free_ char *t = NULL; - unsigned ln = 1; + _cleanup_free_ char *temp = NULL; + unsigned line = 1; int r; - assert(new_path); - assert(ret_tmp_fn); - assert(ret_edit_line); + assert(target_path); + assert(!comment_paths || (marker_start && marker_end)); + assert(ret_temp_filename); - r = tempfn_random(new_path, NULL, &t); + r = tempfn_random(target_path, NULL, &temp); if (r < 0) - return log_error_errno(r, "Failed to determine temporary filename for \"%s\": %m", new_path); + return log_error_errno(r, "Failed to determine temporary filename for \"%s\": %m", target_path); - r = mkdir_parents_label(new_path, 0755); + r = mkdir_parents_label(target_path, 0755); if (r < 0) - return log_error_errno(r, "Failed to create directories for \"%s\": %m", new_path); + return log_error_errno(r, "Failed to create parent directories for \"%s\": %m", target_path); if (original_path) { - r = mac_selinux_create_file_prepare(new_path, S_IFREG); + r = mac_selinux_create_file_prepare(target_path, S_IFREG); if (r < 0) return r; - r = copy_file(original_path, t, 0, 0644, 0, 0, COPY_REFLINK); + r = copy_file(original_path, temp, 0, 0644, 0, 0, COPY_REFLINK); if (r == -ENOENT) { - r = touch(t); + r = touch(temp); mac_selinux_create_file_clear(); if (r < 0) - return log_error_errno(r, "Failed to create temporary file \"%s\": %m", t); + return log_error_errno(r, "Failed to create temporary file \"%s\": %m", temp); } else { mac_selinux_create_file_clear(); if (r < 0) - return log_error_errno(r, "Failed to create temporary file for \"%s\": %m", new_path); + return log_error_errno(r, "Failed to create temporary file for \"%s\": %m", target_path); } - } else if (original_unit_paths) { - _cleanup_free_ char *new_contents = NULL; + } + + if (comment_paths) { + _cleanup_free_ char *target_contents = NULL; _cleanup_fclose_ FILE *f = NULL; - r = mac_selinux_create_file_prepare(new_path, S_IFREG); + r = mac_selinux_create_file_prepare(target_path, S_IFREG); if (r < 0) return r; - f = fopen(t, "we"); + f = fopen(temp, "we"); mac_selinux_create_file_clear(); if (!f) - return log_error_errno(errno, "Failed to open \"%s\": %m", t); + return log_error_errno(errno, "Failed to open temporary file \"%s\": %m", temp); if (fchmod(fileno(f), 0644) < 0) - return log_error_errno(errno, "Failed to change mode of \"%s\": %m", t); + return log_error_errno(errno, "Failed to change mode of temporary file \"%s\": %m", temp); - r = read_full_file(new_path, &new_contents, NULL); + r = read_full_file(target_path, &target_contents, NULL); if (r < 0 && r != -ENOENT) - return log_error_errno(r, "Failed to read \"%s\": %m", new_path); + return log_error_errno(r, "Failed to read target file \"%s\": %m", target_path); fprintf(f, "### Editing %s\n" @@ -98,25 +100,25 @@ int create_edit_temp_file( "%s%s" "\n" "%s\n", - new_path, - strempty(marker_start), - strempty(new_contents), - new_contents && endswith(new_contents, "\n") ? "" : "\n", - strempty(marker_end)); + target_path, + marker_start, + strempty(target_contents), + target_contents && endswith(target_contents, "\n") ? "" : "\n", + marker_end); - ln = 4; /* start editing at the contents */ + line = 4; /* Start editing at the contents area */ - /* Add a comment with the contents of the original unit files */ - STRV_FOREACH(path, original_unit_paths) { + /* Add a comment with the contents of the original files */ + STRV_FOREACH(path, comment_paths) { _cleanup_free_ char *contents = NULL; - /* Skip the file that's being edited */ - if (path_equal(*path, new_path)) + /* Skip the file that's being edited, already processed in above */ + if (path_equal(*path, target_path)) continue; r = read_full_file(*path, &contents, NULL); if (r < 0) - return log_error_errno(r, "Failed to read \"%s\": %m", *path); + return log_error_errno(r, "Failed to read original file \"%s\": %m", *path); fprintf(f, "\n\n### %s", *path); if (!isempty(contents)) { @@ -125,17 +127,20 @@ int create_edit_temp_file( commented_contents = strreplace(strstrip(contents), "\n", "\n# "); if (!commented_contents) return log_oom(); + fprintf(f, "\n# %s", commented_contents); } } r = fflush_and_check(f); if (r < 0) - return log_error_errno(r, "Failed to create temporary file \"%s\": %m", t); + return log_error_errno(r, "Failed to create temporary file \"%s\": %m", temp); } - *ret_tmp_fn = TAKE_PTR(t); - *ret_edit_line = ln; + *ret_temp_filename = TAKE_PTR(temp); + + if (ret_edit_line) + *ret_edit_line = line; return 0; } @@ -214,7 +219,7 @@ int run_editor(const EditFile *files) { } } - log_error("Cannot edit units, no editor available. Please set either $SYSTEMD_EDITOR, $EDITOR or $VISUAL."); + log_error("Cannot edit files, no editor available. Please set either $SYSTEMD_EDITOR, $EDITOR or $VISUAL."); _exit(EXIT_FAILURE); } @@ -227,6 +232,8 @@ int trim_edit_markers(const char *path, const char *marker_start, const char *ma const char *c = NULL; int r; + assert(!marker_start == !marker_end); + /* Trim out the lines between the two markers */ r = read_full_file(path, &old_contents, NULL); if (r < 0) diff --git a/src/shared/edit-util.h b/src/shared/edit-util.h index a761d54390..b20c09e896 100644 --- a/src/shared/edit-util.h +++ b/src/shared/edit-util.h @@ -12,12 +12,12 @@ typedef struct EditFile { void edit_file_free_all(EditFile **ef); int create_edit_temp_file( - const char *new_path, + const char *target_path, const char *original_path, - char ** const original_unit_paths, + char * const *comment_paths, const char *marker_start, const char *marker_end, - char **ret_tmp_fn, + char **ret_temp_filename, unsigned *ret_edit_line); int run_editor(const EditFile *files); From 9a11b4f953f8fdd68aa407a4e4f31670fa789e7c Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Sat, 25 Feb 2023 21:02:17 +0800 Subject: [PATCH 3/5] edit-util: introduce EditFileContext This is a rather large change which moves the add and install logic into edit-util. We store an EditFile array and the number of elements, along with the edit markers used in temporary files and whether to remove the parent directories of the target files if they're empty in an EditFileContext object. Call edit_files_add() to add an file to edit, and do_edit_files_and_install() to do the actual editing (through create_edit_temp_file(), run_editor() and trim_edit_markers()). After that, edit_file_context_done() can be used to destroy the object. --- src/shared/edit-util.c | 151 +++++++++++++++++++++++++++++---- src/shared/edit-util.h | 31 ++++--- src/systemctl/systemctl-edit.c | 113 ++++++------------------ 3 files changed, 181 insertions(+), 114 deletions(-) diff --git a/src/shared/edit-util.c b/src/shared/edit-util.c index 2ad81e7e09..38a1dfc089 100644 --- a/src/shared/edit-util.c +++ b/src/shared/edit-util.c @@ -18,19 +18,93 @@ #include "strv.h" #include "tmpfile-util.h" -void edit_file_free_all(EditFile **f) { - if (!f || !*f) - return; +void edit_file_context_done(EditFileContext *context) { + int r; + + assert(context); + + FOREACH_ARRAY(i, context->files, context->n_files) { + if (i->temp) { + (void) unlink(i->temp); + free(i->temp); + } + + if (context->remove_parent) { + _cleanup_free_ char *parent = NULL; + + r = path_extract_directory(i->path, &parent); + if (r < 0) + log_debug_errno(r, "Failed to extract directory from '%s', ignoring: %m", i->path); + + /* No need to check if the dir is empty, rmdir does nothing if it is not the case. */ + (void) rmdir(parent); + } - for (EditFile *i = *f; i->path; i++) { free(i->path); - free(i->tmp); + free(i->original_path); + strv_free(i->comment_paths); } - free(*f); + context->files = mfree(context->files); + context->n_files = 0; } -int create_edit_temp_file( +bool edit_files_contains(const EditFileContext *context, const char *path) { + assert(context); + assert(path); + + FOREACH_ARRAY(i, context->files, context->n_files) + if (streq(i->path, path)) + return true; + + return false; +} + +int edit_files_add( + EditFileContext *context, + const char *path, + const char *original_path, + char * const *comment_paths) { + + _cleanup_free_ char *new_path = NULL, *new_original_path = NULL; + _cleanup_strv_free_ char **new_comment_paths = NULL; + + assert(context); + assert(path); + + if (edit_files_contains(context, path)) + return 0; + + if (!GREEDY_REALLOC0(context->files, context->n_files + 2)) + return log_oom(); + + new_path = strdup(path); + if (!new_path) + return log_oom(); + + if (original_path) { + new_original_path = strdup(original_path); + if (!new_original_path) + return log_oom(); + } + + if (comment_paths) { + new_comment_paths = strv_copy(comment_paths); + if (!new_comment_paths) + return log_oom(); + } + + context->files[context->n_files] = (EditFile) { + .path = TAKE_PTR(new_path), + .original_path = TAKE_PTR(new_original_path), + .comment_paths = TAKE_PTR(new_comment_paths), + }; + context->n_files++; + + return 1; +} + +static int create_edit_temp_file( const char *target_path, const char *original_path, char * const *comment_paths, @@ -145,10 +219,10 @@ int create_edit_temp_file( return 0; } -int run_editor(const EditFile *files) { +static int run_editor(const EditFileContext *context) { int r; - assert(files); + assert(context); r = safe_fork("(editor)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG|FORK_WAIT, NULL); if (r < 0) @@ -179,8 +253,7 @@ int run_editor(const EditFile *files) { argc = n_editor_args; } - for (const EditFile *f = files; f->path; f++) - argc += 2; + argc += context->n_files * 2; args = newa(char*, argc + 1); @@ -190,18 +263,18 @@ int run_editor(const EditFile *files) { args[i] = editor_args[i]; } - if (files[0].path && files[0].line > 1 && !files[1].path) { + if (context->n_files == 1 && context->files[0].line > 1) { /* If editing a single file only, use the +LINE syntax to put cursor on the right line */ - if (asprintf(args + i, "+%u", files[0].line) < 0) { + if (asprintf(args + i, "+%u", context->files[0].line) < 0) { (void) log_oom(); _exit(EXIT_FAILURE); } i++; - args[i++] = files[0].tmp; + args[i++] = context->files[0].temp; } else - for (const EditFile *f = files; f->path; f++) - args[i++] = f->tmp; + FOREACH_ARRAY(f, context->files, context->n_files) + args[i++] = f->temp; args[i] = NULL; @@ -226,7 +299,7 @@ int run_editor(const EditFile *files) { return 0; } -int trim_edit_markers(const char *path, const char *marker_start, const char *marker_end) { +static int trim_edit_markers(const char *path, const char *marker_start, const char *marker_end) { _cleanup_free_ char *old_contents = NULL, *new_contents = NULL; char *contents_start, *contents_end; const char *c = NULL; @@ -266,3 +339,47 @@ int trim_edit_markers(const char *path, const char *marker_start, const char *ma return 1; /* Changed, but good */ } + +int do_edit_files_and_install(EditFileContext *context) { + int r; + + assert(context); + + if (context->n_files == 0) + return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), "Got no files to edit."); + + FOREACH_ARRAY(i, context->files, context->n_files) + if (isempty(i->temp)) { + r = create_edit_temp_file(i->path, + i->original_path, + i->comment_paths, + context->marker_start, + context->marker_end, + &i->temp, + &i->line); + if (r < 0) + return r; + } + + r = run_editor(context); + if (r < 0) + return r; + + FOREACH_ARRAY(i, context->files, context->n_files) { + /* Always call trim_edit_markers to tell if the temp file is empty */ + r = trim_edit_markers(i->temp, context->marker_start, context->marker_end); + if (r < 0) + return r; + if (r == 0) /* temp file doesn't carry actual changes, ignoring */ + continue; + + r = RET_NERRNO(rename(i->temp, i->path)); + if (r < 0) + return log_error_errno(r, "Failed to rename \"%s\" to \"%s\": %m", i->temp, i->path); + i->temp = mfree(i->temp); + + log_info("Successfully installed edited file '%s'.", i->path); + } + + return 0; +} diff --git a/src/shared/edit-util.h b/src/shared/edit-util.h index b20c09e896..7b705060e4 100644 --- a/src/shared/edit-util.h +++ b/src/shared/edit-util.h @@ -5,21 +5,28 @@ typedef struct EditFile { char *path; - char *tmp; + char *original_path; + char **comment_paths; + char *temp; unsigned line; } EditFile; -void edit_file_free_all(EditFile **ef); +typedef struct EditFileContext { + EditFile *files; + size_t n_files; + const char *marker_start; + const char *marker_end; + bool remove_parent; +} EditFileContext; -int create_edit_temp_file( - const char *target_path, +void edit_file_context_done(EditFileContext *context); + +bool edit_files_contains(const EditFileContext *context, const char *path); + +int edit_files_add( + EditFileContext *context, + const char *path, const char *original_path, - char * const *comment_paths, - const char *marker_start, - const char *marker_end, - char **ret_temp_filename, - unsigned *ret_edit_line); + char * const *comment_paths); -int run_editor(const EditFile *files); - -int trim_edit_markers(const char *path, const char *marker_start, const char *marker_end); +int do_edit_files_and_install(EditFileContext *context); diff --git a/src/systemctl/systemctl-edit.c b/src/systemctl/systemctl-edit.c index 88ac98d8a2..b44e595b25 100644 --- a/src/systemctl/systemctl-edit.c +++ b/src/systemctl/systemctl-edit.c @@ -136,50 +136,43 @@ static int get_file_to_edit( } static int unit_file_create_new( + EditFileContext *context, const LookupPaths *paths, const char *unit_name, const char *suffix, - char ** const original_unit_paths, - EditFile *ret_edit_file) { + char * const *original_unit_paths) { - _cleanup_free_ char *new_path = NULL, *tmp_path = NULL; - unsigned edit_line; + _cleanup_free_ char *new_path = NULL; const char *ending; int r; + assert(context); assert(unit_name); - assert(ret_edit_file); ending = strjoina(unit_name, suffix); r = get_file_to_edit(paths, ending, &new_path); if (r < 0) return r; - r = create_edit_temp_file(new_path, NULL, original_unit_paths, EDIT_MARKER_START, EDIT_MARKER_END, &tmp_path, &edit_line); + r = edit_files_add(context, new_path, NULL, original_unit_paths); if (r < 0) return r; - *ret_edit_file = (EditFile) { - .path = TAKE_PTR(new_path), - .tmp = TAKE_PTR(tmp_path), - .line = edit_line, - }; return 0; } static int unit_file_create_copy( + EditFileContext *context, const LookupPaths *paths, const char *unit_name, - const char *fragment_path, - EditFile *ret_edit_file) { + const char *fragment_path) { - _cleanup_free_ char *new_path = NULL, *tmp_path = NULL; - unsigned edit_line; + _cleanup_free_ char *new_path = NULL; int r; + assert(context); assert(fragment_path); assert(unit_name); - assert(ret_edit_file); r = get_file_to_edit(paths, unit_name, &new_path); if (r < 0) @@ -195,33 +188,27 @@ static int unit_file_create_copy( return log_warning_errno(SYNTHETIC_ERRNO(EKEYREJECTED), "%s skipped.", unit_name); } - r = create_edit_temp_file(new_path, fragment_path, NULL, EDIT_MARKER_START, EDIT_MARKER_END, &tmp_path, &edit_line); + r = edit_files_add(context, new_path, fragment_path, NULL); if (r < 0) return r; - *ret_edit_file = (EditFile) { - .path = TAKE_PTR(new_path), - .tmp = TAKE_PTR(tmp_path), - .line = edit_line, - }; return 0; } static int find_paths_to_edit( sd_bus *bus, - char **names, - EditFile **ret_edit_files) { + EditFileContext *context, + char **names) { _cleanup_(hashmap_freep) Hashmap *cached_name_map = NULL, *cached_id_map = NULL; - _cleanup_(edit_file_free_all) EditFile *edit_files = NULL; _cleanup_(lookup_paths_free) LookupPaths lp = {}; _cleanup_free_ char *drop_in_alloc = NULL, *suffix = NULL; const char *drop_in; - size_t n_edit_files = 0; int r; + assert(bus); + assert(context); assert(names); - assert(ret_edit_files); if (isempty(arg_drop_in)) drop_in = "override.conf"; @@ -261,9 +248,6 @@ static int find_paths_to_edit( if (r < 0) return r; - if (!GREEDY_REALLOC0(edit_files, n_edit_files + 2)) - return log_oom(); - if (!path) { if (!arg_force) { log_info("Run 'systemctl edit%s --force --full %s' to create a new unit.", @@ -275,11 +259,11 @@ static int find_paths_to_edit( /* Create a new unit from scratch */ r = unit_file_create_new( + context, &lp, *name, arg_full ? NULL : suffix, - NULL, - edit_files + n_edit_files); + NULL); } else { _cleanup_free_ char *unit_name = NULL; @@ -305,35 +289,36 @@ static int find_paths_to_edit( if (arg_full) r = unit_file_create_copy( + context, &lp, unit_name, - path, - edit_files + n_edit_files); + path); else { r = strv_prepend(&unit_paths, path); if (r < 0) return log_oom(); r = unit_file_create_new( + context, &lp, unit_name, suffix, - unit_paths, - edit_files + n_edit_files); + unit_paths); } } if (r < 0) return r; - - n_edit_files++; } - *ret_edit_files = n_edit_files > 0 ? TAKE_PTR(edit_files) : NULL; return 0; } int verb_edit(int argc, char *argv[], void *userdata) { - _cleanup_(edit_file_free_all) EditFile *edit_files = NULL; + _cleanup_(edit_file_context_done) EditFileContext context = { + .marker_start = EDIT_MARKER_START, + .marker_end = EDIT_MARKER_END, + .remove_parent = !arg_full, + }; _cleanup_(lookup_paths_free) LookupPaths lp = {}; _cleanup_strv_free_ char **names = NULL; sd_bus *bus; @@ -371,36 +356,13 @@ int verb_edit(int argc, char *argv[], void *userdata) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit %s: unit is masked.", *tmp); } - r = find_paths_to_edit(bus, names, &edit_files); + r = find_paths_to_edit(bus, &context, names); if (r < 0) return r; - if (!edit_files) - return -ENOENT; - r = run_editor(edit_files); + r = do_edit_files_and_install(&context); if (r < 0) - goto end; - - for (EditFile *f = edit_files; f->path; f++) { - /* If the temporary file is empty we ignore it. This allows the user to cancel the - * modification. */ - r = trim_edit_markers(f->tmp, EDIT_MARKER_START, EDIT_MARKER_END); - if (r < 0) - goto end; - if (r == 0) /* has no actual contents? then ignore it */ - continue; - - r = RET_NERRNO(rename(f->tmp, f->path)); - if (r < 0) { - log_error_errno(r, "Failed to rename \"%s\" to \"%s\": %m", f->tmp, f->path); - goto end; - } - - f->tmp = mfree(f->tmp); - log_info("Successfully installed edited file '%s'.", f->path); - } - - r = 0; + return r; if (!arg_no_reload && !install_client_side()) { r = daemon_reload(ACTION_RELOAD, /* graceful= */ false); @@ -408,24 +370,5 @@ int verb_edit(int argc, char *argv[], void *userdata) { r = 0; } -end: - for (EditFile *f = ASSERT_PTR(edit_files); f->path; f++) { - - if (f->tmp) - (void) unlink(f->tmp); - - /* Removing empty dropin dirs */ - if (!arg_full) { - _cleanup_free_ char *dir = NULL; - - r = path_extract_directory(f->path, &dir); - if (r < 0) - return log_error_errno(r, "Failed to extract directory from '%s': %m", f->path); - - /* No need to check if the dir is empty, rmdir does nothing if it is not the case. */ - (void) rmdir(dir); - } - } - return r; } From 90c462bdd180496d0b0254a0bd580cdcafbcb1b9 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Sun, 26 Feb 2023 01:54:10 +0800 Subject: [PATCH 4/5] systemctl: edit: several cleanups --- src/systemctl/systemctl-edit.c | 37 +++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/systemctl/systemctl-edit.c b/src/systemctl/systemctl-edit.c index b44e595b25..d85abbd165 100644 --- a/src/systemctl/systemctl-edit.c +++ b/src/systemctl/systemctl-edit.c @@ -105,21 +105,24 @@ int verb_cat(int argc, char *argv[], void *userdata) { } static int get_file_to_edit( - const LookupPaths *paths, + const LookupPaths *lp, const char *name, char **ret_path) { - _cleanup_free_ char *path = NULL, *run = NULL; + _cleanup_free_ char *path = NULL; + assert(lp); assert(name); assert(ret_path); - path = path_join(paths->persistent_config, name); + path = path_join(lp->persistent_config, name); if (!path) return log_oom(); if (arg_runtime) { - run = path_join(paths->runtime_config, name); + _cleanup_free_ char *run = NULL; + + run = path_join(lp->runtime_config, name); if (!run) return log_oom(); @@ -137,20 +140,23 @@ static int get_file_to_edit( static int unit_file_create_new( EditFileContext *context, - const LookupPaths *paths, + const LookupPaths *lp, const char *unit_name, const char *suffix, char * const *original_unit_paths) { - _cleanup_free_ char *new_path = NULL; - const char *ending; + _cleanup_free_ char *unit = NULL, *new_path = NULL; int r; assert(context); + assert(lp); assert(unit_name); - ending = strjoina(unit_name, suffix); - r = get_file_to_edit(paths, ending, &new_path); + unit = strjoin(unit_name, suffix); + if (!unit) + return log_oom(); + + r = get_file_to_edit(lp, unit, &new_path); if (r < 0) return r; @@ -163,7 +169,7 @@ static int unit_file_create_new( static int unit_file_create_copy( EditFileContext *context, - const LookupPaths *paths, + const LookupPaths *lp, const char *unit_name, const char *fragment_path) { @@ -171,10 +177,11 @@ static int unit_file_create_copy( int r; assert(context); + assert(lp); assert(fragment_path); assert(unit_name); - r = get_file_to_edit(paths, unit_name, &new_path); + r = get_file_to_edit(lp, unit_name, &new_path); if (r < 0) return r; @@ -184,6 +191,7 @@ static int unit_file_create_copy( r = ask_char(&response, "yn", "\"%s\" already exists. Overwrite with \"%s\"? [(y)es, (n)o] ", new_path, fragment_path); if (r < 0) return r; + if (response != 'y') return log_warning_errno(SYNTHETIC_ERRNO(EKEYREJECTED), "%s skipped.", unit_name); } @@ -236,17 +244,18 @@ static int find_paths_to_edit( _cleanup_free_ char *path = NULL; _cleanup_strv_free_ char **unit_paths = NULL; - r = unit_find_paths(bus, *name, &lp, false, &cached_name_map, &cached_id_map, &path, &unit_paths); + r = unit_find_paths(bus, *name, &lp, /* force_client_side= */ false, &cached_name_map, &cached_id_map, &path, &unit_paths); if (r == -EKEYREJECTED) { /* If loading of the unit failed server side complete, then the server won't tell us * the unit file path. In that case, find the file client side. */ + log_debug_errno(r, "Unit '%s' was not loaded correctly, retrying client-side.", *name); - r = unit_find_paths(bus, *name, &lp, true, &cached_name_map, &cached_id_map, &path, &unit_paths); + r = unit_find_paths(bus, *name, &lp, /* force_client_side= */ true, &cached_name_map, &cached_id_map, &path, &unit_paths); } if (r == -ERFKILL) return log_error_errno(r, "Unit '%s' masked, cannot edit.", *name); if (r < 0) - return r; + return r; /* Already logged by unit_find_paths() */ if (!path) { if (!arg_force) { From f8970df54b7a28a20572a7e32372791e9169c163 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Sun, 26 Feb 2023 02:09:24 +0800 Subject: [PATCH 5/5] edit-util: several cleanups for run_editor run_editor is now switched to heap allocation for simplicity. The code for child is made into an individual function for simpler error handling. --- src/shared/edit-util.c | 132 ++++++++++++++++++++--------------------- 1 file changed, 65 insertions(+), 67 deletions(-) diff --git a/src/shared/edit-util.c b/src/shared/edit-util.c index 38a1dfc089..822175a472 100644 --- a/src/shared/edit-util.c +++ b/src/shared/edit-util.c @@ -219,6 +219,68 @@ static int create_edit_temp_file( return 0; } +static int run_editor_child(const EditFileContext *context) { + _cleanup_strv_free_ char **args = NULL; + const char *editor; + int r; + + /* 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)) { + _cleanup_strv_free_ char **editor_args = NULL; + + editor_args = strv_split(editor, WHITESPACE); + if (!editor_args) + return log_oom(); + + args = TAKE_PTR(editor_args); + } + + if (context->n_files == 1 && context->files[0].line > 1) { + /* If editing a single file only, use the +LINE syntax to put cursor on the right line */ + r = strv_extendf(&args, "+%u", context->files[0].line); + if (r < 0) + return log_oom(); + } + + FOREACH_ARRAY(i, context->files, context->n_files) { + r = strv_extend(&args, i->temp); + if (r < 0) + return log_oom(); + } + + if (!isempty(editor)) + execvp(args[0], (char* const*) args); + + bool prepended = false; + FOREACH_STRING(name, "editor", "nano", "vim", "vi") { + if (!prepended) { + r = strv_prepend(&args, name); + prepended = true; + } else + r = free_and_strdup(&args[0], name); + if (r < 0) + return log_oom(); + + execvp(args[0], (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) + return log_error_errno(errno, "Failed to execute '%s': %m", name); + } + + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), + "Cannot edit files, no editor available. Please set either $SYSTEMD_EDITOR, $EDITOR or $VISUAL."); +} + static int run_editor(const EditFileContext *context) { int r; @@ -227,73 +289,9 @@ static int run_editor(const EditFileContext *context) { r = safe_fork("(editor)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG|FORK_WAIT, NULL); if (r < 0) return r; - if (r == 0) { - size_t n_editor_args = 0, i = 1, argc; - char **editor_args = NULL, **args; - const 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 (isempty(editor)) - argc = 1; - else { - editor_args = strv_split(editor, WHITESPACE); - if (!editor_args) { - (void) log_oom(); - _exit(EXIT_FAILURE); - } - n_editor_args = strv_length(editor_args); - argc = n_editor_args; - } - - argc += context->n_files * 2; - - args = newa(char*, argc + 1); - - if (n_editor_args > 0) { - args[0] = editor_args[0]; - for (; i < n_editor_args; i++) - args[i] = editor_args[i]; - } - - if (context->n_files == 1 && context->files[0].line > 1) { - /* If editing a single file only, use the +LINE syntax to put cursor on the right line */ - if (asprintf(args + i, "+%u", context->files[0].line) < 0) { - (void) log_oom(); - _exit(EXIT_FAILURE); - } - - i++; - args[i++] = context->files[0].temp; - } else - FOREACH_ARRAY(f, context->files, context->n_files) - args[i++] = f->temp; - - args[i] = NULL; - - if (n_editor_args > 0) - execvp(args[0], (char* const*) args); - - FOREACH_STRING(name, "editor", "nano", "vim", "vi") { - args[0] = (char*) name; - execvp(name, (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_errno(errno, "Failed to execute %s: %m", name); - _exit(EXIT_FAILURE); - } - } - - log_error("Cannot edit files, no editor available. Please set either $SYSTEMD_EDITOR, $EDITOR or $VISUAL."); - _exit(EXIT_FAILURE); + if (r == 0) { /* Child */ + r = run_editor_child(context); + _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); } return 0;