1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
6 #include "alloc-util.h"
9 #include "dirent-util.h"
10 #include "errno-util.h"
11 #include "extract-word.h"
14 #include "glyph-util.h"
17 #include "main-func.h"
18 #include "nulstr-util.h"
20 #include "parse-argument.h"
21 #include "path-util.h"
22 #include "pretty-print.h"
23 #include "process-util.h"
24 #include "stat-util.h"
25 #include "string-util.h"
28 static const char prefixes
[] =
37 static const char suffixes
[] =
44 "systemd/system-preset\0"
45 "systemd/user-preset\0"
49 static const char have_dropins
[] =
53 static PagerFlags arg_pager_flags
= 0;
54 static int arg_diff
= -1;
58 SHOW_EQUIVALENT
= 1 << 1,
59 SHOW_REDIRECTED
= 1 << 2,
60 SHOW_OVERRIDDEN
= 1 << 3,
61 SHOW_UNCHANGED
= 1 << 4,
62 SHOW_EXTENDED
= 1 << 5,
65 (SHOW_MASKED
| SHOW_EQUIVALENT
| SHOW_REDIRECTED
| SHOW_OVERRIDDEN
| SHOW_EXTENDED
)
68 static int equivalent(const char *a
, const char *b
) {
69 _cleanup_free_
char *x
= NULL
, *y
= NULL
;
72 r
= chase(a
, NULL
, CHASE_TRAIL_SLASH
, &x
, NULL
);
76 r
= chase(b
, NULL
, CHASE_TRAIL_SLASH
, &y
, NULL
);
80 return path_equal(x
, y
);
83 static int notify_override_masked(const char *top
, const char *bottom
) {
84 if (!(arg_flags
& SHOW_MASKED
))
87 printf("%s%s%s %s %s %s\n",
88 ansi_highlight_red(), "[MASKED]", ansi_normal(),
89 top
, glyph(GLYPH_ARROW_RIGHT
), bottom
);
93 static int notify_override_equivalent(const char *top
, const char *bottom
) {
94 if (!(arg_flags
& SHOW_EQUIVALENT
))
97 printf("%s%s%s %s %s %s\n",
98 ansi_highlight_green(), "[EQUIVALENT]", ansi_normal(),
99 top
, glyph(GLYPH_ARROW_RIGHT
), bottom
);
103 static int notify_override_redirected(const char *top
, const char *bottom
) {
104 if (!(arg_flags
& SHOW_REDIRECTED
))
107 printf("%s%s%s %s %s %s\n",
108 ansi_highlight(), "[REDIRECTED]", ansi_normal(),
109 top
, glyph(GLYPH_ARROW_RIGHT
), bottom
);
113 static int notify_override_overridden(const char *top
, const char *bottom
) {
114 if (!(arg_flags
& SHOW_OVERRIDDEN
))
117 printf("%s%s%s %s %s %s\n",
118 ansi_highlight(), "[OVERRIDDEN]", ansi_normal(),
119 top
, glyph(GLYPH_ARROW_RIGHT
), bottom
);
123 static int notify_override_extended(const char *top
, const char *bottom
) {
124 if (!(arg_flags
& SHOW_EXTENDED
))
127 printf("%s%s%s %s %s %s\n",
128 ansi_highlight(), "[EXTENDED]", ansi_normal(),
129 top
, glyph(GLYPH_ARROW_RIGHT
), bottom
);
133 static int notify_override_unchanged(const char *f
) {
134 if (!(arg_flags
& SHOW_UNCHANGED
))
137 printf("[UNCHANGED] %s\n", f
);
141 static int found_override(const char *top
, const char *bottom
) {
142 _cleanup_free_
char *dest
= NULL
;
149 if (null_or_empty_path(top
) > 0)
150 return notify_override_masked(top
, bottom
);
152 r
= readlink_malloc(top
, &dest
);
154 if (equivalent(dest
, bottom
) > 0)
155 return notify_override_equivalent(top
, bottom
);
157 return notify_override_redirected(top
, bottom
);
160 r
= notify_override_overridden(top
, bottom
);
168 r
= safe_fork("(diff)", FORK_RESET_SIGNALS
|FORK_DEATHSIG_SIGTERM
|FORK_CLOSE_ALL_FDS
|FORK_RLIMIT_NOFILE_SAFE
|FORK_LOG
, &pid
);
172 execlp("diff", "diff", "-us", "--", bottom
, top
, NULL
);
174 log_error_errno(errno
, "Failed to execute diff: %m");
178 (void) wait_for_terminate_and_check("diff", pid
, WAIT_LOG_ABNORMAL
);
184 DEFINE_PRIVATE_HASH_OPS_FULL(
186 char, string_hash_func
, string_compare_func
, free
,
187 OrderedHashmap
, ordered_hashmap_free
);
189 static int path_put(OrderedHashmap
**h
, const char *dir_path
, const char *filename
, bool override
) {
197 _unused_ _cleanup_free_
char *old
= NULL
;
198 free(ordered_hashmap_remove2(*h
, filename
, (void**) &old
));
199 } else if (ordered_hashmap_contains(*h
, filename
))
202 _cleanup_free_
char *p
= path_join(dir_path
, filename
);
206 _cleanup_free_
char *f
= strdup(filename
);
210 r
= ordered_hashmap_ensure_put(h
, &path_hash_ops_free_free
, f
, p
);
220 static int enumerate_dir_d(
221 OrderedHashmap
**top
,
222 OrderedHashmap
**bottom
,
223 OrderedHashmap
**drops
,
227 _cleanup_free_
char *unit
= NULL
;
228 _cleanup_free_
char *path
= NULL
;
229 _cleanup_strv_free_
char **list
= NULL
;
233 assert(!endswith(drop
, "/"));
235 path
= path_join(toppath
, drop
);
239 log_debug("Looking at %s", path
);
245 c
= strrchr(unit
, '.');
250 r
= get_files_in_directory(path
, &list
);
252 return log_error_errno(r
, "Failed to enumerate %s: %m", path
);
256 STRV_FOREACH(file
, list
) {
258 if (!endswith(*file
, ".conf"))
261 log_debug("Adding at top: %s %s %s/%s", *file
, glyph(GLYPH_ARROW_RIGHT
), path
, *file
);
262 r
= path_put(top
, path
, *file
, /* override = */ false);
266 log_debug("Adding at bottom: %s %s %s/%s", *file
, glyph(GLYPH_ARROW_RIGHT
), path
, *file
);
267 r
= path_put(bottom
, path
, *file
, /* override = */ true);
271 OrderedHashmap
*h
= ordered_hashmap_get(*drops
, unit
);
273 h
= ordered_hashmap_new(&path_hash_ops_free_free
);
276 r
= ordered_hashmap_ensure_put(drops
, &drop_hash_ops
, unit
, h
);
278 ordered_hashmap_free(h
);
286 log_debug("Adding to drops: %s %s %s %s %s/%s",
287 unit
, glyph(GLYPH_ARROW_RIGHT
), *file
, glyph(GLYPH_ARROW_RIGHT
), path
, *file
);
288 r
= path_put(&h
, path
, *file
, /* override = */ false);
293 static int enumerate_dir(
294 OrderedHashmap
**top
,
295 OrderedHashmap
**bottom
,
296 OrderedHashmap
**drops
,
297 const char *path
, bool dropins
) {
299 _cleanup_closedir_
DIR *d
= NULL
;
300 _cleanup_strv_free_
char **files
= NULL
, **dirs
= NULL
;
301 size_t n_files
= 0, n_dirs
= 0;
309 log_debug("Looking at %s", path
);
316 return log_error_errno(errno
, "Failed to open %s: %m", path
);
319 FOREACH_DIRENT_ALL(de
, d
, return -errno
) {
320 if (dropins
&& de
->d_type
== DT_DIR
&& endswith(de
->d_name
, ".d")) {
321 if (!GREEDY_REALLOC0(dirs
, n_dirs
+ 2))
324 dirs
[n_dirs
] = strdup(de
->d_name
);
330 if (!dirent_is_file(de
))
333 if (!GREEDY_REALLOC0(files
, n_files
+ 2))
336 files
[n_files
] = strdup(de
->d_name
);
345 STRV_FOREACH(t
, dirs
) {
346 r
= enumerate_dir_d(top
, bottom
, drops
, path
, *t
);
351 STRV_FOREACH(t
, files
) {
352 log_debug("Adding at top: %s %s %s/%s", *t
, glyph(GLYPH_ARROW_RIGHT
), path
, *t
);
353 r
= path_put(top
, path
, *t
, /* override = */ false);
357 log_debug("Adding at bottom: %s %s %s/%s", *t
, glyph(GLYPH_ARROW_RIGHT
), path
, *t
);
358 r
= path_put(bottom
, path
, *t
, /* override = */ true);
366 static int process_suffix(const char *suffix
, const char *onlyprefix
) {
370 assert(!startswith(suffix
, "/"));
371 assert(!strstr(suffix
, "//"));
373 bool dropins
= nulstr_contains(have_dropins
, suffix
);
375 _cleanup_ordered_hashmap_free_ OrderedHashmap
*top
= NULL
, *bottom
= NULL
, *drops
= NULL
;
376 NULSTR_FOREACH(p
, prefixes
) {
377 _cleanup_free_
char *t
= NULL
;
379 t
= path_join(p
, suffix
);
383 RET_GATHER(ret
, enumerate_dir(&top
, &bottom
, &drops
, t
, dropins
));
388 ORDERED_HASHMAP_FOREACH_KEY(f
, key
, top
) {
391 o
= ordered_hashmap_get(bottom
, key
);
394 if (!onlyprefix
|| startswith(o
, onlyprefix
)) {
395 if (path_equal(o
, f
)) {
396 notify_override_unchanged(f
);
398 r
= found_override(f
, o
);
406 OrderedHashmap
*h
= ordered_hashmap_get(drops
, key
);
408 ORDERED_HASHMAP_FOREACH(o
, h
)
409 if (!onlyprefix
|| startswith(o
, onlyprefix
))
410 n_found
+= notify_override_extended(f
, o
);
413 return ret
< 0 ? ret
: n_found
;
416 static int process_suffixes(const char *onlyprefix
) {
419 NULSTR_FOREACH(n
, suffixes
) {
420 r
= process_suffix(n
, onlyprefix
);
430 static int process_suffix_chop(const char *arg
) {
433 if (!path_is_absolute(arg
))
434 return process_suffix(arg
, NULL
);
436 /* Strip prefix from the suffix */
437 NULSTR_FOREACH(p
, prefixes
) {
440 suffix
= startswith(arg
, p
);
442 suffix
+= strspn(suffix
, "/");
444 return process_suffix(suffix
, p
);
446 return process_suffixes(arg
);
450 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
451 "Invalid suffix specification %s.", arg
);
454 static int help(void) {
455 _cleanup_free_
char *link
= NULL
;
458 r
= terminal_urlify_man("systemd-delta", "1", &link
);
462 printf("%s [OPTIONS...] [SUFFIX...]\n\n"
463 "Find overridden configuration files.\n\n"
464 " -h --help Show this help\n"
465 " --version Show package version\n"
466 " --no-pager Do not pipe output into a pager\n"
467 " --diff[=1|0] Show a diff when overridden files differ\n"
468 " -t --type=LIST... Only display a selected set of override types\n"
469 "\nSee the %s for details.\n",
470 program_invocation_short_name
,
476 static int parse_flags(const char *flag_str
, int flags
) {
478 _cleanup_free_
char *word
= NULL
;
481 r
= extract_first_word(&flag_str
, &word
, ",", EXTRACT_DONT_COALESCE_SEPARATORS
);
487 if (streq(word
, "masked"))
488 flags
|= SHOW_MASKED
;
489 else if (streq(word
, "equivalent"))
490 flags
|= SHOW_EQUIVALENT
;
491 else if (streq(word
, "redirected"))
492 flags
|= SHOW_REDIRECTED
;
493 else if (streq(word
, "overridden"))
494 flags
|= SHOW_OVERRIDDEN
;
495 else if (streq(word
, "unchanged"))
496 flags
|= SHOW_UNCHANGED
;
497 else if (streq(word
, "extended"))
498 flags
|= SHOW_EXTENDED
;
499 else if (streq(word
, "default"))
500 flags
|= SHOW_DEFAULTS
;
506 static int parse_argv(int argc
, char *argv
[]) {
509 ARG_NO_PAGER
= 0x100,
514 static const struct option options
[] = {
515 { "help", no_argument
, NULL
, 'h' },
516 { "version", no_argument
, NULL
, ARG_VERSION
},
517 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
518 { "diff", optional_argument
, NULL
, ARG_DIFF
},
519 { "type", required_argument
, NULL
, 't' },
528 while ((c
= getopt_long(argc
, argv
, "ht:", options
, NULL
)) >= 0)
539 arg_pager_flags
|= PAGER_DISABLE
;
544 f
= parse_flags(optarg
, arg_flags
);
546 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
547 "Failed to parse flags field.");
553 r
= parse_boolean_argument("--diff", optarg
, NULL
);
563 assert_not_reached();
569 static int run(int argc
, char *argv
[]) {
570 int r
, k
, n_found
= 0;
574 r
= parse_argv(argc
, argv
);
579 arg_flags
= SHOW_DEFAULTS
;
582 arg_diff
= !!(arg_flags
& SHOW_OVERRIDDEN
);
584 arg_flags
|= SHOW_OVERRIDDEN
;
586 pager_open(arg_pager_flags
);
589 for (int i
= optind
; i
< argc
; i
++) {
590 path_simplify(argv
[i
]);
592 k
= process_suffix_chop(argv
[i
]);
600 k
= process_suffixes(NULL
);
608 printf("%s%i overridden configuration files found.\n", n_found
? "\n" : "", n_found
);
612 DEFINE_MAIN_FUNCTION(run
);