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