changeset 13956:ba0ec3e45034

3523 SMB NT Notify returning too soon Reviewed by: Albert Lee <trisk@nexenta.com> Reviewed by: Dan McDonald <danmcd@nexenta.com> Reviewed by: Yakov Zaytsev <yakov@nexenta.com> Approved by: Richard Lowe <richlowe@richlowe.net>
author Gordon Ross <gwr@nexenta.com>
date Wed, 10 Oct 2012 20:07:26 -0400
parents be2bd4e678d9
children 512faafc0eaf
files usr/src/uts/common/fs/smbsrv/smb_common_transact.c usr/src/uts/common/fs/smbsrv/smb_fem.c usr/src/uts/common/fs/smbsrv/smb_fsops.c usr/src/uts/common/fs/smbsrv/smb_node.c usr/src/uts/common/fs/smbsrv/smb_nt_cancel.c usr/src/uts/common/fs/smbsrv/smb_nt_transact_notify_change.c usr/src/uts/common/fs/smbsrv/smb_ofile.c usr/src/uts/common/fs/smbsrv/smb_rename.c usr/src/uts/common/fs/smbsrv/smb_server.c usr/src/uts/common/fs/smbsrv/smb_session.c usr/src/uts/common/smbsrv/smb_kproto.h usr/src/uts/common/smbsrv/smb_ktypes.h
diffstat 12 files changed, 412 insertions(+), 495 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/uts/common/fs/smbsrv/smb_common_transact.c	Mon Sep 10 22:06:18 2012 -0400
+++ b/usr/src/uts/common/fs/smbsrv/smb_common_transact.c	Wed Oct 10 20:07:26 2012 -0400
@@ -21,6 +21,7 @@
 
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2012 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #include <smbsrv/smb_kproto.h>
@@ -127,7 +128,7 @@
 
 	if (smb_xa_open(xa)) {
 		smb_xa_rele(sr->session, xa);
-		smbsr_error(sr, 0, ERRDOS, ERRsrverror);
+		smbsr_error(sr, 0, ERRSRV, ERRsrverror);
 		return (SDRC_ERROR);
 	}
 	sr->r_xa = xa;
@@ -314,7 +315,7 @@
 
 	if (smb_xa_open(xa)) {
 		smb_xa_rele(sr->session, xa);
-		smbsr_error(sr, 0, ERRDOS, ERRsrverror);
+		smbsr_error(sr, 0, ERRSRV, ERRsrverror);
 		return (SDRC_ERROR);
 	}
 	sr->r_xa = xa;
@@ -586,7 +587,7 @@
 
 	if (smb_xa_open(xa)) {
 		smb_xa_rele(sr->session, xa);
-		smbsr_error(sr, 0, ERRDOS, ERRsrverror);
+		smbsr_error(sr, 0, ERRSRV, ERRsrverror);
 		return (SDRC_ERROR);
 	}
 	sr->r_xa = xa;
--- a/usr/src/uts/common/fs/smbsrv/smb_fem.c	Mon Sep 10 22:06:18 2012 -0400
+++ b/usr/src/uts/common/fs/smbsrv/smb_fem.c	Wed Oct 10 20:07:26 2012 -0400
@@ -20,6 +20,7 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2012 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #include <smbsrv/smb_kproto.h>
@@ -223,7 +224,7 @@
 	    ct, vsecp);
 
 	if (error == 0)
-		smb_node_notify_change(dnode);
+		smb_node_notify_change(dnode, FILE_ACTION_ADDED, name);
 
 	return (error);
 }
@@ -257,7 +258,7 @@
 	error = vnext_remove(arg, name, cr, ct, flags);
 
 	if (error == 0)
-		smb_node_notify_change(dnode);
+		smb_node_notify_change(dnode, FILE_ACTION_REMOVED, name);
 
 	return (error);
 }
@@ -281,10 +282,18 @@
 
 	error = vnext_rename(arg, snm, tdvp, tnm, cr, ct, flags);
 
-	if (error == 0)
-		smb_node_notify_change(dnode);
+	if (error != 0)
+		return (error);
 
-	return (error);
+	/*
+	 * Note that renames in the same directory are normally
+	 * delivered in {old,new} pairs, and clients expect them
+	 * in that order, if both events are delivered.
+	 */
+	smb_node_notify_change(dnode, FILE_ACTION_RENAMED_OLD_NAME, snm);
+	smb_node_notify_change(dnode, FILE_ACTION_RENAMED_NEW_NAME, tnm);
+
+	return (0);
 }
 
 static int
@@ -308,7 +317,7 @@
 	error = vnext_mkdir(arg, name, vap, vpp, cr, ct, flags, vsecp);
 
 	if (error == 0)
-		smb_node_notify_change(dnode);
+		smb_node_notify_change(dnode, FILE_ACTION_ADDED, name);
 
 	return (error);
 }
@@ -332,7 +341,7 @@
 	error = vnext_rmdir(arg, name, cdir, cr, ct, flags);
 
 	if (error == 0)
-		smb_node_notify_change(dnode);
+		smb_node_notify_change(dnode, FILE_ACTION_REMOVED, name);
 
 	return (error);
 }
@@ -356,7 +365,7 @@
 	error = vnext_link(arg, svp, tnm, cr, ct, flags);
 
 	if (error == 0)
-		smb_node_notify_change(dnode);
+		smb_node_notify_change(dnode, FILE_ACTION_ADDED, tnm);
 
 	return (error);
 }
@@ -381,7 +390,7 @@
 	error = vnext_symlink(arg, linkname, vap, target, cr, ct, flags);
 
 	if (error == 0)
-		smb_node_notify_change(dnode);
+		smb_node_notify_change(dnode, FILE_ACTION_ADDED, linkname);
 
 	return (error);
 }
--- a/usr/src/uts/common/fs/smbsrv/smb_fsops.c	Mon Sep 10 22:06:18 2012 -0400
+++ b/usr/src/uts/common/fs/smbsrv/smb_fsops.c	Wed Oct 10 20:07:26 2012 -0400
@@ -20,6 +20,7 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2012 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #include <sys/sid.h>
@@ -423,7 +424,8 @@
 
 	/* notify change to the unnamed stream */
 	if (rc == 0)
-		smb_node_notify_change(dnode);
+		smb_node_notify_change(dnode,
+		    FILE_ACTION_ADDED_STREAM, fname);
 
 	return (rc);
 }
@@ -679,9 +681,10 @@
 		rc = smb_vop_stream_remove(fnode->vp, name, flags, cr);
 
 		/* notify change to the unnamed stream */
