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