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