1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2012 Lennart Poettering
7 Copyright 2013 Zbigniew Jędrzejewski-Szmek
9 systemd is free software; you can redistribute it and/or modify it
10 under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation; either version 2.1 of the License, or
12 (at your option) any later version.
14 systemd is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
19 You should have received a copy of the GNU Lesser General Public License
20 along with systemd; If not, see <http://www.gnu.org/licenses/>.
31 #include "path-util.h"
37 static const char prefixes[] =
49 static const char suffixes[] =
56 "systemd/system-preset\0"
57 "systemd/user-preset\0"
61 static const char have_dropins[] =
65 static bool arg_no_pager = false;
66 static int arg_diff = -1;
70 SHOW_EQUIVALENT = 1 << 1,
71 SHOW_REDIRECTED = 1 << 2,
72 SHOW_OVERRIDDEN = 1 << 3,
73 SHOW_UNCHANGED = 1 << 4,
74 SHOW_EXTENDED = 1 << 5,
77 (SHOW_MASKED | SHOW_EQUIVALENT | SHOW_REDIRECTED | SHOW_OVERRIDDEN | SHOW_EXTENDED)
80 static void pager_open_if_enabled(void) {
88 static inline const char* arrow(void) {
89 return is_locale_utf8() ? "→" : "->";
92 static int equivalent(const char *a, const char *b) {
93 _cleanup_free_ char *x = NULL, *y = NULL;
95 x = canonicalize_file_name(a);
99 y = canonicalize_file_name(b);
103 return path_equal(x, y);
106 static int notify_override_masked(const char *top, const char *bottom) {
107 if (!(arg_flags & SHOW_MASKED))
110 printf("%s%s%s %s %s %s\n",
111 ansi_highlight_red(), "[MASKED]", ansi_highlight_off(),
112 top, arrow(), bottom);
116 static int notify_override_equivalent(const char *top, const char *bottom) {
117 if (!(arg_flags & SHOW_EQUIVALENT))
120 printf("%s%s%s %s %s %s\n",
121 ansi_highlight_green(), "[EQUIVALENT]", ansi_highlight_off(),
122 top, arrow(), bottom);
126 static int notify_override_redirected(const char *top, const char *bottom) {
127 if (!(arg_flags & SHOW_REDIRECTED))
130 printf("%s%s%s %s %s %s\n",
131 ansi_highlight(), "[REDIRECTED]", ansi_highlight_off(),
132 top, arrow(), bottom);
136 static int notify_override_overridden(const char *top, const char *bottom) {
137 if (!(arg_flags & SHOW_OVERRIDDEN))
140 printf("%s%s%s %s %s %s\n",
141 ansi_highlight(), "[OVERRIDDEN]", ansi_highlight_off(),
142 top, arrow(), bottom);
146 static int notify_override_extended(const char *top, const char *bottom) {
147 if (!(arg_flags & SHOW_EXTENDED))
150 printf("%s%s%s %s %s %s\n",
151 ansi_highlight(), "[EXTENDED]", ansi_highlight_off(),
152 top, arrow(), bottom);
156 static int notify_override_unchanged(const char *f) {
157 if (!(arg_flags & SHOW_UNCHANGED))
160 printf("[UNCHANGED] %s\n", f);
164 static int found_override(const char *top, const char *bottom) {
165 _cleanup_free_ char *dest = NULL;
172 if (null_or_empty_path(top) > 0)
173 return notify_override_masked(top, bottom);
175 k = readlink_malloc(top, &dest);
177 if (equivalent(dest, bottom) > 0)
178 return notify_override_equivalent(top, bottom);
180 return notify_override_redirected(top, bottom);
183 k = notify_override_overridden(top, bottom);
193 log_error("Failed to fork off diff: %m");
195 } else if (pid == 0) {
196 execlp("diff", "diff", "-us", "--", bottom, top, NULL);
197 log_error("Failed to execute diff: %m");
201 wait_for_terminate(pid, NULL);
208 static int enumerate_dir_d(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *toppath, const char *drop) {
209 _cleanup_free_ char *unit = NULL;
210 _cleanup_free_ char *path = NULL;
211 _cleanup_strv_free_ char **list = NULL;
216 assert(!endswith(drop, "/"));
218 path = strjoin(toppath, "/", drop, NULL);
222 log_debug("Looking at %s", path);
228 c = strrchr(unit, '.');
233 r = get_files_in_directory(path, &list);
235 log_error("Failed to enumerate %s: %s", path, strerror(-r));
239 STRV_FOREACH(file, list) {
245 if (!endswith(*file, ".conf"))
248 p = strjoin(path, "/", *file, NULL);
251 d = p + strlen(toppath) + 1;
253 log_debug("Adding at top: %s %s %s", d, arrow(), p);
254 k = hashmap_put(top, d, p);
259 d = p + strlen(toppath) + 1;
260 } else if (k != -EEXIST) {
265 log_debug("Adding at bottom: %s %s %s", d, arrow(), p);
266 free(hashmap_remove(bottom, d));
267 k = hashmap_put(bottom, d, p);
273 h = hashmap_get(drops, unit);
275 h = hashmap_new(string_hash_func, string_compare_func);
278 hashmap_put(drops, unit, h);
288 log_debug("Adding to drops: %s %s %s %s %s",
289 unit, arrow(), basename(p), arrow(), p);
290 k = hashmap_put(h, basename(p), p);
300 static int enumerate_dir(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *path, bool dropins) {
301 _cleanup_closedir_ DIR *d;
308 log_debug("Looking at %s", path);
315 log_error("Failed to open %s: %m", path);
329 dirent_ensure_type(d, de);
331 if (dropins && de->d_type == DT_DIR && endswith(de->d_name, ".d"))
332 enumerate_dir_d(top, bottom, drops, path, de->d_name);
334 if (!dirent_is_file(de))
337 p = strjoin(path, "/", de->d_name, NULL);
341 log_debug("Adding at top: %s %s %s", basename(p), arrow(), p);
342 k = hashmap_put(top, basename(p), p);
347 } else if (k != -EEXIST) {
352 log_debug("Adding at bottom: %s %s %s", basename(p), arrow(), p);
353 free(hashmap_remove(bottom, basename(p)));
354 k = hashmap_put(bottom, basename(p), p);
362 static int process_suffix(const char *suffix, const char *onlyprefix) {
365 Hashmap *top, *bottom, *drops;
374 assert(!startswith(suffix, "/"));
375 assert(!strstr(suffix, "//"));
377 dropins = nulstr_contains(have_dropins, suffix);
379 top = hashmap_new(string_hash_func, string_compare_func);
380 bottom = hashmap_new(string_hash_func, string_compare_func);
381 drops = hashmap_new(string_hash_func, string_compare_func);
382 if (!top || !bottom || !drops) {
387 NULSTR_FOREACH(p, prefixes) {
388 _cleanup_free_ char *t = NULL;
390 t = strjoin(p, "/", suffix, NULL);
396 k = enumerate_dir(top, bottom, drops, t, dropins);
401 HASHMAP_FOREACH_KEY(f, key, top, i) {
404 o = hashmap_get(bottom, key);
407 if (!onlyprefix || startswith(o, onlyprefix)) {
408 if (path_equal(o, f)) {
409 notify_override_unchanged(f);
411 k = found_override(f, o);
419 h = hashmap_get(drops, key);
421 HASHMAP_FOREACH(o, h, j)
422 if (!onlyprefix || startswith(o, onlyprefix))
423 n_found += notify_override_extended(f, o);
428 hashmap_free_free(top);
430 hashmap_free_free(bottom);
432 HASHMAP_FOREACH_KEY(h, key, drops, i){
433 hashmap_free_free(hashmap_remove(drops, key));
434 hashmap_remove(drops, key);
439 return r < 0 ? r : n_found;
442 static int process_suffixes(const char *onlyprefix) {
446 NULSTR_FOREACH(n, suffixes) {
447 r = process_suffix(n, onlyprefix);
456 static int process_suffix_chop(const char *arg) {
461 if (!path_is_absolute(arg))
462 return process_suffix(arg, NULL);
464 /* Strip prefix from the suffix */
465 NULSTR_FOREACH(p, prefixes) {
466 const char *suffix = startswith(arg, p);
468 suffix += strspn(suffix, "/");
470 return process_suffix(suffix, NULL);
472 return process_suffixes(arg);
476 log_error("Invalid suffix specification %s.", arg);
480 static int help(void) {
482 printf("%s [OPTIONS...] [SUFFIX...]\n\n"
483 "Find overridden configuration files.\n\n"
484 " -h --help Show this help\n"
485 " --version Show package version\n"
486 " --no-pager Do not pipe output into a pager\n"
487 " --diff[=1|0] Show a diff when overridden files differ\n"
488 " -t --type=LIST... Only display a selected set of override types\n",
489 program_invocation_short_name);
494 static int parse_flags(const char *flag_str, int flags) {
498 FOREACH_WORD(w, l, flag_str, state) {
499 if (strneq("masked", w, l))
500 flags |= SHOW_MASKED;
501 else if (strneq ("equivalent", w, l))
502 flags |= SHOW_EQUIVALENT;
503 else if (strneq("redirected", w, l))
504 flags |= SHOW_REDIRECTED;
505 else if (strneq("overridden", w, l))
506 flags |= SHOW_OVERRIDDEN;
507 else if (strneq("unchanged", w, l))
508 flags |= SHOW_UNCHANGED;
509 else if (strneq("extended", w, l))
510 flags |= SHOW_EXTENDED;
511 else if (strneq("default", w, l))
512 flags |= SHOW_DEFAULTS;
519 static int parse_argv(int argc, char *argv[]) {
522 ARG_NO_PAGER = 0x100,
527 static const struct option options[] = {
528 { "help", no_argument, NULL, 'h' },
529 { "version", no_argument, NULL, ARG_VERSION },
530 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
531 { "diff", optional_argument, NULL, ARG_DIFF },
532 { "type", required_argument, NULL, 't' },
541 while ((c = getopt_long(argc, argv, "ht:", options, NULL)) >= 0) {
550 puts(PACKAGE_STRING);
551 puts(SYSTEMD_FEATURES);
560 f = parse_flags(optarg, arg_flags);
562 log_error("Failed to parse flags field.");
575 b = parse_boolean(optarg);
577 log_error("Failed to parse diff boolean.");
590 assert_not_reached("Unhandled option");
597 int main(int argc, char *argv[]) {
601 log_parse_environment();
604 r = parse_argv(argc, argv);
609 arg_flags = SHOW_DEFAULTS;
612 arg_diff = !!(arg_flags & SHOW_OVERRIDDEN);
614 arg_flags |= SHOW_OVERRIDDEN;
616 pager_open_if_enabled();
621 for (i = optind; i < argc; i++) {
622 path_kill_slashes(argv[i]);
623 k = process_suffix_chop(argv[i]);
631 k = process_suffixes(NULL);
639 printf("%s%i overridden configuration files found.\n",
640 n_found ? "\n" : "", n_found);
645 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;