chiark / gitweb /
delta: use same nomenclature for equivalent and redirected everywhere
[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
35 static bool arg_no_pager = false;
36
37 enum {
38         SHOW_MASKED = 1 << 0,
39         SHOW_EQUIVALENT = 1 << 1,
40         SHOW_REDIRECTED = 1 << 2,
41         SHOW_OVERRIDEN = 1 << 3,
42         SHOW_UNCHANGED = 1 << 4,
43         SHOW_DIFF = 1 << 5,
44
45         SHOW_DEFAULTS =
46         (SHOW_MASKED | SHOW_EQUIVALENT | SHOW_REDIRECTED | SHOW_OVERRIDEN | SHOW_DIFF)
47 };
48
49 static int equivalent(const char *a, const char *b) {
50         char *x, *y;
51         int r;
52
53         x = canonicalize_file_name(a);
54         if (!x)
55                 return -errno;
56
57         y = canonicalize_file_name(b);
58         if (!y) {
59                 free(x);
60                 return -errno;
61         }
62
63         r = path_equal(x, y);
64         free(x);
65         free(y);
66
67         return r;
68 }
69
70 static int notify_override_masked(int flags, const char *top, const char *bottom) {
71         if (!(flags & SHOW_MASKED))
72                 return 0;
73
74         printf(ANSI_HIGHLIGHT_RED_ON "[MASKED]" ANSI_HIGHLIGHT_OFF "     %s → %s\n", top, bottom);
75         return 1;
76 }
77
78 static int notify_override_equivalent(int flags, const char *top, const char *bottom) {
79         if (!(flags & SHOW_EQUIVALENT))
80                 return 0;
81
82         printf(ANSI_HIGHLIGHT_GREEN_ON "[EQUIVALENT]" ANSI_HIGHLIGHT_OFF " %s → %s\n", top, bottom);
83         return 1;
84 }
85
86 static int notify_override_redirirected(int flags, const char *top, const char *bottom) {
87         if (!(flags & SHOW_REDIRECTED))
88                 return 0;
89
90         printf(ANSI_HIGHLIGHT_ON "[REDIRECTED]" ANSI_HIGHLIGHT_OFF "   %s → %s\n", top, bottom);
91         return 1;
92 }
93
94 static int notify_override_overriden(int flags, const char *top, const char *bottom) {
95         if (!(flags & SHOW_OVERRIDEN))
96                 return 0;
97
98         printf(ANSI_HIGHLIGHT_ON "[OVERRIDEN]" ANSI_HIGHLIGHT_OFF "  %s → %s\n", top, bottom);
99         return 1;
100 }
101
102 static int notify_override_unchanged(int flags, const char *top, const char *bottom) {
103         if (!(flags & SHOW_UNCHANGED))
104                 return 0;
105
106         printf(ANSI_HIGHLIGHT_ON "[UNCHANGED]" ANSI_HIGHLIGHT_OFF "  %s → %s\n", top, bottom);
107         return 1;
108 }
109
110 static int found_override(int flags, const char *top, const char *bottom) {
111         char *dest;
112         int k;
113         pid_t pid;
114
115         assert(top);
116         assert(bottom);
117
118         if (null_or_empty_path(top) > 0) {
119                 notify_override_masked(flags, top, bottom);
120                 goto finish;
121         }
122
123         k = readlink_malloc(top, &dest);
124         if (k >= 0) {
125                 if (equivalent(dest, bottom) > 0)
126                         notify_override_equivalent(flags, top, bottom);
127                 else
128                         notify_override_redirirected(flags, top, bottom);
129
130                 free(dest);
131                 goto finish;
132         }
133
134         notify_override_overriden(flags, top, bottom);
135         if (!(flags & SHOW_DIFF))
136                 goto finish;
137
138         putchar('\n');
139
140         fflush(stdout);
141
142         pid = fork();
143         if (pid < 0) {
144                 log_error("Failed to fork off diff: %m");
145                 return -errno;
146         } else if (pid == 0) {
147                 execlp("diff", "diff", "-us", "--", bottom, top, NULL);
148                 log_error("Failed to execute diff: %m");
149                 _exit(1);
150         }
151
152         wait_for_terminate(pid, NULL);
153
154         putchar('\n');
155
156 finish:
157
158         return 0;
159 }
160
161 static int enumerate_dir(Hashmap *top, Hashmap *bottom, const char *path) {
162         DIR *d;
163         int r = 0;
164
165         assert(top);
166         assert(bottom);
167         assert(path);
168
169         d = opendir(path);
170         if (!d) {
171                 if (errno == ENOENT)
172                         return 0;
173
174                 log_error("Failed to enumerate %s: %m", path);
175                 return -errno;
176         }
177
178         for (;;) {
179                 struct dirent *de, buf;
180                 int k;
181                 char *p;
182
183                 k = readdir_r(d, &buf, &de);
184                 if (k != 0) {
185                         r = -k;
186                         goto finish;
187                 }
188
189                 if (!de)
190                         break;
191
192                 if (!dirent_is_file(de))
193                         continue;
194
195                 p = join(path, "/", de->d_name, NULL);
196                 if (!p) {
197                         r = -ENOMEM;
198                         goto finish;
199                 }
200
201                 path_kill_slashes(p);
202
203                 k = hashmap_put(top, path_get_file_name(p), p);
204                 if (k >= 0) {
205                         p = strdup(p);
206                         if (!p) {
207                                 r = -ENOMEM;
208                                 goto finish;
209                         }
210                 } else if (k != -EEXIST) {
211                         free(p);
212                         r = k;
213                         goto finish;
214                 }
215
216                 free(hashmap_remove(bottom, path_get_file_name(p)));
217                 k = hashmap_put(bottom, path_get_file_name(p), p);
218                 if (k < 0) {
219                         free(p);
220                         r = k;
221                         goto finish;
222                 }
223         }
224
225 finish:
226         closedir(d);
227
228         return r;
229 }
230
231 static int process_suffix(int flags, const char *prefixes, const char *suffix) {
232         const char *p;
233         char *f;
234         Hashmap *top, *bottom;
235         int r = 0, k;
236         Iterator i;
237         int n_found = 0;
238
239         assert(prefixes);
240         assert(suffix);
241
242         top = hashmap_new(string_hash_func, string_compare_func);
243         if (!top) {
244                 r = -ENOMEM;
245                 goto finish;
246         }
247
248         bottom = hashmap_new(string_hash_func, string_compare_func);
249         if (!bottom) {
250                 r = -ENOMEM;
251                 goto finish;
252         }
253
254         NULSTR_FOREACH(p, prefixes) {
255                 char *t;
256
257                 t = join(p, "/", suffix, NULL);
258                 if (!t) {
259                         r = -ENOMEM;
260                         goto finish;
261                 }
262
263                 k = enumerate_dir(top, bottom, t);
264                 if (k < 0)
265                         r = k;
266
267                 log_debug("Looking at %s", t);
268                 free(t);
269         }
270
271         HASHMAP_FOREACH(f, top, i) {
272                 char *o;
273
274                 o = hashmap_get(bottom, path_get_file_name(f));
275                 assert(o);
276
277                 if (path_equal(o, f)) {
278                         notify_override_unchanged(flags, f, o);
279                         continue;
280                 }
281
282                 k = found_override(flags, f, o);
283                 if (k < 0)
284                         r = k;
285
286                 n_found ++;
287         }
288
289 finish:
290         if (top)
291                 hashmap_free_free(top);
292         if (bottom)
293                 hashmap_free_free(bottom);
294
295         return r < 0 ? r : n_found;
296 }
297
298 static int process_suffix_chop(int flags, const char *prefixes, const char *suffix) {
299         const char *p;
300
301         assert(prefixes);
302         assert(suffix);
303
304         if (!path_is_absolute(suffix))
305                 return process_suffix(flags, prefixes, suffix);
306
307         /* Strip prefix from the suffix */
308         NULSTR_FOREACH(p, prefixes) {
309                 if (startswith(suffix, p)) {
310                         suffix += strlen(p);;
311                         suffix += strspn(suffix, "/");
312                         return process_suffix(flags, prefixes, suffix);
313                 }
314         }
315
316         log_error("Invalid suffix specification %s.", suffix);
317         return -EINVAL;
318 }
319
320 static void help(void) {
321
322         printf("%s [OPTIONS...] [SUFFIX...]\n\n"
323                "Find overridden configuration files.\n\n"
324                "  -h --help           Show this help\n"
325                "     --version        Show package version\n"
326                "     --no-pager       Do not pipe output into a pager\n"
327                "     --diff[=1|0]     Show a diff when overriden files differ\n"
328                "  -t --type=LIST...   Only display a selected set of override types\n",
329                program_invocation_short_name);
330 }
331
332 static int parse_flags(int flags, const char *flag_str) {
333         char *w, *state;
334         size_t l;
335
336         FOREACH_WORD(w, l, flag_str, state) {
337                 if (strncmp("masked", w, l) == 0) {
338                         flags |= SHOW_MASKED;
339                 } else if (strncmp ("equivalent", w, l) == 0) {
340                         flags |= SHOW_EQUIVALENT;
341                 } else if (strncmp("redirected", w, l) == 0) {
342                         flags |= SHOW_REDIRECTED;
343                 } else if (strncmp("override", w, l) == 0) {
344                         flags |= SHOW_OVERRIDEN;
345                 } else if (strncmp("unchanged", w, l) == 0) {
346                         flags |= SHOW_UNCHANGED;
347                 } else if (strncmp("default", w, l) == 0) {
348                         flags |= SHOW_DEFAULTS;
349                 } else {
350                         log_error("Unknown type filter: %s", w);
351                         return -1;
352                 }
353         }
354         return flags;
355 }
356
357 static int parse_argv(int argc, char *argv[], int *flags) {
358
359         enum {
360                 ARG_NO_PAGER = 0x100,
361                 ARG_DIFF,
362                 ARG_VERSION
363         };
364
365         static const struct option options[] = {
366                 { "help",      no_argument,       NULL, 'h'          },
367                 { "version",   no_argument,       NULL, ARG_VERSION  },
368                 { "no-pager",  no_argument,       NULL, ARG_NO_PAGER },
369                 { "diff",      optional_argument, NULL, ARG_DIFF     },
370                 { "type",      required_argument, NULL, 't'          },
371                 { NULL,        0,                 NULL, 0            }
372         };
373
374         int c;
375
376         assert(argc >= 1);
377         assert(argv);
378
379         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
380
381                 switch (c) {
382
383                 case 'h':
384                         help();
385                         return 0;
386
387                 case ARG_VERSION:
388                         puts(PACKAGE_STRING);
389                         puts(DISTRIBUTION);
390                         puts(SYSTEMD_FEATURES);
391                         return 0;
392
393                 case ARG_NO_PAGER:
394                         arg_no_pager = true;
395                         break;
396
397                 case '?':
398                         return -EINVAL;
399
400                 case 't':
401                         *flags = parse_flags(*flags, optarg);
402                         if (*flags < 0)
403                                 return -EINVAL;
404                         break;
405
406                 case ARG_DIFF:
407                         if (!optarg) {
408                                 *flags |= SHOW_DIFF;
409                         } else {
410                                 if (parse_boolean(optarg))
411                                         *flags |= SHOW_DIFF;
412                                 else
413                                         *flags &= ~SHOW_DIFF;
414                         }
415                         break;
416
417                 default:
418                         log_error("Unknown option code %c", c);
419                         return -EINVAL;
420                 }
421         }
422
423         return 1;
424 }
425
426 int main(int argc, char *argv[]) {
427
428         const char prefixes[] =
429                 "/etc\0"
430                 "/run\0"
431                 "/usr/local/lib\0"
432                 "/usr/local/share\0"
433                 "/usr/lib\0"
434                 "/usr/share\0"
435 #ifdef HAVE_SPLIT_USR
436                 "/lib\0"
437 #endif
438                 ;
439
440         const char suffixes[] =
441                 "sysctl.d\0"
442                 "tmpfiles.d\0"
443                 "modules-load.d\0"
444                 "binfmt.d\0"
445                 "systemd/system\0"
446                 "systemd/user\0"
447                 "systemd/system.preset\0"
448                 "systemd/user.preset\0"
449                 "udev/rules.d\0"
450                 "modprobe.d\0";
451
452         int r = 0, k;
453         int n_found = 0;
454         int flags = 0;
455
456         log_parse_environment();
457         log_open();
458
459         r = parse_argv(argc, argv, &flags);
460         if (r <= 0)
461                 goto finish;
462
463         if (flags == 0)
464                 flags = SHOW_DEFAULTS;
465         if (flags == SHOW_DIFF)
466                 flags |= SHOW_OVERRIDEN;
467
468         if (!arg_no_pager)
469                 pager_open();
470
471         if (optind < argc) {
472                 int i;
473
474                 for (i = optind; i < argc; i++) {
475                         k = process_suffix_chop(flags, prefixes, argv[i]);
476                         if (k < 0)
477                                 r = k;
478                         else
479                                 n_found += k;
480                 }
481
482         } else {
483                 const char *n;
484
485                 NULSTR_FOREACH(n, suffixes) {
486                         k = process_suffix(flags, prefixes, n);
487                         if (k < 0)
488                                 r = k;
489                         else
490                                 n_found += k;
491                 }
492         }
493
494         if (r >= 0)
495                 printf("\n%i overriden configuration files found.\n", n_found);
496
497 finish:
498         pager_close();
499
500         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
501 }