]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/delta/delta.c
9c8841d46de87e1649ce59a78eafd2f3e650d642
[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 int k;
165 pid_t pid;
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 k = readlink_malloc(top, &dest);
174 if (k >= 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 k = notify_override_overridden(top, bottom);
182 if (!arg_diff)
183 return k;
184
185 putchar('\n');
186
187 fflush(stdout);
188
189 pid = fork();
190 if (pid < 0)
191 return log_error_errno(errno, "Failed to fork off diff: %m");
192 else if (pid == 0) {
193
194 (void) reset_all_signal_handlers();
195 (void) reset_signal_mask();
196 assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
197
198 execlp("diff", "diff", "-us", "--", bottom, top, NULL);
199 log_error_errno(errno, "Failed to execute diff: %m");
200 _exit(EXIT_FAILURE);
201 }
202
203 wait_for_terminate_and_warn("diff", pid, false);
204 putchar('\n');
205
206 return k;
207 }
208
209 static int enumerate_dir_d(
210 OrderedHashmap *top,
211 OrderedHashmap *bottom,
212 OrderedHashmap *drops,
213 const char *toppath, const char *drop) {
214
215 _cleanup_free_ char *unit = NULL;
216 _cleanup_free_ char *path = NULL;
217 _cleanup_strv_free_ char **list = NULL;
218 char **file;
219 char *c;
220 int r;
221
222 assert(!endswith(drop, "/"));
223
224 path = strjoin(toppath, "/", drop);
225 if (!path)
226 return -ENOMEM;
227
228 log_debug("Looking at %s", path);
229
230 unit = strdup(drop);
231 if (!unit)
232 return -ENOMEM;
233
234 c = strrchr(unit, '.');
235 if (!c)
236 return -EINVAL;
237 *c = 0;
238
239 r = get_files_in_directory(path, &list);
240 if (r < 0)
241 return log_error_errno(r, "Failed to enumerate %s: %m", path);
242
243 strv_sort(list);
244
245 STRV_FOREACH(file, list) {
246 OrderedHashmap *h;
247 int k;
248 char *p;
249 char *d;
250
251 if (!endswith(*file, ".conf"))
252 continue;
253
254 p = strjoin(path, "/", *file);
255 if (!p)
256 return -ENOMEM;
257 d = p + strlen(toppath) + 1;
258
259 log_debug("Adding at top: %s %s %s", d, special_glyph(ARROW), p);
260 k = ordered_hashmap_put(top, d, p);
261 if (k >= 0) {
262 p = strdup(p);
263 if (!p)
264 return -ENOMEM;
265 d = p + strlen(toppath) + 1;
266 } else if (k != -EEXIST) {
267 free(p);
268 return k;
269 }
270
271 log_debug("Adding at bottom: %s %s %s", d, special_glyph(ARROW), p);
272 free(ordered_hashmap_remove(bottom, d));
273 k = ordered_hashmap_put(bottom, d, p);
274 if (k < 0) {
275 free(p);
276 return k;
277 }
278
279 h = ordered_hashmap_get(drops, unit);
280 if (!h) {
281 h = ordered_hashmap_new(&string_hash_ops);
282 if (!h)
283 return -ENOMEM;
284 ordered_hashmap_put(drops, unit, h);
285 unit = strdup(unit);
286 if (!unit)
287 return -ENOMEM;
288 }
289
290 p = strdup(p);
291 if (!p)
292 return -ENOMEM;
293
294 log_debug("Adding to drops: %s %s %s %s %s",
295 unit, special_glyph(ARROW), basename(p), special_glyph(ARROW), p);
296 k = ordered_hashmap_put(h, basename(p), p);
297 if (k < 0) {
298 free(p);
299 if (k != -EEXIST)
300 return k;
301 }
302 }
303 return 0;
304 }
305
306 static int enumerate_dir(
307 OrderedHashmap *top,
308 OrderedHashmap *bottom,
309 OrderedHashmap *drops,
310 const char *path, bool dropins) {
311
312 _cleanup_closedir_ DIR *d = NULL;
313 struct dirent *de;
314 _cleanup_strv_free_ char **files = NULL, **dirs = NULL;
315 size_t n_files = 0, allocated_files = 0, n_dirs = 0, allocated_dirs = 0;
316 char **t;
317 int r;
318
319 assert(top);
320 assert(bottom);
321 assert(drops);
322 assert(path);
323
324 log_debug("Looking at %s", path);
325
326 d = opendir(path);
327 if (!d) {
328 if (errno == ENOENT)
329 return 0;
330
331 return log_error_errno(errno, "Failed to open %s: %m", path);
332 }
333
334 FOREACH_DIRENT_ALL(de, d, return -errno) {
335 dirent_ensure_type(d, de);
336
337 if (dropins && de->d_type == DT_DIR && endswith(de->d_name, ".d")) {
338 if (!GREEDY_REALLOC0(dirs, allocated_dirs, n_dirs + 2))
339 return -ENOMEM;
340
341 dirs[n_dirs] = strdup(de->d_name);
342 if (!dirs[n_dirs])
343 return -ENOMEM;
344 n_dirs ++;
345 }
346
347 if (!dirent_is_file(de))
348 continue;
349
350 if (!GREEDY_REALLOC0(files, allocated_files, n_files + 2))
351 return -ENOMEM;
352
353 files[n_files] = strdup(de->d_name);
354 if (!files[n_files])
355 return -ENOMEM;
356 n_files ++;
357 }
358
359 strv_sort(dirs);
360 strv_sort(files);
361
362 STRV_FOREACH(t, dirs) {
363 r = enumerate_dir_d(top, bottom, drops, path, *t);
364 if (r < 0)
365 return r;
366 }
367
368 STRV_FOREACH(t, files) {
369 _cleanup_free_ char *p = NULL;
370
371 p = strjoin(path, "/", *t);
372 if (!p)
373 return -ENOMEM;
374
375 log_debug("Adding at top: %s %s %s", basename(p), special_glyph(ARROW), p);
376 r = ordered_hashmap_put(top, basename(p), p);
377 if (r >= 0) {
378 p = strdup(p);
379 if (!p)
380 return -ENOMEM;
381 } else if (r != -EEXIST)
382 return r;
383
384 log_debug("Adding at bottom: %s %s %s", basename(p), special_glyph(ARROW), p);
385 free(ordered_hashmap_remove(bottom, basename(p)));
386 r = ordered_hashmap_put(bottom, basename(p), p);
387 if (r < 0)
388 return r;
389 p = NULL;
390 }
391
392 return 0;
393 }
394
395 static bool should_skip_path(const char *prefix, const char *suffix) {
396 #if HAVE_SPLIT_USR
397 _cleanup_free_ char *target = NULL;
398 const char *p;
399 char *dirname;
400
401 dirname = strjoina(prefix, "/", suffix);
402
403 if (chase_symlinks(dirname, NULL, 0, &target) < 0)
404 return false;
405
406 NULSTR_FOREACH(p, prefixes) {
407 if (path_startswith(dirname, p))
408 continue;
409
410 if (path_equal(target, strjoina(p, "/", suffix))) {
411 log_debug("%s redirects to %s, skipping.", dirname, target);
412 return true;
413 }
414 }
415 #endif
416 return false;
417 }
418
419 static int process_suffix(const char *suffix, const char *onlyprefix) {
420 const char *p;
421 char *f;
422 OrderedHashmap *top, *bottom, *drops;
423 OrderedHashmap *h;
424 char *key;
425 int r = 0, k;
426 Iterator i, j;
427 int n_found = 0;
428 bool dropins;
429
430 assert(suffix);
431 assert(!startswith(suffix, "/"));
432 assert(!strstr(suffix, "//"));
433
434 dropins = nulstr_contains(have_dropins, suffix);
435
436 top = ordered_hashmap_new(&string_hash_ops);
437 bottom = ordered_hashmap_new(&string_hash_ops);
438 drops = ordered_hashmap_new(&string_hash_ops);
439 if (!top || !bottom || !drops) {
440 r = -ENOMEM;
441 goto finish;
442 }
443
444 NULSTR_FOREACH(p, prefixes) {
445 _cleanup_free_ char *t = NULL;
446
447 if (should_skip_path(p, suffix))
448 continue;
449
450 t = strjoin(p, "/", suffix);
451 if (!t) {
452 r = -ENOMEM;
453 goto finish;
454 }
455
456 k = enumerate_dir(top, bottom, drops, t, dropins);
457 if (r == 0)
458 r = k;
459 }
460
461 ORDERED_HASHMAP_FOREACH_KEY(f, key, top, i) {
462 char *o;
463
464 o = ordered_hashmap_get(bottom, key);
465 assert(o);
466
467 if (!onlyprefix || startswith(o, onlyprefix)) {
468 if (path_equal(o, f)) {
469 notify_override_unchanged(f);
470 } else {
471 k = found_override(f, o);
472 if (k < 0)
473 r = k;
474 else
475 n_found += k;
476 }
477 }
478
479 h = ordered_hashmap_get(drops, key);
480 if (h)
481 ORDERED_HASHMAP_FOREACH(o, h, j)
482 if (!onlyprefix || startswith(o, onlyprefix))
483 n_found += notify_override_extended(f, o);
484 }
485
486 finish:
487 ordered_hashmap_free_free(top);
488 ordered_hashmap_free_free(bottom);
489
490 ORDERED_HASHMAP_FOREACH_KEY(h, key, drops, i) {
491 ordered_hashmap_free_free(ordered_hashmap_remove(drops, key));
492 ordered_hashmap_remove(drops, key);
493 free(key);
494 }
495 ordered_hashmap_free(drops);
496
497 return r < 0 ? r : n_found;
498 }
499
500 static int process_suffixes(const char *onlyprefix) {
501 const char *n;
502 int n_found = 0, r;
503
504 NULSTR_FOREACH(n, suffixes) {
505 r = process_suffix(n, onlyprefix);
506 if (r < 0)
507 return r;
508
509 n_found += r;
510 }
511
512 return n_found;
513 }
514
515 static int process_suffix_chop(const char *arg) {
516 const char *p;
517
518 assert(arg);
519
520 if (!path_is_absolute(arg))
521 return process_suffix(arg, NULL);
522
523 /* Strip prefix from the suffix */
524 NULSTR_FOREACH(p, prefixes) {
525 const char *suffix;
526
527 suffix = startswith(arg, p);
528 if (suffix) {
529 suffix += strspn(suffix, "/");
530 if (*suffix)
531 return process_suffix(suffix, p);
532 else
533 return process_suffixes(arg);
534 }
535 }
536
537 log_error("Invalid suffix specification %s.", arg);
538 return -EINVAL;
539 }
540
541 static void help(void) {
542 printf("%s [OPTIONS...] [SUFFIX...]\n\n"
543 "Find overridden configuration files.\n\n"
544 " -h --help Show this help\n"
545 " --version Show package version\n"
546 " --no-pager Do not pipe output into a pager\n"
547 " --diff[=1|0] Show a diff when overridden files differ\n"
548 " -t --type=LIST... Only display a selected set of override types\n"
549 , program_invocation_short_name);
550 }
551
552 static int parse_flags(const char *flag_str, int flags) {
553 const char *word, *state;
554 size_t l;
555
556 FOREACH_WORD_SEPARATOR(word, l, flag_str, ",", state) {
557 if (strneq("masked", word, l))
558 flags |= SHOW_MASKED;
559 else if (strneq ("equivalent", word, l))
560 flags |= SHOW_EQUIVALENT;
561 else if (strneq("redirected", word, l))
562 flags |= SHOW_REDIRECTED;
563 else if (strneq("overridden", word, l))
564 flags |= SHOW_OVERRIDDEN;
565 else if (strneq("unchanged", word, l))
566 flags |= SHOW_UNCHANGED;
567 else if (strneq("extended", word, l))
568 flags |= SHOW_EXTENDED;
569 else if (strneq("default", word, l))
570 flags |= SHOW_DEFAULTS;
571 else
572 return -EINVAL;
573 }
574 return flags;
575 }
576
577 static int parse_argv(int argc, char *argv[]) {
578
579 enum {
580 ARG_NO_PAGER = 0x100,
581 ARG_DIFF,
582 ARG_VERSION
583 };
584
585 static const struct option options[] = {
586 { "help", no_argument, NULL, 'h' },
587 { "version", no_argument, NULL, ARG_VERSION },
588 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
589 { "diff", optional_argument, NULL, ARG_DIFF },
590 { "type", required_argument, NULL, 't' },
591 {}
592 };
593
594 int c;
595
596 assert(argc >= 1);
597 assert(argv);
598
599 while ((c = getopt_long(argc, argv, "ht:", options, NULL)) >= 0)
600
601 switch (c) {
602
603 case 'h':
604 help();
605 return 0;
606
607 case ARG_VERSION:
608 return version();
609
610 case ARG_NO_PAGER:
611 arg_no_pager = true;
612 break;
613
614 case 't': {
615 int f;
616 f = parse_flags(optarg, arg_flags);
617 if (f < 0) {
618 log_error("Failed to parse flags field.");
619 return -EINVAL;
620 }
621 arg_flags = f;
622 break;
623 }
624
625 case ARG_DIFF:
626 if (!optarg)
627 arg_diff = 1;
628 else {
629 int b;
630
631 b = parse_boolean(optarg);
632 if (b < 0) {
633 log_error("Failed to parse diff boolean.");
634 return -EINVAL;
635 }
636
637 arg_diff = b;
638 }
639 break;
640
641 case '?':
642 return -EINVAL;
643
644 default:
645 assert_not_reached("Unhandled option");
646 }
647
648 return 1;
649 }
650
651 int main(int argc, char *argv[]) {
652 int r, k, n_found = 0;
653
654 log_parse_environment();
655 log_open();
656
657 r = parse_argv(argc, argv);
658 if (r <= 0)
659 goto finish;
660
661 if (arg_flags == 0)
662 arg_flags = SHOW_DEFAULTS;
663
664 if (arg_diff < 0)
665 arg_diff = !!(arg_flags & SHOW_OVERRIDDEN);
666 else if (arg_diff)
667 arg_flags |= SHOW_OVERRIDDEN;
668
669 pager_open(arg_no_pager, false);
670
671 if (optind < argc) {
672 int i;
673
674 for (i = optind; i < argc; i++) {
675 path_kill_slashes(argv[i]);
676
677 k = process_suffix_chop(argv[i]);
678 if (k < 0)
679 r = k;
680 else
681 n_found += k;
682 }
683
684 } else {
685 k = process_suffixes(NULL);
686 if (k < 0)
687 r = k;
688 else
689 n_found += k;
690 }
691
692 if (r >= 0)
693 printf("%s%i overridden configuration files found.\n", n_found ? "\n" : "", n_found);
694
695 finish:
696 pager_close();
697
698 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
699 }