chiark / gitweb /
Merge nss-myhostname
[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
35 static bool arg_no_pager = false;
36 static int arg_diff = -1;
37
38 static enum {
39         SHOW_MASKED = 1 << 0,
40         SHOW_EQUIVALENT = 1 << 1,
41         SHOW_REDIRECTED = 1 << 2,
42         SHOW_OVERRIDDEN = 1 << 3,
43         SHOW_UNCHANGED = 1 << 4,
44
45         SHOW_DEFAULTS =
46         (SHOW_MASKED | SHOW_EQUIVALENT | SHOW_REDIRECTED | SHOW_OVERRIDDEN)
47 } arg_flags = 0;
48
49 static int equivalent(const char *a, const char *b) {
50         _cleanup_free_ char *x = NULL, *y = NULL;
51
52         x = canonicalize_file_name(a);
53         if (!x)
54                 return -errno;
55
56         y = canonicalize_file_name(b);
57         if (!y)
58                 return -errno;
59
60         return path_equal(x, y);
61 }
62
63 static int notify_override_masked(const char *top, const char *bottom) {
64         if (!(arg_flags & SHOW_MASKED))
65                 return 0;
66
67         printf(ANSI_HIGHLIGHT_RED_ON "[MASKED]" ANSI_HIGHLIGHT_OFF "     %s → %s\n", top, bottom);
68         return 1;
69 }
70
71 static int notify_override_equivalent(const char *top, const char *bottom) {
72         if (!(arg_flags & SHOW_EQUIVALENT))
73                 return 0;
74
75         printf(ANSI_HIGHLIGHT_GREEN_ON "[EQUIVALENT]" ANSI_HIGHLIGHT_OFF " %s → %s\n", top, bottom);
76         return 1;
77 }
78
79 static int notify_override_redirected(const char *top, const char *bottom) {
80         if (!(arg_flags & SHOW_REDIRECTED))
81                 return 0;
82
83         printf(ANSI_HIGHLIGHT_ON "[REDIRECTED]" ANSI_HIGHLIGHT_OFF "   %s → %s\n", top, bottom);
84         return 1;
85 }
86
87 static int notify_override_overridden(const char *top, const char *bottom) {
88         if (!(arg_flags & SHOW_OVERRIDDEN))
89                 return 0;
90
91         printf(ANSI_HIGHLIGHT_ON "[OVERRIDDEN]" ANSI_HIGHLIGHT_OFF " %s → %s\n", top, bottom);
92         return 1;
93 }
94
95 static int notify_override_unchanged(const char *f) {
96         if (!(arg_flags & SHOW_UNCHANGED))
97                 return 0;
98
99         printf("[UNCHANGED]  %s\n", f);
100         return 1;
101 }
102
103 static int found_override(const char *top, const char *bottom) {
104         _cleanup_free_ char *dest = NULL;
105         int k;
106         pid_t pid;
107
108         assert(top);
109         assert(bottom);
110
111         if (null_or_empty_path(top) > 0) {
112                 notify_override_masked(top, bottom);
113                 return 0;
114         }
115
116         k = readlink_malloc(top, &dest);
117         if (k >= 0) {
118                 if (equivalent(dest, bottom) > 0)
119                         notify_override_equivalent(top, bottom);
120                 else
121                         notify_override_redirected(top, bottom);
122
123                 return 0;
124         }
125
126         notify_override_overridden(top, bottom);
127         if (!arg_diff)
128                 return 0;
129
130         putchar('\n');
131
132         fflush(stdout);
133
134         pid = fork();
135         if (pid < 0) {
136                 log_error("Failed to fork off diff: %m");
137                 return -errno;
138         } else if (pid == 0) {
139                 execlp("diff", "diff", "-us", "--", bottom, top, NULL);
140                 log_error("Failed to execute diff: %m");
141                 _exit(1);
142         }
143
144         wait_for_terminate(pid, NULL);
145
146         putchar('\n');
147
148         return 0;
149 }
150
151 static int enumerate_dir(Hashmap *top, Hashmap *bottom, const char *path) {
152         _cleanup_closedir_ DIR *d;
153
154         assert(top);
155         assert(bottom);
156         assert(path);
157
158         d = opendir(path);
159         if (!d) {
160                 if (errno == ENOENT)
161                         return 0;
162
163                 log_error("Failed to enumerate %s: %m", path);
164                 return -errno;
165         }
166
167         for (;;) {
168                 struct dirent *de;
169                 union dirent_storage buf;
170                 int k;
171                 char *p;
172
173                 k = readdir_r(d, &buf.de, &de);
174                 if (k != 0)
175                         return -k;
176
177                 if (!de)
178                         break;
179
180                 if (!dirent_is_file(de))
181                         continue;
182
183                 p = strjoin(path, "/", de->d_name, NULL);
184                 if (!p)
185                         return -ENOMEM;
186
187                 path_kill_slashes(p);
188
189                 k = hashmap_put(top, path_get_file_name(p), p);
190                 if (k >= 0) {
191                         p = strdup(p);
192                         if (!p)
193                                 return -ENOMEM;
194                 } else if (k != -EEXIST) {
195                         free(p);
196                         return k;
197                 }
198
199                 free(hashmap_remove(bottom, path_get_file_name(p)));
200                 k = hashmap_put(bottom, path_get_file_name(p), p);
201                 if (k < 0) {
202                         free(p);
203                         return k;
204                 }
205         }
206
207         return 0;
208 }
209
210 static int process_suffix(const char *prefixes, const char *suffix) {
211         const char *p;
212         char *f;
213         Hashmap *top, *bottom=NULL;
214         int r = 0, k;
215         Iterator i;
216         int n_found = 0;
217
218         assert(prefixes);
219         assert(suffix);
220
221         top = hashmap_new(string_hash_func, string_compare_func);
222         if (!top) {
223                 r = -ENOMEM;
224                 goto finish;
225         }
226
227         bottom = hashmap_new(string_hash_func, string_compare_func);
228         if (!bottom) {
229                 r = -ENOMEM;
230                 goto finish;
231         }
232
233         NULSTR_FOREACH(p, prefixes) {
234                 _cleanup_free_ char *t = NULL;
235
236                 t = strjoin(p, "/", suffix, NULL);
237                 if (!t) {
238                         r = -ENOMEM;
239                         goto finish;
240                 }
241
242                 k = enumerate_dir(top, bottom, t);
243                 if (k < 0)
244                         r = k;
245
246                 log_debug("Looking at %s", t);
247         }
248
249         HASHMAP_FOREACH(f, top, i) {
250                 char *o;
251
252                 o = hashmap_get(bottom, path_get_file_name(f));
253                 assert(o);
254
255                 if (path_equal(o, f)) {
256                         notify_override_unchanged(f);
257                         continue;
258                 }
259
260                 k = found_override(f, o);
261                 if (k < 0)
262                         r = k;
263
264                 n_found ++;
265         }
266
267 finish:
268         if (top)
269                 hashmap_free_free(top);
270         if (bottom)
271                 hashmap_free_free(bottom);
272
273         return r < 0 ? r : n_found;
274 }
275
276 static int process_suffix_chop(const char *prefixes, const char *suffix) {
277         const char *p;
278
279         assert(prefixes);
280         assert(suffix);
281
282         if (!path_is_absolute(suffix))
283                 return process_suffix(prefixes, suffix);
284
285         /* Strip prefix from the suffix */
286         NULSTR_FOREACH(p, prefixes) {
287                 if (startswith(suffix, p)) {
288                         suffix += strlen(p);
289                         suffix += strspn(suffix, "/");
290                         return process_suffix(prefixes, suffix);
291                 }
292         }
293
294         log_error("Invalid suffix specification %s.", suffix);
295         return -EINVAL;
296 }
297
298 static void help(void) {
299
300         printf("%s [OPTIONS...] [SUFFIX...]\n\n"
301                "Find overridden configuration files.\n\n"
302                "  -h --help           Show this help\n"
303                "     --version        Show package version\n"
304                "     --no-pager       Do not pipe output into a pager\n"
305                "     --diff[=1|0]     Show a diff when overridden files differ\n"
306                "  -t --type=LIST...   Only display a selected set of override types\n",
307                program_invocation_short_name);
308 }
309
310 static int parse_flags(const char *flag_str, int flags) {
311         char *w, *state;
312         size_t l;
313
314         FOREACH_WORD(w, l, flag_str, state) {
315                 if (strncmp("masked", w, l) == 0)
316                         flags |= SHOW_MASKED;
317                 else if (strncmp ("equivalent", w, l) == 0)
318                         flags |= SHOW_EQUIVALENT;
319                 else if (strncmp("redirected", w, l) == 0)
320                         flags |= SHOW_REDIRECTED;
321                 else if (strncmp("overridden", w, l) == 0)
322                         flags |= SHOW_OVERRIDDEN;
323                 else if (strncmp("unchanged", w, l) == 0)
324                         flags |= SHOW_UNCHANGED;
325                 else if (strncmp("default", w, l) == 0)
326                         flags |= SHOW_DEFAULTS;
327                 else
328                         return -EINVAL;
329         }
330         return flags;
331 }
332
333 static int parse_argv(int argc, char *argv[]) {
334
335         enum {
336                 ARG_NO_PAGER = 0x100,
337                 ARG_DIFF,
338                 ARG_VERSION
339         };
340
341         static const struct option options[] = {
342                 { "help",      no_argument,       NULL, 'h'          },
343                 { "version",   no_argument,       NULL, ARG_VERSION  },
344                 { "no-pager",  no_argument,       NULL, ARG_NO_PAGER },
345                 { "diff",      optional_argument, NULL, ARG_DIFF     },
346                 { "type",      required_argument, NULL, 't'          },
347                 { NULL,        0,                 NULL, 0            }
348         };
349
350         int c;
351
352         assert(argc >= 1);
353         assert(argv);
354
355         while ((c = getopt_long(argc, argv, "ht:", options, NULL)) >= 0) {
356
357                 switch (c) {
358
359                 case 'h':
360                         help();
361                         return 0;
362
363                 case ARG_VERSION:
364                         puts(PACKAGE_STRING);
365                         puts(SYSTEMD_FEATURES);
366                         return 0;
367
368                 case ARG_NO_PAGER:
369                         arg_no_pager = true;
370                         break;
371
372                 case '?':
373                         return -EINVAL;
374
375                 case 't': {
376                         int f;
377                         f = parse_flags(optarg, arg_flags);
378                         if (f < 0) {
379                                 log_error("Failed to parse flags field.");
380                                 return -EINVAL;
381                         }
382                         arg_flags = f;
383                         break;
384                 }
385
386                 case ARG_DIFF:
387                         if (!optarg)
388                                 arg_diff = 1;
389                         else {
390                                 int b;
391
392                                 b = parse_boolean(optarg);
393                                 if (b < 0) {
394                                         log_error("Failed to parse diff boolean.");
395                                         return -EINVAL;
396                                 } else if (b)
397                                         arg_diff = 1;
398                                 else
399                                         arg_diff = 0;
400                         }
401                         break;
402
403                 default:
404                         log_error("Unknown option code %c", c);
405                         return -EINVAL;
406                 }
407         }
408
409         return 1;
410 }
411
412 int main(int argc, char *argv[]) {
413
414         const char prefixes[] =
415                 "/etc\0"
416                 "/run\0"
417                 "/usr/local/lib\0"
418                 "/usr/local/share\0"
419                 "/usr/lib\0"
420                 "/usr/share\0"
421 #ifdef HAVE_SPLIT_USR
422                 "/lib\0"
423 #endif
424                 ;
425
426         const char suffixes[] =
427                 "sysctl.d\0"
428                 "tmpfiles.d\0"
429                 "modules-load.d\0"
430                 "binfmt.d\0"
431                 "systemd/system\0"
432                 "systemd/user\0"
433                 "systemd/system-preset\0"
434                 "systemd/user-preset\0"
435                 "udev/rules.d\0"
436                 "modprobe.d\0";
437
438         int r = 0, k;
439         int n_found = 0;
440
441         log_parse_environment();
442         log_open();
443
444         r = parse_argv(argc, argv);
445         if (r <= 0)
446                 goto finish;
447
448         if (arg_flags == 0)
449                 arg_flags = SHOW_DEFAULTS;
450
451         if (arg_diff < 0)
452                 arg_diff = !!(arg_flags & SHOW_OVERRIDDEN);
453         else if (arg_diff)
454                 arg_flags |= SHOW_OVERRIDDEN;
455
456         if (!arg_no_pager)
457                 pager_open();
458
459         if (optind < argc) {
460                 int i;
461
462                 for (i = optind; i < argc; i++) {
463                         k = process_suffix_chop(prefixes, argv[i]);
464                         if (k < 0)
465                                 r = k;
466                         else
467                                 n_found += k;
468                 }
469
470         } else {
471                 const char *n;
472
473                 NULSTR_FOREACH(n, suffixes) {
474                         k = process_suffix(prefixes, n);
475                         if (k < 0)
476                                 r = k;
477                         else
478                                 n_found += k;
479                 }
480         }
481
482         if (r >= 0)
483                 printf("\n%i overridden configuration files found.\n", n_found);
484
485 finish:
486         pager_close();
487
488         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
489 }