-		if ((rc == 0) && fnode->n_dnode)
-			smb_node_notify_change(fnode->n_dnode);
-
+		if ((rc == 0) && fnode->n_dnode) {
+			smb_node_notify_change(fnode->n_dnode,
+			    FILE_ACTION_REMOVED_STREAM, fnode->od_name);
+		}
 	} else if (smb_is_stream_name(name)) {
 		smb_stream_parse_name(name, fname, sname);
 
@@ -710,8 +713,10 @@
 		smb_node_release(fnode);
 
 		/* notify change to the unnamed stream */
-		if (rc == 0)
-			smb_node_notify_change(dnode);
+		if (rc == 0) {
+			smb_node_notify_change(dnode,
+			    FILE_ACTION_REMOVED_STREAM, fname);
+		}
 	} else {
 		rc = smb_vop_remove(dnode->vp, name, flags, cr);
 
--- a/usr/src/uts/common/fs/smbsrv/smb_node.c	Mon Sep 10 22:06:18 2012 -0400
+++ b/usr/src/uts/common/fs/smbsrv/smb_node.c	Wed Oct 10 20:07:26 2012 -0400
@@ -20,6 +20,7 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2012 Nexenta Systems, Inc.  All rights reserved.
  */
 /*
  * SMB Node State Machine
@@ -784,15 +785,51 @@
 	return (status);
 }
 
+/*
+ * SMB Change Notification
+ */
+
 void
-smb_node_notify_change(smb_node_t *node)
+smb_node_fcn_subscribe(smb_node_t *node, smb_request_t *sr)
+{
+	smb_node_fcn_t		*fcn = &node->n_fcn;
+
+	mutex_enter(&fcn->fcn_mutex);
+	if (fcn->fcn_count == 0)
+		smb_fem_fcn_install(node);
+	fcn->fcn_count++;
+	list_insert_tail(&fcn->fcn_watchers, sr);
+	mutex_exit(&fcn->fcn_mutex);
+}
+
+void
+smb_node_fcn_unsubscribe(smb_node_t *node, smb_request_t *sr)
+{
+	smb_node_fcn_t		*fcn = &node->n_fcn;
+
+	mutex_enter(&fcn->fcn_mutex);
+	list_remove(&fcn->fcn_watchers, sr);
+	fcn->fcn_count--;
+	if (fcn->fcn_count == 0)
+		smb_fem_fcn_uninstall(node);
+	mutex_exit(&fcn->fcn_mutex);
+}
+
+void
+smb_node_notify_change(smb_node_t *node, uint_t action, const char *name)
 {
 	SMB_NODE_VALID(node);
 
-	if (node->flags & NODE_FLAGS_NOTIFY_CHANGE) {
-		node->flags |= NODE_FLAGS_CHANGED;
-		smb_process_node_notify_change_queue(node);
-	}
+	smb_notify_event(node, action, name);
+
+	/*
+	 * These two events come as a pair:
+	 *   FILE_ACTION_RENAMED_OLD_NAME
+	 *   FILE_ACTION_RENAMED_NEW_NAME
+	 * Only do the parent notify for "new".
+	 */
+	if (action == FILE_ACTION_RENAMED_OLD_NAME)
+		return;
 
 	smb_node_notify_parents(node);
 }
@@ -808,17 +845,17 @@
 void
 smb_node_notify_parents(smb_node_t *dnode)
 {
-	smb_node_t *pnode = dnode;
+	smb_node_t *pnode;	/* parent */
 
 	SMB_NODE_VALID(dnode);
+	pnode = dnode->n_dnode;
 
-	while ((pnode = pnode->n_dnode) != NULL) {
+	while (pnode != NULL) {
 		SMB_NODE_VALID(pnode);
-		if ((pnode->flags & NODE_FLAGS_NOTIFY_CHANGE) &&
-		    (pnode->flags & NODE_FLAGS_WATCH_TREE)) {
-			pnode->flags |= NODE_FLAGS_CHANGED;
-			smb_process_node_notify_change_queue(pnode);
-		}
+		smb_notify_event(pnode, 0, dnode->od_name);
+		/* cd .. */
+		dnode = pnode;
+		pnode = dnode->n_dnode;
 	}
 }
 
@@ -1140,6 +1177,9 @@
 	    offsetof(smb_ofile_t, f_nnd));
 	smb_llist_constructor(&node->n_lock_list, sizeof (smb_lock_t),
 	    offsetof(smb_lock_t, l_lnd));
+	mutex_init(&node->n_fcn.fcn_mutex, NULL, MUTEX_DEFAULT, NULL);
+	list_create(&node->n_fcn.fcn_watchers, sizeof (smb_request_t),
+	    offsetof(smb_request_t, sr_ncr.nc_lnd));
 	cv_init(&node->n_oplock.ol_cv, NULL, CV_DEFAULT, NULL);
 	mutex_init(&node->n_oplock.ol_mutex, NULL, MUTEX_DEFAULT, NULL);
 	list_create(&node->n_oplock.ol_grants, sizeof (smb_oplock_grant_t),
@@ -1165,6 +1205,8 @@
 	rw_destroy(&node->n_lock);
 	cv_destroy(&node->n_oplock.ol_cv);
 	mutex_destroy(&node->n_oplock.ol_mutex);
+	list_destroy(&node->n_fcn.fcn_watchers);
+	mutex_destroy(&node->n_fcn.fcn_mutex);
 	smb_llist_destructor(&node->n_lock_list);
 	smb_llist_destructor(&node->n_ofile_list);
 	list_destroy(&node->n_oplock.ol_grants);
@@ -1416,9 +1458,12 @@
 	if (tmp_attr.sa_mask)
 		smb_node_set_cached_timestamps(node, &tmp_attr);
 
-	if (tmp_attr.sa_mask & SMB_AT_MTIME || explicit_times & SMB_AT_MTIME) {
-		if (node->n_dnode != NULL)
-			smb_node_notify_change(node->n_dnode);
+	if ((tmp_attr.sa_mask & SMB_AT_MTIME) ||
+	    (explicit_times & SMB_AT_MTIME)) {
+		if (node->n_dnode != NULL) {
+			smb_node_notify_change(node->n_dnode,
+			    FILE_ACTION_MODIFIED, node->od_name);
+		}
 	}
 
 	return (0);
--- a/usr/src/uts/common/fs/smbsrv/smb_nt_cancel.c	Mon Sep 10 22:06:18 2012 -0400
+++ b/usr/src/uts/common/fs/smbsrv/smb_nt_cancel.c	Wed Oct 10 20:07:26 2012 -0400
@@ -21,6 +21,7 @@
 /*
  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
+ * Copyright 2012 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -79,9 +80,5 @@
 	}
 	smb_slist_exit(&session->s_req_list);
 
-	/* Now, search the notify change queue to find the request */
-
-	smb_reply_specific_cancel_request(sr);
-
 	return (SDRC_NO_REPLY);
 }
--- a/usr/src/uts/common/fs/smbsrv/smb_nt_transact_notify_change.c	Mon Sep 10 22:06:18 2012 -0400
+++ b/usr/src/uts/common/fs/smbsrv/smb_nt_transact_notify_change.c	Wed Oct 10 20:07:26 2012 -0400
@@ -20,8 +20,8 @@
  */
 
 /*
- * Copyright 2011 Nexenta Systems, Inc.  All rights reserved.
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2012 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -113,86 +113,50 @@
 #include <smbsrv/smb_kproto.h>
 #include <sys/sdt.h>
 
-static void smb_notify_change_daemon(smb_thread_t *, void *);
-
-static boolean_t	smb_notify_initialized = B_FALSE;
-static smb_slist_t	smb_ncr_list;
-static smb_slist_t	smb_nce_list;
-static smb_thread_t	smb_thread_notify_daemon;
-
 /*
- * smb_notify_init
- *
- * This function is not multi-thread safe. The caller must make sure only one
- * thread makes the call.
+ * We add this flag to the CompletionFilter (see above) when the
+ * client sets WatchTree.  Must not overlap FILE_NOTIFY_VALID_MASK.
  */
-int
-smb_notify_init(void)
-{
-	int	rc;
-
-	if (smb_notify_initialized)
-		return (0);
-
-	smb_slist_constructor(&smb_ncr_list, sizeof (smb_request_t),
-	    offsetof(smb_request_t, sr_ncr.nc_lnd));
-
-	smb_slist_constructor(&smb_nce_list, sizeof (smb_request_t),
-	    offsetof(smb_request_t, sr_ncr.nc_lnd));
-
-	smb_thread_init(&smb_thread_notify_daemon,
-	    "smb_notify_change_daemon", smb_notify_change_daemon, NULL);
+#define	NODE_FLAGS_WATCH_TREE		0x10000000
+#if (NODE_FLAGS_WATCH_TREE & FILE_NOTIFY_VALID_MASK)
+#error "NODE_FLAGS_WATCH_TREE"
+#endif
 
-	rc = smb_thread_start(&smb_thread_notify_daemon);
-	if (rc) {
-		smb_thread_destroy(&smb_thread_notify_daemon);
-		smb_slist_destructor(&smb_ncr_list);
-		smb_slist_destructor(&smb_nce_list);
-		return (rc);
-	}
-
-	smb_notify_initialized = B_TRUE;
-
-	return (0);
-}
+static void smb_notify_sr(smb_request_t *, uint_t, const char *);
 
-/*
- * smb_notify_fini
- *
- * This function is not multi-thread safe. The caller must make sure only one
- * thread makes the call.
- */
-void
-smb_notify_fini(void)
-{
-	if (!smb_notify_initialized)
-		return;
-
-	smb_thread_stop(&smb_thread_notify_daemon);
-	smb_thread_destroy(&smb_thread_notify_daemon);
-	smb_slist_destructor(&smb_ncr_list);
-	smb_slist_destructor(&smb_nce_list);
-	smb_notify_initialized = B_FALSE;
-}
+static int smb_notify_encode_action(struct smb_request *, struct smb_xa *,
+	uint32_t, char *);
 
 /*
  * smb_nt_transact_notify_change
  *
- * This function is responsible for processing NOTIFY CHANGE requests.
- * Requests are stored in a global queue. This queue is processed when
- * a monitored directory is changed or client cancels one of its already
- * sent requests.
+ * Handle and SMB NT transact NOTIFY CHANGE request.
+ * Basically, wait until "something has changed", and either
+ * return information about what changed, or return a special
+ * error telling the client "many things changed".
+ *
+ * The implementation uses a per-node list of waiting notify
+ * requests like this one, each with a blocked worker thead.
+ * Later, FEM and/or smbsrv events wake these threads, which
+ * then send the reply to the client.
  */
 smb_sdrc_t
-smb_nt_transact_notify_change(struct smb_request *sr, struct smb_xa *xa)
+smb_nt_transact_notify_change(smb_request_t *sr, struct smb_xa *xa)
 {
 	uint32_t		CompletionFilter;
 	unsigned char		WatchTree;
+	smb_error_t		err;
+	int			rc;
 	smb_node_t		*node;
 
 	if (smb_mbc_decodef(&xa->req_setup_mb, "lwb",
-	    &CompletionFilter, &sr->smb_fid, &WatchTree) != 0)
-		return (SDRC_NOT_IMPLEMENTED);
+	    &CompletionFilter, &sr->smb_fid, &WatchTree) != 0) {
+		smbsr_error(sr, NT_STATUS_INVALID_PARAMETER, 0, 0);
+		return (SDRC_ERROR);
+	}
+	CompletionFilter &= FILE_NOTIFY_VALID_MASK;
+	if (WatchTree)
+		CompletionFilter |= NODE_FLAGS_WATCH_TREE;
 
 	smbsr_lookup_file(sr);
 	if (sr->fid_ofile == NULL) {
@@ -201,7 +165,6 @@
 	}
 
 	node = sr->fid_ofile->f_node;
-
 	if (node == NULL || !smb_node_is_dir(node)) {
 		/*
 		 * Notify change requests are only valid on directories.
@@ -210,133 +173,70 @@
 		return (SDRC_ERROR);
 	}
 
-	mutex_enter(&sr->sr_mutex);
-	switch (sr->sr_state) {
-	case SMB_REQ_STATE_ACTIVE:
-		node->waiting_event++;
-		node->flags |= NODE_FLAGS_NOTIFY_CHANGE;
-		if ((node->flags & NODE_FLAGS_CHANGED) == 0) {
-			sr->sr_ncr.nc_node = node;
-			sr->sr_ncr.nc_flags = CompletionFilter;
-			if (WatchTree)
-				sr->sr_ncr.nc_flags |= NODE_FLAGS_WATCH_TREE;
-
-			sr->sr_keep = B_TRUE;
-			sr->sr_state = SMB_REQ_STATE_WAITING_EVENT;
-
-			smb_slist_insert_tail(&smb_ncr_list, sr);
+	/*
+	 * Prepare to receive event data.
+	 */
+	sr->sr_ncr.nc_flags = CompletionFilter;
+	ASSERT(sr->sr_ncr.nc_action == 0);
+	ASSERT(sr->sr_ncr.nc_fname == NULL);
+	sr->sr_ncr.nc_fname = kmem_zalloc(MAXNAMELEN, KM_SLEEP);
 
-			/*
-			 * Monitor events system-wide.
-			 *
-			 * XXX: smb_node_ref() and smb_node_release()
-			 * take &node->n_lock.  May need alternate forms
-			 * of these routines if node->n_lock is taken
-			 * around calls to smb_fem_fcn_install() and
-			 * smb_fem_fcn_uninstall().
-			 */
-
-			smb_fem_fcn_install(node);
-
-			mutex_exit(&sr->sr_mutex);
-			return (SDRC_SR_KEPT);
-		} else {
-			/* node already changed, reply immediately */
-			if (--node->waiting_event == 0)
-				node->flags &=
-				    ~(NODE_FLAGS_NOTIFY_CHANGE |
-				    NODE_FLAGS_CHANGED);
-			mutex_exit(&sr->sr_mutex);
-			return (SDRC_SUCCESS);
-		}
+	/*
+	 * Subscribe to events on this node.
+	 */
+	smb_node_fcn_subscribe(node, sr);
 
-	case SMB_REQ_STATE_CANCELED:
-		mutex_exit(&sr->sr_mutex);
-		smbsr_error(sr, NT_STATUS_CANCELLED, 0, 0);
-		return (SDRC_ERROR);
-
-	default:
-		ASSERT(0);
-		mutex_exit(&sr->sr_mutex);
-		return (SDRC_SUCCESS);
+	/*
+	 * Wait for subscribed events to arrive.
+	 * Expect SMB_REQ_STATE_EVENT_OCCURRED
+	 * or SMB_REQ_STATE_CANCELED when signaled.
+	 * Note it's possible (though rare) to already
+	 * have SMB_REQ_STATE_CANCELED here.
+	 */
+	mutex_enter(&sr->sr_mutex);
+	if (sr->sr_state == SMB_REQ_STATE_ACTIVE)
+		sr->sr_state = SMB_REQ_STATE_WAITING_EVENT;
+	while (sr->sr_state == SMB_REQ_STATE_WAITING_EVENT) {
+		cv_wait(&sr->sr_ncr.nc_cv, &sr->sr_mutex);
 	}
-}
-
-/*
- * smb_reply_notify_change_request
- *
- * This function sends appropriate response to an already queued NOTIFY CHANGE
- * request. If node is changed (reply == NODE_FLAGS_CHANGED), a normal reply is
- * sent.
- * If client cancels the request or session dropped, an NT_STATUS_CANCELED
- * is sent in reply.
- */
+	if (sr->sr_state == SMB_REQ_STATE_EVENT_OCCURRED)
+		sr->sr_state = SMB_REQ_STATE_ACTIVE;
+	mutex_exit(&sr->sr_mutex);
 
