chiark / gitweb /
ptyfwd: Don't set the output prop of stdin, nor the input props of stdout.
[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, basename(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, basename(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, basename(p)));
317                 k = hashmap_put(bottom, basename(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 int 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         return 0;
446 }
447
448 static int parse_flags(const char *flag_str, int flags) {
449         char *w, *state;
450         size_t l;
451
452         FOREACH_WORD(w, l, flag_str, state) {
453                 if (strneq("masked", w, l))
454                         flags |= SHOW_MASKED;
455                 else if (strneq ("equivalent", w, l))
456                         flags |= SHOW_EQUIVALENT;
457                 else if (strneq("redirected", w, l))
458                         flags |= SHOW_REDIRECTED;
459                 else if (strneq("overridden", w, l))
460                         flags |= SHOW_OVERRIDDEN;
461                 else if (strneq("unchanged", w, l))
462                         flags |= SHOW_UNCHANGED;
463                 else if (strneq("extended", w, l))
464                         flags |= SHOW_EXTENDED;
465                 else if (strneq("default", w, l))
466                         flags |= SHOW_DEFAULTS;
467                 else
468                         return -EINVAL;
469         }
470         return flags;
471 }
472
473 static int parse_argv(int argc, char *argv[]) {
474
475         enum {
476                 ARG_NO_PAGER = 0x100,
477                 ARG_DIFF,
478                 ARG_VERSION
479         };
480
481         static const struct option options[] = {
482                 { "help",      no_argument,       NULL, 'h'          },
483                 { "version",   no_argument,       NULL, ARG_VERSION  },
484                 { "no-pager",  no_argument,       NULL, ARG_NO_PAGER },
485                 { "diff",      optional_argument, NULL, ARG_DIFF     },
486                 { "type",      required_argument, NULL, 't'          },
487                 {}
488         };
489
490         int c;
491
492         assert(argc >= 1);
493         assert(argv);
494
495         while ((c = getopt_long(argc, argv, "ht:", options, NULL)) >= 0) {
496
497                 switch (c) {
498
499                 case 'h':
500                         help();
501                         return 0;
502
503                 case ARG_VERSION:
504                         puts(PACKAGE_STRING);
505                         puts(SYSTEMD_FEATURES);
506                         return 0;
507
508                 case ARG_NO_PAGER:
509                         arg_no_pager = true;
510                         break;
511
512                 case 't': {
513                         int f;
514                         f = parse_flags(optarg, arg_flags);
515                         if (f < 0) {
516                                 log_error("Failed to parse flags field.");
517                                 return -EINVAL;
518                         }
519                         arg_flags = f;
520                         break;
521                 }
522
523                 case ARG_DIFF:
524                         if (!optarg)
525                                 arg_diff = 1;
526                         else {
527                                 int b;
528
529                                 b = parse_boolean(optarg);
530                                 if (b < 0) {
531                                         log_error("Failed to parse diff boolean.");
532                                         return -EINVAL;
533                                 } else if (b)
534                                         arg_diff = 1;
535                                 else
536                                         arg_diff = 0;
537                         }
538                         break;
539
540                 case '?':
541                         return -EINVAL;
542
543                 default:
544                         assert_not_reached("Unhandled option");
545                 }
546         }
547
548         return 1;
549 }
550
551 int main(int argc, char *argv[]) {
552
553         const char prefixes[] =
554                 "/etc\0"
555                 "/run\0"
556                 "/usr/local/lib\0"
557                 "/usr/local/share\0"
558                 "/usr/lib\0"
559                 "/usr/share\0"
560 #ifdef HAVE_SPLIT_USR
561                 "/lib\0"
562 #endif
563                 ;
564
565         const char suffixes[] =
566                 "sysctl.d\0"
567                 "tmpfiles.d\0"
568                 "modules-load.d\0"
569                 "binfmt.d\0"
570                 "systemd/system\0"
571                 "systemd/user\0"
572                 "systemd/system-preset\0"
573                 "systemd/user-preset\0"
574                 "udev/rules.d\0"
575                 "modprobe.d\0";
576
577         const char have_dropins[] =
578                 "systemd/system\0"
579                 "systemd/user\0";
580
581         int r = 0, k;
582         int n_found = 0;
583
584         log_parse_environment();
585         log_open();
586
587         r = parse_argv(argc, argv);
588         if (r <= 0)
589                 goto finish;
590
591         if (arg_flags == 0)
592                 arg_flags = SHOW_DEFAULTS;
593
594         if (arg_diff < 0)
595                 arg_diff = !!(arg_flags & SHOW_OVERRIDDEN);
596         else if (arg_diff)
597                 arg_flags |= SHOW_OVERRIDDEN;
598
599         if (!arg_no_pager)
600                 pager_open(false);
601
602         if (optind < argc) {
603                 int i;
604
605                 for (i = optind; i < argc; i++) {
606                         k = process_suffix_chop(prefixes, argv[i], have_dropins);
607                         if (k < 0)
608                                 r = k;
609                         else
610                                 n_found += k;
611                 }
612
613         } else {
614                 const char *n;
615
616                 NULSTR_FOREACH(n, suffixes) {
617                         k = process_suffix(prefixes, n, nulstr_contains(have_dropins, n));
618                         if (k < 0)
619                                 r = k;
620                         else
621                                 n_found += k;
622                 }
623         }
624
625         if (r >= 0)
626                 printf("%s%i overridden configuration files found.\n",
627                        n_found ? "\n" : "", n_found);
628
629 finish:
630         pager_close();
631
632         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
633 }