chiark / gitweb /
19eaed36316c6fe9a0dabf2a051ff8d674c01866
[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\n",
107                ansi_highlight_red(), "[MASKED]", ansi_highlight_off(), top, bottom);
108         return 1;
109 }
110
111 static int notify_override_equivalent(const char *top, const char *bottom) {
112         if (!(arg_flags & SHOW_EQUIVALENT))
113                 return 0;
114
115         printf("%s%s%s %s → %s\n",
116                ansi_highlight_green(), "[EQUIVALENT]", ansi_highlight_off(), top, bottom);
117         return 1;
118 }
119
120 static int notify_override_redirected(const char *top, const char *bottom) {
121         if (!(arg_flags & SHOW_REDIRECTED))
122                 return 0;
123
124         printf("%s%s%s   %s → %s\n",
125                ansi_highlight(), "[REDIRECTED]", ansi_highlight_off(), top, bottom);
126         return 1;
127 }
128
129 static int notify_override_overridden(const char *top, const char *bottom) {
130         if (!(arg_flags & SHOW_OVERRIDDEN))
131                 return 0;
132
133         printf("%s%s%s %s → %s\n",
134                ansi_highlight(), "[OVERRIDDEN]", ansi_highlight_off(), top, bottom);
135         return 1;
136 }
137
138 static int notify_override_extended(const char *top, const char *bottom) {
139         if (!(arg_flags & SHOW_EXTENDED))
140                return 0;
141
142         printf("%s%s%s   %s → %s\n",
143                ansi_highlight(), "[EXTENDED]", ansi_highlight_off(), top, bottom);
144         return 1;
145 }
146
147 static int notify_override_unchanged(const char *f) {
148         if (!(arg_flags & SHOW_UNCHANGED))
149                 return 0;
150
151         printf("[UNCHANGED]  %s\n", f);
152         return 1;
153 }
154
155 static int found_override(const char *top, const char *bottom) {
156         _cleanup_free_ char *dest = NULL;
157         int k;
158         pid_t pid;
159
160         assert(top);
161         assert(bottom);
162
163         if (null_or_empty_path(top) > 0)
164                 return notify_override_masked(top, bottom);
165
166         k = readlink_malloc(top, &dest);
167         if (k >= 0) {
168                 if (equivalent(dest, bottom) > 0)
169                         return notify_override_equivalent(top, bottom);
170                 else
171                         return notify_override_redirected(top, bottom);
172         }
173
174         k = notify_override_overridden(top, bottom);
175         if (!arg_diff)
176                 return k;
177
178         putchar('\n');
179
180         fflush(stdout);
181
182         pid = fork();
183         if (pid < 0) {
184                 log_error("Failed to fork off diff: %m");
185                 return -errno;
186         } else if (pid == 0) {
187                 execlp("diff", "diff", "-us", "--", bottom, top, NULL);
188                 log_error("Failed to execute diff: %m");
189                 _exit(1);
190         }
191
192         wait_for_terminate(pid, NULL);
193
194         putchar('\n');
195
196         return k;
197 }
198
199 static int enumerate_dir_d(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *toppath, const char *drop) {
200         _cleanup_free_ char *unit = NULL;
201         _cleanup_free_ char *path = NULL;
202         _cleanup_strv_free_ char **list = NULL;
203         char **file;
204         char *c;
205         int r;
206
207         assert(!endswith(drop, "/"));
208
209         path = strjoin(toppath, "/", drop, NULL);
210         if (!path)
211                 return -ENOMEM;
212
213         log_debug("Looking at %s", path);
214
215         unit = strdup(drop);
216         if (!unit)
217                 return -ENOMEM;
218
219         c = strrchr(unit, '.');
220         if (!c)
221                 return -EINVAL;
222         *c = 0;
223
224         r = get_files_in_directory(path, &list);
225         if (r < 0){
226                 log_error("Failed to enumerate %s: %s", path, strerror(-r));
227                 return r;
228         }
229
230         STRV_FOREACH(file, list) {
231                 Hashmap *h;
232                 int k;
233                 char *p;
234                 char *d;
235
236                 if (!endswith(*file, ".conf"))
237                         continue;
238
239                 p = strjoin(path, "/", *file, NULL);
240                 if (!p)
241                         return -ENOMEM;
242                 d = p + strlen(toppath) + 1;
243
244                 log_debug("Adding at top: %s → %s", d, p);
245                 k = hashmap_put(top, d, p);
246                 if (k >= 0) {
247                         p = strdup(p);
248                         if (!p)
249                                 return -ENOMEM;
250                         d = p + strlen(toppath) + 1;
251                 } else if (k != -EEXIST) {
252                         free(p);
253                         return k;
254                 }
255
256                 log_debug("Adding at bottom: %s → %s", d, p);
257                 free(hashmap_remove(bottom, d));
258                 k = hashmap_put(bottom, d, p);
259                 if (k < 0) {
260                         free(p);
261                         return k;
262                 }
263
264                 h = hashmap_get(drops, unit);
265                 if (!h) {
266                         h = hashmap_new(string_hash_func, string_compare_func);
267                         if (!h)
268                                 return -ENOMEM;
269                         hashmap_put(drops, unit, h);
270                         unit = strdup(unit);
271                         if (!unit)
272                                 return -ENOMEM;
273                 }
274
275                 p = strdup(p);
276                 if (!p)
277                         return -ENOMEM;
278
279                 log_debug("Adding to drops: %s → %s → %s", unit, basename(p), p);
280                 k = hashmap_put(h, basename(p), p);
281                 if (k < 0) {
282                         free(p);
283                         if (k != -EEXIST)
284                                 return k;
285                 }
286         }
287         return 0;
288 }
289
290 static int enumerate_dir(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *path, bool dropins) {
291         _cleanup_closedir_ DIR *d;
292
293         assert(top);
294         assert(bottom);
295         assert(drops);
296         assert(path);
297
298         log_debug("Looking at %s", path);
299
300         d = opendir(path);
301         if (!d) {
302                 if (errno == ENOENT)
303                         return 0;
304
305                 log_error("Failed to open %s: %m", path);
306                 return -errno;
307         }
308
309         for (;;) {
310                 struct dirent *de;
311                 int k;
312                 char *p;
313
314                 errno = 0;
315                 de = readdir(d);
316                 if (!de)
317                         return -errno;
318
319                 if (dropins && de->d_type == DT_DIR && endswith(de->d_name, ".d"))
320                         enumerate_dir_d(top, bottom, drops, path, de->d_name);
321
322                 if (!dirent_is_file(de))
323                         continue;
324
325                 p = strjoin(path, "/", de->d_name, NULL);
326                 if (!p)
327                         return -ENOMEM;
328
329                 log_debug("Adding at top: %s → %s", basename(p), p);
330                 k = hashmap_put(top, basename(p), p);
331                 if (k >= 0) {
332                         p = strdup(p);
333                         if (!p)
334                                 return -ENOMEM;
335                 } else if (k != -EEXIST) {
336                         free(p);
337                         return k;
338                 }
339
340                 log_debug("Adding at bottom: %s → %s", basename(p), p);
341                 free(hashmap_remove(bottom, basename(p)));
342                 k = hashmap_put(bottom, basename(p), p);
343                 if (k < 0) {
344                         free(p);
345                         return k;
346                 }
347         }
348 }
349
350 static int process_suffix(const char *suffix, const char *onlyprefix) {
351         const char *p;
352         char *f;
353         Hashmap *top, *bottom, *drops;
354         Hashmap *h;
355         char *key;
356         int r = 0, k;
357         Iterator i, j;
358         int n_found = 0;
359         bool dropins;
360
361         assert(suffix);
362         assert(!startswith(suffix, "/"));
363         assert(!strstr(suffix, "//"));
364
365         dropins = nulstr_contains(have_dropins, suffix);
366
367         top = hashmap_new(string_hash_func, string_compare_func);
368         bottom = hashmap_new(string_hash_func, string_compare_func);
369         drops = hashmap_new(string_hash_func, string_compare_func);
370         if (!top || !bottom || !drops) {
371                 r = -ENOMEM;
372                 goto finish;
373         }
374
375         NULSTR_FOREACH(p, prefixes) {
376                 _cleanup_free_ char *t = NULL;
377
378                 t = strjoin(p, "/", suffix, NULL);
379                 if (!t) {
380                         r = -ENOMEM;
381                         goto finish;
382                 }
383
384                 k = enumerate_dir(top, bottom, drops, t, dropins);
385                 if (r == 0)
386                         r = k;
387         }
388
389         HASHMAP_FOREACH_KEY(f, key, top, i) {
390                 char *o;
391
392                 o = hashmap_get(bottom, key);
393                 assert(o);
394
395                 if (!onlyprefix || startswith(o, onlyprefix)) {
396                         if (path_equal(o, f)) {
397                                 notify_override_unchanged(f);
398                         } else {
399                                 k = found_override(f, o);
400                                 if (k < 0)
401                                         r = k;
402                                 else
403                                         n_found += k;
404                         }
405                 }
406
407                 h = hashmap_get(drops, key);
408                 if (h)
409                         HASHMAP_FOREACH(o, h, j)
410                                 if (!onlyprefix || startswith(o, onlyprefix))
411                                         n_found += notify_override_extended(f, o);
412         }
413
414 finish:
415         if (top)
416                 hashmap_free_free(top);
417         if (bottom)
418                 hashmap_free_free(bottom);
419         if (drops) {
420                 HASHMAP_FOREACH_KEY(h, key, drops, i){
421                         hashmap_free_free(hashmap_remove(drops, key));
422                         hashmap_remove(drops, key);
423                         free(key);
424                 }
425                 hashmap_free(drops);
426         }
427         return r < 0 ? r : n_found;
428 }
429
430 static int process_suffixes(const char *onlyprefix) {
431         const char *n;
432         int n_found = 0, r;
433
434         NULSTR_FOREACH(n, suffixes) {
435                 r = process_suffix(n, onlyprefix);
436                 if (r < 0)
437                         return r;
438                 else
439                         n_found += r;
440         }
441         return n_found;
442 }
443
444 static int process_suffix_chop(const char *arg) {
445         const char *p;
446
447         assert(arg);
448
449         if (!path_is_absolute(arg))
450                 return process_suffix(arg, NULL);
451
452         /* Strip prefix from the suffix */
453         NULSTR_FOREACH(p, prefixes) {
454                 const char *suffix = startswith(arg, p);
455                 if (suffix) {
456                         suffix += strspn(suffix, "/");
457                         if (*suffix)
458                                 return process_suffix(suffix, NULL);
459                         else
460                                 return process_suffixes(arg);
461                 }
462         }
463
464         log_error("Invalid suffix specification %s.", arg);
465         return -EINVAL;
466 }
467
468 static int help(void) {
469
470         printf("%s [OPTIONS...] [SUFFIX...]\n\n"
471                "Find overridden configuration files.\n\n"
472                "  -h --help           Show this help\n"
473                "     --version        Show package version\n"
474                "     --no-pager       Do not pipe output into a pager\n"
475                "     --diff[=1|0]     Show a diff when overridden files differ\n"
476                "  -t --type=LIST...   Only display a selected set of override types\n",
477                program_invocation_short_name);
478
479         return 0;
480 }
481
482 static int parse_flags(const char *flag_str, int flags) {
483         char *w, *state;
484         size_t l;
485
486         FOREACH_WORD(w, l, flag_str, state) {
487                 if (strneq("masked", w, l))
488                         flags |= SHOW_MASKED;
489                 else if (strneq ("equivalent", w, l))
490                         flags |= SHOW_EQUIVALENT;
491                 else if (strneq("redirected", w, l))
492                         flags |= SHOW_REDIRECTED;
493                 else if (strneq("overridden", w, l))
494                         flags |= SHOW_OVERRIDDEN;
495                 else if (strneq("unchanged", w, l))
496                         flags |= SHOW_UNCHANGED;
497                 else if (strneq("extended", w, l))
498                         flags |= SHOW_EXTENDED;
499                 else if (strneq("default", w, l))
500                         flags |= SHOW_DEFAULTS;
501                 else
502                         return -EINVAL;
503         }
504         return flags;
505 }
506
507 static int parse_argv(int argc, char *argv[]) {
508
509         enum {
510                 ARG_NO_PAGER = 0x100,
511                 ARG_DIFF,
512                 ARG_VERSION
513         };
514
515         static const struct option options[] = {
516                 { "help",      no_argument,       NULL, 'h'          },
517                 { "version",   no_argument,       NULL, ARG_VERSION  },
518                 { "no-pager",  no_argument,       NULL, ARG_NO_PAGER },
519                 { "diff",      optional_argument, NULL, ARG_DIFF     },
520                 { "type",      required_argument, NULL, 't'          },
521                 {}
522         };
523
524         int c;
525
526         assert(argc >= 1);
527         assert(argv);
528
529         while ((c = getopt_long(argc, argv, "ht:", options, NULL)) >= 0) {
530
531                 switch (c) {
532
533                 case 'h':
534                         help();
535                         return 0;
536
537                 case ARG_VERSION:
538                         puts(PACKAGE_STRING);
539                         puts(SYSTEMD_FEATURES);
540                         return 0;
541
542                 case ARG_NO_PAGER:
543                         arg_no_pager = true;
544                         break;
545
546                 case 't': {
547                         int f;
548                         f = parse_flags(optarg, arg_flags);
549                         if (f < 0) {
550                                 log_error("Failed to parse flags field.");
551                                 return -EINVAL;
552                         }
553                         arg_flags = f;
554                         break;
555                 }
556
557                 case ARG_DIFF:
558                         if (!optarg)
559                                 arg_diff = 1;
560                         else {
561                                 int b;
562
563                                 b = parse_boolean(optarg);
564                                 if (b < 0) {
565                                         log_error("Failed to parse diff boolean.");
566                                         return -EINVAL;
567                                 } else if (b)
568                                         arg_diff = 1;
569                                 else
570                                         arg_diff = 0;
571                         }
572                         break;
573
574                 case '?':
575                         return -EINVAL;
576
577                 default:
578                         assert_not_reached("Unhandled option");
579                 }
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 }