-void
-smb_reply_notify_change_request(smb_request_t *sr)
-{
-	smb_node_t	*node;
-	smb_srqueue_t	*srq;
-	int		total_bytes, n_setup, n_param, n_data;
-	int		param_off, param_pad, data_off, data_pad;
-	struct		smb_xa *xa;
-	smb_error_t	err;
+	/*
+	 * Unsubscribe from events on this node.
+	 */
+	smb_node_fcn_unsubscribe(node, sr);
 
-	SMB_REQ_VALID(sr);
-	srq = sr->session->s_srqueue;
-	smb_srqueue_waitq_to_runq(srq);
-
-	xa = sr->r_xa;
-	node = sr->sr_ncr.nc_node;
+	/*
+	 * Build the reply
+	 */
 
-	if (--node->waiting_event == 0) {
-		node->flags &= ~(NODE_FLAGS_NOTIFY_CHANGE | NODE_FLAGS_CHANGED);
-		smb_fem_fcn_uninstall(node);
-	}
-
-	mutex_enter(&sr->sr_mutex);
 	switch (sr->sr_state) {
 
-	case SMB_REQ_STATE_EVENT_OCCURRED:
-		sr->sr_state = SMB_REQ_STATE_ACTIVE;
-
-		/* many things changed */
-
-		(void) smb_mbc_encodef(&xa->rep_data_mb, "l", 0L);
-
-		/* setup the NT transact reply */
-
-		n_setup = MBC_LENGTH(&xa->rep_setup_mb);
-		n_param = MBC_LENGTH(&xa->rep_param_mb);
-		n_data  = MBC_LENGTH(&xa->rep_data_mb);
-
-		n_setup = (n_setup + 1) / 2; /* Convert to setup words */
-		param_pad = 1; /* must be one */
-		param_off = param_pad + 32 + 37 + (n_setup << 1) + 2;
-		/* Pad to 4 bytes */
-		data_pad = (4 - ((param_off + n_param) & 3)) % 4;
-		/* Param off from hdr */
-		data_off = param_off + n_param + data_pad;
-		total_bytes = param_pad + n_param + data_pad + n_data;
-
-		(void) smbsr_encode_result(sr, 18+n_setup, total_bytes,
-		    "b3.llllllllbCw#.C#.C",
-		    18 + n_setup,	/* wct */
-		    n_param,		/* Total Parameter Bytes */
-		    n_data,		/* Total Data Bytes */
-		    n_param,		/* Total Parameter Bytes this buffer */
-		    param_off,		/* Param offset from header start */
-		    0,			/* Param displacement */
-		    n_data,		/* Total Data Bytes this buffer */
-		    data_off,		/* Data offset from header start */
-		    0,			/* Data displacement */
-		    n_setup,		/* suwcnt */
-		    &xa->rep_setup_mb,	/* setup[] */
-		    total_bytes,	/* Total data bytes */
-		    param_pad,
-		    &xa->rep_param_mb,
-		    data_pad,
-		    &xa->rep_data_mb);
+	case SMB_REQ_STATE_ACTIVE:
+		/*
+		 * If we have event data, marshall it now, else just
+		 * say "many things changed". Note that when we are
+		 * woken by a WatchTree event (action == 0) then we
+		 * don't have true event details, and only know the
+		 * directory under which something changed.  In that
+		 * case we just say "many things changed".
+		 */
+		if (sr->sr_ncr.nc_action != 0 && 0 ==
+		    smb_notify_encode_action(sr, xa,
+		    sr->sr_ncr.nc_action, sr->sr_ncr.nc_fname)) {
+			rc = SDRC_SUCCESS;
+			break;
+		}
+		/*
+		 * This error says "many things changed".
+		 */
+		err.status = NT_STATUS_NOTIFY_ENUM_DIR;
+		err.errcls   = ERRDOS;
+		err.errcode  = ERROR_NOTIFY_ENUM_DIR;
+		smbsr_set_error(sr, &err);
+		rc = SDRC_ERROR;
 		break;
 
 	case SMB_REQ_STATE_CANCELED:
@@ -344,284 +244,226 @@
 		err.errcls   = ERRDOS;
 		err.errcode  = ERROR_OPERATION_ABORTED;
 		smbsr_set_error(sr, &err);
+		rc = SDRC_ERROR;
+		break;
 
-		(void) smb_mbc_encodef(&sr->reply, "bwbw",
-		    (short)0, 0L, (short)0, 0L);
-		sr->smb_wct = 0;
-		sr->smb_bcc = 0;
-		break;
 	default:
 		ASSERT(0);
+		err.status   = NT_STATUS_INTERNAL_ERROR;
+		err.errcls   = ERRDOS;
+		err.errcode  = ERROR_INTERNAL_ERROR;
+		smbsr_set_error(sr, &err);
+		rc = SDRC_ERROR;
+		break;
 	}
-	mutex_exit(&sr->sr_mutex);
 
-	/* Setup the header */
-	(void) smb_mbc_poke(&sr->reply, 0, SMB_HEADER_ED_FMT,
-	    sr->first_smb_com,
-	    sr->smb_rcls,
-	    sr->smb_reh,
-	    sr->smb_err,
-	    sr->smb_flg | SMB_FLAGS_REPLY,
-	    sr->smb_flg2,
-	    sr->smb_pid_high,
-	    sr->smb_sig,
-	    sr->smb_tid,
-	    sr->smb_pid,
-	    sr->smb_uid,
-	    sr->smb_mid);
+	if (sr->sr_ncr.nc_fname != NULL) {
+		kmem_free(sr->sr_ncr.nc_fname, MAXNAMELEN);
+		sr->sr_ncr.nc_fname = NULL;
+	}
 
-	if (sr->session->signing.flags & SMB_SIGNING_ENABLED)
-		smb_sign_reply(sr, NULL);
-
-	/* send the reply */
-	DTRACE_PROBE1(ncr__reply, struct smb_request *, sr)
-	(void) smb_session_send(sr->session, 0, &sr->reply);
-	smbsr_cleanup(sr);
-
-	mutex_enter(&sr->sr_mutex);
-	sr->sr_state = SMB_REQ_STATE_COMPLETED;
-	mutex_exit(&sr->sr_mutex);
-	smb_srqueue_runq_exit(srq);
-	smb_request_free(sr);
+	return (rc);
 }
 
 /*
- * smb_process_session_notify_change_queue
+ * Encode a FILE_NOTIFY_INFORMATION struct.
  *
- * This function traverses notify change request queue and sends
- * cancel replies to all of requests that are related to a specific
- * session.
+ * We only ever put one of these in a response, so this
+ * does not bother handling appending additional ones.
  */
