1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
8 #include "alloc-util.h"
9 #include "dirent-util.h"
13 #include "locale-util.h"
15 #include "main-func.h"
16 #include "nulstr-util.h"
18 #include "parse-argument.h"
19 #include "parse-util.h"
20 #include "path-util.h"
21 #include "pretty-print.h"
22 #include "process-util.h"
23 #include "signal-util.h"
24 #include "stat-util.h"
25 #include "string-util.h"
27 #include "terminal-util.h"
29 static const char prefixes
[] =
41 static const char suffixes
[] =
48 "systemd/system-preset\0"
49 "systemd/user-preset\0"
53 static const char have_dropins
[] =
57 static PagerFlags arg_pager_flags
= 0;
58 static int arg_diff
= -1;
62 SHOW_EQUIVALENT
= 1 << 1,
63 SHOW_REDIRECTED
= 1 << 2,
64 SHOW_OVERRIDDEN
= 1 << 3,
65 SHOW_UNCHANGED
= 1 << 4,
66 SHOW_EXTENDED
= 1 << 5,
69 (SHOW_MASKED
| SHOW_EQUIVALENT
| SHOW_REDIRECTED
| SHOW_OVERRIDDEN
| SHOW_EXTENDED
)
72 static int equivalent(const char *a
, const char *b
) {
73 _cleanup_free_
char *x
= NULL
, *y
= NULL
;
76 r
= chase_symlinks(a
, NULL
, CHASE_TRAIL_SLASH
, &x
, NULL
);
80 r
= chase_symlinks(b
, NULL
, CHASE_TRAIL_SLASH
, &y
, NULL
);
84 return path_equal(x
, y
);
87 static int notify_override_masked(const char *top
, const char *bottom
) {
88 if (!(arg_flags
& SHOW_MASKED
))
91 printf("%s%s%s %s %s %s\n",
92 ansi_highlight_red(), "[MASKED]", ansi_normal(),
93 top
, special_glyph(SPECIAL_GLYPH_ARROW
), bottom
);
97 static int notify_override_equivalent(const char *top
, const char *bottom
) {
98 if (!(arg_flags
& SHOW_EQUIVALENT
))
101 printf("%s%s%s %s %s %s\n",
102 ansi_highlight_green(), "[EQUIVALENT]", ansi_normal(),
103 top
, special_glyph(SPECIAL_GLYPH_ARROW
), bottom
);
107 static int notify_override_redirected(const char *top
, const char *bottom
) {
108 if (!(arg_flags
& SHOW_REDIRECTED
))
111 printf("%s%s%s %s %s %s\n",
112 ansi_highlight(), "[REDIRECTED]", ansi_normal(),
113 top
, special_glyph(SPECIAL_GLYPH_ARROW
), bottom
);
117 static int notify_override_overridden(const char *top
, const char *bottom
) {
118 if (!(arg_flags
& SHOW_OVERRIDDEN
))
121 printf("%s%s%s %s %s %s\n",
122 ansi_highlight(), "[OVERRIDDEN]", ansi_normal(),
123 top
, special_glyph(SPECIAL_GLYPH_ARROW
), bottom
);
127 static int notify_override_extended(const char *top
, const char *bottom
) {
128 if (!(arg_flags
& SHOW_EXTENDED
))
131 printf("%s%s%s %s %s %s\n",
132 ansi_highlight(), "[EXTENDED]", ansi_normal(),
133 top
, special_glyph(SPECIAL_GLYPH_ARROW
), bottom
);
137 static int notify_override_unchanged(const char *f
) {
138 if (!(arg_flags
& SHOW_UNCHANGED
))
141 printf("[UNCHANGED] %s\n", f
);
145 static int found_override(const char *top
, const char *bottom
) {
146 _cleanup_free_
char *dest
= NULL
;
153 if (null_or_empty_path(top
) > 0)
154 return notify_override_masked(top
, bottom
);
156 r
= readlink_malloc(top
, &dest
);
158 if (equivalent(dest
, bottom
) > 0)
159 return notify_override_equivalent(top
, bottom
);
161 return notify_override_redirected(top
, bottom
);
164 r
= notify_override_overridden(top
, bottom
);
172 r
= safe_fork("(diff)", FORK_RESET_SIGNALS
|FORK_DEATHSIG
|FORK_CLOSE_ALL_FDS
|FORK_RLIMIT_NOFILE_SAFE
|FORK_LOG
, &pid
);
176 execlp("diff", "diff", "-us", "--", bottom
, top
, NULL
);
178 log_error_errno(errno
, "Failed to execute diff: %m");
182 (void) wait_for_terminate_and_check("diff", pid
, WAIT_LOG_ABNORMAL
);
188 static int enumerate_dir_d(
190 OrderedHashmap
*bottom
,
191 OrderedHashmap
*drops
,
192 const char *toppath
, const char *drop
) {
194 _cleanup_free_
char *unit
= NULL
;
195 _cleanup_free_
char *path
= NULL
;
196 _cleanup_strv_free_
char **list
= NULL
;
201 assert(!endswith(drop
, "/"));
203 path
= path_join(toppath
, drop
);
207 log_debug("Looking at %s", path
);
213 c
= strrchr(unit
, '.');
218 r
= get_files_in_directory(path
, &list
);
220 return log_error_errno(r
, "Failed to enumerate %s: %m", path
);
224 STRV_FOREACH(file
, list
) {
230 if (!endswith(*file
, ".conf"))
233 p
= path_join(path
, *file
);
236 d
= p
+ strlen(toppath
) + 1;
238 log_debug("Adding at top: %s %s %s", d
, special_glyph(SPECIAL_GLYPH_ARROW
), p
);
239 k
= ordered_hashmap_put(top
, d
, p
);
244 d
= p
+ strlen(toppath
) + 1;
245 } else if (k
!= -EEXIST
) {
250 log_debug("Adding at bottom: %s %s %s", d
, special_glyph(SPECIAL_GLYPH_ARROW
), p
);
251 free(ordered_hashmap_remove(bottom
, d
));
252 k
= ordered_hashmap_put(bottom
, d
, p
);
258 h
= ordered_hashmap_get(drops
, unit
);
260 h
= ordered_hashmap_new(&string_hash_ops
);
263 ordered_hashmap_put(drops
, unit
, h
);
273 log_debug("Adding to drops: %s %s %s %s %s",
274 unit
, special_glyph(SPECIAL_GLYPH_ARROW
), basename(p
), special_glyph(SPECIAL_GLYPH_ARROW
), p
);
275 k
= ordered_hashmap_put(h
, basename(p
), p
);
285 static int enumerate_dir(
287 OrderedHashmap
*bottom
,
288 OrderedHashmap
*drops
,
289 const char *path
, bool dropins
) {
291 _cleanup_closedir_
DIR *d
= NULL
;
293 _cleanup_strv_free_
char **files
= NULL
, **dirs
= NULL
;
294 size_t n_files
= 0, allocated_files
= 0, n_dirs
= 0, allocated_dirs
= 0;
303 log_debug("Looking at %s", path
);
310 return log_error_errno(errno
, "Failed to open %s: %m", path
);
313 FOREACH_DIRENT_ALL(de
, d
, return -errno
) {
314 dirent_ensure_type(d
, de
);
316 if (dropins
&& de
->d_type
== DT_DIR
&& endswith(de
->d_name
, ".d")) {
317 if (!GREEDY_REALLOC0(dirs
, allocated_dirs
, n_dirs
+ 2))
320 dirs
[n_dirs
] = strdup(de
->d_name
);
326 if (!dirent_is_file(de
))
329 if (!GREEDY_REALLOC0(files
, allocated_files
, n_files
+ 2))
332 files
[n_files
] = strdup(de
->d_name
);
341 STRV_FOREACH(t
, dirs
) {
342 r
= enumerate_dir_d(top
, bottom
, drops
, path
, *t
);
347 STRV_FOREACH(t
, files
) {
348 _cleanup_free_
char *p
= NULL
;
350 p
= path_join(path
, *t
);
354 log_debug("Adding at top: %s %s %s", basename(p
), special_glyph(SPECIAL_GLYPH_ARROW
), p
);
355 r
= ordered_hashmap_put(top
, basename(p
), p
);
360 } else if (r
!= -EEXIST
)
363 log_debug("Adding at bottom: %s %s %s", basename(p
), special_glyph(SPECIAL_GLYPH_ARROW
), p
);
364 free(ordered_hashmap_remove(bottom
, basename(p
)));
365 r
= ordered_hashmap_put(bottom
, basename(p
), p
);
374 static int should_skip_path(const char *prefix
, const char *suffix
) {
376 _cleanup_free_
char *target
= NULL
;
377 const char *dirname
, *p
;
379 dirname
= prefix_roota(prefix
, suffix
);
381 if (chase_symlinks(dirname
, NULL
, 0, &target
, NULL
) < 0)
384 NULSTR_FOREACH(p
, prefixes
) {
385 _cleanup_free_
char *tmp
= NULL
;
387 if (path_startswith(dirname
, p
))
390 tmp
= path_join(p
, suffix
);
394 if (path_equal(target
, tmp
)) {
395 log_debug("%s redirects to %s, skipping.", dirname
, target
);
403 static int process_suffix(const char *suffix
, const char *onlyprefix
) {
406 OrderedHashmap
*top
, *bottom
, *drops
, *h
;
407 int r
= 0, k
, n_found
= 0;
411 assert(!startswith(suffix
, "/"));
412 assert(!strstr(suffix
, "//"));
414 dropins
= nulstr_contains(have_dropins
, suffix
);
416 top
= ordered_hashmap_new(&string_hash_ops
);
417 bottom
= ordered_hashmap_new(&string_hash_ops
);
418 drops
= ordered_hashmap_new(&string_hash_ops
);
419 if (!top
|| !bottom
|| !drops
) {
424 NULSTR_FOREACH(p
, prefixes
) {
425 _cleanup_free_
char *t
= NULL
;
427 if (should_skip_path(p
, suffix
) > 0)
430 t
= path_join(p
, suffix
);
436 k
= enumerate_dir(top
, bottom
, drops
, t
, dropins
);
441 ORDERED_HASHMAP_FOREACH_KEY(f
, key
, top
) {
444 o
= ordered_hashmap_get(bottom
, key
);
447 if (!onlyprefix
|| startswith(o
, onlyprefix
)) {
448 if (path_equal(o
, f
)) {
449 notify_override_unchanged(f
);
451 k
= found_override(f
, o
);
459 h
= ordered_hashmap_get(drops
, key
);
461 ORDERED_HASHMAP_FOREACH(o
, h
)
462 if (!onlyprefix
|| startswith(o
, onlyprefix
))
463 n_found
+= notify_override_extended(f
, o
);
467 ordered_hashmap_free_free(top
);
468 ordered_hashmap_free_free(bottom
);
470 ORDERED_HASHMAP_FOREACH_KEY(h
, key
, drops
) {
471 ordered_hashmap_free_free(ordered_hashmap_remove(drops
, key
));
472 ordered_hashmap_remove(drops
, key
);
475 ordered_hashmap_free(drops
);
477 return r
< 0 ? r
: n_found
;
480 static int process_suffixes(const char *onlyprefix
) {
484 NULSTR_FOREACH(n
, suffixes
) {
485 r
= process_suffix(n
, onlyprefix
);
495 static int process_suffix_chop(const char *arg
) {
500 if (!path_is_absolute(arg
))
501 return process_suffix(arg
, NULL
);
503 /* Strip prefix from the suffix */
504 NULSTR_FOREACH(p
, prefixes
) {
507 suffix
= startswith(arg
, p
);
509 suffix
+= strspn(suffix
, "/");
511 return process_suffix(suffix
, p
);
513 return process_suffixes(arg
);
517 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
518 "Invalid suffix specification %s.", arg
);
521 static int help(void) {
522 _cleanup_free_
char *link
= NULL
;
525 r
= terminal_urlify_man("systemd-delta", "1", &link
);
529 printf("%s [OPTIONS...] [SUFFIX...]\n\n"
530 "Find overridden configuration files.\n\n"
531 " -h --help Show this help\n"
532 " --version Show package version\n"
533 " --no-pager Do not pipe output into a pager\n"
534 " --diff[=1|0] Show a diff when overridden files differ\n"
535 " -t --type=LIST... Only display a selected set of override types\n"
536 "\nSee the %s for details.\n",
537 program_invocation_short_name
,
543 static int parse_flags(const char *flag_str
, int flags
) {
545 _cleanup_free_
char *word
= NULL
;
548 r
= extract_first_word(&flag_str
, &word
, ",", EXTRACT_DONT_COALESCE_SEPARATORS
);
554 if (streq(word
, "masked"))
555 flags
|= SHOW_MASKED
;
556 else if (streq(word
, "equivalent"))
557 flags
|= SHOW_EQUIVALENT
;
558 else if (streq(word
, "redirected"))
559 flags
|= SHOW_REDIRECTED
;
560 else if (streq(word
, "overridden"))
561 flags
|= SHOW_OVERRIDDEN
;
562 else if (streq(word
, "unchanged"))
563 flags
|= SHOW_UNCHANGED
;
564 else if (streq(word
, "extended"))
565 flags
|= SHOW_EXTENDED
;
566 else if (streq(word
, "default"))
567 flags
|= SHOW_DEFAULTS
;
573 static int parse_argv(int argc
, char *argv
[]) {
576 ARG_NO_PAGER
= 0x100,
581 static const struct option options
[] = {
582 { "help", no_argument
, NULL
, 'h' },
583 { "version", no_argument
, NULL
, ARG_VERSION
},
584 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
585 { "diff", optional_argument
, NULL
, ARG_DIFF
},
586 { "type", required_argument
, NULL
, 't' },
595 while ((c
= getopt_long(argc
, argv
, "ht:", options
, NULL
)) >= 0)
606 arg_pager_flags
|= PAGER_DISABLE
;
611 f
= parse_flags(optarg
, arg_flags
);
613 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
614 "Failed to parse flags field.");
620 r
= parse_boolean_argument("--diff", optarg
, NULL
);
630 assert_not_reached("Unhandled option");
636 static int run(int argc
, char *argv
[]) {
637 int r
, k
, n_found
= 0;
641 r
= parse_argv(argc
, argv
);
646 arg_flags
= SHOW_DEFAULTS
;
649 arg_diff
= !!(arg_flags
& SHOW_OVERRIDDEN
);
651 arg_flags
|= SHOW_OVERRIDDEN
;
653 (void) pager_open(arg_pager_flags
);
658 for (i
= optind
; i
< argc
; i
++) {
659 path_simplify(argv
[i
], false);
661 k
= process_suffix_chop(argv
[i
]);
669 k
= process_suffixes(NULL
);
677 printf("%s%i overridden configuration files found.\n", n_found
? "\n" : "", n_found
);
681 DEFINE_MAIN_FUNCTION(run
);