[PATCH] fs/ntfs3: do not restart readdir() when entries are added
From: Hengyu Liang
Date: Thu Nov 12 2026 - 23:58:52 EST
Commit dffc7f2f177b ("fs/ntfs3: allow readdir() to finish after
directory mutations without rewinddir()") made ntfs_readdir() start
again from the first entry when it reaches the end of a directory whose
version has changed. This helps programs that remove the entries while
they read them.
However, indx_insert_entry() increments the version as well. As of now,
a program that reads a directory while another program adds files to it
gets the same names again and again, and reaches the end only after a
pass during which no file was added.
The issue can be reproduced on an NTFS volume mounted on /mnt:
mkdir /mnt/d
for i in $(seq 3000); do echo > /mnt/d/a$i; done
for i in $(seq 10000); do echo > /mnt/d/b$i; done &
sleep 1
ls /mnt/d | wc -l
Before commit dffc7f2f177b ("fs/ntfs3: allow readdir() to finish after
directory mutations without rewinddir()"), the result is:
5489
After that commit, the result is:
1837538
This patch will increment the version only in indx_delete_entry(), so
that added entries do not restart readdir().
Fixes: dffc7f2f177b ("fs/ntfs3: allow readdir() to finish after directory mutations without rewinddir()")
Cc: stable@xxxxxxxxxxxxxxx # v7.0
Signed-off-by: Hengyu Liang <hengyul@xxxxxxxxxx>
---
The readdir() + unlink() loop of that commit keeps working: it still
empties a directory of 20000 files in one pass (298 files are left on
v6.19).
Application impact (Ubuntu 26.04 user space): "tar -cf" (GNU tar 1.35)
of a folder with 3000 files while "cp -r" adds 2000 files to it. On
v6.19, tar ends after 1.2 s and the archive has each file once. On
v7.0, tar runs for 29 s and the archive has 200434 members, each file
up to 48 times.
Tested on ntfs3 master: without this patch the archive has 261512
members; with it each file is there once.
fs/ntfs3/dir.c | 6 +++---
fs/ntfs3/index.c | 1 -
fs/ntfs3/ntfs_fs.h | 2 +-
3 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/fs/ntfs3/dir.c b/fs/ntfs3/dir.c
index 7ee1a5d4189e..defc84693e39 100644
--- a/fs/ntfs3/dir.c
+++ b/fs/ntfs3/dir.c
@@ -476,15 +476,15 @@ static int ntfs_readdir(struct file *file, struct dir_context *ctx)
if (!pos) {
/*
- * ni->dir.version increments each directory change.
- * Save the initial value of ni->dir.version.
+ * ni->dir.version increments when a delete reshapes the
+ * name tree. Save the initial value of ni->dir.version.
*/
file->private_data = (void *)ni->dir.version;
}
if (pos >= eod) {
if (file->private_data == (void *)ni->dir.version) {
- /* No changes since first readdir. */
+ /* No such delete since first readdir. */
return 0;
}
diff --git a/fs/ntfs3/index.c b/fs/ntfs3/index.c
index c06b36760448..29884bf3d35e 100644
--- a/fs/ntfs3/index.c
+++ b/fs/ntfs3/index.c
@@ -2100,7 +2100,6 @@ int indx_insert_entry(struct ntfs_index *indx, struct ntfs_inode *ni,
fnd->level - 1, fnd, cmp);
}
- indx->version += 1;
out:
fnd_put(fnd_a);
out1:
diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h
index f400cf94d05b..12212fb7a2be 100644
--- a/fs/ntfs3/ntfs_fs.h
+++ b/fs/ntfs3/ntfs_fs.h
@@ -198,7 +198,7 @@ struct ntfs_index {
struct runs_tree alloc_run;
/* read/write access to 'bitmap_run'/'alloc_run' while ntfs_readdir */
struct rw_semaphore run_lock;
- size_t version; /* increment each change */
+ size_t version; /* incremented when a delete reshapes the tree */
u8 index_bits; // log2(root->index_block_size)
u8 idx2vbn_bits; // log2(root->index_block_clst)
base-commit: 0c895b1f6ab37a7c72a437818f307ef6e629051c
--
2.53.0