Linux CIFS filesystem development
 help / color / mirror / Atom feed
From: Namjae Jeon <linkinjeon@kernel.org>
To: linux-cifs@vger.kernel.org
Cc: smfrench@gmail.com, senozhatsky@chromium.org, tom@talpey.com,
	atteh.mailbox@gmail.com, Namjae Jeon <linkinjeon@kernel.org>
Subject: [PATCH 2/7] ksmbd: add SMB3 request replay support
Date: Thu, 16 Jul 2026 18:57:05 +0900	[thread overview]
Message-ID: <20260716095711.6228-2-linkinjeon@kernel.org> (raw)
In-Reply-To: <20260716095711.6228-1-linkinjeon@kernel.org>

SMB3 clients can replay selected requests after a channel disconnect by
setting SMB2_FLAGS_REPLAY_OPERATION. The command sequence window rejects
duplicate MessageIds on one connection, but it does not validate requests
resent on another channel with a new MessageId.

Add the state and validation required to replay durable CREATE and
file-handle operations:

- track each open ChannelSequence, outstanding request counts, and lock
  sequence entries;
- retain a request-owned open reference until the common response path
  completes ChannelSequence accounting;
- replay DurableHandleReqV2 CREATE requests by CreateGuid, validating the
  durable state, SecurityContext, session, lease key, and persistent flag;
- publish CreateGuid and SecurityContext before an oplock or lease break can
  defer CREATE, rejecting replays of that pending CREATE with
  STATUS_FILE_NOT_AVAILABLE;
- retain the original CREATE action and replay completed CreateGuid requests,
  including requests that did not receive a durable-handle grant, without
  modifying the existing open;
- make replayed oplock and lease break acknowledgements idempotent; and
- preserve SMB2_FLAGS_REPLAY_OPERATION in responses.

Return STATUS_FILE_NOT_AVAILABLE when ChannelSequence validation rejects a
replayed WRITE, IOCTL, or SET_INFO request.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/ksmbd_work.c        |   2 +
 fs/smb/server/ksmbd_work.h        |   9 +
 fs/smb/server/mgmt/user_session.c |   8 -
 fs/smb/server/oplock.c            |  38 ++-
 fs/smb/server/oplock.h            |   2 +-
 fs/smb/server/server.c            |   9 +-
 fs/smb/server/smb2pdu.c           | 525 ++++++++++++++++++++++++++++--
 fs/smb/server/smb2pdu.h           |   1 +
 fs/smb/server/vfs_cache.c         |  13 +-
 fs/smb/server/vfs_cache.h         |  18 +
 10 files changed, 586 insertions(+), 39 deletions(-)

diff --git a/fs/smb/server/ksmbd_work.c b/fs/smb/server/ksmbd_work.c
index c3f8915c3952..f35335307670 100644
--- a/fs/smb/server/ksmbd_work.c
+++ b/fs/smb/server/ksmbd_work.c
@@ -11,6 +11,7 @@
 #include "server.h"
 #include "connection.h"
 #include "ksmbd_work.h"
+#include "vfs_cache.h"
 #include "mgmt/ksmbd_ida.h"
 
 static struct kmem_cache *work_cache;
@@ -88,6 +89,7 @@ void ksmbd_free_work_struct(struct ksmbd_work *work)
 		ksmbd_release_id(&work->conn->async_ida, work->async_id);
 	if (work->owns_conn_ref)
 		ksmbd_conn_put(work->conn);
+	ksmbd_fd_put(work, work->request_open);
 	kmem_cache_free(work_cache, work);
 }
 
diff --git a/fs/smb/server/ksmbd_work.h b/fs/smb/server/ksmbd_work.h
index e35a40d764d6..52d0c4dee65c 100644
--- a/fs/smb/server/ksmbd_work.h
+++ b/fs/smb/server/ksmbd_work.h
@@ -12,6 +12,7 @@
 struct ksmbd_conn;
 struct ksmbd_session;
 struct ksmbd_tree_connect;
+struct ksmbd_file;
 
 #define KSMBD_WORK_INLINE_IOVS	4
 
