]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/delta/delta.c
shared: add process-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 <string.h>
25 #include <unistd.h>
26 #include <getopt.h>
27
28 #include "hashmap.h"
29 #include "util.h"
30 #include "path-util.h"
31 #include "log.h"
32 #include "pager.h"
33 #include "build.h"
34 #include "strv.h"
35 #include "process-util.h"
36
37 static 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
49 static 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
61 static const char have_dropins[] =
62 "systemd/system\0"
63 "systemd/user\0";
64
65 static bool arg_no_pager = false;
66 static int arg_diff = -1;
67
68 static 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
80 static void pager_open_if_enabled(void) {
81
82 if (arg_no_pager)
83 return;
84
85 pager_open(false);
86 }
87
88 static 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
102 static 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 %s\n",
107 ansi_highlight_red(), "[MASKED]", ansi_highlight_off(),
108 top, draw_special_char(DRAW_ARROW), bottom);
109 return 1;
110 }
111
112 static int notify_override_equivalent(const char *top, const char *bottom) {
113 if (!(arg_flags & SHOW_EQUIVALENT))
114 return 0;
115
116 printf("%s%s%s %s %s %s\n",
117 ansi_highlight_green(), "[EQUIVALENT]", ansi_highlight_off(),
118 top, draw_special_char(DRAW_ARROW), bottom);
119 return 1;
120 }
121
122 static int notify_override_redirected(const char *top, const char *bottom) {
123 if (!(arg_flags & SHOW_REDIRECTED))
124 return 0;
125
126 printf("%s%s%s %s %s %s\n",
127 ansi_highlight(), "[REDIRECTED]", ansi_highlight_off(),
128 top, draw_special_char(DRAW_ARROW), bottom);
129 return 1;
130 }
131
132 static int notify_override_overridden(const char *top, const char *bottom) {
133 if (!(arg_flags & SHOW_OVERRIDDEN))
134 return 0;
135
136 printf("%s%s%s %s %s %s\n",
137 ansi_highlight(), "[OVERRIDDEN]", ansi_highlight_off(),
138 top, draw_special_char(DRAW_ARROW), bottom);
139 return 1;
140 }
141
142 static int notify_override_extended(const char *top, const char *bottom) {
143 if (!(arg_flags & SHOW_EXTENDED))
144 return 0;
145
146 printf("%s%s%s %s %s %s\n",
147 ansi_highlight(), "[EXTENDED]", ansi_highlight_off(),
148 top, draw_special_char(DRAW_ARROW), bottom);
149 return 1;
150 }
151
152 static int notify_override_unchanged(const char *f) {
153 if (!(arg_flags & SHOW_UNCHANGED))
154 return 0;
155
156 printf("[UNCHANGED] %s\n", f);
157 return 1;
158 }
159
160 static int found_override(const char *top, const char *bottom) {
161 _cleanup_free_ char *dest = NULL;
162 int k;
163 pid_t pid;
164
165 assert(top);
166 assert(bottom);
167
168 if (null_or_empty_path(top) > 0)
169 return notify_override_masked(top, bottom);
170
171 k = readlink_malloc(top, &dest);
172 if (k >= 0) {
173 if (equivalent(dest, bottom) > 0)
174 return notify_override_equivalent(top, bottom);
175 else
176 return notify_override_redirected(top, bottom);
177 }
178
179 k = notify_override_overridden(top, bottom);
180 if (!arg_diff)
181 return k;
182
183 putchar('\n');
184
185 fflush(stdout);
186
187 pid = fork();
188 if (pid < 0)
189 return log_error_errno(errno, "Failed to fork off diff: %m");
190 else if (pid == 0) {
191 execlp("diff", "diff", "-us", "--", bottom, top, NULL);
192 log_error_errno(errno, "Failed to execute diff: %m");
193 _exit(1);
194 }
195
196 wait_for_terminate_and_warn("diff", pid, false);
197 putchar('\n');
198
199 return k;
200 }
201
202 static int enumerate_dir_d(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *toppath, const char *drop) {
203 _cleanup_free_ char *unit = NULL;
204 _cleanup_free_ char *path = NULL;
205 _cleanup_strv_free_ char **list = NULL;
206 char **file;
207 char *c;
208 int r;
209
210 assert(!endswith(drop, "/"));
211
212 path = strjoin(toppath, "/", drop, NULL);
213 if (!path)
214 return -ENOMEM;
215
216 log_debug("Looking at %s", path);
217
218 unit = strdup(drop);
219 if (!unit)
220 return -ENOMEM;
221
222 c = strrchr(unit, '.');
223 if (!c)
224 return -EINVAL;
225 *c = 0;
226
227 r = get_files_in_directory(path, &list);
228 if (r < 0)
229 return log_error_errno(r, "Failed to enumerate %s: %m", path);
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;
243 d = p + strlen(toppath) + 1;
244
245 log_debug("Adding at top: %s %s %s", d, draw_special_char(DRAW_ARROW), p);
246 k = hashmap_put(top, d, p);
247 if (k >= 0) {
248 p = strdup(p);
249 if (!p)
250 return -ENOMEM;
251 d = p + strlen(toppath) + 1;
252 } else if (k != -EEXIST) {
253 free(p);
254 return k;
255 }
256
257 log_debug("Adding at bottom: %s %s %s", d, draw_special_char(DRAW_ARROW), p);
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
265 h = hashmap_get(drops, unit);
266 if (!h) {
267 h = hashmap_new(&string_hash_ops);
268 if (!h)
269 return -ENOMEM;
270 hashmap_put(drops, unit, h);
271 unit = strdup(unit);
272 if (!unit)
273 return -ENOMEM;
274 }
275
276 p = strdup(p);
277 if (!p)
278 return -ENOMEM;
279
280 log_debug("Adding to drops: %s %s %s %s %s",
281 unit, draw_special_char(DRAW_ARROW), basename(p), draw_special_char(DRAW_ARROW), p);
282 k = hashmap_put(h, basename(p), p);
283 if (k < 0) {
284 free(p);
285 if (k != -EEXIST)
286 return k;
287 }
288 }
289 return 0;
290 }
291
292 static int enumerate_dir(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *path, bool dropins) {
293 _cleanup_closedir_ DIR *d;
294
295 assert(top);
296 assert(bottom);
297 assert(drops);
298 assert(path);
299
300 log_debug("Looking at %s", path);
301
302 d = opendir(path);
303 if (!d) {
304 if (errno == ENOENT)
305 return 0;
306
307 log_error_errno(errno, "Failed to open %s: %m", path);
308 return -errno;
309 }
310
311 for (;;) {
312 struct dirent *de;
313 int k;
314 char *p;
315
316 errno = 0;
317 de = readdir(d);
318 if (!de)
319 return -errno;
320
321 dirent_ensure_type(d, de);
322
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
326 if (!dirent_is_file(de))
327 continue;
328
329 p = strjoin(path, "/", de->d_name, NULL);
330 if (!p)
331 return -ENOMEM;
332
333 log_debug("Adding at top: %s %s %s", basename(p), draw_special_char(DRAW_ARROW), p);
334 k = hashmap_put(top, basename(p), p);
335 if (k >= 0) {
336 p = strdup(p);
337 if (!p)
338 return -ENOMEM;
339 } else if (k != -EEXIST) {
340 free(p);
341 return k;
342 }
343
344 log_debug("Adding at bottom: %s %s %s", basename(p), draw_special_char(DRAW_ARROW), p);
345 free(hashmap_remove(bottom, basename(p)));
346 k = hashmap_put(bottom, basename(p), p);
347 if (k < 0) {
348 free(p);
349 return k;
350 }
351 }
352 }
353
354 static int process_suffix(const char *suffix, const char *onlyprefix) {
355 const char *p;
356 char *f;
357 Hashmap *top, *bottom, *drops;
358 Hashmap *h;
359 char *key;
360 int r = 0, k;
361 Iterator i, j;
362 int n_found = 0;
363 bool dropins;
364
365 assert(suffix);
366 assert(!startswith(suffix, "/"));
367 assert(!strstr(suffix, "//"));
368
369 dropins = nulstr_contains(have_dropins, suffix);
370
371 top = hashmap_new(&string_hash_ops);
372 bottom = hashmap_new(&string_hash_ops);
373 drops = hashmap_new(&string_hash_ops);
374 if (!top || !bottom || !drops) {
375 r = -ENOMEM;
376 goto finish;
377 }
378
379 NULSTR_FOREACH(p, prefixes) {
380 _cleanup_free_ char *t = NULL;
381
382 t = strjoin(p, "/", suffix, NULL);
383 if (!t) {
384 r = -ENOMEM;
385 goto finish;
386 }
387
388 k = enumerate_dir(top, bottom, drops, t, dropins);
389 if (r == 0)
390 r = k;
391 }
392
393 HASHMAP_FOREACH_KEY(f, key, top, i) {
394 char *o;
395
396 o = hashmap_get(bottom, key);
397 assert(o);
398
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 }
409 }
410
411 h = hashmap_get(drops, key);
412 if (h)
413 HASHMAP_FOREACH(o, h, j)
414 if (!onlyprefix || startswith(o, onlyprefix))
415 n_found += notify_override_extended(f, o);
416 }
417
418 finish:
419 if (top)
420 hashmap_free_free(top);
421 if (bottom)
422 hashmap_free_free(bottom);
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 }
431 return r < 0 ? r : n_found;
432 }
433
434 static 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
448 static int process_suffix_chop(const char *arg) {
449 const char *p;
450
451 assert(arg);
452
453 if (!path_is_absolute(arg))
454 return process_suffix(arg, NULL);
455
456 /* Strip prefix from the suffix */
457 NULSTR_FOREACH(p, prefixes) {
458 const char *suffix = startswith(arg, p);
459 if (suffix) {
460 suffix += strspn(suffix, "/");
461 if (*suffix)
462 return process_suffix(suffix, NULL);
463 else
464 return process_suffixes(arg);
465 }
466 }
467
468 log_error("Invalid suffix specification %s.", arg);
469 return -EINVAL;
470 }
471
472 static void help(void) {
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"
477 " --no-pager Do not pipe output into a pager\n"
478 " --diff[=1|0] Show a diff when overridden files differ\n"
479 " -t --type=LIST... Only display a selected set of override types\n"
480 , program_invocation_short_name);
481 }
482
483 static int parse_flags(const char *flag_str, int flags) {
484 const char *word, *state;
485 size_t l;
486
487 FOREACH_WORD_SEPARATOR(word, l, flag_str, ",", state) {
488 if (strneq("masked", word, l))
489 flags |= SHOW_MASKED;
490 else if (strneq ("equivalent", word, l))
491 flags |= SHOW_EQUIVALENT;
492 else if (strneq("redirected", word, l))
493 flags |= SHOW_REDIRECTED;
494 else if (strneq("overridden", word, l))
495 flags |= SHOW_OVERRIDDEN;
496 else if (strneq("unchanged", word, l))
497 flags |= SHOW_UNCHANGED;
498 else if (strneq("extended", word, l))
499 flags |= SHOW_EXTENDED;
500 else if (strneq("default", word, l))
501 flags |= SHOW_DEFAULTS;
502 else
503 return -EINVAL;
504 }
505 return flags;
506 }
507
508 static int parse_argv(int argc, char *argv[]) {
509
510 enum {
511 ARG_NO_PAGER = 0x100,
512 ARG_DIFF,
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 },
520 { "diff", optional_argument, NULL, ARG_DIFF },
521 { "type", required_argument, NULL, 't' },
522 {}
523 };
524
525 int c;
526
527 assert(argc >= 1);
528 assert(argv);
529
530 while ((c = getopt_long(argc, argv, "ht:", options, NULL)) >= 0)
531
532 switch (c) {
533
534 case 'h':
535 help();
536 return 0;
537
538 case ARG_VERSION:
539 puts(PACKAGE_STRING);
540 puts(SYSTEMD_FEATURES);
541 return 0;
542
543 case ARG_NO_PAGER:
544 arg_no_pager = true;
545 break;
546
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.");
552 return -EINVAL;
553 }
554 arg_flags = f;
555 break;
556 }
557
558 case ARG_DIFF:
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;
570 else
571 arg_diff = 0;
572 }
573 break;
574
575 case '?':
576 return -EINVAL;
577
578 default:
579 assert_not_reached("Unhandled option");
580 }
581
582 return 1;
583 }
584
585 int main(int argc, char *argv[]) {
586 int r = 0, k;
587 int n_found = 0;
588
589 log_parse_environment();
590 log_open();
591
592 r = parse_argv(argc, argv);
593 if (r <= 0)
594 goto finish;
595
596 if (arg_flags == 0)
597 arg_flags = SHOW_DEFAULTS;
598
599 if (arg_diff < 0)
600 arg_diff = !!(arg_flags & SHOW_OVERRIDDEN);
601 else if (arg_diff)
602 arg_flags |= SHOW_OVERRIDDEN;
603
604 pager_open_if_enabled();
605
606 if (optind < argc) {
607 int i;
608
609 for (i = optind; i < argc; i++) {
610 path_kill_slashes(argv[i]);
611 k = process_suffix_chop(argv[i]);
612 if (k < 0)
613 r = k;
614 else
615 n_found += k;
616 }
617
618 } else {
619 k = process_suffixes(NULL);
620 if (k < 0)
621 r = k;
622 else
623 n_found += k;
624 }
625
626 if (r >= 0)
627 printf("%s%i overridden configuration files found.\n",
628 n_found ? "\n" : "", n_found);
629
630 finish:
631 pager_close();
632
633 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
634 }