diff options
Diffstat (limited to 'src/test/test-recurse-dir.c')
-rw-r--r-- | src/test/test-recurse-dir.c | 177 |
1 files changed, 177 insertions, 0 deletions
diff --git a/src/test/test-recurse-dir.c b/src/test/test-recurse-dir.c new file mode 100644 index 0000000..8684d06 --- /dev/null +++ b/src/test/test-recurse-dir.c @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <ftw.h> + +#include "fd-util.h" +#include "log.h" +#include "missing_magic.h" +#include "recurse-dir.h" +#include "strv.h" +#include "tests.h" + +static char **list_nftw = NULL; + +static int nftw_cb( + const char *fpath, + const struct stat *sb, + int typeflag, + struct FTW *ftwbuf) { + + if (ftwbuf->level == 0) /* skip top-level */ + return FTW_CONTINUE; + + switch (typeflag) { + + case FTW_F: + log_debug("ftw found %s", fpath); + assert_se(strv_extend(&list_nftw, fpath) >= 0); + break; + + case FTW_SL: + log_debug("ftw found symlink %s, ignoring.", fpath); + break; + + case FTW_D: + log_debug("ftw entering %s", fpath); + assert_se(strv_extendf(&list_nftw, "%s/", fpath) >= 0); + break; + + case FTW_DNR: + log_debug("ftw open directory failed %s", fpath); + break; + + case FTW_NS: + log_debug("ftw stat inode failed %s", fpath); + break; + + case FTW_DP: + case FTW_SLN: + default: + assert_not_reached(); + } + + return FTW_CONTINUE; +} + +static int recurse_dir_callback( + RecurseDirEvent event, + const char *path, + int dir_fd, + int inode_fd, + const struct dirent *de, + const struct statx *sx, + void *userdata) { + + char ***l = userdata; + + assert_se(path); + assert_se(de); + + switch (event) { + + case RECURSE_DIR_ENTRY: + assert_se(!IN_SET(de->d_type, DT_UNKNOWN, DT_DIR)); + + log_debug("found %s%s", path, + de->d_type == DT_LNK ? ", ignoring." : ""); + + if (de->d_type != DT_LNK) + assert_se(strv_extend(l, path) >= 0); + break; + + case RECURSE_DIR_ENTER: + assert_se(de->d_type == DT_DIR); + + log_debug("entering %s", path); + assert_se(strv_extendf(l, "%s/", path) >= 0); + break; + + case RECURSE_DIR_LEAVE: + log_debug("leaving %s", path); + break; + + case RECURSE_DIR_SKIP_MOUNT: + log_debug("skipping mount %s", path); + break; + + case RECURSE_DIR_SKIP_DEPTH: + log_debug("skipping depth %s", path); + break; + + case RECURSE_DIR_SKIP_OPEN_DIR_ERROR_BASE...RECURSE_DIR_SKIP_OPEN_DIR_ERROR_MAX: + log_debug_errno(event - RECURSE_DIR_SKIP_OPEN_DIR_ERROR_BASE, "failed to open dir %s: %m", path); + break; + + case RECURSE_DIR_SKIP_OPEN_INODE_ERROR_BASE...RECURSE_DIR_SKIP_OPEN_INODE_ERROR_MAX: + log_debug_errno(event - RECURSE_DIR_SKIP_OPEN_INODE_ERROR_BASE, "failed to open inode %s: %m", path); + break; + + case RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE...RECURSE_DIR_SKIP_STAT_INODE_ERROR_MAX: + log_debug_errno(event - RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE, "failed to stat inode %s: %m", path); + break; + + default: + assert_not_reached(); + } + + return RECURSE_DIR_CONTINUE; +} + +int main(int argc, char *argv[]) { + _cleanup_strv_free_ char **list_recurse_dir = NULL; + const char *p; + usec_t t1, t2, t3, t4; + _cleanup_close_ int fd = -EBADF; + + log_show_color(true); + test_setup_logging(LOG_INFO); + + if (argc > 1) + p = argv[1]; + else + p = "/usr/share/man"; /* something hopefully reasonably stable while we run (and limited in size) */ + + fd = open(p, O_DIRECTORY|O_CLOEXEC); + if (fd < 0 && errno == ENOENT) + return log_tests_skipped_errno(errno, "Couldn't open directory %s", p); + assert_se(fd >= 0); + + /* If the test directory is on an overlayfs then files and their directory may return different + * st_dev in stat results, which confuses nftw into thinking they're on different filesystems and + * won't return the result when the FTW_MOUNT flag is set. */ + if (fd_is_fs_type(fd, OVERLAYFS_SUPER_MAGIC)) + return log_tests_skipped("nftw mountpoint detection produces false-positives on overlayfs"); + + /* Enumerate the specified dirs in full, once via nftw(), and once via recurse_dir(), and ensure the + * results are identical. nftw() sometimes skips symlinks (see + * https://github.com/systemd/systemd/issues/29603), so ignore them to avoid bogus errors. */ + + t1 = now(CLOCK_MONOTONIC); + assert_se(recurse_dir(fd, p, 0, UINT_MAX, RECURSE_DIR_SORT|RECURSE_DIR_ENSURE_TYPE|RECURSE_DIR_SAME_MOUNT, recurse_dir_callback, &list_recurse_dir) >= 0); + t2 = now(CLOCK_MONOTONIC); + + t3 = now(CLOCK_MONOTONIC); + assert_se(nftw(p, nftw_cb, 64, FTW_PHYS|FTW_MOUNT) >= 0); + t4 = now(CLOCK_MONOTONIC); + + log_info("recurse_dir(): %s – nftw(): %s", FORMAT_TIMESPAN(t2 - t1, 1), FORMAT_TIMESPAN(t4 - t3, 1)); + + strv_sort(list_recurse_dir); + strv_sort(list_nftw); + + for (size_t i = 0;; i++) { + const char *a = list_nftw ? list_nftw[i] : NULL, + *b = list_recurse_dir ? list_recurse_dir[i] : NULL; + + if (!streq_ptr(a, b)) { + log_error("entry %zu different: %s vs %s", i, strna(a), strna(b)); + assert_not_reached(); + } + + if (!a) + break; + } + + list_nftw = strv_free(list_nftw); + return 0; +} |