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