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