@@ -93,6 +94,7 @@ struct ksmbd_work {
 	/* Work owns a reference to @conn. */
 	bool				owns_conn_ref:1;
 	bool                            need_invalidate_rkey:1;
+	bool				request_open_chseq_tracked:1;
 
 	unsigned int                    remote_key;
 	/* cancel works */
@@ -100,6 +102,13 @@ struct ksmbd_work {
 	void                            **cancel_argv;
 	void                            (*cancel_fn)(void **argv);
 
+	/*
+	 * Refcounted open associated with the SMB2 command currently being
+	 * processed.
+	 */
+	struct ksmbd_file		*request_open;
+	__le16				request_open_chseq;
+
 	struct work_struct              work;
 	/* List head at conn->requests */
 	struct list_head                request_entry;
diff --git a/fs/smb/server/mgmt/user_session.c b/fs/smb/server/mgmt/user_session.c
index 5b9bd46ff3a8..cbe00f00f3f6 100644
--- a/fs/smb/server/mgmt/user_session.c
+++ b/fs/smb/server/mgmt/user_session.c
@@ -478,14 +478,6 @@ void ksmbd_sessions_deregister(struct ksmbd_conn *conn)
 
 	down_write(&conn->session_lock);
 	xa_for_each(&conn->sessions, id, sess) {
-		unsigned long chann_id;
-		struct channel *chann;
-
-		xa_for_each(&sess->ksmbd_chann_list, chann_id, chann) {
-			if (chann->conn != conn)
-				ksmbd_conn_set_exiting(chann->conn);
-		}
-
 		ksmbd_chann_del(conn, sess);
 		if (xa_empty(&sess->ksmbd_chann_list)) {
 			xa_erase(&conn->sessions, sess->id);
diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c
index 74bd2fadc757..1949bb794b5e 100644
--- a/fs/smb/server/oplock.c
+++ b/fs/smb/server/oplock.c
@@ -1203,6 +1203,18 @@ static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level,
 		if (brk_opinfo->level == SMB2_OPLOCK_LEVEL_BATCH ||
 		    brk_opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE)
 			brk_opinfo->op_state = OPLOCK_ACK_WAIT;
+
+		/*
+		 * Keep a conflicting CREATE asynchronous while waiting for an
+		 * oplock-break acknowledgement.  Besides avoiding a blocked client
+		 * request, this lets a replay arrive while the original CREATE is
+		 * still pending and be rejected with FILE_NOT_AVAILABLE.
+		 */
+		if (in_work) {
+			setup_async_work(in_work, NULL, NULL);
+			smb2_send_interim_resp(in_work, STATUS_PENDING);
+			release_async_work(in_work);
+		}
 	}
 
 	err = smb2_oplock_break_noti(brk_opinfo);
@@ -1450,7 +1462,7 @@ void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp)
  */
 int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
 		     struct ksmbd_file *fp, __u16 tid,
-		     struct lease_ctx_info *lctx, int share_ret)
+		     struct lease_ctx_info *lctx, int share_ret, bool replay)
 {
 	int err = 0;
 	int break_level = SMB2_OPLOCK_LEVEL_II;
@@ -1535,6 +1547,21 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
 	prev_op_has_lease = prev_opinfo->is_lease;
 	if (prev_op_has_lease)
 		prev_op_state = prev_opinfo->o_lease->state;
+	/*
+	 * A replay received while this open is waiting for an oplock or lease
+	 * break must not observe an intermediate level and proceed as a new
+	 * open. This check has to precede break_needed. an oplock may already
+	 * have been downgraded from Batch to II while its acknowledgement is
+	 * still pending.
+	 */
+	if (replay &&
+	    (test_bit(0, &prev_opinfo->pending_break) ||
+	     prev_opinfo->op_state == OPLOCK_ACK_WAIT)) {
+		err = -EINPROGRESS;
+		opinfo_put(prev_opinfo);
+		goto err_out;
+	}
+
 	if (share_ret < 0 &&
 	    prev_opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE) {
 		err = share_ret;
@@ -1569,7 +1596,14 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
 		goto set_lev;
 	}
 	if (err == -ENOENT) {
-		if (req_op_level != SMB2_OPLOCK_LEVEL_NONE)
+		/*
+		 * A pending durable CREATE can lose the previous oplock when
+		 * its holder closes the file. In that case grant the original
+		 * request its full caching state. Other opens still need the
+		 * normal shared-open downgrade below.
+		 */
+		if (!prev_durable_open &&
+		    req_op_level != SMB2_OPLOCK_LEVEL_NONE)
 			req_op_level = SMB2_OPLOCK_LEVEL_II;
 		goto set_lev;
 	}
diff --git a/fs/smb/server/oplock.h b/fs/smb/server/oplock.h
index ee1550f5c177..23274b645ede 100644
--- a/fs/smb/server/oplock.h
+++ b/fs/smb/server/oplock.h
@@ -96,7 +96,7 @@ struct oplock_break_info {
 
 int smb_grant_oplock(struct ksmbd_work *work, int req_op_level,
 		     u64 pid, struct ksmbd_file *fp, __u16 tid,
-		     struct lease_ctx_info *lctx, int share_ret);
+		     struct lease_ctx_info *lctx, int share_ret, bool replay);
 void smb_break_all_levII_oplock(struct ksmbd_work *work,
 					struct ksmbd_file *fp, int is_trunc);
 void smb_break_all_levII_oplock_rename(struct ksmbd_work *work, struct ksmbd_file *fp);
diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c
index f5baba934840..b0e9689fd3c8 100644
--- a/fs/smb/server/server.c
+++ b/fs/smb/server/server.c
@@ -15,6 +15,7 @@
 
 #include "server.h"
 #include "smb_common.h"
+#include "smb2pdu.h"
 #include "../common/smb2status.h"
 #include "connection.h"
 #include "transport_ipc.h"
@@ -221,8 +222,10 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
 		}
 
 		rc = __process_request(work, conn, &command);
-		if (rc == SERVER_HANDLER_ABORT)
+		if (rc == SERVER_HANDLER_ABORT) {
+			smb2_complete_request_open(work);
 			break;
+		}
 
 		/*
 		 * Call smb2_set_rsp_credits() function to set number of credits
@@ -235,10 +238,13 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
 			if (rc < 0) {
 				conn->ops->set_rsp_status(work,
 					STATUS_INVALID_PARAMETER);
+				smb2_complete_request_open(work);
 				goto send;
 			}
 		}
 
+		smb2_complete_request_open(work);
+
 		is_chained = is_chained_smb2_message(work);
 
 		if (work->sess &&
@@ -254,6 +260,7 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
 	} while (is_chained == true);
 
 send:
+	smb2_complete_request_open(work);
 	/*
 	 * Release any credit charge still outstanding for this request.  On
 	 * the normal path smb2_set_rsp_credits() already returned it, but the
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index e09c60191dc2..602b60417174 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -570,6 +570,8 @@ static void init_chained_smb2_rsp(struct ksmbd_work *work)
 	 */
 	rsp_hdr->Flags = (SMB2_FLAGS_SERVER_TO_REDIR |
 				SMB2_FLAGS_RELATED_OPERATIONS);
+	if (rcv_hdr->Flags & SMB2_FLAGS_REPLAY_OPERATION)
+		rsp_hdr->Flags |= SMB2_FLAGS_REPLAY_OPERATION;
 	rsp_hdr->NextCommand = 0;
 	rsp_hdr->MessageId = rcv_hdr->MessageId;
 	rsp_hdr->Id.SyncId.ProcessId = rcv_hdr->Id.SyncId.ProcessId;
@@ -662,6 +664,8 @@ int init_smb2_rsp_hdr(struct ksmbd_work *work)
 	 * Message is response. We don't grant oplock yet.
 	 */
 	rsp_hdr->Flags = (SMB2_FLAGS_SERVER_TO_REDIR);
+	if (rcv_hdr->Flags & SMB2_FLAGS_REPLAY_OPERATION)
+		rsp_hdr->Flags |= SMB2_FLAGS_REPLAY_OPERATION;
 	rsp_hdr->NextCommand = 0;
 	rsp_hdr->MessageId = rcv_hdr->MessageId;
 	rsp_hdr->Id.SyncId.ProcessId = rcv_hdr->Id.SyncId.ProcessId;
@@ -672,6 +676,185 @@ int init_smb2_rsp_hdr(struct ksmbd_work *work)
 	return 0;
 }
 
