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