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