]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/delta/delta.c
d13516e3107f9da76fab27a8848e0c7c31c9bde8
[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 if (r < 0)
290 return r;
291 }
292 return 0;
293 }
294
295 static int enumerate_dir(
296 OrderedHashmap **top,
297 OrderedHashmap **bottom,
298 OrderedHashmap **drops,
299 const char *path, bool dropins) {
300
301 _cleanup_closedir_ DIR *d = NULL;
302 _cleanup_strv_free_ char **files = NULL, **dirs = NULL;
303 size_t n_files = 0, n_dirs = 0;
304 int r;
305
306 assert(top);
307 assert(bottom);
308 assert(drops);
309 assert(path);
310
311 log_debug("Looking at %s", path);
312
313 d = opendir(path);
314 if (!d) {
315 if (errno == ENOENT)
316 return 0;
317
318 return log_error_errno(errno, "Failed to open %s: %m", path);
319 }
320
321 FOREACH_DIRENT_ALL(de, d, return -errno) {
322 if (dropins && de->d_type == DT_DIR && endswith(de->d_name, ".d")) {
323 if (!GREEDY_REALLOC0(dirs, n_dirs + 2))
324 return -ENOMEM;
325
326 dirs[n_dirs] = strdup(de->d_name);
327 if (!dirs[n_dirs])
328 return -ENOMEM;
329 n_dirs++;
330 }
331
332 if (!dirent_is_file(de))
333 continue;
334
335 if (!GREEDY_REALLOC0(files, n_files + 2))
336 return -ENOMEM;
337
338 files[n_files] = strdup(de->d_name);
339 if (!files[n_files])
340 return -ENOMEM;
341 n_files++;
342 }
343
344 strv_sort(dirs);
345 strv_sort(files);
346
347 STRV_FOREACH(t, dirs) {
348 r = enumerate_dir_d(top, bottom, drops, path, *t);
349 if (r < 0)
350 return r;
351 }
352
353 STRV_FOREACH(t, files) {
354 log_debug("Adding at top: %s %s %s/%s", *t, glyph(GLYPH_ARROW_RIGHT), path, *t);
355 r = path_put(top, path, *t, /* override = */ false);
356 if (r < 0)
357 return r;
358
359 log_debug("Adding at bottom: %s %s %s/%s", *t, glyph(GLYPH_ARROW_RIGHT), path, *t);
360 r = path_put(bottom, path, *t, /* override = */ true);
361 if (r < 0)
362 return r;
363 }
364
365 return 0;
366 }
367
368 static int process_suffix(const char *suffix, const char *onlyprefix) {
369 int r, ret = 0;
370
371 assert(suffix);
372 assert(!startswith(suffix, "/"));
373 assert(!strstr(suffix, "//"));
374
375 bool dropins = nulstr_contains(have_dropins, suffix);
376
377 _cleanup_ordered_hashmap_free_ OrderedHashmap *top = NULL, *bottom = NULL, *drops = NULL;
378 NULSTR_FOREACH(p, prefixes) {
379 _cleanup_free_ char *t = NULL;
380
381 t = path_join(p, suffix);
382 if (!t)
383 return -ENOMEM;
384
385 RET_GATHER(ret, enumerate_dir(&top, &bottom, &drops, t, dropins));
386 }
387
388 int n_found = 0;
389 char *f, *key;
390 ORDERED_HASHMAP_FOREACH_KEY(f, key, top) {
391 char *o;
392
393 o = ordered_hashmap_get(bottom, key);
394 assert(o);
395
396 if (!onlyprefix || startswith(o, onlyprefix)) {
397 if (path_equal(o, f)) {
398 notify_override_unchanged(f);
399 } else {
400 r = found_override(f, o);
401 if (r < 0)
402 RET_GATHER(ret, r);
403 else
404 n_found += r;
405 }
406 }
407
408 OrderedHashmap *h = ordered_hashmap_get(drops, key);
409 if (h)
410 ORDERED_HASHMAP_FOREACH(o, h)
411 if (!onlyprefix || startswith(o, onlyprefix))
412 n_found += notify_override_extended(f, o);
413 }
414
415 return ret < 0 ? ret : n_found;
416 }
417
418 static int process_suffixes(const char *onlyprefix) {
419 int n_found = 0, r;
420
421 NULSTR_FOREACH(n, suffixes) {
422 r = process_suffix(n, onlyprefix);
423 if (r < 0)
424 return r;
425
426 n_found += r;
427 }
428
429 return n_found;
430 }
431
432 static int process_suffix_chop(const char *arg) {
433 assert(arg);
434
435 if (!path_is_absolute(arg))
436 return process_suffix(arg, NULL);
437
438 /* Strip prefix from the suffix */
439 NULSTR_FOREACH(p, prefixes) {
440 const char *suffix;
441
442 suffix = startswith(arg, p);
443 if (suffix) {
444 suffix += strspn(suffix, "/");
445 if (*suffix)
446 return process_suffix(suffix, p);
447 else
448 return process_suffixes(arg);
449 }
450 }
451
452 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
453 "Invalid suffix specification %s.", arg);
454 }
455
456 static int help(void) {
457 _cleanup_free_ char *link = NULL;
458 int r;
459
460 r = terminal_urlify_man("systemd-delta", "1", &link);
461 if (r < 0)
462 return log_oom();
463
464 printf("%s [OPTIONS...] [SUFFIX...]\n\n"
465 "Find overridden configuration files.\n\n"
466 " -h --help Show this help\n"
467 " --version Show package version\n"
468 " --no-pager Do not pipe output into a pager\n"
469 " --diff[=1|0] Show a diff when overridden files differ\n"
470 " -t --type=LIST... Only display a selected set of override types\n"
471 "\nSee the %s for details.\n",
472 program_invocation_short_name,
473 link);
474
475 return 0;
476 }
477
478 static int parse_flags(const char *flag_str, int flags) {
479 for (;;) {
480 _cleanup_free_ char *word = NULL;
481 int r;
482
483 r = extract_first_word(&flag_str, &word, ",", EXTRACT_DONT_COALESCE_SEPARATORS);
484 if (r < 0)
485 return r;
486 if (r == 0)
487 return flags;
488
489 if (streq(word, "masked"))
490 flags |= SHOW_MASKED;
491 else if (streq(word, "equivalent"))
492 flags |= SHOW_EQUIVALENT;
493 else if (streq(word, "redirected"))
494 flags |= SHOW_REDIRECTED;
495 else if (streq(word, "overridden"))
496 flags |= SHOW_OVERRIDDEN;
497 else if (streq(word, "unchanged"))
498 flags |= SHOW_UNCHANGED;
499 else if (streq(word, "extended"))
500 flags |= SHOW_EXTENDED;
501 else if (streq(word, "default"))
502 flags |= SHOW_DEFAULTS;
503 else
504 return -EINVAL;
505 }
506 }
507
508 static int parse_argv(int argc, char *argv[]) {
509
510 enum {
511 ARG_NO_PAGER = 0x100,
512 ARG_DIFF,
513 ARG_VERSION
514 };
515
516 static const struct option options[] = {
517 { "help", no_argument, NULL, 'h' },
518 { "version", no_argument, NULL, ARG_VERSION },
519 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
520 { "diff", optional_argument, NULL, ARG_DIFF },
521 { "type", required_argument, NULL, 't' },
522 {}
523 };
524
525 int c, r;
526
527 assert(argc >= 1);
528 assert(argv);
529
530 while ((c = getopt_long(argc, argv, "ht:", options, NULL)) >= 0)
531
532 switch (c) {
533
534 case 'h':
535 return help();
536
537 case ARG_VERSION:
538 return version();
539
540 case ARG_NO_PAGER:
541 arg_pager_flags |= PAGER_DISABLE;
542 break;
543
544 case 't': {
545 int f;
546 f = parse_flags(optarg, arg_flags);
547 if (f < 0)
548 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
549 "Failed to parse flags field.");
550 arg_flags = f;
551 break;
552 }
553
554 case ARG_DIFF:
555 r = parse_boolean_argument("--diff", optarg, NULL);
556 if (r < 0)
557 return r;
558 arg_diff = r;
559 break;
560
561 case '?':
562 return -EINVAL;
563
564 default:
565 assert_not_reached();
566 }
567
568 return 1;
569 }
570
571 static int run(int argc, char *argv[]) {
572 int r, k, n_found = 0;
573
574 log_setup();
575
576 r = parse_argv(argc, argv);
577 if (r <= 0)
578 return r;
579
580 if (arg_flags == 0)
581 arg_flags = SHOW_DEFAULTS;
582
583 if (arg_diff < 0)
584 arg_diff = !!(arg_flags & SHOW_OVERRIDDEN);
585 else if (arg_diff)
586 arg_flags |= SHOW_OVERRIDDEN;
587
588 pager_open(arg_pager_flags);
589
590 if (optind < argc) {
591 for (int i = optind; i < argc; i++) {
592 path_simplify(argv[i]);
593
594 k = process_suffix_chop(argv[i]);
595 if (k < 0)
596 r = k;
597 else
598 n_found += k;
599 }
600
601 } else {
602 k = process_suffixes(NULL);
603 if (k < 0)
604 r = k;
605 else
606 n_found += k;
607 }
608
609 if (r >= 0)
610 printf("%s%i overridden configuration files found.\n", n_found ? "\n" : "", n_found);
611 return r;
612 }
613
614 DEFINE_MAIN_FUNCTION(run);