summaryrefslogtreecommitdiffstats
path: root/src/test/test-recurse-dir.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/test-recurse-dir.c')
-rw-r--r--src/test/test-recurse-dir.c177
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;
+}