--- old/usr/src/uts/common/fs/smbsrv/smb2_lock.c	Mon Aug  5 17:26:22 2019
+++ new/usr/src/uts/common/fs/smbsrv/smb2_lock.c	Mon Aug  5 17:26:22 2019
@@ -10,7 +10,7 @@
  */
 
 /*
- * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -19,17 +19,26 @@
 
 #include <smbsrv/smb2_kproto.h>
 
-struct SMB2_LOCK_ELEMENT {
+/*
+ * [MS-SMB2] 2.2.26 LockSequenceIndex, LockSequenceNumber.
+ */
+#define	SMB2_LSN_SHIFT	4
+#define	SMB2_LSN_MASK	0xf
+
+typedef struct SMB2_LOCK_ELEMENT {
 	uint64_t Offset;
 	uint64_t Length;
 	uint32_t Flags;
 	uint32_t reserved;
-};
+} lock_elem_t;
 
-static smb_sdrc_t smb2_lock_async(smb_request_t *);
-static uint32_t smb2_lock_exec(smb_request_t *, uint16_t);
-static uint32_t smb2_lock_elem(smb_request_t *, struct SMB2_LOCK_ELEMENT *);
+static uint32_t smb2_unlock(smb_request_t *);
+static uint32_t smb2__lock(smb_request_t *);
+static uint32_t smb2_lock_blocking(smb_request_t *);
 
+static boolean_t smb2_lock_chk_lockseq(smb_ofile_t *, uint32_t);
+static void smb2_lock_set_lockseq(smb_ofile_t *, uint32_t);
+
 /*
  * This is a somewhat arbitrary sanity limit on the length of the
  * SMB2_LOCK_ELEMENT array.  It usually has length one or two.
@@ -39,19 +48,17 @@
 smb_sdrc_t
 smb2_lock(smb_request_t *sr)
 {
-	struct SMB2_LOCK_ELEMENT elem;
+	lock_elem_t *lvec, *lk;
 	smb2fid_t smb2fid;
-	uint32_t save_offset;
 	uint32_t LockSequence;
 	uint32_t status;
 	uint16_t StructSize;
 	uint16_t LockCount;
 	uint16_t i;
-	boolean_t MayBlock = B_FALSE;
-	int rc = 0;
+	int rc;
 
 	/*
-	 * SMB2 Lock request
+	 * Decode SMB2 Lock request
 	 */
 	rc = smb_mbc_decodef(
 	    &sr->smb_data, "wwlqq",
@@ -63,9 +70,14 @@
 	if (rc || StructSize != 48)
 		return (SDRC_ERROR);
 
+	/*
+	 * Want FID lookup before the start probe.
+	 */
 	status = smb2sr_lookup_fid(sr, &smb2fid);
+	DTRACE_SMB2_START(op__Lock, smb_request_t *, sr);
+
 	if (status)
-		goto errout;
+		goto errout; /* Bad FID */
 	if (sr->fid_ofile->f_node == NULL || LockCount == 0) {
 		status = NT_STATUS_INVALID_PARAMETER;
 		goto errout;
@@ -76,218 +88,323 @@
 	}
 
 	/*
-	 * Process the array of SMB2_LOCK_ELEMENT structs
-	 * We do this twice.  (it's always a short list)
-	 * The first time, just validate the flags, and check
-	 * if any of the locking request might need to block.
-	 * The second time (either here, or in the async
-	 * handler function) process the locks for real.
+	 * Check the LockSequence to determine whether a previous
+	 * lock request succeeded, but the client disconnected
+	 * (retaining a durable or resilient handle).  If so, this
+	 * is a lock "replay".  We'll find the lock sequence here
+	 * and return success without processing the lock again.
 	 */
-	save_offset = sr->smb_data.chain_offset;
+	if (sr->session->dialect < SMB_VERS_2_1)
+		LockSequence = 0;
+	if ((sr->session->dialect == SMB_VERS_2_1) &&
+	    sr->fid_ofile->dh_vers != SMB2_RESILIENT)
+		LockSequence = 0;
+	/* dialect 3.0 or later can always use LockSequence */
+
+	if (LockSequence != 0 &&
+	    smb2_lock_chk_lockseq(sr->fid_ofile, LockSequence)) {
+		status = NT_STATUS_SUCCESS;
+		goto errout;
+	}
+
+	/*
+	 * Parse the array of SMB2_LOCK_ELEMENT structs.
+	 * This array is free'd in smb_srm_fini.
+	 */
+	lvec = smb_srm_zalloc(sr, LockCount * sizeof (*lvec));
 	for (i = 0; i < LockCount; i++) {
+		lk = &lvec[i];
 		rc = smb_mbc_decodef(
 		    &sr->smb_data, "qqll",
-		    &elem.Offset,	/* q */
-		    &elem.Length,	/* q */
-		    &elem.Flags,	/* l */
-		    &elem.reserved);	/* l */
+		    &lk->Offset,	/* q */
+		    &lk->Length,	/* q */
+		    &lk->Flags,		/* l */
+		    &lk->reserved);	/* l */
 		if (rc) {
 			status = NT_STATUS_INVALID_PARAMETER;
 			goto errout;
 		}
+	}
 
