From: Gil Portnoy Date: Fri, 26 Jun 2026 19:11:14 +0000 (+0300) Subject: ksmbd: close superseded durable handles through refcount handoff X-Git-Tag: v7.2-rc2~9^2~2 X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=c706195e5e06402d8d1d20908978cdc82eae6185;p=thirdparty%2Flinux.git ksmbd: close superseded durable handles through refcount handoff ksmbd_close_disconnected_durable_delete_on_close() collects disconnected durable handles for a name being superseded by a new delete-on-close open, drops ci->m_lock, then closes each collected handle directly with __ksmbd_close_fd(). That bypasses the FP_CLOSED and refcount handoff used by the other close paths. If a durable reconnect or the durable scavenger already took a reference to the same fp, the direct __ksmbd_close_fd() can free the ksmbd_file while that other holder still owns a live reference. Claim the disconnected durable handle before unlinking it from m_fp_list. While holding ci->m_lock and global_ft.lock, only take ownership when the durable lifetime reference is the only remaining reference. Then take a transient reference, remove the fp from global_ft, mark it FP_CLOSED, and move it to the local dispose list. If another holder already has a reference, leave the fp linked and let that holder complete its path. The dispose loop then drops both references owned by the claim. This keeps the force-close path in the same refcount handoff model as the durable scavenger and avoids leaving a live reconnected fp detached from m_fp_list. Fixes: 166e4c07023b ("ksmbd: supersede disconnected delete-on-close durable handle") Signed-off-by: Gil Portnoy Co-developed-by: Namjae Jeon Signed-off-by: Namjae Jeon Signed-off-by: Steve French --- diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c index 3c9443ac3522..73d28942dc0a 100644 --- a/fs/smb/server/vfs_cache.c +++ b/fs/smb/server/vfs_cache.c @@ -581,7 +581,20 @@ bool ksmbd_close_disconnected_durable_delete_on_close(struct dentry *dentry) if (fp->conn || !fp->is_durable || fp->f_state != FP_INITED) continue; - list_move_tail(&fp->node, &dispose); + + /* + * Claim the close before unlinking fp from m_fp_list. + * refcount == 1 means only the durable lifetime ref is + * left. Add a transient ref so final close can drop both. + */ + write_lock(&global_ft.lock); + if (atomic_read(&fp->refcount) == 1) { + atomic_inc(&fp->refcount); + __ksmbd_remove_durable_fd(fp); + ksmbd_mark_fp_closed(fp); + list_move_tail(&fp->node, &dispose); + } + write_unlock(&global_ft.lock); } } up_write(&ci->m_lock); @@ -589,16 +602,18 @@ bool ksmbd_close_disconnected_durable_delete_on_close(struct dentry *dentry) /* * Drop our lookup reference before closing so the last __ksmbd_close_fd() * can drop m_count to zero and unlink the delete-on-close file. The - * collected handles still hold references, so ci stays valid until they - * are closed below. + * collected handles still hold the transient reference taken above, so + * ci stays valid until they are closed below. */ ksmbd_inode_put(ci); while (!list_empty(&dispose)) { fp = list_first_entry(&dispose, struct ksmbd_file, node); list_del_init(&fp->node); - __ksmbd_close_fd(NULL, fp); - closed = true; + if (atomic_sub_and_test(2, &fp->refcount)) { + __ksmbd_close_fd(NULL, fp); + closed = true; + } } return closed;