]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/delta/delta.c
Merge pull request #7154 from keszybz/bootspec
[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 int should_skip_prefix(const char* p) {
396 #if HAVE_SPLIT_USR
397 int r;
398 _cleanup_free_ char *target = NULL;
399
400 r = chase_symlinks(p, NULL, 0, &target);
401 if (r < 0)
402 return r;
403
404 return !streq(p, target) && nulstr_contains(prefixes, target);
405 #else
406 return 0;
407 #endif
408 }
409
410 static int process_suffix(const char *suffix, const char *onlyprefix) {
411 const char *p;
412 char *f;
413 OrderedHashmap *top, *bottom, *drops;
414 OrderedHashmap *h;
415 char *key;
416 int r = 0, k;
417 Iterator i, j;
418 int n_found = 0;
419 bool dropins;
420
421 assert(suffix);
422 assert(!startswith(suffix, "/"));
423 assert(!strstr(suffix, "//"));
424
425 dropins = nulstr_contains(have_dropins, suffix);
426
427 top = ordered_hashmap_new(&string_hash_ops);
428 bottom = ordered_hashmap_new(&string_hash_ops);
429 drops = ordered_hashmap_new(&string_hash_ops);
430 if (!top || !bottom || !drops) {
431 r = -ENOMEM;
432 goto finish;
433 }
434
435 NULSTR_FOREACH(p, prefixes) {
436 _cleanup_free_ char *t = NULL;
437 int skip;
438
439 skip = should_skip_prefix(p);
440 if (skip < 0) {
441 r = skip;
442 goto finish;
443 }
444 if (skip)
445 continue;
446
447 t = strjoin(p, "/", suffix);
448 if (!t) {
449 r = -ENOMEM;
450 goto finish;
451 }
452
453 k = enumerate_dir(top, bottom, drops, t, dropins);
454 if (r == 0)
455 r = k;
456 }
457
458 ORDERED_HASHMAP_FOREACH_KEY(f, key, top, i) {
459 char *o;
460
461 o = ordered_hashmap_get(bottom, key);
462 assert(o);
463
464 if (!onlyprefix || startswith(o, onlyprefix)) {
465 if (path_equal(o, f)) {
466 notify_override_unchanged(f);
467 } else {
468 k = found_override(f, o);
469 if (k < 0)
470 r = k;
471 else
472 n_found += k;
473 }
474 }
475
476 h = ordered_hashmap_get(drops, key);
477 if (h)
478 ORDERED_HASHMAP_FOREACH(o, h, j)
479 if (!onlyprefix || startswith(o, onlyprefix))
480 n_found += notify_override_extended(f, o);
481 }
482
483 finish:
484 ordered_hashmap_free_free(top);
485 ordered_hashmap_free_free(bottom);
486
487 ORDERED_HASHMAP_FOREACH_KEY(h, key, drops, i) {
488 ordered_hashmap_free_free(ordered_hashmap_remove(drops, key));
489 ordered_hashmap_remove(drops, key);
490 free(key);
491 }
492 ordered_hashmap_free(drops);
493
494 return r < 0 ? r : n_found;
495 }
496
497 static int process_suffixes(const char *onlyprefix) {
498 const char *n;
499 int n_found = 0, r;
500
501 NULSTR_FOREACH(n, suffixes) {
502 r = process_suffix(n, onlyprefix);
503 if (r < 0)
504 return r;
505
506 n_found += r;
507 }
508
509 return n_found;
510 }
511
512 static int process_suffix_chop(const char *arg) {
513 const char *p;
514
515 assert(arg);
516
517 if (!path_is_absolute(arg))
518 return process_suffix(arg, NULL);
519
520 /* Strip prefix from the suffix */
521 NULSTR_FOREACH(p, prefixes) {
522 const char *suffix;
523 int skip;
524
525 skip = should_skip_prefix(p);
526 if (skip < 0)
527 return skip;
528 if (skip)
529 continue;
530
531 suffix = startswith(arg, p);
532 if (suffix) {
533 suffix += strspn(suffix, "/");
534 if (*suffix)
535 return process_suffix(suffix, NULL);
536 else
537 return process_suffixes(arg);
538 }
539 }
540
541 log_error("Invalid suffix specification %s.", arg);
542 return -EINVAL;
543 }
544
545 static void help(void) {
546 printf("%s [OPTIONS...] [SUFFIX...]\n\n"
547 "Find overridden configuration files.\n\n"
548 " -h --help Show this help\n"
549 " --version Show package version\n"
550 " --no-pager Do not pipe output into a pager\n"
551 " --diff[=1|0] Show a diff when overridden files differ\n"
552 " -t --type=LIST... Only display a selected set of override types\n"
553 , program_invocation_short_name);
554 }
555
556 static int parse_flags(const char *flag_str, int flags) {
557 const char *word, *state;
558 size_t l;
559
560 FOREACH_WORD_SEPARATOR(word, l, flag_str, ",", state) {
561 if (strneq("masked", word, l))
562 flags |= SHOW_MASKED;
563 else if (strneq ("equivalent", word, l))
564 flags |= SHOW_EQUIVALENT;
565 else if (strneq("redirected", word, l))
566 flags |= SHOW_REDIRECTED;
567 else if (strneq("overridden", word, l))
568 flags |= SHOW_OVERRIDDEN;
569 else if (strneq("unchanged", word, l))
570 flags |= SHOW_UNCHANGED;
571 else if (strneq("extended", word, l))
572 flags |= SHOW_EXTENDED;
573 else if (strneq("default", word, l))
574 flags |= SHOW_DEFAULTS;
575 else
576 return -EINVAL;
577 }
578 return flags;
579 }
580
581 static int parse_argv(int argc, char *argv[]) {
582
583 enum {
584 ARG_NO_PAGER = 0x100,
585 ARG_DIFF,
586 ARG_VERSION
587 };
588
589 static const struct option options[] = {
590 { "help", no_argument, NULL, 'h' },
591 { "version", no_argument, NULL, ARG_VERSION },
592 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
593 { "diff", optional_argument, NULL, ARG_DIFF },
594 { "type", required_argument, NULL, 't' },
595 {}
596 };
597
598 int c;
599
600 assert(argc >= 1);
601 assert(argv);
602
603 while ((c = getopt_long(argc, argv, "ht:", options, NULL)) >= 0)
604
605 switch (c) {
606
607 case 'h':
608 help();
609 return 0;
610
611 case ARG_VERSION:
612 return version();
613
614 case ARG_NO_PAGER:
615 arg_no_pager = true;
616 break;
617
618 case 't': {
619 int f;
620 f = parse_flags(optarg, arg_flags);
621 if (f < 0) {
622 log_error("Failed to parse flags field.");
623 return -EINVAL;
624 }
625 arg_flags = f;
626 break;
627 }
628
629 case ARG_DIFF:
630 if (!optarg)
631 arg_diff = 1;
632 else {
633 int b;
634
635 b = parse_boolean(optarg);
636 if (b < 0) {
637 log_error("Failed to parse diff boolean.");
638 return -EINVAL;
639 }
640
641 arg_diff = b;
642 }
643 break;
644
645 case '?':
646 return -EINVAL;
647
648 default:
649 assert_not_reached("Unhandled option");
650 }
651
652 return 1;
653 }
654
655 int main(int argc, char *argv[]) {
656 int r, k, n_found = 0;
657
658 log_parse_environment();
659 log_open();
660
661 r = parse_argv(argc, argv);
662 if (r <= 0)
663 goto finish;
664
665 if (arg_flags == 0)
666 arg_flags = SHOW_DEFAULTS;
667
668 if (arg_diff < 0)
669 arg_diff = !!(arg_flags & SHOW_OVERRIDDEN);
670 else if (arg_diff)
671 arg_flags |= SHOW_OVERRIDDEN;
672
673 pager_open(arg_no_pager, false);
674
675 if (optind < argc) {
676 int i;
677
678 for (i = optind; i < argc; i++) {
679 path_kill_slashes(argv[i]);
680
681 k = process_suffix_chop(argv[i]);
682 if (k < 0)
683 r = k;
684 else
685 n_found += k;
686 }
687
688 } else {
689 k = process_suffixes(NULL);
690 if (k < 0)
691 r = k;
692 else
693 n_found += k;
694 }
695
696 if (r >= 0)
697 printf("%s%i overridden configuration files found.\n", n_found ? "\n" : "", n_found);
698
699 finish:
700 pager_close();
701
702 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
703 }