chiark / gitweb /
delta: fix spelling of overridden
[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 static int arg_diff = -1;
37
38 static enum {
39         SHOW_MASKED = 1 << 0,
40         SHOW_EQUIVALENT = 1 << 1,
41         SHOW_REDIRECTED = 1 << 2,
42         SHOW_OVERRIDDEN = 1 << 3,
43         SHOW_UNCHANGED = 1 << 4,
44
45         SHOW_DEFAULTS =
46         (SHOW_MASKED | SHOW_EQUIVALENT | SHOW_REDIRECTED | SHOW_OVERRIDDEN)
47 } arg_flags = 0;
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(const char *top, const char *bottom) {
71         if (!(arg_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(const char *top, const char *bottom) {
79         if (!(arg_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_redirected(const char *top, const char *bottom) {
87         if (!(arg_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_overridden(const char *top, const char *bottom) {
95         if (!(arg_flags & SHOW_OVERRIDDEN))
96                 return 0;
97
98         printf(ANSI_HIGHLIGHT_ON "[OVERRIDDEN]" ANSI_HIGHLIGHT_OFF " %s → %s\n", top, bottom);
99         return 1;
100 }
101
102 static int notify_override_unchanged(const char *f) {
103         if (!(arg_flags & SHOW_UNCHANGED))
104                 return 0;
105
106         printf("[UNCHANGED]  %s\n", f);
107         return 1;
108 }
109
110 static int found_override(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(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(top, bottom);
127                 else
128                         notify_override_redirected(top, bottom);
129
130                 free(dest);
131                 goto finish;
132         }
133
134         notify_override_overridden(top, bottom);
135         if (!arg_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(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(f);
279                         continue;
280                 }
281
282                 k = found_override(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(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(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(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 overridden 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(const char *flag_str, int flags) {
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("overridden", w, l) == 0)
344                         flags |= SHOW_OVERRIDDEN;
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                         return -EINVAL;
351         }
352         return flags;
353 }
354
355 static int parse_argv(int argc, char *argv[]) {
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                         int f;
400                         f = parse_flags(optarg, arg_flags);
401                         if (f < 0) {
402                                 log_error("Failed to parse flags field.");
403                                 return -EINVAL;
404                         }
405                         arg_flags = f;
406                         break;
407                 }
408
409                 case ARG_DIFF:
410                         if (!optarg)
411                                 arg_diff = 1;
412                         else {
413                                 int b;
414
415                                 b = parse_boolean(optarg);
416                                 if (b < 0) {
417                                         log_error("Failed to parse diff boolean.");
418                                         return -EINVAL;
419                                 } else if (b)
420                                         arg_diff = 1;
421                                 else
422                                         arg_diff = 0;
423                         }
424                         break;
425
426                 default:
427                         log_error("Unknown option code %c", c);
428                         return -EINVAL;
429                 }
430         }
431
432         return 1;
433 }
434
435 int main(int argc, char *argv[]) {
436
437         const char prefixes[] =
438                 "/etc\0"
439                 "/run\0"
440                 "/usr/local/lib\0"
441                 "/usr/local/share\0"
442                 "/usr/lib\0"
443                 "/usr/share\0"
444 #ifdef HAVE_SPLIT_USR
445                 "/lib\0"
446 #endif
447                 ;
448
449         const char suffixes[] =
450                 "sysctl.d\0"
451                 "tmpfiles.d\0"
452                 "modules-load.d\0"
453                 "binfmt.d\0"
454                 "systemd/system\0"
455                 "systemd/user\0"
456                 "systemd/system.preset\0"
457                 "systemd/user.preset\0"
458                 "udev/rules.d\0"
459                 "modprobe.d\0";
460
461         int r = 0, k;
462         int n_found = 0;
463
464         log_parse_environment();
465         log_open();
466
467         r = parse_argv(argc, argv);
468         if (r <= 0)
469                 goto finish;
470
471         if (arg_flags == 0)
472                 arg_flags = SHOW_DEFAULTS;
473
474         if (arg_diff < 0)
475                 arg_diff = !!(arg_flags & SHOW_OVERRIDDEN);
476         else if (arg_diff)
477                 arg_flags |= SHOW_OVERRIDDEN;
478
479         if (!arg_no_pager)
480                 pager_open();
481
482         if (optind < argc) {
483                 int i;
484
485                 for (i = optind; i < argc; i++) {
486                         k = process_suffix_chop(prefixes, argv[i]);
487                         if (k < 0)
488                                 r = k;
489                         else
490                                 n_found += k;
491                 }
492
493         } else {
494                 const char *n;
495
496                 NULSTR_FOREACH(n, suffixes) {
497                         k = process_suffix(prefixes, n);
498                         if (k < 0)
499                                 r = k;
500                         else
501                                 n_found += k;
502                 }
503         }
504
505         if (r >= 0)
506                 printf("\n%i overridden configuration files found.\n", n_found);
507
508 finish:
509         pager_close();
510
511         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
512 }