+static __le16 smb3_hdr_channel_sequence(struct smb2_hdr *hdr)
+{
+	return ((struct smb3_hdr_req *)hdr)->ChannelSequence;
+}
+
+static bool smb3_hdr_replay(struct smb2_hdr *hdr)
+{
+	return hdr->Flags & SMB2_FLAGS_REPLAY_OPERATION;
+}
+
+static int smb3_verify_channel_sequence(struct ksmbd_work *work,
+					struct ksmbd_file *fp,
+					struct smb2_hdr *hdr,
+					bool allow_stale)
+{
+	__le16 chseq_le;
+	u16 chseq, old_chseq;
+	int ret = 0;
+
+	if (work->conn->dialect < SMB30_PROT_ID)
+		return 0;
+
+	chseq_le = smb3_hdr_channel_sequence(hdr);
+	chseq = le16_to_cpu(chseq_le);
+
+	spin_lock(&fp->f_lock);
+	old_chseq = le16_to_cpu(fp->channel_sequence);
+	if (smb3_hdr_replay(hdr)) {
+		if (chseq == old_chseq && fp->outstanding_pre_requests == 0) {
+			fp->outstanding_requests++;
+		} else if ((u16)(chseq - old_chseq) <= 0x7fff &&
+			   fp->outstanding_pre_requests == 0) {
+			fp->outstanding_pre_requests += fp->outstanding_requests;
+			fp->outstanding_requests = 1;
+			fp->channel_sequence = chseq_le;
+		} else if (allow_stale) {
+			fp->outstanding_pre_requests++;
+		} else {
+			ret = -EAGAIN;
+		}
+	} else {
+		if (chseq == old_chseq) {
+			fp->outstanding_requests++;
+		} else if ((u16)(chseq - old_chseq) <= 0x7fff) {
+			fp->outstanding_pre_requests += fp->outstanding_requests;
+			fp->outstanding_requests = 1;
+			fp->channel_sequence = chseq_le;
+		} else if (allow_stale) {
+			fp->outstanding_pre_requests++;
+		} else {
+			ret = -EAGAIN;
+		}
+	}
+	spin_unlock(&fp->f_lock);
+
+	return ret;
+}
+
+static void smb3_complete_channel_sequence(struct ksmbd_work *work,
+					   struct ksmbd_file *fp,
+					   __le16 chseq_le)
+{
+	u16 chseq;
+
+	if (work->conn->dialect < SMB30_PROT_ID)
+		return;
+
+	chseq = le16_to_cpu(chseq_le);
+
+	spin_lock(&fp->f_lock);
+	if (chseq == le16_to_cpu(fp->channel_sequence)) {
+		if (fp->outstanding_requests)
+			fp->outstanding_requests--;
+	} else {
+		if (fp->outstanding_pre_requests)
+			fp->outstanding_pre_requests--;
+	}
+	spin_unlock(&fp->f_lock);
+}
+
+static int smb2_set_request_open(struct ksmbd_work *work, struct ksmbd_file *fp,
+				 struct smb2_hdr *hdr, bool verify_chseq,
+				 bool allow_stale_chseq)
+{
+	struct ksmbd_file *open;
+	int ret;
+
+	smb2_complete_request_open(work);
+
+	open = ksmbd_file_get(fp);
+	if (!open)
+		return -ESTALE;
+
+	if (verify_chseq) {
+		ret = smb3_verify_channel_sequence(work, fp, hdr,
+						   allow_stale_chseq);
+		if (ret) {
+			ksmbd_fd_put(work, open);
+			return ret;
+		}
+		work->request_open_chseq_tracked = true;
+	}
+
+	work->request_open = open;
+	work->request_open_chseq = smb3_hdr_channel_sequence(hdr);
+	return 0;
+}
+
+void smb2_complete_request_open(struct ksmbd_work *work)
+{
+	struct ksmbd_file *open = work->request_open;
+
+	if (!open)
+		return;
+
+	if (work->request_open_chseq_tracked)
+		smb3_complete_channel_sequence(work, open,
+					       work->request_open_chseq);
+
+	work->request_open = NULL;
+	work->request_open_chseq_tracked = false;
+	ksmbd_fd_put(work, open);
+}
+
+static bool smb2_lock_sequence_applicable(struct ksmbd_work *work,
+					  struct ksmbd_file *fp)
+{
+	return fp->is_resilient || fp->is_durable || fp->is_persistent ||
+	       (work->conn->dialect >= SMB30_PROT_ID &&
+		(work->conn->cli_cap & SMB2_GLOBAL_CAP_MULTI_CHANNEL));
+}
+
+static void smb2_verify_lock_sequence(struct ksmbd_work *work,
+				      struct ksmbd_file *fp,
+				      struct smb2_lock_req *req)
+{
+	u32 val, index;
+	u8 sequence;
+
+	if (work->conn->dialect == SMB20_PROT_ID ||
+	    !smb2_lock_sequence_applicable(work, fp))
+		return;
+
+	val = le32_to_cpu(req->LockSequenceNumber);
+	sequence = val & 0xf;
+	index = val >> 4;
+	if (!index || index > KSMBD_LOCK_SEQ_ARRAY_SIZE)
+		return;
+
+	spin_lock(&fp->f_lock);
+	if (fp->lock_seq[index - 1].valid &&
+	    fp->lock_seq[index - 1].sequence != sequence)
+		fp->lock_seq[index - 1].valid = false;
+	spin_unlock(&fp->f_lock);
+}
+
+static void smb2_update_lock_sequence(struct ksmbd_work *work,
+				      struct ksmbd_file *fp,
+				      struct smb2_lock_req *req)
+{
+	u32 val, index;
+	u8 sequence;
+
+	if (work->conn->dialect == SMB20_PROT_ID ||
+	    !smb2_lock_sequence_applicable(work, fp))
+		return;
+
+	val = le32_to_cpu(req->LockSequenceNumber);
+	sequence = val & 0xf;
+	index = val >> 4;
+	if (!index || index > KSMBD_LOCK_SEQ_ARRAY_SIZE)
+		return;
+
+	spin_lock(&fp->f_lock);
+	fp->lock_seq[index - 1].valid = true;
+	fp->lock_seq[index - 1].sequence = sequence;
+	spin_unlock(&fp->f_lock);
+}
+
 /**
  * smb2_allocate_rsp_buf() - allocate smb2 response buffer
  * @work:	smb work containing smb request buffer
@@ -3164,12 +3347,86 @@ struct durable_info {
 	unsigned short int type;
 	bool persistent;
 	bool reconnected;
+	bool replay;
+	bool replay_consumed;
 	bool app_instance_id;
 	unsigned int timeout;
 	char *CreateGuid;
 	char AppInstanceId[SMB2_CREATE_GUID_SIZE];
 };
 
+static int smb2_check_durable_replay(struct ksmbd_work *work,
+				     struct ksmbd_file *fp,
+				     struct lease_ctx_info *lc,
+				     bool persistent)
+{
+	struct oplock_info *opinfo;
+	int ret = 0;
+
+	if (!fp->is_durable && !fp->is_persistent)
+		return -EACCES;
+
+	if (ksmbd_vfs_compare_durable_owner(fp, work->sess->user) == false)
+		return -EACCES;
+
+	if (fp->is_persistent && !persistent)
+		return -EINVAL;
+
+	opinfo = opinfo_get(fp);
+	if (!opinfo)
+		return 0;
+
+	if (opinfo->sess && opinfo->sess->id != work->sess->id) {
+		ret = -ENOEXEC;
+		goto out;
+	}
+
+	if (opinfo->is_lease) {
+		if (!lc ||
+		    memcmp(opinfo->o_lease->lease_key, lc->lease_key,
+			   SMB2_LEASE_KEY_SIZE)) {
+			ret = -EACCES;
+			goto out;
+		}
+	} else {
+		if (lc) {
+			ret = -EACCES;
+			goto out;
+		}
+
+		if (fp->is_durable && opinfo->level != SMB2_OPLOCK_LEVEL_BATCH)
+			ret = -EACCES;
+	}
+out:
+	opinfo_put(opinfo);
+	return ret;
+}
+
+static bool smb2_durable_replay_consumed(struct ksmbd_file *fp)
+{
+	bool consumed;
+
+	spin_lock(&fp->f_lock);
+	consumed = fp->durable_replay_consumed;
+	spin_unlock(&fp->f_lock);
+
+	return consumed;
+}
+
+static void smb2_mark_durable_replay_consumed(struct ksmbd_file *fp)
+{
+	spin_lock(&fp->f_lock);
+	fp->durable_replay_consumed = true;
+	spin_unlock(&fp->f_lock);
+}
+
+static bool smb2_durable_replay_differs(struct ksmbd_file *fp,
+					struct smb2_create_req *req)
+{
+	return fp->cdoption != req->CreateDisposition ||
+		fp->create_file_attributes != req->FileAttributes;
+}
+
 static int parse_durable_handle_context(struct ksmbd_work *work,
 					struct smb2_create_req *req,
 					struct lease_ctx_info *lc,
@@ -3297,6 +3554,10 @@ static int parse_durable_handle_context(struct ksmbd_work *work,
 			durable_v2_blob =
 				(struct create_durable_req_v2 *)context;
 			ksmbd_debug(SMB, "Request for durable v2 open\n");
+			dh_info->CreateGuid = durable_v2_blob->dcontext.CreateGuid;
+			dh_info->persistent =
+				le32_to_cpu(durable_v2_blob->dcontext.Flags) &
+				SMB2_DHANDLE_FLAG_PERSISTENT;
 			dh_info->fp = ksmbd_lookup_fd_cguid(durable_v2_blob->dcontext.CreateGuid);
 			if (dh_info->fp) {
 				if (!memcmp(conn->ClientGUID, dh_info->fp->client_guid,
@@ -3307,12 +3568,73 @@ static int parse_durable_handle_context(struct ksmbd_work *work,
 						goto out;
 					}
 
-					if (dh_info->fp->conn) {
+					if (dh_info->fp->f_state == FP_NEW) {
+						/* Original CREATE is still pending. */
+						ksmbd_put_durable_fd(dh_info->fp);
+						err = -EAGAIN;
+						goto out;
+					}
+
+					if (!dh_info->fp->is_durable &&
+					    !dh_info->fp->is_persistent) {
+						/*
+						 * A DurableHandleReqV2 CREATE can complete
+						 * without granting durability (for example, if
+						 * it requested no oplock).  Its CreateGuid still
+						 * identifies a completed CREATE for replay.
+						 */
+						if (dh_info->fp->conn &&
+						    ksmbd_vfs_compare_durable_owner(
+							    dh_info->fp, work->sess->user)) {
+							if (smb2_durable_replay_consumed(
+								    dh_info->fp)) {
+								ksmbd_put_durable_fd(dh_info->fp);
+								dh_info->fp = NULL;
+								dh_info->type = dh_idx;
+								dh_info->replay_consumed = true;
+								break;
+							}
+							if (smb2_durable_replay_differs(
+								    dh_info->fp, req))
+								smb2_mark_durable_replay_consumed(
+									dh_info->fp);
+							dh_info->replay = true;
+							dh_info->type = dh_idx;
+							goto out;
+						}
+						ksmbd_put_durable_fd(dh_info->fp);
+						err = -EACCES;
+						goto out;
+					}
+
+					if (dh_info->fp->conn &&
+					    smb2_durable_replay_consumed(dh_info->fp)) {
+						ksmbd_put_durable_fd(dh_info->fp);
+						dh_info->fp = NULL;
+						dh_info->type = dh_idx;
+						dh_info->replay_consumed = true;
+						break;
+					}
+
+					err = smb2_check_durable_replay(work,
+									dh_info->fp,
+									lc,
+									dh_info->persistent);
+					if (err) {
 						ksmbd_put_durable_fd(dh_info->fp);
-						err = -EBADF;
 						goto out;
 					}
