1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2012 Lennart Poettering
6 Copyright 2013 Zbigniew Jędrzejewski-Szmek
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
25 #include <sys/prctl.h>
28 #include "alloc-util.h"
29 #include "dirent-util.h"
33 #include "locale-util.h"
36 #include "parse-util.h"
37 #include "path-util.h"
38 #include "process-util.h"
39 #include "signal-util.h"
40 #include "stat-util.h"
41 #include "string-util.h"
43 #include "terminal-util.h"
46 static const char prefixes
[] =
58 static const char suffixes
[] =
65 "systemd/system-preset\0"
66 "systemd/user-preset\0"
70 static const char have_dropins
[] =
74 static bool arg_no_pager
= false;
75 static int arg_diff
= -1;
79 SHOW_EQUIVALENT
= 1 << 1,
80 SHOW_REDIRECTED
= 1 << 2,
81 SHOW_OVERRIDDEN
= 1 << 3,
82 SHOW_UNCHANGED
= 1 << 4,
83 SHOW_EXTENDED
= 1 << 5,
86 (SHOW_MASKED
| SHOW_EQUIVALENT
| SHOW_REDIRECTED
| SHOW_OVERRIDDEN
| SHOW_EXTENDED
)
89 static int equivalent(const char *a
, const char *b
) {
90 _cleanup_free_
char *x
= NULL
, *y
= NULL
;
93 r
= chase_symlinks(a
, NULL
, 0, &x
);
97 r
= chase_symlinks(b
, NULL
, 0, &y
);
101 return path_equal(x
, y
);
104 static int notify_override_masked(const char *top
, const char *bottom
) {
105 if (!(arg_flags
& SHOW_MASKED
))
108 printf("%s%s%s %s %s %s\n",
109 ansi_highlight_red(), "[MASKED]", ansi_normal(),
110 top
, special_glyph(ARROW
), bottom
);
114 static int notify_override_equivalent(const char *top
, const char *bottom
) {
115 if (!(arg_flags
& SHOW_EQUIVALENT
))
118 printf("%s%s%s %s %s %s\n",
119 ansi_highlight_green(), "[EQUIVALENT]", ansi_normal(),
120 top
, special_glyph(ARROW
), bottom
);
124 static int notify_override_redirected(const char *top
, const char *bottom
) {
125 if (!(arg_flags
& SHOW_REDIRECTED
))
128 printf("%s%s%s %s %s %s\n",
129 ansi_highlight(), "[REDIRECTED]", ansi_normal(),
130 top
, special_glyph(ARROW
), bottom
);
134 static int notify_override_overridden(const char *top
, const char *bottom
) {
135 if (!(arg_flags
& SHOW_OVERRIDDEN
))
138 printf("%s%s%s %s %s %s\n",
139 ansi_highlight(), "[OVERRIDDEN]", ansi_normal(),
140 top
, special_glyph(ARROW
), bottom
);
144 static int notify_override_extended(const char *top
, const char *bottom
) {
145 if (!(arg_flags
& SHOW_EXTENDED
))
148 printf("%s%s%s %s %s %s\n",
149 ansi_highlight(), "[EXTENDED]", ansi_normal(),
150 top
, special_glyph(ARROW
), bottom
);
154 static int notify_override_unchanged(const char *f
) {
155 if (!(arg_flags
& SHOW_UNCHANGED
))
158 printf("[UNCHANGED] %s\n", f
);
162 static int found_override(const char *top
, const char *bottom
) {
163 _cleanup_free_
char *dest
= NULL
;
170 if (null_or_empty_path(top
) > 0)
171 return notify_override_masked(top
, bottom
);
173 k
= readlink_malloc(top
, &dest
);
175 if (equivalent(dest
, bottom
) > 0)
176 return notify_override_equivalent(top
, bottom
);
178 return notify_override_redirected(top
, bottom
);
181 k
= notify_override_overridden(top
, bottom
);
191 return log_error_errno(errno
, "Failed to fork off diff: %m");
194 (void) reset_all_signal_handlers();
195 (void) reset_signal_mask();
196 assert_se(prctl(PR_SET_PDEATHSIG
, SIGTERM
) == 0);
198 execlp("diff", "diff", "-us", "--", bottom
, top
, NULL
);
199 log_error_errno(errno
, "Failed to execute diff: %m");
203 wait_for_terminate_and_warn("diff", pid
, false);
209 static int enumerate_dir_d(
211 OrderedHashmap
*bottom
,
212 OrderedHashmap
*drops
,
213 const char *toppath
, const char *drop
) {
215 _cleanup_free_
char *unit
= NULL
;
216 _cleanup_free_
char *path
= NULL
;
217 _cleanup_strv_free_
char **list
= NULL
;
222 assert(!endswith(drop
, "/"));
224 path
= strjoin(toppath
, "/", drop
);
228 log_debug("Looking at %s", path
);
234 c
= strrchr(unit
, '.');
239 r
= get_files_in_directory(path
, &list
);
241 return log_error_errno(r
, "Failed to enumerate %s: %m", path
);
245 STRV_FOREACH(file
, list
) {
251 if (!endswith(*file
, ".conf"))
254 p
= strjoin(path
, "/", *file
);
257 d
= p
+ strlen(toppath
) + 1;
259 log_debug("Adding at top: %s %s %s", d
, special_glyph(ARROW
), p
);
260 k
= ordered_hashmap_put(top
, d
, p
);
265 d
= p
+ strlen(toppath
) + 1;
266 } else if (k
!= -EEXIST
) {
271 log_debug("Adding at bottom: %s %s %s", d
, special_glyph(ARROW
), p
);
272 free(ordered_hashmap_remove(bottom
, d
));
273 k
= ordered_hashmap_put(bottom
, d
, p
);
279 h
= ordered_hashmap_get(drops
, unit
);
281 h
= ordered_hashmap_new(&string_hash_ops
);
284 ordered_hashmap_put(drops
, unit
, h
);
294 log_debug("Adding to drops: %s %s %s %s %s",
295 unit
, special_glyph(ARROW
), basename(p
), special_glyph(ARROW
), p
);
296 k
= ordered_hashmap_put(h
, basename(p
), p
);
306 static int enumerate_dir(
308 OrderedHashmap
*bottom
,
309 OrderedHashmap
*drops
,
310 const char *path
, bool dropins
) {
312 _cleanup_closedir_
DIR *d
= NULL
;
314 _cleanup_strv_free_
char **files
= NULL
, **dirs
= NULL
;
315 size_t n_files
= 0, allocated_files
= 0, n_dirs
= 0, allocated_dirs
= 0;
324 log_debug("Looking at %s", path
);
331 return log_error_errno(errno
, "Failed to open %s: %m", path
);
334 FOREACH_DIRENT_ALL(de
, d
, return -errno
) {
335 dirent_ensure_type(d
, de
);
337 if (dropins
&& de
->d_type
== DT_DIR
&& endswith(de
->d_name
, ".d")) {
338 if (!GREEDY_REALLOC0(dirs
, allocated_dirs
, n_dirs
+ 2))
341 dirs
[n_dirs
] = strdup(de
->d_name
);
347 if (!dirent_is_file(de
))
350 if (!GREEDY_REALLOC0(files
, allocated_files
, n_files
+ 2))
353 files
[n_files
] = strdup(de
->d_name
);
362 STRV_FOREACH(t
, dirs
) {
363 r
= enumerate_dir_d(top
, bottom
, drops
, path
, *t
);
368 STRV_FOREACH(t
, files
) {
369 _cleanup_free_
char *p
= NULL
;
371 p
= strjoin(path
, "/", *t
);
375 log_debug("Adding at top: %s %s %s", basename(p
), special_glyph(ARROW
), p
);
376 r
= ordered_hashmap_put(top
, basename(p
), p
);
381 } else if (r
!= -EEXIST
)
384 log_debug("Adding at bottom: %s %s %s", basename(p
), special_glyph(ARROW
), p
);
385 free(ordered_hashmap_remove(bottom
, basename(p
)));
386 r
= ordered_hashmap_put(bottom
, basename(p
), p
);
395 static bool should_skip_path(const char *prefix
, const char *suffix
) {
397 _cleanup_free_
char *target
= NULL
;
401 dirname
= strjoina(prefix
, "/", suffix
);
403 if (chase_symlinks(dirname
, NULL
, 0, &target
) < 0)
406 NULSTR_FOREACH(p
, prefixes
) {
407 if (path_startswith(dirname
, p
))
410 if (path_equal(target
, strjoina(p
, "/", suffix
))) {
411 log_debug("%s redirects to %s, skipping.", dirname
, target
);
419 static int process_suffix(const char *suffix
, const char *onlyprefix
) {
422 OrderedHashmap
*top
, *bottom
, *drops
;
431 assert(!startswith(suffix
, "/"));
432 assert(!strstr(suffix
, "//"));
434 dropins
= nulstr_contains(have_dropins
, suffix
);
436 top
= ordered_hashmap_new(&string_hash_ops
);
437 bottom
= ordered_hashmap_new(&string_hash_ops
);
438 drops
= ordered_hashmap_new(&string_hash_ops
);
439 if (!top
|| !bottom
|| !drops
) {
444 NULSTR_FOREACH(p
, prefixes
) {
445 _cleanup_free_
char *t
= NULL
;
447 if (should_skip_path(p
, suffix
))
450 t
= strjoin(p
, "/", suffix
);
456 k
= enumerate_dir(top
, bottom
, drops
, t
, dropins
);
461 ORDERED_HASHMAP_FOREACH_KEY(f
, key
, top
, i
) {
464 o
= ordered_hashmap_get(bottom
, key
);
467 if (!onlyprefix
|| startswith(o
, onlyprefix
)) {
468 if (path_equal(o
, f
)) {
469 notify_override_unchanged(f
);
471 k
= found_override(f
, o
);
479 h
= ordered_hashmap_get(drops
, key
);
481 ORDERED_HASHMAP_FOREACH(o
, h
, j
)
482 if (!onlyprefix
|| startswith(o
, onlyprefix
))
483 n_found
+= notify_override_extended(f
, o
);
487 ordered_hashmap_free_free(top
);
488 ordered_hashmap_free_free(bottom
);
490 ORDERED_HASHMAP_FOREACH_KEY(h
, key
, drops
, i
) {
491 ordered_hashmap_free_free(ordered_hashmap_remove(drops
, key
));
492 ordered_hashmap_remove(drops
, key
);
495 ordered_hashmap_free(drops
);
497 return r
< 0 ? r
: n_found
;
500 static int process_suffixes(const char *onlyprefix
) {
504 NULSTR_FOREACH(n
, suffixes
) {
505 r
= process_suffix(n
, onlyprefix
);
515 static int process_suffix_chop(const char *arg
) {
520 if (!path_is_absolute(arg
))
521 return process_suffix(arg
, NULL
);
523 /* Strip prefix from the suffix */
524 NULSTR_FOREACH(p
, prefixes
) {
527 suffix
= startswith(arg
, p
);
529 suffix
+= strspn(suffix
, "/");
531 return process_suffix(suffix
, p
);
533 return process_suffixes(arg
);
537 log_error("Invalid suffix specification %s.", arg
);
541 static void help(void) {
542 printf("%s [OPTIONS...] [SUFFIX...]\n\n"
543 "Find overridden configuration files.\n\n"
544 " -h --help Show this help\n"
545 " --version Show package version\n"
546 " --no-pager Do not pipe output into a pager\n"
547 " --diff[=1|0] Show a diff when overridden files differ\n"
548 " -t --type=LIST... Only display a selected set of override types\n"
549 , program_invocation_short_name
);
552 static int parse_flags(const char *flag_str
, int flags
) {
553 const char *word
, *state
;
556 FOREACH_WORD_SEPARATOR(word
, l
, flag_str
, ",", state
) {
557 if (strneq("masked", word
, l
))
558 flags
|= SHOW_MASKED
;
559 else if (strneq ("equivalent", word
, l
))
560 flags
|= SHOW_EQUIVALENT
;
561 else if (strneq("redirected", word
, l
))
562 flags
|= SHOW_REDIRECTED
;
563 else if (strneq("overridden", word
, l
))
564 flags
|= SHOW_OVERRIDDEN
;
565 else if (strneq("unchanged", word
, l
))
566 flags
|= SHOW_UNCHANGED
;
567 else if (strneq("extended", word
, l
))
568 flags
|= SHOW_EXTENDED
;
569 else if (strneq("default", word
, l
))
570 flags
|= SHOW_DEFAULTS
;
577 static int parse_argv(int argc
, char *argv
[]) {
580 ARG_NO_PAGER
= 0x100,
585 static const struct option options
[] = {
586 { "help", no_argument
, NULL
, 'h' },
587 { "version", no_argument
, NULL
, ARG_VERSION
},
588 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
589 { "diff", optional_argument
, NULL
, ARG_DIFF
},
590 { "type", required_argument
, NULL
, 't' },
599 while ((c
= getopt_long(argc
, argv
, "ht:", options
, NULL
)) >= 0)
616 f
= parse_flags(optarg
, arg_flags
);
618 log_error("Failed to parse flags field.");
631 b
= parse_boolean(optarg
);
633 log_error("Failed to parse diff boolean.");
645 assert_not_reached("Unhandled option");
651 int main(int argc
, char *argv
[]) {
652 int r
, k
, n_found
= 0;
654 log_parse_environment();
657 r
= parse_argv(argc
, argv
);
662 arg_flags
= SHOW_DEFAULTS
;
665 arg_diff
= !!(arg_flags
& SHOW_OVERRIDDEN
);
667 arg_flags
|= SHOW_OVERRIDDEN
;
669 pager_open(arg_no_pager
, false);
674 for (i
= optind
; i
< argc
; i
++) {
675 path_kill_slashes(argv
[i
]);
677 k
= process_suffix_chop(argv
[i
]);
685 k
= process_suffixes(NULL
);
693 printf("%s%i overridden configuration files found.\n", n_found
? "\n" : "", n_found
);
698 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;