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