2 This file is part of systemd.
4 Copyright 2012 Lennart Poettering
5 Copyright 2013 Zbigniew Jędrzejewski-Szmek
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
24 #include <sys/prctl.h>
27 #include "alloc-util.h"
28 #include "dirent-util.h"
32 #include "locale-util.h"
35 #include "parse-util.h"
36 #include "path-util.h"
37 #include "process-util.h"
38 #include "signal-util.h"
39 #include "stat-util.h"
40 #include "string-util.h"
42 #include "terminal-util.h"
45 static const char prefixes
[] =
57 static const char suffixes
[] =
64 "systemd/system-preset\0"
65 "systemd/user-preset\0"
69 static const char have_dropins
[] =
73 static bool arg_no_pager
= false;
74 static int arg_diff
= -1;
78 SHOW_EQUIVALENT
= 1 << 1,
79 SHOW_REDIRECTED
= 1 << 2,
80 SHOW_OVERRIDDEN
= 1 << 3,
81 SHOW_UNCHANGED
= 1 << 4,
82 SHOW_EXTENDED
= 1 << 5,
85 (SHOW_MASKED
| SHOW_EQUIVALENT
| SHOW_REDIRECTED
| SHOW_OVERRIDDEN
| SHOW_EXTENDED
)
88 static int equivalent(const char *a
, const char *b
) {
89 _cleanup_free_
char *x
= NULL
, *y
= NULL
;
92 r
= chase_symlinks(a
, NULL
, 0, &x
);
96 r
= chase_symlinks(b
, NULL
, 0, &y
);
100 return path_equal(x
, y
);
103 static int notify_override_masked(const char *top
, const char *bottom
) {
104 if (!(arg_flags
& SHOW_MASKED
))
107 printf("%s%s%s %s %s %s\n",
108 ansi_highlight_red(), "[MASKED]", ansi_normal(),
109 top
, special_glyph(ARROW
), bottom
);
113 static int notify_override_equivalent(const char *top
, const char *bottom
) {
114 if (!(arg_flags
& SHOW_EQUIVALENT
))
117 printf("%s%s%s %s %s %s\n",
118 ansi_highlight_green(), "[EQUIVALENT]", ansi_normal(),
119 top
, special_glyph(ARROW
), bottom
);
123 static int notify_override_redirected(const char *top
, const char *bottom
) {
124 if (!(arg_flags
& SHOW_REDIRECTED
))
127 printf("%s%s%s %s %s %s\n",
128 ansi_highlight(), "[REDIRECTED]", ansi_normal(),
129 top
, special_glyph(ARROW
), bottom
);
133 static int notify_override_overridden(const char *top
, const char *bottom
) {
134 if (!(arg_flags
& SHOW_OVERRIDDEN
))
137 printf("%s%s%s %s %s %s\n",
138 ansi_highlight(), "[OVERRIDDEN]", ansi_normal(),
139 top
, special_glyph(ARROW
), bottom
);
143 static int notify_override_extended(const char *top
, const char *bottom
) {
144 if (!(arg_flags
& SHOW_EXTENDED
))
147 printf("%s%s%s %s %s %s\n",
148 ansi_highlight(), "[EXTENDED]", ansi_normal(),
149 top
, special_glyph(ARROW
), bottom
);
153 static int notify_override_unchanged(const char *f
) {
154 if (!(arg_flags
& SHOW_UNCHANGED
))
157 printf("[UNCHANGED] %s\n", f
);
161 static int found_override(const char *top
, const char *bottom
) {
162 _cleanup_free_
char *dest
= NULL
;
169 if (null_or_empty_path(top
) > 0)
170 return notify_override_masked(top
, bottom
);
172 k
= readlink_malloc(top
, &dest
);
174 if (equivalent(dest
, bottom
) > 0)
175 return notify_override_equivalent(top
, bottom
);
177 return notify_override_redirected(top
, bottom
);
180 k
= notify_override_overridden(top
, bottom
);
190 return log_error_errno(errno
, "Failed to fork off diff: %m");
193 (void) reset_all_signal_handlers();
194 (void) reset_signal_mask();
195 assert_se(prctl(PR_SET_PDEATHSIG
, SIGTERM
) == 0);
197 execlp("diff", "diff", "-us", "--", bottom
, top
, NULL
);
198 log_error_errno(errno
, "Failed to execute diff: %m");
202 wait_for_terminate_and_warn("diff", pid
, false);
208 static int enumerate_dir_d(
210 OrderedHashmap
*bottom
,
211 OrderedHashmap
*drops
,
212 const char *toppath
, const char *drop
) {
214 _cleanup_free_
char *unit
= NULL
;
215 _cleanup_free_
char *path
= NULL
;
216 _cleanup_strv_free_
char **list
= NULL
;
221 assert(!endswith(drop
, "/"));
223 path
= strjoin(toppath
, "/", drop
);
227 log_debug("Looking at %s", path
);
233 c
= strrchr(unit
, '.');
238 r
= get_files_in_directory(path
, &list
);
240 return log_error_errno(r
, "Failed to enumerate %s: %m", path
);
244 STRV_FOREACH(file
, list
) {
250 if (!endswith(*file
, ".conf"))
253 p
= strjoin(path
, "/", *file
);
256 d
= p
+ strlen(toppath
) + 1;
258 log_debug("Adding at top: %s %s %s", d
, special_glyph(ARROW
), p
);
259 k
= ordered_hashmap_put(top
, d
, p
);
264 d
= p
+ strlen(toppath
) + 1;
265 } else if (k
!= -EEXIST
) {
270 log_debug("Adding at bottom: %s %s %s", d
, special_glyph(ARROW
), p
);
271 free(ordered_hashmap_remove(bottom
, d
));
272 k
= ordered_hashmap_put(bottom
, d
, p
);
278 h
= ordered_hashmap_get(drops
, unit
);
280 h
= ordered_hashmap_new(&string_hash_ops
);
283 ordered_hashmap_put(drops
, unit
, h
);
293 log_debug("Adding to drops: %s %s %s %s %s",
294 unit
, special_glyph(ARROW
), basename(p
), special_glyph(ARROW
), p
);
295 k
= ordered_hashmap_put(h
, basename(p
), p
);
305 static int enumerate_dir(
307 OrderedHashmap
*bottom
,
308 OrderedHashmap
*drops
,
309 const char *path
, bool dropins
) {
311 _cleanup_closedir_
DIR *d
= NULL
;
313 _cleanup_strv_free_
char **files
= NULL
, **dirs
= NULL
;
314 size_t n_files
= 0, allocated_files
= 0, n_dirs
= 0, allocated_dirs
= 0;
323 log_debug("Looking at %s", path
);
330 return log_error_errno(errno
, "Failed to open %s: %m", path
);
333 FOREACH_DIRENT_ALL(de
, d
, return -errno
) {
334 dirent_ensure_type(d
, de
);
336 if (dropins
&& de
->d_type
== DT_DIR
&& endswith(de
->d_name
, ".d")) {
337 if (!GREEDY_REALLOC0(dirs
, allocated_dirs
, n_dirs
+ 2))
340 dirs
[n_dirs
] = strdup(de
->d_name
);
346 if (!dirent_is_file(de
))
349 if (!GREEDY_REALLOC0(files
, allocated_files
, n_files
+ 2))
352 files
[n_files
] = strdup(de
->d_name
);
361 STRV_FOREACH(t
, dirs
) {
362 r
= enumerate_dir_d(top
, bottom
, drops
, path
, *t
);
367 STRV_FOREACH(t
, files
) {
368 _cleanup_free_
char *p
= NULL
;
370 p
= strjoin(path
, "/", *t
);
374 log_debug("Adding at top: %s %s %s", basename(p
), special_glyph(ARROW
), p
);
375 r
= ordered_hashmap_put(top
, basename(p
), p
);
380 } else if (r
!= -EEXIST
)
383 log_debug("Adding at bottom: %s %s %s", basename(p
), special_glyph(ARROW
), p
);
384 free(ordered_hashmap_remove(bottom
, basename(p
)));
385 r
= ordered_hashmap_put(bottom
, basename(p
), p
);
394 static int should_skip_prefix(const char* p
) {
397 _cleanup_free_
char *target
= NULL
;
399 r
= chase_symlinks(p
, NULL
, 0, &target
);
403 return !streq(p
, target
) && nulstr_contains(prefixes
, target
);
409 static int process_suffix(const char *suffix
, const char *onlyprefix
) {
412 OrderedHashmap
*top
, *bottom
, *drops
;
421 assert(!startswith(suffix
, "/"));
422 assert(!strstr(suffix
, "//"));
424 dropins
= nulstr_contains(have_dropins
, suffix
);
426 top
= ordered_hashmap_new(&string_hash_ops
);
427 bottom
= ordered_hashmap_new(&string_hash_ops
);
428 drops
= ordered_hashmap_new(&string_hash_ops
);
429 if (!top
|| !bottom
|| !drops
) {
434 NULSTR_FOREACH(p
, prefixes
) {
435 _cleanup_free_
char *t
= NULL
;
438 skip
= should_skip_prefix(p
);
446 t
= strjoin(p
, "/", suffix
);
452 k
= enumerate_dir(top
, bottom
, drops
, t
, dropins
);
457 ORDERED_HASHMAP_FOREACH_KEY(f
, key
, top
, i
) {
460 o
= ordered_hashmap_get(bottom
, key
);
463 if (!onlyprefix
|| startswith(o
, onlyprefix
)) {
464 if (path_equal(o
, f
)) {
465 notify_override_unchanged(f
);
467 k
= found_override(f
, o
);
475 h
= ordered_hashmap_get(drops
, key
);
477 ORDERED_HASHMAP_FOREACH(o
, h
, j
)
478 if (!onlyprefix
|| startswith(o
, onlyprefix
))
479 n_found
+= notify_override_extended(f
, o
);
483 ordered_hashmap_free_free(top
);
484 ordered_hashmap_free_free(bottom
);
486 ORDERED_HASHMAP_FOREACH_KEY(h
, key
, drops
, i
) {
487 ordered_hashmap_free_free(ordered_hashmap_remove(drops
, key
));
488 ordered_hashmap_remove(drops
, key
);
491 ordered_hashmap_free(drops
);
493 return r
< 0 ? r
: n_found
;
496 static int process_suffixes(const char *onlyprefix
) {
500 NULSTR_FOREACH(n
, suffixes
) {
501 r
= process_suffix(n
, onlyprefix
);
511 static int process_suffix_chop(const char *arg
) {
516 if (!path_is_absolute(arg
))
517 return process_suffix(arg
, NULL
);
519 /* Strip prefix from the suffix */
520 NULSTR_FOREACH(p
, prefixes
) {
524 skip
= should_skip_prefix(p
);
530 suffix
= startswith(arg
, p
);
532 suffix
+= strspn(suffix
, "/");
534 return process_suffix(suffix
, NULL
);
536 return process_suffixes(arg
);
540 log_error("Invalid suffix specification %s.", arg
);
544 static void help(void) {
545 printf("%s [OPTIONS...] [SUFFIX...]\n\n"
546 "Find overridden configuration files.\n\n"
547 " -h --help Show this help\n"
548 " --version Show package version\n"
549 " --no-pager Do not pipe output into a pager\n"
550 " --diff[=1|0] Show a diff when overridden files differ\n"
551 " -t --type=LIST... Only display a selected set of override types\n"
552 , program_invocation_short_name
);
555 static int parse_flags(const char *flag_str
, int flags
) {
556 const char *word
, *state
;
559 FOREACH_WORD_SEPARATOR(word
, l
, flag_str
, ",", state
) {
560 if (strneq("masked", word
, l
))
561 flags
|= SHOW_MASKED
;
562 else if (strneq ("equivalent", word
, l
))
563 flags
|= SHOW_EQUIVALENT
;
564 else if (strneq("redirected", word
, l
))
565 flags
|= SHOW_REDIRECTED
;
566 else if (strneq("overridden", word
, l
))
567 flags
|= SHOW_OVERRIDDEN
;
568 else if (strneq("unchanged", word
, l
))
569 flags
|= SHOW_UNCHANGED
;
570 else if (strneq("extended", word
, l
))
571 flags
|= SHOW_EXTENDED
;
572 else if (strneq("default", word
, l
))
573 flags
|= SHOW_DEFAULTS
;
580 static int parse_argv(int argc
, char *argv
[]) {
583 ARG_NO_PAGER
= 0x100,
588 static const struct option options
[] = {
589 { "help", no_argument
, NULL
, 'h' },
590 { "version", no_argument
, NULL
, ARG_VERSION
},
591 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
592 { "diff", optional_argument
, NULL
, ARG_DIFF
},
593 { "type", required_argument
, NULL
, 't' },
602 while ((c
= getopt_long(argc
, argv
, "ht:", options
, NULL
)) >= 0)
619 f
= parse_flags(optarg
, arg_flags
);
621 log_error("Failed to parse flags field.");
634 b
= parse_boolean(optarg
);
636 log_error("Failed to parse diff boolean.");
648 assert_not_reached("Unhandled option");
654 int main(int argc
, char *argv
[]) {
655 int r
, k
, n_found
= 0;
657 log_parse_environment();
660 r
= parse_argv(argc
, argv
);
665 arg_flags
= SHOW_DEFAULTS
;
668 arg_diff
= !!(arg_flags
& SHOW_OVERRIDDEN
);
670 arg_flags
|= SHOW_OVERRIDDEN
;
672 pager_open(arg_no_pager
, false);
677 for (i
= optind
; i
< argc
; i
++) {
678 path_kill_slashes(argv
[i
]);
680 k
= process_suffix_chop(argv
[i
]);
688 k
= process_suffixes(NULL
);
696 printf("%s%i overridden configuration files found.\n", n_found
? "\n" : "", n_found
);
701 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;