xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_nt_transact_notify_change.c (revision c8ec8eea9849cac239663c46be8a7f5d2ba7ca00)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"@(#)smb_nt_transact_notify_change.c	1.6	08/08/07 SMI"
27 
28 /*
29  * File Change Notification (FCN)
30  */
31 
32 /*
33  * SMB: nt_transact_notify_change
34  *
35  *  Client Setup Words                 Description
36  *  ================================== =================================
37  *
38  *  ULONG CompletionFilter;            Specifies operation to monitor
39  *  USHORT Fid;                        Fid of directory to monitor
40  *  BOOLEAN WatchTree;                 TRUE = watch all subdirectories too
41  *  UCHAR Reserved;                    MBZ
42  *
43  * This command notifies the client when the directory specified by Fid is
44  * modified.  It also returns the name(s) of the file(s) that changed.  The
45  * command completes once the directory has been modified based on the
46  * supplied CompletionFilter.  The command is a "single shot" and therefore
47  * needs to be reissued to watch for more directory changes.
48  *
49  * A directory file must be opened before this command may be used.  Once
50  * the directory is open, this command may be used to begin watching files
51  * and subdirectories in the specified directory for changes.  The first
52  * time the command is issued, the MaxParameterCount field in the transact
53  * header determines the size of the buffer that will be used at the server
54  * to buffer directory change information between issuances of the notify
55  * change commands.
56  *
57  * When a change that is in the CompletionFilter is made to the directory,
58  * the command completes.  The names of the files that have changed since
59  * the last time the command was issued are returned to the client.  The
60  * ParameterCount field of the response indicates the number of bytes that
61  * are being returned.  If too many files have changed since the last time
62  * the command was issued, then zero bytes are returned and an alternate
63  * status code is returned in the Status field of the response.
64  *
65  * The CompletionFilter is a mask created as the sum of any of the
66  * following flags:
67  *
68  * FILE_NOTIFY_CHANGE_FILE_NAME        0x00000001
69  * FILE_NOTIFY_CHANGE_DIR_NAME         0x00000002
70  * FILE_NOTIFY_CHANGE_NAME             0x00000003
71  * FILE_NOTIFY_CHANGE_ATTRIBUTES       0x00000004
72  * FILE_NOTIFY_CHANGE_SIZE             0x00000008
73  * FILE_NOTIFY_CHANGE_LAST_WRITE       0x00000010
74  * FILE_NOTIFY_CHANGE_LAST_ACCESS      0x00000020
75  * FILE_NOTIFY_CHANGE_CREATION         0x00000040
76  * FILE_NOTIFY_CHANGE_EA               0x00000080
77  * FILE_NOTIFY_CHANGE_SECURITY         0x00000100
78  * FILE_NOTIFY_CHANGE_STREAM_NAME      0x00000200
79  * FILE_NOTIFY_CHANGE_STREAM_SIZE      0x00000400
80  * FILE_NOTIFY_CHANGE_STREAM_WRITE     0x00000800
81  *
82  *  Server Response                    Description
83  *  ================================== ================================
84  *  ParameterCount                     # of bytes of change data
85  *  Parameters[ ParameterCount ]       FILE_NOTIFY_INFORMATION
86  *                                      structures
87  *
88  * The response contains FILE_NOTIFY_INFORMATION structures, as defined
89  * below.  The NextEntryOffset field of the structure specifies the offset,
90  * in bytes, from the start of the current entry to the next entry in the
91  * list.  If this is the last entry in the list, this field is zero.  Each
92  * entry in the list must be longword aligned, so NextEntryOffset must be a
93  * multiple of four.
94  *
95  * typedef struct {
96  *     ULONG NextEntryOffset;
97  *     ULONG Action;
98  *     ULONG FileNameLength;
99  *     WCHAR FileName[1];
100  * } FILE_NOTIFY_INFORMATION;
101  *
102  * Where Action describes what happened to the file named FileName:
103  *
104  * FILE_ACTION_ADDED            0x00000001
105  * FILE_ACTION_REMOVED          0x00000002
106  * FILE_ACTION_MODIFIED         0x00000003
107  * FILE_ACTION_RENAMED_OLD_NAME 0x00000004
108  * FILE_ACTION_RENAMED_NEW_NAME 0x00000005
109  * FILE_ACTION_ADDED_STREAM     0x00000006
110  * FILE_ACTION_REMOVED_STREAM   0x00000007
111  * FILE_ACTION_MODIFIED_STREAM  0x00000008
112  */
113 
114 #include <smbsrv/smb_incl.h>
115 #include <sys/sdt.h>
116 
117 static void smb_notify_change_daemon(smb_thread_t *, void *);
118 static boolean_t smb_notify_change_required(smb_request_t *, smb_node_t *);
119 
120 static boolean_t	smb_notify_initialized = B_FALSE;
121 static smb_slist_t	smb_ncr_list;
122 static smb_slist_t	smb_nce_list;
123 static smb_thread_t	smb_thread_notify_daemon;
124 
125 /*
126  * smb_notify_init
127  *
128  * This function is not multi-thread safe. The caller must make sure only one
129  * thread makes the call.
130  */
131 int
132 smb_notify_init(void)
133 {
134 	int	rc;
135 
136 	if (smb_notify_initialized)
137 		return (0);
138 
139 	smb_slist_constructor(&smb_ncr_list, sizeof (smb_request_t),
140 	    offsetof(smb_request_t, sr_ncr.nc_lnd));
141 
142 	smb_slist_constructor(&smb_nce_list, sizeof (smb_request_t),
143 	    offsetof(smb_request_t, sr_ncr.nc_lnd));
144 
145 	smb_thread_init(&smb_thread_notify_daemon,
146 	    "smb_notify_change_daemon", smb_notify_change_daemon, NULL,
147 	    NULL, NULL);
148 
149 	rc = smb_thread_start(&smb_thread_notify_daemon);
150 	if (rc) {
151 		smb_thread_destroy(&smb_thread_notify_daemon);
152 		smb_slist_destructor(&smb_ncr_list);
153 		smb_slist_destructor(&smb_nce_list);
154 		return (rc);
155 	}
156 
157 	smb_notify_initialized = B_TRUE;
158 
159 	return (0);
160 }
161 
162 /*
163  * smb_notify_fini
164  *
165  * This function is not multi-thread safe. The caller must make sure only one
166  * thread makes the call.
167  */
168 void
169 smb_notify_fini(void)
170 {
171 	if (!smb_notify_initialized)
172 		return;
173 
174 	smb_thread_stop(&smb_thread_notify_daemon);
175 	smb_thread_destroy(&smb_thread_notify_daemon);
176 	smb_slist_destructor(&smb_ncr_list);
177 	smb_slist_destructor(&smb_nce_list);
178 	smb_notify_initialized = B_FALSE;
179 }
180 
181 /*
182  * smb_nt_transact_notify_change
183  *
184  * This function is responsible for processing NOTIFY CHANGE requests.
185  * Requests are stored in a global queue. This queue is processed when
186  * a monitored directory is changed or client cancels one of its already
187  * sent requests.
188  */
189 smb_sdrc_t
190 smb_nt_transact_notify_change(struct smb_request *sr, struct smb_xa *xa)
191 {
192 	uint32_t		CompletionFilter;
193 	unsigned char		WatchTree;
194 	smb_node_t		*node;
195 
196 	if (smb_mbc_decodef(&xa->req_setup_mb, "lwb",
197 	    &CompletionFilter, &sr->smb_fid, &WatchTree) != 0)
198 		return (SDRC_NOT_IMPLEMENTED);
199 
200 	sr->fid_ofile = smb_ofile_lookup_by_fid(sr->tid_tree, sr->smb_fid);
201 	if (sr->fid_ofile == NULL) {
202 		smbsr_error(sr, NT_STATUS_INVALID_HANDLE, ERRDOS, ERRbadfid);
203 		return (SDRC_ERROR);
204 	}
205 
206 	node = sr->fid_ofile->f_node;
207 
208 	if (node->attr.sa_vattr.va_type != VDIR) {
209 		/*
210 		 * Notify change requests are only valid on directories.
211 		 */
212 		smbsr_error(sr, NT_STATUS_NOT_A_DIRECTORY, 0, 0);
213 		return (SDRC_ERROR);
214 	}
215 
216 	mutex_enter(&sr->sr_mutex);
217 	switch (sr->sr_state) {
218 	case SMB_REQ_STATE_ACTIVE:
219 		node->waiting_event++;
220 		node->flags |= NODE_FLAGS_NOTIFY_CHANGE;
221 		if ((node->flags & NODE_FLAGS_CHANGED) == 0) {
222 			sr->sr_ncr.nc_node = node;
223 			sr->sr_ncr.nc_flags = CompletionFilter;
224 			if (WatchTree)
225 				sr->sr_ncr.nc_flags |= NODE_FLAGS_WATCH_TREE;
226 
227 			sr->sr_keep = B_TRUE;
228 			sr->sr_state = SMB_REQ_STATE_WAITING_EVENT;
229 
230 			smb_slist_insert_tail(&smb_ncr_list, sr);
231 
232 			/*
233 			 * Monitor events system-wide.
234 			 *
235 			 * XXX: smb_node_ref() and smb_node_release()
236 			 * take &node->n_lock.  May need alternate forms
237 			 * of these routines if node->n_lock is taken
238 			 * around calls to smb_fem_fcn_install() and
239 			 * smb_fem_fcn_uninstall().
240 			 */
241 
242 			smb_fem_fcn_install(node);
243 
244 			mutex_exit(&sr->sr_mutex);
245 			return (SDRC_NO_REPLY);
246 		} else {
247 			/* node already changed, reply immediately */
248 			if (--node->waiting_event == 0)
249 				node->flags &=
250 				    ~(NODE_FLAGS_NOTIFY_CHANGE |
251 				    NODE_FLAGS_CHANGED);
252 			mutex_exit(&sr->sr_mutex);
253 			return (SDRC_SUCCESS);
254 		}
255 
256 	case SMB_REQ_STATE_CANCELED:
257 		mutex_exit(&sr->sr_mutex);
258 		smbsr_error(sr, NT_STATUS_CANCELLED, 0, 0);
259 		return (SDRC_ERROR);
260 
261 	default:
262 		ASSERT(0);
263 		mutex_exit(&sr->sr_mutex);
264 		return (SDRC_SUCCESS);
265 	}
266 }
267 
268 /*
269  * smb_reply_notify_change_request
270  *
271  * This function sends appropriate response to an already queued NOTIFY CHANGE
272  * request. If node is changed (reply == NODE_FLAGS_CHANGED), a normal reply is
273  * sent.
274  * If client cancels the request or session dropped, an NT_STATUS_CANCELED
275  * is sent in reply.
276  */
277 
278 void
279 smb_reply_notify_change_request(smb_request_t *sr)
280 {
281 	smb_node_t	*node;
282 	int		total_bytes, n_setup, n_param, n_data;
283 	int		param_off, param_pad, data_off, data_pad;
284 	struct		smb_xa *xa;
285 	smb_error_t	err;
286 
287 	xa = sr->r_xa;
288 	node = sr->sr_ncr.nc_node;
289 
290 	if (--node->waiting_event == 0) {
291 		node->flags &= ~(NODE_FLAGS_NOTIFY_CHANGE | NODE_FLAGS_CHANGED);
292 		smb_fem_fcn_uninstall(node);
293 	}
294 
295 	mutex_enter(&sr->sr_mutex);
296 	switch (sr->sr_state) {
297 
298 	case SMB_REQ_STATE_EVENT_OCCURRED:
299 		sr->sr_state = SMB_REQ_STATE_ACTIVE;
300 
301 		/* many things changed */
302 
303 		(void) smb_mbc_encodef(&xa->rep_data_mb, "l", 0L);
304 
305 		/* setup the NT transact reply */
306 
307 		n_setup = MBC_LENGTH(&xa->rep_setup_mb);
308 		n_param = MBC_LENGTH(&xa->rep_param_mb);
309 		n_data  = MBC_LENGTH(&xa->rep_data_mb);
310 
311 		n_setup = (n_setup + 1) / 2; /* Convert to setup words */
312 		param_pad = 1; /* must be one */
313 		param_off = param_pad + 32 + 37 + (n_setup << 1) + 2;
314 		/* Pad to 4 bytes */
315 		data_pad = (4 - ((param_off + n_param) & 3)) % 4;
316 		/* Param off from hdr */
317 		data_off = param_off + n_param + data_pad;
318 		total_bytes = param_pad + n_param + data_pad + n_data;
319 
320 		(void) smbsr_encode_result(sr, 18+n_setup, total_bytes,
321 		    "b3.llllllllbCw#.C#.C",
322 		    18 + n_setup,	/* wct */
323 		    n_param,		/* Total Parameter Bytes */
324 		    n_data,		/* Total Data Bytes */
325 		    n_param,		/* Total Parameter Bytes this buffer */
326 		    param_off,		/* Param offset from header start */
327 		    0,			/* Param displacement */
328 		    n_data,		/* Total Data Bytes this buffer */
329 		    data_off,		/* Data offset from header start */
330 		    0,			/* Data displacement */
331 		    n_setup,		/* suwcnt */
332 		    &xa->rep_setup_mb,	/* setup[] */
333 		    total_bytes,	/* Total data bytes */
334 		    param_pad,
335 		    &xa->rep_param_mb,
336 		    data_pad,
337 		    &xa->rep_data_mb);
338 		break;
339 
340 	case SMB_REQ_STATE_CANCELED:
341 		err.severity = ERROR_SEVERITY_ERROR;
342 		err.status   = NT_STATUS_CANCELLED;
343 		err.errcls   = ERRDOS;
344 		err.errcode  = ERROR_OPERATION_ABORTED;
345 		smbsr_set_error(sr, &err);
346 
347 		(void) smb_mbc_encodef(&sr->reply, "bwbw",
348 		    (short)0, 0L, (short)0, 0L);
349 		sr->smb_wct = 0;
350 		sr->smb_bcc = 0;
351 		break;
352 	default:
353 		ASSERT(0);
354 	}
355 	mutex_exit(&sr->sr_mutex);
356 
357 	/* Setup the header */
358 	(void) smb_mbc_poke(&sr->reply, 0, SMB_HEADER_ED_FMT,
359 	    sr->first_smb_com,
360 	    sr->smb_rcls,
361 	    sr->smb_reh,
362 	    sr->smb_err,
363 	    sr->smb_flg | SMB_FLAGS_REPLY,
364 	    sr->smb_flg2,
365 	    sr->smb_pid_high,
366 	    sr->smb_sig,
367 	    sr->smb_tid,
368 	    sr->smb_pid,
369 	    sr->smb_uid,
370 	    sr->smb_mid);
371 
372 	if (sr->session->signing.flags & SMB_SIGNING_ENABLED)
373 		smb_sign_reply(sr, NULL);
374 
375 	/* send the reply */
376 	DTRACE_PROBE1(ncr__reply, struct smb_request *, sr)
377 	(void) smb_session_send(sr->session, 0, &sr->reply);
378 	smbsr_cleanup(sr);
379 
380 	mutex_enter(&sr->sr_mutex);
381 	sr->sr_state = SMB_REQ_STATE_COMPLETED;
382 	mutex_exit(&sr->sr_mutex);
383 	smb_request_free(sr);
384 }
385 
386 /*
387  * smb_process_session_notify_change_queue
388  *
389  * This function traverses notify change request queue and sends
390  * cancel replies to all of requests that are related to a specific
391  * session.
392  */
393 void
394 smb_process_session_notify_change_queue(
395     smb_session_t	*session,
396     smb_tree_t		*tree)
397 {
398 	smb_request_t	*sr;
399 	smb_request_t	*tmp;
400 	boolean_t	sig = B_FALSE;
401 
402 	smb_slist_enter(&smb_ncr_list);
403 	smb_slist_enter(&smb_nce_list);
404 	sr = smb_slist_head(&smb_ncr_list);
405 	while (sr) {
406 		ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
407 		tmp = smb_slist_next(&smb_ncr_list, sr);
408 		if ((sr->session == session) &&
409 		    (tree == NULL || sr->tid_tree == tree)) {
410 			mutex_enter(&sr->sr_mutex);
411 			switch (sr->sr_state) {
412 			case SMB_REQ_STATE_WAITING_EVENT:
413 				smb_slist_obj_move(
414 				    &smb_nce_list,
415 				    &smb_ncr_list,
416 				    sr);
417 				sr->sr_state = SMB_REQ_STATE_CANCELED;
418 				sig = B_TRUE;
419 				break;
420 			default:
421 				ASSERT(0);
422 				break;
423 			}
424 			mutex_exit(&sr->sr_mutex);
425 		}
426 		sr = tmp;
427 	}
428 	smb_slist_exit(&smb_nce_list);
429 	smb_slist_exit(&smb_ncr_list);
430 	if (sig)
431 		smb_thread_signal(&smb_thread_notify_daemon);
432 }
433 
434 /*
435  * smb_process_file_notify_change_queue
436  *
437  * This function traverses notify change request queue and sends
438  * cancel replies to all of requests that are related to the
439  * specified file.
440  */
441 void
442 smb_process_file_notify_change_queue(struct smb_ofile *of)
443 {
444 	smb_request_t	*sr;
445 	smb_request_t	*tmp;
446 	boolean_t	sig = B_FALSE;
447 
448 	smb_slist_enter(&smb_ncr_list);
449 	smb_slist_enter(&smb_nce_list);
450 	sr = smb_slist_head(&smb_ncr_list);
451 	while (sr) {
452 		ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
453 		tmp = smb_slist_next(&smb_ncr_list, sr);
454 		if (sr->fid_ofile == of) {
455 			mutex_enter(&sr->sr_mutex);
456 			switch (sr->sr_state) {
457 			case SMB_REQ_STATE_WAITING_EVENT:
458 				smb_slist_obj_move(&smb_nce_list,
459 				    &smb_ncr_list, sr);
460 				sr->sr_state = SMB_REQ_STATE_CANCELED;
461 				sig = B_TRUE;
462 				break;
463 			default:
464 				ASSERT(0);
465 				break;
466 			}
467 			mutex_exit(&sr->sr_mutex);
468 		}
469 		sr = tmp;
470 	}
471 	smb_slist_exit(&smb_nce_list);
472 	smb_slist_exit(&smb_ncr_list);
473 	if (sig)
474 		smb_thread_signal(&smb_thread_notify_daemon);
475 }
476 
477 /*
478  * smb_reply_specific_cancel_request
479  *
480  * This function searches global request list for a specific request. If found,
481  * moves the request to event queue and kicks the notify change daemon.
482  */
483 
484 void
485 smb_reply_specific_cancel_request(struct smb_request *zsr)
486 {
487 	smb_request_t	*sr;
488 	smb_request_t	*tmp;
489 	boolean_t	sig = B_FALSE;
490 
491 	smb_slist_enter(&smb_ncr_list);
492 	smb_slist_enter(&smb_nce_list);
493 	sr = smb_slist_head(&smb_ncr_list);
494 	while (sr) {
495 		ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
496 		tmp = smb_slist_next(&smb_ncr_list, sr);
497 		if ((sr->session == zsr->session) &&
498 		    (sr->smb_sid == zsr->smb_sid) &&
499 		    (sr->smb_uid == zsr->smb_uid) &&
500 		    (sr->smb_pid == zsr->smb_pid) &&
501 		    (sr->smb_tid == zsr->smb_tid) &&
502 		    (sr->smb_mid == zsr->smb_mid)) {
503 			mutex_enter(&sr->sr_mutex);
504 			switch (sr->sr_state) {
505 			case SMB_REQ_STATE_WAITING_EVENT:
506 				smb_slist_obj_move(&smb_nce_list,
507 				    &smb_ncr_list, sr);
508 				sr->sr_state = SMB_REQ_STATE_CANCELED;
509 				sig = B_TRUE;
510 				break;
511 			default:
512 				ASSERT(0);
513 				break;
514 			}
515 			mutex_exit(&sr->sr_mutex);
516 		}
517 		sr = tmp;
518 	}
519 	smb_slist_exit(&smb_nce_list);
520 	smb_slist_exit(&smb_ncr_list);
521 	if (sig)
522 		smb_thread_signal(&smb_thread_notify_daemon);
523 }
524 
525 /*
526  * smb_process_node_notify_change_queue
527  *
528  * This function searches notify change request queue and sends
529  * 'NODE MODIFIED' reply to all requests which are related to a
530  * specific node.
531  * WatchTree flag: We handle this flag in a special manner just
532  * for DAVE clients. When something is changed, we notify all
533  * requests which came from DAVE clients on the same volume which
534  * has been modified. We don't care about the tree that they wanted
535  * us to monitor. any change in any part of the volume will lead
536  * to notifying all notify change requests from DAVE clients on the
537  * different parts of the volume hierarchy.
538  */
539 void
540 smb_process_node_notify_change_queue(smb_node_t *node)
541 {
542 	smb_request_t	*sr;
543 	smb_request_t	*tmp;
544 	boolean_t	sig = B_FALSE;
545 
546 	ASSERT(node->n_magic == SMB_NODE_MAGIC);
547 
548 	if (!(node->flags & NODE_FLAGS_NOTIFY_CHANGE))
549 		return;
550 
551 	node->flags |= NODE_FLAGS_CHANGED;
552 
553 	smb_slist_enter(&smb_ncr_list);
554 	smb_slist_enter(&smb_nce_list);
555 	sr = smb_slist_head(&smb_ncr_list);
556 	while (sr) {
557 		ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
558 		tmp = smb_slist_next(&smb_ncr_list, sr);
559 
560 		if (smb_notify_change_required(sr, node)) {
561 			mutex_enter(&sr->sr_mutex);
562 			switch (sr->sr_state) {
563 			case SMB_REQ_STATE_WAITING_EVENT:
564 				smb_slist_obj_move(&smb_nce_list,
565 				    &smb_ncr_list, sr);
566 				sr->sr_state = SMB_REQ_STATE_EVENT_OCCURRED;
567 				sig = B_TRUE;
568 				break;
569 			default:
570 				ASSERT(0);
571 				break;
572 			}
573 			mutex_exit(&sr->sr_mutex);
574 		}
575 		sr = tmp;
576 	}
577 	smb_slist_exit(&smb_nce_list);
578 	smb_slist_exit(&smb_ncr_list);
579 	if (sig)
580 		smb_thread_signal(&smb_thread_notify_daemon);
581 }
582 
583 /*
584  * Change notification is required if:
585  *	- the request node matches the specified node
586  * or
587  *	- the request is from a Mac client, the watch-tree flag
588  *	is set and it is monitoring a tree on the same volume.
589  */
590 static boolean_t
591 smb_notify_change_required(smb_request_t *sr, smb_node_t *node)
592 {
593 	smb_node_t *nc_node = sr->sr_ncr.nc_node;
594 
595 	if (nc_node == node)
596 		return (B_TRUE);
597 
598 	if ((sr->sr_ncr.nc_flags & NODE_FLAGS_WATCH_TREE) &&
599 	    (sr->session->native_os == NATIVE_OS_MACOS) &&
600 	    smb_vfs_cmp(SMB_NODE_VFS(nc_node), SMB_NODE_VFS(node)))
601 		return (B_TRUE);
602 
603 	return (B_FALSE);
604 }
605 
606 /*
607  * smb_notify_change_daemon
608  *
609  * This function processes notify change event list and send appropriate
610  * responses to the requests. This function executes in the system as an
611  * indivdual thread.
612  */
613 static void
614 smb_notify_change_daemon(smb_thread_t *thread, void *arg)
615 {
616 	_NOTE(ARGUNUSED(arg))
617 
618 	smb_request_t	*sr;
619 	smb_request_t	*tmp;
620 	list_t		sr_list;
621 
622 	list_create(&sr_list, sizeof (smb_request_t),
623 	    offsetof(smb_request_t, sr_ncr.nc_lnd));
624 
625 	while (smb_thread_continue(thread)) {
626 
627 		while (smb_slist_move_tail(&sr_list, &smb_nce_list)) {
628 			sr = list_head(&sr_list);
629 			while (sr) {
630 				ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
631 				tmp = list_next(&sr_list, sr);
632 				list_remove(&sr_list, sr);
633 				smb_reply_notify_change_request(sr);
634 				sr = tmp;
635 			}
636 		}
637 	}
638 	list_destroy(&sr_list);
639 }
640