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