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