chiark / gitweb /
delta.c: use _cleanup_
[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, "h", 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(DISTRIBUTION);
366                         puts(SYSTEMD_FEATURES);
367                         return 0;
368
369                 case ARG_NO_PAGER:
370                         arg_no_pager = true;
371                         break;
372
373                 case '?':
374                         return -EINVAL;
375
376                 case 't': {
377                         int f;
378                         f = parse_flags(optarg, arg_flags);
379                         if (f < 0) {
380                                 log_error("Failed to parse flags field.");
381                                 return -EINVAL;
382                         }
383                         arg_flags = f;
384                         break;
385                 }
386
387                 case ARG_DIFF:
388                         if (!optarg)
389                                 arg_diff = 1;
390                         else {
391                                 int b;
392
393                                 b = parse_boolean(optarg);
394                                 if (b < 0) {
395                                         log_error("Failed to parse diff boolean.");
396                                         return -EINVAL;
397                                 } else if (b)
398                                         arg_diff = 1;
399                                 else
400                                         arg_diff = 0;
401                         }
402                         break;
403
404                 default:
405                         log_error("Unknown option code %c", c);
406                         return -EINVAL;
407                 }
408         }
409
410         return 1;
411 }
412
413 int main(int argc, char *argv[]) {
414
415         const char prefixes[] =
416                 "/etc\0"
417                 "/run\0"
418                 "/usr/local/lib\0"
419                 "/usr/local/share\0"
420                 "/usr/lib\0"
421                 "/usr/share\0"
422 #ifdef HAVE_SPLIT_USR
423                 "/lib\0"
424 #endif
425                 ;
426
427         const char suffixes[] =
428                 "sysctl.d\0"
429                 "tmpfiles.d\0"
430                 "modules-load.d\0"
431                 "binfmt.d\0"
432                 "systemd/system\0"
433                 "systemd/user\0"
434                 "systemd/system-preset\0"
435                 "systemd/user-preset\0"
436                 "udev/rules.d\0"
437                 "modprobe.d\0";
438
439         int r = 0, k;
440         int n_found = 0;
441
442         log_parse_environment();
443         log_open();
444
445         r = parse_argv(argc, argv);
446         if (r <= 0)
447                 goto finish;
448
449         if (arg_flags == 0)
450                 arg_flags = SHOW_DEFAULTS;
451
452         if (arg_diff < 0)
453                 arg_diff = !!(arg_flags & SHOW_OVERRIDDEN);
454         else if (arg_diff)
455                 arg_flags |= SHOW_OVERRIDDEN;
456
457         if (!arg_no_pager)
458                 pager_open();
459
460         if (optind < argc) {
461                 int i;
462
463                 for (i = optind; i < argc; i++) {
464                         k = process_suffix_chop(prefixes, argv[i]);
465                         if (k < 0)
466                                 r = k;
467                         else
468                                 n_found += k;
469                 }
470
471         } else {
472                 const char *n;
473
474                 NULSTR_FOREACH(n, suffixes) {
475                         k = process_suffix(prefixes, n);
476                         if (k < 0)
477                                 r = k;
478                         else
479                                 n_found += k;
480                 }
481         }
482
483         if (r >= 0)
484                 printf("\n%i overridden configuration files found.\n", n_found);
485
486 finish:
487         pager_close();
488
489         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
490 }