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"
36 #include "terminal-util.h"
39 static const char prefixes
[] =
51 static const char suffixes
[] =
58 "systemd/system-preset\0"
59 "systemd/user-preset\0"
63 static const char have_dropins
[] =
67 static bool arg_no_pager
= false;
68 static int arg_diff
= -1;
72 SHOW_EQUIVALENT
= 1 << 1,
73 SHOW_REDIRECTED
= 1 << 2,
74 SHOW_OVERRIDDEN
= 1 << 3,
75 SHOW_UNCHANGED
= 1 << 4,
76 SHOW_EXTENDED
= 1 << 5,
79 (SHOW_MASKED
| SHOW_EQUIVALENT
| SHOW_REDIRECTED
| SHOW_OVERRIDDEN
| SHOW_EXTENDED
)
82 static void pager_open_if_enabled(void) {
90 static int equivalent(const char *a
, const char *b
) {
91 _cleanup_free_
char *x
= NULL
, *y
= NULL
;
93 x
= canonicalize_file_name(a
);
97 y
= canonicalize_file_name(b
);
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
, draw_special_char(DRAW_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
, draw_special_char(DRAW_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
, draw_special_char(DRAW_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
, draw_special_char(DRAW_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
, draw_special_char(DRAW_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 k
= 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 k
= notify_override_overridden(top
, bottom
);
191 return log_error_errno(errno
, "Failed to fork off diff: %m");
194 (void) reset_all_signal_handlers();
195 (void) reset_signal_mask();
196 assert_se(prctl(PR_SET_PDEATHSIG
, SIGTERM
) == 0);
198 execlp("diff", "diff", "-us", "--", bottom
, top
, NULL
);
199 log_error_errno(errno
, "Failed to execute diff: %m");
203 wait_for_terminate_and_warn("diff", pid
, false);
209 static int enumerate_dir_d(Hashmap
*top
, Hashmap
*bottom
, Hashmap
*drops
, 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
, NULL
);
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
);
238 STRV_FOREACH(file
, list
) {
244 if (!endswith(*file
, ".conf"))
247 p
= strjoin(path
, "/", *file
, NULL
);
250 d
= p
+ strlen(toppath
) + 1;
252 log_debug("Adding at top: %s %s %s", d
, draw_special_char(DRAW_ARROW
), p
);
253 k
= hashmap_put(top
, d
, p
);
258 d
= p
+ strlen(toppath
) + 1;
259 } else if (k
!= -EEXIST
) {
264 log_debug("Adding at bottom: %s %s %s", d
, draw_special_char(DRAW_ARROW
), p
);
265 free(hashmap_remove(bottom
, d
));
266 k
= hashmap_put(bottom
, d
, p
);
272 h
= hashmap_get(drops
, unit
);
274 h
= hashmap_new(&string_hash_ops
);
277 hashmap_put(drops
, unit
, h
);
287 log_debug("Adding to drops: %s %s %s %s %s",
288 unit
, draw_special_char(DRAW_ARROW
), basename(p
), draw_special_char(DRAW_ARROW
), p
);
289 k
= hashmap_put(h
, basename(p
), p
);
299 static int enumerate_dir(Hashmap
*top
, Hashmap
*bottom
, Hashmap
*drops
, const char *path
, bool dropins
) {
300 _cleanup_closedir_
DIR *d
;
307 log_debug("Looking at %s", path
);
314 log_error_errno(errno
, "Failed to open %s: %m", path
);
328 dirent_ensure_type(d
, de
);
330 if (dropins
&& de
->d_type
== DT_DIR
&& endswith(de
->d_name
, ".d"))
331 enumerate_dir_d(top
, bottom
, drops
, path
, de
->d_name
);
333 if (!dirent_is_file(de
))
336 p
= strjoin(path
, "/", de
->d_name
, NULL
);
340 log_debug("Adding at top: %s %s %s", basename(p
), draw_special_char(DRAW_ARROW
), p
);
341 k
= hashmap_put(top
, basename(p
), p
);
346 } else if (k
!= -EEXIST
) {
351 log_debug("Adding at bottom: %s %s %s", basename(p
), draw_special_char(DRAW_ARROW
), p
);
352 free(hashmap_remove(bottom
, basename(p
)));
353 k
= hashmap_put(bottom
, basename(p
), p
);
361 static int process_suffix(const char *suffix
, const char *onlyprefix
) {
364 Hashmap
*top
, *bottom
, *drops
;
373 assert(!startswith(suffix
, "/"));
374 assert(!strstr(suffix
, "//"));
376 dropins
= nulstr_contains(have_dropins
, suffix
);
378 top
= hashmap_new(&string_hash_ops
);
379 bottom
= hashmap_new(&string_hash_ops
);
380 drops
= hashmap_new(&string_hash_ops
);
381 if (!top
|| !bottom
|| !drops
) {
386 NULSTR_FOREACH(p
, prefixes
) {
387 _cleanup_free_
char *t
= NULL
;
389 t
= strjoin(p
, "/", suffix
, NULL
);
395 k
= enumerate_dir(top
, bottom
, drops
, t
, dropins
);
400 HASHMAP_FOREACH_KEY(f
, key
, top
, i
) {
403 o
= hashmap_get(bottom
, key
);
406 if (!onlyprefix
|| startswith(o
, onlyprefix
)) {
407 if (path_equal(o
, f
)) {
408 notify_override_unchanged(f
);
410 k
= found_override(f
, o
);
418 h
= hashmap_get(drops
, key
);
420 HASHMAP_FOREACH(o
, h
, j
)
421 if (!onlyprefix
|| startswith(o
, onlyprefix
))
422 n_found
+= notify_override_extended(f
, o
);
427 hashmap_free_free(top
);
429 hashmap_free_free(bottom
);
431 HASHMAP_FOREACH_KEY(h
, key
, drops
, i
){
432 hashmap_free_free(hashmap_remove(drops
, key
));
433 hashmap_remove(drops
, key
);
438 return r
< 0 ? r
: n_found
;
441 static int process_suffixes(const char *onlyprefix
) {
445 NULSTR_FOREACH(n
, suffixes
) {
446 r
= process_suffix(n
, onlyprefix
);
455 static int process_suffix_chop(const char *arg
) {
460 if (!path_is_absolute(arg
))
461 return process_suffix(arg
, NULL
);
463 /* Strip prefix from the suffix */
464 NULSTR_FOREACH(p
, prefixes
) {
465 const char *suffix
= startswith(arg
, p
);
467 suffix
+= strspn(suffix
, "/");
469 return process_suffix(suffix
, NULL
);
471 return process_suffixes(arg
);
475 log_error("Invalid suffix specification %s.", arg
);
479 static void help(void) {
480 printf("%s [OPTIONS...] [SUFFIX...]\n\n"
481 "Find overridden configuration files.\n\n"
482 " -h --help Show this help\n"
483 " --version Show package version\n"
484 " --no-pager Do not pipe output into a pager\n"
485 " --diff[=1|0] Show a diff when overridden files differ\n"
486 " -t --type=LIST... Only display a selected set of override types\n"
487 , program_invocation_short_name
);
490 static int parse_flags(const char *flag_str
, int flags
) {
491 const char *word
, *state
;
494 FOREACH_WORD_SEPARATOR(word
, l
, flag_str
, ",", state
) {
495 if (strneq("masked", word
, l
))
496 flags
|= SHOW_MASKED
;
497 else if (strneq ("equivalent", word
, l
))
498 flags
|= SHOW_EQUIVALENT
;
499 else if (strneq("redirected", word
, l
))
500 flags
|= SHOW_REDIRECTED
;
501 else if (strneq("overridden", word
, l
))
502 flags
|= SHOW_OVERRIDDEN
;
503 else if (strneq("unchanged", word
, l
))
504 flags
|= SHOW_UNCHANGED
;
505 else if (strneq("extended", word
, l
))
506 flags
|= SHOW_EXTENDED
;
507 else if (strneq("default", word
, l
))
508 flags
|= SHOW_DEFAULTS
;
515 static int parse_argv(int argc
, char *argv
[]) {
518 ARG_NO_PAGER
= 0x100,
523 static const struct option options
[] = {
524 { "help", no_argument
, NULL
, 'h' },
525 { "version", no_argument
, NULL
, ARG_VERSION
},
526 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
527 { "diff", optional_argument
, NULL
, ARG_DIFF
},
528 { "type", required_argument
, NULL
, 't' },
537 while ((c
= getopt_long(argc
, argv
, "ht:", options
, NULL
)) >= 0)
554 f
= parse_flags(optarg
, arg_flags
);
556 log_error("Failed to parse flags field.");
569 b
= parse_boolean(optarg
);
571 log_error("Failed to parse diff boolean.");
584 assert_not_reached("Unhandled option");
590 int main(int argc
, char *argv
[]) {
594 log_parse_environment();
597 r
= parse_argv(argc
, argv
);
602 arg_flags
= SHOW_DEFAULTS
;
605 arg_diff
= !!(arg_flags
& SHOW_OVERRIDDEN
);
607 arg_flags
|= SHOW_OVERRIDDEN
;
609 pager_open_if_enabled();
614 for (i
= optind
; i
< argc
; i
++) {
615 path_kill_slashes(argv
[i
]);
616 k
= process_suffix_chop(argv
[i
]);
624 k
= process_suffixes(NULL
);
632 printf("%s%i overridden configuration files found.\n",
633 n_found
? "\n" : "", n_found
);
638 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;