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