mirror of
https://github.com/morgan9e/systemd
synced 2026-04-14 00:14:32 +09:00
conf-files: make conf-file enumerators provide more detailed information of enumerated files (#38006)
This introduces `struct ConfFile` that stores detailed information of an enumerated file, and introduces `conf_files_list_full()` and friends that provide results in `ConfFile`. Then make udev, hwdb, catalog, and cat-files use the new function and struct to make them not read files outside of specified root directory.
This commit is contained in:
@@ -126,10 +126,6 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
|
||||
assert(!FLAGS_SET(flags, CHASE_STEP|CHASE_EXTRACT_FILENAME));
|
||||
assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
|
||||
|
||||
/* Either the file may be missing, or we return an fd to the final object, but both make no sense */
|
||||
if (FLAGS_SET(flags, CHASE_NONEXISTENT))
|
||||
assert(!ret_fd);
|
||||
|
||||
if (FLAGS_SET(flags, CHASE_STEP))
|
||||
assert(!ret_fd);
|
||||
|
||||
@@ -552,11 +548,13 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
|
||||
}
|
||||
|
||||
if (ret_fd) {
|
||||
/* Return the O_PATH fd we currently are looking to the caller. It can translate it to a
|
||||
* proper fd by opening /proc/self/fd/xyz. */
|
||||
|
||||
assert(fd >= 0);
|
||||
*ret_fd = TAKE_FD(fd);
|
||||
if (exists) {
|
||||
/* Return the O_PATH fd we currently are looking to the caller. It can translate it
|
||||
* to a proper fd by opening /proc/self/fd/xyz. */
|
||||
assert(fd >= 0);
|
||||
*ret_fd = TAKE_FD(fd);
|
||||
} else
|
||||
*ret_fd = -EBADF;
|
||||
}
|
||||
|
||||
if (FLAGS_SET(flags, CHASE_STEP))
|
||||
|
||||
@@ -20,316 +20,24 @@
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
|
||||
static int files_add(
|
||||
DIR *dir,
|
||||
const char *dirpath,
|
||||
int rfd,
|
||||
const char *root, /* for logging, can be NULL */
|
||||
Hashmap **files,
|
||||
Set **masked,
|
||||
const char *suffix,
|
||||
ConfFilesFlags flags) {
|
||||
ConfFile* conf_file_free(ConfFile *c) {
|
||||
if (!c)
|
||||
return NULL;
|
||||
|
||||
int r;
|
||||
free(c->name);
|
||||
free(c->result);
|
||||
free(c->original_path);
|
||||
free(c->resolved_path);
|
||||
safe_close(c->fd);
|
||||
|
||||
assert(dir);
|
||||
assert(dirpath);
|
||||
assert(rfd >= 0 || rfd == AT_FDCWD);
|
||||
assert(files);
|
||||
assert(masked);
|
||||
|
||||
root = strempty(root);
|
||||
|
||||
FOREACH_DIRENT(de, dir, return log_debug_errno(errno, "Failed to read directory '%s/%s': %m",
|
||||
root, skip_leading_slash(dirpath))) {
|
||||
|
||||
/* Does this match the suffix? */
|
||||
if (suffix && !endswith(de->d_name, suffix))
|
||||
continue;
|
||||
|
||||
/* Has this file already been found in an earlier directory? */
|
||||
if (hashmap_contains(*files, de->d_name)) {
|
||||
log_debug("Skipping overridden file '%s/%s/%s'.",
|
||||
root, skip_leading_slash(dirpath), de->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Has this been masked in an earlier directory? */
|
||||
if ((flags & CONF_FILES_FILTER_MASKED) != 0 && set_contains(*masked, de->d_name)) {
|
||||
log_debug("File '%s/%s/%s' is masked by previous entry.",
|
||||
root, skip_leading_slash(dirpath), de->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
_cleanup_free_ char *p = path_join(dirpath, de->d_name);
|
||||
if (!p)
|
||||
return log_oom_debug();
|
||||
|
||||
_cleanup_free_ char *resolved_path = NULL;
|
||||
bool need_stat = (flags & (CONF_FILES_FILTER_MASKED | CONF_FILES_REGULAR | CONF_FILES_DIRECTORY | CONF_FILES_EXECUTABLE)) != 0;
|
||||
struct stat st;
|
||||
|
||||
if (!need_stat || FLAGS_SET(flags, CONF_FILES_FILTER_MASKED_BY_SYMLINK)) {
|
||||
|
||||
/* Even if no verification is requested, let's unconditionally call chaseat(),
|
||||
* to drop unsafe symlinks. */
|
||||
|
||||
r = chaseat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT | CHASE_NONEXISTENT, &resolved_path, /* ret_fd = */ NULL);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to chase '%s/%s', ignoring: %m",
|
||||
root, skip_leading_slash(p));
|
||||
continue;
|
||||
}
|
||||
if (r == 0 && FLAGS_SET(flags, CONF_FILES_FILTER_MASKED_BY_SYMLINK)) {
|
||||
|
||||
/* If the path points to /dev/null in a image or so, then the device node may not exist. */
|
||||
if (path_equal(skip_leading_slash(resolved_path), "dev/null")) {
|
||||
/* Mark this one as masked */
|
||||
r = set_put_strdup(masked, de->d_name);
|
||||
if (r < 0)
|
||||
return log_oom_debug();
|
||||
|
||||
log_debug("File '%s/%s' is a mask (symlink to /dev/null).",
|
||||
root, skip_leading_slash(p));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* If the flag is set, we need to have stat, hence, skip the entry. */
|
||||
log_debug_errno(SYNTHETIC_ERRNO(ENOENT), "Failed to chase '%s/%s', ignoring: %m",
|
||||
root, skip_leading_slash(p));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (need_stat && fstatat(rfd, resolved_path, &st, AT_SYMLINK_NOFOLLOW) < 0) {
|
||||
log_debug_errno(errno, "Failed to stat '%s/%s', ignoring: %m",
|
||||
root, skip_leading_slash(p));
|
||||
continue;
|
||||
}
|
||||
|
||||
} else {
|
||||
r = chase_and_statat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, &resolved_path, &st);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to chase and stat '%s/%s', ignoring: %m",
|
||||
root, skip_leading_slash(p));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Is this a masking entry? */
|
||||
if (FLAGS_SET(flags, CONF_FILES_FILTER_MASKED_BY_SYMLINK) && stat_may_be_dev_null(&st)) {
|
||||
/* Mark this one as masked */
|
||||
r = set_put_strdup(masked, de->d_name);
|
||||
if (r < 0)
|
||||
return log_oom_debug();
|
||||
|
||||
log_debug("File '%s/%s' is a mask (symlink to /dev/null).", root, skip_leading_slash(p));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (FLAGS_SET(flags, CONF_FILES_FILTER_MASKED_BY_EMPTY) && stat_is_empty(&st)) {
|
||||
/* Mark this one as masked */
|
||||
r = set_put_strdup(masked, de->d_name);
|
||||
if (r < 0)
|
||||
return log_oom_debug();
|
||||
|
||||
log_debug("File '%s/%s' is a mask (an empty file).", root, skip_leading_slash(p));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (FLAGS_SET(flags, CONF_FILES_REGULAR|CONF_FILES_DIRECTORY)) {
|
||||
if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) {
|
||||
log_debug("Ignoring '%s/%s', as it is neither a regular file or directory.", root, skip_leading_slash(p));
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
/* Is this node a regular file? */
|
||||
if (FLAGS_SET(flags, CONF_FILES_REGULAR) && !S_ISREG(st.st_mode)) {
|
||||
log_debug("Ignoring '%s/%s', as it is not a regular file.", root, skip_leading_slash(p));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Is this node a directory? */
|
||||
if (FLAGS_SET(flags, CONF_FILES_DIRECTORY) && !S_ISDIR(st.st_mode)) {
|
||||
log_debug("Ignoring '%s/%s', as it is not a directory.", root, skip_leading_slash(p));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Does this node have the executable bit set?
|
||||
* As requested: check if the file is marked executable. Note that we don't check access(X_OK)
|
||||
* here, as we care about whether the file is marked executable at all, and not whether it is
|
||||
* executable for us, because if so, such errors are stuff we should log about. */
|
||||
if (FLAGS_SET(flags, CONF_FILES_EXECUTABLE) && (st.st_mode & 0111) == 0) {
|
||||
log_debug("Ignoring '%s/%s', as it is not marked executable.", root, skip_leading_slash(p));
|
||||
continue;
|
||||
}
|
||||
|
||||
_cleanup_free_ char *n = strdup(de->d_name);
|
||||
if (!n)
|
||||
return log_oom_debug();
|
||||
|
||||
r = hashmap_ensure_put(files, &string_hash_ops_free_free, n, p);
|
||||
if (r < 0) {
|
||||
assert(r == -ENOMEM);
|
||||
return log_oom_debug();
|
||||
}
|
||||
assert(r > 0);
|
||||
|
||||
TAKE_PTR(n);
|
||||
TAKE_PTR(p);
|
||||
}
|
||||
|
||||
return 0;
|
||||
return mfree(c);
|
||||
}
|
||||
|
||||
static int copy_and_sort_files_from_hashmap(Hashmap *fh, const char *root, ConfFilesFlags flags, char ***ret) {
|
||||
_cleanup_free_ char **sv = NULL;
|
||||
_cleanup_strv_free_ char **files = NULL;
|
||||
size_t n = 0;
|
||||
int r;
|
||||
void conf_file_free_many(ConfFile **array, size_t n) {
|
||||
FOREACH_ARRAY(i, array, n)
|
||||
conf_file_free(*i);
|
||||
|
||||
assert(ret);
|
||||
|
||||
r = hashmap_dump_sorted(fh, (void***) &sv, /* ret_n = */ NULL);
|
||||
if (r < 0)
|
||||
return log_oom_debug();
|
||||
|
||||
/* The entries in the array given by hashmap_dump_sorted() are still owned by the hashmap. */
|
||||
STRV_FOREACH(s, sv) {
|
||||
_cleanup_free_ char *p = NULL;
|
||||
|
||||
if (FLAGS_SET(flags, CONF_FILES_BASENAME)) {
|
||||
r = path_extract_filename(*s, &p);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to extract filename from '%s': %m", *s);
|
||||
} else if (root) {
|
||||
r = chaseat_prefix_root(*s, root, &p);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to prefix '%s' with root '%s': %m", *s, root);
|
||||
}
|
||||
|
||||
if (p)
|
||||
r = strv_consume_with_size(&files, &n, TAKE_PTR(p));
|
||||
else
|
||||
r = strv_extend_with_size(&files, &n, *s);
|
||||
if (r < 0)
|
||||
return log_oom_debug();
|
||||
}
|
||||
|
||||
*ret = TAKE_PTR(files);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int insert_replacement(Hashmap **fh, char *filename_replacement, char *resolved_replacement, char **ret) {
|
||||
_cleanup_free_ char *fname = ASSERT_PTR(filename_replacement), *path = ASSERT_PTR(resolved_replacement);
|
||||
int r;
|
||||
|
||||
assert(fh);
|
||||
|
||||
/* This consumes the input filename and path. */
|
||||
|
||||
const char *existing = hashmap_get(*fh, fname);
|
||||
if (existing) {
|
||||
log_debug("An entry with higher priority already exists ('%s' -> '%s'), ignoring the replacement: %s",
|
||||
fname, existing, path);
|
||||
if (ret)
|
||||
*ret = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
_cleanup_free_ char *copy = NULL;
|
||||
if (ret) {
|
||||
copy = strdup(path);
|
||||
if (!copy)
|
||||
return log_oom_debug();
|
||||
}
|
||||
|
||||
r = hashmap_ensure_put(fh, &string_hash_ops_free_free, fname, path);
|
||||
if (r < 0) {
|
||||
assert(r == -ENOMEM);
|
||||
return log_oom_debug();
|
||||
}
|
||||
assert(r > 0);
|
||||
|
||||
log_debug("Inserted replacement: '%s' -> '%s'", fname, path);
|
||||
|
||||
TAKE_PTR(fname);
|
||||
TAKE_PTR(path);
|
||||
|
||||
if (ret)
|
||||
*ret = TAKE_PTR(copy);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int conf_files_list_impl(
|
||||
const char *suffix,
|
||||
int rfd,
|
||||
const char *root, /* for logging, can be NULL */
|
||||
ConfFilesFlags flags,
|
||||
const char * const *dirs,
|
||||
const char *replacement,
|
||||
Hashmap **ret,
|
||||
char **ret_inserted) {
|
||||
|
||||
_cleanup_hashmap_free_ Hashmap *fh = NULL;
|
||||
_cleanup_set_free_ Set *masked = NULL;
|
||||
int r;
|
||||
|
||||
assert(rfd >= 0 || rfd == AT_FDCWD);
|
||||
assert(ret);
|
||||
|
||||
_cleanup_free_ char *filename_replacement = NULL, *resolved_dirpath_replacement = NULL, *resolved_replacement = NULL, *inserted = NULL;
|
||||
if (replacement) {
|
||||
r = path_extract_filename(replacement, &filename_replacement);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_free_ char *d = NULL;
|
||||
r = path_extract_directory(replacement, &d);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = chaseat(rfd, d, CHASE_AT_RESOLVE_IN_ROOT | CHASE_NONEXISTENT, &resolved_dirpath_replacement, /* ret_fd = */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
resolved_replacement = path_join(resolved_dirpath_replacement, filename_replacement);
|
||||
if (!resolved_replacement)
|
||||
return log_oom_debug();
|
||||
}
|
||||
|
||||
STRV_FOREACH(p, dirs) {
|
||||
_cleanup_closedir_ DIR *dir = NULL;
|
||||
_cleanup_free_ char *path = NULL;
|
||||
|
||||
r = chase_and_opendirat(rfd, *p, CHASE_AT_RESOLVE_IN_ROOT, &path, &dir);
|
||||
if (r < 0) {
|
||||
if (r != -ENOENT)
|
||||
log_debug_errno(r, "Failed to chase and open directory '%s%s', ignoring: %m", strempty(root), *p);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (resolved_replacement && path_equal(resolved_dirpath_replacement, path)) {
|
||||
r = insert_replacement(&fh, TAKE_PTR(filename_replacement), TAKE_PTR(resolved_replacement), ret_inserted ? &inserted : NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = files_add(dir, path, rfd, root, &fh, &masked, suffix, flags);
|
||||
if (r == -ENOMEM)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (resolved_replacement) {
|
||||
r = insert_replacement(&fh, TAKE_PTR(filename_replacement), TAKE_PTR(resolved_replacement), ret_inserted ? &inserted : NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
*ret = TAKE_PTR(fh);
|
||||
if (ret_inserted)
|
||||
*ret_inserted = TAKE_PTR(inserted);
|
||||
return 0;
|
||||
free(array);
|
||||
}
|
||||
|
||||
static int prepare_dirs(const char *root, char * const *dirs, int *ret_rfd, char **ret_root, char ***ret_dirs) {
|
||||
@@ -339,25 +47,27 @@ static int prepare_dirs(const char *root, char * const *dirs, int *ret_rfd, char
|
||||
|
||||
assert(ret_rfd);
|
||||
assert(ret_root);
|
||||
assert(ret_dirs);
|
||||
assert(ret_dirs || strv_isempty(dirs));
|
||||
|
||||
r = empty_or_root_harder_to_null(&root);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to determine if '%s' points to the root directory: %m", strempty(root));
|
||||
|
||||
dirs_abs = strv_copy(dirs);
|
||||
if (!dirs_abs)
|
||||
return log_oom();
|
||||
if (ret_dirs) {
|
||||
dirs_abs = strv_copy(dirs);
|
||||
if (!dirs_abs)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
if (root) {
|
||||
/* WHen a non-trivial root is specified, we will prefix the result later. Hence, it is not
|
||||
/* When a non-trivial root is specified, we will prefix the result later. Hence, it is not
|
||||
* necessary to modify each config directories here. but needs to normalize the root directory. */
|
||||
r = path_make_absolute_cwd(root, &root_abs);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to make '%s' absolute: %m", root);
|
||||
|
||||
path_simplify(root_abs);
|
||||
} else {
|
||||
} else if (ret_dirs) {
|
||||
/* When an empty root or "/" is specified, we will open "/" below, hence we need to make
|
||||
* each config directory absolute if relative. */
|
||||
r = path_strv_make_absolute_cwd(dirs_abs);
|
||||
@@ -371,7 +81,462 @@ static int prepare_dirs(const char *root, char * const *dirs, int *ret_rfd, char
|
||||
|
||||
*ret_rfd = TAKE_FD(rfd);
|
||||
*ret_root = TAKE_PTR(root_abs);
|
||||
*ret_dirs = TAKE_PTR(dirs_abs);
|
||||
if (ret_dirs)
|
||||
*ret_dirs = TAKE_PTR(dirs_abs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int conf_file_prefix_root(ConfFile *c, const char *root) {
|
||||
char *p;
|
||||
int r;
|
||||
|
||||
assert(c);
|
||||
|
||||
r = chaseat_prefix_root(c->result, root, &p);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to prefix '%s' with root '%s': %m", c->result, root);
|
||||
free_and_replace(c->result, p);
|
||||
|
||||
r = chaseat_prefix_root(c->resolved_path, root, &p);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to prefix '%s' with root '%s': %m", c->resolved_path, root);
|
||||
free_and_replace(c->resolved_path, p);
|
||||
|
||||
/* Do not use chaseat_prefix_root(), as it is for the result of chaseat(), but the path is not chased. */
|
||||
p = path_join(empty_to_root(root), skip_leading_slash(c->original_path));
|
||||
if (!p)
|
||||
return log_oom_debug();
|
||||
path_simplify(p);
|
||||
free_and_replace(c->original_path, p);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int conf_file_new_at(const char *path, int rfd, ChaseFlags chase_flags, ConfFile **ret) {
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
assert(rfd >= 0 || rfd == AT_FDCWD);
|
||||
assert(ret);
|
||||
|
||||
_cleanup_free_ char *root = NULL;
|
||||
if (rfd >= 0 && DEBUG_LOGGING)
|
||||
(void) fd_get_path(rfd, &root);
|
||||
|
||||
_cleanup_(conf_file_freep) ConfFile *c = new(ConfFile, 1);
|
||||
if (!c)
|
||||
return log_oom_debug();
|
||||
|
||||
*c = (ConfFile) {
|
||||
.original_path = strdup(path),
|
||||
.fd = -EBADF,
|
||||
};
|
||||
|
||||
if (!c->original_path)
|
||||
return log_oom_debug();
|
||||
|
||||
r = path_extract_filename(path, &c->name);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to extract filename from '%s': %m", path);
|
||||
|
||||
_cleanup_free_ char *dirpath = NULL, *resolved_dirpath = NULL;
|
||||
r = path_extract_directory(path, &dirpath);
|
||||
if (r < 0 && r != -EDESTADDRREQ)
|
||||
return log_debug_errno(r, "Failed to extract directory from '%s': %m", path);
|
||||
if (r >= 0) {
|
||||
r = chaseat(rfd, dirpath,
|
||||
CHASE_AT_RESOLVE_IN_ROOT | (FLAGS_SET(chase_flags, CHASE_NONEXISTENT) ? CHASE_NONEXISTENT : CHASE_MUST_BE_DIRECTORY),
|
||||
&resolved_dirpath, /* ret_fd = */ NULL);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to chase '%s%s': %m", empty_to_root(root), skip_leading_slash(dirpath));
|
||||
}
|
||||
|
||||
c->result = path_join(resolved_dirpath, c->name);
|
||||
if (!c->result)
|
||||
return log_oom_debug();
|
||||
|
||||
r = chaseat(rfd, c->result, CHASE_AT_RESOLVE_IN_ROOT | chase_flags, &c->resolved_path, &c->fd);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to chase '%s%s': %m", empty_to_root(root), skip_leading_slash(c->original_path));
|
||||
|
||||
if (c->fd >= 0 && fstat(c->fd, &c->st) < 0)
|
||||
return log_debug_errno(r, "Failed to stat '%s%s': %m", empty_to_root(root), skip_leading_slash(c->resolved_path));
|
||||
|
||||
*ret = TAKE_PTR(c);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int conf_file_new(const char *path, const char *root, ChaseFlags chase_flags, ConfFile **ret) {
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
assert((chase_flags & (CHASE_PREFIX_ROOT | CHASE_STEP)) == 0);
|
||||
assert(ret);
|
||||
|
||||
_cleanup_free_ char *root_abs = NULL;
|
||||
_cleanup_close_ int rfd = -EBADF;
|
||||
r = prepare_dirs(root, /* dirs = */ NULL, &rfd, &root_abs, /* ret_dirs = */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_free_ char *path_abs = NULL;
|
||||
if (!root_abs) {
|
||||
r = path_make_absolute_cwd(path, &path_abs);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to make '%s' absolute: %m", path);
|
||||
|
||||
path = path_abs;
|
||||
}
|
||||
|
||||
_cleanup_(conf_file_freep) ConfFile *c = NULL;
|
||||
r = conf_file_new_at(path, rfd, chase_flags, &c);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = conf_file_prefix_root(c, root_abs);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*ret = TAKE_PTR(c);
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
|
||||
conf_file_hash_ops,
|
||||
char, string_hash_func, string_compare_func,
|
||||
ConfFile, conf_file_free);
|
||||
|
||||
static int files_add(
|
||||
DIR *dir,
|
||||
const char *original_dirpath,
|
||||
const char *resolved_dirpath,
|
||||
int rfd,
|
||||
const char *root, /* for logging, can be NULL */
|
||||
Hashmap **files,
|
||||
Set **masked,
|
||||
const char *suffix,
|
||||
ConfFilesFlags flags) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(dir);
|
||||
assert(original_dirpath);
|
||||
assert(resolved_dirpath);
|
||||
assert(rfd >= 0 || rfd == AT_FDCWD);
|
||||
assert(files);
|
||||
assert(masked);
|
||||
|
||||
root = strempty(root);
|
||||
|
||||
FOREACH_DIRENT(de, dir, return log_debug_errno(errno, "Failed to read directory '%s/%s': %m",
|
||||
root, skip_leading_slash(original_dirpath))) {
|
||||
|
||||
_cleanup_free_ char *original_path = path_join(original_dirpath, de->d_name);
|
||||
if (!original_path)
|
||||
return log_oom_debug();
|
||||
|
||||
/* Does this match the suffix? */
|
||||
if (suffix && !endswith(de->d_name, suffix)) {
|
||||
log_debug("Skipping file '%s/%s' with unexpected suffix.", root, skip_leading_slash(original_path));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Has this file already been found in an earlier directory? */
|
||||
if (hashmap_contains(*files, de->d_name)) {
|
||||
log_debug("Skipping overridden file '%s/%s'.", root, skip_leading_slash(original_path));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Has this been masked in an earlier directory? */
|
||||
if ((flags & CONF_FILES_FILTER_MASKED) != 0 && set_contains(*masked, de->d_name)) {
|
||||
log_debug("File '%s/%s' is masked by previous entry.", root, skip_leading_slash(original_path));
|
||||
continue;
|
||||
}
|
||||
|
||||
_cleanup_free_ char *p = path_join(resolved_dirpath, de->d_name);
|
||||
if (!p)
|
||||
return log_oom_debug();
|
||||
|
||||
_cleanup_free_ char *resolved_path = NULL;
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
bool need_stat = (flags & (CONF_FILES_FILTER_MASKED | CONF_FILES_REGULAR | CONF_FILES_DIRECTORY | CONF_FILES_EXECUTABLE)) != 0;
|
||||
ChaseFlags chase_flags = CHASE_AT_RESOLVE_IN_ROOT;
|
||||
|
||||
if (!need_stat || FLAGS_SET(flags, CONF_FILES_FILTER_MASKED_BY_SYMLINK))
|
||||
/* Even if no verification is requested, let's unconditionally call chaseat(),
|
||||
* to drop unsafe symlinks. */
|
||||
chase_flags |= CHASE_NONEXISTENT;
|
||||
|
||||
r = chaseat(rfd, p, chase_flags, &resolved_path, &fd);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to chase '%s/%s', ignoring: %m",
|
||||
root, skip_leading_slash(original_path));
|
||||
continue;
|
||||
}
|
||||
if (r == 0) {
|
||||
if (FLAGS_SET(flags, CONF_FILES_FILTER_MASKED_BY_SYMLINK)) {
|
||||
|
||||
/* If the path points to /dev/null in a image or so, then the device node may not exist. */
|
||||
if (path_equal(skip_leading_slash(resolved_path), "dev/null")) {
|
||||
/* Mark this one as masked */
|
||||
r = set_put_strdup(masked, de->d_name);
|
||||
if (r < 0)
|
||||
return log_oom_debug();
|
||||
|
||||
log_debug("File '%s/%s' is a mask (symlink to /dev/null).",
|
||||
root, skip_leading_slash(original_path));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (need_stat) {
|
||||
/* If we need to have stat, skip the entry. */
|
||||
log_debug_errno(SYNTHETIC_ERRNO(ENOENT), "Failed to chase '%s/%s', ignoring: %m",
|
||||
root, skip_leading_slash(original_path));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Even if we do not need stat, let's take stat now. The caller may use the info later. */
|
||||
struct stat st = {};
|
||||
if (fd >= 0 && fstat(fd, &st) < 0) {
|
||||
log_debug_errno(errno, "Failed to stat '%s/%s', ignoring: %m",
|
||||
root, skip_leading_slash(original_path));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Is this a masking entry? */
|
||||
if (FLAGS_SET(flags, CONF_FILES_FILTER_MASKED_BY_SYMLINK) && stat_may_be_dev_null(&st)) {
|
||||
/* Mark this one as masked */
|
||||
r = set_put_strdup(masked, de->d_name);
|
||||
if (r < 0)
|
||||
return log_oom_debug();
|
||||
|
||||
log_debug("File '%s/%s' is a mask (symlink to /dev/null).", root, skip_leading_slash(original_path));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (FLAGS_SET(flags, CONF_FILES_FILTER_MASKED_BY_EMPTY) && stat_is_empty(&st)) {
|
||||
/* Mark this one as masked */
|
||||
r = set_put_strdup(masked, de->d_name);
|
||||
if (r < 0)
|
||||
return log_oom_debug();
|
||||
|
||||
log_debug("File '%s/%s' is a mask (an empty file).", root, skip_leading_slash(original_path));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (FLAGS_SET(flags, CONF_FILES_REGULAR|CONF_FILES_DIRECTORY)) {
|
||||
if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) {
|
||||
log_debug("Ignoring '%s/%s', as it is neither a regular file or directory.", root, skip_leading_slash(original_path));
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
/* Is this node a regular file? */
|
||||
if (FLAGS_SET(flags, CONF_FILES_REGULAR) && !S_ISREG(st.st_mode)) {
|
||||
log_debug("Ignoring '%s/%s', as it is not a regular file.", root, skip_leading_slash(original_path));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Is this node a directory? */
|
||||
if (FLAGS_SET(flags, CONF_FILES_DIRECTORY) && !S_ISDIR(st.st_mode)) {
|
||||
log_debug("Ignoring '%s/%s', as it is not a directory.", root, skip_leading_slash(original_path));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Does this node have the executable bit set?
|
||||
* As requested: check if the file is marked executable. Note that we don't check access(X_OK)
|
||||
* here, as we care about whether the file is marked executable at all, and not whether it is
|
||||
* executable for us, because if so, such errors are stuff we should log about. */
|
||||
if (FLAGS_SET(flags, CONF_FILES_EXECUTABLE) && (st.st_mode & 0111) == 0) {
|
||||
log_debug("Ignoring '%s/%s', as it is not marked executable.", root, skip_leading_slash(original_path));
|
||||
continue;
|
||||
}
|
||||
|
||||
_cleanup_(conf_file_freep) ConfFile *c = new(ConfFile, 1);
|
||||
if (!c)
|
||||
return log_oom_debug();
|
||||
|
||||
*c = (ConfFile) {
|
||||
.name = strdup(de->d_name),
|
||||
.result = TAKE_PTR(p),
|
||||
.original_path = TAKE_PTR(original_path),
|
||||
.resolved_path = TAKE_PTR(resolved_path),
|
||||
.fd = TAKE_FD(fd),
|
||||
.st = st,
|
||||
};
|
||||
|
||||
if (!c->name)
|
||||
return log_oom_debug();
|
||||
|
||||
r = hashmap_ensure_put(files, &conf_file_hash_ops, c->name, c);
|
||||
if (r < 0) {
|
||||
assert(r == -ENOMEM);
|
||||
return log_oom_debug();
|
||||
}
|
||||
assert(r > 0);
|
||||
|
||||
TAKE_PTR(c);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dump_files(Hashmap *fh, const char *root, ConfFile ***ret_files, size_t *ret_n_files) {
|
||||
ConfFile **files = NULL;
|
||||
size_t n_files = 0;
|
||||
int r;
|
||||
|
||||
CLEANUP_ARRAY(files, n_files, conf_file_free_many);
|
||||
|
||||
assert(ret_files);
|
||||
assert(ret_n_files);
|
||||
|
||||
/* The entries in the array given by hashmap_dump_sorted() are still owned by the hashmap. */
|
||||
r = hashmap_dump_sorted(fh, (void***) &files, &n_files);
|
||||
if (r < 0)
|
||||
return log_oom_debug();
|
||||
|
||||
/* Hence, we need to remove them from the hashmap. */
|
||||
FOREACH_ARRAY(i, files, n_files)
|
||||
assert_se(hashmap_remove(fh, (*i)->name) == *i);
|
||||
|
||||
if (root)
|
||||
FOREACH_ARRAY(i, files, n_files) {
|
||||
r = conf_file_prefix_root(*i, root);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
*ret_files = TAKE_PTR(files);
|
||||
*ret_n_files = n_files;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int copy_and_sort_files_from_hashmap(Hashmap *fh, const char *root, ConfFilesFlags flags, char ***ret) {
|
||||
_cleanup_strv_free_ char **results = NULL;
|
||||
_cleanup_free_ ConfFile **files = NULL;
|
||||
size_t n_files = 0, n_results = 0;
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
/* The entries in the array given by hashmap_dump_sorted() are still owned by the hashmap.
|
||||
* Hence, do not use conf_file_free_many() for 'entries' */
|
||||
r = hashmap_dump_sorted(fh, (void***) &files, &n_files);
|
||||
if (r < 0)
|
||||
return log_oom_debug();
|
||||
|
||||
FOREACH_ARRAY(i, files, n_files) {
|
||||
ConfFile *c = *i;
|
||||
|
||||
if (FLAGS_SET(flags, CONF_FILES_BASENAME))
|
||||
r = strv_extend_with_size(&results, &n_results, c->name);
|
||||
else if (root) {
|
||||
char *p;
|
||||
|
||||
r = chaseat_prefix_root(c->result, root, &p);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to prefix '%s' with root '%s': %m", c->result, root);
|
||||
|
||||
r = strv_consume_with_size(&results, &n_results, TAKE_PTR(p));
|
||||
} else
|
||||
r = strv_extend_with_size(&results, &n_results, c->result);
|
||||
if (r < 0)
|
||||
return log_oom_debug();
|
||||
}
|
||||
|
||||
*ret = TAKE_PTR(results);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int insert_replacement(Hashmap **fh, ConfFile *replacement, const ConfFile **ret) {
|
||||
_cleanup_(conf_file_freep) ConfFile *c = ASSERT_PTR(replacement);
|
||||
int r;
|
||||
|
||||
assert(fh);
|
||||
assert(ret);
|
||||
|
||||
/* This consumes the input ConfFile. */
|
||||
|
||||
ConfFile *existing = hashmap_get(*fh, c->name);
|
||||
if (existing) {
|
||||
log_debug("An entry with higher priority '%s' -> '%s' already exists, ignoring the replacement: %s",
|
||||
existing->name, existing->result, c->original_path);
|
||||
*ret = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = hashmap_ensure_put(fh, &conf_file_hash_ops, c->name, c);
|
||||
if (r < 0) {
|
||||
assert(r == -ENOMEM);
|
||||
return log_oom_debug();
|
||||
}
|
||||
assert(r > 0);
|
||||
|
||||
log_debug("Inserted replacement: '%s' -> '%s'", c->name, c->result);
|
||||
|
||||
*ret = TAKE_PTR(c);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int conf_files_list_impl(
|
||||
const char *suffix,
|
||||
int rfd,
|
||||
const char *root, /* for logging, can be NULL */
|
||||
ConfFilesFlags flags,
|
||||
const char * const *dirs,
|
||||
const char *replacement,
|
||||
Hashmap **ret,
|
||||
const ConfFile **ret_inserted) {
|
||||
|
||||
_cleanup_hashmap_free_ Hashmap *fh = NULL;
|
||||
_cleanup_set_free_ Set *masked = NULL;
|
||||
_cleanup_(conf_file_freep) ConfFile *c = NULL;
|
||||
const ConfFile *inserted = NULL;
|
||||
int r;
|
||||
|
||||
assert(rfd >= 0 || rfd == AT_FDCWD);
|
||||
assert(ret);
|
||||
|
||||
if (replacement) {
|
||||
r = conf_file_new_at(replacement, rfd, CHASE_NONEXISTENT, &c);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
STRV_FOREACH(p, dirs) {
|
||||
_cleanup_closedir_ DIR *dir = NULL;
|
||||
_cleanup_free_ char *path = NULL;
|
||||
|
||||
r = chase_and_opendirat(rfd, *p, CHASE_AT_RESOLVE_IN_ROOT, &path, &dir);
|
||||
if (r < 0) {
|
||||
if (r != -ENOENT)
|
||||
log_debug_errno(r, "Failed to chase and open directory '%s/%s', ignoring: %m", strempty(root), skip_leading_slash(*p));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c && streq_ptr(path_startswith(c->result, path), c->name)) {
|
||||
r = insert_replacement(&fh, TAKE_PTR(c), &inserted);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = files_add(dir, *p, path, rfd, root, &fh, &masked, suffix, flags);
|
||||
if (r == -ENOMEM)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (c) {
|
||||
r = insert_replacement(&fh, TAKE_PTR(c), &inserted);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
*ret = TAKE_PTR(fh);
|
||||
if (ret_inserted)
|
||||
*ret_inserted = inserted;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -402,6 +567,35 @@ int conf_files_list_strv(
|
||||
return copy_and_sort_files_from_hashmap(fh, empty_to_root(root_abs), flags, ret);
|
||||
}
|
||||
|
||||
int conf_files_list_strv_full(
|
||||
const char *suffix,
|
||||
const char *root,
|
||||
ConfFilesFlags flags,
|
||||
const char * const *dirs,
|
||||
ConfFile ***ret_files,
|
||||
size_t *ret_n_files) {
|
||||
|
||||
_cleanup_hashmap_free_ Hashmap *fh = NULL;
|
||||
_cleanup_close_ int rfd = -EBADF;
|
||||
_cleanup_free_ char *root_abs = NULL;
|
||||
_cleanup_strv_free_ char **dirs_abs = NULL;
|
||||
int r;
|
||||
|
||||
assert(ret_files);
|
||||
assert(ret_n_files);
|
||||
|
||||
r = prepare_dirs(root, (char**) dirs, &rfd, &root_abs, &dirs_abs);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = conf_files_list_impl(suffix, rfd, root_abs, flags, (const char * const *) dirs_abs,
|
||||
/* replacement = */ NULL, &fh, /* ret_inserted = */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return dump_files(fh, empty_to_root(root_abs), ret_files, ret_n_files);
|
||||
}
|
||||
|
||||
int conf_files_list_strv_at(
|
||||
char ***ret,
|
||||
const char *suffix,
|
||||
@@ -426,14 +620,48 @@ int conf_files_list_strv_at(
|
||||
return copy_and_sort_files_from_hashmap(fh, /* root = */ NULL, flags, ret);
|
||||
}
|
||||
|
||||
int conf_files_list_strv_at_full(
|
||||
const char *suffix,
|
||||
int rfd,
|
||||
ConfFilesFlags flags,
|
||||
const char * const *dirs,
|
||||
ConfFile ***ret_files,
|
||||
size_t *ret_n_files) {
|
||||
|
||||
_cleanup_hashmap_free_ Hashmap *fh = NULL;
|
||||
_cleanup_free_ char *root = NULL;
|
||||
int r;
|
||||
|
||||
assert(rfd >= 0 || rfd == AT_FDCWD);
|
||||
assert(ret_files);
|
||||
assert(ret_n_files);
|
||||
|
||||
if (rfd >= 0 && DEBUG_LOGGING)
|
||||
(void) fd_get_path(rfd, &root); /* for logging */
|
||||
|
||||
r = conf_files_list_impl(suffix, rfd, root, flags, dirs, /* replacement = */ NULL, &fh, /* ret_inserted = */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return dump_files(fh, /* root = */ NULL, ret_files, ret_n_files);
|
||||
}
|
||||
|
||||
int conf_files_list(char ***ret, const char *suffix, const char *root, ConfFilesFlags flags, const char *dir) {
|
||||
return conf_files_list_strv(ret, suffix, root, flags, STRV_MAKE_CONST(dir));
|
||||
}
|
||||
|
||||
int conf_files_list_full(const char *suffix, const char *root, ConfFilesFlags flags, const char *dir, ConfFile ***ret_files, size_t *ret_n_files) {
|
||||
return conf_files_list_strv_full(suffix, root, flags, STRV_MAKE_CONST(dir), ret_files, ret_n_files);
|
||||
}
|
||||
|
||||
int conf_files_list_at(char ***ret, const char *suffix, int rfd, ConfFilesFlags flags, const char *dir) {
|
||||
return conf_files_list_strv_at(ret, suffix, rfd, flags, STRV_MAKE_CONST(dir));
|
||||
}
|
||||
|
||||
int conf_files_list_at_full(const char *suffix, int rfd, ConfFilesFlags flags, const char *dir, ConfFile ***ret_files, size_t *ret_n_files) {
|
||||
return conf_files_list_strv_at_full(suffix, rfd, flags, STRV_MAKE_CONST(dir), ret_files, ret_n_files);
|
||||
}
|
||||
|
||||
int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, ConfFilesFlags flags, const char *dirs) {
|
||||
_cleanup_strv_free_ char **d = NULL;
|
||||
|
||||
@@ -446,6 +674,19 @@ int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, Co
|
||||
return conf_files_list_strv(ret, suffix, root, flags, (const char**) d);
|
||||
}
|
||||
|
||||
int conf_files_list_nulstr_full(const char *suffix, const char *root, ConfFilesFlags flags, const char *dirs, ConfFile ***ret_files, size_t *ret_n_files) {
|
||||
_cleanup_strv_free_ char **d = NULL;
|
||||
|
||||
assert(ret_files);
|
||||
assert(ret_n_files);
|
||||
|
||||
d = strv_split_nulstr(dirs);
|
||||
if (!d)
|
||||
return -ENOMEM;
|
||||
|
||||
return conf_files_list_strv_full(suffix, root, flags, (const char**) d, ret_files, ret_n_files);
|
||||
}
|
||||
|
||||
int conf_files_list_nulstr_at(char ***ret, const char *suffix, int rfd, ConfFilesFlags flags, const char *dirs) {
|
||||
_cleanup_strv_free_ char **d = NULL;
|
||||
|
||||
@@ -458,6 +699,19 @@ int conf_files_list_nulstr_at(char ***ret, const char *suffix, int rfd, ConfFile
|
||||
return conf_files_list_strv_at(ret, suffix, rfd, flags, (const char**) d);
|
||||
}
|
||||
|
||||
int conf_files_list_nulstr_at_full(const char *suffix, int rfd, ConfFilesFlags flags, const char *dirs, ConfFile ***ret_files, size_t *ret_n_files) {
|
||||
_cleanup_strv_free_ char **d = NULL;
|
||||
|
||||
assert(ret_files);
|
||||
assert(ret_n_files);
|
||||
|
||||
d = strv_split_nulstr(dirs);
|
||||
if (!d)
|
||||
return -ENOMEM;
|
||||
|
||||
return conf_files_list_strv_at_full(suffix, rfd, flags, (const char**) d, ret_files, ret_n_files);
|
||||
}
|
||||
|
||||
int conf_files_list_with_replacement(
|
||||
const char *root,
|
||||
char **config_dirs,
|
||||
@@ -471,6 +725,7 @@ int conf_files_list_with_replacement(
|
||||
_cleanup_close_ int rfd = -EBADF;
|
||||
_cleanup_free_ char *root_abs = NULL;
|
||||
_cleanup_strv_free_ char **dirs_abs = NULL;
|
||||
const ConfFile *c = NULL;
|
||||
int r;
|
||||
|
||||
assert(ret_files);
|
||||
@@ -480,18 +735,14 @@ int conf_files_list_with_replacement(
|
||||
return r;
|
||||
|
||||
r = conf_files_list_impl(".conf", rfd, root_abs, flags, (const char * const *) dirs_abs,
|
||||
replacement, &fh, ret_inserted ? &inserted : NULL);
|
||||
replacement, &fh, ret_inserted ? &c : NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (inserted) {
|
||||
char *p;
|
||||
|
||||
r = chaseat_prefix_root(inserted, root_abs, &p);
|
||||
if (c) {
|
||||
r = chaseat_prefix_root(c->result, root_abs, &inserted);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to prefix '%s' with root '%s': %m", inserted, empty_to_root(root_abs));
|
||||
|
||||
free_and_replace(inserted, p);
|
||||
return log_debug_errno(r, "Failed to prefix '%s' with root '%s': %m", c->result, empty_to_root(root_abs));
|
||||
}
|
||||
|
||||
r = copy_and_sort_files_from_hashmap(fh, empty_to_root(root_abs), flags, ret_files);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "forward.h"
|
||||
|
||||
typedef enum ConfFilesFlags {
|
||||
@@ -13,12 +15,36 @@ typedef enum ConfFilesFlags {
|
||||
CONF_FILES_FILTER_MASKED = CONF_FILES_FILTER_MASKED_BY_SYMLINK | CONF_FILES_FILTER_MASKED_BY_EMPTY,
|
||||
} ConfFilesFlags;
|
||||
|
||||
typedef struct ConfFile {
|
||||
char *name; /* name of a file found in config directories */
|
||||
char *result; /* resolved config directory with the original file name found in the directory */
|
||||
char *original_path; /* original config directory with the original file name found in the directory */
|
||||
char *resolved_path; /* fully resolved path, where the filename part of the path may be different from the original name */
|
||||
int fd; /* O_PATH fd to resolved_path, -EBADF if the resolved_path does not exist */
|
||||
struct stat st; /* stat of the file. */
|
||||
} ConfFile;
|
||||
|
||||
ConfFile* conf_file_free(ConfFile *c);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(ConfFile*, conf_file_free);
|
||||
void conf_file_free_many(ConfFile **array, size_t n);
|
||||
|
||||
int conf_file_new_at(const char *path, int rfd, ChaseFlags chase_flags, ConfFile **ret);
|
||||
int conf_file_new(const char *path, const char *root, ChaseFlags chase_flags, ConfFile **ret);
|
||||
|
||||
int conf_files_list(char ***ret, const char *suffix, const char *root, ConfFilesFlags flags, const char *dir);
|
||||
int conf_files_list_at(char ***ret, const char *suffix, int rfd, ConfFilesFlags flags, const char *dir);
|
||||
int conf_files_list_strv(char ***ret, const char *suffix, const char *root, ConfFilesFlags flags, const char* const* dirs);
|
||||
int conf_files_list_strv_at(char ***ret, const char *suffix, int rfd, ConfFilesFlags flags, const char * const *dirs);
|
||||
int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, ConfFilesFlags flags, const char *dirs);
|
||||
int conf_files_list_nulstr_at(char ***ret, const char *suffix, int rfd, ConfFilesFlags flags, const char *dirs);
|
||||
|
||||
int conf_files_list_full(const char *suffix, const char *root, ConfFilesFlags flags, const char *dir, ConfFile ***ret_files, size_t *ret_n_files);
|
||||
int conf_files_list_at_full(const char *suffix, int rfd, ConfFilesFlags flags, const char *dir, ConfFile ***ret_files, size_t *ret_n_files);
|
||||
int conf_files_list_strv_full(const char *suffix, const char *root, ConfFilesFlags flags, const char * const *dirs, ConfFile ***ret_files, size_t *ret_n_files);
|
||||
int conf_files_list_strv_at_full(const char *suffix, int rfd, ConfFilesFlags flags, const char * const *dirs, ConfFile ***ret_files, size_t *ret_n_files);
|
||||
int conf_files_list_nulstr_full(const char *suffix, const char *root, ConfFilesFlags flags, const char *dirs, ConfFile ***ret_files, size_t *ret_n_files);
|
||||
int conf_files_list_nulstr_at_full(const char *suffix, int rfd, ConfFilesFlags flags, const char *dirs, ConfFile ***ret_files, size_t *ret_n_files);
|
||||
|
||||
int conf_files_list_with_replacement(
|
||||
const char *root,
|
||||
char **config_dirs,
|
||||
|
||||
@@ -104,6 +104,7 @@ typedef struct Set Set;
|
||||
|
||||
typedef struct dual_timestamp dual_timestamp;
|
||||
typedef struct triple_timestamp triple_timestamp;
|
||||
typedef struct ConfFile ConfFile;
|
||||
typedef struct LockFile LockFile;
|
||||
typedef struct PidRef PidRef;
|
||||
typedef struct Prioq Prioq;
|
||||
|
||||
@@ -19,7 +19,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
assert_se(fd >= 0);
|
||||
assert_se(write(fd, data, size) == (ssize_t) size);
|
||||
|
||||
(void) catalog_import_file(&h, name);
|
||||
(void) catalog_import_file(&h, fd, name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -263,7 +263,7 @@ static int catalog_entry_lang(
|
||||
return strdup_to(ret, t);
|
||||
}
|
||||
|
||||
int catalog_import_file(OrderedHashmap **h, const char *path) {
|
||||
int catalog_import_file(OrderedHashmap **h, int fd, const char *path) {
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
_cleanup_free_ char *payload = NULL;
|
||||
size_t payload_size = 0;
|
||||
@@ -274,9 +274,10 @@ int catalog_import_file(OrderedHashmap **h, const char *path) {
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert(fd >= 0);
|
||||
assert(path);
|
||||
|
||||
f = fopen(path, "re");
|
||||
f = fopen(FORMAT_PROC_FD_PATH(fd), "re");
|
||||
if (!f)
|
||||
return log_error_errno(errno, "Failed to open file %s: %m", path);
|
||||
|
||||
@@ -449,17 +450,23 @@ int catalog_update(const char *database, const char *root, const char* const *di
|
||||
if (!dirs)
|
||||
dirs = catalog_file_dirs;
|
||||
|
||||
_cleanup_strv_free_ char **files = NULL;
|
||||
r = conf_files_list_strv(&files, ".catalog", root, 0, dirs);
|
||||
ConfFile **files = NULL;
|
||||
size_t n_files = 0;
|
||||
|
||||
CLEANUP_ARRAY(files, n_files, conf_file_free_many);
|
||||
|
||||
r = conf_files_list_strv_full(".catalog", root, CONF_FILES_REGULAR | CONF_FILES_FILTER_MASKED, dirs, &files, &n_files);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get catalog files: %m");
|
||||
|
||||
_cleanup_ordered_hashmap_free_ OrderedHashmap *h = NULL;
|
||||
STRV_FOREACH(f, files) {
|
||||
log_debug("Reading file '%s'", *f);
|
||||
r = catalog_import_file(&h, *f);
|
||||
FOREACH_ARRAY(i, files, n_files) {
|
||||
ConfFile *c = *i;
|
||||
|
||||
log_debug("Reading file: '%s' -> '%s'", c->original_path, c->resolved_path);
|
||||
r = catalog_import_file(&h, c->fd, c->original_path);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to import file '%s': %m", *f);
|
||||
return log_error_errno(r, "Failed to import file '%s': %m", c->original_path);
|
||||
}
|
||||
|
||||
if (ordered_hashmap_isempty(h)) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include "forward.h"
|
||||
|
||||
int catalog_import_file(OrderedHashmap **h, const char *path);
|
||||
int catalog_import_file(OrderedHashmap **h, int fd, const char *path);
|
||||
int catalog_update(const char *database, const char *root, const char* const *dirs);
|
||||
int catalog_get(const char *database, sd_id128_t id, char **ret_text);
|
||||
int catalog_list(FILE *f, const char *database, bool oneline);
|
||||
|
||||
@@ -31,7 +31,7 @@ static OrderedHashmap* test_import(const char* contents, ssize_t size, int code)
|
||||
assert_se(fd >= 0);
|
||||
assert_se(write(fd, contents, size) == size);
|
||||
|
||||
assert_se(catalog_import_file(&h, name) == code);
|
||||
assert_se(catalog_import_file(&h, fd, name) == code);
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "conf-files.h"
|
||||
#include "errno-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "fs-util.h"
|
||||
@@ -457,7 +458,7 @@ static int insert_data(struct trie *trie, char **match_list, char *line, const c
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int import_file(struct trie *trie, const char *filename, uint16_t file_priority, bool compat) {
|
||||
static int import_file(struct trie *trie, int fd, const char *filename, uint16_t file_priority, bool compat) {
|
||||
enum {
|
||||
HW_NONE,
|
||||
HW_MATCH,
|
||||
@@ -468,7 +469,11 @@ static int import_file(struct trie *trie, const char *filename, uint16_t file_pr
|
||||
uint32_t line_number = 0;
|
||||
int r;
|
||||
|
||||
f = fopen(filename, "re");
|
||||
assert(trie);
|
||||
assert(fd >= 0);
|
||||
assert(filename);
|
||||
|
||||
f = fopen(FORMAT_PROC_FD_PATH(fd), "re");
|
||||
if (!f)
|
||||
return -errno;
|
||||
|
||||
@@ -572,9 +577,8 @@ static int import_file(struct trie *trie, const char *filename, uint16_t file_pr
|
||||
int hwdb_update(const char *root, const char *hwdb_bin_dir, bool strict, bool compat) {
|
||||
_cleanup_free_ char *hwdb_bin = NULL;
|
||||
_cleanup_(trie_freep) struct trie *trie = NULL;
|
||||
_cleanup_strv_free_ char **files = NULL;
|
||||
uint16_t file_priority = 1;
|
||||
int r = 0, err;
|
||||
int r, ret = 0;
|
||||
|
||||
/* The argument 'compat' controls the format version of database. If false, then hwdb.bin will be
|
||||
* created with additional information such that priority, line number, and filename of database
|
||||
@@ -601,11 +605,16 @@ int hwdb_update(const char *root, const char *hwdb_bin_dir, bool strict, bool co
|
||||
|
||||
trie->nodes_count++;
|
||||
|
||||
err = conf_files_list_strv(&files, ".hwdb", root, 0, conf_file_dirs);
|
||||
if (err < 0)
|
||||
return log_error_errno(err, "Failed to enumerate hwdb files: %m");
|
||||
ConfFile **files = NULL;
|
||||
size_t n_files = 0;
|
||||
|
||||
if (strv_isempty(files)) {
|
||||
CLEANUP_ARRAY(files, n_files, conf_file_free_many);
|
||||
|
||||
r = conf_files_list_strv_full(".hwdb", root, CONF_FILES_REGULAR | CONF_FILES_FILTER_MASKED, conf_file_dirs, &files, &n_files);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enumerate hwdb files: %m");
|
||||
|
||||
if (n_files == 0) {
|
||||
if (unlink(hwdb_bin) < 0) {
|
||||
if (errno != ENOENT)
|
||||
return log_error_errno(errno, "Failed to remove compiled hwdb database %s: %m", hwdb_bin);
|
||||
@@ -617,11 +626,11 @@ int hwdb_update(const char *root, const char *hwdb_bin_dir, bool strict, bool co
|
||||
return 0;
|
||||
}
|
||||
|
||||
STRV_FOREACH(f, files) {
|
||||
log_debug("Reading file \"%s\"", *f);
|
||||
err = import_file(trie, *f, file_priority++, compat);
|
||||
if (err < 0 && strict)
|
||||
r = err;
|
||||
FOREACH_ARRAY(i, files, n_files) {
|
||||
ConfFile *c = *i;
|
||||
|
||||
log_debug("Reading file \"%s\" -> \"%s\"", c->original_path, c->resolved_path);
|
||||
RET_GATHER(ret, import_file(trie, c->fd, c->original_path, file_priority++, compat));
|
||||
}
|
||||
|
||||
strbuf_complete(trie->strings);
|
||||
@@ -641,15 +650,15 @@ int hwdb_update(const char *root, const char *hwdb_bin_dir, bool strict, bool co
|
||||
trie->strings->dedup_len, trie->strings->dedup_count);
|
||||
|
||||
(void) mkdir_parents_label(hwdb_bin, 0755);
|
||||
err = trie_store(trie, hwdb_bin, compat);
|
||||
if (err < 0)
|
||||
return log_error_errno(err, "Failed to write database %s: %m", hwdb_bin);
|
||||
r = trie_store(trie, hwdb_bin, compat);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write database %s: %m", hwdb_bin);
|
||||
|
||||
err = label_fix(hwdb_bin, 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
r = label_fix(hwdb_bin, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return r;
|
||||
return strict ? ret : 0;
|
||||
}
|
||||
|
||||
int hwdb_query(const char *modalias, const char *root) {
|
||||
|
||||
@@ -6,12 +6,15 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "chase.h"
|
||||
#include "color-util.h"
|
||||
#include "conf-files.h"
|
||||
#include "constants.h"
|
||||
#include "env-util.h"
|
||||
#include "errno-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "fs-util.h"
|
||||
#include "log.h"
|
||||
#include "path-util.h"
|
||||
#include "pretty-print.h"
|
||||
@@ -183,34 +186,45 @@ int terminal_urlify_man(const char *page, const char *section, char **ret) {
|
||||
return terminal_urlify(url, text, ret);
|
||||
}
|
||||
|
||||
static int cat_file(const char *filename, bool newline, CatFlags flags) {
|
||||
static int cat_file(const ConfFile *c, bool *newline, CatFlags flags) {
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
_cleanup_free_ char *urlified = NULL, *section = NULL, *old_section = NULL;
|
||||
int r;
|
||||
|
||||
assert(filename);
|
||||
assert(c);
|
||||
assert(c->original_path);
|
||||
assert(c->resolved_path);
|
||||
assert(c->fd >= 0);
|
||||
|
||||
f = fopen(filename, "re");
|
||||
if (!f)
|
||||
return log_error_errno(errno, "Failed to open \"%s\": %m", filename);
|
||||
if (newline) {
|
||||
if (*newline)
|
||||
putc('\n', stdout);
|
||||
*newline = true;
|
||||
}
|
||||
|
||||
r = terminal_urlify_path(filename, NULL, &urlified);
|
||||
bool resolved = !path_equal(c->original_path, c->resolved_path);
|
||||
|
||||
r = terminal_urlify_path(c->resolved_path, NULL, &urlified);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to urlify path \"%s\": %m", filename);
|
||||
return log_error_errno(r, "Failed to urlify path \"%s\": %m", c->resolved_path);
|
||||
|
||||
printf("%s%s# %s%s\n",
|
||||
newline ? "\n" : "",
|
||||
printf("%s# %s%s%s%s\n",
|
||||
ansi_highlight_blue(),
|
||||
resolved ? c->original_path : "",
|
||||
resolved ? " -> " : "",
|
||||
urlified,
|
||||
ansi_normal());
|
||||
fflush(stdout);
|
||||
|
||||
f = fopen(FORMAT_PROC_FD_PATH(c->fd), "re");
|
||||
if (!f)
|
||||
return log_error_errno(errno, "Failed to open \"%s\": %m", c->resolved_path);
|
||||
|
||||
for (bool continued = false;;) {
|
||||
_cleanup_free_ char *line = NULL;
|
||||
|
||||
r = read_line(f, LONG_LINE_MAX, &line);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read \"%s\": %m", filename);
|
||||
return log_error_errno(r, "Failed to read \"%s\": %m", c->resolved_path);
|
||||
if (r == 0)
|
||||
break;
|
||||
|
||||
@@ -291,22 +305,45 @@ static int cat_file(const char *filename, bool newline, CatFlags flags) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cat_files(const char *file, char **dropins, CatFlags flags) {
|
||||
int cat_files_full(const ConfFile *file, ConfFile * const *dropins, size_t n_dropins, CatFlags flags) {
|
||||
bool newline = false;
|
||||
int ret = 0;
|
||||
|
||||
assert(dropins || n_dropins == 0);
|
||||
|
||||
if (file)
|
||||
ret = cat_file(file, &newline, flags);
|
||||
|
||||
FOREACH_ARRAY(i, dropins, n_dropins)
|
||||
RET_GATHER(ret, cat_file(*i, &newline, flags));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cat_file_by_path(const char *p, bool *newline, CatFlags flags) {
|
||||
_cleanup_(conf_file_freep) ConfFile *c = NULL;
|
||||
int r;
|
||||
|
||||
if (file) {
|
||||
r = cat_file(file, /* newline= */ false, flags);
|
||||
if (r < 0)
|
||||
return log_warning_errno(r, "Failed to cat %s: %m", file);
|
||||
}
|
||||
assert(p);
|
||||
|
||||
STRV_FOREACH(path, dropins) {
|
||||
r = cat_file(*path, /* newline= */ file || path != dropins, flags);
|
||||
if (r < 0)
|
||||
return log_warning_errno(r, "Failed to cat %s: %m", *path);
|
||||
}
|
||||
r = conf_file_new(p, /* root = */ NULL, CHASE_MUST_BE_REGULAR, &c);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to chase '%s': %m", p);
|
||||
|
||||
return 0;
|
||||
return cat_file(c, newline, flags);
|
||||
}
|
||||
|
||||
int cat_files(const char *file, char **dropins, CatFlags flags) {
|
||||
bool newline = false;
|
||||
int ret = 0;
|
||||
|
||||
if (file)
|
||||
ret = cat_file_by_path(file, &newline, flags);
|
||||
|
||||
STRV_FOREACH(path, dropins)
|
||||
RET_GATHER(ret, cat_file_by_path(*path, &newline, flags));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void print_separator(void) {
|
||||
@@ -384,8 +421,7 @@ static int guess_type(const char **name, char ***ret_prefixes, bool *ret_is_coll
|
||||
}
|
||||
|
||||
int conf_files_cat(const char *root, const char *name, CatFlags flags) {
|
||||
_cleanup_strv_free_ char **dirs = NULL, **files = NULL;
|
||||
_cleanup_free_ char *path = NULL;
|
||||
_cleanup_strv_free_ char **dirs = NULL;
|
||||
char **prefixes = NULL; /* explicit initialization to appease gcc */
|
||||
bool is_collection;
|
||||
const char *extension;
|
||||
@@ -416,17 +452,18 @@ int conf_files_cat(const char *root, const char *name, CatFlags flags) {
|
||||
}
|
||||
|
||||
/* First locate the main config file, if any */
|
||||
_cleanup_(conf_file_freep) ConfFile *c = NULL;
|
||||
if (!is_collection) {
|
||||
STRV_FOREACH(prefix, prefixes) {
|
||||
path = path_join(root, *prefix, name);
|
||||
if (!path)
|
||||
_cleanup_free_ char *p = path_join(*prefix, name);
|
||||
if (!p)
|
||||
return log_oom();
|
||||
if (access(path, F_OK) == 0)
|
||||
|
||||
if (conf_file_new(p, root, CHASE_MUST_BE_REGULAR, &c) >= 0)
|
||||
break;
|
||||
path = mfree(path);
|
||||
}
|
||||
|
||||
if (!path)
|
||||
if (!c)
|
||||
printf("%s# Main configuration file %s not found%s\n",
|
||||
ansi_highlight_magenta(),
|
||||
name,
|
||||
@@ -434,7 +471,10 @@ int conf_files_cat(const char *root, const char *name, CatFlags flags) {
|
||||
}
|
||||
|
||||
/* Then locate the drop-ins, if any */
|
||||
r = conf_files_list_strv(&files, extension, root, 0, (const char* const*) dirs);
|
||||
ConfFile **dropins = NULL;
|
||||
size_t n_dropins = 0;
|
||||
CLEANUP_ARRAY(dropins, n_dropins, conf_file_free_many);
|
||||
r = conf_files_list_strv_full(extension, root, CONF_FILES_REGULAR | CONF_FILES_FILTER_MASKED, (const char* const*) dirs, &dropins, &n_dropins);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to query file list: %m");
|
||||
|
||||
@@ -442,7 +482,7 @@ int conf_files_cat(const char *root, const char *name, CatFlags flags) {
|
||||
if (is_collection)
|
||||
flags |= CAT_FORMAT_HAS_SECTIONS;
|
||||
|
||||
return cat_files(path, files, flags);
|
||||
return cat_files_full(c, dropins, n_dropins, flags);
|
||||
}
|
||||
|
||||
int terminal_tint_color(double hue, char **ret) {
|
||||
|
||||
@@ -30,6 +30,7 @@ typedef enum CatFlags {
|
||||
CAT_TLDR = 1 << 2, /* Only print comments and relevant section headers */
|
||||
} CatFlags;
|
||||
|
||||
int cat_files_full(const ConfFile *file, ConfFile * const *dropins, size_t n_dropins, CatFlags flags);
|
||||
int cat_files(const char *file, char **dropins, CatFlags flags);
|
||||
int conf_files_cat(const char *root, const char *name, CatFlags flags);
|
||||
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "chase.h"
|
||||
#include "conf-files.h"
|
||||
#include "fd-util.h"
|
||||
#include "fuzz.h"
|
||||
#include "tests.h"
|
||||
#include "tmpfile-util.h"
|
||||
#include "udev-rules.h"
|
||||
|
||||
@@ -24,7 +27,11 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
fflush(f);
|
||||
|
||||
assert_se(rules = udev_rules_new(RESOLVE_NAME_EARLY));
|
||||
r = udev_rules_parse_file(rules, filename, /* extra_checks = */ false, NULL);
|
||||
|
||||
_cleanup_(conf_file_freep) ConfFile *c = NULL;
|
||||
ASSERT_OK(conf_file_new(filename, /* root = */ NULL, CHASE_MUST_BE_REGULAR, &c));
|
||||
|
||||
r = udev_rules_parse_file(rules, c, /* extra_checks = */ false, /* ret = */ NULL);
|
||||
log_info_errno(r, "Parsing %s: %m", filename);
|
||||
assert_se(r >= 0 || /* OK */
|
||||
r == -ENOBUFS); /* line length exceeded */
|
||||
|
||||
@@ -1645,46 +1645,34 @@ static void udev_check_rule_line(UdevRuleLine *line) {
|
||||
udev_check_conflicts_duplicates(line);
|
||||
}
|
||||
|
||||
int udev_rules_parse_file(UdevRules *rules, const char *filename, bool extra_checks, UdevRuleFile **ret) {
|
||||
int udev_rules_parse_file(UdevRules *rules, const ConfFile *c, bool extra_checks, UdevRuleFile **ret) {
|
||||
_cleanup_(udev_rule_file_freep) UdevRuleFile *rule_file = NULL;
|
||||
_cleanup_free_ char *name = NULL;
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
struct stat st;
|
||||
int r;
|
||||
|
||||
assert(rules);
|
||||
assert(filename);
|
||||
assert(c);
|
||||
assert(c->fd >= 0);
|
||||
assert(c->original_path);
|
||||
|
||||
f = fopen(filename, "re");
|
||||
f = fopen(FORMAT_PROC_FD_PATH(c->fd), "re");
|
||||
if (!f) {
|
||||
if (extra_checks)
|
||||
return -errno;
|
||||
|
||||
if (errno == ENOENT)
|
||||
return 0;
|
||||
|
||||
return log_warning_errno(errno, "Failed to open %s, ignoring: %m", filename);
|
||||
return log_warning_errno(errno, "Failed to open %s, ignoring: %m", c->original_path);
|
||||
}
|
||||
|
||||
if (fstat(fileno(f), &st) < 0)
|
||||
return log_warning_errno(errno, "Failed to stat %s, ignoring: %m", filename);
|
||||
|
||||
if (null_or_empty(&st)) {
|
||||
log_debug("Skipping empty file: %s", filename);
|
||||
if (ret)
|
||||
*ret = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = hashmap_put_stats_by_path(&rules->stats_by_path, filename, &st);
|
||||
r = hashmap_put_stats_by_path(&rules->stats_by_path, c->original_path, &c->st);
|
||||
if (r < 0)
|
||||
return log_warning_errno(r, "Failed to save stat for %s, ignoring: %m", filename);
|
||||
return log_warning_errno(r, "Failed to save stat for %s, ignoring: %m", c->original_path);
|
||||
|
||||
(void) fd_warn_permissions(filename, fileno(f));
|
||||
(void) stat_warn_permissions(c->original_path, &c->st);
|
||||
|
||||
log_debug("Reading rules file: %s", filename);
|
||||
log_debug("Reading rules file: %s", c->original_path);
|
||||
|
||||
name = strdup(filename);
|
||||
name = strdup(c->original_path);
|
||||
if (!name)
|
||||
return log_oom();
|
||||
|
||||
@@ -1775,7 +1763,7 @@ int udev_rules_parse_file(UdevRules *rules, const char *filename, bool extra_che
|
||||
*ret = rule_file;
|
||||
|
||||
TAKE_PTR(rule_file);
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned udev_rule_file_get_issues(UdevRuleFile *rule_file) {
|
||||
@@ -1800,7 +1788,7 @@ UdevRules* udev_rules_new(ResolveNameTiming resolve_name_timing) {
|
||||
|
||||
int udev_rules_load(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing, char * const *extra) {
|
||||
_cleanup_(udev_rules_freep) UdevRules *rules = NULL;
|
||||
_cleanup_strv_free_ char **files = NULL, **directories = NULL;
|
||||
_cleanup_strv_free_ char **directories = NULL;
|
||||
int r;
|
||||
|
||||
rules = udev_rules_new(resolve_name_timing);
|
||||
@@ -1817,14 +1805,22 @@ int udev_rules_load(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = conf_files_list_strv(&files, ".rules", NULL, 0, (const char* const*) directories);
|
||||
ConfFile **files = NULL;
|
||||
size_t n_files = 0;
|
||||
|
||||
CLEANUP_ARRAY(files, n_files, conf_file_free_many);
|
||||
|
||||
r = conf_files_list_strv_full(".rules", /* root = */ NULL, CONF_FILES_REGULAR | CONF_FILES_FILTER_MASKED,
|
||||
(const char* const*) directories, &files, &n_files);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to enumerate rules files: %m");
|
||||
|
||||
STRV_FOREACH(f, files) {
|
||||
r = udev_rules_parse_file(rules, *f, /* extra_checks = */ false, NULL);
|
||||
FOREACH_ARRAY(i, files, n_files) {
|
||||
ConfFile *c = *i;
|
||||
|
||||
r = udev_rules_parse_file(rules, c, /* extra_checks = */ false, /* ret = */ NULL);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to read rules file %s, ignoring: %m", *f);
|
||||
log_debug_errno(r, "Failed to read rules file '%s', ignoring: %m", c->original_path);
|
||||
}
|
||||
|
||||
*ret_rules = TAKE_PTR(rules);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "udev-forward.h"
|
||||
|
||||
int udev_rule_parse_value(char *str, char **ret_value, char **ret_endpos, bool *ret_is_case_insensitive);
|
||||
int udev_rules_parse_file(UdevRules *rules, const char *filename, bool extra_checks, UdevRuleFile **ret);
|
||||
int udev_rules_parse_file(UdevRules *rules, const ConfFile *c, bool extra_checks, UdevRuleFile **ret);
|
||||
unsigned udev_rule_file_get_issues(UdevRuleFile *rule_file);
|
||||
UdevRules* udev_rules_new(ResolveNameTiming resolve_name_timing);
|
||||
int udev_rules_load(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing, char * const *extra);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <getopt.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "conf-files.h"
|
||||
#include "log.h"
|
||||
#include "parse-argument.h"
|
||||
#include "pretty-print.h"
|
||||
@@ -101,11 +102,15 @@ int cat_main(int argc, char *argv[], void *userdata) {
|
||||
if (arg_config)
|
||||
return conf_files_cat(arg_root, "udev/udev.conf", arg_cat_flags);
|
||||
|
||||
_cleanup_strv_free_ char **files = NULL;
|
||||
r = search_rules_files(strv_skip(argv, optind), arg_root, &files);
|
||||
ConfFile **files = NULL;
|
||||
size_t n_files = 0;
|
||||
|
||||
CLEANUP_ARRAY(files, n_files, conf_file_free_many);
|
||||
|
||||
r = search_rules_files(strv_skip(argv, optind), arg_root, &files, &n_files);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* udev rules file does not support dropin configs. So, we can safely pass multiple files as dropins. */
|
||||
return cat_files(/* file = */ NULL, /* dropins = */ files, arg_cat_flags);
|
||||
return cat_files_full(/* file = */ NULL, files, n_files, arg_cat_flags);
|
||||
}
|
||||
|
||||
@@ -221,108 +221,122 @@ int udev_ping(usec_t timeout_usec, bool ignore_connection_failure) {
|
||||
return 1; /* received reply */
|
||||
}
|
||||
|
||||
static int search_rules_file_in_conf_dirs(const char *s, const char *root, char ***files) {
|
||||
_cleanup_free_ char *filename = NULL;
|
||||
static int search_rules_file_in_conf_dirs(const char *s, const char *root, ConfFile ***files, size_t *n_files) {
|
||||
_cleanup_free_ char *with_suffix = NULL;
|
||||
int r;
|
||||
|
||||
assert(s);
|
||||
assert(files);
|
||||
assert(n_files);
|
||||
|
||||
if (!endswith(s, ".rules"))
|
||||
filename = strjoin(s, ".rules");
|
||||
else
|
||||
filename = strdup(s);
|
||||
if (!filename)
|
||||
return log_oom();
|
||||
if (isempty(s) || is_path(s))
|
||||
return 0;
|
||||
|
||||
if (!filename_is_valid(filename))
|
||||
if (!endswith(s, ".rules")) {
|
||||
with_suffix = strjoin(s, ".rules");
|
||||
if (!with_suffix)
|
||||
return log_oom();
|
||||
|
||||
s = with_suffix;
|
||||
}
|
||||
|
||||
if (!filename_is_valid(s))
|
||||
return 0;
|
||||
|
||||
STRV_FOREACH(p, CONF_PATHS_STRV("udev/rules.d")) {
|
||||
_cleanup_free_ char *path = NULL, *resolved = NULL;
|
||||
|
||||
path = path_join(*p, filename);
|
||||
_cleanup_free_ char *path = path_join(*p, s);
|
||||
if (!path)
|
||||
return log_oom();
|
||||
|
||||
r = chase(path, root, CHASE_PREFIX_ROOT | CHASE_MUST_BE_REGULAR, &resolved, /* ret_fd = */ NULL);
|
||||
_cleanup_(conf_file_freep) ConfFile *c = NULL;
|
||||
r = conf_file_new(path, root, CHASE_MUST_BE_REGULAR, &c);
|
||||
if (r == -ENOENT)
|
||||
continue;
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to chase \"%s\": %m", path);
|
||||
|
||||
r = strv_consume(files, TAKE_PTR(resolved));
|
||||
if (r < 0)
|
||||
if (!GREEDY_REALLOC_APPEND(*files, *n_files, &c, 1))
|
||||
return log_oom();
|
||||
|
||||
TAKE_PTR(c);
|
||||
return 1; /* found */
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int search_rules_file(const char *s, const char *root, char ***files) {
|
||||
static int search_rules_file(const char *s, const char *root, ConfFile ***files, size_t *n_files) {
|
||||
int r;
|
||||
|
||||
assert(s);
|
||||
assert(files);
|
||||
assert(n_files);
|
||||
|
||||
/* If the input is a file name (e.g. 99-systemd.rules), then try to find it in udev/rules.d directories. */
|
||||
r = search_rules_file_in_conf_dirs(s, root, files);
|
||||
r = search_rules_file_in_conf_dirs(s, root, files, n_files);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
/* If not found, or if it is a path, then chase it. */
|
||||
struct stat st;
|
||||
_cleanup_free_ char *resolved = NULL;
|
||||
r = chase_and_stat(s, root, CHASE_PREFIX_ROOT, &resolved, &st);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to chase \"%s\": %m", s);
|
||||
|
||||
r = stat_verify_regular(&st);
|
||||
if (r == -EISDIR) {
|
||||
_cleanup_strv_free_ char **files_in_dir = NULL;
|
||||
|
||||
r = conf_files_list_strv(&files_in_dir, ".rules", root, 0, (const char* const*) STRV_MAKE_CONST(s));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enumerate rules files in '%s': %m", resolved);
|
||||
|
||||
r = strv_extend_strv_consume(files, TAKE_PTR(files_in_dir), /* filter_duplicates = */ false);
|
||||
if (r < 0)
|
||||
_cleanup_(conf_file_freep) ConfFile *c = NULL;
|
||||
r = conf_file_new(s, root, CHASE_MUST_BE_REGULAR, &c);
|
||||
if (r >= 0) {
|
||||
if (!GREEDY_REALLOC_APPEND(*files, *n_files, &c, 1))
|
||||
return log_oom();
|
||||
|
||||
TAKE_PTR(c);
|
||||
return 0;
|
||||
}
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "'%s' is neither a regular file nor a directory: %m", resolved);
|
||||
|
||||
r = strv_consume(files, TAKE_PTR(resolved));
|
||||
if (r != -EISDIR)
|
||||
return log_error_errno(r, "Failed to chase \"%s\": %m", s);
|
||||
|
||||
/* If a directory is specified, then find all rules file in the directory. */
|
||||
ConfFile **f = NULL;
|
||||
size_t n = 0;
|
||||
|
||||
CLEANUP_ARRAY(f, n, conf_file_free_many);
|
||||
|
||||
r = conf_files_list_strv_full(".rules", root, CONF_FILES_REGULAR, (const char* const*) STRV_MAKE_CONST(s), &f, &n);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enumerate rules files in '%s': %m", s);
|
||||
|
||||
if (!GREEDY_REALLOC_APPEND(*files, *n_files, f, n))
|
||||
return log_oom();
|
||||
|
||||
TAKE_PTR(f);
|
||||
n = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int search_rules_files(char * const *a, const char *root, char ***ret) {
|
||||
_cleanup_strv_free_ char **files = NULL;
|
||||
int search_rules_files(char * const *a, const char *root, ConfFile ***ret_files, size_t *ret_n_files) {
|
||||
ConfFile **files = NULL;
|
||||
size_t n_files = 0;
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
CLEANUP_ARRAY(files, n_files, conf_file_free_many);
|
||||
|
||||
assert(ret_files);
|
||||
assert(ret_n_files);
|
||||
|
||||
if (strv_isempty(a)) {
|
||||
r = conf_files_list_strv(&files, ".rules", root, 0, (const char* const*) CONF_PATHS_STRV("udev/rules.d"));
|
||||
r = conf_files_list_strv_full(".rules", root, CONF_FILES_REGULAR | CONF_FILES_FILTER_MASKED,
|
||||
(const char* const*) CONF_PATHS_STRV("udev/rules.d"), &files, &n_files);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enumerate rules files: %m");
|
||||
|
||||
if (root && strv_isempty(files))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No rules files found in %s.", root);
|
||||
if (root && n_files == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No rules files found in '%s'.", root);
|
||||
|
||||
} else
|
||||
STRV_FOREACH(s, a) {
|
||||
r = search_rules_file(*s, root, &files);
|
||||
r = search_rules_file(*s, root, &files, &n_files);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
*ret = TAKE_PTR(files);
|
||||
*ret_files = TAKE_PTR(files);
|
||||
*ret_n_files = n_files;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -12,4 +12,4 @@ int parse_device_action(const char *str, sd_device_action_t *ret);
|
||||
int parse_resolve_name_timing(const char *str, ResolveNameTiming *ret);
|
||||
int parse_key_value_argument(const char *str, bool require_value, char **key, char **value);
|
||||
int udev_ping(usec_t timeout, bool ignore_connection_failure);
|
||||
int search_rules_files(char * const *a, const char *root, char ***ret);
|
||||
int search_rules_files(char * const *a, const char *root, ConfFile ***ret_files, size_t *ret_n_files);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "conf-files.h"
|
||||
#include "errno-util.h"
|
||||
#include "log.h"
|
||||
#include "parse-argument.h"
|
||||
@@ -100,37 +101,39 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int verify_rules_file(UdevRules *rules, const char *fname) {
|
||||
static int verify_rules_file(UdevRules *rules, const ConfFile *c) {
|
||||
UdevRuleFile *file;
|
||||
int r;
|
||||
|
||||
r = udev_rules_parse_file(rules, fname, /* extra_checks = */ true, &file);
|
||||
assert(rules);
|
||||
assert(c);
|
||||
|
||||
r = udev_rules_parse_file(rules, c, /* extra_checks = */ true, &file);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse rules file %s: %m", fname);
|
||||
if (r == 0) /* empty file. */
|
||||
return 0;
|
||||
return log_error_errno(r, "Failed to parse rules file %s: %m", c->original_path);
|
||||
|
||||
unsigned issues = udev_rule_file_get_issues(file);
|
||||
unsigned mask = (1U << LOG_ERR) | (1U << LOG_WARNING);
|
||||
if (issues & mask)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"%s: udev rules check failed.", fname);
|
||||
"%s: udev rules check failed.", c->original_path);
|
||||
|
||||
if (arg_style && (issues & (1U << LOG_NOTICE)))
|
||||
return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"%s: udev rules have style issues.", fname);
|
||||
"%s: udev rules have style issues.", c->original_path);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int verify_rules(UdevRules *rules, char **files) {
|
||||
static int verify_rules(UdevRules *rules, ConfFile * const *files, size_t n_files) {
|
||||
size_t fail_count = 0, success_count = 0;
|
||||
int r, ret = 0;
|
||||
|
||||
assert(rules);
|
||||
assert(files || n_files == 0);
|
||||
|
||||
STRV_FOREACH(fp, files) {
|
||||
r = verify_rules_file(rules, *fp);
|
||||
FOREACH_ARRAY(i, files, n_files) {
|
||||
r = verify_rules_file(rules, *i);
|
||||
if (r < 0)
|
||||
++fail_count;
|
||||
else
|
||||
@@ -165,10 +168,14 @@ int verify_main(int argc, char *argv[], void *userdata) {
|
||||
if (!rules)
|
||||
return -ENOMEM;
|
||||
|
||||
_cleanup_strv_free_ char **files = NULL;
|
||||
r = search_rules_files(strv_skip(argv, optind), arg_root, &files);
|
||||
ConfFile **files = NULL;
|
||||
size_t n_files = 0;
|
||||
|
||||
CLEANUP_ARRAY(files, n_files, conf_file_free_many);
|
||||
|
||||
r = search_rules_files(strv_skip(argv, optind), arg_root, &files, &n_files);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return verify_rules(rules, files);
|
||||
return verify_rules(rules, files, n_files);
|
||||
}
|
||||
|
||||
@@ -132,11 +132,11 @@ assert_0 "${rules_dir}"
|
||||
|
||||
# Directory with a loop.
|
||||
ln -s . "${rules_dir}/loop.rules"
|
||||
assert_1 "${rules_dir}"
|
||||
assert_0 "${rules_dir}"
|
||||
rm "${rules_dir}/loop.rules"
|
||||
|
||||
# Empty rules.
|
||||
touch "${rules_dir}/empty.rules"
|
||||
# Effectively empty rules.
|
||||
echo '#' >"${rules_dir}/empty.rules"
|
||||
assert_0 --root="${workdir}"
|
||||
: >"${exo}"
|
||||
assert_0 --root="${workdir}" --no-summary
|
||||
@@ -315,8 +315,10 @@ if ! getent passwd 12345 >/dev/null; then
|
||||
test_syntax_error 'OWNER="12345"' "Unknown user '12345', ignoring."
|
||||
fi
|
||||
# regular user
|
||||
test_syntax_error 'OWNER="testuser"' "User 'testuser' is not a system user, ignoring."
|
||||
test_syntax_error "OWNER=\"$(id -u testuser)\"" "User '$(id -u testuser)' is not a system user, ignoring."
|
||||
if getent passwd testuser >/dev/null; then
|
||||
test_syntax_error 'OWNER="testuser"' "User 'testuser' is not a system user, ignoring."
|
||||
test_syntax_error "OWNER=\"$(id -u testuser)\"" "User '$(id -u testuser)' is not a system user, ignoring."
|
||||
fi
|
||||
test_syntax_error 'GROUP{a}="b"' 'Invalid attribute for GROUP.'
|
||||
test_syntax_error 'GROUP-="b"' 'Invalid operator for GROUP.'
|
||||
test_syntax_error 'GROUP!="b"' 'Invalid operator for GROUP.'
|
||||
@@ -345,8 +347,10 @@ if ! getent group 12345 >/dev/null; then
|
||||
test_syntax_error 'GROUP="12345"' "Unknown group '12345', ignoring."
|
||||
fi
|
||||
# regular group
|
||||
test_syntax_error 'GROUP="testuser"' "Group 'testuser' is not a system group, ignoring."
|
||||
test_syntax_error "GROUP=\"$(id -g testuser)\"" "Group '$(id -g testuser)' is not a system group, ignoring."
|
||||
if getent group testuser >/dev/null; then
|
||||
test_syntax_error 'GROUP="testuser"' "Group 'testuser' is not a system group, ignoring."
|
||||
test_syntax_error "GROUP=\"$(id -g testuser)\"" "Group '$(id -g testuser)' is not a system group, ignoring."
|
||||
fi
|
||||
test_syntax_error 'MODE{a}="b"' 'Invalid attribute for MODE.'
|
||||
test_syntax_error 'MODE-="b"' 'Invalid operator for MODE.'
|
||||
test_syntax_error 'MODE!="b"' 'Invalid operator for MODE.'
|
||||
|
||||
@@ -313,6 +313,7 @@ if [[ ! -v ASAN_OPTIONS ]]; then
|
||||
# check that systemd-analyze cat-config paths work in a chroot
|
||||
mkdir -p /tmp/root
|
||||
mount --bind / /tmp/root
|
||||
mount -t proc proc /tmp/root/proc
|
||||
if mountpoint -q /usr; then
|
||||
mount --bind /usr /tmp/root/usr
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user