chiark / gitweb /
delta: add missing files
[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
37 static int equivalent(const char *a, const char *b) {
38         char *x, *y;
39         int r;
40
41         x = canonicalize_file_name(a);
42         if (!x)
43                 return -errno;
44
45         y = canonicalize_file_name(b);
46         if (!y) {
47                 free(x);
48                 return -errno;
49         }
50
51         r = path_equal(x, y);
52         free(x);
53         free(y);
54
55         return r;
56 }
57
58 static int found_override(const char *top, const char *bottom) {
59         char *dest;
60         int k;
61         pid_t pid;
62
63         assert(top);
64         assert(bottom);
65
66         if (null_or_empty_path(top) > 0) {
67                 printf(ANSI_HIGHLIGHT_RED_ON "[MASK]" ANSI_HIGHLIGHT_OFF "       %s → %s\n", top, bottom);
68                 goto finish;
69         }
70
71         k = readlink_malloc(top, &dest);
72         if (k >= 0) {
73                 if (equivalent(dest, bottom) > 0)
74                         printf(ANSI_HIGHLIGHT_GREEN_ON "[EQUIVALENT]" ANSI_HIGHLIGHT_OFF " %s → %s\n", top, bottom);
75                 else
76                         printf(ANSI_HIGHLIGHT_ON "[REDIRECT]" ANSI_HIGHLIGHT_OFF "   %s → %s\n", top, bottom);
77
78                 free(dest);
79                 goto finish;
80         }
81
82         printf(ANSI_HIGHLIGHT_ON "[OVERRIDE]" ANSI_HIGHLIGHT_OFF "   %s → %s\n", top, bottom);
83
84         putchar('\n');
85
86         fflush(stdout);
87
88         pid = fork();
89         if (pid < 0) {
90                 log_error("Failed to fork off diff: %m");
91                 return -errno;
92         } else if (pid == 0) {
93                 execlp("diff", "diff", "-us", "--", bottom, top, NULL);
94                 log_error("Failed to execute diff: %m");
95                 _exit(1);
96         }
97
98         wait_for_terminate(pid, NULL);
99
100         putchar('\n');
101
102 finish:
103
104         return 0;
105 }
106
107 static int enumerate_dir(Hashmap *top, Hashmap *bottom, const char *path) {
108         DIR *d;
109         int r = 0;
110
111         assert(top);
112         assert(bottom);
113         assert(path);
114
115         d = opendir(path);
116         if (!d) {
117                 if (errno == ENOENT)
118                         return 0;
119
120                 log_error("Failed to enumerate %s: %m", path);
121                 return -errno;
122         }
123
124         for (;;) {
125                 struct dirent *de, buf;
126                 int k;
127                 char *p;
128
129                 k = readdir_r(d, &buf, &de);
130                 if (k != 0) {
131                         r = -k;
132                         goto finish;
133                 }
134
135                 if (!de)
136                         break;
137
138                 if (!dirent_is_file(de))
139                         continue;
140
141                 p = join(path, "/", de->d_name, NULL);
142                 if (!p) {
143                         r = -ENOMEM;
144                         goto finish;
145                 }
146
147                 path_kill_slashes(p);
148
149                 k = hashmap_put(top, path_get_file_name(p), p);
150                 if (k >= 0) {
151                         p = strdup(p);
152                         if (!p) {
153                                 r = -ENOMEM;
154                                 goto finish;
155                         }
156                 } else if (k != -EEXIST) {
157                         free(p);
158                         r = k;
159                         goto finish;
160                 }
161
162                 free(hashmap_remove(bottom, path_get_file_name(p)));
163                 k = hashmap_put(bottom, path_get_file_name(p), p);
164                 if (k < 0) {
165                         free(p);
166                         r = k;
167                         goto finish;
168                 }
169         }
170
171 finish:
172         closedir(d);
173
174         return r;
175 }
176
177 static int process_suffix(const char *prefixes, const char *suffix) {
178         const char *p;
179         char *f;
180         Hashmap *top, *bottom;
181         int r = 0, k;
182         Iterator i;
183         int n_found = 0;
184
185         assert(prefixes);
186         assert(suffix);
187
188         top = hashmap_new(string_hash_func, string_compare_func);
189         if (!top) {
190                 r = -ENOMEM;
191                 goto finish;
192         }
193
194         bottom = hashmap_new(string_hash_func, string_compare_func);
195         if (!bottom) {
196                 r = -ENOMEM;
197                 goto finish;
198         }
199
200         NULSTR_FOREACH(p, prefixes) {
201                 char *t;
202
203                 t = join(p, "/", suffix, NULL);
204                 if (!t) {
205                         r = -ENOMEM;
206                         goto finish;
207                 }
208
209                 k = enumerate_dir(top, bottom, t);
210                 if (k < 0)
211                         r = k;
212
213                 log_debug("Looking at %s", t);
214                 free(t);
215         }
216
217         HASHMAP_FOREACH(f, top, i) {
218                 char *o;
219
220                 o = hashmap_get(bottom, path_get_file_name(f));
221                 assert(o);
222
223                 if (path_equal(o, f))
224                         continue;
225
226                 k = found_override(f, o);
227                 if (k < 0)
228                         r = k;
229
230                 n_found ++;
231         }
232
233 finish:
234         if (top)
235                 hashmap_free_free(top);
236         if (bottom)
237                 hashmap_free_free(bottom);
238
239         return r < 0 ? r : n_found;
240 }
241
242 static int process_suffix_chop(const char *prefixes, const char *suffix) {
243         const char *p;
244
245         assert(prefixes);
246         assert(suffix);
247
248         if (!path_is_absolute(suffix))
249                 return process_suffix(prefixes, suffix);
250
251         /* Strip prefix from the suffix */
252         NULSTR_FOREACH(p, prefixes) {
253                 if (startswith(suffix, p)) {
254                         suffix += strlen(p);;
255                         suffix += strspn(suffix, "/");
256                         return process_suffix(prefixes, suffix);
257                 }
258         }
259
260         log_error("Invalid suffix specification %s.", suffix);
261         return -EINVAL;
262 }
263
264 static void help(void) {
265
266         printf("%s [OPTIONS...] [SUFFIX...]\n\n"
267                "Find overridden configuration files.\n\n"
268                "  -h --help           Show this help\n"
269                "     --version        Show package version\n"
270                "     --no-pager       Do not pipe output into a pager\n",
271                program_invocation_short_name);
272 }
273
274 static int parse_argv(int argc, char *argv[]) {
275
276         enum {
277                 ARG_NO_PAGER = 0x100,
278                 ARG_VERSION
279         };
280
281         static const struct option options[] = {
282                 { "help",      no_argument,       NULL, 'h'          },
283                 { "version",   no_argument,       NULL, ARG_VERSION  },
284                 { "no-pager",  no_argument,       NULL, ARG_NO_PAGER },
285                 { NULL,        0,                 NULL, 0            }
286         };
287
288         int c;
289
290         assert(argc >= 1);
291         assert(argv);
292
293         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
294
295                 switch (c) {
296
297                 case 'h':
298                         help();
299                         return 0;
300
301                 case ARG_VERSION:
302                         puts(PACKAGE_STRING);
303                         puts(DISTRIBUTION);
304                         puts(SYSTEMD_FEATURES);
305                         return 0;
306
307                 case ARG_NO_PAGER:
308                         arg_no_pager = true;
309                         break;
310
311                 case '?':
312                         return -EINVAL;
313
314                 default:
315                         log_error("Unknown option code %c", c);
316                         return -EINVAL;
317                 }
318         }
319
320         return 1;
321 }
322
323 int main(int argc, char *argv[]) {
324
325         const char prefixes[] =
326                 "/etc\0"
327                 "/run\0"
328                 "/usr/local/lib\0"
329                 "/usr/local/share\0"
330                 "/usr/lib\0"
331                 "/usr/share\0"
332 #ifdef HAVE_SPLIT_USR
333                 "/lib\0"
334 #endif
335                 ;
336
337         const char suffixes[] =
338                 "sysctl.d\0"
339                 "tmpfiles.d\0"
340                 "modules-load.d\0"
341                 "systemd/system\0"
342                 "systemd/user\0"
343                 "binfmt.d\0"
344                 "udev/rules.d\0"
345                 "modprobe.d\0";
346
347         int r = 0, k;
348         int n_found = 0;
349
350         log_parse_environment();
351         log_open();
352
353         r = parse_argv(argc, argv);
354         if (r <= 0)
355                 goto finish;
356
357         if (!arg_no_pager)
358                 pager_open();
359
360         if (optind < argc) {
361                 int i;
362
363                 for (i = optind; i < argc; i++) {
364                         k = process_suffix_chop(prefixes, argv[i]);
365                         if (k < 0)
366                                 r = k;
367                         else
368                                 n_found += k;
369                 }
370
371         } else {
372                 const char *n;
373
374                 NULSTR_FOREACH(n, suffixes) {
375                         k = process_suffix(prefixes, n);
376                         if (k < 0)
377                                 r = k;
378                         else
379                                 n_found += k;
380                 }
381         }
382
383         if (r >= 0)
384                 printf("\n%i overriden configuration files found.\n", n_found);
385
386 finish:
387         pager_close();
388
389         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
390 }