1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2012 Lennart Poettering
7 Copyright 2013 Zbigniew Jędrzejewski-Szmek
9 systemd is free software; you can redistribute it and/or modify it
10 under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation; either version 2.1 of the License, or
12 (at your option) any later version.
14 systemd is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
19 You should have received a copy of the GNU Lesser General Public License
20 along with systemd; If not, see <http://www.gnu.org/licenses/>.
26 #include <sys/prctl.h>
29 #include "dirent-util.h"
34 #include "parse-util.h"
35 #include "path-util.h"
36 #include "process-util.h"
37 #include "signal-util.h"
38 #include "string-util.h"
40 #include "terminal-util.h"
43 static const char prefixes
[] =
55 static const char suffixes
[] =
62 "systemd/system-preset\0"
63 "systemd/user-preset\0"
67 static const char have_dropins
[] =
71 static bool arg_no_pager
= false;
72 static int arg_diff
= -1;
76 SHOW_EQUIVALENT
= 1 << 1,
77 SHOW_REDIRECTED
= 1 << 2,
78 SHOW_OVERRIDDEN
= 1 << 3,
79 SHOW_UNCHANGED
= 1 << 4,
80 SHOW_EXTENDED
= 1 << 5,
83 (SHOW_MASKED
| SHOW_EQUIVALENT
| SHOW_REDIRECTED
| SHOW_OVERRIDDEN
| SHOW_EXTENDED
)
86 static void pager_open_if_enabled(void) {
94 static int equivalent(const char *a
, const char *b
) {
95 _cleanup_free_
char *x
= NULL
, *y
= NULL
;
97 x
= canonicalize_file_name(a
);
101 y
= canonicalize_file_name(b
);
105 return path_equal(x
, y
);
108 static int notify_override_masked(const char *top
, const char *bottom
) {
109 if (!(arg_flags
& SHOW_MASKED
))
112 printf("%s%s%s %s %s %s\n",
113 ansi_highlight_red(), "[MASKED]", ansi_normal(),
114 top
, draw_special_char(DRAW_ARROW
), bottom
);
118 static int notify_override_equivalent(const char *top
, const char *bottom
) {
119 if (!(arg_flags
& SHOW_EQUIVALENT
))
122 printf("%s%s%s %s %s %s\n",
123 ansi_highlight_green(), "[EQUIVALENT]", ansi_normal(),
124 top
, draw_special_char(DRAW_ARROW
), bottom
);
128 static int notify_override_redirected(const char *top
, const char *bottom
) {
129 if (!(arg_flags
& SHOW_REDIRECTED
))
132 printf("%s%s%s %s %s %s\n",
133 ansi_highlight(), "[REDIRECTED]", ansi_normal(),
134 top
, draw_special_char(DRAW_ARROW
), bottom
);
138 static int notify_override_overridden(const char *top
, const char *bottom
) {
139 if (!(arg_flags
& SHOW_OVERRIDDEN
))
142 printf("%s%s%s %s %s %s\n",
143 ansi_highlight(), "[OVERRIDDEN]", ansi_normal(),
144 top
, draw_special_char(DRAW_ARROW
), bottom
);
148 static int notify_override_extended(const char *top
, const char *bottom
) {
149 if (!(arg_flags
& SHOW_EXTENDED
))
152 printf("%s%s%s %s %s %s\n",
153 ansi_highlight(), "[EXTENDED]", ansi_normal(),
154 top
, draw_special_char(DRAW_ARROW
), bottom
);
158 static int notify_override_unchanged(const char *f
) {
159 if (!(arg_flags
& SHOW_UNCHANGED
))
162 printf("[UNCHANGED] %s\n", f
);
166 static int found_override(const char *top
, const char *bottom
) {
167 _cleanup_free_
char *dest
= NULL
;
174 if (null_or_empty_path(top
) > 0)
175 return notify_override_masked(top
, bottom
);
177 k
= readlink_malloc(top
, &dest
);
179 if (equivalent(dest
, bottom
) > 0)
180 return notify_override_equivalent(top
, bottom
);
182 return notify_override_redirected(top
, bottom
);
185 k
= notify_override_overridden(top
, bottom
);
195 return log_error_errno(errno
, "Failed to fork off diff: %m");
198 (void) reset_all_signal_handlers();
199 (void) reset_signal_mask();
200 assert_se(prctl(PR_SET_PDEATHSIG
, SIGTERM
) == 0);
202 execlp("diff", "diff", "-us", "--", bottom
, top
, NULL
);
203 log_error_errno(errno
, "Failed to execute diff: %m");
207 wait_for_terminate_and_warn("diff", pid
, false);
213 static int enumerate_dir_d(Hashmap
*top
, Hashmap
*bottom
, Hashmap
*drops
, 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
, NULL
);
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
);
242 STRV_FOREACH(file
, list
) {
248 if (!endswith(*file
, ".conf"))
251 p
= strjoin(path
, "/", *file
, NULL
);
254 d
= p
+ strlen(toppath
) + 1;
256 log_debug("Adding at top: %s %s %s", d
, draw_special_char(DRAW_ARROW
), p
);
257 k
= hashmap_put(top
, d
, p
);
262 d
= p
+ strlen(toppath
) + 1;
263 } else if (k
!= -EEXIST
) {
268 log_debug("Adding at bottom: %s %s %s", d
, draw_special_char(DRAW_ARROW
), p
);
269 free(hashmap_remove(bottom
, d
));
270 k
= hashmap_put(bottom
, d
, p
);
276 h
= hashmap_get(drops
, unit
);
278 h
= hashmap_new(&string_hash_ops
);
281 hashmap_put(drops
, unit
, h
);
291 log_debug("Adding to drops: %s %s %s %s %s",
292 unit
, draw_special_char(DRAW_ARROW
), basename(p
), draw_special_char(DRAW_ARROW
), p
);
293 k
= hashmap_put(h
, basename(p
), p
);
303 static int enumerate_dir(Hashmap
*top
, Hashmap
*bottom
, Hashmap
*drops
, const char *path
, bool dropins
) {
304 _cleanup_closedir_
DIR *d
;
311 log_debug("Looking at %s", path
);
318 log_error_errno(errno
, "Failed to open %s: %m", path
);
332 dirent_ensure_type(d
, de
);
334 if (dropins
&& de
->d_type
== DT_DIR
&& endswith(de
->d_name
, ".d"))
335 enumerate_dir_d(top
, bottom
, drops
, path
, de
->d_name
);
337 if (!dirent_is_file(de
))
340 p
= strjoin(path
, "/", de
->d_name
, NULL
);
344 log_debug("Adding at top: %s %s %s", basename(p
), draw_special_char(DRAW_ARROW
), p
);
345 k
= hashmap_put(top
, basename(p
), p
);
350 } else if (k
!= -EEXIST
) {
355 log_debug("Adding at bottom: %s %s %s", basename(p
), draw_special_char(DRAW_ARROW
), p
);
356 free(hashmap_remove(bottom
, basename(p
)));
357 k
= hashmap_put(bottom
, basename(p
), p
);
365 static int process_suffix(const char *suffix
, const char *onlyprefix
) {
368 Hashmap
*top
, *bottom
, *drops
;
377 assert(!startswith(suffix
, "/"));
378 assert(!strstr(suffix
, "//"));
380 dropins
= nulstr_contains(have_dropins
, suffix
);
382 top
= hashmap_new(&string_hash_ops
);
383 bottom
= hashmap_new(&string_hash_ops
);
384 drops
= hashmap_new(&string_hash_ops
);
385 if (!top
|| !bottom
|| !drops
) {
390 NULSTR_FOREACH(p
, prefixes
) {
391 _cleanup_free_
char *t
= NULL
;
393 t
= strjoin(p
, "/", suffix
, NULL
);
399 k
= enumerate_dir(top
, bottom
, drops
, t
, dropins
);
404 HASHMAP_FOREACH_KEY(f
, key
, top
, i
) {
407 o
= hashmap_get(bottom
, key
);
410 if (!onlyprefix
|| startswith(o
, onlyprefix
)) {
411 if (path_equal(o
, f
)) {
412 notify_override_unchanged(f
);
414 k
= found_override(f
, o
);
422 h
= hashmap_get(drops
, key
);
424 HASHMAP_FOREACH(o
, h
, j
)
425 if (!onlyprefix
|| startswith(o
, onlyprefix
))
426 n_found
+= notify_override_extended(f
, o
);
431 hashmap_free_free(top
);
433 hashmap_free_free(bottom
);
435 HASHMAP_FOREACH_KEY(h
, key
, drops
, i
){
436 hashmap_free_free(hashmap_remove(drops
, key
));
437 hashmap_remove(drops
, key
);
442 return r
< 0 ? r
: n_found
;
445 static int process_suffixes(const char *onlyprefix
) {
449 NULSTR_FOREACH(n
, suffixes
) {
450 r
= process_suffix(n
, onlyprefix
);
459 static int process_suffix_chop(const char *arg
) {
464 if (!path_is_absolute(arg
))
465 return process_suffix(arg
, NULL
);
467 /* Strip prefix from the suffix */
468 NULSTR_FOREACH(p
, prefixes
) {
469 const char *suffix
= startswith(arg
, p
);
471 suffix
+= strspn(suffix
, "/");
473 return process_suffix(suffix
, NULL
);
475 return process_suffixes(arg
);
479 log_error("Invalid suffix specification %s.", arg
);
483 static void help(void) {
484 printf("%s [OPTIONS...] [SUFFIX...]\n\n"
485 "Find overridden configuration files.\n\n"
486 " -h --help Show this help\n"
487 " --version Show package version\n"
488 " --no-pager Do not pipe output into a pager\n"
489 " --diff[=1|0] Show a diff when overridden files differ\n"
490 " -t --type=LIST... Only display a selected set of override types\n"
491 , program_invocation_short_name
);
494 static int parse_flags(const char *flag_str
, int flags
) {
495 const char *word
, *state
;
498 FOREACH_WORD_SEPARATOR(word
, l
, flag_str
, ",", state
) {
499 if (strneq("masked", word
, l
))
500 flags
|= SHOW_MASKED
;
501 else if (strneq ("equivalent", word
, l
))
502 flags
|= SHOW_EQUIVALENT
;
503 else if (strneq("redirected", word
, l
))
504 flags
|= SHOW_REDIRECTED
;
505 else if (strneq("overridden", word
, l
))
506 flags
|= SHOW_OVERRIDDEN
;
507 else if (strneq("unchanged", word
, l
))
508 flags
|= SHOW_UNCHANGED
;
509 else if (strneq("extended", word
, l
))
510 flags
|= SHOW_EXTENDED
;
511 else if (strneq("default", word
, l
))
512 flags
|= SHOW_DEFAULTS
;
519 static int parse_argv(int argc
, char *argv
[]) {
522 ARG_NO_PAGER
= 0x100,
527 static const struct option options
[] = {
528 { "help", no_argument
, NULL
, 'h' },
529 { "version", no_argument
, NULL
, ARG_VERSION
},
530 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
531 { "diff", optional_argument
, NULL
, ARG_DIFF
},
532 { "type", required_argument
, NULL
, 't' },
541 while ((c
= getopt_long(argc
, argv
, "ht:", options
, NULL
)) >= 0)
558 f
= parse_flags(optarg
, arg_flags
);
560 log_error("Failed to parse flags field.");
573 b
= parse_boolean(optarg
);
575 log_error("Failed to parse diff boolean.");
588 assert_not_reached("Unhandled option");
594 int main(int argc
, char *argv
[]) {
598 log_parse_environment();
601 r
= parse_argv(argc
, argv
);
606 arg_flags
= SHOW_DEFAULTS
;
609 arg_diff
= !!(arg_flags
& SHOW_OVERRIDDEN
);
611 arg_flags
|= SHOW_OVERRIDDEN
;
613 pager_open_if_enabled();
618 for (i
= optind
; i
< argc
; i
++) {
619 path_kill_slashes(argv
[i
]);
620 k
= process_suffix_chop(argv
[i
]);
628 k
= process_suffixes(NULL
);
636 printf("%s%i overridden configuration files found.\n",
637 n_found
? "\n" : "", n_found
);
642 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;