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