-		/*
-		 * Make sure the flags are valid;
-		 * Find out if we might block.
-		 */
-		switch (elem.Flags) {
-		case SMB2_LOCKFLAG_SHARED_LOCK:
-		case SMB2_LOCKFLAG_EXCLUSIVE_LOCK:
-			MayBlock = B_TRUE;
-			break;
-
-		/* BEGIN CSTYLED */
-		case SMB2_LOCKFLAG_SHARED_LOCK |
-		     SMB2_LOCKFLAG_FAIL_IMMEDIATELY:
-		case SMB2_LOCKFLAG_EXCLUSIVE_LOCK |
-		     SMB2_LOCKFLAG_FAIL_IMMEDIATELY:
-		case SMB2_LOCKFLAG_UNLOCK:
-		/* END CSTYLED */
-			break;
-
-		default:
-			status = NT_STATUS_INVALID_PARAMETER;
-			goto errout;
-		}
+	/*
+	 * [MS-SMB2] 3.3.5.14
+	 * If the flags of the [first element of] the Locks array
+	 * [has] SMB2_LOCKFLAG_UNLOCK set, the server MUST process
+	 * the lock array as a series of unlocks. Otherwise, it
+	 * MUST process the lock array as a series of lock requests.
+	 */
+	sr->arg.lock.lvec = lvec;
+	sr->arg.lock.lcnt = LockCount;
+	sr->arg.lock.lseq = LockSequence;
+	if (lvec[0].Flags & SMB2_LOCKFLAG_UNLOCK) {
+		status = smb2_unlock(sr);
+	} else {
+		status = smb2__lock(sr);
 	}
 
-	if (MayBlock) {
-		/*
-		 * May need to block.  "Go async".
-		 */
-		status = smb2sr_go_async(sr, smb2_lock_async);
-		goto errout;
+	if (sr->fid_ofile->dh_persist) {
+		smb2_dh_update_locks(sr, sr->fid_ofile);
 	}
 
-	sr->smb_data.chain_offset = save_offset;
-	status = smb2_lock_exec(sr, LockCount);
-	if (status)
-		goto errout;
+errout:
+	sr->smb2_status = status;
+	DTRACE_SMB2_DONE(op__Lock, smb_request_t *, sr);
 
+	if (status) {
+		smb2sr_put_error(sr, status);
+		return (SDRC_SUCCESS);
+	}
+
 	/*
-	 * SMB2 Lock reply (sync)
+	 * Encode SMB2 Lock reply
 	 */
-	StructSize = 4;
 	(void) smb_mbc_encodef(
 	    &sr->reply, "w..",
-	    StructSize);	/* w */
-	    /* reserved		  .. */
+	    4); /* StructSize	w */
+	    /* reserved		.. */
 	return (SDRC_SUCCESS);
-
-errout:
-	smb2sr_put_error(sr, status);
-	return (SDRC_SUCCESS);
 }
 
