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