-void
-smb_process_session_notify_change_queue(
-    smb_session_t	*session,
-    smb_tree_t		*tree)
+static int
+smb_notify_encode_action(struct smb_request *sr, struct smb_xa *xa,
+	uint32_t action, char *fname)
 {
-	smb_request_t	*sr;
-	smb_request_t	*tmp;
-	boolean_t	sig = B_FALSE;
+	uint32_t namelen;
+	int rc;
 
-	smb_slist_enter(&smb_ncr_list);
-	smb_slist_enter(&smb_nce_list);
-	sr = smb_slist_head(&smb_ncr_list);
-	while (sr) {
-		ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
-		tmp = smb_slist_next(&smb_ncr_list, sr);
-		if ((sr->session == session) &&
-		    (tree == NULL || sr->tid_tree == tree)) {
-			mutex_enter(&sr->sr_mutex);
-			switch (sr->sr_state) {
-			case SMB_REQ_STATE_WAITING_EVENT:
-				smb_slist_obj_move(
-				    &smb_nce_list,
-				    &smb_ncr_list,
-				    sr);
-				smb_srqueue_waitq_enter(
-				    sr->session->s_srqueue);
-				sr->sr_state = SMB_REQ_STATE_CANCELED;
-				sig = B_TRUE;
-				break;
-			default:
-				ASSERT(0);
-				break;
-			}
-			mutex_exit(&sr->sr_mutex);
-		}
-		sr = tmp;
-	}
-	smb_slist_exit(&smb_nce_list);
-	smb_slist_exit(&smb_ncr_list);
-	if (sig)
-		smb_thread_signal(&smb_thread_notify_daemon);
-}
+	if (action < FILE_ACTION_ADDED ||
+	    action > FILE_ACTION_MODIFIED_STREAM)
+		return (-1);
 