-static smb_sdrc_t
-smb2_lock_async(smb_request_t *sr)
+/*
+ * Process what should be an array of unlock requests.
+ */
+static uint32_t
+smb2_unlock(smb_request_t *sr)
 {
-	smb2fid_t smb2fid;
-	uint32_t LockSequence;
-	uint32_t status;
-	uint16_t StructSize;
-	uint16_t LockCount;
-	int rc = 0;
+	lock_elem_t *lk;
+	lock_elem_t *lvec = sr->arg.lock.lvec;
+	uint32_t LockCount = sr->arg.lock.lcnt;
+	uint32_t LockSequence = sr->arg.lock.lseq;
+	uint32_t status = 0;
+	uint32_t pid = 0;	/* SMB2 ignores lock PIDs. */
+	int i;
 
-	/*
-	 * Decode the lock request again.  It should all decode
-	 * exactly the same as the first time we saw it.  If not,
-	 * report an "internal error".
-	 */
-	rc = smb_mbc_decodef(
-	    &sr->smb_data, "wwlqq",
-	    &StructSize,		/* w */
-	    &LockCount,			/* w */
-	    &LockSequence,		/* l */
-	    &smb2fid.persistent,	/* q */
-	    &smb2fid.temporal);		/* q */
-	if (rc || StructSize != 48)
-		return (SDRC_ERROR);
+	for (i = 0; i < LockCount; i++) {
+		lk = &lvec[i];
 
-	status = smb2sr_lookup_fid(sr, &smb2fid);
-	if (status)
-		goto errout;
-	if (sr->fid_ofile->f_node == NULL || LockCount == 0) {
-		status = NT_STATUS_INTERNAL_ERROR;
-		goto errout;
+		if (lk->Flags != SMB2_LOCKFLAG_UNLOCK) {
+			status = NT_STATUS_INVALID_PARAMETER;
+			break;
+		}
+
+		status = smb_unlock_range(sr, lk->Offset, lk->Length, pid);
+		if (status != 0)
+			break;
 	}
+	if (status == 0 && LockSequence != 0) {
+		smb2_lock_set_lockseq(sr->fid_ofile, LockSequence);
+	}
 
-	status = smb2_lock_exec(sr, LockCount);
-	if (status)
-		goto errout;
-
-	/*
-	 * SMB2 Lock reply (async)
-	 */
-	StructSize = 4;
-	(void) smb_mbc_encodef(
-	    &sr->reply, "w..",
-	    StructSize);	/* w */
-	    /* reserved		  .. */
-	return (SDRC_SUCCESS);
-
-errout:
-	smb2sr_put_error(sr, status);
-	return (SDRC_SUCCESS);
+	return (status);
 }
 
 /*
- * Execute the vector of locks.  This is the common function called by
- * either the sync or async code paths.  We've already decoded this
- * request once when we get here, so if there are any decode errors
- * then it's some kind of internal error.
+ * Process what should be an array of lock requests.
  */
 static uint32_t
-smb2_lock_exec(smb_request_t *sr, uint16_t LockCount)
+smb2__lock(smb_request_t *sr)
 {
-	struct SMB2_LOCK_ELEMENT elem;
+	lock_elem_t *lk;
+	lock_elem_t *lvec = sr->arg.lock.lvec;
+	uint32_t LockCount = sr->arg.lock.lcnt;
+	uint32_t LockSequence = sr->arg.lock.lseq;
+	uint32_t i;
+	uint32_t ltype;
+	uint32_t pid = 0;	/* SMB2 ignores lock PIDs */
+	uint32_t timeout = 0;
 	uint32_t status = 0;
-	uint16_t i;
-	int rc;
 
-	/*
-	 * On entry, out position in the input data should be
-	 * after both the SMB2 header and the fixed part of
-	 * the SMB Lock request header (24).
-	 */
-	ASSERT(sr->smb_data.chain_offset ==
-	    (sr->smb2_cmd_hdr + SMB2_HDR_SIZE + 24));
+	for (i = 0; i < LockCount; i++) {
+		lk = &lvec[i];
 
-	/*
-	 * This is checked by our callers, but let's make sure.
-	 */
-	ASSERT(sr->fid_ofile->f_node != NULL);
+		switch (lk->Flags) {
 
-	for (i = 0; i < LockCount; i++) {
-		rc = smb_mbc_decodef(
-		    &sr->smb_data, "qqll",
-		    &elem.Offset,	/* q */
-		    &elem.Length,	/* q */
-		    &elem.Flags,	/* l */
-		    &elem.reserved);	/* l */
-		if (rc) {
-			status = NT_STATUS_INTERNAL_ERROR;
+		case SMB2_LOCKFLAG_SHARED_LOCK:
+		case SMB2_LOCKFLAG_EXCLUSIVE_LOCK:
+			/*
+			 * Blocking locks have special rules:
+			 * Must be exactly one element, else
+			 * invalid parameter.
+			 */
+			if (i == 0 && LockCount == 1) {
+				status = smb2_lock_blocking(sr);
+				return (status);
+			}
+			/* FALLTHROUGH */
+		case SMB2_LOCKFLAG_UNLOCK:
+		default:
+			status = NT_STATUS_INVALID_PARAMETER;
+			goto end_loop;
+
+		/* BEGIN CSTYLED */
+		case SMB2_LOCKFLAG_SHARED_LOCK |
+		     SMB2_LOCKFLAG_FAIL_IMMEDIATELY:
+		/* END CSTYLED */
+			ltype = SMB_LOCK_TYPE_READONLY;
 			break;
-		}
-		status = smb2_lock_elem(sr, &elem);
-		if (status)
+
+		/* BEGIN CSTYLED */
+		case SMB2_LOCKFLAG_EXCLUSIVE_LOCK |
+		     SMB2_LOCKFLAG_FAIL_IMMEDIATELY:
+		/* END CSTYLED */
+			ltype = SMB_LOCK_TYPE_READWRITE;
 			break;
+		}
+
+		status = smb_lock_range(sr, lk->Offset, lk->Length, pid,
+		    ltype, timeout);
+		if (status != 0) {
+			goto end_loop;
+		}
 	}
+
+end_loop:
+	if (status != 0) {
+		/*
+		 * Oh... we have to rollback.
+		 */
+		while (i > 0) {
+			--i;
+			lk = &lvec[i];
+			(void) smb_unlock_range(sr,
+			    lk->Offset, lk->Length, pid);
+		}
+	}
+	if (status == 0 && LockSequence != 0)
+		smb2_lock_set_lockseq(sr->fid_ofile, LockSequence);
+
 	return (status);
 }
 
+/*
+ * Handler for blocking lock requests, which may "go async".
+ * Always exactly one lock request here.
+ */
 static uint32_t
-smb2_lock_elem(smb_request_t *sr, struct SMB2_LOCK_ELEMENT *elem)
+smb2_lock_blocking(smb_request_t *sr)
 {
-	smb_node_t *node = sr->fid_ofile->f_node;
+	lock_elem_t *lk = sr->arg.lock.lvec;
+	uint32_t LockCount = sr->arg.lock.lcnt;
+	uint32_t LockSequence = sr->arg.lock.lseq;
 	uint32_t status;
 	uint32_t ltype;
-	uint32_t timeout = 0;
+	uint32_t pid = 0;	/* SMB2 ignores lock PIDs */
+	uint32_t timeout = UINT_MAX;
 
-	switch (elem->Flags) {
+	ASSERT(sr->fid_ofile->f_node != NULL);
+	ASSERT(LockCount == 1);
+
+	switch (lk->Flags) {
 	case SMB2_LOCKFLAG_SHARED_LOCK:
-		timeout = UINT_MAX;
-		/* FALLTHROUGH */
-	case SMB2_LOCKFLAG_SHARED_LOCK | SMB2_LOCKFLAG_FAIL_IMMEDIATELY:
 		ltype = SMB_LOCK_TYPE_READONLY;
-		status = smb_lock_range(sr,
-		    elem->Offset, elem->Length,
-		    timeout, ltype);
 		break;
 
 	case SMB2_LOCKFLAG_EXCLUSIVE_LOCK:
-		timeout = UINT_MAX;
-		/* FALLTHROUGH */
-	case SMB2_LOCKFLAG_EXCLUSIVE_LOCK | SMB2_LOCKFLAG_FAIL_IMMEDIATELY:
 		ltype = SMB_LOCK_TYPE_READWRITE;
-		status = smb_lock_range(sr,
-		    elem->Offset, elem->Length,
-		    timeout, ltype);
 		break;
 
-	case SMB2_LOCKFLAG_UNLOCK:
-		status = smb_unlock_range(sr, node,
-		    elem->Offset, elem->Length);
-		break;
+	default:
+		ASSERT(0);
+		return (NT_STATUS_INTERNAL_ERROR);
+	}
 
 	/*
-	 * We've already checked the flags previously, so any
-	 * surprises here are some kind of internal error.
+	 * Try the lock first with timeout=0 as we can often
+	 * get a lock without going async and avoid an extra
+	 * round trip with the client.  Also, only go async
+	 * for status returns that mean we will block.
 	 */
-	default:
-		status = NT_STATUS_INTERNAL_ERROR;
-		break;
+	status = smb_lock_range(sr, lk->Offset, lk->Length, pid, ltype, 0);
+	if (status == NT_STATUS_LOCK_NOT_GRANTED ||
+	    status == NT_STATUS_FILE_LOCK_CONFLICT) {
+		status = smb2sr_go_async(sr);
+		if (status != 0)
+			return (status);
+		status = smb_lock_range(sr, lk->Offset, lk->Length,
+		    pid, ltype, timeout);
 	}
 
+	if (status == 0 && LockSequence != 0)
+		smb2_lock_set_lockseq(sr->fid_ofile, LockSequence);
+
 	return (status);
+}
+
+/*
+ * Check whether we've stored a given LockSequence
+ *
+ * [MS-SMB2] 3.3.5.14
+ *
+ * The server verifies the LockSequence by performing the following steps:
+ *
+ * 1. The server MUST use LockSequenceIndex as an index into the
+ * Open.LockSequenceArray in order to locate the sequence number entry.
+ * If the index exceeds the maximum extent of the Open.LockSequenceArray,
+ * or LockSequenceIndex is 0, or if the sequence number entry is empty,
+ * the server MUST skip step 2 and continue lock/unlock processing.
+ *
+ * 2. The server MUST compare LockSequenceNumber to the SequenceNumber of
+ * the entry located in step 1. If the sequence numbers are equal, the
+ * server MUST complete the lock/unlock request with success. Otherwise,
+ * the server MUST reset the entry value to empty and continue lock/unlock
+ * processing.
+ */
+boolean_t
+smb2_lock_chk_lockseq(smb_ofile_t *ofile, uint32_t lockseq)
+{
+	uint32_t lsi;
+	uint8_t lsn;
+	boolean_t rv;
+
+	/*
+	 * LockSequenceNumber is the low four bits.
+	 * LockSequenceIndex is the remaining 28 bits.
+	 * valid range is 1..64, which we convert to an
+	 * array index in the range 0..63
+	 */
+	lsn = lockseq & SMB2_LSN_MASK;
+	lsi = (lockseq >> SMB2_LSN_SHIFT);
+	if (lsi == 0 || lsi > SMB_OFILE_LSEQ_MAX)
+		return (B_FALSE);
+	--lsi;
+
+	mutex_enter(&ofile->f_mutex);
+
+	if (ofile->f_lock_seq[lsi] == lsn) {
+		rv = B_TRUE;
+	} else {
+		ofile->f_lock_seq[lsi] = (uint8_t)-1;	/* "Empty" */
+		rv = B_FALSE;
+	}
+
+	mutex_exit(&ofile->f_mutex);
+
+	return (rv);
+}
+
+static void
+smb2_lock_set_lockseq(smb_ofile_t *ofile, uint32_t lockseq)
+{
+	uint32_t lsi;
+	uint8_t lsn;
+
+	/*
+	 * LockSequenceNumber is the low four bits.
+	 * LockSequenceIndex is the remaining 28 bits.
+	 * valid range is 1..64, which we convert to an
+	 * array index in the range 0..63
+	 */
+	lsn = lockseq & SMB2_LSN_MASK;
+	lsi = (lockseq >> SMB2_LSN_SHIFT);
+	if (lsi == 0 || lsi > SMB_OFILE_LSEQ_MAX) {
+		cmn_err(CE_NOTE, "smb2_lock_set_lockseq, index=%u", lsi);
+		return;
+	}
+	--lsi;
+
+	mutex_enter(&ofile->f_mutex);
+
+	ofile->f_lock_seq[lsi] = lsn;
+
+	mutex_exit(&ofile->f_mutex);
 }
