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