From 397befbb10708daf2d4bc11f8bbb5286798f9eaa Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 13 Jan 2026 14:39:28 +0900 Subject: [PATCH] conf-files: split out several helper functions from files_add() No functional change, preparation for later commit. (cherry picked from commit 4a8e0ea4ec80fc5b9ce93ee8d114b205ceda4759) --- src/basic/conf-files.c | 248 +++++++++++++++++++++++++---------------- 1 file changed, 155 insertions(+), 93 deletions(-) diff --git a/src/basic/conf-files.c b/src/basic/conf-files.c index ea41c4da4a2..ccf1601eb80 100644 --- a/src/basic/conf-files.c +++ b/src/basic/conf-files.c @@ -112,6 +112,146 @@ static int conf_file_prefix_root(ConfFile *c, const char *root) { return 0; } +static bool conf_files_need_stat(ConfFilesFlags flags) { + return (flags & (CONF_FILES_FILTER_MASKED | CONF_FILES_REGULAR | CONF_FILES_DIRECTORY | CONF_FILES_EXECUTABLE)) != 0; +} + +static ChaseFlags conf_files_chase_flags(ConfFilesFlags flags) { + ChaseFlags chase_flags = CHASE_AT_RESOLVE_IN_ROOT; + + if (!conf_files_need_stat(flags) || 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; + + return chase_flags; +} + +static int conf_file_chase_and_verify( + int rfd, + const char *root, /* for logging, can be NULL */ + const char *original_path, /* for logging */ + const char *path, + const char *name, + Set **masked, /* optional */ + ConfFilesFlags flags, + char **ret_path, + int *ret_fd, + struct stat *ret_stat) { + + _cleanup_free_ char *resolved_path = NULL; + _cleanup_close_ int fd = -EBADF; + struct stat st = {}; + int r; + + assert(rfd >= 0 || rfd == AT_FDCWD); + assert(original_path); + assert(path); + assert(name); + + root = empty_to_root(root); + + r = chaseat(rfd, path, conf_files_chase_flags(flags), &resolved_path, &fd); + if (r < 0) + return log_debug_errno(r, "Failed to chase '%s%s': %m", + root, skip_leading_slash(original_path)); + 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")) { + if (masked) { + /* Mark this one as masked */ + r = set_put_strdup(masked, name); + if (r < 0) + return log_oom_debug(); + } + + return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), + "File '%s%s' is a mask (symlink to /dev/null).", + root, skip_leading_slash(original_path)); + } + } + + if (conf_files_need_stat(flags)) + /* If we need to have stat, skip the entry. */ + return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), "Failed to chase '%s%s': %m", + root, skip_leading_slash(original_path)); + } + + /* Even if we do not need stat, let's take stat now. The caller may use the info later. */ + if (fd >= 0 && fstat(fd, &st) < 0) + return log_debug_errno(errno, "Failed to stat '%s%s': %m", + root, skip_leading_slash(original_path)); + + /* Is this a masking entry? */ + if (FLAGS_SET(flags, CONF_FILES_FILTER_MASKED_BY_SYMLINK) && stat_may_be_dev_null(&st)) { + if (masked) { + /* Mark this one as masked */ + r = set_put_strdup(masked, name); + if (r < 0) + return log_oom_debug(); + } + + return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), + "File '%s%s' is a mask (symlink to /dev/null).", + root, skip_leading_slash(original_path)); + } + + if (FLAGS_SET(flags, CONF_FILES_FILTER_MASKED_BY_EMPTY) && stat_is_empty(&st)) { + if (masked) { + /* Mark this one as masked */ + r = set_put_strdup(masked, name); + if (r < 0) + return log_oom_debug(); + } + + return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), + "File '%s%s' is a mask (an empty file).", + root, skip_leading_slash(original_path)); + } + + if (FLAGS_SET(flags, CONF_FILES_REGULAR|CONF_FILES_DIRECTORY)) { + if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADFD), + "File '%s%s' is neither a regular file or directory.", + root, skip_leading_slash(original_path)); + } else { + /* Is this node a regular file? */ + if (FLAGS_SET(flags, CONF_FILES_REGULAR)) { + r = stat_verify_regular(&st); + if (r < 0) + return log_debug_errno(r, "File '%s%s' is not a regular file: %m", + root, skip_leading_slash(original_path)); + } + + /* Is this node a directory? */ + if (FLAGS_SET(flags, CONF_FILES_DIRECTORY)) { + r = stat_verify_directory(&st); + if (r < 0) + return log_debug_errno(r, "File '%s%s' is not a directory: %m", + root, skip_leading_slash(original_path)); + } + } + + /* 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) + return log_debug_errno(SYNTHETIC_ERRNO(ENOEXEC), + "File '%s%s' is not marked executable.", + root, skip_leading_slash(original_path)); + + if (ret_path) + *ret_path = TAKE_PTR(resolved_path); + if (ret_fd) + *ret_fd = TAKE_FD(fd); + if (ret_stat) + *ret_stat = st; + + return 0; +} + int conf_file_new_at(const char *path, int rfd, ChaseFlags chase_flags, ConfFile **ret) { int r; @@ -261,100 +401,22 @@ static int files_add( _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.", - 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)); + struct stat st; + r = conf_file_chase_and_verify( + rfd, + root, + original_path, + p, + de->d_name, + masked, + flags, + &resolved_path, + &fd, + &st); + if (r == -ENOMEM) + return r; + if (r < 0) continue; - } _cleanup_(conf_file_freep) ConfFile *c = new(ConfFile, 1); if (!c) -- 2.47.3