-					dh_info->reconnected = true;
+
+					if (dh_info->fp->conn) {
+						if (smb2_durable_replay_differs(dh_info->fp,
+									       req))
+							smb2_mark_durable_replay_consumed(
+									dh_info->fp);
+						dh_info->replay = true;
+					} else {
+						dh_info->reconnected = true;
+					}
+					dh_info->type = dh_idx;
 					goto out;
 				}
 				ksmbd_put_durable_fd(dh_info->fp);
@@ -3321,10 +3643,6 @@ static int parse_durable_handle_context(struct ksmbd_work *work,
 
 			if ((lc && (lc->req_state & SMB2_LEASE_HANDLE_CACHING_LE)) ||
 			    req_op_level == SMB2_OPLOCK_LEVEL_BATCH) {
-				dh_info->CreateGuid =
-					durable_v2_blob->dcontext.CreateGuid;
-				dh_info->persistent =
-					le32_to_cpu(durable_v2_blob->dcontext.Flags);
 				dh_info->timeout =
 					le32_to_cpu(durable_v2_blob->dcontext.Timeout);
 				dh_info->type = dh_idx;
@@ -3407,6 +3725,7 @@ int smb2_open(struct ksmbd_work *work)
 	int contxt_cnt = 0, query_disk_id = 0;
 	bool maximal_access_ctxt = false, posix_ctxt = false;
 	bool aapl_ctxt = false;
+	bool durable_rsp = true;
 	__u64 aapl_req_bitmap = 0, aapl_client_caps = 0;
 	int s_type = 0;
 	int next_off = 0;
@@ -3526,6 +3845,21 @@ int smb2_open(struct ksmbd_work *work)
 		if (rc)
 			goto err_out2;
 
+		if (dh_info.replay == true) {
+			fp = dh_info.fp;
+			if (ksmbd_override_fsids(work)) {
+				rc = -ENOMEM;
+				goto err_out2;
+			}
+
+			file_info = FILE_OPENED;
+			rc = ksmbd_vfs_getattr(&fp->filp->f_path, &stat);
+			if (rc)
+				goto err_out2;
+
+			goto reconnected_fp;
+		}
+
 		if (dh_info.reconnected == true) {
 			rc = smb2_check_durable_oplock(conn, share, dh_info.fp,
 					lc, sess->user, name);
@@ -3950,7 +4284,23 @@ int smb2_open(struct ksmbd_work *work)
 		goto err_out;
 	}
 
+	/*
+	 * Publish the client and create GUID before an oplock/lease break can
+	 * make this CREATE pending.  A replay of that in-flight CREATE must find
+	 * this FP_NEW handle and fail with STATUS_FILE_NOT_AVAILABLE instead of
+	 * waiting on the same break again.
+	 */
+	memcpy(fp->client_guid, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE);
+	if (dh_info.CreateGuid) {
+		memcpy(fp->create_guid, dh_info.CreateGuid, SMB2_CREATE_GUID_SIZE);
+		fp->durable_replay_consumed = dh_info.replay_consumed;
+		rc = ksmbd_vfs_set_durable_owner(fp, sess->user);
+		if (rc)
+			goto err_out;
+	}
+
 	fp->cdoption = req->CreateDisposition;
+	fp->create_file_attributes = req->FileAttributes;
 	fp->daccess = daccess;
 	fp->saccess = req->ShareAccess;
 	fp->coption = req->CreateOptions;
@@ -4118,7 +4468,8 @@ int smb2_open(struct ksmbd_work *work)
 		rc = smb_grant_oplock(work, req_op_level,
 				      fp->persistent_id, fp,
 				      le32_to_cpu(req->hdr.Id.SyncId.TreeId),
-				      lc, share_ret);
+				      lc, share_ret,
+				      smb3_hdr_replay(&req->hdr));
 		if (rc < 0)
 			goto err_out1;
 	}
