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>
32 #include "path-util.h"
33 #include "process-util.h"
34 #include "signal-util.h"
35 #include "string-util.h"
37 #include "terminal-util.h"
40 static const char prefixes
[] =
52 static const char suffixes
[] =
59 "systemd/system-preset\0"
60 "systemd/user-preset\0"
64 static const char have_dropins
[] =
68 static bool arg_no_pager
= false;
69 static int arg_diff
= -1;
73 SHOW_EQUIVALENT
= 1 << 1,
74 SHOW_REDIRECTED
= 1 << 2,
75 SHOW_OVERRIDDEN
= 1 << 3,
76 SHOW_UNCHANGED
= 1 << 4,
77 SHOW_EXTENDED
= 1 << 5,
80 (SHOW_MASKED
| SHOW_EQUIVALENT
| SHOW_REDIRECTED
| SHOW_OVERRIDDEN
| SHOW_EXTENDED
)
83 static void pager_open_if_enabled(void) {
91 static int equivalent(const char *a
, const char *b
) {
92 _cleanup_free_
char *x
= NULL
, *y
= NULL
;
94 x
= canonicalize_file_name(a
);
98 y
= canonicalize_file_name(b
);
102 return path_equal(x
, y
);
105 static int notify_override_masked(const char *top
, const char *bottom
) {
106 if (!(arg_flags
& SHOW_MASKED
))
109 printf("%s%s%s %s %s %s\n",
110 ansi_highlight_red(), "[MASKED]", ansi_normal(),
111 top
, draw_special_char(DRAW_ARROW
), bottom
);
115 static int notify_override_equivalent(const char *top
, const char *bottom
) {
116 if (!(arg_flags
& SHOW_EQUIVALENT
))
119 printf("%s%s%s %s %s %s\n",
120 ansi_highlight_green(), "[EQUIVALENT]", ansi_normal(),
121 top
, draw_special_char(DRAW_ARROW
), bottom
);
125 static int notify_override_redirected(const char *top
, const char *bottom
) {
126 if (!(arg_flags
& SHOW_REDIRECTED
))
129 printf("%s%s%s %s %s %s\n",
130 ansi_highlight(), "[REDIRECTED]", ansi_normal(),
131 top
, draw_special_char(DRAW_ARROW
), bottom
);
135 static int notify_override_overridden(const char *top
, const char *bottom
) {
136 if (!(arg_flags
& SHOW_OVERRIDDEN
))
139 printf("%s%s%s %s %s %s\n",
140 ansi_highlight(), "[OVERRIDDEN]", ansi_normal(),
141 top
, draw_special_char(DRAW_ARROW
), bottom
);
145 static int notify_override_extended(const char *top
, const char *bottom
) {
146 if (!(arg_flags
& SHOW_EXTENDED
))
149 printf("%s%s%s %s %s %s\n",
150 ansi_highlight(), "[EXTENDED]", ansi_normal(),
151 top
, draw_special_char(DRAW_ARROW
), bottom
);
155 static int notify_override_unchanged(const char *f
) {
156 if (!(arg_flags
& SHOW_UNCHANGED
))
159 printf("[UNCHANGED] %s\n", f
);
163 static int found_override(const char *top
, const char *bottom
) {
164 _cleanup_free_
char *dest
= NULL
;
171 if (null_or_empty_path(top
) > 0)
172 return notify_override_masked(top
, bottom
);
174 k
= readlink_malloc(top
, &dest
);
176 if (equivalent(dest
, bottom
) > 0)
177 return notify_override_equivalent(top
, bottom
);
179 return notify_override_redirected(top
, bottom
);
182 k
= notify_override_overridden(top
, bottom
);
192 return log_error_errno(errno
, "Failed to fork off diff: %m");
195 (void) reset_all_signal_handlers();
196 (void) reset_signal_mask();
197 assert_se(prctl(PR_SET_PDEATHSIG
, SIGTERM
) == 0);
199 execlp("diff", "diff", "-us", "--", bottom
, top
, NULL
);
200 log_error_errno(errno
, "Failed to execute diff: %m");
204 wait_for_terminate_and_warn("diff", pid
, false);
210 static int enumerate_dir_d(Hashmap
*top
, Hashmap
*bottom
, Hashmap
*drops
, const char *toppath
, const char *drop
) {
211 _cleanup_free_
char *unit
= NULL
;
212 _cleanup_free_
char *path
= NULL
;
213 _cleanup_strv_free_
char **list
= NULL
;
218 assert(!endswith(drop
, "/"));
220 path
= strjoin(toppath
, "/", drop
, NULL
);
224 log_debug("Looking at %s", path
);
230 c
= strrchr(unit
, '.');
235 r
= get_files_in_directory(path
, &list
);
237 return log_error_errno(r
, "Failed to enumerate %s: %m", path
);
239 STRV_FOREACH(file
, list
) {
245 if (!endswith(*file
, ".conf"))
248 p
= strjoin(path
, "/", *file
, NULL
);
251 d
= p
+ strlen(toppath
) + 1;
253 log_debug("Adding at top: %s %s %s", d
, draw_special_char(DRAW_ARROW
), p
);
254 k
= hashmap_put(top
, d
, p
);
259 d
= p
+ strlen(toppath
) + 1;
260 } else if (k
!= -EEXIST
) {
265 log_debug("Adding at bottom: %s %s %s", d
, draw_special_char(DRAW_ARROW
), p
);
266 free(hashmap_remove(bottom
, d
));
267 k
= hashmap_put(bottom
, d
, p
);
273 h
= hashmap_get(drops
, unit
);
275 h
= hashmap_new(&string_hash_ops
);
278 hashmap_put(drops
, unit
, h
);
288 log_debug("Adding to drops: %s %s %s %s %s",
289 unit
, draw_special_char(DRAW_ARROW
), basename(p
), draw_special_char(DRAW_ARROW
), p
);
290 k
= hashmap_put(h
, basename(p
), p
);
300 static int enumerate_dir(Hashmap
*top
, Hashmap
*bottom
, Hashmap
*drops
, const char *path
, bool dropins
) {
301 _cleanup_closedir_
DIR *d
;
308 log_debug("Looking at %s", path
);
315 log_error_errno(errno
, "Failed to open %s: %m", path
);
329 dirent_ensure_type(d
, de
);
331 if (dropins
&& de
->d_type
== DT_DIR
&& endswith(de
->d_name
, ".d"))
332 enumerate_dir_d(top
, bottom
, drops
, path
, de
->d_name
);
334 if (!dirent_is_file(de
))
337 p
= strjoin(path
, "/", de
->d_name
, NULL
);
341 log_debug("Adding at top: %s %s %s", basename(p
), draw_special_char(DRAW_ARROW
), p
);
342 k
= hashmap_put(top
, basename(p
), p
);
347 } else if (k
!= -EEXIST
) {
352 log_debug("Adding at bottom: %s %s %s", basename(p
), draw_special_char(DRAW_ARROW
), p
);
353 free(hashmap_remove(bottom
, basename(p
)));
354 k
= hashmap_put(bottom
, basename(p
), p
);
362 static int process_suffix(const char *suffix
, const char *onlyprefix
) {
365 Hashmap
*top
, *bottom
, *drops
;
374 assert(!startswith(suffix
, "/"));
375 assert(!strstr(suffix
, "//"));
377 dropins
= nulstr_contains(have_dropins
, suffix
);
379 top
= hashmap_new(&string_hash_ops
);
380 bottom
= hashmap_new(&string_hash_ops
);
381 drops
= hashmap_new(&string_hash_ops
);
382 if (!top
|| !bottom
|| !drops
) {
387 NULSTR_FOREACH(p
, prefixes
) {
388 _cleanup_free_
char *t
= NULL
;
390 t
= strjoin(p
, "/", suffix
, NULL
);
396 k
= enumerate_dir(top
, bottom
, drops
, t
, dropins
);
401 HASHMAP_FOREACH_KEY(f
, key
, top
, i
) {
404 o
= hashmap_get(bottom
, key
);
407 if (!onlyprefix
|| startswith(o
, onlyprefix
)) {
408 if (path_equal(o
, f
)) {
409 notify_override_unchanged(f
);
411 k
= found_override(f
, o
);
419 h
= hashmap_get(drops
, key
);
421 HASHMAP_FOREACH(o
, h
, j
)
422 if (!onlyprefix
|| startswith(o
, onlyprefix
))
423 n_found
+= notify_override_extended(f
, o
);
428 hashmap_free_free(top
);
430 hashmap_free_free(bottom
);
432 HASHMAP_FOREACH_KEY(h
, key
, drops
, i
){
433 hashmap_free_free(hashmap_remove(drops
, key
));
434 hashmap_remove(drops
, key
);
439 return r
< 0 ? r
: n_found
;
442 static int process_suffixes(const char *onlyprefix
) {
446 NULSTR_FOREACH(n
, suffixes
) {
447 r
= process_suffix(n
, onlyprefix
);
456 static int process_suffix_chop(const char *arg
) {
461 if (!path_is_absolute(arg
))
462 return process_suffix(arg
, NULL
);
464 /* Strip prefix from the suffix */
465 NULSTR_FOREACH(p
, prefixes
) {
466 const char *suffix
= startswith(arg
, p
);
468 suffix
+= strspn(suffix
, "/");
470 return process_suffix(suffix
, NULL
);
472 return process_suffixes(arg
);
476 log_error("Invalid suffix specification %s.", arg
);
480 static void help(void) {
481 printf("%s [OPTIONS...] [SUFFIX...]\n\n"
482 "Find overridden configuration files.\n\n"
483 " -h --help Show this help\n"
484 " --version Show package version\n"
485 " --no-pager Do not pipe output into a pager\n"
486 " --diff[=1|0] Show a diff when overridden files differ\n"
487 " -t --type=LIST... Only display a selected set of override types\n"
488 , program_invocation_short_name
);
491 static int parse_flags(const char *flag_str
, int flags
) {
492 const char *word
, *state
;
495 FOREACH_WORD_SEPARATOR(word
, l
, flag_str
, ",", state
) {
496 if (strneq("masked", word
, l
))
497 flags
|= SHOW_MASKED
;
498 else if (strneq ("equivalent", word
, l
))
499 flags
|= SHOW_EQUIVALENT
;
500 else if (strneq("redirected", word
, l
))
501 flags
|= SHOW_REDIRECTED
;
502 else if (strneq("overridden", word
, l
))
503 flags
|= SHOW_OVERRIDDEN
;
504 else if (strneq("unchanged", word
, l
))
505 flags
|= SHOW_UNCHANGED
;
506 else if (strneq("extended", word
, l
))
507 flags
|= SHOW_EXTENDED
;
508 else if (strneq("default", word
, l
))
509 flags
|= SHOW_DEFAULTS
;
516 static int parse_argv(int argc
, char *argv
[]) {
519 ARG_NO_PAGER
= 0x100,
524 static const struct option options
[] = {
525 { "help", no_argument
, NULL
, 'h' },
526 { "version", no_argument
, NULL
, ARG_VERSION
},
527 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
528 { "diff", optional_argument
, NULL
, ARG_DIFF
},
529 { "type", required_argument
, NULL
, 't' },
538 while ((c
= getopt_long(argc
, argv
, "ht:", options
, NULL
)) >= 0)
555 f
= parse_flags(optarg
, arg_flags
);
557 log_error("Failed to parse flags field.");
570 b
= parse_boolean(optarg
);
572 log_error("Failed to parse diff boolean.");
585 assert_not_reached("Unhandled option");
591 int main(int argc
, char *argv
[]) {
595 log_parse_environment();
598 r
= parse_argv(argc
, argv
);
603 arg_flags
= SHOW_DEFAULTS
;
606 arg_diff
= !!(arg_flags
& SHOW_OVERRIDDEN
);
608 arg_flags
|= SHOW_OVERRIDDEN
;
610 pager_open_if_enabled();
615 for (i
= optind
; i
< argc
; i
++) {
616 path_kill_slashes(argv
[i
]);
617 k
= process_suffix_chop(argv
[i
]);
625 k
= process_suffixes(NULL
);
633 printf("%s%i overridden configuration files found.\n",
634 n_found
? "\n" : "", n_found
);
639 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;