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 int should_skip_prefix(const char* p
) {
398 _cleanup_free_
char *target
= NULL
;
400 r
= chase_symlinks(p
, NULL
, 0, &target
);
404 return !streq(p
, target
) && nulstr_contains(prefixes
, target
);
410 static int process_suffix(const char *suffix
, const char *onlyprefix
) {
413 OrderedHashmap
*top
, *bottom
, *drops
;
422 assert(!startswith(suffix
, "/"));
423 assert(!strstr(suffix
, "//"));
425 dropins
= nulstr_contains(have_dropins
, suffix
);
427 top
= ordered_hashmap_new(&string_hash_ops
);
428 bottom
= ordered_hashmap_new(&string_hash_ops
);
429 drops
= ordered_hashmap_new(&string_hash_ops
);
430 if (!top
|| !bottom
|| !drops
) {
435 NULSTR_FOREACH(p
, prefixes
) {
436 _cleanup_free_
char *t
= NULL
;
439 skip
= should_skip_prefix(p
);
447 t
= strjoin(p
, "/", suffix
);
453 k
= enumerate_dir(top
, bottom
, drops
, t
, dropins
);
458 ORDERED_HASHMAP_FOREACH_KEY(f
, key
, top
, i
) {
461 o
= ordered_hashmap_get(bottom
, key
);
464 if (!onlyprefix
|| startswith(o
, onlyprefix
)) {
465 if (path_equal(o
, f
)) {
466 notify_override_unchanged(f
);
468 k
= found_override(f
, o
);
476 h
= ordered_hashmap_get(drops
, key
);
478 ORDERED_HASHMAP_FOREACH(o
, h
, j
)
479 if (!onlyprefix
|| startswith(o
, onlyprefix
))
480 n_found
+= notify_override_extended(f
, o
);
484 ordered_hashmap_free_free(top
);
485 ordered_hashmap_free_free(bottom
);
487 ORDERED_HASHMAP_FOREACH_KEY(h
, key
, drops
, i
) {
488 ordered_hashmap_free_free(ordered_hashmap_remove(drops
, key
));
489 ordered_hashmap_remove(drops
, key
);
492 ordered_hashmap_free(drops
);
494 return r
< 0 ? r
: n_found
;
497 static int process_suffixes(const char *onlyprefix
) {
501 NULSTR_FOREACH(n
, suffixes
) {
502 r
= process_suffix(n
, onlyprefix
);
512 static int process_suffix_chop(const char *arg
) {
517 if (!path_is_absolute(arg
))
518 return process_suffix(arg
, NULL
);
520 /* Strip prefix from the suffix */
521 NULSTR_FOREACH(p
, prefixes
) {
525 skip
= should_skip_prefix(p
);
531 suffix
= startswith(arg
, p
);
533 suffix
+= strspn(suffix
, "/");
535 return process_suffix(suffix
, NULL
);
537 return process_suffixes(arg
);
541 log_error("Invalid suffix specification %s.", arg
);
545 static void help(void) {
546 printf("%s [OPTIONS...] [SUFFIX...]\n\n"
547 "Find overridden configuration files.\n\n"
548 " -h --help Show this help\n"
549 " --version Show package version\n"
550 " --no-pager Do not pipe output into a pager\n"
551 " --diff[=1|0] Show a diff when overridden files differ\n"
552 " -t --type=LIST... Only display a selected set of override types\n"
553 , program_invocation_short_name
);
556 static int parse_flags(const char *flag_str
, int flags
) {
557 const char *word
, *state
;
560 FOREACH_WORD_SEPARATOR(word
, l
, flag_str
, ",", state
) {
561 if (strneq("masked", word
, l
))
562 flags
|= SHOW_MASKED
;
563 else if (strneq ("equivalent", word
, l
))
564 flags
|= SHOW_EQUIVALENT
;
565 else if (strneq("redirected", word
, l
))
566 flags
|= SHOW_REDIRECTED
;
567 else if (strneq("overridden", word
, l
))
568 flags
|= SHOW_OVERRIDDEN
;
569 else if (strneq("unchanged", word
, l
))
570 flags
|= SHOW_UNCHANGED
;
571 else if (strneq("extended", word
, l
))
572 flags
|= SHOW_EXTENDED
;
573 else if (strneq("default", word
, l
))
574 flags
|= SHOW_DEFAULTS
;
581 static int parse_argv(int argc
, char *argv
[]) {
584 ARG_NO_PAGER
= 0x100,
589 static const struct option options
[] = {
590 { "help", no_argument
, NULL
, 'h' },
591 { "version", no_argument
, NULL
, ARG_VERSION
},
592 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
593 { "diff", optional_argument
, NULL
, ARG_DIFF
},
594 { "type", required_argument
, NULL
, 't' },
603 while ((c
= getopt_long(argc
, argv
, "ht:", options
, NULL
)) >= 0)
620 f
= parse_flags(optarg
, arg_flags
);
622 log_error("Failed to parse flags field.");
635 b
= parse_boolean(optarg
);
637 log_error("Failed to parse diff boolean.");
649 assert_not_reached("Unhandled option");
655 int main(int argc
, char *argv
[]) {
656 int r
, k
, n_found
= 0;
658 log_parse_environment();
661 r
= parse_argv(argc
, argv
);
666 arg_flags
= SHOW_DEFAULTS
;
669 arg_diff
= !!(arg_flags
& SHOW_OVERRIDDEN
);
671 arg_flags
|= SHOW_OVERRIDDEN
;
673 pager_open(arg_no_pager
, false);
678 for (i
= optind
; i
< argc
; i
++) {
679 path_kill_slashes(argv
[i
]);
681 k
= process_suffix_chop(argv
[i
]);
689 k
= process_suffixes(NULL
);
697 printf("%s%i overridden configuration files found.\n", n_found
? "\n" : "", n_found
);
702 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;