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