-/*
- * smb_process_file_notify_change_queue
- *
- * This function traverses notify change request queue and sends
- * cancel replies to all of requests that are related to the
- * specified file.
- */
-void
-smb_process_file_notify_change_queue(struct smb_ofile *of)
-{
-	smb_request_t	*sr;
-	smb_request_t	*tmp;
-	boolean_t	sig = B_FALSE;
+	namelen = smb_ascii_or_unicode_strlen(sr, fname);
+	if (namelen == 0)
+		return (-1);
 
-	smb_slist_enter(&smb_ncr_list);
-	smb_slist_enter(&smb_nce_list);
-	sr = smb_slist_head(&smb_ncr_list);
-	while (sr) {
-		ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
-		tmp = smb_slist_next(&smb_ncr_list, sr);
-		if (sr->fid_ofile == of) {
-			mutex_enter(&sr->sr_mutex);
-			switch (sr->sr_state) {
-			case SMB_REQ_STATE_WAITING_EVENT:
-				smb_slist_obj_move(&smb_nce_list,
-				    &smb_ncr_list, sr);
-				smb_srqueue_waitq_enter(
-				    sr->session->s_srqueue);
-				sr->sr_state = SMB_REQ_STATE_CANCELED;
-				sig = B_TRUE;
-				break;
-			default:
-				ASSERT(0);
-				break;
-			}
-			mutex_exit(&sr->sr_mutex);
-		}
-		sr = tmp;
-	}
-	smb_slist_exit(&smb_nce_list);
-	smb_slist_exit(&smb_ncr_list);
-	if (sig)
-		smb_thread_signal(&smb_thread_notify_daemon);
+	rc = smb_mbc_encodef(&xa->rep_data_mb, "%lllu", sr,
+	    0, /* NextEntryOffset */
+	    action, namelen, fname);
+	return (rc);
 }
 
 /*
- * smb_reply_specific_cancel_request
+ * smb_notify_file_closed
  *
- * This function searches global request list for a specific request. If found,
- * moves the request to event queue and kicks the notify change daemon.
+ * Cancel any change-notify calls on this open file.
  */
-
 void
