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 "alloc-util.h"
30 #include "dirent-util.h"
34 #include "locale-util.h"
37 #include "parse-util.h"
38 #include "path-util.h"
39 #include "process-util.h"
40 #include "signal-util.h"
41 #include "stat-util.h"
42 #include "string-util.h"
44 #include "terminal-util.h"
47 static const char prefixes
[] =
59 static const char suffixes
[] =
66 "systemd/system-preset\0"
67 "systemd/user-preset\0"
71 static const char have_dropins
[] =
75 static bool arg_no_pager
= false;
76 static int arg_diff
= -1;
80 SHOW_EQUIVALENT
= 1 << 1,
81 SHOW_REDIRECTED
= 1 << 2,
82 SHOW_OVERRIDDEN
= 1 << 3,
83 SHOW_UNCHANGED
= 1 << 4,
84 SHOW_EXTENDED
= 1 << 5,
87 (SHOW_MASKED
| SHOW_EQUIVALENT
| SHOW_REDIRECTED
| SHOW_OVERRIDDEN
| SHOW_EXTENDED
)
90 static void pager_open_if_enabled(void) {
98 static int equivalent(const char *a
, const char *b
) {
99 _cleanup_free_
char *x
= NULL
, *y
= NULL
;
101 x
= canonicalize_file_name(a
);
105 y
= canonicalize_file_name(b
);
109 return path_equal(x
, y
);
112 static int notify_override_masked(const char *top
, const char *bottom
) {
113 if (!(arg_flags
& SHOW_MASKED
))
116 printf("%s%s%s %s %s %s\n",
117 ansi_highlight_red(), "[MASKED]", ansi_normal(),
118 top
, draw_special_char(DRAW_ARROW
), bottom
);
122 static int notify_override_equivalent(const char *top
, const char *bottom
) {
123 if (!(arg_flags
& SHOW_EQUIVALENT
))
126 printf("%s%s%s %s %s %s\n",
127 ansi_highlight_green(), "[EQUIVALENT]", ansi_normal(),
128 top
, draw_special_char(DRAW_ARROW
), bottom
);
132 static int notify_override_redirected(const char *top
, const char *bottom
) {
133 if (!(arg_flags
& SHOW_REDIRECTED
))
136 printf("%s%s%s %s %s %s\n",
137 ansi_highlight(), "[REDIRECTED]", ansi_normal(),
138 top
, draw_special_char(DRAW_ARROW
), bottom
);
142 static int notify_override_overridden(const char *top
, const char *bottom
) {
143 if (!(arg_flags
& SHOW_OVERRIDDEN
))
146 printf("%s%s%s %s %s %s\n",
147 ansi_highlight(), "[OVERRIDDEN]", ansi_normal(),
148 top
, draw_special_char(DRAW_ARROW
), bottom
);
152 static int notify_override_extended(const char *top
, const char *bottom
) {
153 if (!(arg_flags
& SHOW_EXTENDED
))
156 printf("%s%s%s %s %s %s\n",
157 ansi_highlight(), "[EXTENDED]", ansi_normal(),
158 top
, draw_special_char(DRAW_ARROW
), bottom
);
162 static int notify_override_unchanged(const char *f
) {
163 if (!(arg_flags
& SHOW_UNCHANGED
))
166 printf("[UNCHANGED] %s\n", f
);
170 static int found_override(const char *top
, const char *bottom
) {
171 _cleanup_free_
char *dest
= NULL
;
178 if (null_or_empty_path(top
) > 0)
179 return notify_override_masked(top
, bottom
);
181 k
= readlink_malloc(top
, &dest
);
183 if (equivalent(dest
, bottom
) > 0)
184 return notify_override_equivalent(top
, bottom
);
186 return notify_override_redirected(top
, bottom
);
189 k
= notify_override_overridden(top
, bottom
);
199 return log_error_errno(errno
, "Failed to fork off diff: %m");
202 (void) reset_all_signal_handlers();
203 (void) reset_signal_mask();
204 assert_se(prctl(PR_SET_PDEATHSIG
, SIGTERM
) == 0);
206 execlp("diff", "diff", "-us", "--", bottom
, top
, NULL
);
207 log_error_errno(errno
, "Failed to execute diff: %m");
211 wait_for_terminate_and_warn("diff", pid
, false);
217 static int enumerate_dir_d(Hashmap
*top
, Hashmap
*bottom
, Hashmap
*drops
, const char *toppath
, const char *drop
) {
218 _cleanup_free_
char *unit
= NULL
;
219 _cleanup_free_
char *path
= NULL
;
220 _cleanup_strv_free_
char **list
= NULL
;
225 assert(!endswith(drop
, "/"));
227 path
= strjoin(toppath
, "/", drop
, NULL
);
231 log_debug("Looking at %s", path
);
237 c
= strrchr(unit
, '.');
242 r
= get_files_in_directory(path
, &list
);
244 return log_error_errno(r
, "Failed to enumerate %s: %m", path
);
246 STRV_FOREACH(file
, list
) {
252 if (!endswith(*file
, ".conf"))
255 p
= strjoin(path
, "/", *file
, NULL
);
258 d
= p
+ strlen(toppath
) + 1;
260 log_debug("Adding at top: %s %s %s", d
, draw_special_char(DRAW_ARROW
), p
);
261 k
= hashmap_put(top
, d
, p
);
266 d
= p
+ strlen(toppath
) + 1;
267 } else if (k
!= -EEXIST
) {
272 log_debug("Adding at bottom: %s %s %s", d
, draw_special_char(DRAW_ARROW
), p
);
273 free(hashmap_remove(bottom
, d
));
274 k
= hashmap_put(bottom
, d
, p
);
280 h
= hashmap_get(drops
, unit
);
282 h
= hashmap_new(&string_hash_ops
);
285 hashmap_put(drops
, unit
, h
);
295 log_debug("Adding to drops: %s %s %s %s %s",
296 unit
, draw_special_char(DRAW_ARROW
), basename(p
), draw_special_char(DRAW_ARROW
), p
);
297 k
= hashmap_put(h
, basename(p
), p
);
307 static int enumerate_dir(Hashmap
*top
, Hashmap
*bottom
, Hashmap
*drops
, const char *path
, bool dropins
) {
308 _cleanup_closedir_
DIR *d
;
315 log_debug("Looking at %s", path
);
322 return log_error_errno(errno
, "Failed to open %s: %m", path
);
335 dirent_ensure_type(d
, de
);
337 if (dropins
&& de
->d_type
== DT_DIR
&& endswith(de
->d_name
, ".d"))
338 enumerate_dir_d(top
, bottom
, drops
, path
, de
->d_name
);
340 if (!dirent_is_file(de
))
343 p
= strjoin(path
, "/", de
->d_name
, NULL
);
347 log_debug("Adding at top: %s %s %s", basename(p
), draw_special_char(DRAW_ARROW
), p
);
348 k
= hashmap_put(top
, basename(p
), p
);
353 } else if (k
!= -EEXIST
) {
358 log_debug("Adding at bottom: %s %s %s", basename(p
), draw_special_char(DRAW_ARROW
), p
);
359 free(hashmap_remove(bottom
, basename(p
)));
360 k
= hashmap_put(bottom
, basename(p
), p
);
368 static int process_suffix(const char *suffix
, const char *onlyprefix
) {
371 Hashmap
*top
, *bottom
, *drops
;
380 assert(!startswith(suffix
, "/"));
381 assert(!strstr(suffix
, "//"));
383 dropins
= nulstr_contains(have_dropins
, suffix
);
385 top
= hashmap_new(&string_hash_ops
);
386 bottom
= hashmap_new(&string_hash_ops
);
387 drops
= hashmap_new(&string_hash_ops
);
388 if (!top
|| !bottom
|| !drops
) {
393 NULSTR_FOREACH(p
, prefixes
) {
394 _cleanup_free_
char *t
= NULL
;
396 t
= strjoin(p
, "/", suffix
, NULL
);
402 k
= enumerate_dir(top
, bottom
, drops
, t
, dropins
);
407 HASHMAP_FOREACH_KEY(f
, key
, top
, i
) {
410 o
= hashmap_get(bottom
, key
);
413 if (!onlyprefix
|| startswith(o
, onlyprefix
)) {
414 if (path_equal(o
, f
)) {
415 notify_override_unchanged(f
);
417 k
= found_override(f
, o
);
425 h
= hashmap_get(drops
, key
);
427 HASHMAP_FOREACH(o
, h
, j
)
428 if (!onlyprefix
|| startswith(o
, onlyprefix
))
429 n_found
+= notify_override_extended(f
, o
);
434 hashmap_free_free(top
);
436 hashmap_free_free(bottom
);
438 HASHMAP_FOREACH_KEY(h
, key
, drops
, i
){
439 hashmap_free_free(hashmap_remove(drops
, key
));
440 hashmap_remove(drops
, key
);
445 return r
< 0 ? r
: n_found
;
448 static int process_suffixes(const char *onlyprefix
) {
452 NULSTR_FOREACH(n
, suffixes
) {
453 r
= process_suffix(n
, onlyprefix
);
462 static int process_suffix_chop(const char *arg
) {
467 if (!path_is_absolute(arg
))
468 return process_suffix(arg
, NULL
);
470 /* Strip prefix from the suffix */
471 NULSTR_FOREACH(p
, prefixes
) {
472 const char *suffix
= startswith(arg
, p
);
474 suffix
+= strspn(suffix
, "/");
476 return process_suffix(suffix
, NULL
);
478 return process_suffixes(arg
);
482 log_error("Invalid suffix specification %s.", arg
);
486 static void help(void) {
487 printf("%s [OPTIONS...] [SUFFIX...]\n\n"
488 "Find overridden configuration files.\n\n"
489 " -h --help Show this help\n"
490 " --version Show package version\n"
491 " --no-pager Do not pipe output into a pager\n"
492 " --diff[=1|0] Show a diff when overridden files differ\n"
493 " -t --type=LIST... Only display a selected set of override types\n"
494 , program_invocation_short_name
);
497 static int parse_flags(const char *flag_str
, int flags
) {
498 const char *word
, *state
;
501 FOREACH_WORD_SEPARATOR(word
, l
, flag_str
, ",", state
) {
502 if (strneq("masked", word
, l
))
503 flags
|= SHOW_MASKED
;
504 else if (strneq ("equivalent", word
, l
))
505 flags
|= SHOW_EQUIVALENT
;
506 else if (strneq("redirected", word
, l
))
507 flags
|= SHOW_REDIRECTED
;
508 else if (strneq("overridden", word
, l
))
509 flags
|= SHOW_OVERRIDDEN
;
510 else if (strneq("unchanged", word
, l
))
511 flags
|= SHOW_UNCHANGED
;
512 else if (strneq("extended", word
, l
))
513 flags
|= SHOW_EXTENDED
;
514 else if (strneq("default", word
, l
))
515 flags
|= SHOW_DEFAULTS
;
522 static int parse_argv(int argc
, char *argv
[]) {
525 ARG_NO_PAGER
= 0x100,
530 static const struct option options
[] = {
531 { "help", no_argument
, NULL
, 'h' },
532 { "version", no_argument
, NULL
, ARG_VERSION
},
533 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
534 { "diff", optional_argument
, NULL
, ARG_DIFF
},
535 { "type", required_argument
, NULL
, 't' },
544 while ((c
= getopt_long(argc
, argv
, "ht:", options
, NULL
)) >= 0)
561 f
= parse_flags(optarg
, arg_flags
);
563 log_error("Failed to parse flags field.");
576 b
= parse_boolean(optarg
);
578 log_error("Failed to parse diff boolean.");
591 assert_not_reached("Unhandled option");
597 int main(int argc
, char *argv
[]) {
601 log_parse_environment();
604 r
= parse_argv(argc
, argv
);
609 arg_flags
= SHOW_DEFAULTS
;
612 arg_diff
= !!(arg_flags
& SHOW_OVERRIDDEN
);
614 arg_flags
|= SHOW_OVERRIDDEN
;
616 pager_open_if_enabled();
621 for (i
= optind
; i
< argc
; i
++) {
622 path_kill_slashes(argv
[i
]);
623 k
= process_suffix_chop(argv
[i
]);
631 k
= process_suffixes(NULL
);
639 printf("%s%i overridden configuration files found.\n",
640 n_found
? "\n" : "", n_found
);
645 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;