Merge pull request #30302 from keszybz/systemd-edit-stdin

systemctl edit --stdin
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2023-12-06 22:28:02 +01:00 committed by GitHub
commit 34f4fcb59f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 247 additions and 157 deletions

View file

@ -513,7 +513,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
<!-- Note that we don't document force-reload here, as that is just compatibility support, and we generally
don't document that. -->
<xi:include href="version-info.xml" xpointer="v229"/>
<xi:include href="version-info.xml" xpointer="v229"/>
</listitem>
</varlistentry>
<varlistentry>
@ -1066,8 +1066,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
</tgroup>
</table>
<xi:include href="version-info.xml" xpointer="v238"/>
<xi:include href="version-info.xml" xpointer="v238"/>
</listitem>
</varlistentry>
@ -1171,7 +1170,6 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
<command>enable</command>.</para>
<xi:include href="version-info.xml" xpointer="v217"/>
</listitem>
</varlistentry>
@ -1179,38 +1177,40 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
<term><command>edit <replaceable>UNIT</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>Edit or replace a drop-in snippet or the main unit file, to extend or override the
definition of the specified unit.</para>
<para>Depending on whether <option>--system</option> (the default),
<option>--user</option>, or <option>--global</option> 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.</para>
<para>Depending on whether <option>--system</option> (the default), <option>--user</option>, or
<option>--global</option> 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.</para>
<para>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 <command>systemctl daemon-reload --system</command> or
<command>systemctl daemon-reload --user</command>. For <command>edit --global</command>, 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).</para>
<para>If <option>--full</option> is specified, a replacement for the main unit file will be
created or edited. Otherwise, a drop-in file will be created or edited.</para>
<para>If <option>--drop-in=</option> is specified, the given drop-in file name
will be used instead of the default <filename>override.conf</filename>.</para>
<para>If <option>--full</option> is specified, this will copy the
original units instead of creating drop-in files.</para>
<para>If <option>--force</option> is specified and any units do
not already exist, new unit files will be opened for editing.</para>
<para>The unit must exist, i.e. its main unit file must be present. If <option>--force</option>
is specified, this requirement is ignored and a new unit may be created (with
<option>--full</option>), or a drop-in for a nonexistent unit may be crated.</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 <option>--stdin</option> is specified, the new contents will be read from standard
input. In this mode, the old contents of the file are discarded.</para>
<para>If the temporary file is empty upon exit, the modification of
the related unit is canceled.</para>
<para>After the units have been edited, systemd configuration is
reloaded (in a way that is equivalent to <command>daemon-reload</command>).
</para>
<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
@ -1565,7 +1565,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
</tgroup>
</table>
<xi:include href="version-info.xml" xpointer="v215"/>
<xi:include href="version-info.xml" xpointer="v215"/>
</listitem>
</varlistentry>
@ -1949,7 +1949,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
<option>-P</option> once will also affect all properties listed with
<option>-p</option>/<option>--property=</option>.</para>
<xi:include href="version-info.xml" xpointer="v246"/>
<xi:include href="version-info.xml" xpointer="v246"/>
</listitem>
</varlistentry>
@ -2091,7 +2091,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
<para>When printing properties with <command>show</command>, only print the value, and skip the
property name and <literal>=</literal>. Also see option <option>-P</option> above.</para>
<xi:include href="version-info.xml" xpointer="v230"/>
<xi:include href="version-info.xml" xpointer="v230"/>
</listitem>
</varlistentry>
@ -2101,7 +2101,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
<listitem>
<para>When showing sockets, show the type of the socket.</para>
<xi:include href="version-info.xml" xpointer="v202"/>
<xi:include href="version-info.xml" xpointer="v202"/>
</listitem>
</varlistentry>
@ -2109,72 +2109,72 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
<term><option>--job-mode=</option></term>
<listitem>
<para>When queuing a new job, this option controls how to deal with
already queued jobs. It takes one of <literal>fail</literal>,
<literal>replace</literal>,
<literal>replace-irreversibly</literal>,
<literal>isolate</literal>,
<literal>ignore-dependencies</literal>,
<literal>ignore-requirements</literal>,
<literal>flush</literal>,
<literal>triggering</literal>, or
<literal>restart-dependencies</literal>. Defaults to
<literal>replace</literal>, except when the
<command>isolate</command> command is used which implies the
<literal>isolate</literal> job mode.</para>
<para>When queuing a new job, this option controls how to deal with
already queued jobs. It takes one of <literal>fail</literal>,
<literal>replace</literal>,
<literal>replace-irreversibly</literal>,
<literal>isolate</literal>,
<literal>ignore-dependencies</literal>,
<literal>ignore-requirements</literal>,
<literal>flush</literal>,
<literal>triggering</literal>, or
<literal>restart-dependencies</literal>. Defaults to
<literal>replace</literal>, except when the
<command>isolate</command> command is used which implies the
<literal>isolate</literal> job mode.</para>
<para>If <literal>fail</literal> is specified and a requested
operation conflicts with a pending job (more specifically:
causes an already pending start job to be reversed into a stop
job or vice versa), cause the operation to fail.</para>
<para>If <literal>fail</literal> is specified and a requested
operation conflicts with a pending job (more specifically:
causes an already pending start job to be reversed into a stop
job or vice versa), cause the operation to fail.</para>
<para>If <literal>replace</literal> (the default) is
specified, any conflicting pending job will be replaced, as
necessary.</para>
<para>If <literal>replace</literal> (the default) is
specified, any conflicting pending job will be replaced, as
necessary.</para>
<para>If <literal>replace-irreversibly</literal> is specified,
operate like <literal>replace</literal>, but also mark the new
jobs as irreversible. This prevents future conflicting
transactions from replacing these jobs (or even being enqueued
while the irreversible jobs are still pending). Irreversible
jobs can still be cancelled using the <command>cancel</command>
command. This job mode should be used on any transaction which
pulls in <filename>shutdown.target</filename>.</para>
<para>If <literal>replace-irreversibly</literal> is specified,
operate like <literal>replace</literal>, but also mark the new
jobs as irreversible. This prevents future conflicting
transactions from replacing these jobs (or even being enqueued
while the irreversible jobs are still pending). Irreversible
jobs can still be cancelled using the <command>cancel</command>
command. This job mode should be used on any transaction which
pulls in <filename>shutdown.target</filename>.</para>
<para><literal>isolate</literal> is only valid for start
operations and causes all other units to be stopped when the
specified unit is started. This mode is always used when the
<command>isolate</command> command is used.</para>
<para><literal>isolate</literal> is only valid for start
operations and causes all other units to be stopped when the
specified unit is started. This mode is always used when the
<command>isolate</command> command is used.</para>
<para><literal>flush</literal> will cause all queued jobs to
be canceled when the new job is enqueued.</para>
<para><literal>flush</literal> will cause all queued jobs to
be canceled when the new job is enqueued.</para>
<para>If <literal>ignore-dependencies</literal> is specified,
then all unit dependencies are ignored for this new job and
the operation is executed immediately. If passed, no required
units of the unit passed will be pulled in, and no ordering
dependencies will be honored. This is mostly a debugging and
rescue tool for the administrator and should not be used by
applications.</para>
<para>If <literal>ignore-dependencies</literal> is specified,
then all unit dependencies are ignored for this new job and
the operation is executed immediately. If passed, no required
units of the unit passed will be pulled in, and no ordering
dependencies will be honored. This is mostly a debugging and
rescue tool for the administrator and should not be used by
applications.</para>
<para><literal>ignore-requirements</literal> is similar to
<literal>ignore-dependencies</literal>, but only causes the
requirement dependencies to be ignored, the ordering
dependencies will still be honored.</para>
<para><literal>ignore-requirements</literal> is similar to
<literal>ignore-dependencies</literal>, but only causes the
requirement dependencies to be ignored, the ordering
dependencies will still be honored.</para>
<para><literal>triggering</literal> may only be used with
<command>systemctl stop</command>. In this mode, the specified
unit and any active units that trigger it are stopped. See the
discussion of
<varname>Triggers=</varname> in <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for more information about triggering units.</para>
<para><literal>triggering</literal> may only be used with
<command>systemctl stop</command>. In this mode, the specified
unit and any active units that trigger it are stopped. See the
discussion of
<varname>Triggers=</varname> in <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for more information about triggering units.</para>
<para><literal>restart-dependencies</literal> may only be used with
<command>systemctl start</command>. In this mode, dependencies of
the specified unit will receive restart propagation, as if a restart
job had been enqueued for the unit.</para>
<para><literal>restart-dependencies</literal> may only be used with
<command>systemctl start</command>. In this mode, dependencies of
the specified unit will receive restart propagation, as if a restart
job had been enqueued for the unit.</para>
<xi:include href="version-info.xml" xpointer="v209"/>
<xi:include href="version-info.xml" xpointer="v209"/>
</listitem>
</varlistentry>
@ -2190,7 +2190,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
run as effect of the enqueued jobs might request further jobs to be pulled in. This means that
completion of the listed jobs might ultimately entail more jobs than the listed ones.</para>
<xi:include href="version-info.xml" xpointer="v242"/>
<xi:include href="version-info.xml" xpointer="v242"/>
</listitem>
</varlistentry>
@ -2237,7 +2237,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
<listitem>
<para>Shortcut for <option>--check-inhibitors=no</option>.</para>
<xi:include href="version-info.xml" xpointer="v198"/>
<xi:include href="version-info.xml" xpointer="v198"/>
</listitem>
</varlistentry>
@ -2414,7 +2414,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
<filename>&UMOUNT_PATH;</filename>), but no main process is defined. If omitted, defaults to
<option>all</option>.</para>
<xi:include href="version-info.xml" xpointer="v252"/>
<xi:include href="version-info.xml" xpointer="v252"/>
</listitem>
</varlistentry>
@ -2458,7 +2458,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
<varname>FileDescriptorStorePreserve=</varname> option is enabled, since the file descriptor store
is otherwise cleaned automatically when the unit is stopped.</para>
<xi:include href="version-info.xml" xpointer="v243"/>
<xi:include href="version-info.xml" xpointer="v243"/>
</listitem>
</varlistentry>
@ -2494,7 +2494,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
short message explaining the reason for the operation. The message will be logged together with the default
shutdown message.</para>
<xi:include href="version-info.xml" xpointer="v225"/>
<xi:include href="version-info.xml" xpointer="v225"/>
</listitem>
</varlistentry>
@ -2508,7 +2508,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
or stop operation is only carried out when the respective enable or
disable operation has been successful.</para>
<xi:include href="version-info.xml" xpointer="v220"/>
<xi:include href="version-info.xml" xpointer="v220"/>
</listitem>
</varlistentry>
@ -2575,7 +2575,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
enabled according to the preset rules, or only enabled, or
only disabled.</para>
<xi:include href="version-info.xml" xpointer="v215"/>
<xi:include href="version-info.xml" xpointer="v215"/>
</listitem>
</varlistentry>
@ -2611,7 +2611,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
reboot into the firmware setup interface. Note that this functionality is not available on all
systems.</para>
<xi:include href="version-info.xml" xpointer="v220"/>
<xi:include href="version-info.xml" xpointer="v220"/>
</listitem>
</varlistentry>
@ -2624,7 +2624,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
menu timeout. Pass zero in order to disable the menu timeout. Note that not all boot loaders
support this functionality.</para>
<xi:include href="version-info.xml" xpointer="v242"/>
<xi:include href="version-info.xml" xpointer="v242"/>
</listitem>
</varlistentry>
@ -2637,7 +2637,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
as argument, or <literal>help</literal> in order to list available entries. Note that not all boot
loaders support this functionality.</para>
<xi:include href="version-info.xml" xpointer="v242"/>
<xi:include href="version-info.xml" xpointer="v242"/>
</listitem>
</varlistentry>
@ -2645,11 +2645,12 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
<term><option>--reboot-argument=</option></term>
<listitem>
<para>This switch is used with <command>reboot</command>. The value is architecture and firmware specific. As an example, <literal>recovery</literal>
might be used to trigger system recovery, and <literal>fota</literal> might be used to trigger a
<quote>firmware over the air</quote> update.</para>
<para>This switch is used with <command>reboot</command>. The value is architecture and firmware
specific. As an example, <literal>recovery</literal> might be used to trigger system recovery, and
<literal>fota</literal> might be used to trigger a <quote>firmware over the air</quote>
update.</para>
<xi:include href="version-info.xml" xpointer="v246"/>
<xi:include href="version-info.xml" xpointer="v246"/>
</listitem>
</varlistentry>
@ -2662,7 +2663,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
the output is printed as a list instead of a tree, and the bullet
circles are omitted.</para>
<xi:include href="version-info.xml" xpointer="v203"/>
<xi:include href="version-info.xml" xpointer="v203"/>
</listitem>
</varlistentry>
@ -2720,7 +2721,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
</varlistentry>
</variablelist>
<xi:include href="version-info.xml" xpointer="v247"/>
<xi:include href="version-info.xml" xpointer="v247"/>
</listitem>
</varlistentry>
@ -2779,7 +2780,28 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
section "PARSING TIMESTAMPS". Specially, if <literal>show</literal> is given, the currently scheduled
action will be shown, which can be canceled by passing an empty string or <literal>cancel</literal>.</para>
<xi:include href="version-info.xml" xpointer="v254"/>
<xi:include href="version-info.xml" xpointer="v254"/>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--stdin</option></term>
<listitem>
<para>When used with <command>edit</command>, 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:</para>
<programlisting>$ systemctl edit --drop-in=limits.conf --stdin some-service.service &lt;&lt;EOF
[Unit]
AllowedCPUs=7,11
EOF
</programlisting>
<para>Multiple drop-ins may be "edited" in this mode; the same contents will be written to all of
them.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
</varlistentry>