-smb_reply_specific_cancel_request(struct smb_request *zsr)
+smb_notify_file_closed(struct smb_ofile *of)
+{
+	smb_session_t	*ses;
+	smb_request_t	*sr;
+	smb_slist_t	*list;
+
+	SMB_OFILE_VALID(of);
+	ses = of->f_session;
+	SMB_SESSION_VALID(ses);
+	list = &ses->s_req_list;
+
+	smb_slist_enter(list);
+
+	sr = smb_slist_head(list);
+	while (sr) {
+		SMB_REQ_VALID(sr);
+		if (sr->sr_state == SMB_REQ_STATE_WAITING_EVENT &&
+		    sr->fid_ofile == of) {
+			smb_request_cancel(sr);
+		}
+		sr = smb_slist_next(list, sr);
+	}
+
+	smb_slist_exit(list);
+}
+
+
+/*
+ * smb_notify_event
+ *
+ * Post an event to the watchers on a given node.
+ *
+ * This makes one exception for RENAME, where we expect a
+ * pair of events for the {old,new} directory element names.
+ * This only delivers an event for the "new" name.
+ *
+ * The event delivery mechanism does not implement delivery of
+ * multiple events for one "NT Notify" call.  One could do that,
+ * but modern clients don't actually use the event data.  They
+ * set a max. received data size of zero, which means we discard
+ * the data and send the special "lots changed" error instead.
+ * Given that, there's not really any point in implementing the
+ * delivery of multiple events.  In fact, we don't even need to
+ * implement single event delivery, but do so for completeness,
+ * for debug convenience, and to be nice to older clients that
+ * may actually want some event data instead of the error.
+ *
+ * Given that we only deliver a single event for an "NT Notify"
+ * caller, we want to deliver the "new" name event.  (The "old"
+ * name event is less important, even ignored by some clients.)
+ * Since we know these are delivered in pairs, we can simply
+ * discard the "old" name event, knowing that the "new" name
+ * event will be delivered immediately afterwards.
+ *
+ * So, why do event sources post the "old name" event at all?
+ * (1) For debugging, so we see both {old,new} names here.
+ * (2) If in the future someone decides to implement the
+ * delivery of both {old,new} events, the changes can be
+ * mostly isolated to this file.
+ */
+void
+smb_notify_event(smb_node_t *node, uint_t action, const char *name)
 {
 	smb_request_t	*sr;
-	smb_request_t	*tmp;
-	boolean_t	sig = B_FALSE;
+	smb_node_fcn_t	*fcn;
+
+	SMB_NODE_VALID(node);
+	fcn = &node->n_fcn;
 
-	smb_slist_enter(&smb_ncr_list);
-	smb_slist_enter(&smb_nce_list);
-	sr = smb_slist_head(&smb_ncr_list);
+	if (action == FILE_ACTION_RENAMED_OLD_NAME)
+		return; /* see above */
+
+	mutex_enter(&fcn->fcn_mutex);
+
+	sr = list_head(&fcn->fcn_watchers);
 	while (sr) {
-		ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
-		tmp = smb_slist_next(&smb_ncr_list, sr);
-		if ((sr->session == zsr->session) &&
-		    (sr->smb_uid == zsr->smb_uid) &&
-		    (sr->smb_pid == zsr->smb_pid) &&
-		    (sr->smb_tid == zsr->smb_tid) &&
-		    (sr->smb_mid == zsr->smb_mid)) {
-			mutex_enter(&sr->sr_mutex);
-			switch (sr->sr_state) {
-			case SMB_REQ_STATE_WAITING_EVENT:
-				smb_slist_obj_move(&smb_nce_list,
-				    &smb_ncr_list, sr);
-				smb_srqueue_waitq_enter(
-				    sr->session->s_srqueue);
-				sr->sr_state = SMB_REQ_STATE_CANCELED;
-				sig = B_TRUE;
-				break;
-			default:
-				ASSERT(0);
-				break;
-			}
-			mutex_exit(&sr->sr_mutex);
-		}
-		sr = tmp;
+		smb_notify_sr(sr, action, name);
+		sr = list_next(&fcn->fcn_watchers, sr);
 	}
-	smb_slist_exit(&smb_nce_list);
-	smb_slist_exit(&smb_ncr_list);
-	if (sig)
-		smb_thread_signal(&smb_thread_notify_daemon);
+
+	mutex_exit(&fcn->fcn_mutex);
 }
 
 /*
- * smb_process_node_notify_change_queue
- *
- * This function searches notify change request queue and sends
- * 'NODE MODIFIED' reply to all requests which are related to a
- * specific node.
- * WatchTree flag: We handle this flag in a special manner just
- * for DAVE clients. When something is changed, we notify all
- * requests which came from DAVE clients on the same volume which
- * has been modified. We don't care about the tree that they wanted
- * us to monitor. any change in any part of the volume will lead
- * to notifying all notify change requests from DAVE clients on the
- * different parts of the volume hierarchy.
+ * What completion filter (masks) apply to each of the
+ * FILE_ACTION_... events.
  */
-void
-smb_process_node_notify_change_queue(smb_node_t *node)
-{
-	smb_request_t	*sr;
-	smb_request_t	*tmp;
-	smb_node_t	*nc_node;
-	boolean_t	sig = B_FALSE;
+static const uint32_t
+smb_notify_action_mask[] = {
+	/* 0: Special, used by smb_node_notify_parents() */
+	NODE_FLAGS_WATCH_TREE,
 
-	ASSERT(node->n_magic == SMB_NODE_MAGIC);
+	/* FILE_ACTION_ADDED	 */
+	FILE_NOTIFY_CHANGE_NAME,
 
-	if (!(node->flags & NODE_FLAGS_NOTIFY_CHANGE))
-		return;
-
-	node->flags |= NODE_FLAGS_CHANGED;
+	/* FILE_ACTION_REMOVED	 */
+	FILE_NOTIFY_CHANGE_NAME,
 
-	smb_slist_enter(&smb_ncr_list);
-	smb_slist_enter(&smb_nce_list);
-	sr = smb_slist_head(&smb_ncr_list);
-	while (sr) {
-		ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
-		tmp = smb_slist_next(&smb_ncr_list, sr);
+	/* FILE_ACTION_MODIFIED	 */
+	FILE_NOTIFY_CHANGE_ATTRIBUTES |
+	FILE_NOTIFY_CHANGE_SIZE |
+	FILE_NOTIFY_CHANGE_LAST_WRITE |
+	FILE_NOTIFY_CHANGE_LAST_ACCESS |
+	FILE_NOTIFY_CHANGE_CREATION |
+	FILE_NOTIFY_CHANGE_EA |
+	FILE_NOTIFY_CHANGE_SECURITY,
+
+	/* FILE_ACTION_RENAMED_OLD_NAME */
+	FILE_NOTIFY_CHANGE_NAME,
 
-		nc_node = sr->sr_ncr.nc_node;
-		if (nc_node == node) {
-			mutex_enter(&sr->sr_mutex);
-			switch (sr->sr_state) {
-			case SMB_REQ_STATE_WAITING_EVENT:
-				smb_slist_obj_move(&smb_nce_list,
-				    &smb_ncr_list, sr);
-				smb_srqueue_waitq_enter(
-				    sr->session->s_srqueue);
-				sr->sr_state = SMB_REQ_STATE_EVENT_OCCURRED;
-				sig = B_TRUE;
-				break;
-			default:
-				ASSERT(0);
-				break;
-			}
-			mutex_exit(&sr->sr_mutex);
-		}
-		sr = tmp;
-	}
-	smb_slist_exit(&smb_nce_list);
-	smb_slist_exit(&smb_ncr_list);
-	if (sig)
-		smb_thread_signal(&smb_thread_notify_daemon);
-}
+	/* FILE_ACTION_RENAMED_NEW_NAME */
+	FILE_NOTIFY_CHANGE_NAME,
+
+	/* FILE_ACTION_ADDED_STREAM */
+	FILE_NOTIFY_CHANGE_STREAM_NAME,
+
+	/* FILE_ACTION_REMOVED_STREAM */
+	FILE_NOTIFY_CHANGE_STREAM_NAME,
+
+	/* FILE_ACTION_MODIFIED_STREAM */
+	FILE_NOTIFY_CHANGE_STREAM_SIZE |
+	FILE_NOTIFY_CHANGE_STREAM_WRITE,
+};
+static const int smb_notify_action_nelm =
+	sizeof (smb_notify_action_mask) /
+	sizeof (smb_notify_action_mask[0]);
 
 /*
- * smb_notify_change_daemon
+ * smb_notify_sr
  *
- * This function processes notify change event list and send appropriate
- * responses to the requests. This function executes in the system as an
- * indivdual thread.
+ * Post an event to an smb request waiting on some node.
+ *
+ * Note that node->fcn.mutex is held.  This implies a
+ * lock order: node->fcn.mutex, then sr_mutex
  */
 static void
