--- old/usr/src/uts/common/fs/smbsrv/smb2_change_notify.c	Mon Aug  5 17:26:20 2019
+++ new/usr/src/uts/common/fs/smbsrv/smb2_change_notify.c	Mon Aug  5 17:26:20 2019
@@ -21,7 +21,7 @@
 
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates.
- * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -30,12 +30,12 @@
 
 #include <smbsrv/smb2_kproto.h>
 
-static smb_sdrc_t smb2_change_notify_async(smb_request_t *);
+/* For the output DataOffset fields in here. */
+#define	DATA_OFF	(SMB2_HDR_SIZE + 8)
 
 smb_sdrc_t
 smb2_change_notify(smb_request_t *sr)
 {
-	smb_node_t *node = NULL;
 	uint16_t StructSize;
 	uint16_t iFlags;
 	uint32_t oBufLength;
@@ -61,87 +61,122 @@
 		return (SDRC_ERROR);
 
 	status = smb2sr_lookup_fid(sr, &smb2fid);
-	if (status)
-		goto puterror;
+	DTRACE_SMB2_START(op__ChangeNotify, smb_request_t *, sr);
 
-	node = sr->fid_ofile->f_node;
-	if (node == NULL || !smb_node_is_dir(node)) {
-		status = NT_STATUS_INVALID_PARAMETER;
-		goto puterror;
+	if (status != 0)
+		goto errout; /* Bad FID */
+
+	/*
+	 * Only deal with change notify last in a compound,
+	 * because it blocks indefinitely.  This status gets
+	 * "sticky" handling in smb2sr_work().
+	 */
+	if (sr->smb2_next_command != 0) {
+		status = NT_STATUS_INSUFFICIENT_RESOURCES;
+		goto errout;
 	}
 
+	CompletionFilter &= FILE_NOTIFY_VALID_MASK;
+	if (iFlags & SMB2_WATCH_TREE)
+		CompletionFilter |= FILE_NOTIFY_CHANGE_EV_SUBDIR;
+
+	if (oBufLength > smb2_max_trans)
+		oBufLength = smb2_max_trans;
+
 	/*
-	 * Let Change Notify "go async", because it
-	 * may block indefinitely.
+	 * Check for events and consume, non-blocking.
+	 * Special return STATUS_PENDING means:
+	 *   No events; caller must call "act2" next.
+	 * SMB2 does that in "async mode".
 	 */
-	status = smb2sr_go_async(sr, smb2_change_notify_async);
-puterror:
-	ASSERT(status != 0);
-	smb2sr_put_error(sr, status);
+	status = smb_notify_act1(sr, oBufLength, CompletionFilter);
+	if (status == NT_STATUS_PENDING) {
+		status = smb2sr_go_async(sr);
+		if (status != 0)
+			goto errout;
+		status = smb_notify_act2(sr);
+		if (status == NT_STATUS_PENDING) {
+			/* See next: smb2_change_notify_finish */
+			return (SDRC_SR_KEPT);
+		}
+	}
+
+errout:
+	sr->smb2_status = status;
+	DTRACE_SMB2_DONE(op__ChangeNotify, smb_request_t *, sr);
+
+	if (NT_SC_SEVERITY(status) == NT_STATUS_SEVERITY_SUCCESS) {
+		oBufLength = sr->raw_data.chain_offset;
+		(void) smb_mbc_encodef(
+		    &sr->reply, "wwlC",
+		    9,	/* StructSize */	/* w */
+		    DATA_OFF,			/* w */
+		    oBufLength,			/* l */
+		    &sr->raw_data);		/* C */
+	} else {
+		smb2sr_put_error(sr, status);
+	}
+
 	return (SDRC_SUCCESS);
 }
 
-static smb_sdrc_t
-smb2_change_notify_async(smb_request_t *sr)
+/*
+ * This is called via taskq_dispatch in smb_notify.c
+ * to finish up an NT transact notify change request.
+ * Build an SMB2 Change Notify reply and send it.
+ */
+void
+smb2_change_notify_finish(void *arg)
 {
-	uint16_t StructSize;
-	uint16_t iFlags;
-	uint32_t oBufLength;
-	smb2fid_t smb2fid;
-	uint32_t CompletionFilter;
-	uint32_t reserved;
+	smb_request_t *sr = arg;
+	smb_disp_stats_t *sds;
 	uint32_t status;
-	uint16_t DataOff;
-	int rc = 0;
+	uint32_t oBufLength;
 
+	SMB_REQ_VALID(sr);
+
 	/*
-	 * SMB2 Change Notify request
+	 * Common part of notify, puts data in sr->raw_data
 	 */
-	rc = smb_mbc_decodef(
-	    &sr->smb_data,		"wwlqqll",
-	    &StructSize,		/* w */
-	    &iFlags,			/* w */
-	    &oBufLength,		/* l */
-	    &smb2fid.persistent,	/* q */
-	    &smb2fid.temporal,		/* q */
-	    &CompletionFilter,		/* l */
-	    &reserved);			/* l */
-	if (rc || StructSize != 32)
-		return (SDRC_ERROR);
+	status = smb_notify_act3(sr);
 
-	status = smb2sr_lookup_fid(sr, &smb2fid);
-	if (status != 0) {
-		smb2sr_put_error(sr, status);
-		return (SDRC_SUCCESS);
-	}
+	/*
+	 * The prior thread returned SDRC_SR_KEPT and skiped
+	 * the dtrace DONE probe, so fire that here.
+	 */
+	sr->smb2_status = status;
+	DTRACE_SMB2_DONE(op__ChangeNotify, smb_request_t *, sr);
 
-	CompletionFilter &= FILE_NOTIFY_VALID_MASK;
-	if (iFlags & SMB2_WATCH_TREE)
-		CompletionFilter |= NODE_FLAGS_WATCH_TREE;
-
-	if (oBufLength > smb2_max_trans)
-		oBufLength = smb2_max_trans;
-	sr->raw_data.max_bytes = oBufLength;
-
-	status = smb_notify_common(sr, &sr->raw_data, CompletionFilter);
-	if (status != 0) {
+	if (NT_SC_SEVERITY(status) == NT_STATUS_SEVERITY_SUCCESS) {
+		oBufLength = sr->raw_data.chain_offset;
+		(void) smb_mbc_encodef(
+		    &sr->reply, "wwlC",
+		    9,	/* StructSize */	/* w */
+		    DATA_OFF,			/* w */
+		    oBufLength,			/* l */
+		    &sr->raw_data);		/* C */
+	} else {
 		smb2sr_put_error(sr, status);
-		return (SDRC_SUCCESS);
 	}
 
 	/*
-	 * SMB2 Change Notify reply
+	 * Record some statistics: (just tx bytes here)
 	 */
-	DataOff = SMB2_HDR_SIZE + 8;
-	oBufLength = MBC_LENGTH(&sr->raw_data);
-	rc = smb_mbc_encodef(
-	    &sr->reply, "wwlC",
-	    9,	/* StructSize */	/* w */
-	    DataOff,			/* w */
-	    oBufLength,			/* l */
-	    &sr->raw_data);		/* C */
-	if (rc)
-		return (SDRC_ERROR);
+	sds = &sr->session->s_server->sv_disp_stats2[SMB2_CHANGE_NOTIFY];
+	atomic_add_64(&sds->sdt_txb, (int64_t)(sr->reply.chain_offset));
 
-	return (SDRC_SUCCESS);
+	/*
+	 * Put (overwrite) the final SMB2 header,
+	 * sign, send.
+	 */
+	(void) smb2_encode_header(sr, B_TRUE);
+	if (sr->smb2_hdr_flags & SMB2_FLAGS_SIGNED)
+		smb2_sign_reply(sr);
+	smb2_send_reply(sr);
+
+	mutex_enter(&sr->sr_mutex);
+	sr->sr_state = SMB_REQ_STATE_COMPLETED;
+	mutex_exit(&sr->sr_mutex);
+
+	smb_request_free(sr);
 }
