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