]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ksmbd: supersede disconnected delete-on-close durable handle
authorNamjae Jeon <linkinjeon@kernel.org>
Sun, 21 Jun 2026 10:33:03 +0000 (19:33 +0900)
committerSteve French <stfrench@microsoft.com>
Tue, 23 Jun 2026 01:15:05 +0000 (20:15 -0500)
A durable handle opened with FILE_DELETE_ON_CLOSE is preserved across a
disconnect so it can be reclaimed by a durable reconnect.
smb2.durable-open.delete_on_close2 disconnects such a handle and then
reconnects it, expecting the reconnect to succeed.

When the client does not reconnect but instead opens the same name with a
new delete-on-close create, the preserved handle keeps the file present
with delete-on-close set. ksmbd then rejects the new open with
STATUS_ACCESS_DENIED on the file_present + FILE_DELETE_ON_CLOSE +
OPEN_IF/OVERWRITE_IF path. smb2.durable-open.delete_on_close1 expects this
open to create a fresh, empty file instead, i.e. the disconnected handle's
delete-on-close must take effect first.

Add ksmbd_close_disconnected_durable_delete_on_close(), which closes
disconnected (conn == NULL) durable handles that keep a delete-on-close
file present. The final close promotes S_DEL_ON_CLS to S_DEL_PENDING and
unlinks the file, so a re-resolved path is absent and the new open creates
it fresh. Call it from smb2_open() before the delete-on-close conflict
check, only for the conflicting open shapes. A live (connected) handle
still keeps the file and blocks the open as before.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/server/smb2pdu.c
fs/smb/server/vfs_cache.c
fs/smb/server/vfs_cache.h

index 2bc275ed450aea89b03f3332c751e3f1e6a67e7c..37a20c5740cd13d05ae109c0e9c97315a5ec70bc 100644 (file)
@@ -3272,6 +3272,23 @@ int smb2_open(struct ksmbd_work *work)
 
        rc = ksmbd_vfs_kern_path(work, name, LOOKUP_NO_SYMLINKS,
                                 &path, 1);
+
+       /*
+        * A durable handle opened with delete-on-close is preserved across a
+        * disconnect so it can be reclaimed by a durable reconnect.  When a new
+        * delete-on-close open for the same name arrives instead, the
+        * disconnected handle must give way: close it so its delete-on-close
+        * removes the file, then re-resolve so this open can create a fresh one.
+        */
+       if (!rc && (req->CreateOptions & FILE_DELETE_ON_CLOSE_LE) &&
+           (req->CreateDisposition == FILE_OVERWRITE_IF_LE ||
+            req->CreateDisposition == FILE_OPEN_IF_LE) &&
+           ksmbd_close_disconnected_durable_delete_on_close(path.dentry)) {
+               path_put(&path);
+               rc = ksmbd_vfs_kern_path(work, name, LOOKUP_NO_SYMLINKS,
+                                        &path, 1);
+       }
+
        if (!rc) {
                file_present = true;
 
index 39c56942ae440b21100fec49860a6f5ef96258fe..96fa3f160d5bc11c4f50e13ccdbfdef18a7ccc7b 100644 (file)
@@ -517,6 +517,63 @@ static void __ksmbd_close_fd(struct ksmbd_file_table *ft, struct ksmbd_file *fp)
        kmem_cache_free(filp_cache, fp);
 }
 
+/**
+ * ksmbd_close_disconnected_durable_delete_on_close() - drop a delete-on-close
+ *     file kept present only by disconnected durable handles
+ * @dentry:    dentry of the file being opened
+ *
+ * A durable handle opened with delete-on-close is preserved across a
+ * disconnect so it can be reclaimed by a durable reconnect.  When a new
+ * (non-reconnect) open arrives for the same name instead, the disconnected
+ * handle has to give way.  Close such handles so their delete-on-close is
+ * applied and the file is removed once the last handle is gone, letting the
+ * new open create a fresh file.
+ *
+ * The caller's inode reference is dropped before closing so that the final
+ * close can promote S_DEL_ON_CLS to S_DEL_PENDING and unlink the file.
+ *
+ * Return:     true if a disconnected durable handle was closed.
+ */
+bool ksmbd_close_disconnected_durable_delete_on_close(struct dentry *dentry)
+{
+       struct ksmbd_inode *ci;
+       struct ksmbd_file *fp, *tmp;
+       LIST_HEAD(dispose);
+       bool closed = false;
+
+       ci = ksmbd_inode_lookup_lock(dentry);
+       if (!ci)
+               return false;
+
+       down_write(&ci->m_lock);
+       if (ci->m_flags & (S_DEL_ON_CLS | S_DEL_ON_CLS_STREAM | S_DEL_PENDING)) {
+               list_for_each_entry_safe(fp, tmp, &ci->m_fp_list, node) {
+                       if (fp->conn || !fp->is_durable ||
+                           fp->f_state != FP_INITED)
+                               continue;
+                       list_move_tail(&fp->node, &dispose);
+               }
+       }
+       up_write(&ci->m_lock);
+
+       /*
+        * 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.
+        */
+       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;
+       }
+
+       return closed;
+}
+
 static struct ksmbd_file *ksmbd_fp_get(struct ksmbd_file *fp)
 {
        if (fp->f_state != FP_INITED)
index a3a9fda6de91774cbc36cd640d8fee16ca0a3578..21c24956c7c2a4ed2b54320cd76d565ea3076cf3 100644 (file)
@@ -160,6 +160,7 @@ struct ksmbd_file *ksmbd_lookup_fd_slow(struct ksmbd_work *work, u64 id,
 void ksmbd_fd_put(struct ksmbd_work *work, struct ksmbd_file *fp);
 struct ksmbd_inode *ksmbd_inode_lookup_lock(struct dentry *d);
 void ksmbd_inode_put(struct ksmbd_inode *ci);
+bool ksmbd_close_disconnected_durable_delete_on_close(struct dentry *dentry);
 struct ksmbd_file *ksmbd_lookup_global_fd(unsigned long long id);
 struct ksmbd_file *ksmbd_lookup_durable_fd(unsigned long long id);
 void ksmbd_put_durable_fd(struct ksmbd_file *fp);