]> git-server-git.apps.pok.os.sepia.ceph.com Git - xfstests-dev.git/commitdiff
generic: test reading a large directory while renaming its files
authorFilipe Manana <fdmanana@suse.com>
Tue, 5 Dec 2023 16:39:20 +0000 (16:39 +0000)
committerZorro Lang <zlang@kernel.org>
Sun, 10 Dec 2023 13:38:00 +0000 (21:38 +0800)
Test that on a fairly large directory if we keep renaming files while
holding the directory open and doing readdir(3) calls, we don't end up
in an infinite loop.

This exercise a bug that existed in btrfs and was fixed in kernel 6.5
by commit 9b378f6ad48c ("btrfs: fix infinite directory reads").

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: Zorro Lang <zlang@redhat.com>
Signed-off-by: Zorro Lang <zlang@kernel.org>
.gitignore
src/Makefile
src/readdir-while-renames.c [new file with mode: 0644]
tests/generic/736 [new file with mode: 0755]
tests/generic/736.out [new file with mode: 0644]

index 4c32ac42205968e2a760fc9222fd6553d6d96893..7508b6e8b282efec61d2f42f0f22a5834c782b08 100644 (file)
@@ -121,6 +121,7 @@ tags
 /src/punch-alternating
 /src/pwrite_mmap_blocked
 /src/randholes
+/src/readdir-while-renames
 /src/rename
 /src/renameat2
 /src/resvtest
index 8160a0e8ecec769e00e9eabe86a4b77aa909851e..d79015cef41eada193a68e180ce2827fbf9ca31e 100644 (file)
@@ -19,7 +19,8 @@ TARGETS = dirstress fill fill2 getpagesize holes lstat64 \
        t_ofd_locks t_mmap_collision mmap-write-concurrent \
        t_get_file_time t_create_short_dirs t_create_long_dirs t_enospc \
        t_mmap_writev_overlap checkpoint_journal mmap-rw-fault allocstale \
-       t_mmap_cow_memory_failure fake-dump-rootino dio-buf-fault rewinddir-test
+       t_mmap_cow_memory_failure fake-dump-rootino dio-buf-fault rewinddir-test \
+       readdir-while-renames
 
 LINUX_TARGETS = xfsctl bstat t_mtab getdevicesize preallo_rw_pattern_reader \
        preallo_rw_pattern_writer ftrunc trunc fs_perms testx looptest \
