1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
6 #include "alloc-util.h"
8 #include "conf-files.h"
9 #include "dirent-util.h"
10 #include "errno-util.h"
13 #include "glyph-util.h"
16 #include "nulstr-util.h"
17 #include "path-util.h"
19 #include "stat-util.h"
20 #include "string-util.h"
27 const char *root
, /* for logging, can be NULL */
31 ConfFilesFlags flags
) {
37 assert(rfd
>= 0 || rfd
== AT_FDCWD
);
41 root
= strempty(root
);
43 FOREACH_DIRENT(de
, dir
, return log_debug_errno(errno
, "Failed to read directory '%s/%s': %m",
44 root
, skip_leading_slash(dirpath
))) {
46 /* Does this match the suffix? */
47 if (suffix
&& !endswith(de
->d_name
, suffix
))
50 /* Has this file already been found in an earlier directory? */
51 if (hashmap_contains(*files
, de
->d_name
)) {
52 log_debug("Skipping overridden file '%s/%s/%s'.",
53 root
, skip_leading_slash(dirpath
), de
->d_name
);
57 /* Has this been masked in an earlier directory? */
58 if ((flags
& CONF_FILES_FILTER_MASKED
) != 0 && set_contains(*masked
, de
->d_name
)) {
59 log_debug("File '%s/%s/%s' is masked by previous entry.",
60 root
, skip_leading_slash(dirpath
), de
->d_name
);
64 _cleanup_free_
char *p
= path_join(dirpath
, de
->d_name
);
66 return log_oom_debug();
68 _cleanup_free_
char *resolved_path
= NULL
;
69 bool need_stat
= (flags
& (CONF_FILES_FILTER_MASKED
| CONF_FILES_REGULAR
| CONF_FILES_DIRECTORY
| CONF_FILES_EXECUTABLE
)) != 0;
72 if (!need_stat
|| FLAGS_SET(flags
, CONF_FILES_FILTER_MASKED_BY_SYMLINK
)) {
74 /* Even if no verification is requested, let's unconditionally call chaseat(),
75 * to drop unsafe symlinks. */
77 r
= chaseat(rfd
, p
, CHASE_AT_RESOLVE_IN_ROOT
| CHASE_NONEXISTENT
, &resolved_path
, /* ret_fd = */ NULL
);
79 log_debug_errno(r
, "Failed to chase '%s/%s', ignoring: %m",
80 root
, skip_leading_slash(p
));
83 if (r
== 0 && FLAGS_SET(flags
, CONF_FILES_FILTER_MASKED_BY_SYMLINK
)) {
85 /* If the path points to /dev/null in a image or so, then the device node may not exist. */
86 if (path_equal(skip_leading_slash(resolved_path
), "dev/null")) {
87 /* Mark this one as masked */
88 r
= set_put_strdup(masked
, de
->d_name
);
90 return log_oom_debug();
92 log_debug("File '%s/%s' is a mask (symlink to /dev/null).",
93 root
, skip_leading_slash(p
));
97 /* If the flag is set, we need to have stat, hence, skip the entry. */
98 log_debug_errno(SYNTHETIC_ERRNO(ENOENT
), "Failed to chase '%s/%s', ignoring: %m",
99 root
, skip_leading_slash(p
));
103 if (need_stat
&& fstatat(rfd
, resolved_path
, &st
, AT_SYMLINK_NOFOLLOW
) < 0) {
104 log_debug_errno(errno
, "Failed to stat '%s/%s', ignoring: %m",
105 root
, skip_leading_slash(p
));
110 r
= chase_and_statat(rfd
, p
, CHASE_AT_RESOLVE_IN_ROOT
, &resolved_path
, &st
);
112 log_debug_errno(r
, "Failed to chase and stat '%s/%s', ignoring: %m",
113 root
, skip_leading_slash(p
));
118 /* Is this a masking entry? */
119 if (FLAGS_SET(flags
, CONF_FILES_FILTER_MASKED_BY_SYMLINK
) && stat_may_be_dev_null(&st
)) {
120 /* Mark this one as masked */
121 r
= set_put_strdup(masked
, de
->d_name
);
123 return log_oom_debug();
125 log_debug("File '%s/%s' is a mask (symlink to /dev/null).", root
, skip_leading_slash(p
));
129 if (FLAGS_SET(flags
, CONF_FILES_FILTER_MASKED_BY_EMPTY
) && stat_is_empty(&st
)) {
130 /* Mark this one as masked */
131 r
= set_put_strdup(masked
, de
->d_name
);
133 return log_oom_debug();
135 log_debug("File '%s/%s' is a mask (an empty file).", root
, skip_leading_slash(p
));
139 if (FLAGS_SET(flags
, CONF_FILES_REGULAR
|CONF_FILES_DIRECTORY
)) {
140 if (!S_ISREG(st
.st_mode
) && !S_ISDIR(st
.st_mode
)) {
141 log_debug("Ignoring '%s/%s', as it is neither a regular file or directory.", root
, skip_leading_slash(p
));
145 /* Is this node a regular file? */
146 if (FLAGS_SET(flags
, CONF_FILES_REGULAR
) && !S_ISREG(st
.st_mode
)) {
147 log_debug("Ignoring '%s/%s', as it is not a regular file.", root
, skip_leading_slash(p
));
151 /* Is this node a directory? */
152 if (FLAGS_SET(flags
, CONF_FILES_DIRECTORY
) && !S_ISDIR(st
.st_mode
)) {
153 log_debug("Ignoring '%s/%s', as it is not a directory.", root
, skip_leading_slash(p
));
158 /* Does this node have the executable bit set?
159 * As requested: check if the file is marked executable. Note that we don't check access(X_OK)
160 * here, as we care about whether the file is marked executable at all, and not whether it is
161 * executable for us, because if so, such errors are stuff we should log about. */
162 if (FLAGS_SET(flags
, CONF_FILES_EXECUTABLE
) && (st
.st_mode
& 0111) == 0) {
163 log_debug("Ignoring '%s/%s', as it is not marked executable.", root
, skip_leading_slash(p
));
167 _cleanup_free_
char *n
= strdup(de
->d_name
);
169 return log_oom_debug();
171 r
= hashmap_ensure_put(files
, &string_hash_ops_free_free
, n
, p
);
173 assert(r
== -ENOMEM
);
174 return log_oom_debug();
185 static int copy_and_sort_files_from_hashmap(Hashmap
*fh
, const char *root
, ConfFilesFlags flags
, char ***ret
) {
186 _cleanup_free_
char **sv
= NULL
;
187 _cleanup_strv_free_
char **files
= NULL
;
193 r
= hashmap_dump_sorted(fh
, (void***) &sv
, /* ret_n = */ NULL
);
195 return log_oom_debug();
197 /* The entries in the array given by hashmap_dump_sorted() are still owned by the hashmap. */
198 STRV_FOREACH(s
, sv
) {
199 _cleanup_free_
char *p
= NULL
;
201 if (FLAGS_SET(flags
, CONF_FILES_BASENAME
)) {
202 r
= path_extract_filename(*s
, &p
);
204 return log_debug_errno(r
, "Failed to extract filename from '%s': %m", *s
);
206 r
= chaseat_prefix_root(*s
, root
, &p
);
208 return log_debug_errno(r
, "Failed to prefix '%s' with root '%s': %m", *s
, root
);
212 r
= strv_consume_with_size(&files
, &n
, TAKE_PTR(p
));
214 r
= strv_extend_with_size(&files
, &n
, *s
);
216 return log_oom_debug();
219 *ret
= TAKE_PTR(files
);
223 static int insert_replacement(Hashmap
**fh
, char *filename_replacement
, char *resolved_replacement
, char **ret
) {
224 _cleanup_free_
char *fname
= ASSERT_PTR(filename_replacement
), *path
= ASSERT_PTR(resolved_replacement
);
229 /* This consumes the input filename and path. */
231 const char *existing
= hashmap_get(*fh
, fname
);
233 log_debug("An entry with higher priority already exists ('%s' -> '%s'), ignoring the replacement: %s",
234 fname
, existing
, path
);
240 _cleanup_free_
char *copy
= NULL
;
244 return log_oom_debug();
247 r
= hashmap_ensure_put(fh
, &string_hash_ops_free_free
, fname
, path
);
249 assert(r
== -ENOMEM
);
250 return log_oom_debug();
254 log_debug("Inserted replacement: '%s' -> '%s'", fname
, path
);
260 *ret
= TAKE_PTR(copy
);
264 static int conf_files_list_impl(
267 const char *root
, /* for logging, can be NULL */
268 ConfFilesFlags flags
,
269 const char * const *dirs
,
270 const char *replacement
,
272 char **ret_inserted
) {
274 _cleanup_hashmap_free_ Hashmap
*fh
= NULL
;
275 _cleanup_set_free_ Set
*masked
= NULL
;
278 assert(rfd
>= 0 || rfd
== AT_FDCWD
);
281 _cleanup_free_
char *filename_replacement
= NULL
, *resolved_dirpath_replacement
= NULL
, *resolved_replacement
= NULL
, *inserted
= NULL
;
283 r
= path_extract_filename(replacement
, &filename_replacement
);
287 _cleanup_free_
char *d
= NULL
;
288 r
= path_extract_directory(replacement
, &d
);
292 r
= chaseat(rfd
, d
, CHASE_AT_RESOLVE_IN_ROOT
| CHASE_NONEXISTENT
, &resolved_dirpath_replacement
, /* ret_fd = */ NULL
);
296 resolved_replacement
= path_join(resolved_dirpath_replacement
, filename_replacement
);
297 if (!resolved_replacement
)
298 return log_oom_debug();
301 STRV_FOREACH(p
, dirs
) {
302 _cleanup_closedir_
DIR *dir
= NULL
;
303 _cleanup_free_
char *path
= NULL
;
305 r
= chase_and_opendirat(rfd
, *p
, CHASE_AT_RESOLVE_IN_ROOT
, &path
, &dir
);
308 log_debug_errno(r
, "Failed to chase and open directory '%s%s', ignoring: %m", strempty(root
), *p
);
312 if (resolved_replacement
&& path_equal(resolved_dirpath_replacement
, path
)) {
313 r
= insert_replacement(&fh
, TAKE_PTR(filename_replacement
), TAKE_PTR(resolved_replacement
), ret_inserted
? &inserted
: NULL
);
318 r
= files_add(dir
, path
, rfd
, root
, &fh
, &masked
, suffix
, flags
);
323 if (resolved_replacement
) {
324 r
= insert_replacement(&fh
, TAKE_PTR(filename_replacement
), TAKE_PTR(resolved_replacement
), ret_inserted
? &inserted
: NULL
);
331 *ret_inserted
= TAKE_PTR(inserted
);
335 static int prepare_dirs(const char *root
, char * const *dirs
, int *ret_rfd
, char **ret_root
, char ***ret_dirs
) {
336 _cleanup_free_
char *root_abs
= NULL
;
337 _cleanup_strv_free_
char **dirs_abs
= NULL
;
344 r
= empty_or_root_harder_to_null(&root
);
346 return log_debug_errno(r
, "Failed to determine if '%s' points to the root directory: %m", strempty(root
));
348 dirs_abs
= strv_copy(dirs
);
353 /* WHen a non-trivial root is specified, we will prefix the result later. Hence, it is not
354 * necessary to modify each config directories here. but needs to normalize the root directory. */
355 r
= path_make_absolute_cwd(root
, &root_abs
);
357 return log_debug_errno(r
, "Failed to make '%s' absolute: %m", root
);
359 path_simplify(root_abs
);
361 /* When an empty root or "/" is specified, we will open "/" below, hence we need to make
362 * each config directory absolute if relative. */
363 r
= path_strv_make_absolute_cwd(dirs_abs
);
365 return log_debug_errno(r
, "Failed to make directories absolute: %m");
368 _cleanup_close_
int rfd
= open(empty_to_root(root_abs
), O_CLOEXEC
|O_DIRECTORY
|O_PATH
);
370 return log_debug_errno(errno
, "Failed to open '%s': %m", empty_to_root(root_abs
));
372 *ret_rfd
= TAKE_FD(rfd
);
373 *ret_root
= TAKE_PTR(root_abs
);
374 *ret_dirs
= TAKE_PTR(dirs_abs
);
378 int conf_files_list_strv(
382 ConfFilesFlags flags
,
383 const char * const *dirs
) {
385 _cleanup_hashmap_free_ Hashmap
*fh
= NULL
;
386 _cleanup_close_
int rfd
= -EBADF
;
387 _cleanup_free_
char *root_abs
= NULL
;
388 _cleanup_strv_free_
char **dirs_abs
= NULL
;
393 r
= prepare_dirs(root
, (char**) dirs
, &rfd
, &root_abs
, &dirs_abs
);
397 r
= conf_files_list_impl(suffix
, rfd
, root_abs
, flags
, (const char * const *) dirs_abs
,
398 /* replacement = */ NULL
, &fh
, /* ret_inserted = */ NULL
);
402 return copy_and_sort_files_from_hashmap(fh
, empty_to_root(root_abs
), flags
, ret
);
405 int conf_files_list_strv_at(
409 ConfFilesFlags flags
,
410 const char * const *dirs
) {
412 _cleanup_hashmap_free_ Hashmap
*fh
= NULL
;
413 _cleanup_free_
char *root
= NULL
;
416 assert(rfd
>= 0 || rfd
== AT_FDCWD
);
419 if (rfd
>= 0 && DEBUG_LOGGING
)
420 (void) fd_get_path(rfd
, &root
); /* for logging */
422 r
= conf_files_list_impl(suffix
, rfd
, root
, flags
, dirs
, /* replacement = */ NULL
, &fh
, /* ret_inserted = */ NULL
);
426 return copy_and_sort_files_from_hashmap(fh
, /* root = */ NULL
, flags
, ret
);
429 int conf_files_list(char ***ret
, const char *suffix
, const char *root
, ConfFilesFlags flags
, const char *dir
) {
430 return conf_files_list_strv(ret
, suffix
, root
, flags
, STRV_MAKE_CONST(dir
));
433 int conf_files_list_at(char ***ret
, const char *suffix
, int rfd
, ConfFilesFlags flags
, const char *dir
) {
434 return conf_files_list_strv_at(ret
, suffix
, rfd
, flags
, STRV_MAKE_CONST(dir
));
437 int conf_files_list_nulstr(char ***ret
, const char *suffix
, const char *root
, ConfFilesFlags flags
, const char *dirs
) {
438 _cleanup_strv_free_
char **d
= NULL
;
442 d
= strv_split_nulstr(dirs
);
446 return conf_files_list_strv(ret
, suffix
, root
, flags
, (const char**) d
);
449 int conf_files_list_nulstr_at(char ***ret
, const char *suffix
, int rfd
, ConfFilesFlags flags
, const char *dirs
) {
450 _cleanup_strv_free_
char **d
= NULL
;
454 d
= strv_split_nulstr(dirs
);
458 return conf_files_list_strv_at(ret
, suffix
, rfd
, flags
, (const char**) d
);
461 int conf_files_list_with_replacement(
464 const char *replacement
,
466 char **ret_inserted
) {
468 _cleanup_hashmap_free_ Hashmap
*fh
= NULL
;
469 _cleanup_free_
char *inserted
= NULL
;
470 ConfFilesFlags flags
= CONF_FILES_REGULAR
| CONF_FILES_FILTER_MASKED_BY_SYMLINK
;
471 _cleanup_close_
int rfd
= -EBADF
;
472 _cleanup_free_
char *root_abs
= NULL
;
473 _cleanup_strv_free_
char **dirs_abs
= NULL
;
478 r
= prepare_dirs(root
, config_dirs
, &rfd
, &root_abs
, &dirs_abs
);
482 r
= conf_files_list_impl(".conf", rfd
, root_abs
, flags
, (const char * const *) dirs_abs
,
483 replacement
, &fh
, ret_inserted
? &inserted
: NULL
);
490 r
= chaseat_prefix_root(inserted
, root_abs
, &p
);
492 return log_debug_errno(r
, "Failed to prefix '%s' with root '%s': %m", inserted
, empty_to_root(root_abs
));
494 free_and_replace(inserted
, p
);
497 r
= copy_and_sort_files_from_hashmap(fh
, empty_to_root(root_abs
), flags
, ret_files
);
502 *ret_inserted
= TAKE_PTR(inserted
);
506 int conf_files_list_dropins(
508 const char *dropin_dirname
,
510 const char * const *dirs
) {
512 _cleanup_strv_free_
char **dropin_dirs
= NULL
;
517 assert(dropin_dirname
);
520 suffix
= strjoina("/", dropin_dirname
);
521 r
= strv_extend_strv_concat(&dropin_dirs
, dirs
, suffix
);
525 return conf_files_list_strv(ret
, ".conf", root
, 0, (const char* const*) dropin_dirs
);
529 * Open and read a config file.
531 * The <fn> argument may be:
532 * - '-', meaning stdin.
533 * - a file name without a path. In this case <config_dirs> are searched.
534 * - a path, either relative or absolute. In this case <fn> is opened directly.
536 * This method is only suitable for configuration files which have a flat layout without dropins.
540 const char **config_dirs
,
542 parse_line_t parse_line
,
545 bool *invalid_config
) {
547 _cleanup_fclose_
FILE *_f
= NULL
;
548 _cleanup_free_
char *_fn
= NULL
;
555 if (streq(fn
, "-")) {
559 log_debug("Reading config from stdin%s", glyph(GLYPH_ELLIPSIS
));
561 } else if (is_path(fn
)) {
562 r
= path_make_absolute_cwd(fn
, &_fn
);
564 return log_error_errno(r
, "Failed to make path absolute: %m");
567 f
= _f
= fopen(fn
, "re");
571 log_debug("Reading config file \"%s\"%s", fn
, glyph(GLYPH_ELLIPSIS
));
574 r
= search_and_fopen(fn
, "re", root
, config_dirs
, &_f
, &_fn
);
578 log_debug("Reading config file \"%s\"%s", fn
, glyph(GLYPH_ELLIPSIS
));
582 if (r
== -ENOENT
&& ignore_enoent
) {
583 log_debug_errno(r
, "Failed to open \"%s\", ignoring: %m", fn
);
584 return 0; /* No error, but nothing happened. */
587 return log_error_errno(r
, "Failed to read '%s': %m", fn
);
589 r
= 1; /* We entered the part where we may modify state. */
592 _cleanup_free_
char *line
= NULL
;
593 bool invalid_line
= false;
596 k
= read_stripped_line(f
, LONG_LINE_MAX
, &line
);
598 return log_error_errno(k
, "Failed to read '%s': %m", fn
);
604 if (IN_SET(line
[0], 0, '#'))
607 k
= parse_line(fn
, v
, line
, invalid_config
? &invalid_line
: NULL
, userdata
);
608 if (k
< 0 && invalid_line
)
609 /* Allow reporting with a special code if the caller requested this. */
610 *invalid_config
= true;
612 /* The first error, if any, becomes our return value. */
617 RET_GATHER(r
, log_error_errno(SYNTHETIC_ERRNO(EIO
), "Failed to read from file %s.", fn
));