chiark / gitweb /
99305716ed7a30998a255238ce44a23ea4e4e4ae
[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;
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(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 }