chiark / gitweb /
2fdbeeae81e38c66b751a533301a6256e5d8a792
[elogind.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 <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
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, r;
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                 log_error("Failed to fork off diff: %m");
190                 return -errno;
191         } else if (pid == 0) {
192                 execlp("diff", "diff", "-us", "--", bottom, top, NULL);
193                 log_error("Failed to execute diff: %m");
194                 _exit(1);
195         }
196
197         r = wait_for_terminate(pid, NULL);
198         if (r < 0)
199                 log_warning("Failed to wait for diff: %s", strerror(-r));
200
201         putchar('\n');
202
203         return k;
204 }
205
206 static int enumerate_dir_d(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *toppath, const char *drop) {
207         _cleanup_free_ char *unit = NULL;
208         _cleanup_free_ char *path = NULL;
209         _cleanup_strv_free_ char **list = NULL;
210         char **file;
211         char *c;
212         int r;
213
214         assert(!endswith(drop, "/"));
215
216         path = strjoin(toppath, "/", drop, NULL);
217         if (!path)
218                 return -ENOMEM;
219
220         log_debug("Looking at %s", path);
221
222         unit = strdup(drop);
223         if (!unit)
224                 return -ENOMEM;
225
226         c = strrchr(unit, '.');
227         if (!c)
228                 return -EINVAL;
229         *c = 0;
230
231         r = get_files_in_directory(path, &list);
232         if (r < 0){
233                 log_error("Failed to enumerate %s: %s", path, strerror(-r));
234                 return r;
235         }
236
237         STRV_FOREACH(file, list) {
238                 Hashmap *h;
239                 int k;
240                 char *p;
241                 char *d;
242
243                 if (!endswith(*file, ".conf"))
244                         continue;
245
246                 p = strjoin(path, "/", *file, NULL);
247                 if (!p)
248                         return -ENOMEM;
249                 d = p + strlen(toppath) + 1;
250
251                 log_debug("Adding at top: %s %s %s", d, draw_special_char(DRAW_ARROW), p);
252                 k = hashmap_put(top, d, p);
253                 if (k >= 0) {
254                         p = strdup(p);
255                         if (!p)
256                                 return -ENOMEM;
257                         d = p + strlen(toppath) + 1;
258                 } else if (k != -EEXIST) {
259                         free(p);
260                         return k;
261                 }
262
263                 log_debug("Adding at bottom: %s %s %s", d, draw_special_char(DRAW_ARROW), p);
264                 free(hashmap_remove(bottom, d));
265                 k = hashmap_put(bottom, d, p);
266                 if (k < 0) {
267                         free(p);
268                         return k;
269                 }
270
271                 h = hashmap_get(drops, unit);
272                 if (!h) {
273                         h = hashmap_new(&string_hash_ops);
274                         if (!h)
275                                 return -ENOMEM;
276                         hashmap_put(drops, unit, h);
277                         unit = strdup(unit);
278                         if (!unit)
279                                 return -ENOMEM;
280                 }
281
282                 p = strdup(p);
283                 if (!p)
284                         return -ENOMEM;
285
286                 log_debug("Adding to drops: %s %s %s %s %s",
287                           unit, draw_special_char(DRAW_ARROW), basename(p), draw_special_char(DRAW_ARROW), p);
288                 k = hashmap_put(h, basename(p), p);
289                 if (k < 0) {
290                         free(p);
291                         if (k != -EEXIST)
292                                 return k;
293                 }
294         }
295         return 0;
296 }
297
298 static int enumerate_dir(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *path, bool dropins) {
299         _cleanup_closedir_ DIR *d;
300
301         assert(top);
302         assert(bottom);
303         assert(drops);
304         assert(path);
305
306         log_debug("Looking at %s", path);
307
308         d = opendir(path);
309         if (!d) {
310                 if (errno == ENOENT)
311                         return 0;
312
313                 log_error("Failed to open %s: %m", path);
314                 return -errno;
315         }
316
317         for (;;) {
318                 struct dirent *de;
319                 int k;
320                 char *p;
321
322                 errno = 0;
323                 de = readdir(d);
324                 if (!de)
325                         return -errno;
326
327                 dirent_ensure_type(d, de);
328
329                 if (dropins && de->d_type == DT_DIR && endswith(de->d_name, ".d"))
330                         enumerate_dir_d(top, bottom, drops, path, de->d_name);
331
332                 if (!dirent_is_file(de))
333                         continue;
334
335                 p = strjoin(path, "/", de->d_name, NULL);
336                 if (!p)
337                         return -ENOMEM;
338
339                 log_debug("Adding at top: %s %s %s", basename(p), draw_special_char(DRAW_ARROW), p);
340                 k = hashmap_put(top, basename(p), p);
341                 if (k >= 0) {
342                         p = strdup(p);
343                         if (!p)
344                                 return -ENOMEM;
345                 } else if (k != -EEXIST) {
346                         free(p);
347                         return k;
348                 }
349
350                 log_debug("Adding at bottom: %s %s %s", basename(p), draw_special_char(DRAW_ARROW), p);
351                 free(hashmap_remove(bottom, basename(p)));
352                 k = hashmap_put(bottom, basename(p), p);
353                 if (k < 0) {
354                         free(p);
355                         return k;
356                 }
357         }
358 }
359
360 static int process_suffix(const char *suffix, const char *onlyprefix) {
361         const char *p;
362         char *f;
363         Hashmap *top, *bottom, *drops;
364         Hashmap *h;
365         char *key;
366         int r = 0, k;
367         Iterator i, j;
368         int n_found = 0;
369         bool dropins;
370
371         assert(suffix);
372         assert(!startswith(suffix, "/"));
373         assert(!strstr(suffix, "//"));
374
375         dropins = nulstr_contains(have_dropins, suffix);
376
377         top = hashmap_new(&string_hash_ops);
378         bottom = hashmap_new(&string_hash_ops);
379         drops = hashmap_new(&string_hash_ops);
380         if (!top || !bottom || !drops) {
381                 r = -ENOMEM;
382                 goto finish;
383         }
384
385         NULSTR_FOREACH(p, prefixes) {
386                 _cleanup_free_ char *t = NULL;
387
388                 t = strjoin(p, "/", suffix, NULL);
389                 if (!t) {
390                         r = -ENOMEM;
391                         goto finish;
392                 }
393
394                 k = enumerate_dir(top, bottom, drops, t, dropins);
395                 if (r == 0)
396                         r = k;
397         }
398
399         HASHMAP_FOREACH_KEY(f, key, top, i) {
400                 char *o;
401
402                 o = hashmap_get(bottom, key);
403                 assert(o);
404
405                 if (!onlyprefix || startswith(o, onlyprefix)) {
406                         if (path_equal(o, f)) {
407                                 notify_override_unchanged(f);
408                         } else {
409                                 k = found_override(f, o);
410                                 if (k < 0)
411                                         r = k;
412                                 else
413                                         n_found += k;
414                         }
415                 }
416
417                 h = hashmap_get(drops, key);
418                 if (h)
419                         HASHMAP_FOREACH(o, h, j)
420                                 if (!onlyprefix || startswith(o, onlyprefix))
421                                         n_found += notify_override_extended(f, o);
422         }
423
424 finish:
425         if (top)
426                 hashmap_free_free(top);
427         if (bottom)
428                 hashmap_free_free(bottom);
429         if (drops) {
430                 HASHMAP_FOREACH_KEY(h, key, drops, i){
431                         hashmap_free_free(hashmap_remove(drops, key));
432                         hashmap_remove(drops, key);
433                         free(key);
434                 }
435                 hashmap_free(drops);
436         }
437         return r < 0 ? r : n_found;
438 }
439
440 static int process_suffixes(const char *onlyprefix) {
441         const char *n;
442         int n_found = 0, r;
443
444         NULSTR_FOREACH(n, suffixes) {
445                 r = process_suffix(n, onlyprefix);
446                 if (r < 0)
447                         return r;
448                 else
449                         n_found += r;
450         }
451         return n_found;
452 }
453
454 static int process_suffix_chop(const char *arg) {
455         const char *p;
456
457         assert(arg);
458
459         if (!path_is_absolute(arg))
460                 return process_suffix(arg, NULL);
461
462         /* Strip prefix from the suffix */
463         NULSTR_FOREACH(p, prefixes) {
464                 const char *suffix = startswith(arg, p);
465                 if (suffix) {
466                         suffix += strspn(suffix, "/");
467                         if (*suffix)
468                                 return process_suffix(suffix, NULL);
469                         else
470                                 return process_suffixes(arg);
471                 }
472         }
473
474         log_error("Invalid suffix specification %s.", arg);
475         return -EINVAL;
476 }
477
478 static void help(void) {
479         printf("%s [OPTIONS...] [SUFFIX...]\n\n"
480                "Find overridden configuration files.\n\n"
481                "  -h --help           Show this help\n"
482                "     --version        Show package version\n"
483                "     --no-pager       Do not pipe output into a pager\n"
484                "     --diff[=1|0]     Show a diff when overridden files differ\n"
485                "  -t --type=LIST...   Only display a selected set of override types\n"
486                , program_invocation_short_name);
487 }
488
489 static int parse_flags(const char *flag_str, int flags) {
490         const char *word, *state;
491         size_t l;
492
493         FOREACH_WORD(word, l, flag_str, state) {
494                 if (strneq("masked", word, l))
495                         flags |= SHOW_MASKED;
496                 else if (strneq ("equivalent", word, l))
497                         flags |= SHOW_EQUIVALENT;
498                 else if (strneq("redirected", word, l))
499                         flags |= SHOW_REDIRECTED;
500                 else if (strneq("overridden", word, l))
501                         flags |= SHOW_OVERRIDDEN;
502                 else if (strneq("unchanged", word, l))
503                         flags |= SHOW_UNCHANGED;
504                 else if (strneq("extended", word, l))
505                         flags |= SHOW_EXTENDED;
506                 else if (strneq("default", word, l))
507                         flags |= SHOW_DEFAULTS;
508                 else
509                         return -EINVAL;
510         }
511         return flags;
512 }
513
514 static int parse_argv(int argc, char *argv[]) {
515
516         enum {
517                 ARG_NO_PAGER = 0x100,
518                 ARG_DIFF,
519                 ARG_VERSION
520         };
521
522         static const struct option options[] = {
523                 { "help",      no_argument,       NULL, 'h'          },
524                 { "version",   no_argument,       NULL, ARG_VERSION  },
525                 { "no-pager",  no_argument,       NULL, ARG_NO_PAGER },
526                 { "diff",      optional_argument, NULL, ARG_DIFF     },
527                 { "type",      required_argument, NULL, 't'          },
528                 {}
529         };
530
531         int c;
532
533         assert(argc >= 1);
534         assert(argv);
535
536         while ((c = getopt_long(argc, argv, "ht:", options, NULL)) >= 0)
537
538                 switch (c) {
539
540                 case 'h':
541                         help();
542                         return 0;
543
544                 case ARG_VERSION:
545                         puts(PACKAGE_STRING);
546                         puts(SYSTEMD_FEATURES);
547                         return 0;
548
549                 case ARG_NO_PAGER:
550                         arg_no_pager = true;
551                         break;
552
553                 case 't': {
554                         int f;
555                         f = parse_flags(optarg, arg_flags);
556                         if (f < 0) {
557                                 log_error("Failed to parse flags field.");
558                                 return -EINVAL;
559                         }
560                         arg_flags = f;
561                         break;
562                 }
563
564                 case ARG_DIFF:
565                         if (!optarg)
566                                 arg_diff = 1;
567                         else {
568                                 int b;
569
570                                 b = parse_boolean(optarg);
571                                 if (b < 0) {
572                                         log_error("Failed to parse diff boolean.");
573                                         return -EINVAL;
574                                 } else if (b)
575                                         arg_diff = 1;
576                                 else
577                                         arg_diff = 0;
578                         }
579                         break;
580
581                 case '?':
582                         return -EINVAL;
583
584                 default:
585                         assert_not_reached("Unhandled option");
586                 }
587
588         return 1;
589 }
590
591 int main(int argc, char *argv[]) {
592         int r = 0, k;
593         int n_found = 0;
594
595         log_parse_environment();
596         log_open();
597
598         r = parse_argv(argc, argv);
599         if (r <= 0)
600                 goto finish;
601
602         if (arg_flags == 0)
603                 arg_flags = SHOW_DEFAULTS;
604
605         if (arg_diff < 0)
606                 arg_diff = !!(arg_flags & SHOW_OVERRIDDEN);
607         else if (arg_diff)
608                 arg_flags |= SHOW_OVERRIDDEN;
609
610         pager_open_if_enabled();
611
612         if (optind < argc) {
613                 int i;
614
615                 for (i = optind; i < argc; i++) {
616                         path_kill_slashes(argv[i]);
617                         k = process_suffix_chop(argv[i]);
618                         if (k < 0)
619                                 r = k;
620                         else
621                                 n_found += k;
622                 }
623
624         } else {
625                 k = process_suffixes(NULL);
626                 if (k < 0)
627                         r = k;
628                 else
629                         n_found += k;
630         }
631
632         if (r >= 0)
633                 printf("%s%i overridden configuration files found.\n",
634                        n_found ? "\n" : "", n_found);
635
636 finish:
637         pager_close();
638
639         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
640 }