1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 Copyright 2012 Lennart Poettering
4 Copyright 2013 Zbigniew Jędrzejewski-Szmek
10 #include <sys/prctl.h>
13 #include "alloc-util.h"
14 #include "dirent-util.h"
18 #include "locale-util.h"
21 #include "parse-util.h"
22 #include "path-util.h"
23 #include "process-util.h"
24 #include "signal-util.h"
25 #include "stat-util.h"
26 #include "string-util.h"
28 #include "terminal-util.h"
31 static const char prefixes
[] =
43 static const char suffixes
[] =
50 "systemd/system-preset\0"
51 "systemd/user-preset\0"
55 static const char have_dropins
[] =
59 static bool arg_no_pager
= false;
60 static int arg_diff
= -1;
64 SHOW_EQUIVALENT
= 1 << 1,
65 SHOW_REDIRECTED
= 1 << 2,
66 SHOW_OVERRIDDEN
= 1 << 3,
67 SHOW_UNCHANGED
= 1 << 4,
68 SHOW_EXTENDED
= 1 << 5,
71 (SHOW_MASKED
| SHOW_EQUIVALENT
| SHOW_REDIRECTED
| SHOW_OVERRIDDEN
| SHOW_EXTENDED
)
74 static int equivalent(const char *a
, const char *b
) {
75 _cleanup_free_
char *x
= NULL
, *y
= NULL
;
78 r
= chase_symlinks(a
, NULL
, CHASE_TRAIL_SLASH
, &x
);
82 r
= chase_symlinks(b
, NULL
, CHASE_TRAIL_SLASH
, &y
);
86 return path_equal(x
, y
);
89 static int notify_override_masked(const char *top
, const char *bottom
) {
90 if (!(arg_flags
& SHOW_MASKED
))
93 printf("%s%s%s %s %s %s\n",
94 ansi_highlight_red(), "[MASKED]", ansi_normal(),
95 top
, special_glyph(ARROW
), bottom
);
99 static int notify_override_equivalent(const char *top
, const char *bottom
) {
100 if (!(arg_flags
& SHOW_EQUIVALENT
))
103 printf("%s%s%s %s %s %s\n",
104 ansi_highlight_green(), "[EQUIVALENT]", ansi_normal(),
105 top
, special_glyph(ARROW
), bottom
);
109 static int notify_override_redirected(const char *top
, const char *bottom
) {
110 if (!(arg_flags
& SHOW_REDIRECTED
))
113 printf("%s%s%s %s %s %s\n",
114 ansi_highlight(), "[REDIRECTED]", ansi_normal(),
115 top
, special_glyph(ARROW
), bottom
);
119 static int notify_override_overridden(const char *top
, const char *bottom
) {
120 if (!(arg_flags
& SHOW_OVERRIDDEN
))
123 printf("%s%s%s %s %s %s\n",
124 ansi_highlight(), "[OVERRIDDEN]", ansi_normal(),
125 top
, special_glyph(ARROW
), bottom
);
129 static int notify_override_extended(const char *top
, const char *bottom
) {
130 if (!(arg_flags
& SHOW_EXTENDED
))
133 printf("%s%s%s %s %s %s\n",
134 ansi_highlight(), "[EXTENDED]", ansi_normal(),
135 top
, special_glyph(ARROW
), bottom
);
139 static int notify_override_unchanged(const char *f
) {
140 if (!(arg_flags
& SHOW_UNCHANGED
))
143 printf("[UNCHANGED] %s\n", f
);
147 static int found_override(const char *top
, const char *bottom
) {
148 _cleanup_free_
char *dest
= NULL
;
155 if (null_or_empty_path(top
) > 0)
156 return notify_override_masked(top
, bottom
);
158 r
= readlink_malloc(top
, &dest
);
160 if (equivalent(dest
, bottom
) > 0)
161 return notify_override_equivalent(top
, bottom
);
163 return notify_override_redirected(top
, bottom
);
166 r
= notify_override_overridden(top
, bottom
);
174 r
= safe_fork("(diff)", FORK_RESET_SIGNALS
|FORK_DEATHSIG
|FORK_CLOSE_ALL_FDS
|FORK_LOG
, &pid
);
178 execlp("diff", "diff", "-us", "--", bottom
, top
, NULL
);
180 log_error_errno(errno
, "Failed to execute diff: %m");
184 (void) wait_for_terminate_and_check("diff", pid
, WAIT_LOG_ABNORMAL
);
190 static int enumerate_dir_d(
192 OrderedHashmap
*bottom
,
193 OrderedHashmap
*drops
,
194 const char *toppath
, const char *drop
) {
196 _cleanup_free_
char *unit
= NULL
;
197 _cleanup_free_
char *path
= NULL
;
198 _cleanup_strv_free_
char **list
= NULL
;
203 assert(!endswith(drop
, "/"));
205 path
= strjoin(toppath
, "/", drop
);
209 log_debug("Looking at %s", path
);
215 c
= strrchr(unit
, '.');
220 r
= get_files_in_directory(path
, &list
);
222 return log_error_errno(r
, "Failed to enumerate %s: %m", path
);
226 STRV_FOREACH(file
, list
) {
232 if (!endswith(*file
, ".conf"))
235 p
= strjoin(path
, "/", *file
);
238 d
= p
+ strlen(toppath
) + 1;
240 log_debug("Adding at top: %s %s %s", d
, special_glyph(ARROW
), p
);
241 k
= ordered_hashmap_put(top
, d
, p
);
246 d
= p
+ strlen(toppath
) + 1;
247 } else if (k
!= -EEXIST
) {
252 log_debug("Adding at bottom: %s %s %s", d
, special_glyph(ARROW
), p
);
253 free(ordered_hashmap_remove(bottom
, d
));
254 k
= ordered_hashmap_put(bottom
, d
, p
);
260 h
= ordered_hashmap_get(drops
, unit
);
262 h
= ordered_hashmap_new(&string_hash_ops
);
265 ordered_hashmap_put(drops
, unit
, h
);
275 log_debug("Adding to drops: %s %s %s %s %s",
276 unit
, special_glyph(ARROW
), basename(p
), special_glyph(ARROW
), p
);
277 k
= ordered_hashmap_put(h
, basename(p
), p
);
287 static int enumerate_dir(
289 OrderedHashmap
*bottom
,
290 OrderedHashmap
*drops
,
291 const char *path
, bool dropins
) {
293 _cleanup_closedir_
DIR *d
= NULL
;
295 _cleanup_strv_free_
char **files
= NULL
, **dirs
= NULL
;
296 size_t n_files
= 0, allocated_files
= 0, n_dirs
= 0, allocated_dirs
= 0;
305 log_debug("Looking at %s", path
);
312 return log_error_errno(errno
, "Failed to open %s: %m", path
);
315 FOREACH_DIRENT_ALL(de
, d
, return -errno
) {
316 dirent_ensure_type(d
, de
);
318 if (dropins
&& de
->d_type
== DT_DIR
&& endswith(de
->d_name
, ".d")) {
319 if (!GREEDY_REALLOC0(dirs
, allocated_dirs
, n_dirs
+ 2))
322 dirs
[n_dirs
] = strdup(de
->d_name
);
328 if (!dirent_is_file(de
))
331 if (!GREEDY_REALLOC0(files
, allocated_files
, n_files
+ 2))
334 files
[n_files
] = strdup(de
->d_name
);
343 STRV_FOREACH(t
, dirs
) {
344 r
= enumerate_dir_d(top
, bottom
, drops
, path
, *t
);
349 STRV_FOREACH(t
, files
) {
350 _cleanup_free_
char *p
= NULL
;
352 p
= strjoin(path
, "/", *t
);
356 log_debug("Adding at top: %s %s %s", basename(p
), special_glyph(ARROW
), p
);
357 r
= ordered_hashmap_put(top
, basename(p
), p
);
362 } else if (r
!= -EEXIST
)
365 log_debug("Adding at bottom: %s %s %s", basename(p
), special_glyph(ARROW
), p
);
366 free(ordered_hashmap_remove(bottom
, basename(p
)));
367 r
= ordered_hashmap_put(bottom
, basename(p
), p
);
376 static bool should_skip_path(const char *prefix
, const char *suffix
) {
378 _cleanup_free_
char *target
= NULL
;
382 dirname
= strjoina(prefix
, "/", suffix
);
384 if (chase_symlinks(dirname
, NULL
, 0, &target
) < 0)
387 NULSTR_FOREACH(p
, prefixes
) {
388 if (path_startswith(dirname
, p
))
391 if (path_equal(target
, strjoina(p
, "/", suffix
))) {
392 log_debug("%s redirects to %s, skipping.", dirname
, target
);
400 static int process_suffix(const char *suffix
, const char *onlyprefix
) {
403 OrderedHashmap
*top
, *bottom
, *drops
;
412 assert(!startswith(suffix
, "/"));
413 assert(!strstr(suffix
, "//"));
415 dropins
= nulstr_contains(have_dropins
, suffix
);
417 top
= ordered_hashmap_new(&string_hash_ops
);
418 bottom
= ordered_hashmap_new(&string_hash_ops
);
419 drops
= ordered_hashmap_new(&string_hash_ops
);
420 if (!top
|| !bottom
|| !drops
) {
425 NULSTR_FOREACH(p
, prefixes
) {
426 _cleanup_free_
char *t
= NULL
;
428 if (should_skip_path(p
, suffix
))
431 t
= strjoin(p
, "/", suffix
);
437 k
= enumerate_dir(top
, bottom
, drops
, t
, dropins
);
442 ORDERED_HASHMAP_FOREACH_KEY(f
, key
, top
, i
) {
445 o
= ordered_hashmap_get(bottom
, key
);
448 if (!onlyprefix
|| startswith(o
, onlyprefix
)) {
449 if (path_equal(o
, f
)) {
450 notify_override_unchanged(f
);
452 k
= found_override(f
, o
);
460 h
= ordered_hashmap_get(drops
, key
);
462 ORDERED_HASHMAP_FOREACH(o
, h
, j
)
463 if (!onlyprefix
|| startswith(o
, onlyprefix
))
464 n_found
+= notify_override_extended(f
, o
);
468 ordered_hashmap_free_free(top
);
469 ordered_hashmap_free_free(bottom
);
471 ORDERED_HASHMAP_FOREACH_KEY(h
, key
, drops
, i
) {
472 ordered_hashmap_free_free(ordered_hashmap_remove(drops
, key
));
473 ordered_hashmap_remove(drops
, key
);
476 ordered_hashmap_free(drops
);
478 return r
< 0 ? r
: n_found
;
481 static int process_suffixes(const char *onlyprefix
) {
485 NULSTR_FOREACH(n
, suffixes
) {
486 r
= process_suffix(n
, onlyprefix
);
496 static int process_suffix_chop(const char *arg
) {
501 if (!path_is_absolute(arg
))
502 return process_suffix(arg
, NULL
);
504 /* Strip prefix from the suffix */
505 NULSTR_FOREACH(p
, prefixes
) {
508 suffix
= startswith(arg
, p
);
510 suffix
+= strspn(suffix
, "/");
512 return process_suffix(suffix
, p
);
514 return process_suffixes(arg
);
518 log_error("Invalid suffix specification %s.", arg
);
522 static void help(void) {
523 printf("%s [OPTIONS...] [SUFFIX...]\n\n"
524 "Find overridden configuration files.\n\n"
525 " -h --help Show this help\n"
526 " --version Show package version\n"
527 " --no-pager Do not pipe output into a pager\n"
528 " --diff[=1|0] Show a diff when overridden files differ\n"
529 " -t --type=LIST... Only display a selected set of override types\n"
530 , program_invocation_short_name
);
533 static int parse_flags(const char *flag_str
, int flags
) {
534 const char *word
, *state
;
537 FOREACH_WORD_SEPARATOR(word
, l
, flag_str
, ",", state
) {
538 if (strneq("masked", word
, l
))
539 flags
|= SHOW_MASKED
;
540 else if (strneq ("equivalent", word
, l
))
541 flags
|= SHOW_EQUIVALENT
;
542 else if (strneq("redirected", word
, l
))
543 flags
|= SHOW_REDIRECTED
;
544 else if (strneq("overridden", word
, l
))
545 flags
|= SHOW_OVERRIDDEN
;
546 else if (strneq("unchanged", word
, l
))
547 flags
|= SHOW_UNCHANGED
;
548 else if (strneq("extended", word
, l
))
549 flags
|= SHOW_EXTENDED
;
550 else if (strneq("default", word
, l
))
551 flags
|= SHOW_DEFAULTS
;
558 static int parse_argv(int argc
, char *argv
[]) {
561 ARG_NO_PAGER
= 0x100,
566 static const struct option options
[] = {
567 { "help", no_argument
, NULL
, 'h' },
568 { "version", no_argument
, NULL
, ARG_VERSION
},
569 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
570 { "diff", optional_argument
, NULL
, ARG_DIFF
},
571 { "type", required_argument
, NULL
, 't' },
580 while ((c
= getopt_long(argc
, argv
, "ht:", options
, NULL
)) >= 0)
597 f
= parse_flags(optarg
, arg_flags
);
599 log_error("Failed to parse flags field.");
612 b
= parse_boolean(optarg
);
614 log_error("Failed to parse diff boolean.");
626 assert_not_reached("Unhandled option");
632 int main(int argc
, char *argv
[]) {
633 int r
, k
, n_found
= 0;
635 log_parse_environment();
638 r
= parse_argv(argc
, argv
);
643 arg_flags
= SHOW_DEFAULTS
;
646 arg_diff
= !!(arg_flags
& SHOW_OVERRIDDEN
);
648 arg_flags
|= SHOW_OVERRIDDEN
;
650 (void) pager_open(arg_no_pager
, false);
655 for (i
= optind
; i
< argc
; i
++) {
656 path_simplify(argv
[i
], false);
658 k
= process_suffix_chop(argv
[i
]);
666 k
= process_suffixes(NULL
);
674 printf("%s%i overridden configuration files found.\n", n_found
? "\n" : "", n_found
);
679 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;