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