]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/delta/delta.c
treewide: use log_*_errno whenever %m is in the format string
[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 %s\n",
107 ansi_highlight_red(), "[MASKED]", ansi_highlight_off(),
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_highlight_off(),
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_highlight_off(),
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_highlight_off(),
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_highlight_off(),
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 log_error_errno(errno, "Failed to fork off diff: %m");
190 return -errno;
191 } else if (pid == 0) {
192 execlp("diff", "diff", "-us", "--", bottom, top, NULL);
193 log_error_errno(errno, "Failed to execute diff: %m");
194 _exit(1);
195 }
196
197 wait_for_terminate_and_warn("diff", pid);
198 putchar('\n');
199
200 return k;
201 }
202
203 static int enumerate_dir_d(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *toppath, const char *drop) {
204 _cleanup_free_ char *unit = NULL;
205 _cleanup_free_ char *path = NULL;
206 _cleanup_strv_free_ char **list = NULL;
207 char **file;
208 char *c;
209 int r;
210
211 assert(!endswith(drop, "/"));
212
213 path = strjoin(toppath, "/", drop, NULL);
214 if (!path)
215 return -ENOMEM;
216
217 log_debug("Looking at %s", path);
218
219 unit = strdup(drop);
220 if (!unit)
221 return -ENOMEM;
222
223 c = strrchr(unit, '.');
224 if (!c)
225 return -EINVAL;
226 *c = 0;
227
228 r = get_files_in_directory(path, &list);
229 if (r < 0)
230 return log_error_errno(r, "Failed to enumerate %s: %m", path);
231
232 STRV_FOREACH(file, list) {
233 Hashmap *h;
234 int k;
235 char *p;
236 char *d;
237
238 if (!endswith(*file, ".conf"))
239 continue;
240
241 p = strjoin(path, "/", *file, NULL);
242 if (!p)
243 return -ENOMEM;
244 d = p + strlen(toppath) + 1;
245
246 log_debug("Adding at top: %s %s %s", d, draw_special_char(DRAW_ARROW), p);
247 k = hashmap_put(top, d, p);
248 if (k >= 0) {
249 p = strdup(p);
250 if (!p)
251 return -ENOMEM;
252 d = p + strlen(toppath) + 1;
253 } else if (k != -EEXIST) {
254 free(p);
255 return k;
256 }
257
258 log_debug("Adding at bottom: %s %s %s", d, draw_special_char(DRAW_ARROW), p);
259 free(hashmap_remove(bottom, d));
260 k = hashmap_put(bottom, d, p);
261 if (k < 0) {
262 free(p);
263 return k;
264 }
265
266 h = hashmap_get(drops, unit);
267 if (!h) {
268 h = hashmap_new(&string_hash_ops);
269 if (!h)
270 return -ENOMEM;
271 hashmap_put(drops, unit, h);
272 unit = strdup(unit);
273 if (!unit)
274 return -ENOMEM;
275 }
276
277 p = strdup(p);
278 if (!p)
279 return -ENOMEM;
280
281 log_debug("Adding to drops: %s %s %s %s %s",
282 unit, draw_special_char(DRAW_ARROW), basename(p), draw_special_char(DRAW_ARROW), p);
283 k = hashmap_put(h, basename(p), p);
284 if (k < 0) {
285 free(p);
286 if (k != -EEXIST)
287 return k;
288 }
289 }
290 return 0;
291 }
292
293 static int enumerate_dir(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *path, bool dropins) {
294 _cleanup_closedir_ DIR *d;
295
296 assert(top);
297 assert(bottom);
298 assert(drops);
299 assert(path);
300
301 log_debug("Looking at %s", path);
302
303 d = opendir(path);
304 if (!d) {
305 if (errno == ENOENT)
306 return 0;
307
308 log_error_errno(errno, "Failed to open %s: %m", path);
309 return -errno;
310 }
311
312 for (;;) {
313 struct dirent *de;
314 int k;
315 char *p;
316
317 errno = 0;
318 de = readdir(d);
319 if (!de)
320 return -errno;
321
322 dirent_ensure_type(d, de);
323
324 if (dropins && de->d_type == DT_DIR && endswith(de->d_name, ".d"))
325 enumerate_dir_d(top, bottom, drops, path, de->d_name);
326
327 if (!dirent_is_file(de))
328 continue;
329
330 p = strjoin(path, "/", de->d_name, NULL);
331 if (!p)
332 return -ENOMEM;
333
334 log_debug("Adding at top: %s %s %s", basename(p), draw_special_char(DRAW_ARROW), p);
335 k = hashmap_put(top, basename(p), p);
336 if (k >= 0) {
337 p = strdup(p);
338 if (!p)
339 return -ENOMEM;
340 } else if (k != -EEXIST) {
341 free(p);
342 return k;
343 }
344
345 log_debug("Adding at bottom: %s %s %s", basename(p), draw_special_char(DRAW_ARROW), p);
346 free(hashmap_remove(bottom, basename(p)));
347 k = hashmap_put(bottom, basename(p), p);
348 if (k < 0) {
349 free(p);
350 return k;
351 }
352 }
353 }
354
355 static int process_suffix(const char *suffix, const char *onlyprefix) {
356 const char *p;
357 char *f;
358 Hashmap *top, *bottom, *drops;
359 Hashmap *h;
360 char *key;
361 int r = 0, k;
362 Iterator i, j;
363 int n_found = 0;
364 bool dropins;
365
366 assert(suffix);
367 assert(!startswith(suffix, "/"));
368 assert(!strstr(suffix, "//"));
369
370 dropins = nulstr_contains(have_dropins, suffix);
371
372 top = hashmap_new(&string_hash_ops);
373 bottom = hashmap_new(&string_hash_ops);
374 drops = hashmap_new(&string_hash_ops);
375 if (!top || !bottom || !drops) {
376 r = -ENOMEM;
377 goto finish;
378 }
379
380 NULSTR_FOREACH(p, prefixes) {
381 _cleanup_free_ char *t = NULL;
382
383 t = strjoin(p, "/", suffix, NULL);
384 if (!t) {
385 r = -ENOMEM;
386 goto finish;
387 }
388
389 k = enumerate_dir(top, bottom, drops, t, dropins);
390 if (r == 0)
391 r = k;
392 }
393
394 HASHMAP_FOREACH_KEY(f, key, top, i) {
395 char *o;
396
397 o = hashmap_get(bottom, key);
398 assert(o);
399
400 if (!onlyprefix || startswith(o, onlyprefix)) {
401 if (path_equal(o, f)) {
402 notify_override_unchanged(f);
403 } else {
404 k = found_override(f, o);
405 if (k < 0)
406 r = k;
407 else
408 n_found += k;
409 }
410 }
411
412 h = hashmap_get(drops, key);
413 if (h)
414 HASHMAP_FOREACH(o, h, j)
415 if (!onlyprefix || startswith(o, onlyprefix))
416 n_found += notify_override_extended(f, o);
417 }
418
419 finish:
420 if (top)
421 hashmap_free_free(top);
422 if (bottom)
423 hashmap_free_free(bottom);
424 if (drops) {
425 HASHMAP_FOREACH_KEY(h, key, drops, i){
426 hashmap_free_free(hashmap_remove(drops, key));
427 hashmap_remove(drops, key);
428 free(key);
429 }
430 hashmap_free(drops);
431 }
432 return r < 0 ? r : n_found;
433 }
434
435 static int process_suffixes(const char *onlyprefix) {
436 const char *n;
437 int n_found = 0, r;
438
439 NULSTR_FOREACH(n, suffixes) {
440 r = process_suffix(n, onlyprefix);
441 if (r < 0)
442 return r;
443 else
444 n_found += r;
445 }
446 return n_found;
447 }
448
449 static int process_suffix_chop(const char *arg) {
450 const char *p;
451
452 assert(arg);
453
454 if (!path_is_absolute(arg))
455 return process_suffix(arg, NULL);
456
457 /* Strip prefix from the suffix */
458 NULSTR_FOREACH(p, prefixes) {
459 const char *suffix = startswith(arg, p);
460 if (suffix) {
461 suffix += strspn(suffix, "/");
462 if (*suffix)
463 return process_suffix(suffix, NULL);
464 else
465 return process_suffixes(arg);
466 }
467 }
468
469 log_error("Invalid suffix specification %s.", arg);
470 return -EINVAL;
471 }
472
473 static void help(void) {
474 printf("%s [OPTIONS...] [SUFFIX...]\n\n"
475 "Find overridden configuration files.\n\n"
476 " -h --help Show this help\n"
477 " --version Show package version\n"
478 " --no-pager Do not pipe output into a pager\n"
479 " --diff[=1|0] Show a diff when overridden files differ\n"
480 " -t --type=LIST... Only display a selected set of override types\n"
481 , program_invocation_short_name);
482 }
483
484 static int parse_flags(const char *flag_str, int flags) {
485 const char *word, *state;
486 size_t l;
487
488 FOREACH_WORD(word, l, flag_str, state) {
489 if (strneq("masked", word, l))
490 flags |= SHOW_MASKED;
491 else if (strneq ("equivalent", word, l))
492 flags |= SHOW_EQUIVALENT;
493 else if (strneq("redirected", word, l))
494 flags |= SHOW_REDIRECTED;
495 else if (strneq("overridden", word, l))
496 flags |= SHOW_OVERRIDDEN;
497 else if (strneq("unchanged", word, l))
498 flags |= SHOW_UNCHANGED;
499 else if (strneq("extended", word, l))
500 flags |= SHOW_EXTENDED;
501 else if (strneq("default", word, l))
502 flags |= SHOW_DEFAULTS;
503 else
504 return -EINVAL;
505 }
506 return flags;
507 }
508
509 static int parse_argv(int argc, char *argv[]) {
510
511 enum {
512 ARG_NO_PAGER = 0x100,
513 ARG_DIFF,
514 ARG_VERSION
515 };
516
517 static const struct option options[] = {
518 { "help", no_argument, NULL, 'h' },
519 { "version", no_argument, NULL, ARG_VERSION },
520 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
521 { "diff", optional_argument, NULL, ARG_DIFF },
522 { "type", required_argument, NULL, 't' },
523 {}
524 };
525
526 int c;
527
528 assert(argc >= 1);
529 assert(argv);
530
531 while ((c = getopt_long(argc, argv, "ht:", options, NULL)) >= 0)
532
533 switch (c) {
534
535 case 'h':
536 help();
537 return 0;
538
539 case ARG_VERSION:
540 puts(PACKAGE_STRING);
541 puts(SYSTEMD_FEATURES);
542 return 0;
543
544 case ARG_NO_PAGER:
545 arg_no_pager = true;
546 break;
547
548 case 't': {
549 int f;
550 f = parse_flags(optarg, arg_flags);
551 if (f < 0) {
552 log_error("Failed to parse flags field.");
553 return -EINVAL;
554 }
555 arg_flags = f;
556 break;
557 }
558
559 case ARG_DIFF:
560 if (!optarg)
561 arg_diff = 1;
562 else {
563 int b;
564
565 b = parse_boolean(optarg);
566 if (b < 0) {
567 log_error("Failed to parse diff boolean.");
568 return -EINVAL;
569 } else if (b)
570 arg_diff = 1;
571 else
572 arg_diff = 0;
573 }
574 break;
575
576 case '?':
577 return -EINVAL;
578
579 default:
580 assert_not_reached("Unhandled option");
581 }
582
583 return 1;
584 }
585
586 int main(int argc, char *argv[]) {
587 int r = 0, k;
588 int 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_if_enabled();
606
607 if (optind < argc) {
608 int i;
609
610 for (i = optind; i < argc; i++) {
611 path_kill_slashes(argv[i]);
612 k = process_suffix_chop(argv[i]);
613 if (k < 0)
614 r = k;
615 else
616 n_found += k;
617 }
618
619 } else {
620 k = process_suffixes(NULL);
621 if (k < 0)
622 r = k;
623 else
624 n_found += k;
625 }
626
627 if (r >= 0)
628 printf("%s%i overridden configuration files found.\n",
629 n_found ? "\n" : "", n_found);
630
631 finish:
632 pager_close();
633
634 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
635 }