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