diff --git a/src/shared/edit-util.c b/src/shared/edit-util.c new file mode 100644 index 0000000000..822175a472 --- /dev/null +++ b/src/shared/edit-util.c @@ -0,0 +1,383 @@ +/* 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_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); + } + + free(i->path); + free(i->original_path); + strv_free(i->comment_paths); + } + + context->files = mfree(context->files); + context->n_files = 0; +} + +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, + const char *marker_start, + const char *marker_end, + char **ret_temp_filename, + unsigned *ret_edit_line) { + + _cleanup_free_ char *temp = NULL; + unsigned line = 1; + int r; + + assert(target_path); + assert(!comment_paths || (marker_start && marker_end)); + assert(ret_temp_filename); + + r = tempfn_random(target_path, NULL, &temp); + if (r < 0) + return log_error_errno(r, "Failed to determine temporary filename for \"%s\": %m", target_path); + + r = mkdir_parents_label(target_path, 0755); + if (r < 0) + return log_error_errno(r, "Failed to create parent directories for \"%s\": %m", target_path); + + if (original_path) { + r = mac_selinux_create_file_prepare(target_path, S_IFREG); + if (r < 0) + return r; + + r = copy_file(original_path, temp, 0, 0644, 0, 0, COPY_REFLINK); + if (r == -ENOENT) { + r = touch(temp); + mac_selinux_create_file_clear(); + if (r < 0) + 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", target_path); + } + } + + if (comment_paths) { + _cleanup_free_ char *target_contents = NULL; + _cleanup_fclose_ FILE *f = NULL; + + r = mac_selinux_create_file_prepare(target_path, S_IFREG); + if (r < 0) + return r; + + f = fopen(temp, "we"); + mac_selinux_create_file_clear(); + if (!f) + 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 temporary file \"%s\": %m", temp); + + r = read_full_file(target_path, &target_contents, NULL); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to read target file \"%s\": %m", target_path); + + fprintf(f, + "### Editing %s\n" + "%s\n" + "\n" + "%s%s" + "\n" + "%s\n", + target_path, + marker_start, + strempty(target_contents), + target_contents && endswith(target_contents, "\n") ? "" : "\n", + marker_end); + + line = 4; /* Start editing at the contents area */ + + /* 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, 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 original file \"%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", temp); + } + + *ret_temp_filename = TAKE_PTR(temp); + + if (ret_edit_line) + *ret_edit_line = line; + + 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; + + assert(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) { /* Child */ + r = run_editor_child(context); + _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); + } + + return 0; +} + +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; + 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) + 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 */ +} + +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 new file mode 100644 index 0000000000..7b705060e4 --- /dev/null +++ b/src/shared/edit-util.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "path-lookup.h" + +typedef struct EditFile { + char *path; + char *original_path; + char **comment_paths; + char *temp; + unsigned line; +} EditFile; + +typedef struct EditFileContext { + EditFile *files; + size_t n_files; + const char *marker_start; + const char *marker_end; + bool remove_parent; +} EditFileContext; + +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); + +int do_edit_files_and_install(EditFileContext *context); 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..d85abbd165 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,128 +104,25 @@ 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 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(); @@ -247,71 +138,50 @@ 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, + EditFileContext *context, + const LookupPaths *lp, 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; - const char *ending; + _cleanup_free_ char *unit = NULL, *new_path = NULL; int r; + assert(context); + assert(lp); assert(unit_name); - assert(ret_edit_file); - 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; - r = create_edit_temp_file(new_path, NULL, original_unit_paths, &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( - const LookupPaths *paths, + EditFileContext *context, + const LookupPaths *lp, 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(lp); assert(fragment_path); assert(unit_name); - assert(ret_edit_file); - 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; @@ -321,118 +191,32 @@ 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); } - r = create_edit_temp_file(new_path, fragment_path, NULL, &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 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, - 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"; @@ -460,20 +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; - - if (!GREEDY_REALLOC0(edit_files, n_edit_files + 2)) - return log_oom(); + return r; /* Already logged by unit_find_paths() */ if (!path) { if (!arg_force) { @@ -486,11 +268,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; @@ -516,74 +298,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; } -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_(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; @@ -621,36 +365,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); - 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); @@ -658,24 +379,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; }