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