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