1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include "bus-error.h"
4 #include "copy.h"
5 #include "fd-util.h"
6 #include "fileio.h"
7 #include "fs-util.h"
8 #include "mkdir-label.h"
9 #include "pager.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"
21 
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"
24 
verb_cat(int argc,char * argv[],void * userdata)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;
29         sd_bus *bus;
30         bool first = true;
31         int r, rc = 0;
32 
33         /* Include all units by default — i.e. continue as if the --all option was used */
34         if (strv_isempty(arg_states))
35                 arg_all = true;
36 
37         if (arg_transport != BUS_TRANSPORT_LOCAL)
38                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot remotely cat units.");
39 
40         r = lookup_paths_init_or_warn(&lp, arg_scope, 0, arg_root);
41         if (r < 0)
42                 return r;
43 
44         r = acquire_bus(BUS_MANAGER, &bus);
45         if (r < 0)
46                 return r;
47 
48         r = expand_unit_names(bus, strv_skip(argv, 1), NULL, &names, NULL);
49         if (r < 0)
50                 return log_error_errno(r, "Failed to expand names: %m");
51 
52         r = maybe_extend_with_unit_dependencies(bus, &names);
53         if (r < 0)
54                 return r;
55 
56         pager_open(arg_pager_flags);
57 
58         STRV_FOREACH(name, names) {
59                 _cleanup_free_ char *fragment_path = NULL;
60                 _cleanup_strv_free_ char **dropin_paths = NULL;
61 
62                 r = unit_find_paths(bus, *name, &lp, false, &cached_name_map, &cached_id_map, &fragment_path, &dropin_paths);
63                 if (r == -ERFKILL) {
64                         printf("%s# Unit %s is masked%s.\n",
65                                ansi_highlight_magenta(),
66                                *name,
67                                ansi_normal());
68                         continue;
69                 }
70                 if (r == -EKEYREJECTED) {
71                         printf("%s# Unit %s could not be loaded.%s\n",
72                                ansi_highlight_magenta(),
73                                *name,
74                                ansi_normal());
75                         continue;
76                 }
77                 if (r < 0)
78                         return r;
79                 if (r == 0) {
80                         /* Skip units which have no on-disk counterpart, but propagate the error to the
81                          * user */
82                         rc = -ENOENT;
83                         continue;
84                 }
85 
86                 if (first)
87                         first = false;
88                 else
89                         puts("");
90 
91                 if (need_daemon_reload(bus, *name) > 0) /* ignore errors (<0), this is informational output */
92                         fprintf(stderr,
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",
97                                 ansi_highlight_red(),
98                                 *name,
99                                 ansi_highlight_red(),
100                                 ansi_highlight_red(),
101                                 ansi_highlight_red(),
102                                 arg_scope == LOOKUP_SCOPE_SYSTEM ? "" : " --user",
103                                 ansi_normal());
104 
105                 r = cat_files(fragment_path, dropin_paths, 0);
106                 if (r < 0)
107                         return r;
108         }
109 
110         return rc;
111 }
112 
create_edit_temp_file(const char * new_path,const char * original_path,char ** const original_unit_paths,char ** ret_tmp_fn)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;
115         int r;
116 
117         assert(new_path);
118         assert(ret_tmp_fn);
119 
120         r = tempfn_random(new_path, NULL, &t);
121         if (r < 0)
122                 return log_error_errno(r, "Failed to determine temporary filename for \"%s\": %m", new_path);
123 
124         r = mkdir_parents_label(new_path, 0755);
125         if (r < 0)
126                 return log_error_errno(r, "Failed to create directories for \"%s\": %m", new_path);
127 
128         if (original_path) {
129                 r = mac_selinux_create_file_prepare(new_path, S_IFREG);
130                 if (r < 0)
131                         return r;
132 
133                 r = copy_file(original_path, t, 0, 0644, 0, 0, COPY_REFLINK);
134                 if (r == -ENOENT) {
135                         r = touch(t);
136                         mac_selinux_create_file_clear();
137                         if (r < 0)
138                                 return log_error_errno(r, "Failed to create temporary file \"%s\": %m", t);
139                 } else {
140                         mac_selinux_create_file_clear();
141                         if (r < 0)
142                                 return log_error_errno(r, "Failed to create temporary file for \"%s\": %m", new_path);
143                 }
144         } else if (original_unit_paths) {
145                 _cleanup_free_ char *new_contents = NULL;
146                 _cleanup_fclose_ FILE *f = NULL;
147 
148                 r = mac_selinux_create_file_prepare(new_path, S_IFREG);
149                 if (r < 0)
150                         return r;
151 
152                 f = fopen(t, "we");
153                 mac_selinux_create_file_clear();
154                 if (!f)
155                         return log_error_errno(errno, "Failed to open \"%s\": %m", t);
156 
157                 r = fchmod(fileno(f), 0644);
158                 if (r < 0)
159                         return log_error_errno(errno, "Failed to change mode of \"%s\": %m", t);
160 
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);
164 
165                 fprintf(f,
166                         "### Editing %s\n"
167                         EDIT_MARKER_START
168                         "\n\n%s%s\n"
169                         EDIT_MARKER_END,
170                         new_path,
171                         strempty(new_contents),
172                         new_contents && endswith(new_contents, "\n") ? "" : "\n");
173 
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;
177 
178                         /* Skip the file that's being edited */
179                         if (path_equal(*path, new_path))
180                                 continue;
181 
182                         r = read_full_file(*path, &contents, NULL);
183                         if (r < 0)
184                                 return log_error_errno(r, "Failed to read \"%s\": %m", *path);
185 
186                         fprintf(f, "\n\n### %s", *path);
187                         if (!isempty(contents)) {
188                                 _cleanup_free_ char *commented_contents = NULL;
189 
190                                 commented_contents = strreplace(strstrip(contents), "\n", "\n# ");
191                                 if (!commented_contents)
192                                         return log_oom();
193                                 fprintf(f, "\n# %s", commented_contents);
194                         }
195                 }
196 
197                 r = fflush_and_check(f);
198                 if (r < 0)
199                         return log_error_errno(r, "Failed to create temporary file \"%s\": %m", t);
200         }
201 
202         *ret_tmp_fn = TAKE_PTR(t);
203 
204         return 0;
205 }
206 
get_file_to_edit(const LookupPaths * paths,const char * name,char ** ret_path)207 static int get_file_to_edit(
208                 const LookupPaths *paths,
209                 const char *name,
210                 char **ret_path) {
211 
212         _cleanup_free_ char *path = NULL, *run = NULL;
213 
214         assert(name);
215         assert(ret_path);
216 
217         path = path_join(paths->persistent_config, name);
218         if (!path)
219                 return log_oom();
220 
221         if (arg_runtime) {
222                 run = path_join(paths->runtime_config, name);
223                 if (!run)
224                         return log_oom();
225         }
226 
227         if (arg_runtime) {
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.",
231                                                run, path);
232 
233                 *ret_path = TAKE_PTR(run);
234         } else
235                 *ret_path = TAKE_PTR(path);
236 
237         return 0;
238 }
239 
unit_file_create_new(const LookupPaths * paths,const char * unit_name,const char * suffix,char ** const original_unit_paths,char ** ret_new_path,char ** ret_tmp_path)240 static int unit_file_create_new(
241                 const LookupPaths *paths,
242                 const char *unit_name,
243                 const char *suffix,
244                 char ** const original_unit_paths,
245                 char **ret_new_path,
246                 char **ret_tmp_path) {
247 
248         _cleanup_free_ char *new_path = NULL, *tmp_path = NULL;
249         const char *ending;
250         int r;
251 
252         assert(unit_name);
253         assert(ret_new_path);
254         assert(ret_tmp_path);
255 
256         ending = strjoina(unit_name, suffix);
257         r = get_file_to_edit(paths, ending, &new_path);
258         if (r < 0)
259                 return r;
260 
261         r = create_edit_temp_file(new_path, NULL, original_unit_paths, &tmp_path);
262         if (r < 0)
263                 return r;
264 
265         *ret_new_path = TAKE_PTR(new_path);
266         *ret_tmp_path = TAKE_PTR(tmp_path);
267 
268         return 0;
269 }
270 
unit_file_create_copy(const LookupPaths * paths,const char * unit_name,const char * fragment_path,char ** ret_new_path,char ** ret_tmp_path)271 static int unit_file_create_copy(
272                 const LookupPaths *paths,
273                 const char *unit_name,
274                 const char *fragment_path,
275                 char **ret_new_path,
276                 char **ret_tmp_path) {
277 
278         _cleanup_free_ char *new_path = NULL, *tmp_path = NULL;
279         int r;
280 
281         assert(fragment_path);
282         assert(unit_name);
283         assert(ret_new_path);
284         assert(ret_tmp_path);
285 
286         r = get_file_to_edit(paths, unit_name, &new_path);
287         if (r < 0)
288                 return r;
289 
290         if (!path_equal(fragment_path, new_path) && access(new_path, F_OK) >= 0) {
291                 char response;
292 
293                 r = ask_char(&response, "yn", "\"%s\" already exists. Overwrite with \"%s\"? [(y)es, (n)o] ", new_path, fragment_path);
294                 if (r < 0)
295                         return r;
296                 if (response != 'y')
297                         return log_warning_errno(SYNTHETIC_ERRNO(EKEYREJECTED), "%s skipped.", unit_name);
298         }
299 
300         r = create_edit_temp_file(new_path, fragment_path, NULL, &tmp_path);
301         if (r < 0)
302                 return r;
303 
304         *ret_new_path = TAKE_PTR(new_path);
305         *ret_tmp_path = TAKE_PTR(tmp_path);
306 
307         return 0;
308 }
309 
run_editor(char ** paths)310 static int run_editor(char **paths) {
311         int r;
312 
313         assert(paths);
314 
315         r = safe_fork("(editor)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG|FORK_WAIT, NULL);
316         if (r < 0)
317                 return r;
318         if (r == 0) {
319                 char **editor_args = NULL;
320                 size_t n_editor_args = 0, i = 1, argc;
321                 const char **args, *editor;
322 
323                 argc = strv_length(paths)/2 + 1;
324 
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
327                  * editors. */
328                 editor = getenv("SYSTEMD_EDITOR");
329                 if (!editor)
330                         editor = getenv("EDITOR");
331                 if (!editor)
332                         editor = getenv("VISUAL");
333 
334                 if (!isempty(editor)) {
335                         editor_args = strv_split(editor, WHITESPACE);
336                         if (!editor_args) {
337                                 (void) log_oom();
338                                 _exit(EXIT_FAILURE);
339                         }
340                         n_editor_args = strv_length(editor_args);
341                         argc += n_editor_args - 1;
342                 }
343 
344                 args = newa(const char*, argc + 1);
345 
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];
350                 }
351 
352                 STRV_FOREACH_PAIR(original_path, tmp_path, paths)
353                         args[i++] = *tmp_path;
354                 args[i] = NULL;
355 
356                 if (n_editor_args > 0)
357                         execvp(args[0], (char* const*) args);
358 
359                 FOREACH_STRING(name, "editor", "nano", "vim", "vi") {
360                         args[0] = name;
361                         execvp(name, (char* const*) args);
362                         /* We do not fail if the editor doesn't exist because we want to try each one of them
363                          * before failing. */
364                         if (errno != ENOENT) {
365                                 log_error_errno(errno, "Failed to execute %s: %m", name);
366                                 _exit(EXIT_FAILURE);
367                         }
368                 }
369 
370                 log_error("Cannot edit unit(s), no editor available. Please set either $SYSTEMD_EDITOR, $EDITOR or $VISUAL.");
371                 _exit(EXIT_FAILURE);
372         }
373 
374         return 0;
375 }
376 
find_paths_to_edit(sd_bus * bus,char ** names,char *** paths)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 = {};
380         int r;
381 
382         assert(names);
383         assert(paths);
384 
385         r = lookup_paths_init(&lp, arg_scope, 0, arg_root);
386         if (r < 0)
387                 return r;
388 
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;
393 
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);
400                 }
401                 if (r == -ERFKILL)
402                         return log_error_errno(r, "Unit '%s' masked, cannot edit.", *name);
403                 if (r < 0)
404                         return r;
405 
406                 if (!path) {
407                         if (!arg_force) {
408                                 log_info("Run 'systemctl edit%s --force --full %s' to create a new unit.",
409                                          arg_scope == LOOKUP_SCOPE_GLOBAL ? " --global" :
410                                          arg_scope == LOOKUP_SCOPE_USER ? " --user" : "",
411                                          *name);
412                                 return -ENOENT;
413                         }
414 
415                         /* Create a new unit from scratch */
416                         unit_name = *name;
417                         r = unit_file_create_new(&lp, unit_name,
418                                                  arg_full ? NULL : ".d/override.conf",
419                                                  NULL, &new_path, &tmp_path);
420                 } else {
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;
426 
427                                 r = unit_name_to_instance(*name, &instance);
428                                 if (r < 0)
429                                         return r;
430 
431                                 r = unit_name_replace_instance(unit_name, instance, &tmp_name);
432                                 if (r < 0)
433                                         return r;
434 
435                                 unit_name = tmp_name;
436                         }
437 
438                         if (arg_full)
439                                 r = unit_file_create_copy(&lp, unit_name, path, &new_path, &tmp_path);
440                         else {
441                                 r = strv_prepend(&unit_paths, path);
442                                 if (r < 0)
443                                         return log_oom();
444 
445                                 r = unit_file_create_new(&lp, unit_name, ".d/override.conf", unit_paths, &new_path, &tmp_path);
446                         }
447                 }
448                 if (r < 0)
449                         return r;
450 
451                 r = strv_push_pair(paths, new_path, tmp_path);
452                 if (r < 0)
453                         return log_oom();
454 
455                 new_path = tmp_path = NULL;
456         }
457 
458         return 0;
459 }
460 
trim_edit_markers(const char * path)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;
465         size_t size;
466         int r;
467 
468         /* Trim out the lines between the two markers */
469         r = read_full_file(path, &contents, NULL);
470         if (r < 0)
471                 return log_error_errno(r, "Failed to read temporary file \"%s\": %m", path);
472 
473         size = strlen(contents);
474 
475         contents_start = strstr(contents, EDIT_MARKER_START);
476         if (contents_start)
477                 contents_start += strlen(EDIT_MARKER_START);
478         else
479                 contents_start = contents;
480 
481         contents_end = strstr(contents_start, EDIT_MARKER_END);
482         if (contents_end)
483                 strshorten(contents_start, contents_end - contents_start);
484 
485         contents_start = strstrip(contents_start);
486 
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);
490                 if (r < 0)
491                         return log_error_errno(r, "Failed to modify temporary file \"%s\": %m", path);
492         }
493 
494         return 0;
495 }
496 
verb_edit(int argc,char * argv[],void * userdata)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;
501         sd_bus *bus;
502         int r;
503 
504         if (!on_tty())
505                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit units if not on a tty.");
506 
507         if (arg_transport != BUS_TRANSPORT_LOCAL)
508                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit units remotely.");
509 
510         r = lookup_paths_init_or_warn(&lp, arg_scope, 0, arg_root);
511         if (r < 0)
512                 return r;
513 
514         r = mac_selinux_init();
515         if (r < 0)
516                 return r;
517 
518         r = acquire_bus(BUS_MANAGER, &bus);
519         if (r < 0)
520                 return r;
521 
522         r = expand_unit_names(bus, strv_skip(argv, 1), NULL, &names, NULL);
523         if (r < 0)
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.");
527 
528         STRV_FOREACH(tmp, names) {
529                 r = unit_is_masked(bus, &lp, *tmp);
530                 if (r < 0)
531                         return r;
532                 if (r > 0)
533                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit %s: unit is masked.", *tmp);
534         }
535 
536         r = find_paths_to_edit(bus, names, &paths);
537         if (r < 0)
538                 return r;
539 
540         if (strv_isempty(paths))
541                 return -ENOENT;
542 
543         r = run_editor(paths);
544         if (r < 0)
545                 goto end;
546 
547         STRV_FOREACH_PAIR(original, tmp, paths) {
548                 /* If the temporary file is empty we ignore it. This allows the user to cancel the
549                  * modification. */
550                 r = trim_edit_markers(*tmp);
551                 if (r < 0)
552                         continue;
553 
554                 if (null_or_empty_path(*tmp)) {
555                         log_warning("Editing \"%s\" canceled: temporary file is empty.", *original);
556                         continue;
557                 }
558 
559                 r = rename(*tmp, *original);
560                 if (r < 0) {
561                         r = log_error_errno(errno, "Failed to rename \"%s\" to \"%s\": %m", *tmp, *original);
562                         goto end;
563                 }
564         }
565 
566         r = 0;
567 
568         if (!arg_no_reload && !install_client_side()) {
569                 r = daemon_reload(ACTION_RELOAD, /* graceful= */ false);
570                 if (r > 0)
571                         r = 0;
572         }
573 
574 end:
575         STRV_FOREACH_PAIR(original, tmp, paths) {
576                 (void) unlink(*tmp);
577 
578                 /* Removing empty dropin dirs */
579                 if (!arg_full) {
580                         _cleanup_free_ char *dir = NULL;
581 
582                         dir = dirname_malloc(*original);
583                         if (!dir)
584                                 return log_oom();
585 
586                         /* No need to check if the dir is empty, rmdir does nothing if it is not the case. */
587                         (void) rmdir(dir);
588                 }
589         }
590 
591         return r;
592 }
593