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