]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/delta/delta.c
Merge pull request #1372 from jemk/prefsrc
[thirdparty/systemd.git] / src / delta / delta.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2012 Lennart Poettering
7 Copyright 2013 Zbigniew Jędrzejewski-Szmek
8
9 systemd is free software; you can redistribute it and/or modify it
10 under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation; either version 2.1 of the License, or
12 (at your option) any later version.
13
14 systemd is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public License
20 along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 ***/
22
23 #include <errno.h>
24 #include <getopt.h>
25 #include <string.h>
26 #include <sys/prctl.h>
27 #include <unistd.h>
28
29 #include "hashmap.h"
30 #include "log.h"
31 #include "pager.h"
32 #include "path-util.h"
33 #include "process-util.h"
34 #include "signal-util.h"
35 #include "strv.h"
36 #include "terminal-util.h"
37 #include "util.h"
38
39 static const char prefixes[] =
40 "/etc\0"
41 "/run\0"
42 "/usr/local/lib\0"
43 "/usr/local/share\0"
44 "/usr/lib\0"
45 "/usr/share\0"
46 #ifdef HAVE_SPLIT_USR
47 "/lib\0"
48 #endif
49 ;
50
51 static const char suffixes[] =
52 "sysctl.d\0"
53 "tmpfiles.d\0"
54 "modules-load.d\0"
55 "binfmt.d\0"
56 "systemd/system\0"
57 "systemd/user\0"
58 "systemd/system-preset\0"
59 "systemd/user-preset\0"
60 "udev/rules.d\0"
61 "modprobe.d\0";
62
63 static const char have_dropins[] =
64 "systemd/system\0"
65 "systemd/user\0";
66
67 static bool arg_no_pager = false;
68 static int arg_diff = -1;
69
70 static enum {
71 SHOW_MASKED = 1 << 0,
72 SHOW_EQUIVALENT = 1 << 1,
73 SHOW_REDIRECTED = 1 << 2,
74 SHOW_OVERRIDDEN = 1 << 3,
75 SHOW_UNCHANGED = 1 << 4,
76 SHOW_EXTENDED = 1 << 5,
77
78 SHOW_DEFAULTS =
79 (SHOW_MASKED | SHOW_EQUIVALENT | SHOW_REDIRECTED | SHOW_OVERRIDDEN | SHOW_EXTENDED)
80 } arg_flags = 0;
81
82 static void pager_open_if_enabled(void) {
83
84 if (arg_no_pager)
85 return;
86
87 pager_open(false);
88 }
89
90 static int equivalent(const char *a, const char *b) {
91 _cleanup_free_ char *x = NULL, *y = NULL;
92
93 x = canonicalize_file_name(a);
94 if (!x)
95 return -errno;
96
97 y = canonicalize_file_name(b);
98 if (!y)
99 return -errno;
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, draw_special_char(DRAW_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, draw_special_char(DRAW_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, draw_special_char(DRAW_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, draw_special_char(DRAW_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, draw_special_char(DRAW_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(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *toppath, const char *drop) {
210 _cleanup_free_ char *unit = NULL;
211 _cleanup_free_ char *path = NULL;
212 _cleanup_strv_free_ char **list = NULL;
213 char **file;
214 char *c;
215 int r;
216
217 assert(!endswith(drop, "/"));
218
219 path = strjoin(toppath, "/", drop, NULL);
220 if (!path)
221 return -ENOMEM;
222
223 log_debug("Looking at %s", path);
224
225 unit = strdup(drop);
226 if (!unit)
227 return -ENOMEM;
228
229 c = strrchr(unit, '.');
230 if (!c)
231 return -EINVAL;
232 *c = 0;
233
234 r = get_files_in_directory(path, &list);
235 if (r < 0)
236 return log_error_errno(r, "Failed to enumerate %s: %m", path);
237
238 STRV_FOREACH(file, list) {
239 Hashmap *h;
240 int k;
241 char *p;
242 char *d;
243
244 if (!endswith(*file, ".conf"))
245 continue;
246
247 p = strjoin(path, "/", *file, NULL);
248 if (!p)
249 return -ENOMEM;
250 d = p + strlen(toppath) + 1;
251
252 log_debug("Adding at top: %s %s %s", d, draw_special_char(DRAW_ARROW), p);
253 k = hashmap_put(top, d, p);
254 if (k >= 0) {
255 p = strdup(p);
256 if (!p)
257 return -ENOMEM;
258 d = p + strlen(toppath) + 1;
259 } else if (k != -EEXIST) {
260 free(p);
261 return k;
262 }
263
264 log_debug("Adding at bottom: %s %s %s", d, draw_special_char(DRAW_ARROW), p);
265 free(hashmap_remove(bottom, d));
266 k = hashmap_put(bottom, d, p);
267 if (k < 0) {
268 free(p);
269 return k;
270 }
271
272 h = hashmap_get(drops, unit);
273 if (!h) {
274 h = hashmap_new(&string_hash_ops);
275 if (!h)
276 return -ENOMEM;
277 hashmap_put(drops, unit, h);
278 unit = strdup(unit);
279 if (!unit)
280 return -ENOMEM;
281 }
282
283 p = strdup(p);
284 if (!p)
285 return -ENOMEM;
286
287 log_debug("Adding to drops: %s %s %s %s %s",
288 unit, draw_special_char(DRAW_ARROW), basename(p), draw_special_char(DRAW_ARROW), p);
289 k = hashmap_put(h, basename(p), p);
290 if (k < 0) {
291 free(p);
292 if (k != -EEXIST)
293 return k;
294 }
295 }
296 return 0;
297 }
298
299 static int enumerate_dir(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *path, bool dropins) {
300 _cleanup_closedir_ DIR *d;
301
302 assert(top);
303 assert(bottom);
304 assert(drops);
305 assert(path);
306
307 log_debug("Looking at %s", path);
308
309 d = opendir(path);
310 if (!d) {
311 if (errno == ENOENT)
312 return 0;
313
314 log_error_errno(errno, "Failed to open %s: %m", path);
315 return -errno;
316 }
317
318 for (;;) {
319 struct dirent *de;
320 int k;
321 char *p;
322
323 errno = 0;
324 de = readdir(d);
325 if (!de)
326 return -errno;
327
328 dirent_ensure_type(d, de);
329
330 if (dropins && de->d_type == DT_DIR && endswith(de->d_name, ".d"))
331 enumerate_dir_d(top, bottom, drops, path, de->d_name);
332
333 if (!dirent_is_file(de))
334 continue;
335
336 p = strjoin(path, "/", de->d_name, NULL);
337 if (!p)
338 return -ENOMEM;
339
340 log_debug("Adding at top: %s %s %s", basename(p), draw_special_char(DRAW_ARROW), p);
341 k = hashmap_put(top, basename(p), p);
342 if (k >= 0) {
343 p = strdup(p);
344 if (!p)
345 return -ENOMEM;
346 } else if (k != -EEXIST) {
347 free(p);
348 return k;
349 }
350
351 log_debug("Adding at bottom: %s %s %s", basename(p), draw_special_char(DRAW_ARROW), p);
352 free(hashmap_remove(bottom, basename(p)));
353 k = hashmap_put(bottom, basename(p), p);
354 if (k < 0) {
355 free(p);
356 return k;
357 }
358 }
359 }
360
361 static int process_suffix(const char *suffix, const char *onlyprefix) {
362 const char *p;
363 char *f;
364 Hashmap *top, *bottom, *drops;
365 Hashmap *h;
366 char *key;
367 int r = 0, k;
368 Iterator i, j;
369 int n_found = 0;
370 bool dropins;
371
372 assert(suffix);
373 assert(!startswith(suffix, "/"));
374 assert(!strstr(suffix, "//"));
375
376 dropins = nulstr_contains(have_dropins, suffix);
377
378 top = hashmap_new(&string_hash_ops);
379 bottom = hashmap_new(&string_hash_ops);
380 drops = hashmap_new(&string_hash_ops);
381 if (!top || !bottom || !drops) {
382 r = -ENOMEM;
383 goto finish;
384 }
385
386 NULSTR_FOREACH(p, prefixes) {
387 _cleanup_free_ char *t = NULL;
388
389 t = strjoin(p, "/", suffix, NULL);
390 if (!t) {
391 r = -ENOMEM;
392 goto finish;
393 }
394
395 k = enumerate_dir(top, bottom, drops, t, dropins);
396 if (r == 0)
397 r = k;
398 }
399
400 HASHMAP_FOREACH_KEY(f, key, top, i) {
401 char *o;
402
403 o = hashmap_get(bottom, key);
404 assert(o);
405
406 if (!onlyprefix || startswith(o, onlyprefix)) {
407 if (path_equal(o, f)) {
408 notify_override_unchanged(f);
409 } else {
410 k = found_override(f, o);
411 if (k < 0)
412 r = k;
413 else
414 n_found += k;
415 }
416 }
417
418 h = hashmap_get(drops, key);
419 if (h)
420 HASHMAP_FOREACH(o, h, j)
421 if (!onlyprefix || startswith(o, onlyprefix))
422 n_found += notify_override_extended(f, o);
423 }
424
425 finish:
426 if (top)
427 hashmap_free_free(top);
428 if (bottom)
429 hashmap_free_free(bottom);
430 if (drops) {
431 HASHMAP_FOREACH_KEY(h, key, drops, i){
432 hashmap_free_free(hashmap_remove(drops, key));
433 hashmap_remove(drops, key);
434 free(key);
435 }
436 hashmap_free(drops);
437 }
438 return r < 0 ? r : n_found;
439 }
440
441 static int process_suffixes(const char *onlyprefix) {
442 const char *n;
443 int n_found = 0, r;
444
445 NULSTR_FOREACH(n, suffixes) {
446 r = process_suffix(n, onlyprefix);
447 if (r < 0)
448 return r;
449 else
450 n_found += r;
451 }
452 return n_found;
453 }
454
455 static int process_suffix_chop(const char *arg) {
456 const char *p;
457
458 assert(arg);
459
460 if (!path_is_absolute(arg))
461 return process_suffix(arg, NULL);
462
463 /* Strip prefix from the suffix */
464 NULSTR_FOREACH(p, prefixes) {
465 const char *suffix = startswith(arg, p);
466 if (suffix) {
467 suffix += strspn(suffix, "/");
468 if (*suffix)
469 return process_suffix(suffix, NULL);
470 else
471 return process_suffixes(arg);
472 }
473 }
474
475 log_error("Invalid suffix specification %s.", arg);
476 return -EINVAL;
477 }
478
479 static void help(void) {
480 printf("%s [OPTIONS...] [SUFFIX...]\n\n"
481 "Find overridden configuration files.\n\n"
482 " -h --help Show this help\n"
483 " --version Show package version\n"
484 " --no-pager Do not pipe output into a pager\n"
485 " --diff[=1|0] Show a diff when overridden files differ\n"
486 " -t --type=LIST... Only display a selected set of override types\n"
487 , program_invocation_short_name);
488 }
489
490 static int parse_flags(const char *flag_str, int flags) {
491 const char *word, *state;
492 size_t l;
493
494 FOREACH_WORD_SEPARATOR(word, l, flag_str, ",", state) {
495 if (strneq("masked", word, l))
496 flags |= SHOW_MASKED;
497 else if (strneq ("equivalent", word, l))
498 flags |= SHOW_EQUIVALENT;
499 else if (strneq("redirected", word, l))
500 flags |= SHOW_REDIRECTED;
501 else if (strneq("overridden", word, l))
502 flags |= SHOW_OVERRIDDEN;
503 else if (strneq("unchanged", word, l))
504 flags |= SHOW_UNCHANGED;
505 else if (strneq("extended", word, l))
506 flags |= SHOW_EXTENDED;
507 else if (strneq("default", word, l))
508 flags |= SHOW_DEFAULTS;
509 else
510 return -EINVAL;
511 }
512 return flags;
513 }
514
515 static int parse_argv(int argc, char *argv[]) {
516
517 enum {
518 ARG_NO_PAGER = 0x100,
519 ARG_DIFF,
520 ARG_VERSION
521 };
522
523 static const struct option options[] = {
524 { "help", no_argument, NULL, 'h' },
525 { "version", no_argument, NULL, ARG_VERSION },
526 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
527 { "diff", optional_argument, NULL, ARG_DIFF },
528 { "type", required_argument, NULL, 't' },
529 {}
530 };
531
532 int c;
533
534 assert(argc >= 1);
535 assert(argv);
536
537 while ((c = getopt_long(argc, argv, "ht:", options, NULL)) >= 0)
538
539 switch (c) {
540
541 case 'h':
542 help();
543 return 0;
544
545 case ARG_VERSION:
546 return version();
547
548 case ARG_NO_PAGER:
549 arg_no_pager = true;
550 break;
551
552 case 't': {
553 int f;
554 f = parse_flags(optarg, arg_flags);
555 if (f < 0) {
556 log_error("Failed to parse flags field.");
557 return -EINVAL;
558 }
559 arg_flags = f;
560 break;
561 }
562
563 case ARG_DIFF:
564 if (!optarg)
565 arg_diff = 1;
566 else {
567 int b;
568
569 b = parse_boolean(optarg);
570 if (b < 0) {
571 log_error("Failed to parse diff boolean.");
572 return -EINVAL;
573 } else if (b)
574 arg_diff = 1;
575 else
576 arg_diff = 0;
577 }
578 break;
579
580 case '?':
581 return -EINVAL;
582
583 default:
584 assert_not_reached("Unhandled option");
585 }
586
587 return 1;
588 }
589
590 int main(int argc, char *argv[]) {
591 int r = 0, k;
592 int n_found = 0;
593
594 log_parse_environment();
595 log_open();
596
597 r = parse_argv(argc, argv);
598 if (r <= 0)
599 goto finish;
600
601 if (arg_flags == 0)
602 arg_flags = SHOW_DEFAULTS;
603
604 if (arg_diff < 0)
605 arg_diff = !!(arg_flags & SHOW_OVERRIDDEN);
606 else if (arg_diff)
607 arg_flags |= SHOW_OVERRIDDEN;
608
609 pager_open_if_enabled();
610
611 if (optind < argc) {
612 int i;
613
614 for (i = optind; i < argc; i++) {
615 path_kill_slashes(argv[i]);
616 k = process_suffix_chop(argv[i]);
617 if (k < 0)
618 r = k;
619 else
620 n_found += k;
621 }
622
623 } else {
624 k = process_suffixes(NULL);
625 if (k < 0)
626 r = k;
627 else
628 n_found += k;
629 }
630
631 if (r >= 0)
632 printf("%s%i overridden configuration files found.\n",
633 n_found ? "\n" : "", n_found);
634
635 finish:
636 pager_close();
637
638 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
639 }