View file

@ -93,41 +93,22 @@ int edit_files_add(
.path = TAKE_PTR(new_path),
.original_path = TAKE_PTR(new_original_path),
.comment_paths = TAKE_PTR(new_comment_paths),
.line = 1,
};
context->n_files++;
return 1;
}
static int create_edit_temp_file(EditFile *e) {
_cleanup_(unlink_and_freep) char *temp = NULL;
_cleanup_fclose_ FILE *f = NULL;
const char *source;
bool has_original, has_target;
unsigned line = 1;
int r;
static int populate_edit_temp_file(EditFile *e, FILE *f, const char *filename) {
assert(e);
assert(e->context);
assert(e->path);
assert(!e->comment_paths || (e->context->marker_start && e->context->marker_end));
assert(f);
assert(filename);
if (e->temp)
return 0;
r = mkdir_parents_label(e->path, 0755);
if (r < 0)
return log_error_errno(r, "Failed to create parent directories for '%s': %m", e->path);
r = fopen_temporary_label(e->path, e->path, &f, &temp);
if (r < 0)
return log_error_errno(r, "Failed to create temporary file for '%s': %m", e->path);
if (fchmod(fileno(f), 0644) < 0)
return log_error_errno(errno, "Failed to change mode of temporary file '%s': %m", temp);
has_original = e->original_path && access(e->original_path, F_OK) >= 0;
has_target = access(e->path, F_OK) >= 0;
bool has_original = e->original_path && access(e->original_path, F_OK) >= 0;
bool has_target = access(e->path, F_OK) >= 0;
const char *source;
int r;
if (has_original && (!has_target || e->context->overwrite_with_origin))
/* We are asked to overwrite target with original_path or target doesn't exist. */
@ -160,7 +141,7 @@ static int create_edit_temp_file(EditFile *e) {
source_contents && endswith(source_contents, "\n") ? "" : "\n",
e->context->marker_end);
line = 4; /* Start editing at the contents area */
e->line = 4; /* Start editing at the contents area */
STRV_FOREACH(path, e->comment_paths) {
_cleanup_free_ char *comment = NULL;
@ -189,16 +170,54 @@ static int create_edit_temp_file(EditFile *e) {
r = copy_file_fd(source, fileno(f), COPY_REFLINK);
if (r < 0) {
assert(r != -ENOENT);
return log_error_errno(r, "Failed to copy file '%s' to temporary file '%s': %m", source, temp);
return log_error_errno(r, "Failed to copy file '%s' to temporary file '%s': %m",
source, filename);
}
}
return 0;
}
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;
assert(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;
r = mkdir_parents_label(e->path, 0755);
if (r < 0)
return log_error_errno(r, "Failed to create parent directories for '%s': %m", e->path);
r = fopen_temporary_label(e->path, e->path, &f, &temp);
if (r < 0)
return log_error_errno(r, "Failed to create temporary file for '%s': %m", e->path);
if (fchmod(fileno(f), 0644) < 0)
return log_error_errno(errno, "Failed to change mode of temporary file '%s': %m", temp);
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)
return log_error_errno(r, "Failed to write to temporary file '%s': %m", temp);
e->temp = TAKE_PTR(temp);
e->line = line;
return 0;
}
@ -282,7 +301,7 @@ static int run_editor(const EditFileContext *context) {
}
static int strip_edit_temp_file(EditFile *e) {
_cleanup_free_ char *old_contents = NULL, *new_contents = NULL;
_cleanup_free_ char *old_contents = NULL, *tmp = NULL, *new_contents = NULL;
const char *stripped;
int r;
@ -294,15 +313,17 @@ static int strip_edit_temp_file(EditFile *e) {
if (r < 0)
return log_error_errno(r, "Failed to read temporary file '%s': %m", e->temp);
if (e->context->marker_start) {
tmp = strdup(old_contents);
if (!tmp)
return log_oom();
if (e->context->marker_start && !e->context->stdin) {
/* Trim out the lines between the two markers */
char *contents_start, *contents_end;
assert(e->context->marker_end);
contents_start = strstrafter(old_contents, e->context->marker_start);
if (!contents_start)
contents_start = old_contents;
contents_start = strstrafter(tmp, e->context->marker_start) ?: tmp;
contents_end = strstr(contents_start, e->context->marker_end);
if (contents_end)
@ -310,9 +331,13 @@ static int strip_edit_temp_file(EditFile *e) {
stripped = strstrip(contents_start);
} else
stripped = strstrip(old_contents);
if (isempty(stripped))
return 0; /* File is empty (has no real changes) */
stripped = strstrip(tmp);
if (isempty(stripped)) {
/* File is empty (has no real changes) */
log_notice("%s: after editing, new contents are empty, not writing file.", e->path);
return 0;
}
/* Trim prefix and suffix, but ensure suffixed by single newline */
new_contents = strjoin(stripped, "\n");
@ -320,16 +345,19 @@ static int strip_edit_temp_file(EditFile *e) {
return log_oom();
if (streq(old_contents, new_contents)) /* Don't touch the file if the above didn't change a thing */
return 1; /* Contents unchanged after stripping but has changes */
return 1; /* Contents have real changes */
r = write_string_file(e->temp, new_contents, WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_TRUNCATE | WRITE_STRING_FILE_AVOID_NEWLINE);
r = write_string_file(e->temp, 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 strip temporary file '%s': %m", e->temp);
return 1; /* Contents have real changes and are changed after stripping */
return 1; /* Contents have real changes */
}
int do_edit_files_and_install(EditFileContext *context) {
_cleanup_free_ char *data = NULL;
size_t data_size = 0;
int r;
assert(context);
@ -337,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;

View file

@ -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);

View file

@ -168,4 +168,4 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) {
return verb->dispatch(1, STRV_MAKE(verb->verb), userdata);
return verb->dispatch(left, argv, userdata);
}
}

View file

@ -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)

View file

@ -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;
@ -337,6 +338,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,
@ -463,6 +465,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[] = {
@ -529,6 +532,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 },
{}
};
@ -1019,6 +1023,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);
@ -1069,7 +1077,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;
}

