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