@@ -4262,7 +4613,7 @@ int smb2_open(struct ksmbd_work *work)
 	if (created)
 		smb2_new_xattrs(tcon, &path, fp);
 
-	memcpy(fp->client_guid, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE);
+	fp->create_action = cpu_to_le32(file_info);
 
 	if (dh_info.type == DURABLE_REQ_V2 || dh_info.type == DURABLE_REQ) {
 		if (dh_info.type == DURABLE_REQ_V2 && dh_info.persistent &&
@@ -4271,10 +4622,7 @@ int smb2_open(struct ksmbd_work *work)
 			fp->is_persistent = true;
 		else
 			fp->is_durable = true;
-
 		if (dh_info.type == DURABLE_REQ_V2) {
-			memcpy(fp->create_guid, dh_info.CreateGuid,
-					SMB2_CREATE_GUID_SIZE);
 			if (dh_info.app_instance_id)
 				memcpy(fp->app_instance_id,
 				       dh_info.AppInstanceId,
@@ -4295,10 +4643,22 @@ int smb2_open(struct ksmbd_work *work)
 	 * cares, it sends its own AAPL context on this same CREATE, which
 	 * this function's normal (non-reconnect) parsing already handles.
 	 */
-reconnected_fp:
+	reconnected_fp:
+	if (dh_info.replay)
+		file_info = le32_to_cpu(fp->create_action);
 	rsp->StructureSize = cpu_to_le16(89);
 	opinfo = opinfo_get(fp);
 	rsp->OplockLevel = opinfo != NULL ? opinfo->level : 0;
+	/*
+	 * A durable CREATE replay does not modify the existing open. When
+	 * replayed without an oplock, however, its response reflects that
+	 * request and cannot include a new durable-handle response context.
+	 */
+	if (dh_info.replay && !lc &&
+	    req_op_level == SMB2_OPLOCK_LEVEL_NONE) {
+		rsp->OplockLevel = SMB2_OPLOCK_LEVEL_NONE;
+		durable_rsp = false;
+	}
 	rsp->Flags = 0;
 	rsp->CreateAction = cpu_to_le32(file_info);
 	rsp->CreationTime = cpu_to_le64(fp->create_time);
@@ -4399,7 +4759,8 @@ int smb2_open(struct ksmbd_work *work)
 		next_off = conn->vals->create_disk_id_size;
 	}
 
-	if (dh_info.type == DURABLE_REQ || dh_info.type == DURABLE_REQ_V2) {
+	if (durable_rsp &&
+	    (dh_info.type == DURABLE_REQ || dh_info.type == DURABLE_REQ_V2)) {
 		struct create_context *durable_ccontext;
 
 		durable_ccontext = (struct create_context *)(rsp->Buffer +
@@ -4490,8 +4851,11 @@ int smb2_open(struct ksmbd_work *work)
 
 err_out2:
 	if (!rc) {
-		rc = ksmbd_update_fstate(&work->sess->file_table, fp,
-					 FP_INITED);
+		if (!dh_info.replay)
+			rc = ksmbd_update_fstate(&work->sess->file_table, fp,
+						 FP_INITED);
+		if (!rc)
+			rc = smb2_set_request_open(work, fp, &req->hdr, false, false);
 		if (!rc)
 			rc = ksmbd_iov_pin_rsp(work, (void *)rsp, iov_len);
 	}
@@ -4523,15 +4887,22 @@ int smb2_open(struct ksmbd_work *work)
 			rsp->hdr.Status = STATUS_OBJECT_NAME_COLLISION;
 		else if (rc == -EMFILE)
 			rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
+		else if (rc == -EINPROGRESS)
+			rsp->hdr.Status = STATUS_FILE_NOT_AVAILABLE;
+		else if (rc == -EAGAIN)
+			rsp->hdr.Status = STATUS_FILE_NOT_AVAILABLE;
 		if (!rsp->hdr.Status)
 			rsp->hdr.Status = STATUS_UNEXPECTED_IO_ERROR;
 
-		if (fp)
+		if (fp && !dh_info.replay)
 			ksmbd_fd_put(work, fp);
 		smb2_set_err_rsp(work);
 		ksmbd_debug(SMB, "Error response: %x\n", rsp->hdr.Status);
 	}
 
+	if (dh_info.replay)
+		ksmbd_put_durable_fd(dh_info.fp);
+
 	if (dh_info.reconnected) {
 		/*
 		 * If reconnect succeeded, fp was republished in the
@@ -7669,6 +8040,7 @@ int smb2_set_info(struct ksmbd_work *work)
 	struct smb2_set_info_rsp *rsp;
 	struct ksmbd_file *fp = NULL;
 	int rc = 0;
+	bool chseq_err = false;
 	unsigned int id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
 
 	ksmbd_debug(SMB, "Received smb2 set info request\n");
@@ -7708,6 +8080,13 @@ int smb2_set_info(struct ksmbd_work *work)
 		goto err_out;
 	}
 
+	rc = smb2_set_request_open(work, fp, &req->hdr, true, false);
+	if (rc) {
+		rsp->hdr.Status = STATUS_FILE_NOT_AVAILABLE;
+		chseq_err = true;
+		goto err_out;
+	}
+
 	saved_cred = override_creds(fp->filp->f_cred);
 	switch (req->InfoType) {
 	case SMB2_O_INFO_FILE:
@@ -7758,7 +8137,7 @@ int smb2_set_info(struct ksmbd_work *work)
 		rsp->hdr.Status = STATUS_OBJECT_NAME_INVALID;
 	else if (rc == -EBUSY || rc == -ENOTEMPTY)
 		rsp->hdr.Status = STATUS_DIRECTORY_NOT_EMPTY;
-	else if (rc == -EAGAIN)
+	else if (rc == -EAGAIN && !chseq_err)
 		rsp->hdr.Status = STATUS_FILE_LOCK_CONFLICT;
 	else if (rc == -EBADF || rc == -ESTALE)
 		rsp->hdr.Status = STATUS_INVALID_HANDLE;
@@ -7967,6 +8346,10 @@ int smb2_read(struct ksmbd_work *work)
 		goto out;
 	}
 
+	err = smb2_set_request_open(work, fp, &req->hdr, true, true);
+	if (err)
+		goto out;
+
 	if (!(fp->daccess & (FILE_READ_DATA_LE | FILE_READ_ATTRIBUTES_LE))) {
 		pr_err("Not permitted to read : 0x%x\n", fp->daccess);
 		err = -EACCES;
@@ -8214,6 +8597,7 @@ int smb2_write(struct ksmbd_work *work)
 	char *data_buf;
 	bool writethrough = false, is_rdma_channel = false;
 	bool async_interim = false;
+	bool chseq_err = false;
 	int err = 0;
 	unsigned int max_write_size = work->conn->vals->max_write_size;
 	unsigned int id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
@@ -8303,6 +8687,13 @@ int smb2_write(struct ksmbd_work *work)
 		goto out;
 	}
 
+	err = smb2_set_request_open(work, fp, &req->hdr, true, false);
+	if (err) {
+		rsp->hdr.Status = STATUS_FILE_NOT_AVAILABLE;
+		chseq_err = true;
+		goto out;
+	}
+
 	if (!(fp->daccess & (FILE_WRITE_DATA_LE | FILE_READ_ATTRIBUTES_LE))) {
 		pr_err("Not permitted to write : 0x%x\n", fp->daccess);
 		err = -EACCES;
@@ -8374,7 +8765,7 @@ int smb2_write(struct ksmbd_work *work)
 	if (async_interim)
 		release_async_work(work);
 
-	if (err == -EAGAIN)
+	if (err == -EAGAIN && !chseq_err)
 		rsp->hdr.Status = STATUS_FILE_LOCK_CONFLICT;
 	else if (err == -ENOSPC || err == -EFBIG)
 		rsp->hdr.Status = STATUS_DISK_FULL;
@@ -8386,7 +8777,7 @@ int smb2_write(struct ksmbd_work *work)
 		rsp->hdr.Status = STATUS_SHARING_VIOLATION;
 	else if (err == -EINVAL)
 		rsp->hdr.Status = STATUS_INVALID_PARAMETER;
-	else
+	else if (rsp->hdr.Status == 0)
 		rsp->hdr.Status = STATUS_INVALID_HANDLE;
 
 	smb2_set_err_rsp(work);
@@ -8675,6 +9066,12 @@ int smb2_lock(struct ksmbd_work *work)
 		goto out2;
 	}
 
+	err = smb2_set_request_open(work, fp, &req->hdr, false, false);
+	if (err)
+		goto out2;
+
+	smb2_verify_lock_sequence(work, fp, req);
+
 	filp = fp->filp;
 	lock_count = le16_to_cpu(req->LockCount);
 	lock_ele = req->locks;
@@ -8959,6 +9356,7 @@ int smb2_lock(struct ksmbd_work *work)
 	err = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_lock_rsp));
 	if (err)
 		goto out;
+	smb2_update_lock_sequence(work, fp, req);
 
 	ksmbd_fd_put(work, fp);
 	return 0;
@@ -9509,10 +9907,12 @@ int smb2_ioctl(struct ksmbd_work *work)
 	struct smb2_ioctl_req *req;
 	struct smb2_ioctl_rsp *rsp;
 	unsigned int cnt_code, nbytes = 0, out_buf_len, in_buf_len;
-	u64 id = KSMBD_NO_FID;
+	u64 id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
 	struct ksmbd_conn *conn = work->conn;
 	int ret = 0;
 	char *buffer;
+	bool no_fileid_ioctl = false;
+	bool chseq_err = false;
 
 	ksmbd_debug(SMB, "Received smb2 ioctl request\n");
 
@@ -9525,14 +9925,17 @@ int smb2_ioctl(struct ksmbd_work *work)
 			ksmbd_debug(SMB, "Compound request set FID = %llu\n",
 				    work->compound_fid);
 			id = work->compound_fid;
+			pid = work->compound_pfid;
 		}
 	} else {
 		req = smb_get_msg(work->request_buf);
 		rsp = smb_get_msg(work->response_buf);
 	}
 
-	if (!has_file_id(id))
+	if (!has_file_id(id)) {
 		id = req->VolatileFileId;
+		pid = req->PersistentFileId;
+	}
 
 	if (req->Flags != cpu_to_le32(SMB2_0_IOCTL_IS_FSCTL)) {
 		ret = -EOPNOTSUPP;
@@ -9542,6 +9945,40 @@ int smb2_ioctl(struct ksmbd_work *work)
 	buffer = (char *)req + le32_to_cpu(req->InputOffset);
 
 	cnt_code = le32_to_cpu(req->CtlCode);
+	switch (cnt_code) {
+	case FSCTL_DFS_GET_REFERRALS:
+	case FSCTL_DFS_GET_REFERRALS_EX:
+	case FSCTL_QUERY_NETWORK_INTERFACE_INFO:
+	case FSCTL_VALIDATE_NEGOTIATE_INFO:
+	case FSCTL_PIPE_WAIT:
+		no_fileid_ioctl = true;
+		break;
+	default:
+		break;
+	}
+
+	if (!no_fileid_ioctl && has_file_id(id)) {
+		struct ksmbd_file *fp;
+
+		fp = ksmbd_lookup_fd_slow(work, id, pid);
+		if (!fp) {
+			if (cnt_code == FSCTL_DUPLICATE_EXTENTS_TO_FILE) {
+				rsp->hdr.Status = STATUS_FILE_CLOSED;
+				goto out2;
+			}
+			ret = -ENOENT;
+			goto out;
+		}
+
+		ret = smb2_set_request_open(work, fp, &req->hdr, true, false);
+		ksmbd_fd_put(work, fp);
+		if (ret) {
+			rsp->hdr.Status = STATUS_FILE_NOT_AVAILABLE;
+			chseq_err = true;
+			goto out;
+		}
+	}
+
 	ret = smb2_calc_max_out_buf_len(work,
 			offsetof(struct smb2_ioctl_rsp, Buffer),
 			le32_to_cpu(req->MaxOutputResponse));
@@ -10077,7 +10514,7 @@ int smb2_ioctl(struct ksmbd_work *work)
 		rsp->hdr.Status = STATUS_NOT_SUPPORTED;
 	else if (ret == -ENOSPC)
 		rsp->hdr.Status = STATUS_BUFFER_TOO_SMALL;
-	else if (ret < 0 || rsp->hdr.Status == 0)
+	else if (!chseq_err && (ret < 0 || rsp->hdr.Status == 0))
 		rsp->hdr.Status = STATUS_INVALID_PARAMETER;
 
 out2:
@@ -10117,6 +10554,14 @@ static void smb20_oplock_break_ack(struct ksmbd_work *work)
 		return;
 	}
 
+	ret = smb2_set_request_open(work, fp, &req->hdr, false, false);
+	if (ret) {
+		rsp->hdr.Status = STATUS_FILE_CLOSED;
+		smb2_set_err_rsp(work);
+		ksmbd_fd_put(work, fp);
+		return;
+	}
+
 	opinfo = opinfo_get(fp);
 	if (!opinfo) {
 		pr_err("unexpected null oplock_info\n");
@@ -10129,6 +10574,22 @@ static void smb20_oplock_break_ack(struct ksmbd_work *work)
 	if (opinfo->op_state != OPLOCK_ACK_WAIT) {
 		ksmbd_debug(SMB, "unexpected oplock state 0x%x\n",
 			    opinfo->op_state);
+		if (smb3_hdr_replay(&req->hdr) &&
+		    opinfo->op_state == OPLOCK_STATE_NONE) {
+			rsp->StructureSize = cpu_to_le16(24);
+			rsp->OplockLevel = opinfo->level;
+			rsp->Reserved = 0;
+			rsp->Reserved2 = 0;
+			rsp->VolatileFid = volatile_id;
+			rsp->PersistentFid = persistent_id;
+			ret = ksmbd_iov_pin_rsp(work, rsp,
+						 sizeof(struct smb2_oplock_break));
+			if (ret)
+				ksmbd_debug(SMB,
+					    "failed to pin replayed oplock break response: %d\n",
+					    ret);
+			goto out_no_state_change;
+		}
 		if (opinfo->level == SMB2_OPLOCK_LEVEL_NONE)
 			status = STATUS_INVALID_OPLOCK_PROTOCOL;
 		else
@@ -10197,6 +10658,7 @@ static void smb20_oplock_break_ack(struct ksmbd_work *work)
 out:
 	opinfo->op_state = OPLOCK_STATE_NONE;
 	wake_up_interruptible_all(&opinfo->oplock_q);
+out_no_state_change:
 	opinfo_put(opinfo);
 	ksmbd_fd_put(work, fp);
 }
@@ -10249,11 +10711,15 @@ static void smb21_lease_break_ack(struct ksmbd_work *work)
 	if (opinfo->op_state == OPLOCK_STATE_NONE) {
 		pr_err("unexpected lease break state 0x%x\n",
 		       opinfo->op_state);
+		if (smb3_hdr_replay(&req->hdr))
+			goto replay_rsp;
 		rsp->hdr.Status = STATUS_UNSUCCESSFUL;
 		goto err_out;
 	}
 
 	if (!atomic_read(&opinfo->breaking_cnt)) {
+		if (smb3_hdr_replay(&req->hdr))
+			goto replay_rsp;
 		rsp->hdr.Status = STATUS_UNSUCCESSFUL;
 		goto err_out;
 	}
@@ -10288,6 +10754,19 @@ static void smb21_lease_break_ack(struct ksmbd_work *work)
 	opinfo_put(opinfo);
 	return;
 
+replay_rsp:
+	rsp->StructureSize = cpu_to_le16(36);
+	rsp->Reserved = 0;
+	rsp->Flags = 0;
+	memcpy(rsp->LeaseKey, req->LeaseKey, 16);
+	rsp->LeaseState = lease->state;
+	rsp->LeaseDuration = 0;
+	ret = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_lease_ack));
+	if (ret)
+		goto err_out;
+	opinfo_put(opinfo);
+	return;
+
 err_out:
 	smb2_set_err_rsp(work);
 	opinfo_put(opinfo);
diff --git a/fs/smb/server/smb2pdu.h b/fs/smb/server/smb2pdu.h
index d9fed26dbfcd..02b0f46ac1be 100644
--- a/fs/smb/server/smb2pdu.h
+++ b/fs/smb/server/smb2pdu.h
@@ -432,6 +432,7 @@ bool smb3_encryption_negotiated(struct ksmbd_conn *conn);
 
 /* smb2 misc functions */
 int ksmbd_smb2_check_message(struct ksmbd_work *work);
+void smb2_complete_request_open(struct ksmbd_work *work);
 
 /* smb2 command handlers */
 int smb2_handle_negotiate(struct ksmbd_work *work);
diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index 55a1cc5b909c..c0dbb5ef3bcd 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -729,6 +729,11 @@ static struct ksmbd_file *ksmbd_fp_get(struct ksmbd_file *fp)
 	return fp;
 }
 
+struct ksmbd_file *ksmbd_file_get(struct ksmbd_file *fp)
+{
+	return ksmbd_fp_get(fp);
+}
+
 static struct ksmbd_file *__ksmbd_lookup_fd(struct ksmbd_file_table *ft,
 					    u64 id)
 {
@@ -1570,7 +1575,7 @@ void ksmbd_stop_durable_scavenger(void)
 }
 
 /*
- * ksmbd_vfs_copy_durable_owner - Copy owner info for durable reconnect
+ * ksmbd_vfs_set_durable_owner - Store owner info for durable replay/reconnect
  * @fp: ksmbd file pointer to store owner info
  * @user: user pointer to copy from
  *
@@ -1579,8 +1584,8 @@ void ksmbd_stop_durable_scavenger(void)
  *
  * Return: 0 on success, or negative error code on failure
  */
-static int ksmbd_vfs_copy_durable_owner(struct ksmbd_file *fp,
-		struct ksmbd_user *user)
+int ksmbd_vfs_set_durable_owner(struct ksmbd_file *fp,
+				struct ksmbd_user *user)
 {
 	char *name;
 
@@ -1651,7 +1656,7 @@ static bool session_fd_check(struct ksmbd_tree_connect *tcon,
 	if (WARN_ON_ONCE(!fp->conn))
 		return false;
 
-	if (ksmbd_vfs_copy_durable_owner(fp, user))
+	if (ksmbd_vfs_set_durable_owner(fp, user))
 		return false;
 
 	/*
diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h
index 1d9edc906b54..5aff9bb556ec 100644
--- a/fs/smb/server/vfs_cache.h
+++ b/fs/smb/server/vfs_cache.h
@@ -82,6 +82,13 @@ struct durable_owner {
 	char *name;
 };
 
+#define KSMBD_LOCK_SEQ_ARRAY_SIZE	64
+
+struct ksmbd_lock_sequence {
+	bool				valid;
+	u8				sequence;
+};
+
 struct ksmbd_file {
 	struct file			*filp;
 	u64				persistent_id;
@@ -101,6 +108,7 @@ struct ksmbd_file {
 	__le32				saccess;
 	__le32				coption;
 	__le32				cdoption;
+	__le32				create_file_attributes;
 	__u64				create_time;
 	__u64				change_time;
 	__u64				allocation_size;
@@ -128,6 +136,8 @@ struct ksmbd_file {
 
 	unsigned int			durable_timeout;
 	unsigned int			durable_scavenger_timeout;
+	/* CREATE action returned when this durable handle was established. */
+	__le32				create_action;
 
 	/* if ls is happening on directory, below is valid*/
 	struct ksmbd_readdir_data	readdir_data;
@@ -139,9 +149,14 @@ struct ksmbd_file {
 	bool				is_persistent;
 	bool				is_resilient;
 	bool				durable_reconnect_disabled;
+	bool				durable_replay_consumed;
 
 	bool                            is_posix_ctxt;
 	struct durable_owner		owner;
+	__le16				channel_sequence;
+	unsigned int			outstanding_requests;
+	unsigned int			outstanding_pre_requests;
+	struct ksmbd_lock_sequence	lock_seq[KSMBD_LOCK_SEQ_ARRAY_SIZE];
 
 	/*
 	 * Pending CHANGE_NOTIFY completions for this handle, sent with
@@ -180,6 +195,9 @@ struct ksmbd_file *ksmbd_lookup_fd_fast(struct ksmbd_work *work, u64 id);
 struct ksmbd_file *ksmbd_lookup_foreign_fd(struct ksmbd_work *work, u64 id);
 struct ksmbd_file *ksmbd_lookup_fd_slow(struct ksmbd_work *work, u64 id,
 					u64 pid);
+int ksmbd_vfs_set_durable_owner(struct ksmbd_file *fp,
+				struct ksmbd_user *user);
+struct ksmbd_file *ksmbd_file_get(struct ksmbd_file *fp);
 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);
-- 
2.25.1


  reply	other threads:[~2026-07-16  9:57 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-16  9:57 [PATCH 1/7] ksmbd: implement the command sequence window Namjae Jeon
2026-07-16  9:57 ` Namjae Jeon [this message]
2026-07-16  9:57 ` [PATCH 3/7] ksmbd: fix malformed procfs status output Namjae Jeon
2026-07-16  9:57 ` [PATCH 4/7] ksmbd: expose connection runtime state in procfs Namjae Jeon
2026-07-16  9:57 ` [PATCH 5/7] ksmbd: report session and open file details " Namjae Jeon
2026-07-16  9:57 ` [PATCH 6/7] ksmbd: add procfs monitoring for active shares Namjae Jeon
2026-07-16  9:57 ` [PATCH 7/7] ksmbd: extend procfs server statistics Namjae Jeon

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260716095711.6228-2-linkinjeon@kernel.org \
    --to=linkinjeon@kernel.org \
    --cc=atteh.mailbox@gmail.com \
    --cc=linux-cifs@vger.kernel.org \
    --cc=senozhatsky@chromium.org \
    --cc=smfrench@gmail.com \
    --cc=tom@talpey.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox