chiark / gitweb /
remove unused includes
[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 <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 %s\n",
106                ansi_highlight_red(), "[MASKED]", ansi_highlight_off(),
107                top, draw_special_char(DRAW_ARROW), 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 %s\n",
116                ansi_highlight_green(), "[EQUIVALENT]", ansi_highlight_off(),
117                top, draw_special_char(DRAW_ARROW), bottom);
118         return 1;
119 }
120
121 static int notify_override_redirected(const char *top, const char *bottom) {
122         if (!(arg_flags & SHOW_REDIRECTED))
123                 return 0;
124
125         printf("%s%s%s %s %s %s\n",
126                ansi_highlight(), "[REDIRECTED]", ansi_highlight_off(),
127                top, draw_special_char(DRAW_ARROW), bottom);
128         return 1;
129 }
130
131 static int notify_override_overridden(const char *top, const char *bottom) {
132         if (!(arg_flags & SHOW_OVERRIDDEN))
133                 return 0;
134
135         printf("%s%s%s %s %s %s\n",
136                ansi_highlight(), "[OVERRIDDEN]", ansi_highlight_off(),
137                top, draw_special_char(DRAW_ARROW), bottom);
138         return 1;
139 }
140
141 static int notify_override_extended(const char *top, const char *bottom) {
142         if (!(arg_flags & SHOW_EXTENDED))
143                return 0;
144
145         printf("%s%s%s   %s %s %s\n",
146                ansi_highlight(), "[EXTENDED]", ansi_highlight_off(),
147                top, draw_special_char(DRAW_ARROW), bottom);
148         return 1;
149 }
150
151 static int notify_override_unchanged(const char *f) {
152         if (!(arg_flags & SHOW_UNCHANGED))
153                 return 0;
154
155         printf("[UNCHANGED]  %s\n", f);
156         return 1;
157 }
158
159 static int found_override(const char *top, const char *bottom) {
160         _cleanup_free_ char *dest = NULL;
161         int k;
162         pid_t pid;
163
164         assert(top);
165         assert(bottom);
166
167         if (null_or_empty_path(top) > 0)
168                 return notify_override_masked(top, bottom);
169
170         k = readlink_malloc(top, &dest);
171         if (k >= 0) {
172                 if (equivalent(dest, bottom) > 0)
173                         return notify_override_equivalent(top, bottom);
174                 else
175                         return notify_override_redirected(top, bottom);
176         }
177
178         k = notify_override_overridden(top, bottom);
179         if (!arg_diff)
180                 return k;
181
182         putchar('\n');
183
184         fflush(stdout);
185
186         pid = fork();
187         if (pid < 0)
188                 return log_error_errno(errno, "Failed to fork off diff: %m");
189         else if (pid == 0) {
190                 execlp("diff", "diff", "-us", "--", bottom, top, NULL);
191                 log_error_errno(errno, "Failed to execute diff: %m");
192                 _exit(1);
193         }
194
195         wait_for_terminate_and_warn("diff", pid, false);
196         putchar('\n');
197
198         return k;
199 }
200
201 static int enumerate_dir_d(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *toppath, const char *drop) {
202         _cleanup_free_ char *unit = NULL;
203         _cleanup_free_ char *path = NULL;
204         _cleanup_strv_free_ char **list = NULL;
205         char **file;
206         char *c;
207         int r;
208
209         assert(!endswith(drop, "/"));
210
211         path = strjoin(toppath, "/", drop, NULL);
212         if (!path)
213                 return -ENOMEM;
214
215         log_debug("Looking at %s", path);
216
217         unit = strdup(drop);
218         if (!unit)
219                 return -ENOMEM;
220
221         c = strrchr(unit, '.');
222         if (!c)
223                 return -EINVAL;
224         *c = 0;
225
226         r = get_files_in_directory(path, &list);
227         if (r < 0)
228                 return log_error_errno(r, "Failed to enumerate %s: %m", path);
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 %s", d, draw_special_char(DRAW_ARROW), 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 %s", d, draw_special_char(DRAW_ARROW), 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_ops);
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 %s %s",
280                           unit, draw_special_char(DRAW_ARROW), basename(p), draw_special_char(DRAW_ARROW), p);
281                 k = hashmap_put(h, basename(p), p);
282                 if (k < 0) {
283                         free(p);
284                         if (k != -EEXIST)
285                                 return k;
286                 }
287         }
288         return 0;
289 }
290
291 static int enumerate_dir(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *path, bool dropins) {
292         _cleanup_closedir_ DIR *d;
293
294         assert(top);
295         assert(bottom);
296         assert(drops);
297         assert(path);
298
299         log_debug("Looking at %s", path);
300
301         d = opendir(path);
302         if (!d) {
303                 if (errno == ENOENT)
304                         return 0;
305
306                 log_error_errno(errno, "Failed to open %s: %m", path);
307                 return -errno;
308         }
309
310         for (;;) {
311                 struct dirent *de;
312                 int k;
313                 char *p;
314
315                 errno = 0;
316                 de = readdir(d);
317                 if (!de)
318                         return -errno;
319
320                 dirent_ensure_type(d, de);
321
322                 if (dropins && de->d_type == DT_DIR && endswith(de->d_name, ".d"))
323                         enumerate_dir_d(top, bottom, drops, path, de->d_name);
324
325                 if (!dirent_is_file(de))
326                         continue;
327
328                 p = strjoin(path, "/", de->d_name, NULL);
329                 if (!p)
330                         return -ENOMEM;
331
332                 log_debug("Adding at top: %s %s %s", basename(p), draw_special_char(DRAW_ARROW), p);
333                 k = hashmap_put(top, basename(p), p);
334                 if (k >= 0) {
335                         p = strdup(p);
336                         if (!p)
337                                 return -ENOMEM;
338                 } else if (k != -EEXIST) {
339                         free(p);
340                         return k;
341                 }
342
343                 log_debug("Adding at bottom: %s %s %s", basename(p), draw_special_char(DRAW_ARROW), p);
344                 free(hashmap_remove(bottom, basename(p)));
345                 k = hashmap_put(bottom, basename(p), p);
346                 if (k < 0) {
347                         free(p);
348                         return k;
349                 }
350         }
351 }
352
353 static int process_suffix(const char *suffix, const char *onlyprefix) {
354         const char *p;
355         char *f;
356         Hashmap *top, *bottom, *drops;
357         Hashmap *h;
358         char *key;
359         int r = 0, k;
360         Iterator i, j;
361         int n_found = 0;
362         bool dropins;
363
364         assert(suffix);
365         assert(!startswith(suffix, "/"));
366         assert(!strstr(suffix, "//"));
367
368         dropins = nulstr_contains(have_dropins, suffix);
369
370         top = hashmap_new(&string_hash_ops);
371         bottom = hashmap_new(&string_hash_ops);
372         drops = hashmap_new(&string_hash_ops);
373         if (!top || !bottom || !drops) {
374                 r = -ENOMEM;
375                 goto finish;
376         }
377
378         NULSTR_FOREACH(p, prefixes) {
379                 _cleanup_free_ char *t = NULL;
380
381                 t = strjoin(p, "/", suffix, NULL);
382                 if (!t) {
383                         r = -ENOMEM;
384                         goto finish;
385                 }
386
387                 k = enumerate_dir(top, bottom, drops, t, dropins);
388                 if (r == 0)
389                         r = k;
390         }
391
392         HASHMAP_FOREACH_KEY(f, key, top, i) {
393                 char *o;
394
395                 o = hashmap_get(bottom, key);
396                 assert(o);
397
398                 if (!onlyprefix || startswith(o, onlyprefix)) {
399                         if (path_equal(o, f)) {
400                                 notify_override_unchanged(f);
401                         } else {
402                                 k = found_override(f, o);
403                                 if (k < 0)
404                                         r = k;
405                                 else
406                                         n_found += k;
407                         }
408                 }
409
410                 h = hashmap_get(drops, key);
411                 if (h)
412                         HASHMAP_FOREACH(o, h, j)
413                                 if (!onlyprefix || startswith(o, onlyprefix))
414                                         n_found += notify_override_extended(f, o);
415         }
416
417 finish:
418         if (top)
419                 hashmap_free_free(top);
420         if (bottom)
421                 hashmap_free_free(bottom);
422         if (drops) {
423                 HASHMAP_FOREACH_KEY(h, key, drops, i){
424                         hashmap_free_free(hashmap_remove(drops, key));
425                         hashmap_remove(drops, key);
426                         free(key);
427                 }
428                 hashmap_free(drops);
429         }
430         return r < 0 ? r : n_found;
431 }
432
433 static int process_suffixes(const char *onlyprefix) {
434         const char *n;
435         int n_found = 0, r;
436
437         NULSTR_FOREACH(n, suffixes) {
438                 r = process_suffix(n, onlyprefix);
439                 if (r < 0)
440                         return r;
441                 else
442                         n_found += r;
443         }
444         return n_found;
445 }
446
447 static int process_suffix_chop(const char *arg) {
448         const char *p;
449
450         assert(arg);
451
452         if (!path_is_absolute(arg))
453                 return process_suffix(arg, NULL);
454
455         /* Strip prefix from the suffix */
456         NULSTR_FOREACH(p, prefixes) {
457                 const char *suffix = startswith(arg, p);
458                 if (suffix) {
459                         suffix += strspn(suffix, "/");
460                         if (*suffix)
461                                 return process_suffix(suffix, NULL);
462                         else
463                                 return process_suffixes(arg);
464                 }
465         }
466
467         log_error("Invalid suffix specification %s.", arg);
468         return -EINVAL;
469 }
470
471 static void help(void) {
472         printf("%s [OPTIONS...] [SUFFIX...]\n\n"
473                "Find overridden configuration files.\n\n"
474                "  -h --help           Show this help\n"
475                "     --version        Show package version\n"
476                "     --no-pager       Do not pipe output into a pager\n"
477                "     --diff[=1|0]     Show a diff when overridden files differ\n"
478                "  -t --type=LIST...   Only display a selected set of override types\n"
479                , program_invocation_short_name);
480 }
481
482 static int parse_flags(const char *flag_str, int flags) {
483         const char *word, *state;
484         size_t l;
485
486         FOREACH_WORD_SEPARATOR(word, l, flag_str, ",", state) {
487                 if (strneq("masked", word, l))
488                         flags |= SHOW_MASKED;
489                 else if (strneq ("equivalent", word, l))
490                         flags |= SHOW_EQUIVALENT;
491                 else if (strneq("redirected", word, l))
492                         flags |= SHOW_REDIRECTED;
493                 else if (strneq("overridden", word, l))
494                         flags |= SHOW_OVERRIDDEN;
495                 else if (strneq("unchanged", word, l))
496                         flags |= SHOW_UNCHANGED;
497                 else if (strneq("extended", word, l))
498                         flags |= SHOW_EXTENDED;
499                 else if (strneq("default", word, 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         return 1;
582 }
583
584 int main(int argc, char *argv[]) {
585         int r = 0, k;
586         int n_found = 0;
587
588         log_parse_environment();
589         log_open();
590
591         r = parse_argv(argc, argv);
592         if (r <= 0)
593                 goto finish;
594
595         if (arg_flags == 0)
596                 arg_flags = SHOW_DEFAULTS;
597
598         if (arg_diff < 0)
599                 arg_diff = !!(arg_flags & SHOW_OVERRIDDEN);
600         else if (arg_diff)
601                 arg_flags |= SHOW_OVERRIDDEN;
602
603         pager_open_if_enabled();
604
605         if (optind < argc) {
606                 int i;
607
608                 for (i = optind; i < argc; i++) {
609                         path_kill_slashes(argv[i]);
610                         k = process_suffix_chop(argv[i]);
611                         if (k < 0)
612                                 r = k;
613                         else
614                                 n_found += k;
615                 }
616
617         } else {
618                 k = process_suffixes(NULL);
619                 if (k < 0)
620                         r = k;
621                 else
622                         n_found += k;
623         }
624
625         if (r >= 0)
626                 printf("%s%i overridden configuration files found.\n",
627                        n_found ? "\n" : "", n_found);
628
629 finish:
630         pager_close();
631
632         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
633 }