chiark / gitweb /
remove duplicate semicolons
[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;
180                 union dirent_storage buf;
181                 int k;
182                 char *p;
183
184                 k = readdir_r(d, &buf.de, &de);
185                 if (k != 0) {
186                         r = -k;
187                         goto finish;
188                 }
189
190                 if (!de)
191                         break;
192
193                 if (!dirent_is_file(de))
194                         continue;
195
196                 p = strjoin(path, "/", de->d_name, NULL);
197                 if (!p) {
198                         r = -ENOMEM;
199                         goto finish;
200                 }
201
202                 path_kill_slashes(p);
203
204                 k = hashmap_put(top, path_get_file_name(p), p);
205                 if (k >= 0) {
206                         p = strdup(p);
207                         if (!p) {
208                                 r = -ENOMEM;
209                                 goto finish;
210                         }
211                 } else if (k != -EEXIST) {
212                         free(p);
213                         r = k;
214                         goto finish;
215                 }
216
217                 free(hashmap_remove(bottom, path_get_file_name(p)));
218                 k = hashmap_put(bottom, path_get_file_name(p), p);
219                 if (k < 0) {
220                         free(p);
221                         r = k;
222                         goto finish;
223                 }
224         }
225
226 finish:
227         closedir(d);
228
229         return r;
230 }
231
232 static int process_suffix(const char *prefixes, const char *suffix) {
233         const char *p;
234         char *f;
235         Hashmap *top, *bottom=NULL;
236         int r = 0, k;
237         Iterator i;
238         int n_found = 0;
239
240         assert(prefixes);
241         assert(suffix);
242
243         top = hashmap_new(string_hash_func, string_compare_func);
244         if (!top) {
245                 r = -ENOMEM;
246                 goto finish;
247         }
248
249         bottom = hashmap_new(string_hash_func, string_compare_func);
250         if (!bottom) {
251                 r = -ENOMEM;
252                 goto finish;
253         }
254
255         NULSTR_FOREACH(p, prefixes) {
256                 char *t;
257
258                 t = strjoin(p, "/", suffix, NULL);
259                 if (!t) {
260                         r = -ENOMEM;
261                         goto finish;
262                 }
263
264                 k = enumerate_dir(top, bottom, t);
265                 if (k < 0)
266                         r = k;
267
268                 log_debug("Looking at %s", t);
269                 free(t);
270         }
271
272         HASHMAP_FOREACH(f, top, i) {
273                 char *o;
274
275                 o = hashmap_get(bottom, path_get_file_name(f));
276                 assert(o);
277
278                 if (path_equal(o, f)) {
279                         notify_override_unchanged(f);
280                         continue;
281                 }
282
283                 k = found_override(f, o);
284                 if (k < 0)
285                         r = k;
286
287                 n_found ++;
288         }
289
290 finish:
291         if (top)
292                 hashmap_free_free(top);
293         if (bottom)
294                 hashmap_free_free(bottom);
295
296         return r < 0 ? r : n_found;
297 }
298
299 static int process_suffix_chop(const char *prefixes, const char *suffix) {
300         const char *p;
301
302         assert(prefixes);
303         assert(suffix);
304
305         if (!path_is_absolute(suffix))
306                 return process_suffix(prefixes, suffix);
307
308         /* Strip prefix from the suffix */
309         NULSTR_FOREACH(p, prefixes) {
310                 if (startswith(suffix, p)) {
311                         suffix += strlen(p);
312                         suffix += strspn(suffix, "/");
313                         return process_suffix(prefixes, suffix);
314                 }
315         }
316
317         log_error("Invalid suffix specification %s.", suffix);
318         return -EINVAL;
319 }
320
321 static void help(void) {
322
323         printf("%s [OPTIONS...] [SUFFIX...]\n\n"
324                "Find overridden configuration files.\n\n"
325                "  -h --help           Show this help\n"
326                "     --version        Show package version\n"
327                "     --no-pager       Do not pipe output into a pager\n"
328                "     --diff[=1|0]     Show a diff when overridden files differ\n"
329                "  -t --type=LIST...   Only display a selected set of override types\n",
330                program_invocation_short_name);
331 }
332
333 static int parse_flags(const char *flag_str, int flags) {
334         char *w, *state;
335         size_t l;
336
337         FOREACH_WORD(w, l, flag_str, state) {
338                 if (strncmp("masked", w, l) == 0)
339                         flags |= SHOW_MASKED;
340                 else if (strncmp ("equivalent", w, l) == 0)
341                         flags |= SHOW_EQUIVALENT;
342                 else if (strncmp("redirected", w, l) == 0)
343                         flags |= SHOW_REDIRECTED;
344                 else if (strncmp("overridden", w, l) == 0)
345                         flags |= SHOW_OVERRIDDEN;
346                 else if (strncmp("unchanged", w, l) == 0)
347                         flags |= SHOW_UNCHANGED;
348                 else if (strncmp("default", w, l) == 0)
349                         flags |= SHOW_DEFAULTS;
350                 else
351                         return -EINVAL;
352         }
353         return flags;
354 }
355
356 static int parse_argv(int argc, char *argv[]) {
357
358         enum {
359                 ARG_NO_PAGER = 0x100,
360                 ARG_DIFF,
361                 ARG_VERSION
362         };
363
364         static const struct option options[] = {
365                 { "help",      no_argument,       NULL, 'h'          },
366                 { "version",   no_argument,       NULL, ARG_VERSION  },
367                 { "no-pager",  no_argument,       NULL, ARG_NO_PAGER },
368                 { "diff",      optional_argument, NULL, ARG_DIFF     },
369                 { "type",      required_argument, NULL, 't'          },
370                 { NULL,        0,                 NULL, 0            }
371         };
372
373         int c;
374
375         assert(argc >= 1);
376         assert(argv);
377
378         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
379
380                 switch (c) {
381
382                 case 'h':
383                         help();
384                         return 0;
385
386                 case ARG_VERSION:
387                         puts(PACKAGE_STRING);
388                         puts(DISTRIBUTION);
389                         puts(SYSTEMD_FEATURES);
390                         return 0;
391
392                 case ARG_NO_PAGER:
393                         arg_no_pager = true;
394                         break;
395
396                 case '?':
397                         return -EINVAL;
398
399                 case 't': {
400                         int f;
401                         f = parse_flags(optarg, arg_flags);
402                         if (f < 0) {
403                                 log_error("Failed to parse flags field.");
404                                 return -EINVAL;
405                         }
406                         arg_flags = f;
407                         break;
408                 }
409
410                 case ARG_DIFF:
411                         if (!optarg)
412                                 arg_diff = 1;
413                         else {
414                                 int b;
415
416                                 b = parse_boolean(optarg);
417                                 if (b < 0) {
418                                         log_error("Failed to parse diff boolean.");
419                                         return -EINVAL;
420                                 } else if (b)
421                                         arg_diff = 1;
422                                 else
423                                         arg_diff = 0;
424                         }
425                         break;
426
427                 default:
428                         log_error("Unknown option code %c", c);
429                         return -EINVAL;
430                 }
431         }
432
433         return 1;
434 }
435
436 int main(int argc, char *argv[]) {
437
438         const char prefixes[] =
439                 "/etc\0"
440                 "/run\0"
441                 "/usr/local/lib\0"
442                 "/usr/local/share\0"
443                 "/usr/lib\0"
444                 "/usr/share\0"
445 #ifdef HAVE_SPLIT_USR
446                 "/lib\0"
447 #endif
448                 ;
449
450         const char suffixes[] =
451                 "sysctl.d\0"
452                 "tmpfiles.d\0"
453                 "modules-load.d\0"
454                 "binfmt.d\0"
455                 "systemd/system\0"
456                 "systemd/user\0"
457                 "systemd/system-preset\0"
458                 "systemd/user-preset\0"
459                 "udev/rules.d\0"
460                 "modprobe.d\0";
461
462         int r = 0, k;
463         int n_found = 0;
464
465         log_parse_environment();
466         log_open();
467
468         r = parse_argv(argc, argv);
469         if (r <= 0)
470                 goto finish;
471
472         if (arg_flags == 0)
473                 arg_flags = SHOW_DEFAULTS;
474
475         if (arg_diff < 0)
476                 arg_diff = !!(arg_flags & SHOW_OVERRIDDEN);
477         else if (arg_diff)
478                 arg_flags |= SHOW_OVERRIDDEN;
479
480         if (!arg_no_pager)
481                 pager_open();
482
483         if (optind < argc) {
484                 int i;
485
486                 for (i = optind; i < argc; i++) {
487                         k = process_suffix_chop(prefixes, argv[i]);
488                         if (k < 0)
489                                 r = k;
490                         else
491                                 n_found += k;
492                 }
493
494         } else {
495                 const char *n;
496
497                 NULSTR_FOREACH(n, suffixes) {
498                         k = process_suffix(prefixes, n);
499                         if (k < 0)
500                                 r = k;
501                         else
502                                 n_found += k;
503                 }
504         }
505
506         if (r >= 0)
507                 printf("\n%i overridden configuration files found.\n", n_found);
508
509 finish:
510         pager_close();
511
512         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
513 }