]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/delta/delta.c
use strneq instead of strncmp
[thirdparty/systemd.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 (strneq("masked", w, l))
316 flags |= SHOW_MASKED;
317 else if (strneq ("equivalent", w, l))
318 flags |= SHOW_EQUIVALENT;
319 else if (strneq("redirected", w, l))
320 flags |= SHOW_REDIRECTED;
321 else if (strneq("overridden", w, l))
322 flags |= SHOW_OVERRIDDEN;
323 else if (strneq("unchanged", w, l))
324 flags |= SHOW_UNCHANGED;
325 else if (strneq("default", w, l))
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 }