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