chiark / gitweb /
Unify parse_argv style
[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   Copyright 2013 Zbigniew JÄ™drzejewski-Szmek
8
9   systemd is free software; you can redistribute it and/or modify it
10   under the terms of the GNU Lesser General Public License as published by
11   the Free Software Foundation; either version 2.1 of the License, or
12   (at your option) any later version.
13
14   systemd is distributed in the hope that it will be useful, but
15   WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   Lesser General Public License for more details.
18
19   You should have received a copy of the GNU Lesser General Public License
20   along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 ***/
22
23 #include <errno.h>
24 #include <assert.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <getopt.h>
28
29 #include "hashmap.h"
30 #include "util.h"
31 #include "path-util.h"
32 #include "log.h"
33 #include "pager.h"
34 #include "build.h"
35 #include "strv.h"
36
37 static const char prefixes[] =
38         "/etc\0"
39         "/run\0"
40         "/usr/local/lib\0"
41         "/usr/local/share\0"
42         "/usr/lib\0"
43         "/usr/share\0"
44 #ifdef HAVE_SPLIT_USR
45         "/lib\0"
46 #endif
47         ;
48
49 static const char suffixes[] =
50         "sysctl.d\0"
51         "tmpfiles.d\0"
52         "modules-load.d\0"
53         "binfmt.d\0"
54         "systemd/system\0"
55         "systemd/user\0"
56         "systemd/system-preset\0"
57         "systemd/user-preset\0"
58         "udev/rules.d\0"
59         "modprobe.d\0";
60
61 static const char have_dropins[] =
62         "systemd/system\0"
63         "systemd/user\0";
64
65 static bool arg_no_pager = false;
66 static int arg_diff = -1;
67
68 static enum {
69         SHOW_MASKED = 1 << 0,
70         SHOW_EQUIVALENT = 1 << 1,
71         SHOW_REDIRECTED = 1 << 2,
72         SHOW_OVERRIDDEN = 1 << 3,
73         SHOW_UNCHANGED = 1 << 4,
74         SHOW_EXTENDED = 1 << 5,
75
76         SHOW_DEFAULTS =
77         (SHOW_MASKED | SHOW_EQUIVALENT | SHOW_REDIRECTED | SHOW_OVERRIDDEN | SHOW_EXTENDED)
78 } arg_flags = 0;
79
80 static void pager_open_if_enabled(void) {
81
82         if (arg_no_pager)
83                 return;
84
85         pager_open(false);
86 }
87
88 static int equivalent(const char *a, const char *b) {
89         _cleanup_free_ char *x = NULL, *y = NULL;
90
91         x = canonicalize_file_name(a);
92         if (!x)
93                 return -errno;
94
95         y = canonicalize_file_name(b);
96         if (!y)
97                 return -errno;
98
99         return path_equal(x, y);
100 }
101
102 static int notify_override_masked(const char *top, const char *bottom) {
103         if (!(arg_flags & SHOW_MASKED))
104                 return 0;
105
106         printf("%s%s%s     %s %s %s\n",
107                ansi_highlight_red(), "[MASKED]", ansi_highlight_off(),
108                top, draw_special_char(DRAW_ARROW), bottom);
109         return 1;
110 }
111
112 static int notify_override_equivalent(const char *top, const char *bottom) {
113         if (!(arg_flags & SHOW_EQUIVALENT))
114                 return 0;
115
116         printf("%s%s%s %s %s %s\n",
117                ansi_highlight_green(), "[EQUIVALENT]", ansi_highlight_off(),
118                top, draw_special_char(DRAW_ARROW), bottom);
119         return 1;
120 }
121
122 static int notify_override_redirected(const char *top, const char *bottom) {
123         if (!(arg_flags & SHOW_REDIRECTED))
124                 return 0;
125
126         printf("%s%s%s   %s %s %s\n",
127                ansi_highlight(), "[REDIRECTED]", ansi_highlight_off(),
128                top, draw_special_char(DRAW_ARROW), bottom);
129         return 1;
130 }
131
132 static int notify_override_overridden(const char *top, const char *bottom) {
133         if (!(arg_flags & SHOW_OVERRIDDEN))
134                 return 0;
135
136         printf("%s%s%s %s %s %s\n",
137                ansi_highlight(), "[OVERRIDDEN]", ansi_highlight_off(),
138                top, draw_special_char(DRAW_ARROW), bottom);
139         return 1;
140 }
141
142 static int notify_override_extended(const char *top, const char *bottom) {
143         if (!(arg_flags & SHOW_EXTENDED))
144                return 0;
145
146         printf("%s%s%s   %s %s %s\n",
147                ansi_highlight(), "[EXTENDED]", ansi_highlight_off(),
148                top, draw_special_char(DRAW_ARROW), bottom);
149         return 1;
150 }
151
152 static int notify_override_unchanged(const char *f) {
153         if (!(arg_flags & SHOW_UNCHANGED))
154                 return 0;
155
156         printf("[UNCHANGED]  %s\n", f);
157         return 1;
158 }
159
160 static int found_override(const char *top, const char *bottom) {
161         _cleanup_free_ char *dest = NULL;
162         int k;
163         pid_t pid;
164
165         assert(top);
166         assert(bottom);
167
168         if (null_or_empty_path(top) > 0)
169                 return notify_override_masked(top, bottom);
170
171         k = readlink_malloc(top, &dest);
172         if (k >= 0) {
173                 if (equivalent(dest, bottom) > 0)
174                         return notify_override_equivalent(top, bottom);
175                 else
176                         return notify_override_redirected(top, bottom);
177         }
178
179         k = notify_override_overridden(top, bottom);
180         if (!arg_diff)
181                 return k;
182
183         putchar('\n');
184
185         fflush(stdout);
186
187         pid = fork();
188         if (pid < 0) {
189                 log_error("Failed to fork off diff: %m");
190                 return -errno;
191         } else if (pid == 0) {
192                 execlp("diff", "diff", "-us", "--", bottom, top, NULL);
193                 log_error("Failed to execute diff: %m");
194                 _exit(1);
195         }
196
197         wait_for_terminate(pid, NULL);
198
199         putchar('\n');
200
201         return k;
202 }
203
204 static int enumerate_dir_d(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *toppath, const char *drop) {
205         _cleanup_free_ char *unit = NULL;
206         _cleanup_free_ char *path = NULL;
207         _cleanup_strv_free_ char **list = NULL;
208         char **file;
209         char *c;
210         int r;
211
212         assert(!endswith(drop, "/"));
213
214         path = strjoin(toppath, "/", drop, NULL);
215         if (!path)
216                 return -ENOMEM;
217
218         log_debug("Looking at %s", path);
219
220         unit = strdup(drop);
221         if (!unit)
222                 return -ENOMEM;
223
224         c = strrchr(unit, '.');
225         if (!c)
226                 return -EINVAL;
227         *c = 0;
228
229         r = get_files_in_directory(path, &list);
230         if (r < 0){
231                 log_error("Failed to enumerate %s: %s", path, strerror(-r));
232                 return r;
233         }
234
235         STRV_FOREACH(file, list) {
236                 Hashmap *h;
237                 int k;
238                 char *p;
239                 char *d;
240
241                 if (!endswith(*file, ".conf"))
242                         continue;
243
244                 p = strjoin(path, "/", *file, NULL);
245                 if (!p)
246                         return -ENOMEM;
247                 d = p + strlen(toppath) + 1;
248
249                 log_debug("Adding at top: %s %s %s", d, draw_special_char(DRAW_ARROW), p);
250                 k = hashmap_put(top, d, p);
251                 if (k >= 0) {
252                         p = strdup(p);
253                         if (!p)
254                                 return -ENOMEM;
255                         d = p + strlen(toppath) + 1;
256                 } else if (k != -EEXIST) {
257                         free(p);
258                         return k;
259                 }
260
261                 log_debug("Adding at bottom: %s %s %s", d, draw_special_char(DRAW_ARROW), p);
262                 free(hashmap_remove(bottom, d));
263                 k = hashmap_put(bottom, d, p);
264                 if (k < 0) {
265                         free(p);
266                         return k;
267                 }
268
269                 h = hashmap_get(drops, unit);
270                 if (!h) {
271                         h = hashmap_new(string_hash_func, string_compare_func);
272                         if (!h)
273                                 return -ENOMEM;
274                         hashmap_put(drops, unit, h);
275                         unit = strdup(unit);
276                         if (!unit)
277                                 return -ENOMEM;
278                 }
279
280                 p = strdup(p);
281                 if (!p)
282                         return -ENOMEM;
283
284                 log_debug("Adding to drops: %s %s %s %s %s",
285                           unit, draw_special_char(DRAW_ARROW), basename(p), draw_special_char(DRAW_ARROW), p);
286                 k = hashmap_put(h, basename(p), p);
287                 if (k < 0) {
288                         free(p);
289                         if (k != -EEXIST)
290                                 return k;
291                 }
292         }
293         return 0;
294 }
295
296 static int enumerate_dir(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *path, bool dropins) {
297         _cleanup_closedir_ DIR *d;
298
299         assert(top);
300         assert(bottom);
301         assert(drops);
302         assert(path);
303
304         log_debug("Looking at %s", path);
305
306         d = opendir(path);
307         if (!d) {
308                 if (errno == ENOENT)
309                         return 0;
310
311                 log_error("Failed to open %s: %m", path);
312                 return -errno;
313         }
314
315         for (;;) {
316                 struct dirent *de;
317                 int k;
318                 char *p;
319
320                 errno = 0;
321                 de = readdir(d);
322                 if (!de)
323                         return -errno;
324
325                 dirent_ensure_type(d, de);
326
327                 if (dropins && de->d_type == DT_DIR && endswith(de->d_name, ".d"))
328                         enumerate_dir_d(top, bottom, drops, path, de->d_name);
329
330                 if (!dirent_is_file(de))
331                         continue;
332
333                 p = strjoin(path, "/", de->d_name, NULL);
334                 if (!p)
335                         return -ENOMEM;
336
337                 log_debug("Adding at top: %s %s %s", basename(p), draw_special_char(DRAW_ARROW), p);
338                 k = hashmap_put(top, basename(p), p);
339                 if (k >= 0) {
340                         p = strdup(p);
341                         if (!p)
342                                 return -ENOMEM;
343                 } else if (k != -EEXIST) {
344                         free(p);
345                         return k;
346                 }
347
348                 log_debug("Adding at bottom: %s %s %s", basename(p), draw_special_char(DRAW_ARROW), p);
349                 free(hashmap_remove(bottom, basename(p)));
350                 k = hashmap_put(bottom, basename(p), p);
351                 if (k < 0) {
352                         free(p);
353                         return k;
354                 }
355         }
356 }
357
358 static int process_suffix(const char *suffix, const char *onlyprefix) {
359         const char *p;
360         char *f;
361         Hashmap *top, *bottom, *drops;
362         Hashmap *h;
363         char *key;
364         int r = 0, k;
365         Iterator i, j;
366         int n_found = 0;
367         bool dropins;
368
369         assert(suffix);
370         assert(!startswith(suffix, "/"));
371         assert(!strstr(suffix, "//"));
372
373         dropins = nulstr_contains(have_dropins, suffix);
374
375         top = hashmap_new(string_hash_func, string_compare_func);
376         bottom = hashmap_new(string_hash_func, string_compare_func);
377         drops = hashmap_new(string_hash_func, string_compare_func);
378         if (!top || !bottom || !drops) {
379                 r = -ENOMEM;
380                 goto finish;
381         }
382
383         NULSTR_FOREACH(p, prefixes) {
384                 _cleanup_free_ char *t = NULL;
385
386                 t = strjoin(p, "/", suffix, NULL);
387                 if (!t) {
388                         r = -ENOMEM;
389                         goto finish;
390                 }
391
392                 k = enumerate_dir(top, bottom, drops, t, dropins);
393                 if (r == 0)
394                         r = k;
395         }
396
397         HASHMAP_FOREACH_KEY(f, key, top, i) {
398                 char *o;
399
400                 o = hashmap_get(bottom, key);
401                 assert(o);
402
403                 if (!onlyprefix || startswith(o, onlyprefix)) {
404                         if (path_equal(o, f)) {
405                                 notify_override_unchanged(f);
406                         } else {
407                                 k = found_override(f, o);
408                                 if (k < 0)
409                                         r = k;
410                                 else
411                                         n_found += k;
412                         }
413                 }
414
415                 h = hashmap_get(drops, key);
416                 if (h)
417                         HASHMAP_FOREACH(o, h, j)
418                                 if (!onlyprefix || startswith(o, onlyprefix))
419                                         n_found += notify_override_extended(f, o);
420         }
421
422 finish:
423         if (top)
424                 hashmap_free_free(top);
425         if (bottom)
426                 hashmap_free_free(bottom);
427         if (drops) {
428                 HASHMAP_FOREACH_KEY(h, key, drops, i){
429                         hashmap_free_free(hashmap_remove(drops, key));
430                         hashmap_remove(drops, key);
431                         free(key);
432                 }
433                 hashmap_free(drops);
434         }
435         return r < 0 ? r : n_found;
436 }
437
438 static int process_suffixes(const char *onlyprefix) {
439         const char *n;
440         int n_found = 0, r;
441
442         NULSTR_FOREACH(n, suffixes) {
443                 r = process_suffix(n, onlyprefix);
444                 if (r < 0)
445                         return r;
446                 else
447                         n_found += r;
448         }
449         return n_found;
450 }
451
452 static int process_suffix_chop(const char *arg) {
453         const char *p;
454
455         assert(arg);
456
457         if (!path_is_absolute(arg))
458                 return process_suffix(arg, NULL);
459
460         /* Strip prefix from the suffix */
461         NULSTR_FOREACH(p, prefixes) {
462                 const char *suffix = startswith(arg, p);
463                 if (suffix) {
464                         suffix += strspn(suffix, "/");
465                         if (*suffix)
466                                 return process_suffix(suffix, NULL);
467                         else
468                                 return process_suffixes(arg);
469                 }
470         }
471
472         log_error("Invalid suffix specification %s.", arg);
473         return -EINVAL;
474 }
475
476 static void help(void) {
477         printf("%s [OPTIONS...] [SUFFIX...]\n\n"
478                "Find overridden configuration files.\n\n"
479                "  -h --help           Show this help\n"
480                "     --version        Show package version\n"
481                "     --no-pager       Do not pipe output into a pager\n"
482                "     --diff[=1|0]     Show a diff when overridden files differ\n"
483                "  -t --type=LIST...   Only display a selected set of override types\n"
484                , program_invocation_short_name);
485 }
486
487 static int parse_flags(const char *flag_str, int flags) {
488         const char *word, *state;
489         size_t l;
490
491         FOREACH_WORD(word, l, flag_str, state) {
492                 if (strneq("masked", word, l))
493                         flags |= SHOW_MASKED;
494                 else if (strneq ("equivalent", word, l))
495                         flags |= SHOW_EQUIVALENT;
496                 else if (strneq("redirected", word, l))
497                         flags |= SHOW_REDIRECTED;
498                 else if (strneq("overridden", word, l))
499                         flags |= SHOW_OVERRIDDEN;
500                 else if (strneq("unchanged", word, l))
501                         flags |= SHOW_UNCHANGED;
502                 else if (strneq("extended", word, l))
503                         flags |= SHOW_EXTENDED;
504                 else if (strneq("default", word, l))
505                         flags |= SHOW_DEFAULTS;
506                 else
507                         return -EINVAL;
508         }
509         return flags;
510 }
511
512 static int parse_argv(int argc, char *argv[]) {
513
514         enum {
515                 ARG_NO_PAGER = 0x100,
516                 ARG_DIFF,
517                 ARG_VERSION
518         };
519
520         static const struct option options[] = {
521                 { "help",      no_argument,       NULL, 'h'          },
522                 { "version",   no_argument,       NULL, ARG_VERSION  },
523                 { "no-pager",  no_argument,       NULL, ARG_NO_PAGER },
524                 { "diff",      optional_argument, NULL, ARG_DIFF     },
525                 { "type",      required_argument, NULL, 't'          },
526                 {}
527         };
528
529         int c;
530
531         assert(argc >= 1);
532         assert(argv);
533
534         while ((c = getopt_long(argc, argv, "ht:", options, NULL)) >= 0)
535
536                 switch (c) {
537
538                 case 'h':
539                         help();
540                         return 0;
541
542                 case ARG_VERSION:
543                         puts(PACKAGE_STRING);
544                         puts(SYSTEMD_FEATURES);
545                         return 0;
546
547                 case ARG_NO_PAGER:
548                         arg_no_pager = true;
549                         break;
550
551                 case 't': {
552                         int f;
553                         f = parse_flags(optarg, arg_flags);
554                         if (f < 0) {
555                                 log_error("Failed to parse flags field.");
556                                 return -EINVAL;
557                         }
558                         arg_flags = f;
559                         break;
560                 }
561
562                 case ARG_DIFF:
563                         if (!optarg)
564                                 arg_diff = 1;
565                         else {
566                                 int b;
567
568                                 b = parse_boolean(optarg);
569                                 if (b < 0) {
570                                         log_error("Failed to parse diff boolean.");
571                                         return -EINVAL;
572                                 } else if (b)
573                                         arg_diff = 1;
574                                 else
575                                         arg_diff = 0;
576                         }
577                         break;
578
579                 case '?':
580                         return -EINVAL;
581
582                 default:
583                         assert_not_reached("Unhandled option");
584                 }
585
586         return 1;
587 }
588
589 int main(int argc, char *argv[]) {
590         int r = 0, k;
591         int n_found = 0;
592
593         log_parse_environment();
594         log_open();
595
596         r = parse_argv(argc, argv);
597         if (r <= 0)
598                 goto finish;
599
600         if (arg_flags == 0)
601                 arg_flags = SHOW_DEFAULTS;
602
603         if (arg_diff < 0)
604                 arg_diff = !!(arg_flags & SHOW_OVERRIDDEN);
605         else if (arg_diff)
606                 arg_flags |= SHOW_OVERRIDDEN;
607
608         pager_open_if_enabled();
609
610         if (optind < argc) {
611                 int i;
612
613                 for (i = optind; i < argc; i++) {
614                         path_kill_slashes(argv[i]);
615                         k = process_suffix_chop(argv[i]);
616                         if (k < 0)
617                                 r = k;
618                         else
619                                 n_found += k;
620                 }
621
622         } else {
623                 k = process_suffixes(NULL);
624                 if (k < 0)
625                         r = k;
626                 else
627                         n_found += k;
628         }
629
630         if (r >= 0)
631                 printf("%s%i overridden configuration files found.\n",
632                        n_found ? "\n" : "", n_found);
633
634 finish:
635         pager_close();
636
637         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
638 }