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