]> git.ipfire.org Git - thirdparty/systemd.git/blame_incremental - src/delta/delta.c
install: create_symlink() check unlink() return value
[thirdparty/systemd.git] / src / delta / delta.c
... / ...
CommitLineData
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
7 Copyright 2013 Zbigniew Jędrzejewski-Szmek
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"
35#include "strv.h"
36
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
65static bool arg_no_pager = false;
66static int arg_diff = -1;
67
68static enum {
69 SHOW_MASKED = 1 << 0,
70 SHOW_EQUIVALENT = 1 << 1,
71 SHOW_REDIRECTED = 1 << 2,
72 SHOW_OVERRIDDEN = 1 << 3,
73 SHOW_UNCHANGED = 1 << 4,
74 SHOW_EXTENDED = 1 << 5,
75
76 SHOW_DEFAULTS =
77 (SHOW_MASKED | SHOW_EQUIVALENT | SHOW_REDIRECTED | SHOW_OVERRIDDEN | SHOW_EXTENDED)
78} arg_flags = 0;
79
80static void pager_open_if_enabled(void) {
81
82 if (arg_no_pager)
83 return;
84
85 pager_open(false);
86}
87
88static int equivalent(const char *a, const char *b) {
89 _cleanup_free_ char *x = NULL, *y = NULL;
90
91 x = canonicalize_file_name(a);
92 if (!x)
93 return -errno;
94
95 y = canonicalize_file_name(b);
96 if (!y)
97 return -errno;
98
99 return path_equal(x, y);
100}
101
102static int notify_override_masked(const char *top, const char *bottom) {
103 if (!(arg_flags & SHOW_MASKED))
104 return 0;
105
106 printf("%s%s%s %s → %s\n",
107 ansi_highlight_red(), "[MASKED]", ansi_highlight_off(), top, bottom);
108 return 1;
109}
110
111static int notify_override_equivalent(const char *top, const char *bottom) {
112 if (!(arg_flags & SHOW_EQUIVALENT))
113 return 0;
114
115 printf("%s%s%s %s → %s\n",
116 ansi_highlight_green(), "[EQUIVALENT]", ansi_highlight_off(), top, bottom);
117 return 1;
118}
119
120static int notify_override_redirected(const char *top, const char *bottom) {
121 if (!(arg_flags & SHOW_REDIRECTED))
122 return 0;
123
124 printf("%s%s%s %s → %s\n",
125 ansi_highlight(), "[REDIRECTED]", ansi_highlight_off(), top, bottom);
126 return 1;
127}
128
129static int notify_override_overridden(const char *top, const char *bottom) {
130 if (!(arg_flags & SHOW_OVERRIDDEN))
131 return 0;
132
133 printf("%s%s%s %s → %s\n",
134 ansi_highlight(), "[OVERRIDDEN]", ansi_highlight_off(), top, bottom);
135 return 1;
136}
137
138static int notify_override_extended(const char *top, const char *bottom) {
139 if (!(arg_flags & SHOW_EXTENDED))
140 return 0;
141
142 printf("%s%s%s %s → %s\n",
143 ansi_highlight(), "[EXTENDED]", ansi_highlight_off(), top, bottom);
144 return 1;
145}
146
147static int notify_override_unchanged(const char *f) {
148 if (!(arg_flags & SHOW_UNCHANGED))
149 return 0;
150
151 printf("[UNCHANGED] %s\n", f);
152 return 1;
153}
154
155static int found_override(const char *top, const char *bottom) {
156 _cleanup_free_ char *dest = NULL;
157 int k;
158 pid_t pid;
159
160 assert(top);
161 assert(bottom);
162
163 if (null_or_empty_path(top) > 0)
164 return notify_override_masked(top, bottom);
165
166 k = readlink_malloc(top, &dest);
167 if (k >= 0) {
168 if (equivalent(dest, bottom) > 0)
169 return notify_override_equivalent(top, bottom);
170 else
171 return notify_override_redirected(top, bottom);
172 }
173
174 k = notify_override_overridden(top, bottom);
175 if (!arg_diff)
176 return k;
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
196 return k;
197}
198
199static int enumerate_dir_d(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *toppath, const char *drop) {
200 _cleanup_free_ char *unit = NULL;
201 _cleanup_free_ char *path = NULL;
202 _cleanup_strv_free_ char **list = NULL;
203 char **file;
204 char *c;
205 int r;
206
207 assert(!endswith(drop, "/"));
208
209 path = strjoin(toppath, "/", drop, NULL);
210 if (!path)
211 return -ENOMEM;
212
213 log_debug("Looking at %s", path);
214
215 unit = strdup(drop);
216 if (!unit)
217 return -ENOMEM;
218
219 c = strrchr(unit, '.');
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;
242 d = p + strlen(toppath) + 1;
243
244 log_debug("Adding at top: %s → %s", d, p);
245 k = hashmap_put(top, d, p);
246 if (k >= 0) {
247 p = strdup(p);
248 if (!p)
249 return -ENOMEM;
250 d = p + strlen(toppath) + 1;
251 } else if (k != -EEXIST) {
252 free(p);
253 return k;
254 }
255
256 log_debug("Adding at bottom: %s → %s", d, p);
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
264 h = hashmap_get(drops, unit);
265 if (!h) {
266 h = hashmap_new(string_hash_func, string_compare_func);
267 if (!h)
268 return -ENOMEM;
269 hashmap_put(drops, unit, h);
270 unit = strdup(unit);
271 if (!unit)
272 return -ENOMEM;
273 }
274
275 p = strdup(p);
276 if (!p)
277 return -ENOMEM;
278
279 log_debug("Adding to drops: %s → %s → %s", unit, basename(p), p);
280 k = hashmap_put(h, basename(p), p);
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) {
291 _cleanup_closedir_ DIR *d;
292
293 assert(top);
294 assert(bottom);
295 assert(drops);
296 assert(path);
297
298 log_debug("Looking at %s", path);
299
300 d = opendir(path);
301 if (!d) {
302 if (errno == ENOENT)
303 return 0;
304
305 log_error("Failed to open %s: %m", path);
306 return -errno;
307 }
308
309 for (;;) {
310 struct dirent *de;
311 int k;
312 char *p;
313
314 errno = 0;
315 de = readdir(d);
316 if (!de)
317 return -errno;
318
319 dirent_ensure_type(d, de);
320
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
324 if (!dirent_is_file(de))
325 continue;
326
327 p = strjoin(path, "/", de->d_name, NULL);
328 if (!p)
329 return -ENOMEM;
330
331 log_debug("Adding at top: %s → %s", basename(p), p);
332 k = hashmap_put(top, basename(p), p);
333 if (k >= 0) {
334 p = strdup(p);
335 if (!p)
336 return -ENOMEM;
337 } else if (k != -EEXIST) {
338 free(p);
339 return k;
340 }
341
342 log_debug("Adding at bottom: %s → %s", basename(p), p);
343 free(hashmap_remove(bottom, basename(p)));
344 k = hashmap_put(bottom, basename(p), p);
345 if (k < 0) {
346 free(p);
347 return k;
348 }
349 }
350}
351
352static int process_suffix(const char *suffix, const char *onlyprefix) {
353 const char *p;
354 char *f;
355 Hashmap *top, *bottom, *drops;
356 Hashmap *h;
357 char *key;
358 int r = 0, k;
359 Iterator i, j;
360 int n_found = 0;
361 bool dropins;
362
363 assert(suffix);
364 assert(!startswith(suffix, "/"));
365 assert(!strstr(suffix, "//"));
366
367 dropins = nulstr_contains(have_dropins, suffix);
368
369 top = hashmap_new(string_hash_func, string_compare_func);
370 bottom = hashmap_new(string_hash_func, string_compare_func);
371 drops = hashmap_new(string_hash_func, string_compare_func);
372 if (!top || !bottom || !drops) {
373 r = -ENOMEM;
374 goto finish;
375 }
376
377 NULSTR_FOREACH(p, prefixes) {
378 _cleanup_free_ char *t = NULL;
379
380 t = strjoin(p, "/", suffix, NULL);
381 if (!t) {
382 r = -ENOMEM;
383 goto finish;
384 }
385
386 k = enumerate_dir(top, bottom, drops, t, dropins);
387 if (r == 0)
388 r = k;
389 }
390
391 HASHMAP_FOREACH_KEY(f, key, top, i) {
392 char *o;
393
394 o = hashmap_get(bottom, key);
395 assert(o);
396
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 }
407 }
408
409 h = hashmap_get(drops, key);
410 if (h)
411 HASHMAP_FOREACH(o, h, j)
412 if (!onlyprefix || startswith(o, onlyprefix))
413 n_found += notify_override_extended(f, o);
414 }
415
416finish:
417 if (top)
418 hashmap_free_free(top);
419 if (bottom)
420 hashmap_free_free(bottom);
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 }
429 return r < 0 ? r : n_found;
430}
431
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) {
447 const char *p;
448
449 assert(arg);
450
451 if (!path_is_absolute(arg))
452 return process_suffix(arg, NULL);
453
454 /* Strip prefix from the suffix */
455 NULSTR_FOREACH(p, prefixes) {
456 const char *suffix = startswith(arg, p);
457 if (suffix) {
458 suffix += strspn(suffix, "/");
459 if (*suffix)
460 return process_suffix(suffix, NULL);
461 else
462 return process_suffixes(arg);
463 }
464 }
465
466 log_error("Invalid suffix specification %s.", arg);
467 return -EINVAL;
468}
469
470static int help(void) {
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"
476 " --no-pager Do not pipe output into a pager\n"
477 " --diff[=1|0] Show a diff when overridden files differ\n"
478 " -t --type=LIST... Only display a selected set of override types\n",
479 program_invocation_short_name);
480
481 return 0;
482}
483
484static int parse_flags(const char *flag_str, int flags) {
485 char *w, *state;
486 size_t l;
487
488 FOREACH_WORD(w, l, flag_str, state) {
489 if (strneq("masked", w, l))
490 flags |= SHOW_MASKED;
491 else if (strneq ("equivalent", w, l))
492 flags |= SHOW_EQUIVALENT;
493 else if (strneq("redirected", w, l))
494 flags |= SHOW_REDIRECTED;
495 else if (strneq("overridden", w, l))
496 flags |= SHOW_OVERRIDDEN;
497 else if (strneq("unchanged", w, l))
498 flags |= SHOW_UNCHANGED;
499 else if (strneq("extended", w, l))
500 flags |= SHOW_EXTENDED;
501 else if (strneq("default", w, l))
502 flags |= SHOW_DEFAULTS;
503 else
504 return -EINVAL;
505 }
506 return flags;
507}
508
509static int parse_argv(int argc, char *argv[]) {
510
511 enum {
512 ARG_NO_PAGER = 0x100,
513 ARG_DIFF,
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 },
521 { "diff", optional_argument, NULL, ARG_DIFF },
522 { "type", required_argument, NULL, 't' },
523 {}
524 };
525
526 int c;
527
528 assert(argc >= 1);
529 assert(argv);
530
531 while ((c = getopt_long(argc, argv, "ht:", options, NULL)) >= 0) {
532
533 switch (c) {
534
535 case 'h':
536 help();
537 return 0;
538
539 case ARG_VERSION:
540 puts(PACKAGE_STRING);
541 puts(SYSTEMD_FEATURES);
542 return 0;
543
544 case ARG_NO_PAGER:
545 arg_no_pager = true;
546 break;
547
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.");
553 return -EINVAL;
554 }
555 arg_flags = f;
556 break;
557 }
558
559 case ARG_DIFF:
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;
571 else
572 arg_diff = 0;
573 }
574 break;
575
576 case '?':
577 return -EINVAL;
578
579 default:
580 assert_not_reached("Unhandled option");
581 }
582 }
583
584 return 1;
585}
586
587int main(int argc, char *argv[]) {
588 int r = 0, k;
589 int n_found = 0;
590
591 log_parse_environment();
592 log_open();
593
594 r = parse_argv(argc, argv);
595 if (r <= 0)
596 goto finish;
597
598 if (arg_flags == 0)
599 arg_flags = SHOW_DEFAULTS;
600
601 if (arg_diff < 0)
602 arg_diff = !!(arg_flags & SHOW_OVERRIDDEN);
603 else if (arg_diff)
604 arg_flags |= SHOW_OVERRIDDEN;
605
606 pager_open_if_enabled();
607
608 if (optind < argc) {
609 int i;
610
611 for (i = optind; i < argc; i++) {
612 path_kill_slashes(argv[i]);
613 k = process_suffix_chop(argv[i]);
614 if (k < 0)
615 r = k;
616 else
617 n_found += k;
618 }
619
620 } else {
621 k = process_suffixes(NULL);
622 if (k < 0)
623 r = k;
624 else
625 n_found += k;
626 }
627
628 if (r >= 0)
629 printf("%s%i overridden configuration files found.\n",
630 n_found ? "\n" : "", n_found);
631
632finish:
633 pager_close();
634
635 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
636}