]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/delta/delta.c
process-util: rework wait_for_terminate_and_warn() to take a flags parameter
[thirdparty/systemd.git] / src / delta / delta.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2012 Lennart Poettering
6 Copyright 2013 Zbigniew Jędrzejewski-Szmek
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <getopt.h>
24 #include <string.h>
25 #include <sys/prctl.h>
26 #include <unistd.h>
27
28 #include "alloc-util.h"
29 #include "dirent-util.h"
30 #include "fd-util.h"
31 #include "fs-util.h"
32 #include "hashmap.h"
33 #include "locale-util.h"
34 #include "log.h"
35 #include "pager.h"
36 #include "parse-util.h"
37 #include "path-util.h"
38 #include "process-util.h"
39 #include "signal-util.h"
40 #include "stat-util.h"
41 #include "string-util.h"
42 #include "strv.h"
43 #include "terminal-util.h"
44 #include "util.h"
45
46 static const char prefixes[] =
47 "/etc\0"
48 "/run\0"
49 "/usr/local/lib\0"
50 "/usr/local/share\0"
51 "/usr/lib\0"
52 "/usr/share\0"
53 #if HAVE_SPLIT_USR
54 "/lib\0"
55 #endif
56 ;
57
58 static const char suffixes[] =
59 "sysctl.d\0"
60 "tmpfiles.d\0"
61 "modules-load.d\0"
62 "binfmt.d\0"
63 "systemd/system\0"
64 "systemd/user\0"
65 "systemd/system-preset\0"
66 "systemd/user-preset\0"
67 "udev/rules.d\0"
68 "modprobe.d\0";
69
70 static const char have_dropins[] =
71 "systemd/system\0"
72 "systemd/user\0";
73
74 static bool arg_no_pager = false;
75 static int arg_diff = -1;
76
77 static enum {
78 SHOW_MASKED = 1 << 0,
79 SHOW_EQUIVALENT = 1 << 1,
80 SHOW_REDIRECTED = 1 << 2,
81 SHOW_OVERRIDDEN = 1 << 3,
82 SHOW_UNCHANGED = 1 << 4,
83 SHOW_EXTENDED = 1 << 5,
84
85 SHOW_DEFAULTS =
86 (SHOW_MASKED | SHOW_EQUIVALENT | SHOW_REDIRECTED | SHOW_OVERRIDDEN | SHOW_EXTENDED)
87 } arg_flags = 0;
88
89 static int equivalent(const char *a, const char *b) {
90 _cleanup_free_ char *x = NULL, *y = NULL;
91 int r;
92
93 r = chase_symlinks(a, NULL, 0, &x);
94 if (r < 0)
95 return r;
96
97 r = chase_symlinks(b, NULL, 0, &y);
98 if (r < 0)
99 return r;
100
101 return path_equal(x, y);
102 }
103
104 static int notify_override_masked(const char *top, const char *bottom) {
105 if (!(arg_flags & SHOW_MASKED))
106 return 0;
107
108 printf("%s%s%s %s %s %s\n",
109 ansi_highlight_red(), "[MASKED]", ansi_normal(),
110 top, special_glyph(ARROW), bottom);
111 return 1;
112 }
113
114 static int notify_override_equivalent(const char *top, const char *bottom) {
115 if (!(arg_flags & SHOW_EQUIVALENT))
116 return 0;
117
118 printf("%s%s%s %s %s %s\n",
119 ansi_highlight_green(), "[EQUIVALENT]", ansi_normal(),
120 top, special_glyph(ARROW), bottom);
121 return 1;
122 }
123
124 static int notify_override_redirected(const char *top, const char *bottom) {
125 if (!(arg_flags & SHOW_REDIRECTED))
126 return 0;
127
128 printf("%s%s%s %s %s %s\n",
129 ansi_highlight(), "[REDIRECTED]", ansi_normal(),
130 top, special_glyph(ARROW), bottom);
131 return 1;
132 }
133
134 static int notify_override_overridden(const char *top, const char *bottom) {
135 if (!(arg_flags & SHOW_OVERRIDDEN))
136 return 0;
137
138 printf("%s%s%s %s %s %s\n",
139 ansi_highlight(), "[OVERRIDDEN]", ansi_normal(),
140 top, special_glyph(ARROW), bottom);
141 return 1;
142 }
143
144 static int notify_override_extended(const char *top, const char *bottom) {
145 if (!(arg_flags & SHOW_EXTENDED))
146 return 0;
147
148 printf("%s%s%s %s %s %s\n",
149 ansi_highlight(), "[EXTENDED]", ansi_normal(),
150 top, special_glyph(ARROW), bottom);
151 return 1;
152 }
153
154 static int notify_override_unchanged(const char *f) {
155 if (!(arg_flags & SHOW_UNCHANGED))
156 return 0;
157
158 printf("[UNCHANGED] %s\n", f);
159 return 1;
160 }
161
162 static int found_override(const char *top, const char *bottom) {
163 _cleanup_free_ char *dest = NULL;
164 pid_t pid;
165 int r;
166
167 assert(top);
168 assert(bottom);
169
170 if (null_or_empty_path(top) > 0)
171 return notify_override_masked(top, bottom);
172
173 r = readlink_malloc(top, &dest);
174 if (r >= 0) {
175 if (equivalent(dest, bottom) > 0)
176 return notify_override_equivalent(top, bottom);
177 else
178 return notify_override_redirected(top, bottom);
179 }
180
181 r = notify_override_overridden(top, bottom);
182 if (!arg_diff)
183 return r;
184
185 putchar('\n');
186
187 fflush(stdout);
188
189 r = safe_fork("(diff)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_CLOSE_ALL_FDS|FORK_LOG, &pid);
190 if (r < 0)
191 return r;
192 if (r == 0) {
193 execlp("diff", "diff", "-us", "--", bottom, top, NULL);
194 log_error_errno(errno, "Failed to execute diff: %m");
195 _exit(EXIT_FAILURE);
196 }
197
198 (void) wait_for_terminate_and_check("diff", pid, WAIT_LOG_ABNORMAL);
199 putchar('\n');
200
201 return r;
202 }
203
204 static int enumerate_dir_d(
205 OrderedHashmap *top,
206 OrderedHashmap *bottom,
207 OrderedHashmap *drops,
208 const char *toppath, const char *drop) {
209
210 _cleanup_free_ char *unit = NULL;
211 _cleanup_free_ char *path = NULL;
212 _cleanup_strv_free_ char **list = NULL;
213 char **file;
214 char *c;
215 int r;
216
217 assert(!endswith(drop, "/"));
218
219 path = strjoin(toppath, "/", drop);
220 if (!path)
221 return -ENOMEM;
222
223 log_debug("Looking at %s", path);
224
225 unit = strdup(drop);
226 if (!unit)
227 return -ENOMEM;
228
229 c = strrchr(unit, '.');
230 if (!c)
231 return -EINVAL;
232 *c = 0;
233
234 r = get_files_in_directory(path, &list);
235 if (r < 0)
236 return log_error_errno(r, "Failed to enumerate %s: %m", path);
237
238 strv_sort(list);
239
240 STRV_FOREACH(file, list) {
241 OrderedHashmap *h;
242 int k;
243 char *p;
244 char *d;
245
246 if (!endswith(*file, ".conf"))
247 continue;
248
249 p = strjoin(path, "/", *file);
250 if (!p)
251 return -ENOMEM;
252 d = p + strlen(toppath) + 1;
253
254 log_debug("Adding at top: %s %s %s", d, special_glyph(ARROW), p);
255 k = ordered_hashmap_put(top, d, p);
256 if (k >= 0) {
257 p = strdup(p);
258 if (!p)
259 return -ENOMEM;
260 d = p + strlen(toppath) + 1;
261 } else if (k != -EEXIST) {
262 free(p);
263 return k;
264 }
265
266 log_debug("Adding at bottom: %s %s %s", d, special_glyph(ARROW), p);
267 free(ordered_hashmap_remove(bottom, d));
268 k = ordered_hashmap_put(bottom, d, p);
269 if (k < 0) {
270 free(p);
271 return k;
272 }
273
274 h = ordered_hashmap_get(drops, unit);
275 if (!h) {
276 h = ordered_hashmap_new(&string_hash_ops);
277 if (!h)
278 return -ENOMEM;
279 ordered_hashmap_put(drops, unit, h);
280 unit = strdup(unit);
281 if (!unit)
282 return -ENOMEM;
283 }
284
285 p = strdup(p);
286 if (!p)
287 return -ENOMEM;
288
289 log_debug("Adding to drops: %s %s %s %s %s",
290 unit, special_glyph(ARROW), basename(p), special_glyph(ARROW), p);
291 k = ordered_hashmap_put(h, basename(p), p);
292 if (k < 0) {
293 free(p);
294 if (k != -EEXIST)
295 return k;
296 }
297 }
298 return 0;
299 }
300
301 static int enumerate_dir(
302 OrderedHashmap *top,
303 OrderedHashmap *bottom,
304 OrderedHashmap *drops,
305 const char *path, bool dropins) {
306
307 _cleanup_closedir_ DIR *d = NULL;
308 struct dirent *de;
309 _cleanup_strv_free_ char **files = NULL, **dirs = NULL;
310 size_t n_files = 0, allocated_files = 0, n_dirs = 0, allocated_dirs = 0;
311 char **t;
312 int r;
313
314 assert(top);
315 assert(bottom);
316 assert(drops);
317 assert(path);
318
319 log_debug("Looking at %s", path);
320
321 d = opendir(path);
322 if (!d) {
323 if (errno == ENOENT)
324 return 0;
325
326 return log_error_errno(errno, "Failed to open %s: %m", path);
327 }
328
329 FOREACH_DIRENT_ALL(de, d, return -errno) {
330 dirent_ensure_type(d, de);
331
332 if (dropins && de->d_type == DT_DIR && endswith(de->d_name, ".d")) {
333 if (!GREEDY_REALLOC0(dirs, allocated_dirs, n_dirs + 2))
334 return -ENOMEM;
335
336 dirs[n_dirs] = strdup(de->d_name);
337 if (!dirs[n_dirs])
338 return -ENOMEM;
339 n_dirs ++;
340 }
341
342 if (!dirent_is_file(de))
343 continue;
344
345 if (!GREEDY_REALLOC0(files, allocated_files, n_files + 2))
346 return -ENOMEM;
347
348 files[n_files] = strdup(de->d_name);
349 if (!files[n_files])
350 return -ENOMEM;
351 n_files ++;
352 }
353
354 strv_sort(dirs);
355 strv_sort(files);
356
357 STRV_FOREACH(t, dirs) {
358 r = enumerate_dir_d(top, bottom, drops, path, *t);
359 if (r < 0)
360 return r;
361 }
362
363 STRV_FOREACH(t, files) {
364 _cleanup_free_ char *p = NULL;
365
366 p = strjoin(path, "/", *t);
367 if (!p)
368 return -ENOMEM;
369
370 log_debug("Adding at top: %s %s %s", basename(p), special_glyph(ARROW), p);
371 r = ordered_hashmap_put(top, basename(p), p);
372 if (r >= 0) {
373 p = strdup(p);
374 if (!p)
375 return -ENOMEM;
376 } else if (r != -EEXIST)
377 return r;
378
379 log_debug("Adding at bottom: %s %s %s", basename(p), special_glyph(ARROW), p);
380 free(ordered_hashmap_remove(bottom, basename(p)));
381 r = ordered_hashmap_put(bottom, basename(p), p);
382 if (r < 0)
383 return r;
384 p = NULL;
385 }
386
387 return 0;
388 }
389
390 static int should_skip_prefix(const char* p) {
391 #if HAVE_SPLIT_USR
392 int r;
393 _cleanup_free_ char *target = NULL;
394
395 r = chase_symlinks(p, NULL, 0, &target);
396 if (r < 0)
397 return r;
398
399 return !streq(p, target) && nulstr_contains(prefixes, target);
400 #else
401 return 0;
402 #endif
403 }
404
405 static int process_suffix(const char *suffix, const char *onlyprefix) {
406 const char *p;
407 char *f;
408 OrderedHashmap *top, *bottom, *drops;
409 OrderedHashmap *h;
410 char *key;
411 int r = 0, k;
412 Iterator i, j;
413 int n_found = 0;
414 bool dropins;
415
416 assert(suffix);
417 assert(!startswith(suffix, "/"));
418 assert(!strstr(suffix, "//"));
419
420 dropins = nulstr_contains(have_dropins, suffix);
421
422 top = ordered_hashmap_new(&string_hash_ops);
423 bottom = ordered_hashmap_new(&string_hash_ops);
424 drops = ordered_hashmap_new(&string_hash_ops);
425 if (!top || !bottom || !drops) {
426 r = -ENOMEM;
427 goto finish;
428 }
429
430 NULSTR_FOREACH(p, prefixes) {
431 _cleanup_free_ char *t = NULL;
432 int skip;
433
434 skip = should_skip_prefix(p);
435 if (skip < 0) {
436 r = skip;
437 goto finish;
438 }
439 if (skip)
440 continue;
441
442 t = strjoin(p, "/", suffix);
443 if (!t) {
444 r = -ENOMEM;
445 goto finish;
446 }
447
448 k = enumerate_dir(top, bottom, drops, t, dropins);
449 if (r == 0)
450 r = k;
451 }
452
453 ORDERED_HASHMAP_FOREACH_KEY(f, key, top, i) {
454 char *o;
455
456 o = ordered_hashmap_get(bottom, key);
457 assert(o);
458
459 if (!onlyprefix || startswith(o, onlyprefix)) {
460 if (path_equal(o, f)) {
461 notify_override_unchanged(f);
462 } else {
463 k = found_override(f, o);
464 if (k < 0)
465 r = k;
466 else
467 n_found += k;
468 }
469 }
470
471 h = ordered_hashmap_get(drops, key);
472 if (h)
473 ORDERED_HASHMAP_FOREACH(o, h, j)
474 if (!onlyprefix || startswith(o, onlyprefix))
475 n_found += notify_override_extended(f, o);
476 }
477
478 finish:
479 ordered_hashmap_free_free(top);
480 ordered_hashmap_free_free(bottom);
481
482 ORDERED_HASHMAP_FOREACH_KEY(h, key, drops, i) {
483 ordered_hashmap_free_free(ordered_hashmap_remove(drops, key));
484 ordered_hashmap_remove(drops, key);
485 free(key);
486 }
487 ordered_hashmap_free(drops);
488
489 return r < 0 ? r : n_found;
490 }
491
492 static int process_suffixes(const char *onlyprefix) {
493 const char *n;
494 int n_found = 0, r;
495
496 NULSTR_FOREACH(n, suffixes) {
497 r = process_suffix(n, onlyprefix);
498 if (r < 0)
499 return r;
500
501 n_found += r;
502 }
503
504 return n_found;
505 }
506
507 static int process_suffix_chop(const char *arg) {
508 const char *p;
509
510 assert(arg);
511
512 if (!path_is_absolute(arg))
513 return process_suffix(arg, NULL);
514
515 /* Strip prefix from the suffix */
516 NULSTR_FOREACH(p, prefixes) {
517 const char *suffix;
518 int skip;
519
520 skip = should_skip_prefix(p);
521 if (skip < 0)
522 return skip;
523 if (skip)
524 continue;
525
526 suffix = startswith(arg, p);
527 if (suffix) {
528 suffix += strspn(suffix, "/");
529 if (*suffix)
530 return process_suffix(suffix, NULL);
531 else
532 return process_suffixes(arg);
533 }
534 }
535
536 log_error("Invalid suffix specification %s.", arg);
537 return -EINVAL;
538 }
539
540 static void help(void) {
541 printf("%s [OPTIONS...] [SUFFIX...]\n\n"
542 "Find overridden configuration files.\n\n"
543 " -h --help Show this help\n"
544 " --version Show package version\n"
545 " --no-pager Do not pipe output into a pager\n"
546 " --diff[=1|0] Show a diff when overridden files differ\n"
547 " -t --type=LIST... Only display a selected set of override types\n"
548 , program_invocation_short_name);
549 }
550
551 static int parse_flags(const char *flag_str, int flags) {
552 const char *word, *state;
553 size_t l;
554
555 FOREACH_WORD_SEPARATOR(word, l, flag_str, ",", state) {
556 if (strneq("masked", word, l))
557 flags |= SHOW_MASKED;
558 else if (strneq ("equivalent", word, l))
559 flags |= SHOW_EQUIVALENT;
560 else if (strneq("redirected", word, l))
561 flags |= SHOW_REDIRECTED;
562 else if (strneq("overridden", word, l))
563 flags |= SHOW_OVERRIDDEN;
564 else if (strneq("unchanged", word, l))
565 flags |= SHOW_UNCHANGED;
566 else if (strneq("extended", word, l))
567 flags |= SHOW_EXTENDED;
568 else if (strneq("default", word, l))
569 flags |= SHOW_DEFAULTS;
570 else
571 return -EINVAL;
572 }
573 return flags;
574 }
575
576 static int parse_argv(int argc, char *argv[]) {
577
578 enum {
579 ARG_NO_PAGER = 0x100,
580 ARG_DIFF,
581 ARG_VERSION
582 };
583
584 static const struct option options[] = {
585 { "help", no_argument, NULL, 'h' },
586 { "version", no_argument, NULL, ARG_VERSION },
587 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
588 { "diff", optional_argument, NULL, ARG_DIFF },
589 { "type", required_argument, NULL, 't' },
590 {}
591 };
592
593 int c;
594
595 assert(argc >= 1);
596 assert(argv);
597
598 while ((c = getopt_long(argc, argv, "ht:", options, NULL)) >= 0)
599
600 switch (c) {
601
602 case 'h':
603 help();
604 return 0;
605
606 case ARG_VERSION:
607 return version();
608
609 case ARG_NO_PAGER:
610 arg_no_pager = true;
611 break;
612
613 case 't': {
614 int f;
615 f = parse_flags(optarg, arg_flags);
616 if (f < 0) {
617 log_error("Failed to parse flags field.");
618 return -EINVAL;
619 }
620 arg_flags = f;
621 break;
622 }
623
624 case ARG_DIFF:
625 if (!optarg)
626 arg_diff = 1;
627 else {
628 int b;
629
630 b = parse_boolean(optarg);
631 if (b < 0) {
632 log_error("Failed to parse diff boolean.");
633 return -EINVAL;
634 }
635
636 arg_diff = b;
637 }
638 break;
639
640 case '?':
641 return -EINVAL;
642
643 default:
644 assert_not_reached("Unhandled option");
645 }
646
647 return 1;
648 }
649
650 int main(int argc, char *argv[]) {
651 int r, k, n_found = 0;
652
653 log_parse_environment();
654 log_open();
655
656 r = parse_argv(argc, argv);
657 if (r <= 0)
658 goto finish;
659
660 if (arg_flags == 0)
661 arg_flags = SHOW_DEFAULTS;
662
663 if (arg_diff < 0)
664 arg_diff = !!(arg_flags & SHOW_OVERRIDDEN);
665 else if (arg_diff)
666 arg_flags |= SHOW_OVERRIDDEN;
667
668 pager_open(arg_no_pager, false);
669
670 if (optind < argc) {
671 int i;
672
673 for (i = optind; i < argc; i++) {
674 path_kill_slashes(argv[i]);
675
676 k = process_suffix_chop(argv[i]);
677 if (k < 0)
678 r = k;
679 else
680 n_found += k;
681 }
682
683 } else {
684 k = process_suffixes(NULL);
685 if (k < 0)
686 r = k;
687 else
688 n_found += k;
689 }
690
691 if (r >= 0)
692 printf("%s%i overridden configuration files found.\n", n_found ? "\n" : "", n_found);
693
694 finish:
695 pager_close();
696
697 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
698 }