[PATCH 2/7] ksmbd: add SMB3 request replay support

Namjae Jeon <[email protected]>
Newsgroups org.kernel.vger.linux-cifs
Message-ID <[email protected]>
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 <[email protected]>
---
 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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.