chiark / gitweb /
delta: fix delta for drop-ins
[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
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <assert.h>
24 #include <string.h>
25 #include <unistd.h>
26 #include <getopt.h>
27
28 #include "hashmap.h"
29 #include "util.h"
30 #include "path-util.h"
31 #include "log.h"
32 #include "pager.h"
33 #include "build.h"
34 #include "strv.h"
35
36 static const char prefixes[] =
37         "/etc\0"
38         "/run\0"
39         "/usr/local/lib\0"
40         "/usr/local/share\0"
41         "/usr/lib\0"
42         "/usr/share\0"
43 #ifdef HAVE_SPLIT_USR
44         "/lib\0"
45 #endif
46         ;
47
48 static const char suffixes[] =
49         "sysctl.d\0"
50         "tmpfiles.d\0"
51         "modules-load.d\0"
52         "binfmt.d\0"
53         "systemd/system\0"
54         "systemd/user\0"
55         "systemd/system-preset\0"
56         "systemd/user-preset\0"
57         "udev/rules.d\0"
58         "modprobe.d\0";
59
60 static const char have_dropins[] =
61         "systemd/system\0"
62         "systemd/user\0";
63
64 static bool arg_no_pager = false;
65 static int arg_diff = -1;
66
67 static enum {
68         SHOW_MASKED = 1 << 0,
69         SHOW_EQUIVALENT = 1 << 1,
70         SHOW_REDIRECTED = 1 << 2,
71         SHOW_OVERRIDDEN = 1 << 3,
72         SHOW_UNCHANGED = 1 << 4,
73         SHOW_EXTENDED = 1 << 5,
74
75         SHOW_DEFAULTS =
76         (SHOW_MASKED | SHOW_EQUIVALENT | SHOW_REDIRECTED | SHOW_OVERRIDDEN | SHOW_EXTENDED)
77 } arg_flags = 0;
78
79 static void pager_open_if_enabled(void) {
80
81         if (arg_no_pager)
82                 return;
83
84         pager_open(false);
85 }
86
87 static int equivalent(const char *a, const char *b) {
88         _cleanup_free_ char *x = NULL, *y = NULL;
89
90         x = canonicalize_file_name(a);
91         if (!x)
92                 return -errno;
93
94         y = canonicalize_file_name(b);
95         if (!y)
96                 return -errno;
97
98         return path_equal(x, y);
99 }
100
101 static int notify_override_masked(const char *top, const char *bottom) {
102         if (!(arg_flags & SHOW_MASKED))
103                 return 0;
104
105         printf("%s%s%s     %s → %s\n",
106                ansi_highlight_red(), "[MASKED]", ansi_highlight_off(), top, bottom);
107         return 1;
108 }
109
110 static int notify_override_equivalent(const char *top, const char *bottom) {
111         if (!(arg_flags & SHOW_EQUIVALENT))
112                 return 0;
113
114         printf("%s%s%s %s → %s\n",
115                ansi_highlight_green(), "[EQUIVALENT]", ansi_highlight_off(), top, bottom);
116         return 1;
117 }
118
119 static int notify_override_redirected(const char *top, const char *bottom) {
120         if (!(arg_flags & SHOW_REDIRECTED))
121                 return 0;
122
123         printf("%s%s%s   %s → %s\n",
124                ansi_highlight(), "[REDIRECTED]", ansi_highlight_off(), top, bottom);
125         return 1;
126 }
127
128 static int notify_override_overridden(const char *top, const char *bottom) {
129         if (!(arg_flags & SHOW_OVERRIDDEN))
130                 return 0;
131
132         printf("%s%s%s %s → %s\n",
133                ansi_highlight(), "[OVERRIDDEN]", ansi_highlight_off(), top, bottom);
134         return 1;
135 }
136
137 static int notify_override_extended(const char *top, const char *bottom) {
138         if (!(arg_flags & SHOW_EXTENDED))
139                return 0;
140
141         printf("%s%s%s   %s → %s\n",
142                ansi_highlight(), "[EXTENDED]", ansi_highlight_off(), top, bottom);
143         return 1;
144 }
145
146 static int notify_override_unchanged(const char *f) {
147         if (!(arg_flags & SHOW_UNCHANGED))
148                 return 0;
149
150         printf("[UNCHANGED]  %s\n", f);
151         return 1;
152 }
153
154 static int found_override(const char *top, const char *bottom) {
155         _cleanup_free_ char *dest = NULL;
156         int k;
157         pid_t pid;
158
159         assert(top);
160         assert(bottom);
161
162         if (null_or_empty_path(top) > 0)
163                 return notify_override_masked(top, bottom);
164
165         k = readlink_malloc(top, &dest);
166         if (k >= 0) {
167                 if (equivalent(dest, bottom) > 0)
168                         return notify_override_equivalent(top, bottom);
169                 else
170                         return notify_override_redirected(top, bottom);
171         }
172
173         k = notify_override_overridden(top, bottom);
174         if (!arg_diff)
175                 return k;
176
177         putchar('\n');
178
179         fflush(stdout);
180
181         pid = fork();
182         if (pid < 0) {
183                 log_error("Failed to fork off diff: %m");
184                 return -errno;
185         } else if (pid == 0) {
186                 execlp("diff", "diff", "-us", "--", bottom, top, NULL);
187                 log_error("Failed to execute diff: %m");
188                 _exit(1);
189         }
190
191         wait_for_terminate(pid, NULL);
192
193         putchar('\n');
194
195         return k;
196 }
197
198 static int enumerate_dir_d(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *toppath, const char *drop) {
199         _cleanup_free_ char *unit = NULL;
200         _cleanup_free_ char *path = NULL;
201         _cleanup_strv_free_ char **list = NULL;
202         char **file;
203         char *c;
204         int r;
205
206         assert(!endswith(drop, "/"));
207
208         path = strjoin(toppath, "/", drop, NULL);
209         if (!path)
210                 return -ENOMEM;
211
212         log_debug("Looking at %s", path);
213
214         unit = strdup(drop);
215         if (!unit)
216                 return -ENOMEM;
217
218         c = strrchr(unit, '.');
219         if (!c)
220                 return -EINVAL;
221         *c = 0;
222
223         r = get_files_in_directory(path, &list);
224         if (r < 0){
225                 log_error("Failed to enumerate %s: %s", path, strerror(-r));
226                 return r;
227         }
228
229         STRV_FOREACH(file, list) {
230                 Hashmap *h;
231                 int k;
232                 char *p;
233                 char *d;
234
235                 if (!endswith(*file, ".conf"))
236                         continue;
237
238                 p = strjoin(path, "/", *file, NULL);
239                 if (!p)
240                         return -ENOMEM;
241                 d = p + strlen(toppath) + 1;
242
243                 log_debug("Adding at top: %s → %s", d, p);
244                 k = hashmap_put(top, d, p);
245                 if (k >= 0) {
246                         p = strdup(p);
247                         if (!p)
248                                 return -ENOMEM;
249                         d = p + strlen(toppath) + 1;
250                 } else if (k != -EEXIST) {
251                         free(p);
252                         return k;
253                 }
254
255                 log_debug("Adding at bottom: %s → %s", d, p);
256                 free(hashmap_remove(bottom, d));
257                 k = hashmap_put(bottom, d, p);
258                 if (k < 0) {
259                         free(p);
260                         return k;
261                 }
262
263                 h = hashmap_get(drops, unit);
264                 if (!h) {
265                         h = hashmap_new(string_hash_func, string_compare_func);
266                         if (!h)
267                                 return -ENOMEM;
268                         hashmap_put(drops, unit, h);
269                         unit = strdup(unit);
270                         if (!unit)
271                                 return -ENOMEM;
272                 }
273
274                 p = strdup(p);
275                 if (!p)
276                         return -ENOMEM;
277
278                 log_debug("Adding to drops: %s → %s → %s", unit, basename(p), p);
279                 k = hashmap_put(h, basename(p), p);
280                 if (k < 0) {
281                         free(p);
282                         if (k != -EEXIST)
283                                 return k;
284                 }
285         }
286         return 0;
287 }
288
289 static int enumerate_dir(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *path, bool dropins) {
290         _cleanup_closedir_ DIR *d;
291
292         assert(top);
293         assert(bottom);
294         assert(drops);
295         assert(path);
296
297         log_debug("Looking at %s", path);
298
299         d = opendir(path);
300         if (!d) {
301                 if (errno == ENOENT)
302                         return 0;
303
304                 log_error("Failed to open %s: %m", path);
305                 return -errno;
306         }
307
308         for (;;) {
309                 struct dirent *de;
310                 int k;
311                 char *p;
312
313                 errno = 0;
314                 de = readdir(d);
315                 if (!de)
316                         return -errno;
317
318                 if (dropins && de->d_type == DT_DIR && endswith(de->d_name, ".d"))
319                         enumerate_dir_d(top, bottom, drops, path, de->d_name);
320
321                 if (!dirent_is_file(de))
322                         continue;
323
324                 p = strjoin(path, "/", de->d_name, NULL);
325                 if (!p)
326                         return -ENOMEM;
327
328                 log_debug("Adding at top: %s → %s", basename(p), p);
329                 k = hashmap_put(top, basename(p), p);
330                 if (k >= 0) {
331                         p = strdup(p);
332                         if (!p)
333                                 return -ENOMEM;
334                 } else if (k != -EEXIST) {
335                         free(p);
336                         return k;
337                 }
338
339                 log_debug("Adding at bottom: %s → %s", basename(p), p);
340                 free(hashmap_remove(bottom, basename(p)));
341                 k = hashmap_put(bottom, basename(p), p);
342                 if (k < 0) {
343                         free(p);
344                         return k;
345                 }
346         }
347 }
348
349 static int process_suffix(const char *suffix) {
350         const char *p;
351         char *f;
352         Hashmap *top, *bottom, *drops;
353         Hashmap *h;
354         char *key;
355         int r = 0, k;
356         Iterator i, j;
357         int n_found = 0;
358         bool dropins;
359
360         assert(suffix);
361         assert(!startswith(suffix, "/"));
362         assert(!strstr(suffix, "//"));
363
364         dropins = nulstr_contains(have_dropins, suffix);
365
366         top = hashmap_new(string_hash_func, string_compare_func);
367         bottom = hashmap_new(string_hash_func, string_compare_func);
368         drops = hashmap_new(string_hash_func, string_compare_func);
369         if (!top || !bottom || !drops) {
370                 r = -ENOMEM;
371                 goto finish;
372         }
373
374         NULSTR_FOREACH(p, prefixes) {
375                 _cleanup_free_ char *t = NULL;
376
377                 t = strjoin(p, "/", suffix, NULL);
378                 if (!t) {
379                         r = -ENOMEM;
380                         goto finish;
381                 }
382
383                 k = enumerate_dir(top, bottom, drops, t, dropins);
384                 if (r == 0)
385                         r = k;
386         }
387
388         HASHMAP_FOREACH_KEY(f, key, top, i) {
389                 char *o;
390
391                 o = hashmap_get(bottom, key);
392                 assert(o);
393
394                 if (path_equal(o, f))
395                         notify_override_unchanged(f);
396                 else {
397                         k = found_override(f, o);
398                         if (k < 0)
399                                 r = k;
400                         else
401                                 n_found += k;
402                 }
403
404                 h = hashmap_get(drops, key);
405                 if (h)
406                         HASHMAP_FOREACH(o, h, j)
407                                 n_found += notify_override_extended(f, o);
408         }
409
410 finish:
411         if (top)
412                 hashmap_free_free(top);
413         if (bottom)
414                 hashmap_free_free(bottom);
415         if (drops) {
416                 HASHMAP_FOREACH_KEY(h, key, drops, i){
417                         hashmap_free_free(hashmap_remove(drops, key));
418                         hashmap_remove(drops, key);
419                         free(key);
420                 }
421                 hashmap_free(drops);
422         }
423         return r < 0 ? r : n_found;
424 }
425
426 static int process_suffix_chop(const char *suffix) {
427         const char *p;
428
429         assert(suffix);
430
431         if (!path_is_absolute(suffix))
432                 return process_suffix(suffix);
433
434         /* Strip prefix from the suffix */
435         NULSTR_FOREACH(p, prefixes) {
436                 if (startswith(suffix, p)) {
437                         suffix += strlen(p);
438                         suffix += strspn(suffix, "/");
439                         return process_suffix(suffix);
440                 }
441         }
442
443         log_error("Invalid suffix specification %s.", suffix);
444         return -EINVAL;
445 }
446
447 static int help(void) {
448
449         printf("%s [OPTIONS...] [SUFFIX...]\n\n"
450                "Find overridden configuration files.\n\n"
451                "  -h --help           Show this help\n"
452                "     --version        Show package version\n"
453                "     --no-pager       Do not pipe output into a pager\n"
454                "     --diff[=1|0]     Show a diff when overridden files differ\n"
455                "  -t --type=LIST...   Only display a selected set of override types\n",
456                program_invocation_short_name);
457
458         return 0;
459 }
460
461 static int parse_flags(const char *flag_str, int flags) {
462         char *w, *state;
463         size_t l;
464
465         FOREACH_WORD(w, l, flag_str, state) {
466                 if (strneq("masked", w, l))
467                         flags |= SHOW_MASKED;
468                 else if (strneq ("equivalent", w, l))
469                         flags |= SHOW_EQUIVALENT;
470                 else if (strneq("redirected", w, l))
471                         flags |= SHOW_REDIRECTED;
472                 else if (strneq("overridden", w, l))
473                         flags |= SHOW_OVERRIDDEN;
474                 else if (strneq("unchanged", w, l))
475                         flags |= SHOW_UNCHANGED;
476                 else if (strneq("extended", w, l))
477                         flags |= SHOW_EXTENDED;
478                 else if (strneq("default", w, l))
479                         flags |= SHOW_DEFAULTS;
480                 else
481                         return -EINVAL;
482         }
483         return flags;
484 }
485
486 static int parse_argv(int argc, char *argv[]) {
487
488         enum {
489                 ARG_NO_PAGER = 0x100,
490                 ARG_DIFF,
491                 ARG_VERSION
492         };
493
494         static const struct option options[] = {
495                 { "help",      no_argument,       NULL, 'h'          },
496                 { "version",   no_argument,       NULL, ARG_VERSION  },
497                 { "no-pager",  no_argument,       NULL, ARG_NO_PAGER },
498                 { "diff",      optional_argument, NULL, ARG_DIFF     },
499                 { "type",      required_argument, NULL, 't'          },
500                 {}
501         };
502
503         int c;
504
505         assert(argc >= 1);
506         assert(argv);
507
508         while ((c = getopt_long(argc, argv, "ht:", options, NULL)) >= 0) {
509
510                 switch (c) {
511
512                 case 'h':
513                         help();
514                         return 0;
515
516                 case ARG_VERSION:
517                         puts(PACKAGE_STRING);
518                         puts(SYSTEMD_FEATURES);
519                         return 0;
520
521                 case ARG_NO_PAGER:
522                         arg_no_pager = true;
523                         break;
524
525                 case 't': {
526                         int f;
527                         f = parse_flags(optarg, arg_flags);
528                         if (f < 0) {
529                                 log_error("Failed to parse flags field.");
530                                 return -EINVAL;
531                         }
532                         arg_flags = f;
533                         break;
534                 }
535
536                 case ARG_DIFF:
537                         if (!optarg)
538                                 arg_diff = 1;
539                         else {
540                                 int b;
541
542                                 b = parse_boolean(optarg);
543                                 if (b < 0) {
544                                         log_error("Failed to parse diff boolean.");
545                                         return -EINVAL;
546                                 } else if (b)
547                                         arg_diff = 1;
548                                 else
549                                         arg_diff = 0;
550                         }
551                         break;
552
553                 case '?':
554                         return -EINVAL;
555
556                 default:
557                         assert_not_reached("Unhandled option");
558                 }
559         }
560
561         return 1;
562 }
563
564 int main(int argc, char *argv[]) {
565         int r = 0, k;
566         int n_found = 0;
567
568         log_parse_environment();
569         log_open();
570
571         r = parse_argv(argc, argv);
572         if (r <= 0)
573                 goto finish;
574
575         if (arg_flags == 0)
576                 arg_flags = SHOW_DEFAULTS;
577
578         if (arg_diff < 0)
579                 arg_diff = !!(arg_flags & SHOW_OVERRIDDEN);
580         else if (arg_diff)
581                 arg_flags |= SHOW_OVERRIDDEN;
582
583         pager_open_if_enabled();
584
585         if (optind < argc) {
586                 int i;
587
588                 for (i = optind; i < argc; i++) {
589                         path_kill_slashes(argv[i]);
590                         k = process_suffix_chop(argv[i]);
591                         if (k < 0)
592                                 r = k;
593                         else
594                                 n_found += k;
595                 }
596
597         } else {
598                 const char *n;
599
600                 NULSTR_FOREACH(n, suffixes) {
601                         k = process_suffix(n);
602                         if (k < 0)
603                                 r = k;
604                         else
605                                 n_found += k;
606                 }
607         }
608
609         if (r >= 0)
610                 printf("%s%i overridden configuration files found.\n",
611                        n_found ? "\n" : "", n_found);
612
613 finish:
614         pager_close();
615
616         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
617 }