1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
8 #include "alloc-util.h"
11 #include "dirent-util.h"
14 #include "glyph-util.h"
17 #include "main-func.h"
18 #include "nulstr-util.h"
20 #include "parse-argument.h"
21 #include "parse-util.h"
22 #include "path-util.h"
23 #include "pretty-print.h"
24 #include "process-util.h"
25 #include "signal-util.h"
26 #include "stat-util.h"
27 #include "string-util.h"
29 #include "terminal-util.h"
31 static const char prefixes
[] =
40 static const char suffixes
[] =
47 "systemd/system-preset\0"
48 "systemd/user-preset\0"
52 static const char have_dropins
[] =
56 static PagerFlags arg_pager_flags
= 0;
57 static int arg_diff
= -1;
61 SHOW_EQUIVALENT
= 1 << 1,
62 SHOW_REDIRECTED
= 1 << 2,
63 SHOW_OVERRIDDEN
= 1 << 3,
64 SHOW_UNCHANGED
= 1 << 4,
65 SHOW_EXTENDED
= 1 << 5,
68 (SHOW_MASKED
| SHOW_EQUIVALENT
| SHOW_REDIRECTED
| SHOW_OVERRIDDEN
| SHOW_EXTENDED
)
71 static int equivalent(const char *a
, const char *b
) {
72 _cleanup_free_
char *x
= NULL
, *y
= NULL
;
75 r
= chase(a
, NULL
, CHASE_TRAIL_SLASH
, &x
, NULL
);
79 r
= chase(b
, NULL
, CHASE_TRAIL_SLASH
, &y
, NULL
);
83 return path_equal(x
, y
);
86 static int notify_override_masked(const char *top
, const char *bottom
) {
87 if (!(arg_flags
& SHOW_MASKED
))
90 printf("%s%s%s %s %s %s\n",
91 ansi_highlight_red(), "[MASKED]", ansi_normal(),
92 top
, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT
), bottom
);
96 static int notify_override_equivalent(const char *top
, const char *bottom
) {
97 if (!(arg_flags
& SHOW_EQUIVALENT
))
100 printf("%s%s%s %s %s %s\n",
101 ansi_highlight_green(), "[EQUIVALENT]", ansi_normal(),
102 top
, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT
), bottom
);
106 static int notify_override_redirected(const char *top
, const char *bottom
) {
107 if (!(arg_flags
& SHOW_REDIRECTED
))
110 printf("%s%s%s %s %s %s\n",
111 ansi_highlight(), "[REDIRECTED]", ansi_normal(),
112 top
, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT
), bottom
);
116 static int notify_override_overridden(const char *top
, const char *bottom
) {
117 if (!(arg_flags
& SHOW_OVERRIDDEN
))
120 printf("%s%s%s %s %s %s\n",
121 ansi_highlight(), "[OVERRIDDEN]", ansi_normal(),
122 top
, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT
), bottom
);
126 static int notify_override_extended(const char *top
, const char *bottom
) {
127 if (!(arg_flags
& SHOW_EXTENDED
))
130 printf("%s%s%s %s %s %s\n",
131 ansi_highlight(), "[EXTENDED]", ansi_normal(),
132 top
, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT
), bottom
);
136 static int notify_override_unchanged(const char *f
) {
137 if (!(arg_flags
& SHOW_UNCHANGED
))
140 printf("[UNCHANGED] %s\n", f
);
144 static int found_override(const char *top
, const char *bottom
) {
145 _cleanup_free_
char *dest
= NULL
;
152 if (null_or_empty_path(top
) > 0)
153 return notify_override_masked(top
, bottom
);
155 r
= readlink_malloc(top
, &dest
);
157 if (equivalent(dest
, bottom
) > 0)
158 return notify_override_equivalent(top
, bottom
);
160 return notify_override_redirected(top
, bottom
);
163 r
= notify_override_overridden(top
, bottom
);
171 r
= safe_fork("(diff)", FORK_RESET_SIGNALS
|FORK_DEATHSIG_SIGTERM
|FORK_CLOSE_ALL_FDS
|FORK_RLIMIT_NOFILE_SAFE
|FORK_LOG
, &pid
);
175 execlp("diff", "diff", "-us", "--", bottom
, top
, NULL
);
177 log_error_errno(errno
, "Failed to execute diff: %m");
181 (void) wait_for_terminate_and_check("diff", pid
, WAIT_LOG_ABNORMAL
);
187 static int enumerate_dir_d(
189 OrderedHashmap
*bottom
,
190 OrderedHashmap
*drops
,
191 const char *toppath
, const char *drop
) {
193 _cleanup_free_
char *unit
= NULL
;
194 _cleanup_free_
char *path
= NULL
;
195 _cleanup_strv_free_
char **list
= NULL
;
199 assert(!endswith(drop
, "/"));
201 path
= path_join(toppath
, drop
);
205 log_debug("Looking at %s", path
);
211 c
= strrchr(unit
, '.');
216 r
= get_files_in_directory(path
, &list
);
218 return log_error_errno(r
, "Failed to enumerate %s: %m", path
);
222 STRV_FOREACH(file
, list
) {
228 if (!endswith(*file
, ".conf"))
231 p
= path_join(path
, *file
);
234 d
= p
+ strlen(toppath
) + 1;
236 log_debug("Adding at top: %s %s %s", d
, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT
), p
);
237 k
= ordered_hashmap_put(top
, d
, p
);
242 d
= p
+ strlen(toppath
) + 1;
243 } else if (k
!= -EEXIST
) {
248 log_debug("Adding at bottom: %s %s %s", d
, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT
), p
);
249 free(ordered_hashmap_remove(bottom
, d
));
250 k
= ordered_hashmap_put(bottom
, d
, p
);
256 h
= ordered_hashmap_get(drops
, unit
);
258 h
= ordered_hashmap_new(&string_hash_ops
);
261 ordered_hashmap_put(drops
, unit
, h
);
271 log_debug("Adding to drops: %s %s %s %s %s",
272 unit
, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT
), basename(p
), special_glyph(SPECIAL_GLYPH_ARROW_RIGHT
), p
);
273 k
= ordered_hashmap_put(h
, basename(p
), p
);
283 static int enumerate_dir(
285 OrderedHashmap
*bottom
,
286 OrderedHashmap
*drops
,
287 const char *path
, bool dropins
) {
289 _cleanup_closedir_
DIR *d
= NULL
;
290 _cleanup_strv_free_
char **files
= NULL
, **dirs
= NULL
;
291 size_t n_files
= 0, n_dirs
= 0;
299 log_debug("Looking at %s", path
);
306 return log_error_errno(errno
, "Failed to open %s: %m", path
);
309 FOREACH_DIRENT_ALL(de
, d
, return -errno
) {
310 if (dropins
&& de
->d_type
== DT_DIR
&& endswith(de
->d_name
, ".d")) {
311 if (!GREEDY_REALLOC0(dirs
, n_dirs
+ 2))
314 dirs
[n_dirs
] = strdup(de
->d_name
);
320 if (!dirent_is_file(de
))
323 if (!GREEDY_REALLOC0(files
, n_files
+ 2))
326 files
[n_files
] = strdup(de
->d_name
);
335 STRV_FOREACH(t
, dirs
) {
336 r
= enumerate_dir_d(top
, bottom
, drops
, path
, *t
);
341 STRV_FOREACH(t
, files
) {
342 _cleanup_free_
char *p
= NULL
;
344 p
= path_join(path
, *t
);
348 log_debug("Adding at top: %s %s %s", basename(p
), special_glyph(SPECIAL_GLYPH_ARROW_RIGHT
), p
);
349 r
= ordered_hashmap_put(top
, basename(p
), p
);
354 } else if (r
!= -EEXIST
)
357 log_debug("Adding at bottom: %s %s %s", basename(p
), special_glyph(SPECIAL_GLYPH_ARROW_RIGHT
), p
);
358 free(ordered_hashmap_remove(bottom
, basename(p
)));
359 r
= ordered_hashmap_put(bottom
, basename(p
), p
);
368 static int process_suffix(const char *suffix
, const char *onlyprefix
) {
370 OrderedHashmap
*top
, *bottom
, *drops
, *h
;
371 int r
= 0, k
, n_found
= 0;
375 assert(!startswith(suffix
, "/"));
376 assert(!strstr(suffix
, "//"));
378 dropins
= nulstr_contains(have_dropins
, suffix
);
380 top
= ordered_hashmap_new(&string_hash_ops
);
381 bottom
= ordered_hashmap_new(&string_hash_ops
);
382 drops
= ordered_hashmap_new(&string_hash_ops
);
383 if (!top
|| !bottom
|| !drops
) {
388 NULSTR_FOREACH(p
, prefixes
) {
389 _cleanup_free_
char *t
= NULL
;
391 t
= path_join(p
, suffix
);
397 k
= enumerate_dir(top
, bottom
, drops
, t
, dropins
);
402 ORDERED_HASHMAP_FOREACH_KEY(f
, key
, top
) {
405 o
= ordered_hashmap_get(bottom
, key
);
408 if (!onlyprefix
|| startswith(o
, onlyprefix
)) {
409 if (path_equal(o
, f
)) {
410 notify_override_unchanged(f
);
412 k
= found_override(f
, o
);
420 h
= ordered_hashmap_get(drops
, key
);
422 ORDERED_HASHMAP_FOREACH(o
, h
)
423 if (!onlyprefix
|| startswith(o
, onlyprefix
))
424 n_found
+= notify_override_extended(f
, o
);
428 ordered_hashmap_free_free(top
);
429 ordered_hashmap_free_free(bottom
);
431 ORDERED_HASHMAP_FOREACH_KEY(h
, key
, drops
) {
432 ordered_hashmap_free_free(ordered_hashmap_remove(drops
, key
));
433 ordered_hashmap_remove(drops
, key
);
436 ordered_hashmap_free(drops
);
438 return r
< 0 ? r
: n_found
;
441 static int process_suffixes(const char *onlyprefix
) {
444 NULSTR_FOREACH(n
, suffixes
) {
445 r
= process_suffix(n
, onlyprefix
);
455 static int process_suffix_chop(const char *arg
) {
458 if (!path_is_absolute(arg
))
459 return process_suffix(arg
, NULL
);
461 /* Strip prefix from the suffix */
462 NULSTR_FOREACH(p
, prefixes
) {
465 suffix
= startswith(arg
, p
);
467 suffix
+= strspn(suffix
, "/");
469 return process_suffix(suffix
, p
);
471 return process_suffixes(arg
);
475 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
476 "Invalid suffix specification %s.", arg
);
479 static int help(void) {
480 _cleanup_free_
char *link
= NULL
;
483 r
= terminal_urlify_man("systemd-delta", "1", &link
);
487 printf("%s [OPTIONS...] [SUFFIX...]\n\n"
488 "Find overridden configuration files.\n\n"
489 " -h --help Show this help\n"
490 " --version Show package version\n"
491 " --no-pager Do not pipe output into a pager\n"
492 " --diff[=1|0] Show a diff when overridden files differ\n"
493 " -t --type=LIST... Only display a selected set of override types\n"
494 "\nSee the %s for details.\n",
495 program_invocation_short_name
,
501 static int parse_flags(const char *flag_str
, int flags
) {
503 _cleanup_free_
char *word
= NULL
;
506 r
= extract_first_word(&flag_str
, &word
, ",", EXTRACT_DONT_COALESCE_SEPARATORS
);
512 if (streq(word
, "masked"))
513 flags
|= SHOW_MASKED
;
514 else if (streq(word
, "equivalent"))
515 flags
|= SHOW_EQUIVALENT
;
516 else if (streq(word
, "redirected"))
517 flags
|= SHOW_REDIRECTED
;
518 else if (streq(word
, "overridden"))
519 flags
|= SHOW_OVERRIDDEN
;
520 else if (streq(word
, "unchanged"))
521 flags
|= SHOW_UNCHANGED
;
522 else if (streq(word
, "extended"))
523 flags
|= SHOW_EXTENDED
;
524 else if (streq(word
, "default"))
525 flags
|= SHOW_DEFAULTS
;
531 static int parse_argv(int argc
, char *argv
[]) {
534 ARG_NO_PAGER
= 0x100,
539 static const struct option options
[] = {
540 { "help", no_argument
, NULL
, 'h' },
541 { "version", no_argument
, NULL
, ARG_VERSION
},
542 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
543 { "diff", optional_argument
, NULL
, ARG_DIFF
},
544 { "type", required_argument
, NULL
, 't' },
553 while ((c
= getopt_long(argc
, argv
, "ht:", options
, NULL
)) >= 0)
564 arg_pager_flags
|= PAGER_DISABLE
;
569 f
= parse_flags(optarg
, arg_flags
);
571 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
572 "Failed to parse flags field.");
578 r
= parse_boolean_argument("--diff", optarg
, NULL
);
588 assert_not_reached();
594 static int run(int argc
, char *argv
[]) {
595 int r
, k
, n_found
= 0;
599 r
= parse_argv(argc
, argv
);
604 arg_flags
= SHOW_DEFAULTS
;
607 arg_diff
= !!(arg_flags
& SHOW_OVERRIDDEN
);
609 arg_flags
|= SHOW_OVERRIDDEN
;
611 pager_open(arg_pager_flags
);
614 for (int i
= optind
; i
< argc
; i
++) {
615 path_simplify(argv
[i
]);
617 k
= process_suffix_chop(argv
[i
]);
625 k
= process_suffixes(NULL
);
633 printf("%s%i overridden configuration files found.\n", n_found
? "\n" : "", n_found
);
637 DEFINE_MAIN_FUNCTION(run
);