From: Greg Kroah-Hartman Date: Thu, 30 Jan 2025 08:59:27 +0000 (+0100) Subject: 6.6-stable patches X-Git-Tag: v6.13.1~30 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=f98d552b66498cc7fad7983f4d5226a84724cb81;p=thirdparty%2Fkernel%2Fstable-queue.git 6.6-stable patches added patches: gfs2-truncate-address-space-when-flipping-gfs2_dif_jdata-flag.patch libfs-add-simple_offset_empty.patch libfs-add-simple_offset_rename-api.patch libfs-define-a-minimum-directory-offset.patch libfs-fix-simple_offset_rename_exchange.patch libfs-re-arrange-locking-in-offset_iterate_dir.patch libfs-replace-simple_offset-end-of-directory-detection.patch libfs-return-enospc-when-the-directory-offset-range-is-exhausted.patch libfs-use-d_children-list-to-iterate-simple_offset-directories.patch revert-libfs-add-simple_offset_empty.patch shmem-fix-shmem_rename2.patch smb-client-handle-lack-of-ea-support-in-smb2_query_path_info.patch --- diff --git a/queue-6.6/gfs2-truncate-address-space-when-flipping-gfs2_dif_jdata-flag.patch b/queue-6.6/gfs2-truncate-address-space-when-flipping-gfs2_dif_jdata-flag.patch new file mode 100644 index 0000000000..4e0fcd60ea --- /dev/null +++ b/queue-6.6/gfs2-truncate-address-space-when-flipping-gfs2_dif_jdata-flag.patch @@ -0,0 +1,30 @@ +From 7c9d9223802fbed4dee1ae301661bf346964c9d2 Mon Sep 17 00:00:00 2001 +From: Andreas Gruenbacher +Date: Mon, 13 Jan 2025 19:31:28 +0100 +Subject: gfs2: Truncate address space when flipping GFS2_DIF_JDATA flag + +From: Andreas Gruenbacher + +commit 7c9d9223802fbed4dee1ae301661bf346964c9d2 upstream. + +Truncate an inode's address space when flipping the GFS2_DIF_JDATA flag: +depending on that flag, the pages in the address space will either use +buffer heads or iomap_folio_state structs, and we cannot mix the two. + +Reported-by: Kun Hu , Jiaji Qin +Signed-off-by: Andreas Gruenbacher +Signed-off-by: Greg Kroah-Hartman +--- + fs/gfs2/file.c | 1 + + 1 file changed, 1 insertion(+) + +--- a/fs/gfs2/file.c ++++ b/fs/gfs2/file.c +@@ -251,6 +251,7 @@ static int do_gfs2_set_flags(struct inod + error = filemap_fdatawait(inode->i_mapping); + if (error) + goto out; ++ truncate_inode_pages(inode->i_mapping, 0); + if (new_flags & GFS2_DIF_JDATA) + gfs2_ordered_del_inode(ip); + } diff --git a/queue-6.6/libfs-add-simple_offset_empty.patch b/queue-6.6/libfs-add-simple_offset_empty.patch new file mode 100644 index 0000000000..c77c405375 --- /dev/null +++ b/queue-6.6/libfs-add-simple_offset_empty.patch @@ -0,0 +1,106 @@ +From stable+bounces-110403-greg=kroah.com@vger.kernel.org Fri Jan 24 20:20:42 2025 +From: cel@kernel.org +Date: Fri, 24 Jan 2025 14:19:38 -0500 +Subject: libfs: Add simple_offset_empty() +To: Hugh Dickins , Andrew Morten , Christian Brauner , Al Viro , Greg Kroah-Hartman , Sasha Levin +Cc: , , , yukuai3@huawei.com, yangerkun@huawei.com, Chuck Lever , Jan Kara +Message-ID: <20250124191946.22308-4-cel@kernel.org> + +From: Chuck Lever + +[ Upstream commit ecba88a3b32d733d41e27973e25b2bc580f64281 ] + +For simple filesystems that use directory offset mapping, rely +strictly on the directory offset map to tell when a directory has +no children. + +After this patch is applied, the emptiness test holds only the RCU +read lock when the directory being tested has no children. + +In addition, this adds another layer of confirmation that +simple_offset_add/remove() are working as expected. + +Reviewed-by: Jan Kara +Signed-off-by: Chuck Lever +Link: https://lore.kernel.org/r/170820143463.6328.7872919188371286951.stgit@91.116.238.104.host.secureserver.net +Signed-off-by: Christian Brauner +Stable-dep-of: 5a1a25be995e ("libfs: Add simple_offset_rename() API") +Signed-off-by: Chuck Lever +Signed-off-by: Greg Kroah-Hartman +--- + fs/libfs.c | 32 ++++++++++++++++++++++++++++++++ + include/linux/fs.h | 1 + + mm/shmem.c | 4 ++-- + 3 files changed, 35 insertions(+), 2 deletions(-) + +--- a/fs/libfs.c ++++ b/fs/libfs.c +@@ -313,6 +313,38 @@ void simple_offset_remove(struct offset_ + } + + /** ++ * simple_offset_empty - Check if a dentry can be unlinked ++ * @dentry: dentry to be tested ++ * ++ * Returns 0 if @dentry is a non-empty directory; otherwise returns 1. ++ */ ++int simple_offset_empty(struct dentry *dentry) ++{ ++ struct inode *inode = d_inode(dentry); ++ struct offset_ctx *octx; ++ struct dentry *child; ++ unsigned long index; ++ int ret = 1; ++ ++ if (!inode || !S_ISDIR(inode->i_mode)) ++ return ret; ++ ++ index = DIR_OFFSET_MIN; ++ octx = inode->i_op->get_offset_ctx(inode); ++ xa_for_each(&octx->xa, index, child) { ++ spin_lock(&child->d_lock); ++ if (simple_positive(child)) { ++ spin_unlock(&child->d_lock); ++ ret = 0; ++ break; ++ } ++ spin_unlock(&child->d_lock); ++ } ++ ++ return ret; ++} ++ ++/** + * simple_offset_rename_exchange - exchange rename with directory offsets + * @old_dir: parent of dentry being moved + * @old_dentry: dentry being moved +--- a/include/linux/fs.h ++++ b/include/linux/fs.h +@@ -3197,6 +3197,7 @@ struct offset_ctx { + void simple_offset_init(struct offset_ctx *octx); + int simple_offset_add(struct offset_ctx *octx, struct dentry *dentry); + void simple_offset_remove(struct offset_ctx *octx, struct dentry *dentry); ++int simple_offset_empty(struct dentry *dentry); + int simple_offset_rename_exchange(struct inode *old_dir, + struct dentry *old_dentry, + struct inode *new_dir, +--- a/mm/shmem.c ++++ b/mm/shmem.c +@@ -3368,7 +3368,7 @@ static int shmem_unlink(struct inode *di + + static int shmem_rmdir(struct inode *dir, struct dentry *dentry) + { +- if (!simple_empty(dentry)) ++ if (!simple_offset_empty(dentry)) + return -ENOTEMPTY; + + drop_nlink(d_inode(dentry)); +@@ -3425,7 +3425,7 @@ static int shmem_rename2(struct mnt_idma + return simple_offset_rename_exchange(old_dir, old_dentry, + new_dir, new_dentry); + +- if (!simple_empty(new_dentry)) ++ if (!simple_offset_empty(new_dentry)) + return -ENOTEMPTY; + + if (flags & RENAME_WHITEOUT) { diff --git a/queue-6.6/libfs-add-simple_offset_rename-api.patch b/queue-6.6/libfs-add-simple_offset_rename-api.patch new file mode 100644 index 0000000000..58aa1bc80b --- /dev/null +++ b/queue-6.6/libfs-add-simple_offset_rename-api.patch @@ -0,0 +1,79 @@ +From stable+bounces-110405-greg=kroah.com@vger.kernel.org Fri Jan 24 20:21:49 2025 +From: cel@kernel.org +Date: Fri, 24 Jan 2025 14:19:40 -0500 +Subject: libfs: Add simple_offset_rename() API +To: Hugh Dickins , Andrew Morten , Christian Brauner , Al Viro , Greg Kroah-Hartman , Sasha Levin +Cc: , , , yukuai3@huawei.com, yangerkun@huawei.com, Chuck Lever +Message-ID: <20250124191946.22308-6-cel@kernel.org> + +From: Chuck Lever + +[ Upstream commit 5a1a25be995e1014abd01600479915683e356f5c ] + +I'm about to fix a tmpfs rename bug that requires the use of +internal simple_offset helpers that are not available in mm/shmem.c + +Signed-off-by: Chuck Lever +Link: https://lore.kernel.org/r/20240415152057.4605-3-cel@kernel.org +Signed-off-by: Christian Brauner +Signed-off-by: Chuck Lever +Signed-off-by: Greg Kroah-Hartman +--- + fs/libfs.c | 21 +++++++++++++++++++++ + include/linux/fs.h | 2 ++ + mm/shmem.c | 3 +-- + 3 files changed, 24 insertions(+), 2 deletions(-) + +--- a/fs/libfs.c ++++ b/fs/libfs.c +@@ -357,6 +357,27 @@ int simple_offset_empty(struct dentry *d + } + + /** ++ * simple_offset_rename - handle directory offsets for rename ++ * @old_dir: parent directory of source entry ++ * @old_dentry: dentry of source entry ++ * @new_dir: parent_directory of destination entry ++ * @new_dentry: dentry of destination ++ * ++ * Caller provides appropriate serialization. ++ * ++ * Returns zero on success, a negative errno value on failure. ++ */ ++int simple_offset_rename(struct inode *old_dir, struct dentry *old_dentry, ++ struct inode *new_dir, struct dentry *new_dentry) ++{ ++ struct offset_ctx *old_ctx = old_dir->i_op->get_offset_ctx(old_dir); ++ struct offset_ctx *new_ctx = new_dir->i_op->get_offset_ctx(new_dir); ++ ++ simple_offset_remove(old_ctx, old_dentry); ++ return simple_offset_add(new_ctx, old_dentry); ++} ++ ++/** + * simple_offset_rename_exchange - exchange rename with directory offsets + * @old_dir: parent of dentry being moved + * @old_dentry: dentry being moved +--- a/include/linux/fs.h ++++ b/include/linux/fs.h +@@ -3198,6 +3198,8 @@ void simple_offset_init(struct offset_ct + int simple_offset_add(struct offset_ctx *octx, struct dentry *dentry); + void simple_offset_remove(struct offset_ctx *octx, struct dentry *dentry); + int simple_offset_empty(struct dentry *dentry); ++int simple_offset_rename(struct inode *old_dir, struct dentry *old_dentry, ++ struct inode *new_dir, struct dentry *new_dentry); + int simple_offset_rename_exchange(struct inode *old_dir, + struct dentry *old_dentry, + struct inode *new_dir, +--- a/mm/shmem.c ++++ b/mm/shmem.c +@@ -3434,8 +3434,7 @@ static int shmem_rename2(struct mnt_idma + return error; + } + +- simple_offset_remove(shmem_get_offset_ctx(old_dir), old_dentry); +- error = simple_offset_add(shmem_get_offset_ctx(new_dir), old_dentry); ++ error = simple_offset_rename(old_dir, old_dentry, new_dir, new_dentry); + if (error) + return error; + diff --git a/queue-6.6/libfs-define-a-minimum-directory-offset.patch b/queue-6.6/libfs-define-a-minimum-directory-offset.patch new file mode 100644 index 0000000000..023ccc1d3a --- /dev/null +++ b/queue-6.6/libfs-define-a-minimum-directory-offset.patch @@ -0,0 +1,69 @@ +From stable+bounces-110402-greg=kroah.com@vger.kernel.org Fri Jan 24 20:21:35 2025 +From: cel@kernel.org +Date: Fri, 24 Jan 2025 14:19:37 -0500 +Subject: libfs: Define a minimum directory offset +To: Hugh Dickins , Andrew Morten , Christian Brauner , Al Viro , Greg Kroah-Hartman , Sasha Levin +Cc: , , , yukuai3@huawei.com, yangerkun@huawei.com, Chuck Lever , Jan Kara +Message-ID: <20250124191946.22308-3-cel@kernel.org> + +From: Chuck Lever + +[ Upstream commit 7beea725a8ca412c6190090ce7c3a13b169592a1 ] + +This value is used in several places, so make it a symbolic +constant. + +Reviewed-by: Jan Kara +Signed-off-by: Chuck Lever +Link: https://lore.kernel.org/r/170820142741.6328.12428356024575347885.stgit@91.116.238.104.host.secureserver.net +Signed-off-by: Christian Brauner +Stable-dep-of: ecba88a3b32d ("libfs: Add simple_offset_empty()") +Signed-off-by: Chuck Lever +Signed-off-by: Greg Kroah-Hartman +--- + fs/libfs.c | 13 ++++++++----- + 1 file changed, 8 insertions(+), 5 deletions(-) + +--- a/fs/libfs.c ++++ b/fs/libfs.c +@@ -239,6 +239,11 @@ const struct inode_operations simple_dir + }; + EXPORT_SYMBOL(simple_dir_inode_operations); + ++/* 0 is '.', 1 is '..', so always start with offset 2 or more */ ++enum { ++ DIR_OFFSET_MIN = 2, ++}; ++ + static void offset_set(struct dentry *dentry, u32 offset) + { + dentry->d_fsdata = (void *)((uintptr_t)(offset)); +@@ -260,9 +265,7 @@ void simple_offset_init(struct offset_ct + { + xa_init_flags(&octx->xa, XA_FLAGS_ALLOC1); + lockdep_set_class(&octx->xa.xa_lock, &simple_offset_xa_lock); +- +- /* 0 is '.', 1 is '..', so always start with offset 2 */ +- octx->next_offset = 2; ++ octx->next_offset = DIR_OFFSET_MIN; + } + + /** +@@ -275,7 +278,7 @@ void simple_offset_init(struct offset_ct + */ + int simple_offset_add(struct offset_ctx *octx, struct dentry *dentry) + { +- static const struct xa_limit limit = XA_LIMIT(2, U32_MAX); ++ static const struct xa_limit limit = XA_LIMIT(DIR_OFFSET_MIN, U32_MAX); + u32 offset; + int ret; + +@@ -480,7 +483,7 @@ static int offset_readdir(struct file *f + return 0; + + /* In this case, ->private_data is protected by f_pos_lock */ +- if (ctx->pos == 2) ++ if (ctx->pos == DIR_OFFSET_MIN) + file->private_data = NULL; + else if (file->private_data == ERR_PTR(-ENOENT)) + return 0; diff --git a/queue-6.6/libfs-fix-simple_offset_rename_exchange.patch b/queue-6.6/libfs-fix-simple_offset_rename_exchange.patch new file mode 100644 index 0000000000..c3831a420d --- /dev/null +++ b/queue-6.6/libfs-fix-simple_offset_rename_exchange.patch @@ -0,0 +1,85 @@ +From stable+bounces-110404-greg=kroah.com@vger.kernel.org Fri Jan 24 20:21:48 2025 +From: cel@kernel.org +Date: Fri, 24 Jan 2025 14:19:39 -0500 +Subject: libfs: Fix simple_offset_rename_exchange() +To: Hugh Dickins , Andrew Morten , Christian Brauner , Al Viro , Greg Kroah-Hartman , Sasha Levin +Cc: , , , yukuai3@huawei.com, yangerkun@huawei.com, Chuck Lever +Message-ID: <20250124191946.22308-5-cel@kernel.org> + +From: Chuck Lever + +[ Upstream commit 23cdd0eed3f1fff3af323092b0b88945a7950d8e ] + +User space expects the replacement (old) directory entry to have +the same directory offset after the rename. + +Suggested-by: Christian Brauner +Fixes: a2e459555c5f ("shmem: stable directory offsets") +Signed-off-by: Chuck Lever +Link: https://lore.kernel.org/r/20240415152057.4605-2-cel@kernel.org +Signed-off-by: Christian Brauner +[ cel: adjusted to apply to origin/linux-6.6.y ] +Signed-off-by: Chuck Lever +Signed-off-by: Greg Kroah-Hartman +--- + fs/libfs.c | 25 +++++++++++++++++++------ + 1 file changed, 19 insertions(+), 6 deletions(-) + +--- a/fs/libfs.c ++++ b/fs/libfs.c +@@ -294,6 +294,18 @@ int simple_offset_add(struct offset_ctx + return 0; + } + ++static int simple_offset_replace(struct offset_ctx *octx, struct dentry *dentry, ++ long offset) ++{ ++ void *ret; ++ ++ ret = xa_store(&octx->xa, offset, dentry, GFP_KERNEL); ++ if (xa_is_err(ret)) ++ return xa_err(ret); ++ offset_set(dentry, offset); ++ return 0; ++} ++ + /** + * simple_offset_remove - Remove an entry to a directory's offset map + * @octx: directory offset ctx to be updated +@@ -351,6 +363,9 @@ int simple_offset_empty(struct dentry *d + * @new_dir: destination parent + * @new_dentry: destination dentry + * ++ * This API preserves the directory offset values. Caller provides ++ * appropriate serialization. ++ * + * Returns zero on success. Otherwise a negative errno is returned and the + * rename is rolled back. + */ +@@ -368,11 +383,11 @@ int simple_offset_rename_exchange(struct + simple_offset_remove(old_ctx, old_dentry); + simple_offset_remove(new_ctx, new_dentry); + +- ret = simple_offset_add(new_ctx, old_dentry); ++ ret = simple_offset_replace(new_ctx, old_dentry, new_index); + if (ret) + goto out_restore; + +- ret = simple_offset_add(old_ctx, new_dentry); ++ ret = simple_offset_replace(old_ctx, new_dentry, old_index); + if (ret) { + simple_offset_remove(new_ctx, old_dentry); + goto out_restore; +@@ -387,10 +402,8 @@ int simple_offset_rename_exchange(struct + return 0; + + out_restore: +- offset_set(old_dentry, old_index); +- xa_store(&old_ctx->xa, old_index, old_dentry, GFP_KERNEL); +- offset_set(new_dentry, new_index); +- xa_store(&new_ctx->xa, new_index, new_dentry, GFP_KERNEL); ++ (void)simple_offset_replace(old_ctx, old_dentry, old_index); ++ (void)simple_offset_replace(new_ctx, new_dentry, new_index); + return ret; + } + diff --git a/queue-6.6/libfs-re-arrange-locking-in-offset_iterate_dir.patch b/queue-6.6/libfs-re-arrange-locking-in-offset_iterate_dir.patch new file mode 100644 index 0000000000..0151d1a893 --- /dev/null +++ b/queue-6.6/libfs-re-arrange-locking-in-offset_iterate_dir.patch @@ -0,0 +1,85 @@ +From stable+bounces-110401-greg=kroah.com@vger.kernel.org Fri Jan 24 20:20:29 2025 +From: cel@kernel.org +Date: Fri, 24 Jan 2025 14:19:36 -0500 +Subject: libfs: Re-arrange locking in offset_iterate_dir() +To: Hugh Dickins , Andrew Morten , Christian Brauner , Al Viro , Greg Kroah-Hartman , Sasha Levin +Cc: , , , yukuai3@huawei.com, yangerkun@huawei.com, Chuck Lever , "Liam R. Howlett" , Jan Kara +Message-ID: <20250124191946.22308-2-cel@kernel.org> + +From: Chuck Lever + +[ Upstream commit 3f6d810665dfde0d33785420618ceb03fba0619d ] + +Liam and Matthew say that once the RCU read lock is released, +xa_state is not safe to re-use for the next xas_find() call. But the +RCU read lock must be released on each loop iteration so that +dput(), which might_sleep(), can be called safely. + +Thus we are forced to walk the offset tree with fresh state for each +directory entry. xa_find() can do this for us, though it might be a +little less efficient than maintaining xa_state locally. + +We believe that in the current code base, inode->i_rwsem provides +protection for the xa_state maintained in +offset_iterate_dir(). However, there is no guarantee that will +continue to be the case in the future. + +Since offset_iterate_dir() doesn't build xa_state locally any more, +there's no longer a strong need for offset_find_next(). Clean up by +rolling these two helpers together. + +Suggested-by: Liam R. Howlett +Message-ID: <170785993027.11135.8830043889278631735.stgit@91.116.238.104.host.secureserver.net> +Signed-off-by: Chuck Lever +Link: https://lore.kernel.org/r/170820142021.6328.15047865406275957018.stgit@91.116.238.104.host.secureserver.net +Reviewed-by: Jan Kara +Signed-off-by: Christian Brauner +Signed-off-by: Chuck Lever +Signed-off-by: Greg Kroah-Hartman +--- + fs/libfs.c | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +--- a/fs/libfs.c ++++ b/fs/libfs.c +@@ -401,12 +401,13 @@ static loff_t offset_dir_llseek(struct f + return vfs_setpos(file, offset, U32_MAX); + } + +-static struct dentry *offset_find_next(struct xa_state *xas) ++static struct dentry *offset_find_next(struct offset_ctx *octx, loff_t offset) + { + struct dentry *child, *found = NULL; ++ XA_STATE(xas, &octx->xa, offset); + + rcu_read_lock(); +- child = xas_next_entry(xas, U32_MAX); ++ child = xas_next_entry(&xas, U32_MAX); + if (!child) + goto out; + spin_lock(&child->d_lock); +@@ -429,12 +430,11 @@ static bool offset_dir_emit(struct dir_c + + static void *offset_iterate_dir(struct inode *inode, struct dir_context *ctx) + { +- struct offset_ctx *so_ctx = inode->i_op->get_offset_ctx(inode); +- XA_STATE(xas, &so_ctx->xa, ctx->pos); ++ struct offset_ctx *octx = inode->i_op->get_offset_ctx(inode); + struct dentry *dentry; + + while (true) { +- dentry = offset_find_next(&xas); ++ dentry = offset_find_next(octx, ctx->pos); + if (!dentry) + return ERR_PTR(-ENOENT); + +@@ -443,8 +443,8 @@ static void *offset_iterate_dir(struct i + break; + } + ++ ctx->pos = dentry2offset(dentry) + 1; + dput(dentry); +- ctx->pos = xas.xa_index + 1; + } + return NULL; + } diff --git a/queue-6.6/libfs-replace-simple_offset-end-of-directory-detection.patch b/queue-6.6/libfs-replace-simple_offset-end-of-directory-detection.patch new file mode 100644 index 0000000000..3df89d9e45 --- /dev/null +++ b/queue-6.6/libfs-replace-simple_offset-end-of-directory-detection.patch @@ -0,0 +1,154 @@ +From stable+bounces-110409-greg=kroah.com@vger.kernel.org Fri Jan 24 20:22:03 2025 +From: cel@kernel.org +Date: Fri, 24 Jan 2025 14:19:44 -0500 +Subject: libfs: Replace simple_offset end-of-directory detection +To: Hugh Dickins , Andrew Morten , Christian Brauner , Al Viro , Greg Kroah-Hartman , Sasha Levin +Cc: , , , yukuai3@huawei.com, yangerkun@huawei.com, Chuck Lever +Message-ID: <20250124191946.22308-10-cel@kernel.org> + +From: Chuck Lever + +[ Upstream commit 68a3a65003145644efcbb651e91db249ccd96281 ] + +According to getdents(3), the d_off field in each returned directory +entry points to the next entry in the directory. The d_off field in +the last returned entry in the readdir buffer must contain a valid +offset value, but if it points to an actual directory entry, then +readdir/getdents can loop. + +This patch introduces a specific fixed offset value that is placed +in the d_off field of the last entry in a directory. Some user space +applications assume that the EOD offset value is larger than the +offsets of real directory entries, so the largest valid offset value +is reserved for this purpose. This new value is never allocated by +simple_offset_add(). + +When ->iterate_dir() returns, getdents{64} inserts the ctx->pos +value into the d_off field of the last valid entry in the readdir +buffer. When it hits EOD, offset_readdir() sets ctx->pos to the EOD +offset value so the last entry is updated to point to the EOD marker. + +When trying to read the entry at the EOD offset, offset_readdir() +terminates immediately. + +It is worth noting that using a Maple tree for directory offset +value allocation does not guarantee a 63-bit range of values -- +on platforms where "long" is a 32-bit type, the directory offset +value range is still 0..(2^31 - 1). For broad compatibility with +32-bit user space, the largest tmpfs directory cookie value is now +S32_MAX. + +Fixes: 796432efab1e ("libfs: getdents() should return 0 after reaching EOD") +Signed-off-by: Chuck Lever +Link: https://lore.kernel.org/r/20241228175522.1854234-5-cel@kernel.org +Signed-off-by: Christian Brauner +[ cel: adjusted to apply to origin/linux-6.6.y ] +Signed-off-by: Chuck Lever +Signed-off-by: Greg Kroah-Hartman +--- + fs/libfs.c | 37 +++++++++++++++++++++---------------- + 1 file changed, 21 insertions(+), 16 deletions(-) + +--- a/fs/libfs.c ++++ b/fs/libfs.c +@@ -239,9 +239,15 @@ const struct inode_operations simple_dir + }; + EXPORT_SYMBOL(simple_dir_inode_operations); + +-/* 0 is '.', 1 is '..', so always start with offset 2 or more */ ++/* simple_offset_add() never assigns these to a dentry */ + enum { +- DIR_OFFSET_MIN = 2, ++ DIR_OFFSET_EOD = S32_MAX, ++}; ++ ++/* simple_offset_add() allocation range */ ++enum { ++ DIR_OFFSET_MIN = 2, ++ DIR_OFFSET_MAX = DIR_OFFSET_EOD - 1, + }; + + static void offset_set(struct dentry *dentry, u32 offset) +@@ -278,7 +284,8 @@ void simple_offset_init(struct offset_ct + */ + int simple_offset_add(struct offset_ctx *octx, struct dentry *dentry) + { +- static const struct xa_limit limit = XA_LIMIT(DIR_OFFSET_MIN, U32_MAX); ++ static const struct xa_limit limit = XA_LIMIT(DIR_OFFSET_MIN, ++ DIR_OFFSET_MAX); + u32 offset; + int ret; + +@@ -442,8 +449,6 @@ static loff_t offset_dir_llseek(struct f + return -EINVAL; + } + +- /* In this case, ->private_data is protected by f_pos_lock */ +- file->private_data = NULL; + return vfs_setpos(file, offset, U32_MAX); + } + +@@ -453,7 +458,7 @@ static struct dentry *offset_find_next(s + XA_STATE(xas, &octx->xa, offset); + + rcu_read_lock(); +- child = xas_next_entry(&xas, U32_MAX); ++ child = xas_next_entry(&xas, DIR_OFFSET_MAX); + if (!child) + goto out; + spin_lock(&child->d_lock); +@@ -474,7 +479,7 @@ static bool offset_dir_emit(struct dir_c + inode->i_ino, fs_umode_to_dtype(inode->i_mode)); + } + +-static void *offset_iterate_dir(struct inode *inode, struct dir_context *ctx) ++static void offset_iterate_dir(struct inode *inode, struct dir_context *ctx) + { + struct offset_ctx *octx = inode->i_op->get_offset_ctx(inode); + struct dentry *dentry; +@@ -482,7 +487,7 @@ static void *offset_iterate_dir(struct i + while (true) { + dentry = offset_find_next(octx, ctx->pos); + if (!dentry) +- return ERR_PTR(-ENOENT); ++ goto out_eod; + + if (!offset_dir_emit(ctx, dentry)) { + dput(dentry); +@@ -492,7 +497,10 @@ static void *offset_iterate_dir(struct i + ctx->pos = dentry2offset(dentry) + 1; + dput(dentry); + } +- return NULL; ++ return; ++ ++out_eod: ++ ctx->pos = DIR_OFFSET_EOD; + } + + /** +@@ -512,6 +520,8 @@ static void *offset_iterate_dir(struct i + * + * On return, @ctx->pos contains an offset that will read the next entry + * in this directory when offset_readdir() is called again with @ctx. ++ * Caller places this value in the d_off field of the last entry in the ++ * user's buffer. + * + * Return values: + * %0 - Complete +@@ -524,13 +534,8 @@ static int offset_readdir(struct file *f + + if (!dir_emit_dots(file, ctx)) + return 0; +- +- /* In this case, ->private_data is protected by f_pos_lock */ +- if (ctx->pos == DIR_OFFSET_MIN) +- file->private_data = NULL; +- else if (file->private_data == ERR_PTR(-ENOENT)) +- return 0; +- file->private_data = offset_iterate_dir(d_inode(dir), ctx); ++ if (ctx->pos != DIR_OFFSET_EOD) ++ offset_iterate_dir(d_inode(dir), ctx); + return 0; + } + diff --git a/queue-6.6/libfs-return-enospc-when-the-directory-offset-range-is-exhausted.patch b/queue-6.6/libfs-return-enospc-when-the-directory-offset-range-is-exhausted.patch new file mode 100644 index 0000000000..0edf62f3d6 --- /dev/null +++ b/queue-6.6/libfs-return-enospc-when-the-directory-offset-range-is-exhausted.patch @@ -0,0 +1,51 @@ +From stable+bounces-110407-greg=kroah.com@vger.kernel.org Fri Jan 24 20:20:27 2025 +From: cel@kernel.org +Date: Fri, 24 Jan 2025 14:19:42 -0500 +Subject: libfs: Return ENOSPC when the directory offset range is exhausted +To: Hugh Dickins , Andrew Morten , Christian Brauner , Al Viro , Greg Kroah-Hartman , Sasha Levin +Cc: , , , yukuai3@huawei.com, yangerkun@huawei.com, Chuck Lever , Jeff Layton +Message-ID: <20250124191946.22308-8-cel@kernel.org> + +From: Chuck Lever + +[ Upstream commit 903dc9c43a155e0893280c7472d4a9a3a83d75a6 ] + +Testing shows that the EBUSY error return from mtree_alloc_cyclic() +leaks into user space. The ERRORS section of "man creat(2)" says: + +> EBUSY O_EXCL was specified in flags and pathname refers +> to a block device that is in use by the system +> (e.g., it is mounted). + +ENOSPC is closer to what applications expect in this situation. + +Note that the normal range of simple directory offset values is +2..2^63, so hitting this error is going to be rare to impossible. + +Fixes: 6faddda69f62 ("libfs: Add directory operations for stable offsets") +Cc: stable@vger.kernel.org # v6.9+ +Reviewed-by: Jeff Layton +Reviewed-by: Yang Erkun +Signed-off-by: Chuck Lever +Link: https://lore.kernel.org/r/20241228175522.1854234-2-cel@kernel.org +Signed-off-by: Christian Brauner +[ cel: adjusted to apply to origin/linux-6.6.y ] +Signed-off-by: Chuck Lever +Signed-off-by: Greg Kroah-Hartman +--- + fs/libfs.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/fs/libfs.c ++++ b/fs/libfs.c +@@ -287,8 +287,8 @@ int simple_offset_add(struct offset_ctx + + ret = xa_alloc_cyclic(&octx->xa, &offset, dentry, limit, + &octx->next_offset, GFP_KERNEL); +- if (ret < 0) +- return ret; ++ if (unlikely(ret < 0)) ++ return ret == -EBUSY ? -ENOSPC : ret; + + offset_set(dentry, offset); + return 0; diff --git a/queue-6.6/libfs-use-d_children-list-to-iterate-simple_offset-directories.patch b/queue-6.6/libfs-use-d_children-list-to-iterate-simple_offset-directories.patch new file mode 100644 index 0000000000..2526ae51c5 --- /dev/null +++ b/queue-6.6/libfs-use-d_children-list-to-iterate-simple_offset-directories.patch @@ -0,0 +1,177 @@ +From stable+bounces-110410-greg=kroah.com@vger.kernel.org Fri Jan 24 20:22:00 2025 +From: cel@kernel.org +Date: Fri, 24 Jan 2025 14:19:45 -0500 +Subject: libfs: Use d_children list to iterate simple_offset directories +To: Hugh Dickins , Andrew Morten , Christian Brauner , Al Viro , Greg Kroah-Hartman , Sasha Levin +Cc: , , , yukuai3@huawei.com, yangerkun@huawei.com, Chuck Lever +Message-ID: <20250124191946.22308-11-cel@kernel.org> + +From: Chuck Lever + +[ Upstream commit b9b588f22a0c049a14885399e27625635ae6ef91 ] + +The mtree mechanism has been effective at creating directory offsets +that are stable over multiple opendir instances. However, it has not +been able to handle the subtleties of renames that are concurrent +with readdir. + +Instead of using the mtree to emit entries in the order of their +offset values, use it only to map incoming ctx->pos to a starting +entry. Then use the directory's d_children list, which is already +maintained properly by the dcache, to find the next child to emit. + +One of the sneaky things about this is that when the mtree-allocated +offset value wraps (which is very rare), looking up ctx->pos++ is +not going to find the next entry; it will return NULL. Instead, by +following the d_children list, the offset values can appear in any +order but all of the entries in the directory will be visited +eventually. + +Note also that the readdir() is guaranteed to reach the tail of this +list. Entries are added only at the head of d_children, and readdir +walks from its current position in that list towards its tail. + +Signed-off-by: Chuck Lever +Link: https://lore.kernel.org/r/20241228175522.1854234-6-cel@kernel.org +Signed-off-by: Christian Brauner +[ cel: adjusted to apply to origin/linux-6.6.y ] +Signed-off-by: Chuck Lever +Signed-off-by: Greg Kroah-Hartman +--- + fs/libfs.c | 84 ++++++++++++++++++++++++++++++++++++++++++------------------- + 1 file changed, 59 insertions(+), 25 deletions(-) + +--- a/fs/libfs.c ++++ b/fs/libfs.c +@@ -241,12 +241,13 @@ EXPORT_SYMBOL(simple_dir_inode_operation + + /* simple_offset_add() never assigns these to a dentry */ + enum { ++ DIR_OFFSET_FIRST = 2, /* Find first real entry */ + DIR_OFFSET_EOD = S32_MAX, + }; + + /* simple_offset_add() allocation range */ + enum { +- DIR_OFFSET_MIN = 2, ++ DIR_OFFSET_MIN = DIR_OFFSET_FIRST + 1, + DIR_OFFSET_MAX = DIR_OFFSET_EOD - 1, + }; + +@@ -452,51 +453,84 @@ static loff_t offset_dir_llseek(struct f + return vfs_setpos(file, offset, U32_MAX); + } + +-static struct dentry *offset_find_next(struct offset_ctx *octx, loff_t offset) ++static struct dentry *find_positive_dentry(struct dentry *parent, ++ struct dentry *dentry, ++ bool next) ++{ ++ struct dentry *found = NULL; ++ ++ spin_lock(&parent->d_lock); ++ if (next) ++ dentry = list_next_entry(dentry, d_child); ++ else if (!dentry) ++ dentry = list_first_entry_or_null(&parent->d_subdirs, ++ struct dentry, d_child); ++ for (; dentry && !list_entry_is_head(dentry, &parent->d_subdirs, d_child); ++ dentry = list_next_entry(dentry, d_child)) { ++ if (!simple_positive(dentry)) ++ continue; ++ spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED); ++ if (simple_positive(dentry)) ++ found = dget_dlock(dentry); ++ spin_unlock(&dentry->d_lock); ++ if (likely(found)) ++ break; ++ } ++ spin_unlock(&parent->d_lock); ++ return found; ++} ++ ++static noinline_for_stack struct dentry * ++offset_dir_lookup(struct dentry *parent, loff_t offset) + { ++ struct inode *inode = d_inode(parent); ++ struct offset_ctx *octx = inode->i_op->get_offset_ctx(inode); + struct dentry *child, *found = NULL; ++ + XA_STATE(xas, &octx->xa, offset); + +- rcu_read_lock(); +- child = xas_next_entry(&xas, DIR_OFFSET_MAX); +- if (!child) +- goto out; +- spin_lock(&child->d_lock); +- if (simple_positive(child)) +- found = dget_dlock(child); +- spin_unlock(&child->d_lock); +-out: +- rcu_read_unlock(); ++ if (offset == DIR_OFFSET_FIRST) ++ found = find_positive_dentry(parent, NULL, false); ++ else { ++ rcu_read_lock(); ++ child = xas_next_entry(&xas, DIR_OFFSET_MAX); ++ found = find_positive_dentry(parent, child, false); ++ rcu_read_unlock(); ++ } + return found; + } + + static bool offset_dir_emit(struct dir_context *ctx, struct dentry *dentry) + { +- u32 offset = dentry2offset(dentry); + struct inode *inode = d_inode(dentry); + +- return ctx->actor(ctx, dentry->d_name.name, dentry->d_name.len, offset, +- inode->i_ino, fs_umode_to_dtype(inode->i_mode)); ++ return dir_emit(ctx, dentry->d_name.name, dentry->d_name.len, ++ inode->i_ino, fs_umode_to_dtype(inode->i_mode)); + } + +-static void offset_iterate_dir(struct inode *inode, struct dir_context *ctx) ++static void offset_iterate_dir(struct file *file, struct dir_context *ctx) + { +- struct offset_ctx *octx = inode->i_op->get_offset_ctx(inode); ++ struct dentry *dir = file->f_path.dentry; + struct dentry *dentry; + ++ dentry = offset_dir_lookup(dir, ctx->pos); ++ if (!dentry) ++ goto out_eod; + while (true) { +- dentry = offset_find_next(octx, ctx->pos); +- if (!dentry) +- goto out_eod; ++ struct dentry *next; + +- if (!offset_dir_emit(ctx, dentry)) { +- dput(dentry); ++ ctx->pos = dentry2offset(dentry); ++ if (!offset_dir_emit(ctx, dentry)) + break; +- } + +- ctx->pos = dentry2offset(dentry) + 1; ++ next = find_positive_dentry(dir, dentry, true); + dput(dentry); ++ ++ if (!next) ++ goto out_eod; ++ dentry = next; + } ++ dput(dentry); + return; + + out_eod: +@@ -535,7 +569,7 @@ static int offset_readdir(struct file *f + if (!dir_emit_dots(file, ctx)) + return 0; + if (ctx->pos != DIR_OFFSET_EOD) +- offset_iterate_dir(d_inode(dir), ctx); ++ offset_iterate_dir(file, ctx); + return 0; + } + diff --git a/queue-6.6/revert-libfs-add-simple_offset_empty.patch b/queue-6.6/revert-libfs-add-simple_offset_empty.patch new file mode 100644 index 0000000000..d086a07b86 --- /dev/null +++ b/queue-6.6/revert-libfs-add-simple_offset_empty.patch @@ -0,0 +1,104 @@ +From stable+bounces-110408-greg=kroah.com@vger.kernel.org Fri Jan 24 20:21:08 2025 +From: cel@kernel.org +Date: Fri, 24 Jan 2025 14:19:43 -0500 +Subject: Revert "libfs: Add simple_offset_empty()" +To: Hugh Dickins , Andrew Morten , Christian Brauner , Al Viro , Greg Kroah-Hartman , Sasha Levin +Cc: , , , yukuai3@huawei.com, yangerkun@huawei.com, Chuck Lever +Message-ID: <20250124191946.22308-9-cel@kernel.org> + +From: Chuck Lever + +[ Upstream commit d7bde4f27ceef3dc6d72010a20d4da23db835a32 ] + +simple_empty() and simple_offset_empty() perform the same task. +The latter's use as a canary to find bugs has not found any new +issues. A subsequent patch will remove the use of the mtree for +iterating directory contents, so revert back to using a similar +mechanism for determining whether a directory is indeed empty. + +Only one such mechanism is ever needed. + +Signed-off-by: Chuck Lever +Link: https://lore.kernel.org/r/20241228175522.1854234-3-cel@kernel.org +Reviewed-by: Yang Erkun +Signed-off-by: Christian Brauner +[ cel: adjusted to apply to origin/linux-6.6.y ] +Signed-off-by: Chuck Lever +Signed-off-by: Greg Kroah-Hartman +--- + fs/libfs.c | 32 -------------------------------- + include/linux/fs.h | 1 - + mm/shmem.c | 4 ++-- + 3 files changed, 2 insertions(+), 35 deletions(-) + +--- a/fs/libfs.c ++++ b/fs/libfs.c +@@ -325,38 +325,6 @@ void simple_offset_remove(struct offset_ + } + + /** +- * simple_offset_empty - Check if a dentry can be unlinked +- * @dentry: dentry to be tested +- * +- * Returns 0 if @dentry is a non-empty directory; otherwise returns 1. +- */ +-int simple_offset_empty(struct dentry *dentry) +-{ +- struct inode *inode = d_inode(dentry); +- struct offset_ctx *octx; +- struct dentry *child; +- unsigned long index; +- int ret = 1; +- +- if (!inode || !S_ISDIR(inode->i_mode)) +- return ret; +- +- index = DIR_OFFSET_MIN; +- octx = inode->i_op->get_offset_ctx(inode); +- xa_for_each(&octx->xa, index, child) { +- spin_lock(&child->d_lock); +- if (simple_positive(child)) { +- spin_unlock(&child->d_lock); +- ret = 0; +- break; +- } +- spin_unlock(&child->d_lock); +- } +- +- return ret; +-} +- +-/** + * simple_offset_rename - handle directory offsets for rename + * @old_dir: parent directory of source entry + * @old_dentry: dentry of source entry +--- a/include/linux/fs.h ++++ b/include/linux/fs.h +@@ -3197,7 +3197,6 @@ struct offset_ctx { + void simple_offset_init(struct offset_ctx *octx); + int simple_offset_add(struct offset_ctx *octx, struct dentry *dentry); + void simple_offset_remove(struct offset_ctx *octx, struct dentry *dentry); +-int simple_offset_empty(struct dentry *dentry); + int simple_offset_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry); + int simple_offset_rename_exchange(struct inode *old_dir, +--- a/mm/shmem.c ++++ b/mm/shmem.c +@@ -3368,7 +3368,7 @@ static int shmem_unlink(struct inode *di + + static int shmem_rmdir(struct inode *dir, struct dentry *dentry) + { +- if (!simple_offset_empty(dentry)) ++ if (!simple_empty(dentry)) + return -ENOTEMPTY; + + drop_nlink(d_inode(dentry)); +@@ -3425,7 +3425,7 @@ static int shmem_rename2(struct mnt_idma + return simple_offset_rename_exchange(old_dir, old_dentry, + new_dir, new_dentry); + +- if (!simple_offset_empty(new_dentry)) ++ if (!simple_empty(new_dentry)) + return -ENOTEMPTY; + + if (flags & RENAME_WHITEOUT) { diff --git a/queue-6.6/series b/queue-6.6/series index d9c1c34871..94ca9666d5 100644 --- a/queue-6.6/series +++ b/queue-6.6/series @@ -11,3 +11,15 @@ ata-libata-core-set-ata_qcflag_rtf_filled-in-fill_result_tf.patch cpufreq-amd-pstate-add-check-for-cpufreq_cpu_get-s-return-value.patch ipv6-fix-soft-lockups-in-fib6_select_path-under-high-next-hop-churn.patch rdma-bnxt_re-avoid-cpu-lockups-due-fifo-occupancy-check-loop.patch +gfs2-truncate-address-space-when-flipping-gfs2_dif_jdata-flag.patch +libfs-re-arrange-locking-in-offset_iterate_dir.patch +libfs-define-a-minimum-directory-offset.patch +libfs-add-simple_offset_empty.patch +libfs-fix-simple_offset_rename_exchange.patch +libfs-add-simple_offset_rename-api.patch +shmem-fix-shmem_rename2.patch +libfs-return-enospc-when-the-directory-offset-range-is-exhausted.patch +revert-libfs-add-simple_offset_empty.patch +libfs-replace-simple_offset-end-of-directory-detection.patch +libfs-use-d_children-list-to-iterate-simple_offset-directories.patch +smb-client-handle-lack-of-ea-support-in-smb2_query_path_info.patch diff --git a/queue-6.6/shmem-fix-shmem_rename2.patch b/queue-6.6/shmem-fix-shmem_rename2.patch new file mode 100644 index 0000000000..287112e008 --- /dev/null +++ b/queue-6.6/shmem-fix-shmem_rename2.patch @@ -0,0 +1,54 @@ +From stable+bounces-110406-greg=kroah.com@vger.kernel.org Fri Jan 24 20:22:02 2025 +From: cel@kernel.org +Date: Fri, 24 Jan 2025 14:19:41 -0500 +Subject: shmem: Fix shmem_rename2() +To: Hugh Dickins , Andrew Morten , Christian Brauner , Al Viro , Greg Kroah-Hartman , Sasha Levin +Cc: , , , yukuai3@huawei.com, yangerkun@huawei.com, Chuck Lever +Message-ID: <20250124191946.22308-7-cel@kernel.org> + +From: Chuck Lever + +[ Upstream commit ad191eb6d6942bb835a0b20b647f7c53c1d99ca4 ] + +When renaming onto an existing directory entry, user space expects +the replacement entry to have the same directory offset as the +original one. + +Link: https://gitlab.alpinelinux.org/alpine/aports/-/issues/15966 +Fixes: a2e459555c5f ("shmem: stable directory offsets") +Signed-off-by: Chuck Lever +Link: https://lore.kernel.org/r/20240415152057.4605-4-cel@kernel.org +Signed-off-by: Christian Brauner +Signed-off-by: Chuck Lever +Signed-off-by: Greg Kroah-Hartman +--- + fs/libfs.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +--- a/fs/libfs.c ++++ b/fs/libfs.c +@@ -365,6 +365,9 @@ int simple_offset_empty(struct dentry *d + * + * Caller provides appropriate serialization. + * ++ * User space expects the directory offset value of the replaced ++ * (new) directory entry to be unchanged after a rename. ++ * + * Returns zero on success, a negative errno value on failure. + */ + int simple_offset_rename(struct inode *old_dir, struct dentry *old_dentry, +@@ -372,8 +375,14 @@ int simple_offset_rename(struct inode *o + { + struct offset_ctx *old_ctx = old_dir->i_op->get_offset_ctx(old_dir); + struct offset_ctx *new_ctx = new_dir->i_op->get_offset_ctx(new_dir); ++ long new_offset = dentry2offset(new_dentry); + + simple_offset_remove(old_ctx, old_dentry); ++ ++ if (new_offset) { ++ offset_set(new_dentry, 0); ++ return simple_offset_replace(new_ctx, old_dentry, new_offset); ++ } + return simple_offset_add(new_ctx, old_dentry); + } + diff --git a/queue-6.6/smb-client-handle-lack-of-ea-support-in-smb2_query_path_info.patch b/queue-6.6/smb-client-handle-lack-of-ea-support-in-smb2_query_path_info.patch new file mode 100644 index 0000000000..d48e75f980 --- /dev/null +++ b/queue-6.6/smb-client-handle-lack-of-ea-support-in-smb2_query_path_info.patch @@ -0,0 +1,198 @@ +From 3681c74d342db75b0d641ba60de27bf73e16e66b Mon Sep 17 00:00:00 2001 +From: Paulo Alcantara +Date: Tue, 21 Jan 2025 15:25:36 -0300 +Subject: smb: client: handle lack of EA support in smb2_query_path_info() +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +From: Paulo Alcantara + +commit 3681c74d342db75b0d641ba60de27bf73e16e66b upstream. + +If the server doesn't support both EAs and reparse point in a file, +the SMB2_QUERY_INFO request will fail with either +STATUS_NO_EAS_ON_FILE or STATUS_EAS_NOT_SUPPORT in the compound chain, +so ignore it as long as reparse point isn't +IO_REPARSE_TAG_LX_(CHR|BLK), which would require the EAs to know about +major/minor numbers. + +Reported-by: Pali Rohár +Signed-off-by: Paulo Alcantara (Red Hat) +Signed-off-by: Steve French +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/client/smb2inode.c | 92 ++++++++++++++++++++++++++++++++++------------ + 1 file changed, 69 insertions(+), 23 deletions(-) + +--- a/fs/smb/client/smb2inode.c ++++ b/fs/smb/client/smb2inode.c +@@ -176,27 +176,27 @@ static int smb2_compound_op(const unsign + struct kvec *out_iov, int *out_buftype, struct dentry *dentry) + { + +- struct reparse_data_buffer *rbuf; ++ struct smb2_query_info_rsp *qi_rsp = NULL; + struct smb2_compound_vars *vars = NULL; +- struct kvec *rsp_iov, *iov; +- struct smb_rqst *rqst; +- int rc; +- __le16 *utf16_path = NULL; + __u8 oplock = SMB2_OPLOCK_LEVEL_NONE; +- struct cifs_fid fid; ++ struct cifs_open_info_data *idata; + struct cifs_ses *ses = tcon->ses; ++ struct reparse_data_buffer *rbuf; + struct TCP_Server_Info *server; +- int num_rqst = 0, i; + int resp_buftype[MAX_COMPOUND]; +- struct smb2_query_info_rsp *qi_rsp = NULL; +- struct cifs_open_info_data *idata; ++ int retries = 0, cur_sleep = 1; ++ __u8 delete_pending[8] = {1,}; ++ struct kvec *rsp_iov, *iov; + struct inode *inode = NULL; +- int flags = 0; +- __u8 delete_pending[8] = {1, 0, 0, 0, 0, 0, 0, 0}; ++ __le16 *utf16_path = NULL; ++ struct smb_rqst *rqst; + unsigned int size[2]; +- void *data[2]; ++ struct cifs_fid fid; ++ int num_rqst = 0, i; + unsigned int len; +- int retries = 0, cur_sleep = 1; ++ int tmp_rc, rc; ++ int flags = 0; ++ void *data[2]; + + replay_again: + /* reinitialize for possible replay */ +@@ -637,7 +637,14 @@ finished: + tcon->need_reconnect = true; + } + ++ tmp_rc = rc; + for (i = 0; i < num_cmds; i++) { ++ char *buf = rsp_iov[i + i].iov_base; ++ ++ if (buf && resp_buftype[i + 1] != CIFS_NO_BUFFER) ++ rc = server->ops->map_error(buf, false); ++ else ++ rc = tmp_rc; + switch (cmds[i]) { + case SMB2_OP_QUERY_INFO: + idata = in_iov[i].iov_base; +@@ -803,6 +810,7 @@ finished: + } + } + SMB2_close_free(&rqst[num_rqst]); ++ rc = tmp_rc; + + num_cmds += 2; + if (out_iov && out_buftype) { +@@ -858,22 +866,52 @@ static int parse_create_response(struct + return rc; + } + ++/* Check only if SMB2_OP_QUERY_WSL_EA command failed in the compound chain */ ++static bool ea_unsupported(int *cmds, int num_cmds, ++ struct kvec *out_iov, int *out_buftype) ++{ ++ int i; ++ ++ if (cmds[num_cmds - 1] != SMB2_OP_QUERY_WSL_EA) ++ return false; ++ ++ for (i = 1; i < num_cmds - 1; i++) { ++ struct smb2_hdr *hdr = out_iov[i].iov_base; ++ ++ if (out_buftype[i] == CIFS_NO_BUFFER || !hdr || ++ hdr->Status != STATUS_SUCCESS) ++ return false; ++ } ++ return true; ++} ++ ++static inline void free_rsp_iov(struct kvec *iovs, int *buftype, int count) ++{ ++ int i; ++ ++ for (i = 0; i < count; i++) { ++ free_rsp_buf(buftype[i], iovs[i].iov_base); ++ memset(&iovs[i], 0, sizeof(*iovs)); ++ buftype[i] = CIFS_NO_BUFFER; ++ } ++} ++ + int smb2_query_path_info(const unsigned int xid, + struct cifs_tcon *tcon, + struct cifs_sb_info *cifs_sb, + const char *full_path, + struct cifs_open_info_data *data) + { ++ struct kvec in_iov[3], out_iov[5] = {}; ++ struct cached_fid *cfid = NULL; + struct cifs_open_parms oparms; +- __u32 create_options = 0; + struct cifsFileInfo *cfile; +- struct cached_fid *cfid = NULL; ++ __u32 create_options = 0; ++ int out_buftype[5] = {}; + struct smb2_hdr *hdr; +- struct kvec in_iov[3], out_iov[3] = {}; +- int out_buftype[3] = {}; ++ int num_cmds = 0; + int cmds[3]; + bool islink; +- int i, num_cmds = 0; + int rc, rc2; + + data->adjust_tz = false; +@@ -943,14 +981,14 @@ int smb2_query_path_info(const unsigned + if (rc || !data->reparse_point) + goto out; + +- if (!tcon->posix_extensions) +- cmds[num_cmds++] = SMB2_OP_QUERY_WSL_EA; + /* + * Skip SMB2_OP_GET_REPARSE if symlink already parsed in create + * response. + */ + if (data->reparse.tag != IO_REPARSE_TAG_SYMLINK) + cmds[num_cmds++] = SMB2_OP_GET_REPARSE; ++ if (!tcon->posix_extensions) ++ cmds[num_cmds++] = SMB2_OP_QUERY_WSL_EA; + + oparms = CIFS_OPARMS(cifs_sb, tcon, full_path, + FILE_READ_ATTRIBUTES | +@@ -958,9 +996,18 @@ int smb2_query_path_info(const unsigned + FILE_OPEN, create_options | + OPEN_REPARSE_POINT, ACL_NO_MODE); + cifs_get_readable_path(tcon, full_path, &cfile); ++ free_rsp_iov(out_iov, out_buftype, ARRAY_SIZE(out_iov)); + rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, + &oparms, in_iov, cmds, num_cmds, +- cfile, NULL, NULL, NULL); ++ cfile, out_iov, out_buftype, NULL); ++ if (rc && ea_unsupported(cmds, num_cmds, ++ out_iov, out_buftype)) { ++ if (data->reparse.tag != IO_REPARSE_TAG_LX_BLK && ++ data->reparse.tag != IO_REPARSE_TAG_LX_CHR) ++ rc = 0; ++ else ++ rc = -EOPNOTSUPP; ++ } + break; + case -EREMOTE: + break; +@@ -978,8 +1025,7 @@ int smb2_query_path_info(const unsigned + } + + out: +- for (i = 0; i < ARRAY_SIZE(out_buftype); i++) +- free_rsp_buf(out_buftype[i], out_iov[i].iov_base); ++ free_rsp_iov(out_iov, out_buftype, ARRAY_SIZE(out_iov)); + return rc; + } +