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