-smb_notify_change_daemon(smb_thread_t *thread, void *arg)
+smb_notify_sr(smb_request_t *sr, uint_t action, const char *name)
 {
-	_NOTE(ARGUNUSED(arg))
+	smb_notify_change_req_t	*ncr;
+	uint32_t	mask;
 
-	smb_request_t	*sr;
-	smb_request_t	*tmp;
-	list_t		sr_list;
-
-	list_create(&sr_list, sizeof (smb_request_t),
-	    offsetof(smb_request_t, sr_ncr.nc_lnd));
-
-	while (smb_thread_continue(thread)) {
+	SMB_REQ_VALID(sr);
+	ncr = &sr->sr_ncr;
 
-		while (smb_slist_move_tail(&sr_list, &smb_nce_list)) {
-			sr = list_head(&sr_list);
-			while (sr) {
-				ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
-				tmp = list_next(&sr_list, sr);
-				list_remove(&sr_list, sr);
-				smb_reply_notify_change_request(sr);
-				sr = tmp;
-			}
-		}
+	/*
+	 * Compute the completion filter mask bits for which
+	 * we will signal waiting notify requests.
+	 */
+	if (action >= smb_notify_action_nelm) {
+		ASSERT(0);
+		return;
 	}
-	list_destroy(&sr_list);
+	mask = smb_notify_action_mask[action];
+
+	mutex_enter(&sr->sr_mutex);
+	if (sr->sr_state == SMB_REQ_STATE_WAITING_EVENT &&
+	    (ncr->nc_flags & mask) != 0) {
+		sr->sr_state = SMB_REQ_STATE_EVENT_OCCURRED;
+		/*
+		 * Save event data in the sr_ncr field so the
+		 * reply handler can return it.
+		 */
+		ncr->nc_action = action;
+		if (name != NULL)
+			(void) strlcpy(ncr->nc_fname, name, MAXNAMELEN);
+		cv_signal(&ncr->nc_cv);
+	}
+	mutex_exit(&sr->sr_mutex);
 }
--- a/usr/src/uts/common/fs/smbsrv/smb_ofile.c	Mon Sep 10 22:06:18 2012 -0400
+++ b/usr/src/uts/common/fs/smbsrv/smb_ofile.c	Wed Oct 10 20:07:26 2012 -0400
@@ -20,6 +20,7 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2012 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -324,11 +325,11 @@
 				    of->f_cr);
 
 			/*
-			 * Cancel any notify change requests related
-			 * to this open instance.
+			 * Cancel any notify change requests that
+			 * may be using this open instance.
 			 */
-			if (of->f_node->flags & NODE_FLAGS_NOTIFY_CHANGE)
-				smb_process_file_notify_change_queue(of);
+			if (of->f_node->n_fcn.fcn_count)
+				smb_notify_file_closed(of);
 			smb_server_dec_files(of->f_server);
 		}
 		atomic_dec_32(&of->f_tree->t_open_files);
--- a/usr/src/uts/common/fs/smbsrv/smb_rename.c	Mon Sep 10 22:06:18 2012 -0400
+++ b/usr/src/uts/common/fs/smbsrv/smb_rename.c	Wed Oct 10 20:07:26 2012 -0400
@@ -20,6 +20,7 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2012 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #include <sys/synch.h>
@@ -480,10 +481,25 @@
 	    src_dnode, src_fnode->od_name,
 	    dst_dnode, new_name);
 
-	smb_rename_release_src(sr);
+	if (rc == 0) {
+		/*
+		 * Note that renames in the same directory are normally
+		 * delivered in {old,new} pairs, and clients expect them
+		 * in that order, if both events are delivered.
+		 */
+		int a_src, a_dst; /* action codes */
+		if (src_dnode == dst_dnode) {
+			a_src = FILE_ACTION_RENAMED_OLD_NAME;
+			a_dst = FILE_ACTION_RENAMED_NEW_NAME;
+		} else {
+			a_src = FILE_ACTION_REMOVED;
+			a_dst = FILE_ACTION_ADDED;
+		}
+		smb_node_notify_change(src_dnode, a_src, src_fnode->od_name);
+		smb_node_notify_change(dst_dnode, a_dst, new_name);
+	}
 
-	if (rc == 0)
-		smb_node_notify_change(dst_dnode);
+	smb_rename_release_src(sr);
 
 	if (dst_fqi->fq_fnode) {
 		smb_node_end_crit(dst_fnode);
@@ -617,9 +633,12 @@
 	rc = smb_fsop_link(sr, sr->user_cr, src_fqi->fq_fnode,
 	    dst_fqi->fq_dnode, dst_fqi->fq_last_comp);
 
+	if (rc == 0) {
+		smb_node_notify_change(dst_fqi->fq_dnode,
+		    FILE_ACTION_ADDED, dst_fqi->fq_last_comp);
+	}
+
 	smb_rename_release_src(sr);
-	if (rc == 0)
-		smb_node_notify_change(dst_fqi->fq_dnode);
 	smb_node_release(dst_fqi->fq_dnode);
 	return (rc);
 }
--- a/usr/src/uts/common/fs/smbsrv/smb_server.c	Mon Sep 10 22:06:18 2012 -0400
+++ b/usr/src/uts/common/fs/smbsrv/smb_server.c	Wed Oct 10 20:07:26 2012 -0400
@@ -294,8 +294,6 @@
 			continue;
 		if (rc = smb_fem_init())
 			continue;
-		if (rc = smb_notify_init())
-			continue;
 		if (rc = smb_net_init())
 			continue;
 		smb_llist_init();
@@ -306,7 +304,6 @@
 
 	smb_llist_fini();
 	smb_net_fini();
-	smb_notify_fini();
 	smb_fem_fini();
 	smb_node_fini();
 	smb_vop_fini();
