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