From: Viacheslav Dubeyko Date: Tue, 19 May 2026 22:28:12 +0000 (-0700) Subject: hfs: rework hfsplus_readdir() logic X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7fde7e806657fbe0d33f489521b488eed94f9b39;p=thirdparty%2Flinux.git hfs: rework hfsplus_readdir() logic The xfstests' test-case generic/637 fails with error: FSTYP -- hfs PLATFORM -- Linux/x86_64 kvm-xfstests 6.15.0-rc4-xfstests-g00b827f0cffa #1 SMP PREEMPT_DYNAMIC Fri May 25 MKFS_OPTIONS -- /dev/vdc MOUNT_OPTIONS -- /dev/vdc /vdc QA output created by 637 entries 7 and 8 have duplicate d_off 8 Found unlinked files in open dir (see xfstests-dev/results//generic/637.full for details) Likewise HFS+, currently, HFS has very complicated and fragile logic of rd->file->f_pos correction in hfs_delete_cat(). This patch removes this logic and it stores the current pos into hfs_readdir_data. Finally, if rd->pos == ctx->pos then hfs_readdir() tries to find the position in b-tree's node by means of hfs_cat_key. This position is used to re-start the folder's content traversal. sudo ./check generic/637 FSTYP -- hfs PLATFORM -- Linux/x86_64 hfsplus-testing-0001 7.1.0-rc1+ #55 SMP PREEMPT_DYNAMIC Tue May 19 15:18:02 PDT 2026 MKFS_OPTIONS -- /dev/loop51 MOUNT_OPTIONS -- /dev/loop51 /mnt/scratch generic/637 32s ... 31s Ran: generic/637 Passed all 1 tests Closes: https://github.com/hfs-linux-kernel/hfs-linux-kernel/issues/65 cc: John Paul Adrian Glaubitz cc: Yangtao Li cc: linux-fsdevel@vger.kernel.org Signed-off-by: Viacheslav Dubeyko Link: https://lore.kernel.org/r/20260519222811.1311071-2-slava@dubeyko.com Signed-off-by: Viacheslav Dubeyko --- diff --git a/fs/hfs/catalog.c b/fs/hfs/catalog.c index 7f5339ee57c15..1bfa36d71e247 100644 --- a/fs/hfs/catalog.c +++ b/fs/hfs/catalog.c @@ -340,7 +340,6 @@ int hfs_cat_delete(u32 cnid, struct inode *dir, const struct qstr *str) { struct super_block *sb; struct hfs_find_data fd; - struct hfs_readdir_data *rd; int res, type; hfs_dbg("name %s, cnid %u\n", str ? str->name : NULL, cnid); @@ -366,14 +365,6 @@ int hfs_cat_delete(u32 cnid, struct inode *dir, const struct qstr *str) } } - /* we only need to take spinlock for exclusion with ->release() */ - spin_lock(&HFS_I(dir)->open_dir_lock); - list_for_each_entry(rd, &HFS_I(dir)->open_dir_list, list) { - if (fd.tree->keycmp(fd.search_key, (void *)&rd->key) < 0) - rd->file->f_pos--; - } - spin_unlock(&HFS_I(dir)->open_dir_lock); - res = hfs_brec_remove(&fd); if (res) goto out; diff --git a/fs/hfs/dir.c b/fs/hfs/dir.c index 702c16ea954bf..509c77a99625a 100644 --- a/fs/hfs/dir.c +++ b/fs/hfs/dir.c @@ -97,7 +97,15 @@ static int hfs_readdir(struct file *file, struct dir_context *ctx) } if (ctx->pos >= inode->i_size) goto out; - err = hfs_brec_goto(&fd, ctx->pos - 1); + rd = file->private_data; + if (rd && rd->pos == ctx->pos) { + memcpy(fd.search_key, &rd->key, sizeof(struct hfs_cat_key)); + err = hfs_brec_find(&fd); + if (err == -ENOENT) + err = hfs_brec_goto(&fd, 1); + } else { + err = hfs_brec_goto(&fd, ctx->pos - 1); + } if (err) goto out; @@ -146,7 +154,6 @@ static int hfs_readdir(struct file *file, struct dir_context *ctx) if (err) goto out; } - rd = file->private_data; if (!rd) { rd = kmalloc_obj(struct hfs_readdir_data); if (!rd) { @@ -154,15 +161,8 @@ static int hfs_readdir(struct file *file, struct dir_context *ctx) goto out; } file->private_data = rd; - rd->file = file; - spin_lock(&HFS_I(inode)->open_dir_lock); - list_add(&rd->list, &HFS_I(inode)->open_dir_list); - spin_unlock(&HFS_I(inode)->open_dir_lock); } - /* - * Can be done after the list insertion; exclusion with - * hfs_delete_cat() is provided by directory lock. - */ + rd->pos = ctx->pos; memcpy(&rd->key, &fd.key->cat, sizeof(struct hfs_cat_key)); out: hfs_find_exit(&fd); @@ -171,13 +171,7 @@ out: static int hfs_dir_release(struct inode *inode, struct file *file) { - struct hfs_readdir_data *rd = file->private_data; - if (rd) { - spin_lock(&HFS_I(inode)->open_dir_lock); - list_del(&rd->list); - spin_unlock(&HFS_I(inode)->open_dir_lock); - kfree(rd); - } + kfree(file->private_data); return 0; } diff --git a/fs/hfs/hfs.h b/fs/hfs/hfs.h index 3f2293ff6fdd2..2e958c46bc0af 100644 --- a/fs/hfs/hfs.h +++ b/fs/hfs/hfs.h @@ -14,8 +14,7 @@ /*======== Data structures kept in memory ========*/ struct hfs_readdir_data { - struct list_head list; - struct file *file; + loff_t pos; struct hfs_cat_key key; }; diff --git a/fs/hfs/hfs_fs.h b/fs/hfs/hfs_fs.h index ac0e83f77a0f1..97e8d1f96d6d6 100644 --- a/fs/hfs/hfs_fs.h +++ b/fs/hfs/hfs_fs.h @@ -36,8 +36,6 @@ struct hfs_inode_info { struct hfs_cat_key cat_key; - struct list_head open_dir_list; - spinlock_t open_dir_lock; struct inode *rsrc_inode; struct mutex extents_lock; diff --git a/fs/hfs/inode.c b/fs/hfs/inode.c index b2329de151eca..ab52ad2d2ad8a 100644 --- a/fs/hfs/inode.c +++ b/fs/hfs/inode.c @@ -195,8 +195,6 @@ struct inode *hfs_new_inode(struct inode *dir, const struct qstr *name, umode_t err = -ERANGE; mutex_init(&HFS_I(inode)->extents_lock); - INIT_LIST_HEAD(&HFS_I(inode)->open_dir_list); - spin_lock_init(&HFS_I(inode)->open_dir_lock); hfs_cat_build_key(sb, (btree_key *)&HFS_I(inode)->cat_key, dir->i_ino, name); next_id = atomic64_inc_return(&HFS_SB(sb)->next_id); if (next_id > U32_MAX) { @@ -353,8 +351,6 @@ static int hfs_read_inode(struct inode *inode, void *data) HFS_I(inode)->flags = 0; HFS_I(inode)->rsrc_inode = NULL; mutex_init(&HFS_I(inode)->extents_lock); - INIT_LIST_HEAD(&HFS_I(inode)->open_dir_list); - spin_lock_init(&HFS_I(inode)->open_dir_lock); /* Initialize the inode */ inode->i_uid = hsb->s_uid;