diff --git a/src/basic/conf-files.c b/src/basic/conf-files.c index 2d55142581..85b9ea8e5f 100644 --- a/src/basic/conf-files.c +++ b/src/basic/conf-files.c @@ -23,6 +23,8 @@ 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, @@ -32,12 +34,14 @@ static int files_add( assert(dir); assert(dirpath); + assert(rfd >= 0 || rfd == AT_FDCWD); assert(files); assert(masked); - FOREACH_DIRENT(de, dir, return -errno) { - _cleanup_free_ char *n = NULL, *p = NULL; - struct stat st; + 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)) @@ -45,31 +49,83 @@ static int files_add( /* Has this file already been found in an earlier directory? */ if (hashmap_contains(*files, de->d_name)) { - log_debug("Skipping overridden file '%s/%s'.", dirpath, 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) && set_contains(*masked, de->d_name)) { - log_debug("File '%s/%s' is masked by previous entry.", dirpath, de->d_name); + 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; } - /* Read file metadata if we shall validate the check for file masks, for node types or whether the node is marked executable. */ - if (flags & (CONF_FILES_FILTER_MASKED|CONF_FILES_REGULAR|CONF_FILES_DIRECTORY|CONF_FILES_EXECUTABLE)) - if (fstatat(dirfd(dir), de->d_name, &st, 0) < 0) { - log_debug_errno(errno, "Failed to stat '%s/%s', ignoring: %m", dirpath, de->d_name); + _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) { + r = fstatat(rfd, resolved_path, &st, AT_SYMLINK_NOFOLLOW); + if (r < 0) { + log_debug_errno(r, "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 r; + return log_oom_debug(); - log_debug("File '%s/%s' is a mask (symlink to /dev/null).", dirpath, de->d_name); + log_debug("File '%s/%s' is a mask (symlink to /dev/null).", root, skip_leading_slash(p)); continue; } @@ -77,46 +133,42 @@ static int files_add( /* Mark this one as masked */ r = set_put_strdup(masked, de->d_name); if (r < 0) - return r; + return log_oom_debug(); - log_debug("File '%s/%s' is a mask (an empty file).", dirpath, de->d_name); + log_debug("File '%s/%s' is a mask (an empty file).", root, skip_leading_slash(p)); continue; } - /* Does this node have the right type? */ - if (flags & (CONF_FILES_REGULAR|CONF_FILES_DIRECTORY)) - if (!((flags & CONF_FILES_DIRECTORY) && S_ISDIR(st.st_mode)) && - !((flags & CONF_FILES_REGULAR) && S_ISREG(st.st_mode))) { - log_debug("Ignoring '%s/%s', as it does not have the right type.", dirpath, de->d_name); - continue; - } - - /* Does this node have the executable bit set? */ - if (flags & CONF_FILES_EXECUTABLE) - /* 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 ((st.st_mode & 0111) == 0) { /* not executable */ - log_debug("Ignoring '%s/%s', as it is not marked executable.", dirpath, de->d_name); - continue; - } - - n = strdup(de->d_name); - if (!n) - return -ENOMEM; - - if ((flags & CONF_FILES_BASENAME)) - r = hashmap_ensure_put(files, &string_hash_ops_free, n, n); - else { - p = path_join(dirpath, de->d_name); - if (!p) - return -ENOMEM; - - r = hashmap_ensure_put(files, &string_hash_ops_free_free, n, p); + /* 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(); } - if (r < 0) - return r; assert(r > 0); TAKE_PTR(n); @@ -126,66 +178,51 @@ static int files_add( return 0; } -static int copy_and_sort_files_from_hashmap(Hashmap *fh, char ***ret) { +static int copy_and_sort_files_from_hashmap(Hashmap *fh, const char *root, ConfFilesFlags flags, char ***ret) { _cleanup_free_ char **sv = NULL; - char **files; + _cleanup_strv_free_ char **files = NULL; + size_t n = 0; int r; assert(ret); r = hashmap_dump_sorted(fh, (void***) &sv, /* ret_n = */ NULL); if (r < 0) - return r; + return log_oom_debug(); /* The entries in the array given by hashmap_dump_sorted() are still owned by the hashmap. */ - files = strv_copy(sv); - if (!files) - return -ENOMEM; + STRV_FOREACH(s, sv) { + _cleanup_free_ char *p = NULL; - *ret = files; + 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) { + p = path_join(root, skip_leading_slash(*s)); + if (!p) + return log_oom_debug(); + } + + 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; } -int conf_files_list_strv( - char ***ret, - const char *suffix, - const char *root, - ConfFilesFlags flags, - const char * const *dirs) { - - _cleanup_hashmap_free_ Hashmap *fh = NULL; - _cleanup_set_free_ Set *masked = NULL; - int r; - - assert(ret); - - STRV_FOREACH(p, dirs) { - _cleanup_closedir_ DIR *dir = NULL; - _cleanup_free_ char *path = NULL; - - r = chase_and_opendir(*p, root, CHASE_PREFIX_ROOT, &path, &dir); - if (r < 0) { - if (r != -ENOENT) - log_debug_errno(r, "Failed to chase and open directory '%s', ignoring: %m", *p); - continue; - } - - r = files_add(dir, path, &fh, &masked, suffix, flags); - if (r == -ENOMEM) - return r; - if (r < 0) - log_debug_errno(r, "Failed to search for files in '%s', ignoring: %m", path); - } - - return copy_and_sort_files_from_hashmap(fh, ret); -} - -int conf_files_list_strv_at( - char ***ret, +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 * const *dirs, + Hashmap **ret) { _cleanup_hashmap_free_ Hashmap *fh = NULL; _cleanup_set_free_ Set *masked = NULL; @@ -201,18 +238,64 @@ int conf_files_list_strv_at( 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', ignoring: %m", *p); + log_debug_errno(r, "Failed to chase and open directory '%s%s', ignoring: %m", strempty(root), *p); continue; } - r = files_add(dir, path, &fh, &masked, suffix, flags); + r = files_add(dir, path, rfd, root, &fh, &masked, suffix, flags); if (r == -ENOMEM) return r; - if (r < 0) - log_debug_errno(r, "Failed to search for files in '%s', ignoring: %m", path); } - return copy_and_sort_files_from_hashmap(fh, ret); + *ret = TAKE_PTR(fh); + return 0; +} + +int conf_files_list_strv( + char ***ret, + const char *suffix, + const char *root, + ConfFilesFlags flags, + const char * const *dirs) { + + _cleanup_hashmap_free_ Hashmap *fh = NULL; + int r; + + assert(ret); + + _cleanup_close_ int rfd = open(empty_to_root(root), O_CLOEXEC|O_DIRECTORY|O_PATH); + if (rfd < 0) + return log_debug_errno(errno, "Failed to open '%s': %m", root); + + r = conf_files_list_impl(suffix, rfd, root, flags, dirs, &fh); + if (r < 0) + return r; + + return copy_and_sort_files_from_hashmap(fh, empty_to_root(root), flags, ret); +} + +int conf_files_list_strv_at( + char ***ret, + const char *suffix, + int rfd, + ConfFilesFlags flags, + const char * const *dirs) { + + _cleanup_hashmap_free_ Hashmap *fh = NULL; + _cleanup_free_ char *root = NULL; + int r; + + assert(rfd >= 0 || rfd == AT_FDCWD); + assert(ret); + + if (rfd >= 0 && DEBUG_LOGGING) + (void) fd_get_path(rfd, &root); /* for logging */ + + r = conf_files_list_impl(suffix, rfd, root, flags, dirs, &fh); + if (r < 0) + return r; + + return copy_and_sort_files_from_hashmap(fh, /* root = */ NULL, flags, ret); } int conf_files_insert(char ***strv, const char *root, char **dirs, const char *path) { diff --git a/src/test/test-conf-files.c b/src/test/test-conf-files.c index cd3b2c2d1c..3901831e83 100644 --- a/src/test/test-conf-files.c +++ b/src/test/test-conf-files.c @@ -9,6 +9,7 @@ #include "conf-files.h" #include "fd-util.h" #include "fileio.h" +#include "fs-util.h" #include "path-util.h" #include "rm-rf.h" #include "string-util.h" @@ -17,19 +18,21 @@ #include "tmpfile-util.h" TEST(conf_files_list) { - _cleanup_(rm_rf_physical_and_freep) char *t = NULL; - _cleanup_close_ int tfd = -EBADF; + _cleanup_(rm_rf_physical_and_freep) char *t = NULL, *t2 = NULL; + _cleanup_close_ int tfd = -EBADF, tfd2 = -EBADF; _cleanup_strv_free_ char **result = NULL; - const char *search1, *search2, *search1_a, *search1_b, *search1_c, *search2_aa; + const char *search1, *search2, *search3, *search1_a, *search1_b, *search1_c, *search2_aa, *search2_mm; - tfd = mkdtemp_open("/tmp/test-conf-files-XXXXXX", O_PATH, &t); - assert(tfd >= 0); + ASSERT_OK(tfd = mkdtemp_open("/tmp/test-conf-files-XXXXXX", O_PATH, &t)); + ASSERT_OK(tfd2 = mkdtemp_open("/tmp/test-conf-files-XXXXXX", O_PATH, &t2)); - assert_se(mkdirat(tfd, "dir1", 0755) >= 0); - assert_se(mkdirat(tfd, "dir2", 0755) >= 0); + ASSERT_OK_ERRNO(mkdirat(tfd, "dir1", 0755)); + ASSERT_OK_ERRNO(mkdirat(tfd, "dir2", 0755)); + ASSERT_OK_ERRNO(mkdirat(tfd, "dir3", 0755)); search1 = strjoina(t, "/dir1/"); search2 = strjoina(t, "/dir2/"); + search3 = strjoina(t, "/dir3/"); FOREACH_STRING(p, "a.conf", "b.conf", "c.foo") { _cleanup_free_ char *path = NULL; @@ -38,19 +41,45 @@ TEST(conf_files_list) { assert_se(write_string_file(path, "foobar", WRITE_STRING_FILE_CREATE) >= 0); } - assert_se(symlinkat("/dev/null", tfd, "dir1/m.conf") >= 0); + ASSERT_OK_ERRNO(symlinkat("/dev/null", tfd, "dir1/m.conf")); + ASSERT_OK_ERRNO(symlinkat("../dev/null", tfd, "dir1/mm.conf")); - FOREACH_STRING(p, "a.conf", "aa.conf", "m.conf") { + FOREACH_STRING(p, "a.conf", "aa.conf", "m.conf", "mm.conf") { _cleanup_free_ char *path = NULL; assert_se(path = path_join(search2, p)); assert_se(write_string_file(path, "hogehoge", WRITE_STRING_FILE_CREATE) >= 0); } + ASSERT_OK(touch(strjoina(t2, "/absolute-empty.real"))); + ASSERT_OK(symlinkat_atomic_full(strjoina(t2, "/absolute-empty.real"), AT_FDCWD, strjoina(search3, "absolute-empty.conf"), /* flags = */ 0)); + + ASSERT_OK(write_string_file_at(tfd2, "absolute-non-empty.real", "absolute-non-empty", WRITE_STRING_FILE_CREATE)); + ASSERT_OK(symlinkat_atomic_full(strjoina(t2, "/absolute-non-empty.real"), AT_FDCWD, strjoina(search3, "absolute-non-empty.conf"), /* flags = */ 0)); + + ASSERT_OK(touch(strjoina(t2, "/relative-empty.real"))); + ASSERT_OK(symlinkat_atomic_full(strjoina(t2, "/relative-empty.real"), AT_FDCWD, strjoina(search3, "relative-empty.conf"), SYMLINK_MAKE_RELATIVE)); + + ASSERT_OK(write_string_file_at(tfd2, "relative-non-empty.real", "relative-non-empty", WRITE_STRING_FILE_CREATE)); + ASSERT_OK(symlinkat_atomic_full(strjoina(t2, "/relative-non-empty.real"), AT_FDCWD, strjoina(search3, "relative-non-empty.conf"), SYMLINK_MAKE_RELATIVE)); + + ASSERT_OK(touch(strjoina(t, "/absolute-empty-for-root.real"))); + ASSERT_OK(symlinkat_atomic_full("/absolute-empty-for-root.real", AT_FDCWD, strjoina(search3, "absolute-empty-for-root.conf"), /* flags = */ 0)); + + ASSERT_OK(write_string_file_at(tfd, "absolute-non-empty-for-root.real", "absolute-non-empty", WRITE_STRING_FILE_CREATE)); + ASSERT_OK(symlinkat_atomic_full("/absolute-non-empty-for-root.real", AT_FDCWD, strjoina(search3, "absolute-non-empty-for-root.conf"), /* flags = */ 0)); + + ASSERT_OK(touch(strjoina(t, "/relative-empty-for-root.real"))); + ASSERT_OK(symlinkat_atomic_full("../../../../relative-empty-for-root.real", AT_FDCWD, strjoina(search3, "relative-empty-for-root.conf"), /* flags = */ 0)); + + ASSERT_OK(write_string_file_at(tfd, "relative-non-empty-for-root.real", "relative-non-empty", WRITE_STRING_FILE_CREATE)); + ASSERT_OK(symlinkat_atomic_full("../../../../relative-non-empty-for-root.real", AT_FDCWD, strjoina(search3, "relative-non-empty-for-root.conf"), /* flags = */ 0)); + search1_a = strjoina(search1, "a.conf"); search1_b = strjoina(search1, "b.conf"); search1_c = strjoina(search1, "c.foo"); search2_aa = strjoina(search2, "aa.conf"); + search2_mm = strjoina(search2, "mm.conf"); /* search dir1 without suffix */ assert_se(conf_files_list(&result, NULL, NULL, CONF_FILES_FILTER_MASKED, search1) >= 0); @@ -105,7 +134,7 @@ TEST(conf_files_list) { /* search two dirs */ assert_se(conf_files_list_strv(&result, ".conf", NULL, CONF_FILES_FILTER_MASKED, STRV_MAKE_CONST(search1, search2)) >= 0); strv_print(result); - assert_se(strv_equal(result, STRV_MAKE(search1_a, search2_aa, search1_b))); + assert_se(strv_equal(result, STRV_MAKE(search1_a, search2_aa, search1_b, search2_mm))); result = strv_free(result); @@ -117,7 +146,7 @@ TEST(conf_files_list) { assert_se(conf_files_list_strv_at(&result, ".conf", AT_FDCWD, CONF_FILES_FILTER_MASKED, STRV_MAKE_CONST(search1, search2)) >= 0); strv_print(result); - assert_se(strv_equal(result, STRV_MAKE(search1_a, search2_aa, search1_b))); + assert_se(strv_equal(result, STRV_MAKE(search1_a, search2_aa, search1_b, search2_mm))); result = strv_free(result); @@ -127,10 +156,123 @@ TEST(conf_files_list) { result = strv_free(result); + /* search dir3 */ + ASSERT_OK(conf_files_list(&result, /* suffix = */ NULL, /* root = */ NULL, CONF_FILES_FILTER_MASKED, search3)); + strv_print(result); + ASSERT_TRUE(strv_equal(result, STRV_MAKE(strjoina(search3, "absolute-non-empty.conf"), + strjoina(search3, "relative-non-empty.conf")))); + result = strv_free(result); + + ASSERT_OK(conf_files_list(&result, /* suffix = */ NULL, /* root = */ NULL, CONF_FILES_FILTER_MASKED_BY_EMPTY, search3)); + strv_print(result); + ASSERT_TRUE(strv_equal(result, STRV_MAKE(strjoina(search3, "absolute-non-empty.conf"), + strjoina(search3, "relative-non-empty.conf")))); + result = strv_free(result); + + ASSERT_OK(conf_files_list(&result, /* suffix = */ NULL, /* root = */ NULL, CONF_FILES_FILTER_MASKED_BY_SYMLINK, search3)); + strv_print(result); + ASSERT_TRUE(strv_equal(result, STRV_MAKE(strjoina(search3, "absolute-empty.conf"), + strjoina(search3, "absolute-non-empty.conf"), + strjoina(search3, "relative-empty.conf"), + strjoina(search3, "relative-non-empty.conf")))); + result = strv_free(result); + + ASSERT_OK(conf_files_list(&result, /* suffix = */ NULL, /* root = */ NULL, CONF_FILES_REGULAR, search3)); + strv_print(result); + ASSERT_TRUE(strv_equal(result, STRV_MAKE(strjoina(search3, "absolute-empty.conf"), + strjoina(search3, "absolute-non-empty.conf"), + strjoina(search3, "relative-empty.conf"), + strjoina(search3, "relative-non-empty.conf")))); + result = strv_free(result); + + ASSERT_OK(conf_files_list(&result, /* suffix = */ NULL, t, CONF_FILES_FILTER_MASKED, "/dir3/")); + strv_print(result); + ASSERT_TRUE(strv_equal(result, STRV_MAKE(strjoina(search3, "absolute-non-empty-for-root.conf"), + strjoina(search3, "relative-non-empty-for-root.conf")))); + result = strv_free(result); + + ASSERT_OK(conf_files_list(&result, /* suffix = */ NULL, t, CONF_FILES_FILTER_MASKED_BY_EMPTY, "/dir3/")); + strv_print(result); + ASSERT_TRUE(strv_equal(result, STRV_MAKE(strjoina(search3, "absolute-non-empty-for-root.conf"), + strjoina(search3, "relative-non-empty-for-root.conf")))); + result = strv_free(result); + + ASSERT_OK(conf_files_list(&result, /* suffix = */ NULL, t, CONF_FILES_FILTER_MASKED_BY_SYMLINK, "/dir3/")); + strv_print(result); + ASSERT_TRUE(strv_equal(result, STRV_MAKE(strjoina(search3, "absolute-empty-for-root.conf"), + strjoina(search3, "absolute-non-empty-for-root.conf"), + strjoina(search3, "relative-empty-for-root.conf"), + strjoina(search3, "relative-non-empty-for-root.conf")))); + result = strv_free(result); + + ASSERT_OK(conf_files_list(&result, /* suffix = */ NULL, t, CONF_FILES_REGULAR, "/dir3/")); + strv_print(result); + ASSERT_TRUE(strv_equal(result, STRV_MAKE(strjoina(search3, "absolute-empty-for-root.conf"), + strjoina(search3, "absolute-non-empty-for-root.conf"), + strjoina(search3, "relative-empty-for-root.conf"), + strjoina(search3, "relative-non-empty-for-root.conf")))); + result = strv_free(result); + + ASSERT_OK(conf_files_list_at(&result, /* suffix = */ NULL, AT_FDCWD, CONF_FILES_FILTER_MASKED, search3)); + strv_print(result); + ASSERT_TRUE(strv_equal(result, STRV_MAKE(strjoina(search3, "absolute-non-empty.conf"), + strjoina(search3, "relative-non-empty.conf")))); + result = strv_free(result); + + ASSERT_OK(conf_files_list_at(&result, /* suffix = */ NULL, AT_FDCWD, CONF_FILES_FILTER_MASKED_BY_EMPTY, search3)); + strv_print(result); + ASSERT_TRUE(strv_equal(result, STRV_MAKE(strjoina(search3, "absolute-non-empty.conf"), + strjoina(search3, "relative-non-empty.conf")))); + result = strv_free(result); + + ASSERT_OK(conf_files_list_at(&result, /* suffix = */ NULL, AT_FDCWD, CONF_FILES_FILTER_MASKED_BY_SYMLINK, search3)); + strv_print(result); + ASSERT_TRUE(strv_equal(result, STRV_MAKE(strjoina(search3, "absolute-empty.conf"), + strjoina(search3, "absolute-non-empty.conf"), + strjoina(search3, "relative-empty.conf"), + strjoina(search3, "relative-non-empty.conf")))); + result = strv_free(result); + + ASSERT_OK(conf_files_list_at(&result, /* suffix = */ NULL, AT_FDCWD, CONF_FILES_REGULAR, search3)); + strv_print(result); + ASSERT_TRUE(strv_equal(result, STRV_MAKE(strjoina(search3, "absolute-empty.conf"), + strjoina(search3, "absolute-non-empty.conf"), + strjoina(search3, "relative-empty.conf"), + strjoina(search3, "relative-non-empty.conf")))); + result = strv_free(result); + + ASSERT_OK(conf_files_list_at(&result, /* suffix = */ NULL, tfd, CONF_FILES_FILTER_MASKED, "/dir3/")); + strv_print(result); + ASSERT_TRUE(strv_equal(result, STRV_MAKE("dir3/absolute-non-empty-for-root.conf", + "dir3/relative-non-empty-for-root.conf"))); + result = strv_free(result); + + ASSERT_OK(conf_files_list_at(&result, /* suffix = */ NULL, tfd, CONF_FILES_FILTER_MASKED_BY_EMPTY, "/dir3/")); + strv_print(result); + ASSERT_TRUE(strv_equal(result, STRV_MAKE("dir3/absolute-non-empty-for-root.conf", + "dir3/relative-non-empty-for-root.conf"))); + result = strv_free(result); + + ASSERT_OK(conf_files_list_at(&result, /* suffix = */ NULL, tfd, CONF_FILES_FILTER_MASKED_BY_SYMLINK, "/dir3/")); + strv_print(result); + ASSERT_TRUE(strv_equal(result, STRV_MAKE("dir3/absolute-empty-for-root.conf", + "dir3/absolute-non-empty-for-root.conf", + "dir3/relative-empty-for-root.conf", + "dir3/relative-non-empty-for-root.conf"))); + result = strv_free(result); + + ASSERT_OK(conf_files_list_at(&result, /* suffix = */ NULL, tfd, CONF_FILES_REGULAR, "/dir3/")); + strv_print(result); + ASSERT_TRUE(strv_equal(result, STRV_MAKE("dir3/absolute-empty-for-root.conf", + "dir3/absolute-non-empty-for-root.conf", + "dir3/relative-empty-for-root.conf", + "dir3/relative-non-empty-for-root.conf"))); + result = strv_free(result); + /* filename only */ assert_se(conf_files_list_strv(&result, ".conf", NULL, CONF_FILES_FILTER_MASKED | CONF_FILES_BASENAME, STRV_MAKE_CONST(search1, search2)) >= 0); strv_print(result); - assert_se(strv_equal(result, STRV_MAKE("a.conf", "aa.conf", "b.conf"))); + assert_se(strv_equal(result, STRV_MAKE("a.conf", "aa.conf", "b.conf", "mm.conf"))); result = strv_free(result); @@ -142,7 +284,7 @@ TEST(conf_files_list) { assert_se(conf_files_list_strv_at(&result, ".conf", AT_FDCWD, CONF_FILES_FILTER_MASKED | CONF_FILES_BASENAME, STRV_MAKE_CONST(search1, search2)) >= 0); strv_print(result); - assert_se(strv_equal(result, STRV_MAKE("a.conf", "aa.conf", "b.conf"))); + assert_se(strv_equal(result, STRV_MAKE("a.conf", "aa.conf", "b.conf", "mm.conf"))); result = strv_free(result);