View file

@ -84,6 +84,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;

View file

@ -22,6 +22,8 @@ trap at_exit EXIT
# Note: the service file is created under /usr on purpose to test
# the 'revert' verb as well
export UNIT_NAME="systemctl-test-$RANDOM.service"
export UNIT_NAME2="systemctl-test-$RANDOM.service"
cat >"/usr/lib/systemd/system/$UNIT_NAME" <<\EOF
[Unit]
Description=systemctl test
@ -56,6 +58,20 @@ printf '%b' '[Service]\n' 'ExecStart=\n' 'ExecStart=sleep 10d' >"+4"
EDITOR='mv' script -ec 'systemctl edit "$UNIT_NAME"' /dev/null
printf '%s\n' '[Service]' 'ExecStart=' 'ExecStart=sleep 10d' | cmp - "/etc/systemd/system/$UNIT_NAME.d/override.conf"
systemctl edit "$UNIT_NAME" --stdin --drop-in=override2.conf <<EOF
[Unit]
Description=spectacular
# this comment should remain
EOF
printf '%s\n' '[Unit]' 'Description=spectacular' '# this comment should remain' | \
cmp - "/etc/systemd/system/$UNIT_NAME.d/override2.conf"
# Test simultaneous editing of two units and creation of drop-in for a nonexistent unit
systemctl edit "$UNIT_NAME" "$UNIT_NAME2" --stdin --force --drop-in=override2.conf <<<'[X-Section]'
printf '%s\n' '[X-Section]' | cmp - "/etc/systemd/system/$UNIT_NAME.d/override2.conf"
printf '%s\n' '[X-Section]' | cmp - "/etc/systemd/system/$UNIT_NAME2.d/override2.conf"
# Double free when editing a template unit (#26483)
EDITOR='true' script -ec 'systemctl edit user@0' /dev/null