diff --git a/src/readdir-while-renames.c b/src/readdir-while-renames.c
new file mode 100644 (file)
index 0000000..afeefb0
--- /dev/null
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2023 SUSE Linux Products GmbH.  All Rights Reserved.
+ */
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+/* Number of files we add to the test directory. */
+#define NUM_FILES 5000
+
+int main(int argc, char *argv[])
+{
+       struct dirent *entry;
+       DIR *dir = NULL;
+       char *dir_path = NULL;
+       int dentry_count = 0;
+       int ret = 0;
+       int i;
+
+       if (argc != 2) {
+               fprintf(stderr, "Usage:  %s <directory>\n", argv[0]);
+               ret = 1;
+               goto out;
+       }
+
+       dir_path = malloc(strlen(argv[1]) + strlen("/testdir") + 1);
+       if (!dir_path) {
+               fprintf(stderr, "malloc failure\n");
+               ret = ENOMEM;
+               goto out;
+       }
+       i = strlen(argv[1]);
+       memcpy(dir_path, argv[1], i);
+       memcpy(dir_path + i, "/testdir", strlen("/testdir"));
+       dir_path[i + strlen("/testdir")] = '\0';
+
+       ret = mkdir(dir_path, 0700);
+       if (ret == -1) {
+               fprintf(stderr, "Failed to create test directory: %d\n", errno);
+               ret = errno;
+               goto out;
+       }
+
+       ret = chdir(dir_path);
+       if (ret == -1) {
+               fprintf(stderr, "Failed to chdir to the test directory: %d\n", errno);
+               ret = errno;
+               goto out;
+       }
+
+       /* Now create all files inside the directory. */
+       for (i = 1; i <= NUM_FILES; i++) {
+               char file_name[32];
+               FILE *f;
+
+               sprintf(file_name, "%s/%d", dir_path, i);
+               f = fopen(file_name, "w");
+               if (f == NULL) {
+                       fprintf(stderr, "Failed to create file number %d: %d\n",
+                               i, errno);
+                       ret = errno;
+                       goto out;
+               }
+               fclose(f);
+       }
+
+       dir = opendir(dir_path);
+       if (dir == NULL) {
+               fprintf(stderr, "Failed to open directory: %d\n", errno);
+               ret = errno;
+               goto out;
+       }
+
+       /*
+        * readdir(3) returns NULL when it reaches the end of the directory or
+        * when an error happens, so reset errno to 0 to distinguish between
+        * both cases later.
+        */
+       errno = 0;
+       while ((entry = readdir(dir)) != NULL) {
+               dentry_count++;
+               /*
+                * The actual number of dentries returned varies per filesystem
+                * implementation. On a 6.7-rc4 kernel, on x86_64 with default
+                * mkfs options, xfs returns 5031 dentries while ext4, f2fs and
+                * btrfs return 5002 (the 5000 files plus "." and ".."). These
+                * variations are fine and valid according to POSIX, as some of
+                * the renames may be visible or not while calling readdir(3).
+                * We only want to check we don't enter into an infinite loop,
+                * so let the maximum number of dentries be 3 * NUM_FILES, which
+                * is very reasonable.
+                */
+               if (dentry_count > 3 * NUM_FILES) {
+                       fprintf(stderr,
+                               "Found too many directory entries (%d)\n",
+                               dentry_count);
+                       ret = 1;
+                       goto out;
+               }
+               /* Can't rename "." and "..", skip them. */
+               if (strcmp(entry->d_name, ".") == 0 ||
+                   strcmp(entry->d_name, "..") == 0)
+                       continue;
+               ret = rename(entry->d_name, "TEMPFILE");
+               if (ret == -1) {
+                       fprintf(stderr,
+                               "Failed to rename '%s' to TEMPFILE: %d\n",
+                               entry->d_name, errno);
+                       ret = errno;
+                       goto out;
+               }
+               ret = rename("TEMPFILE", entry->d_name);
+               if (ret == -1) {
+                       fprintf(stderr,
+                               "Failed to rename TEMPFILE to '%s': %d\n",
+                               entry->d_name, errno);
+                       ret = errno;
+                       goto out;
+               }
+       }
+
+       if (errno) {
+               fprintf(stderr, "Failed to read directory: %d\n", errno);
+               ret = errno;
+               goto out;
+       }
+
+       /* It should return at least NUM_FILES entries +2 (for "." and ".."). */
+       if (dentry_count < NUM_FILES + 2) {
+               fprintf(stderr,
+                       "Found less directory entries than expected (%d but expected %d)\n",
+                       dentry_count, NUM_FILES + 2);
+               ret = 2;
+       }
+out:
+       free(dir_path);
+       if (dir != NULL)
+               closedir(dir);
+
+       return ret;
+}
diff --git a/tests/generic/736 b/tests/generic/736
new file mode 100755 (executable)
index 0000000..d2432a8
--- /dev/null
@@ -0,0 +1,37 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2023 SUSE Linux Products GmbH. All Rights Reserved.
+#
+# FS QA Test No. 736
+#
+# Test that on a fairly large directory if we keep renaming files while holding
+# the directory open and doing readdir(3) calls, we don't end up in an infinite
+# loop.
+#
+. ./common/preamble
+_begin_fstest auto quick dir rename
+
+_cleanup()
+{
+       cd /
+       rm -fr $tmp.*
+       rm -fr $target_dir
+}
+
+_supported_fs generic
+_require_test
+_require_test_program readdir-while-renames
+
+[ $FSTYP = "btrfs" ] && _fixed_by_kernel_commit 9b378f6ad48c \
+       "btrfs: fix infinite directory reads"
+
+target_dir="$TEST_DIR/test-$seq"
+rm -fr $target_dir
+mkdir $target_dir
+
+$here/src/readdir-while-renames $target_dir
+
+# success, all done
+echo "Silence is golden"
+status=0
+exit
diff --git a/tests/generic/736.out b/tests/generic/736.out
new file mode 100644 (file)
index 0000000..8588bd4
--- /dev/null
@@ -0,0 +1,2 @@
+QA output created by 736
+Silence is golden