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 r
= 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 r
= notify_override_overridden(top
, bottom
);
189 r
= safe_fork("(diff)", FORK_RESET_SIGNALS
|FORK_DEATHSIG
|FORK_CLOSE_ALL_FDS
|FORK_LOG
, &pid
);
193 execlp("diff", "diff", "-us", "--", bottom
, top
, NULL
);
194 log_error_errno(errno
, "Failed to execute diff: %m");
198 (void) wait_for_terminate_and_check("diff", pid
, WAIT_LOG_ABNORMAL
);
204 static int enumerate_dir_d(
206 OrderedHashmap
*bottom
,
207 OrderedHashmap
*drops
,
208 const char *toppath
, const char *drop
) {
210 _cleanup_free_
char *unit
= NULL
;
211 _cleanup_free_
char *path
= NULL
;
212 _cleanup_strv_free_
char **list
= NULL
;
217 assert(!endswith(drop
, "/"));
219 path
= strjoin(toppath
, "/", drop
);
223 log_debug("Looking at %s", path
);
229 c
= strrchr(unit
, '.');
234 r
= get_files_in_directory(path
, &list
);
236 return log_error_errno(r
, "Failed to enumerate %s: %m", path
);
240 STRV_FOREACH(file
, list
) {
246 if (!endswith(*file
, ".conf"))
249 p
= strjoin(path
, "/", *file
);
252 d
= p
+ strlen(toppath
) + 1;
254 log_debug("Adding at top: %s %s %s", d
, special_glyph(ARROW
), p
);
255 k
= ordered_hashmap_put(top
, d
, p
);
260 d
= p
+ strlen(toppath
) + 1;
261 } else if (k
!= -EEXIST
) {
266 log_debug("Adding at bottom: %s %s %s", d
, special_glyph(ARROW
), p
);
267 free(ordered_hashmap_remove(bottom
, d
));
268 k
= ordered_hashmap_put(bottom
, d
, p
);
274 h
= ordered_hashmap_get(drops
, unit
);
276 h
= ordered_hashmap_new(&string_hash_ops
);
279 ordered_hashmap_put(drops
, unit
, h
);
289 log_debug("Adding to drops: %s %s %s %s %s",
290 unit
, special_glyph(ARROW
), basename(p
), special_glyph(ARROW
), p
);
291 k
= ordered_hashmap_put(h
, basename(p
), p
);
301 static int enumerate_dir(
303 OrderedHashmap
*bottom
,
304 OrderedHashmap
*drops
,
305 const char *path
, bool dropins
) {
307 _cleanup_closedir_
DIR *d
= NULL
;
309 _cleanup_strv_free_
char **files
= NULL
, **dirs
= NULL
;
310 size_t n_files
= 0, allocated_files
= 0, n_dirs
= 0, allocated_dirs
= 0;
319 log_debug("Looking at %s", path
);
326 return log_error_errno(errno
, "Failed to open %s: %m", path
);
329 FOREACH_DIRENT_ALL(de
, d
, return -errno
) {
330 dirent_ensure_type(d
, de
);
332 if (dropins
&& de
->d_type
== DT_DIR
&& endswith(de
->d_name
, ".d")) {
333 if (!GREEDY_REALLOC0(dirs
, allocated_dirs
, n_dirs
+ 2))
336 dirs
[n_dirs
] = strdup(de
->d_name
);
342 if (!dirent_is_file(de
))
345 if (!GREEDY_REALLOC0(files
, allocated_files
, n_files
+ 2))
348 files
[n_files
] = strdup(de
->d_name
);
357 STRV_FOREACH(t
, dirs
) {
358 r
= enumerate_dir_d(top
, bottom
, drops
, path
, *t
);
363 STRV_FOREACH(t
, files
) {
364 _cleanup_free_
char *p
= NULL
;
366 p
= strjoin(path
, "/", *t
);
370 log_debug("Adding at top: %s %s %s", basename(p
), special_glyph(ARROW
), p
);
371 r
= ordered_hashmap_put(top
, basename(p
), p
);
376 } else if (r
!= -EEXIST
)
379 log_debug("Adding at bottom: %s %s %s", basename(p
), special_glyph(ARROW
), p
);
380 free(ordered_hashmap_remove(bottom
, basename(p
)));
381 r
= ordered_hashmap_put(bottom
, basename(p
), p
);
390 static int should_skip_prefix(const char* p
) {
393 _cleanup_free_
char *target
= NULL
;
395 r
= chase_symlinks(p
, NULL
, 0, &target
);
399 return !streq(p
, target
) && nulstr_contains(prefixes
, target
);
405 static int process_suffix(const char *suffix
, const char *onlyprefix
) {
408 OrderedHashmap
*top
, *bottom
, *drops
;
417 assert(!startswith(suffix
, "/"));
418 assert(!strstr(suffix
, "//"));
420 dropins
= nulstr_contains(have_dropins
, suffix
);
422 top
= ordered_hashmap_new(&string_hash_ops
);
423 bottom
= ordered_hashmap_new(&string_hash_ops
);
424 drops
= ordered_hashmap_new(&string_hash_ops
);
425 if (!top
|| !bottom
|| !drops
) {
430 NULSTR_FOREACH(p
, prefixes
) {
431 _cleanup_free_
char *t
= NULL
;
434 skip
= should_skip_prefix(p
);
442 t
= strjoin(p
, "/", suffix
);
448 k
= enumerate_dir(top
, bottom
, drops
, t
, dropins
);
453 ORDERED_HASHMAP_FOREACH_KEY(f
, key
, top
, i
) {
456 o
= ordered_hashmap_get(bottom
, key
);
459 if (!onlyprefix
|| startswith(o
, onlyprefix
)) {
460 if (path_equal(o
, f
)) {
461 notify_override_unchanged(f
);
463 k
= found_override(f
, o
);
471 h
= ordered_hashmap_get(drops
, key
);
473 ORDERED_HASHMAP_FOREACH(o
, h
, j
)
474 if (!onlyprefix
|| startswith(o
, onlyprefix
))
475 n_found
+= notify_override_extended(f
, o
);
479 ordered_hashmap_free_free(top
);
480 ordered_hashmap_free_free(bottom
);
482 ORDERED_HASHMAP_FOREACH_KEY(h
, key
, drops
, i
) {
483 ordered_hashmap_free_free(ordered_hashmap_remove(drops
, key
));
484 ordered_hashmap_remove(drops
, key
);
487 ordered_hashmap_free(drops
);
489 return r
< 0 ? r
: n_found
;
492 static int process_suffixes(const char *onlyprefix
) {
496 NULSTR_FOREACH(n
, suffixes
) {
497 r
= process_suffix(n
, onlyprefix
);
507 static int process_suffix_chop(const char *arg
) {
512 if (!path_is_absolute(arg
))
513 return process_suffix(arg
, NULL
);
515 /* Strip prefix from the suffix */
516 NULSTR_FOREACH(p
, prefixes
) {
520 skip
= should_skip_prefix(p
);
526 suffix
= startswith(arg
, p
);
528 suffix
+= strspn(suffix
, "/");
530 return process_suffix(suffix
, NULL
);
532 return process_suffixes(arg
);
536 log_error("Invalid suffix specification %s.", arg
);
540 static void help(void) {
541 printf("%s [OPTIONS...] [SUFFIX...]\n\n"
542 "Find overridden configuration files.\n\n"
543 " -h --help Show this help\n"
544 " --version Show package version\n"
545 " --no-pager Do not pipe output into a pager\n"
546 " --diff[=1|0] Show a diff when overridden files differ\n"
547 " -t --type=LIST... Only display a selected set of override types\n"
548 , program_invocation_short_name
);
551 static int parse_flags(const char *flag_str
, int flags
) {
552 const char *word
, *state
;
555 FOREACH_WORD_SEPARATOR(word
, l
, flag_str
, ",", state
) {
556 if (strneq("masked", word
, l
))
557 flags
|= SHOW_MASKED
;
558 else if (strneq ("equivalent", word
, l
))
559 flags
|= SHOW_EQUIVALENT
;
560 else if (strneq("redirected", word
, l
))
561 flags
|= SHOW_REDIRECTED
;
562 else if (strneq("overridden", word
, l
))
563 flags
|= SHOW_OVERRIDDEN
;
564 else if (strneq("unchanged", word
, l
))
565 flags
|= SHOW_UNCHANGED
;
566 else if (strneq("extended", word
, l
))
567 flags
|= SHOW_EXTENDED
;
568 else if (strneq("default", word
, l
))
569 flags
|= SHOW_DEFAULTS
;
576 static int parse_argv(int argc
, char *argv
[]) {
579 ARG_NO_PAGER
= 0x100,
584 static const struct option options
[] = {
585 { "help", no_argument
, NULL
, 'h' },
586 { "version", no_argument
, NULL
, ARG_VERSION
},
587 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
588 { "diff", optional_argument
, NULL
, ARG_DIFF
},
589 { "type", required_argument
, NULL
, 't' },
598 while ((c
= getopt_long(argc
, argv
, "ht:", options
, NULL
)) >= 0)
615 f
= parse_flags(optarg
, arg_flags
);
617 log_error("Failed to parse flags field.");
630 b
= parse_boolean(optarg
);
632 log_error("Failed to parse diff boolean.");
644 assert_not_reached("Unhandled option");
650 int main(int argc
, char *argv
[]) {
651 int r
, k
, n_found
= 0;
653 log_parse_environment();
656 r
= parse_argv(argc
, argv
);
661 arg_flags
= SHOW_DEFAULTS
;
664 arg_diff
= !!(arg_flags
& SHOW_OVERRIDDEN
);
666 arg_flags
|= SHOW_OVERRIDDEN
;
668 pager_open(arg_no_pager
, false);
673 for (i
= optind
; i
< argc
; i
++) {
674 path_kill_slashes(argv
[i
]);
676 k
= process_suffix_chop(argv
[i
]);
684 k
= process_suffixes(NULL
);
692 printf("%s%i overridden configuration files found.\n", n_found
? "\n" : "", n_found
);
697 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;