chiark / gitweb /
systemd-delta: Only print colors when on a tty
[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("%s%s%s     %s → %s\n",
70                ansi_highlight_red(), "[MASKED]", ansi_highlight_off(), top, bottom);
71         return 1;
72 }
73
74 static int notify_override_equivalent(const char *top, const char *bottom) {
75         if (!(arg_flags & SHOW_EQUIVALENT))
76                 return 0;
77
78         printf("%s%s%s %s → %s\n",
79                ansi_highlight_green(), "[EQUIVALENT]", ansi_highlight(), top, bottom);
80         return 1;
81 }
82
83 static int notify_override_redirected(const char *top, const char *bottom) {
84         if (!(arg_flags & SHOW_REDIRECTED))
85                 return 0;
86
87         printf("%s%s%s   %s → %s\n",
88                ansi_highlight(), "[REDIRECTED]", ansi_highlight_off(), top, bottom);
89         return 1;
90 }
91
92 static int notify_override_overridden(const char *top, const char *bottom) {
93         if (!(arg_flags & SHOW_OVERRIDDEN))
94                 return 0;
95
96         printf("%s%s%s %s → %s\n",
97                ansi_highlight(), "[OVERRIDDEN]", ansi_highlight_off(), top, bottom);
98         return 1;
99 }
100
101 static int notify_override_extended(const char *top, const char *bottom) {
102         if (!(arg_flags & SHOW_EXTENDED))
103                return 0;
104
105         printf("%s%s%s   %s → %s\n",
106                ansi_highlight(), "[EXTENDED]", ansi_highlight_off(), top, bottom);
107         return 1;
108 }
109
110 static int notify_override_unchanged(const char *f) {
111         if (!(arg_flags & SHOW_UNCHANGED))
112                 return 0;
113
114         printf("[UNCHANGED]  %s\n", f);
115         return 1;
116 }
117
118 static int found_override(const char *top, const char *bottom) {
119         _cleanup_free_ char *dest = NULL;
120         int k;
121         pid_t pid;
122
123         assert(top);
124         assert(bottom);
125
126         if (null_or_empty_path(top) > 0)
127                 return notify_override_masked(top, bottom);
128
129         k = readlink_malloc(top, &dest);
130         if (k >= 0) {
131                 if (equivalent(dest, bottom) > 0)
132                         return notify_override_equivalent(top, bottom);
133                 else
134                         return notify_override_redirected(top, bottom);
135         }
136
137         k = notify_override_overridden(top, bottom);
138         if (!arg_diff)
139                 return k;
140
141         putchar('\n');
142
143         fflush(stdout);
144
145         pid = fork();
146         if (pid < 0) {
147                 log_error("Failed to fork off diff: %m");
148                 return -errno;
149         } else if (pid == 0) {
150                 execlp("diff", "diff", "-us", "--", bottom, top, NULL);
151                 log_error("Failed to execute diff: %m");
152                 _exit(1);
153         }
154
155         wait_for_terminate(pid, NULL);
156
157         putchar('\n');
158
159         return k;
160 }
161
162 static int enumerate_dir_d(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *toppath, const char *drop) {
163         _cleanup_free_ char *conf = NULL;
164         _cleanup_free_ char *path = NULL;
165         _cleanup_strv_free_ char **list = NULL;
166         char **file;
167         char *c;
168         int r;
169
170         path = strjoin(toppath, "/", drop, NULL);
171         if (!path)
172                 return -ENOMEM;
173
174         path_kill_slashes(path);
175
176         conf = strdup(drop);
177         if (!conf)
178                 return -ENOMEM;
179
180         c = strrchr(conf, '.');
181         if (!c)
182                 return -EINVAL;
183         *c = 0;
184
185         r = get_files_in_directory(path, &list);
186         if (r < 0){
187                 log_error("Failed to enumerate %s: %s", path, strerror(-r));
188                 return r;
189         }
190
191         STRV_FOREACH(file, list) {
192                 Hashmap *h;
193                 int k;
194                 char *p;
195                 char *d;
196
197                 if (!endswith(*file, ".conf"))
198                         continue;
199
200                 p = strjoin(path, "/", *file, NULL);
201                 if (!p)
202                         return -ENOMEM;
203
204                 path_kill_slashes(p);
205
206                 d = strrchr(p, '/');
207                 if (!d || d == p) {
208                         free(p);
209                         return -EINVAL;
210                 }
211                 d--;
212                 d = strrchr(p, '/');
213
214                 if (!d || d == p) {
215                         free(p);
216                         return -EINVAL;
217                 }
218
219                 k = hashmap_put(top, d, p);
220                 if (k >= 0) {
221                         p = strdup(p);
222                         if (!p)
223                                 return -ENOMEM;
224                         d = strrchr(p, '/');
225                         d--;
226                         d = strrchr(p, '/');
227                 } else if (k != -EEXIST) {
228                         free(p);
229                         return k;
230                 }
231
232                 free(hashmap_remove(bottom, d));
233                 k = hashmap_put(bottom, d, p);
234                 if (k < 0) {
235                         free(p);
236                         return k;
237                 }
238
239                 h = hashmap_get(drops, conf);
240                 if (!h) {
241                         h = hashmap_new(string_hash_func, string_compare_func);
242                         if (!h)
243                                 return -ENOMEM;
244                         hashmap_put(drops, conf, h);
245                         conf = strdup(conf);
246                         if (!conf)
247                                 return -ENOMEM;
248                 }
249
250                 p = strdup(p);
251                 if (!p)
252                         return -ENOMEM;
253
254                 k = hashmap_put(h, path_get_file_name(p), p);
255                 if (k < 0) {
256                         free(p);
257                         if (k != -EEXIST)
258                                 return k;
259                 }
260         }
261         return 0;
262 }
263
264 static int enumerate_dir(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *path, bool dropins) {
265         _cleanup_closedir_ DIR *d;
266
267         assert(top);
268         assert(bottom);
269         assert(drops);
270         assert(path);
271
272         d = opendir(path);
273         if (!d) {
274                 if (errno == ENOENT)
275                         return 0;
276
277                 log_error("Failed to enumerate %s: %m", path);
278                 return -errno;
279         }
280
281         for (;;) {
282                 struct dirent *de;
283                 union dirent_storage buf;
284                 int k;
285                 char *p;
286
287                 k = readdir_r(d, &buf.de, &de);
288                 if (k != 0)
289                         return -k;
290
291                 if (!de)
292                         break;
293
294                 if (dropins && de->d_type == DT_DIR && endswith(de->d_name, ".d"))
295                         enumerate_dir_d(top, bottom, drops, path, de->d_name);
296
297                 if (!dirent_is_file(de))
298                         continue;
299
300                 p = strjoin(path, "/", de->d_name, NULL);
301                 if (!p)
302                         return -ENOMEM;
303
304                 path_kill_slashes(p);
305
306                 k = hashmap_put(top, path_get_file_name(p), p);
307                 if (k >= 0) {
308                         p = strdup(p);
309                         if (!p)
310                                 return -ENOMEM;
311                 } else if (k != -EEXIST) {
312                         free(p);
313                         return k;
314                 }
315
316                 free(hashmap_remove(bottom, path_get_file_name(p)));
317                 k = hashmap_put(bottom, path_get_file_name(p), p);
318                 if (k < 0) {
319                         free(p);
320                         return k;
321                 }
322         }
323
324         return 0;
325 }
326
327 static int process_suffix(const char *prefixes, const char *suffix, bool dropins) {
328         const char *p;
329         char *f;
330         Hashmap *top, *bottom=NULL, *drops=NULL;
331         Hashmap *h;
332         char *key;
333         int r = 0, k;
334         Iterator i, j;
335         int n_found = 0;
336
337         assert(prefixes);
338         assert(suffix);
339
340         top = hashmap_new(string_hash_func, string_compare_func);
341         if (!top) {
342                 r = -ENOMEM;
343                 goto finish;
344         }
345
346         bottom = hashmap_new(string_hash_func, string_compare_func);
347         if (!bottom) {
348                 r = -ENOMEM;
349                 goto finish;
350         }
351
352         drops = hashmap_new(string_hash_func, string_compare_func);
353         if (!drops) {
354                 r = -ENOMEM;
355                 goto finish;
356         }
357
358         NULSTR_FOREACH(p, prefixes) {
359                 _cleanup_free_ char *t = NULL;
360
361                 t = strjoin(p, "/", suffix, NULL);
362                 if (!t) {
363                         r = -ENOMEM;
364                         goto finish;
365                 }
366
367                 k = enumerate_dir(top, bottom, drops, t, dropins);
368                 if (k < 0)
369                         r = k;
370
371                 log_debug("Looking at %s", t);
372         }
373
374         HASHMAP_FOREACH_KEY(f, key, top, i) {
375                 char *o;
376
377                 o = hashmap_get(bottom, key);
378                 assert(o);
379
380                 if (path_equal(o, f))
381                         notify_override_unchanged(f);
382                 else {
383                         k = found_override(f, o);
384                         if (k < 0)
385                                 r = k;
386                         else
387                                 n_found += k;
388                 }
389
390                 h = hashmap_get(drops, key);
391                 if (h)
392                         HASHMAP_FOREACH(o, h, j)
393                                 n_found += notify_override_extended(f, o);
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("%s%i overridden configuration files found.\n",
626                        n_found ? "\n" : "", n_found);
627
628 finish:
629         pager_close();
630
631         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
632 }