From: Zbigniew Jędrzejewski-Szmek Date: Sat, 2 Dec 2023 15:25:15 +0000 (+0100) Subject: systemctl: add "edit --stdin" X-Git-Tag: v256-rc1~1566^2~1 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=329050c5e2c7e9561699f87b5edb72edd0d54c96;p=thirdparty%2Fsystemd.git systemctl: add "edit --stdin" This is a fancy wrapper around "cat <edit UNIT… - Edit a drop-in snippet or a whole replacement file if - is specified, to extend or override the - specified unit. - - Depending on whether (the default), - , or is specified, - this command creates a drop-in file for each unit either for the system, - for the calling user, or for all futures logins of all users. Then, - the editor (see the "Environment" section below) is invoked on - temporary files which will be written to the real location if the - editor exits successfully. + Edit or replace a drop-in snippet or the main unit file, to extend or override the + definition of the specified unit. + + Depending on whether (the default), , or + is specified, this command will operate on the system unit files, unit + files for the calling user, or the unit files shared between all users. + + The editor (see the "Environment" section below) is invoked on temporary files which will + be written to the real location if the editor exits successfully. After the editing is finished, + configuration is reloaded, equivalent to systemctl daemon-reload --system or + systemctl daemon-reload --user. For edit --global, the + reload is not performed and the edits will take effect only for subsequent logins (or after a + reload is requested in a different way). + + If is specified, a replacement for the main unit file will be + created or edited. Otherwise, a drop-in file will be created or edited. If is specified, the given drop-in file name will be used instead of the default override.conf. - If is specified, this will copy the - original units instead of creating drop-in files. - - If is specified and any units do - not already exist, new unit files will be opened for editing. + The unit must exist, i.e. its main unit file must be present. If + is specified, this requirement is ignored and a new unit may be created (with + ), or a drop-in for a nonexistent unit may be crated. If is specified, the changes will be made temporarily in /run/ and they will be lost on the next reboot. + If is specified, the new contents will be read from standard + input. In this mode, the old contents of the file are discarded. + If the temporary file is empty upon exit, the modification of the related unit is canceled. - After the units have been edited, systemd configuration is - reloaded (in a way that is equivalent to daemon-reload). - - Note that this command cannot be used to remotely edit units and that you cannot temporarily edit units which are in /etc/, since they take precedence over @@ -2764,6 +2766,27 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err + + + + + When used with edit, the contents of the file will be read from standard + input and the editor will not be launched. In this mode, the old contents of the file are + completely replaced. This is useful to "edit" unit files from scripts: + + $ systemctl edit --drop-in=limits.conf --stdin some-service.service <<EOF +[Unit] +AllowedCPUs=7,11 +EOF + + + Multiple drop-ins may be "edited" in this mode; the same contents will be written to all of + them. + + + + + diff --git a/src/shared/edit-util.c b/src/shared/edit-util.c index dd77b0e20e3..2662b2bf1b3 100644 --- a/src/shared/edit-util.c +++ b/src/shared/edit-util.c @@ -178,7 +178,7 @@ static int populate_edit_temp_file(EditFile *e, FILE *f, const char *filename) { return 0; } -static int create_edit_temp_file(EditFile *e) { +static int create_edit_temp_file(EditFile *e, const char *contents, size_t contents_size) { _cleanup_(unlink_and_freep) char *temp = NULL; _cleanup_fclose_ FILE *f = NULL; int r; @@ -187,6 +187,7 @@ static int create_edit_temp_file(EditFile *e) { assert(e->context); assert(e->path); assert(!e->comment_paths || (e->context->marker_start && e->context->marker_end)); + assert(contents || contents_size == 0); if (e->temp) return 0; @@ -202,9 +203,15 @@ static int create_edit_temp_file(EditFile *e) { if (fchmod(fileno(f), 0644) < 0) return log_error_errno(errno, "Failed to change mode of temporary file '%s': %m", temp); - r = populate_edit_temp_file(e, f, temp); - if (r < 0) - return r; + if (e->context->stdin) { + if (fwrite(contents, 1, contents_size, f) != contents_size) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to copy input to temporary file '%s': %m", temp); + } else { + r = populate_edit_temp_file(e, f, temp); + if (r < 0) + return r; + } r = fflush_and_check(f); if (r < 0) @@ -310,7 +317,7 @@ static int strip_edit_temp_file(EditFile *e) { if (!tmp) return log_oom(); - if (e->context->marker_start) { + if (e->context->marker_start && !e->context->stdin) { /* Trim out the lines between the two markers */ char *contents_start, *contents_end; @@ -349,6 +356,8 @@ static int strip_edit_temp_file(EditFile *e) { } int do_edit_files_and_install(EditFileContext *context) { + _cleanup_free_ char *data = NULL; + size_t data_size = 0; int r; assert(context); @@ -356,33 +365,41 @@ int do_edit_files_and_install(EditFileContext *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) { - r = create_edit_temp_file(i); + if (context->stdin) { + r = read_full_stream(stdin, &data, &data_size); + if (r < 0) + return log_error_errno(r, "Failed to read stdin: %m"); + } + + FOREACH_ARRAY(editfile, context->files, context->n_files) { + r = create_edit_temp_file(editfile, data, data_size); if (r < 0) return r; } - r = run_editor(context); - if (r < 0) - return r; + if (!context->stdin) { + r = run_editor(context); + if (r < 0) + return r; + } - FOREACH_ARRAY(i, context->files, context->n_files) { + FOREACH_ARRAY(editfile, context->files, context->n_files) { /* Always call strip_edit_temp_file which will tell if the temp file has actual changes */ - r = strip_edit_temp_file(i); + r = strip_edit_temp_file(editfile); 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)); + r = RET_NERRNO(rename(editfile->temp, editfile->path)); if (r < 0) return log_error_errno(r, "Failed to rename temporary file '%s' to target file '%s': %m", - i->temp, - i->path); - i->temp = mfree(i->temp); + editfile->temp, + editfile->path); + editfile->temp = mfree(editfile->temp); - log_info("Successfully installed edited file '%s'.", i->path); + log_info("Successfully installed edited file '%s'.", editfile->path); } return 0; diff --git a/src/shared/edit-util.h b/src/shared/edit-util.h index 83b3df86839..29ff81f4e74 100644 --- a/src/shared/edit-util.h +++ b/src/shared/edit-util.h @@ -24,7 +24,8 @@ struct EditFileContext { const char *marker_start; const char *marker_end; bool remove_parent; - bool overwrite_with_origin; /* whether to always overwrite target with original file */ + bool overwrite_with_origin; /* Always overwrite target with original file. */ + bool stdin; /* Read contents from stdin instead of launching an editor. */ }; void edit_file_context_done(EditFileContext *context); diff --git a/src/systemctl/systemctl-edit.c b/src/systemctl/systemctl-edit.c index 367afa20f7a..d967b1649ef 100644 --- a/src/systemctl/systemctl-edit.c +++ b/src/systemctl/systemctl-edit.c @@ -317,12 +317,13 @@ int verb_edit(int argc, char *argv[], void *userdata) { .marker_end = DROPIN_MARKER_END, .remove_parent = !arg_full, .overwrite_with_origin = true, + .stdin = arg_stdin, }; _cleanup_strv_free_ char **names = NULL; sd_bus *bus; int r; - if (!on_tty()) + if (!on_tty() && !arg_stdin) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit units if not on a tty."); if (arg_transport != BUS_TRANSPORT_LOCAL) @@ -342,6 +343,10 @@ int verb_edit(int argc, char *argv[], void *userdata) { if (strv_isempty(names)) return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No units matched the specified patterns."); + if (arg_stdin && arg_full && strv_length(names) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "With 'edit --stdin --full', exactly one unit for editing must be specified."); + STRV_FOREACH(tmp, names) { r = unit_is_masked(bus, *tmp); if (r < 0) diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index dd6f6c9873e..2a1d2d7967e 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -103,6 +103,7 @@ bool arg_kill_value_set = false; char *arg_root = NULL; char *arg_image = NULL; usec_t arg_when = 0; +bool arg_stdin = false; const char *arg_reboot_argument = NULL; enum action arg_action = ACTION_SYSTEMCTL; BusTransport arg_transport = BUS_TRANSPORT_LOCAL; @@ -335,6 +336,7 @@ static int systemctl_help(void) { " --drop-in=NAME Edit unit files using the specified drop-in file name\n" " --when=TIME Schedule halt/power-off/reboot/kexec action after\n" " a certain timestamp\n" + " --stdin Read contents of edited file from stdin\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -461,6 +463,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { ARG_NO_WARN, ARG_DROP_IN, ARG_WHEN, + ARG_STDIN, }; static const struct option options[] = { @@ -527,6 +530,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { { "marked", no_argument, NULL, ARG_MARKED }, { "drop-in", required_argument, NULL, ARG_DROP_IN }, { "when", required_argument, NULL, ARG_WHEN }, + { "stdin", no_argument, NULL, ARG_STDIN }, {} }; @@ -1017,6 +1021,10 @@ static int systemctl_parse_argv(int argc, char *argv[]) { break; + case ARG_STDIN: + arg_stdin = true; + break; + case '.': /* Output an error mimicking getopt, and print a hint afterwards */ log_error("%s: invalid option -- '.'", program_invocation_name); @@ -1067,7 +1075,8 @@ static int systemctl_parse_argv(int argc, char *argv[]) { } if (arg_image && arg_root) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported."); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Please specify either --root= or --image=, the combination of both is not supported."); return 1; } diff --git a/src/systemctl/systemctl.h b/src/systemctl/systemctl.h index e8ba8f76a07..9f63443fde9 100644 --- a/src/systemctl/systemctl.h +++ b/src/systemctl/systemctl.h @@ -83,6 +83,7 @@ extern int arg_kill_value; extern bool arg_kill_value_set; extern char *arg_root; extern usec_t arg_when; +extern bool arg_stdin; extern const char *arg_reboot_argument; extern enum action arg_action; extern BusTransport arg_transport;