diff options
author | Filipe Manana <fdmanana@suse.com> | 2023-12-05 16:39:20 +0000 |
---|---|---|
committer | Zorro Lang <zlang@kernel.org> | 2023-12-10 21:38:00 +0800 |
commit | ed058f1c64746b32e2786bf344ae1ff5fd78dfb0 (patch) | |
tree | f54efb3e64cab64f83ffd1f622b42f24705437cb /src | |
parent | b05c808ce4a4686b40c4e0b5029cb889c39f3856 (diff) |
generic: test reading a large directory while renaming its files
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>
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile | 3 | ||||
-rw-r--r-- | src/readdir-while-renames.c | 146 |
2 files changed, 148 insertions, 1 deletions
diff --git a/src/Makefile b/src/Makefile index 8160a0e8..d79015ce 100644 --- a/src/Makefile +++ b/src/Makefile @@ -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 index 00000000..afeefb04 --- /dev/null +++ b/src/readdir-while-renames.c @@ -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; +} |