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