@@ -328,7 +325,6 @@
 	if (smb_llist_get_count(&smb_servers) == 0) {
 		smb_llist_fini();
 		smb_net_fini();
-		smb_notify_fini();
 		smb_fem_fini();
 		smb_node_fini();
 		smb_oplock_fini();
--- a/usr/src/uts/common/fs/smbsrv/smb_session.c	Mon Sep 10 22:06:18 2012 -0400
+++ b/usr/src/uts/common/fs/smbsrv/smb_session.c	Wed Oct 10 20:07:26 2012 -0400
@@ -19,8 +19,8 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2011 Nexenta Systems, Inc.  All rights reserved.
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2012 Nexenta Systems, Inc.  All rights reserved.
  */
 #include <sys/atomic.h>
 #include <sys/strsubr.h>
@@ -392,13 +392,14 @@
 		break;
 
 	case SMB_REQ_STATE_WAITING_EVENT:
-	case SMB_REQ_STATE_EVENT_OCCURRED:
 		/*
-		 * Cancellations for these states are handled by the
-		 * notify-change code
+		 * This request is waiting in change notify.
 		 */
+		sr->sr_state = SMB_REQ_STATE_CANCELED;
+		cv_signal(&sr->sr_ncr.nc_cv);
 		break;
 
+	case SMB_REQ_STATE_EVENT_OCCURRED:
 	case SMB_REQ_STATE_COMPLETED:
 	case SMB_REQ_STATE_CANCELED:
 		/*
@@ -785,8 +786,6 @@
 {
 	smb_request_t	*sr;
 
-	smb_process_session_notify_change_queue(session, tree);
-
 	smb_slist_enter(&session->s_req_list);
 	sr = smb_slist_head(&session->s_req_list);
 
@@ -1076,6 +1075,7 @@
 	bzero(sr, sizeof (smb_request_t));
 
 	mutex_init(&sr->sr_mutex, NULL, MUTEX_DEFAULT, NULL);
+	cv_init(&sr->sr_ncr.nc_cv, NULL, CV_DEFAULT, NULL);
 	smb_srm_init(sr);
 	sr->session = session;
 	sr->sr_server = session->s_server;
@@ -1104,6 +1104,7 @@
 	ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
 	ASSERT(sr->session);
 	ASSERT(sr->r_xa == NULL);
+	ASSERT(sr->sr_ncr.nc_fname == NULL);
 
 	if (sr->fid_ofile != NULL) {
 		smb_ofile_request_complete(sr->fid_ofile);
@@ -1132,6 +1133,7 @@
 		m_freem(sr->raw_data.chain);
 
 	sr->sr_magic = 0;
+	cv_destroy(&sr->sr_ncr.nc_cv);
 	mutex_destroy(&sr->sr_mutex);
 	kmem_cache_free(sr->sr_cache, sr);
 }
--- a/usr/src/uts/common/smbsrv/smb_kproto.h	Mon Sep 10 22:06:18 2012 -0400
+++ b/usr/src/uts/common/smbsrv/smb_kproto.h	Wed Oct 10 20:07:26 2012 -0400
@@ -173,9 +173,6 @@
 
 SMB_NT_TRANSACT_DECL(nt_transact_create);
 
-int smb_notify_init(void);
-void smb_notify_fini(void);
-
 smb_sdrc_t smb_nt_transact_notify_change(smb_request_t *, smb_xa_t *);
 smb_sdrc_t smb_nt_transact_query_security_info(smb_request_t *, smb_xa_t *);
 smb_sdrc_t smb_nt_transact_set_security_info(smb_request_t *, smb_xa_t *);
@@ -208,8 +205,6 @@
 
 int smb_net_id(uint32_t);
 
-void smb_process_file_notify_change_queue(smb_ofile_t *of);
-
 /*
  * oplock functions - node operations
  */
@@ -446,7 +441,10 @@
 DWORD smb_node_rename_check(smb_node_t *);
 DWORD smb_node_delete_check(smb_node_t *);
 boolean_t smb_node_share_check(smb_node_t *);
-void smb_node_notify_change(smb_node_t *);
+
+void smb_node_fcn_subscribe(smb_node_t *, smb_request_t *);
+void smb_node_fcn_unsubscribe(smb_node_t *, smb_request_t *);
+void smb_node_notify_change(smb_node_t *, uint_t, const char *);
 void smb_node_notify_parents(smb_node_t *);
 int smb_node_getattr(smb_request_t *, smb_node_t *, smb_attr_t *);
 int smb_node_setattr(smb_request_t *, smb_node_t *, cred_t *,
@@ -477,9 +475,9 @@
 void smb_vfs_rele_all(smb_export_t *);
 
 /* NOTIFY CHANGE */
-void smb_process_session_notify_change_queue(smb_session_t *, smb_tree_t *);
-void smb_process_node_notify_change_queue(smb_node_t *);
-void smb_reply_specific_cancel_request(smb_request_t *);
+
+void smb_notify_event(smb_node_t *, uint_t, const char *);
+void smb_notify_file_closed(smb_ofile_t *of);
 
 void smb_fem_fcn_install(smb_node_t *);
 void smb_fem_fcn_uninstall(smb_node_t *);
--- a/usr/src/uts/common/smbsrv/smb_ktypes.h	Mon Sep 10 22:06:18 2012 -0400
+++ b/usr/src/uts/common/smbsrv/smb_ktypes.h	Wed Oct 10 20:07:26 2012 -0400
@@ -452,12 +452,18 @@
 } smb_rwx_t;
 
 /* NOTIFY CHANGE */
+typedef struct smb_node_fcn {
+	kmutex_t	fcn_mutex;
+	uint32_t	fcn_count;
+	list_t		fcn_watchers;	/* smb_request_t, sr_ncr.nc_lnd */
+} smb_node_fcn_t;
 
 typedef struct smb_notify_change_req {
-	list_node_t		nc_lnd;
-	struct smb_node		*nc_node;
-	uint32_t		nc_reply_type;
+	list_node_t		nc_lnd;	/* n_fcn.fcn_watchers */
+	kcondvar_t		nc_cv;	/* prot: sr_mutex */
 	uint32_t		nc_flags;
+	uint32_t		nc_action;
+	char			*nc_fname;
 } smb_notify_change_req_t;
 
 /*
@@ -638,6 +644,7 @@
 	volatile int		waiting_event;
 	smb_times_t		n_timestamps;
 	u_offset_t		n_allocsz;
+	smb_node_fcn_t		n_fcn;
 	smb_oplock_t		n_oplock;
 	struct smb_node		*n_dnode;
 	struct smb_node		*n_unode;
@@ -652,10 +659,6 @@
 #define	NODE_FLAGS_DFSLINK		0x00002000
 #define	NODE_FLAGS_VFSROOT		0x00004000
 #define	NODE_FLAGS_SYSTEM		0x00008000
-#define	NODE_FLAGS_WATCH_TREE		0x10000000
-#define	NODE_FLAGS_NOTIFY_CHANGE	\
-	(NODE_FLAGS_WATCH_TREE | FILE_NOTIFY_VALID_MASK)
-#define	NODE_FLAGS_CHANGED		0x08000000
 #define	NODE_FLAGS_WRITE_THROUGH	0x00100000
 #define	NODE_XATTR_DIR			0x01000000
 #define	NODE_FLAGS_DELETE_ON_CLOSE	0x40000000
@@ -1602,7 +1605,6 @@
 	kmutex_t		sr_mutex;
 	list_node_t		sr_session_lnd;
 	smb_req_state_t		sr_state;
-	boolean_t		sr_keep;
 	kmem_cache_t		*sr_cache;
 	struct smb_server	*sr_server;
 	pid_t			*sr_pid;