1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
8 #include "mkdir-label.h"
10 #include "path-util.h"
11 #include "pretty-print.h"
12 #include "process-util.h"
13 #include "selinux-util.h"
14 #include "stat-util.h"
15 #include "systemctl-daemon-reload.h"
16 #include "systemctl-edit.h"
17 #include "systemctl-util.h"
18 #include "systemctl.h"
19 #include "terminal-util.h"
20 #include "tmpfile-util.h"
22 #define EDIT_MARKER_START "### Anything between here and the comment below will become the new contents of the file"
23 #define EDIT_MARKER_END "### Lines below this comment will be discarded"
25 int verb_cat(int argc
, char *argv
[], void *userdata
) {
26 _cleanup_(hashmap_freep
) Hashmap
*cached_name_map
= NULL
, *cached_id_map
= NULL
;
27 _cleanup_(lookup_paths_free
) LookupPaths lp
= {};
28 _cleanup_strv_free_
char **names
= NULL
;
33 /* Include all units by default — i.e. continue as if the --all option was used */
34 if (strv_isempty(arg_states
))
37 if (arg_transport
!= BUS_TRANSPORT_LOCAL
)
38 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Cannot remotely cat units.");
40 r
= lookup_paths_init(&lp
, arg_scope
, 0, arg_root
);
42 return log_error_errno(r
, "Failed to determine unit paths: %m");
44 r
= acquire_bus(BUS_MANAGER
, &bus
);
48 r
= expand_unit_names(bus
, strv_skip(argv
, 1), NULL
, &names
, NULL
);
50 return log_error_errno(r
, "Failed to expand names: %m");
52 r
= maybe_extend_with_unit_dependencies(bus
, &names
);
56 pager_open(arg_pager_flags
);
58 STRV_FOREACH(name
, names
) {
59 _cleanup_free_
char *fragment_path
= NULL
;
60 _cleanup_strv_free_
char **dropin_paths
= NULL
;
62 r
= unit_find_paths(bus
, *name
, &lp
, false, &cached_name_map
, &cached_id_map
, &fragment_path
, &dropin_paths
);
64 printf("%s# Unit %s is masked%s.\n",
65 ansi_highlight_magenta(),
70 if (r
== -EKEYREJECTED
) {
71 printf("%s# Unit %s could not be loaded.%s\n",
72 ansi_highlight_magenta(),
80 /* Skip units which have no on-disk counterpart, but propagate the error to the
91 if (need_daemon_reload(bus
, *name
) > 0) /* ignore errors (<0), this is informational output */
93 "%s# Warning: %s changed on disk, the version systemd has loaded is outdated.\n"
94 "%s# This output shows the current version of the unit's original fragment and drop-in files.\n"
95 "%s# If fragments or drop-ins were added or removed, they are not properly reflected in this output.\n"
96 "%s# Run 'systemctl%s daemon-reload' to reload units.%s\n",
100 ansi_highlight_red(),
101 ansi_highlight_red(),
102 arg_scope
== UNIT_FILE_SYSTEM
? "" : " --user",
105 r
= cat_files(fragment_path
, dropin_paths
, 0);
113 static int create_edit_temp_file(const char *new_path
, const char *original_path
, char ** const original_unit_paths
, char **ret_tmp_fn
) {
114 _cleanup_free_
char *t
= NULL
;
120 r
= tempfn_random(new_path
, NULL
, &t
);
122 return log_error_errno(r
, "Failed to determine temporary filename for \"%s\": %m", new_path
);
124 r
= mkdir_parents_label(new_path
, 0755);
126 return log_error_errno(r
, "Failed to create directories for \"%s\": %m", new_path
);
129 r
= mac_selinux_create_file_prepare(new_path
, S_IFREG
);
133 r
= copy_file(original_path
, t
, 0, 0644, 0, 0, COPY_REFLINK
);
136 mac_selinux_create_file_clear();
138 return log_error_errno(r
, "Failed to create temporary file \"%s\": %m", t
);
140 mac_selinux_create_file_clear();
142 return log_error_errno(r
, "Failed to create temporary file for \"%s\": %m", new_path
);
144 } else if (original_unit_paths
) {
145 _cleanup_free_
char *new_contents
= NULL
;
146 _cleanup_fclose_
FILE *f
= NULL
;
148 r
= mac_selinux_create_file_prepare(new_path
, S_IFREG
);
153 mac_selinux_create_file_clear();
155 return log_error_errno(errno
, "Failed to open \"%s\": %m", t
);
157 r
= fchmod(fileno(f
), 0644);
159 return log_error_errno(errno
, "Failed to change mode of \"%s\": %m", t
);
161 r
= read_full_file(new_path
, &new_contents
, NULL
);
162 if (r
< 0 && r
!= -ENOENT
)
163 return log_error_errno(r
, "Failed to read \"%s\": %m", new_path
);
171 strempty(new_contents
),
172 new_contents
&& endswith(new_contents
, "\n") ? "" : "\n");
174 /* Add a comment with the contents of the original unit files */
175 STRV_FOREACH(path
, original_unit_paths
) {
176 _cleanup_free_
char *contents
= NULL
;
178 /* Skip the file that's being edited */
179 if (path_equal(*path
, new_path
))
182 r
= read_full_file(*path
, &contents
, NULL
);
184 return log_error_errno(r
, "Failed to read \"%s\": %m", *path
);
186 fprintf(f
, "\n\n### %s", *path
);
187 if (!isempty(contents
)) {
188 _cleanup_free_
char *commented_contents
= NULL
;
190 commented_contents
= strreplace(strstrip(contents
), "\n", "\n# ");
191 if (!commented_contents
)
193 fprintf(f
, "\n# %s", commented_contents
);
197 r
= fflush_and_check(f
);
199 return log_error_errno(r
, "Failed to create temporary file \"%s\": %m", t
);
202 *ret_tmp_fn
= TAKE_PTR(t
);
207 static int get_file_to_edit(
208 const LookupPaths
*paths
,
212 _cleanup_free_
char *path
= NULL
, *run
= NULL
;
217 path
= path_join(paths
->persistent_config
, name
);
222 run
= path_join(paths
->runtime_config
, name
);
228 if (access(path
, F_OK
) >= 0)
229 return log_error_errno(SYNTHETIC_ERRNO(EEXIST
),
230 "Refusing to create \"%s\" because it would be overridden by \"%s\" anyway.",
233 *ret_path
= TAKE_PTR(run
);
235 *ret_path
= TAKE_PTR(path
);
240 static int unit_file_create_new(
241 const LookupPaths
*paths
,
242 const char *unit_name
,
244 char ** const original_unit_paths
,
246 char **ret_tmp_path
) {
248 _cleanup_free_
char *new_path
= NULL
, *tmp_path
= NULL
;
253 assert(ret_new_path
);
254 assert(ret_tmp_path
);
256 ending
= strjoina(unit_name
, suffix
);
257 r
= get_file_to_edit(paths
, ending
, &new_path
);
261 r
= create_edit_temp_file(new_path
, NULL
, original_unit_paths
, &tmp_path
);
265 *ret_new_path
= TAKE_PTR(new_path
);
266 *ret_tmp_path
= TAKE_PTR(tmp_path
);
271 static int unit_file_create_copy(
272 const LookupPaths
*paths
,
273 const char *unit_name
,
274 const char *fragment_path
,
276 char **ret_tmp_path
) {
278 _cleanup_free_
char *new_path
= NULL
, *tmp_path
= NULL
;
281 assert(fragment_path
);
283 assert(ret_new_path
);
284 assert(ret_tmp_path
);
286 r
= get_file_to_edit(paths
, unit_name
, &new_path
);
290 if (!path_equal(fragment_path
, new_path
) && access(new_path
, F_OK
) >= 0) {
293 r
= ask_char(&response
, "yn", "\"%s\" already exists. Overwrite with \"%s\"? [(y)es, (n)o] ", new_path
, fragment_path
);
297 return log_warning_errno(SYNTHETIC_ERRNO(EKEYREJECTED
), "%s skipped.", unit_name
);
300 r
= create_edit_temp_file(new_path
, fragment_path
, NULL
, &tmp_path
);
304 *ret_new_path
= TAKE_PTR(new_path
);
305 *ret_tmp_path
= TAKE_PTR(tmp_path
);
310 static int run_editor(char **paths
) {
315 r
= safe_fork("(editor)", FORK_RESET_SIGNALS
|FORK_DEATHSIG
|FORK_RLIMIT_NOFILE_SAFE
|FORK_LOG
|FORK_WAIT
, NULL
);
319 char **editor_args
= NULL
;
320 size_t n_editor_args
= 0, i
= 1, argc
;
321 const char **args
, *editor
, *p
;
323 argc
= strv_length(paths
)/2 + 1;
325 /* SYSTEMD_EDITOR takes precedence over EDITOR which takes precedence over VISUAL. If
326 * neither SYSTEMD_EDITOR nor EDITOR nor VISUAL are present, we try to execute well known
328 editor
= getenv("SYSTEMD_EDITOR");
330 editor
= getenv("EDITOR");
332 editor
= getenv("VISUAL");
334 if (!isempty(editor
)) {
335 editor_args
= strv_split(editor
, WHITESPACE
);
340 n_editor_args
= strv_length(editor_args
);
341 argc
+= n_editor_args
- 1;
344 args
= newa(const char*, argc
+ 1);
346 if (n_editor_args
> 0) {
347 args
[0] = editor_args
[0];
348 for (; i
< n_editor_args
; i
++)
349 args
[i
] = editor_args
[i
];
352 STRV_FOREACH_PAIR(original_path
, tmp_path
, paths
)
353 args
[i
++] = *tmp_path
;
356 if (n_editor_args
> 0)
357 execvp(args
[0], (char* const*) args
);
359 FOREACH_STRING(p
, "editor", "nano", "vim", "vi") {
361 execvp(p
, (char* const*) args
);
362 /* We do not fail if the editor doesn't exist because we want to try each one of them
364 if (errno
!= ENOENT
) {
365 log_error_errno(errno
, "Failed to execute %s: %m", editor
);
370 log_error("Cannot edit unit(s), no editor available. Please set either $SYSTEMD_EDITOR, $EDITOR or $VISUAL.");
377 static int find_paths_to_edit(sd_bus
*bus
, char **names
, char ***paths
) {
378 _cleanup_(hashmap_freep
) Hashmap
*cached_name_map
= NULL
, *cached_id_map
= NULL
;
379 _cleanup_(lookup_paths_free
) LookupPaths lp
= {};
385 r
= lookup_paths_init(&lp
, arg_scope
, 0, arg_root
);
389 STRV_FOREACH(name
, names
) {
390 _cleanup_free_
char *path
= NULL
, *new_path
= NULL
, *tmp_path
= NULL
, *tmp_name
= NULL
;
391 _cleanup_strv_free_
char **unit_paths
= NULL
;
392 const char *unit_name
;
394 r
= unit_find_paths(bus
, *name
, &lp
, false, &cached_name_map
, &cached_id_map
, &path
, &unit_paths
);
395 if (r
== -EKEYREJECTED
) {
396 /* If loading of the unit failed server side complete, then the server won't tell us
397 * the unit file path. In that case, find the file client side. */
398 log_debug_errno(r
, "Unit '%s' was not loaded correctly, retrying client-side.", *name
);
399 r
= unit_find_paths(bus
, *name
, &lp
, true, &cached_name_map
, &cached_id_map
, &path
, &unit_paths
);
402 return log_error_errno(r
, "Unit '%s' masked, cannot edit.", *name
);
408 log_info("Run 'systemctl edit%s --force --full %s' to create a new unit.",
409 arg_scope
== UNIT_FILE_GLOBAL
? " --global" :
410 arg_scope
== UNIT_FILE_USER
? " --user" : "",
415 /* Create a new unit from scratch */
417 r
= unit_file_create_new(&lp
, unit_name
,
418 arg_full
? NULL
: ".d/override.conf",
419 NULL
, &new_path
, &tmp_path
);
421 unit_name
= basename(path
);
422 /* We follow unit aliases, but we need to propagate the instance */
423 if (unit_name_is_valid(*name
, UNIT_NAME_INSTANCE
) &&
424 unit_name_is_valid(unit_name
, UNIT_NAME_TEMPLATE
)) {
425 _cleanup_free_
char *instance
= NULL
;
427 r
= unit_name_to_instance(*name
, &instance
);
431 r
= unit_name_replace_instance(unit_name
, instance
, &tmp_name
);
435 unit_name
= tmp_name
;
439 r
= unit_file_create_copy(&lp
, unit_name
, path
, &new_path
, &tmp_path
);
441 r
= strv_prepend(&unit_paths
, path
);
445 r
= unit_file_create_new(&lp
, unit_name
, ".d/override.conf", unit_paths
, &new_path
, &tmp_path
);
451 r
= strv_push_pair(paths
, new_path
, tmp_path
);
455 new_path
= tmp_path
= NULL
;
461 static int trim_edit_markers(const char *path
) {
462 _cleanup_free_
char *contents
= NULL
;
463 char *contents_start
= NULL
;
464 const char *contents_end
= NULL
;
468 /* Trim out the lines between the two markers */
469 r
= read_full_file(path
, &contents
, NULL
);
471 return log_error_errno(r
, "Failed to read temporary file \"%s\": %m", path
);
473 size
= strlen(contents
);
475 contents_start
= strstr(contents
, EDIT_MARKER_START
);
477 contents_start
+= strlen(EDIT_MARKER_START
);
479 contents_start
= contents
;
481 contents_end
= strstr(contents_start
, EDIT_MARKER_END
);
483 strshorten(contents_start
, contents_end
- contents_start
);
485 contents_start
= strstrip(contents_start
);
487 /* Write new contents if the trimming actually changed anything */
488 if (strlen(contents
) != size
) {
489 r
= write_string_file(path
, contents_start
, WRITE_STRING_FILE_CREATE
| WRITE_STRING_FILE_TRUNCATE
| WRITE_STRING_FILE_AVOID_NEWLINE
);
491 return log_error_errno(r
, "Failed to modify temporary file \"%s\": %m", path
);
497 int verb_edit(int argc
, char *argv
[], void *userdata
) {
498 _cleanup_(lookup_paths_free
) LookupPaths lp
= {};
499 _cleanup_strv_free_
char **names
= NULL
;
500 _cleanup_strv_free_
char **paths
= NULL
;
505 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Cannot edit units if not on a tty.");
507 if (arg_transport
!= BUS_TRANSPORT_LOCAL
)
508 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Cannot edit units remotely.");
510 r
= lookup_paths_init(&lp
, arg_scope
, 0, arg_root
);
512 return log_error_errno(r
, "Failed to determine unit paths: %m");
514 r
= mac_selinux_init();
518 r
= acquire_bus(BUS_MANAGER
, &bus
);
522 r
= expand_unit_names(bus
, strv_skip(argv
, 1), NULL
, &names
, NULL
);
524 return log_error_errno(r
, "Failed to expand names: %m");
525 if (strv_isempty(names
))
526 return log_error_errno(SYNTHETIC_ERRNO(ENOENT
), "No units matched the specified patterns.");
528 STRV_FOREACH(tmp
, names
) {
529 r
= unit_is_masked(bus
, &lp
, *tmp
);
533 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Cannot edit %s: unit is masked.", *tmp
);
536 r
= find_paths_to_edit(bus
, names
, &paths
);
540 if (strv_isempty(paths
))
543 r
= run_editor(paths
);
547 STRV_FOREACH_PAIR(original
, tmp
, paths
) {
548 /* If the temporary file is empty we ignore it. This allows the user to cancel the
550 r
= trim_edit_markers(*tmp
);
554 if (null_or_empty_path(*tmp
)) {
555 log_warning("Editing \"%s\" canceled: temporary file is empty.", *original
);
559 r
= rename(*tmp
, *original
);
561 r
= log_error_errno(errno
, "Failed to rename \"%s\" to \"%s\": %m", *tmp
, *original
);
568 if (!arg_no_reload
&& !install_client_side()) {
569 r
= daemon_reload(ACTION_RELOAD
, /* graceful= */ false);
575 STRV_FOREACH_PAIR(original
, tmp
, paths
) {
578 /* Removing empty dropin dirs */
580 _cleanup_free_
char *dir
= NULL
;
582 dir
= dirname_malloc(*original
);
586 /* No need to check if the dir is empty, rmdir does nothing if it is not the case. */