xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_nt_transact_notify_change.c (revision bea83d026ee1bd1b2a2419e1d0232f107a5d7d9b)
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	"%Z%%M%	%I%	%E% 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 /*
118  * smb_nt_transact_notify_change
119  *
120  * This function is responsible for processing NOTIFY CHANGE requests.
121  * Requests are stored in a global queue. This queue is processed when
122  * a monitored directory is changed or client cancels one of its already
123  * sent requests.
124  */
125 int
126 smb_nt_transact_notify_change(struct smb_request *sr, struct smb_xa *xa)
127 {
128 	uint32_t		CompletionFilter;
129 	unsigned char		WatchTree;
130 	smb_node_t		*node;
131 
132 	if (smb_decode_mbc(&xa->req_setup_mb, "lwb",
133 	    &CompletionFilter, &sr->smb_fid, &WatchTree) != 0)
134 		return (SDRC_UNSUPPORTED);
135 
136 	sr->fid_ofile = smb_ofile_lookup_by_fid(sr->tid_tree, sr->smb_fid);
137 	if (sr->fid_ofile == NULL) {
138 		smbsr_error(sr, NT_STATUS_INVALID_HANDLE, ERRDOS, ERRbadfid);
139 		/* NOTREACHED */
140 	}
141 
142 	node = sr->fid_ofile->f_node;
143 
144 	if (node->attr.sa_vattr.va_type != VDIR) {
145 		/*
146 		 * Notify change requests are only valid on directories.
147 		 */
148 		smbsr_error(sr, NT_STATUS_NOT_A_DIRECTORY, 0, 0);
149 		/* NOTREACHED */
150 	}
151 
152 	mutex_enter(&sr->sr_mutex);
153 	switch (sr->sr_state) {
154 	case SMB_REQ_STATE_ACTIVE:
155 		node->waiting_event++;
156 		node->flags |= NODE_FLAGS_NOTIFY_CHANGE;
157 		if ((node->flags & NODE_FLAGS_CHANGED) == 0) {
158 			sr->sr_ncr.nc_node = node;
159 			sr->sr_ncr.nc_flags = CompletionFilter;
160 			if (WatchTree)
161 				sr->sr_ncr.nc_flags |= NODE_FLAGS_WATCH_TREE;
162 
163 			sr->sr_keep = B_TRUE;
164 			sr->sr_state = SMB_REQ_STATE_WAITING_EVENT;
165 			smb_slist_insert_tail(&smb_info.si_ncr_list, sr);
166 
167 			/*
168 			 * Monitor events system-wide.
169 			 *
170 			 * XXX: smb_node_ref() and smb_node_release()
171 			 * take &node->n_lock.  May need alternate forms
172 			 * of these routines if node->n_lock is taken
173 			 * around calls to smb_fem_fcn_install() and
174 			 * smb_fem_fcn_uninstall().
175 			 */
176 
177 			smb_fem_fcn_install(node);
178 
179 			mutex_exit(&sr->sr_mutex);
180 			return (SDRC_NO_REPLY);
181 		} else {
182 			/* node already changed, reply immediately */
183 			if (--node->waiting_event == 0)
184 				node->flags &=
185 				    ~(NODE_FLAGS_NOTIFY_CHANGE |
186 				    NODE_FLAGS_CHANGED);
187 			mutex_exit(&sr->sr_mutex);
188 			return (SDRC_NORMAL_REPLY);
189 		}
190 
191 	case SMB_REQ_STATE_CANCELED:
192 		mutex_exit(&sr->sr_mutex);
193 		smbsr_error(sr, NT_STATUS_CANCELLED, 0, 0);
194 		/* NOTREACHED */
195 	default:
196 		ASSERT(0);
197 		mutex_exit(&sr->sr_mutex);
198 		return (SDRC_NORMAL_REPLY);
199 	}
200 }
201 
202 /*
203  * smb_reply_notify_change_request
204  *
205  * This function sends appropriate response to an already queued NOTIFY CHANGE
206  * request. If node is changed (reply == NODE_FLAGS_CHANGED), a normal reply is
207  * sent.
208  * If client cancels the request or session dropped, an NT_STATUS_CANCELED
209  * is sent in reply.
210  */
211 int
212 smb_reply_notify_change_request(smb_request_t *sr)
213 {
214 	smb_node_t	*node;
215 	int		total_bytes, n_setup, n_param, n_data;
216 	int		param_off, param_pad, data_off, data_pad;
217 	struct		smb_xa *xa;
218 	smb_error_t	err;
219 
220 	xa = sr->r_xa;
221 	node = sr->sr_ncr.nc_node;
222 
223 	if (--node->waiting_event == 0) {
224 		node->flags &= ~(NODE_FLAGS_NOTIFY_CHANGE | NODE_FLAGS_CHANGED);
225 		smb_fem_fcn_uninstall(node);
226 	}
227 
228 	mutex_enter(&sr->sr_mutex);
229 	switch (sr->sr_state) {
230 
231 	case SMB_REQ_STATE_EVENT_OCCURRED:
232 		sr->sr_state = SMB_REQ_STATE_ACTIVE;
233 
234 		/* many things changed */
235 
236 		(void) smb_encode_mbc(&xa->rep_data_mb, "l", 0L);
237 
238 		/* setup the NT transact reply */
239 
240 		n_setup = MBC_LENGTH(&xa->rep_setup_mb);
241 		n_param = MBC_LENGTH(&xa->rep_param_mb);
242 		n_data  = MBC_LENGTH(&xa->rep_data_mb);
243 
244 		n_setup = (n_setup + 1) / 2; /* Convert to setup words */
245 		param_pad = 1; /* must be one */
246 		param_off = param_pad + 32 + 37 + (n_setup << 1) + 2;
247 		/* Pad to 4 bytes */
248 		data_pad = (4 - ((param_off + n_param) & 3)) % 4;
249 		/* Param off from hdr */
250 		data_off = param_off + n_param + data_pad;
251 		total_bytes = param_pad + n_param + data_pad + n_data;
252 
253 		smbsr_encode_result(sr, 18+n_setup, total_bytes,
254 		    "b 3. llllllllb C w #. C #. C",
255 		    18 + n_setup,	/* wct */
256 		    n_param,		/* Total Parameter Bytes */
257 		    n_data,		/* Total Data Bytes */
258 		    n_param,		/* Total Parameter Bytes this buffer */
259 		    param_off,		/* Param offset from header start */
260 		    0,			/* Param displacement */
261 		    n_data,		/* Total Data Bytes this buffer */
262 		    data_off,		/* Data offset from header start */
263 		    0,			/* Data displacement */
264 		    n_setup,		/* suwcnt */
265 		    &xa->rep_setup_mb,	/* setup[] */
266 		    total_bytes,	/* Total data bytes */
267 		    param_pad,
268 		    &xa->rep_param_mb,
269 		    data_pad,
270 		    &xa->rep_data_mb);
271 		break;
272 
273 	case SMB_REQ_STATE_CANCELED:
274 		err.severity = ERROR_SEVERITY_ERROR;
275 		err.status   = NT_STATUS_CANCELLED;
276 		err.errcls   = ERRDOS;
277 		err.errcode  = ERROR_OPERATION_ABORTED;
278 		smbsr_set_error(sr, &err);
279 
280 		(void) smb_encode_mbc(&sr->reply, "bwbw",
281 		    (short)0, 0L, (short)0, 0L);
282 		sr->smb_wct = 0;
283 		sr->smb_bcc = 0;
284 		break;
285 	default:
286 		ASSERT(0);
287 	}
288 	mutex_exit(&sr->sr_mutex);
289 
290 	/* Setup the header */
291 	(void) smb_poke_mbc(&sr->reply, 0, SMB_HEADER_ED_FMT,
292 	    sr->first_smb_com,
293 	    sr->smb_rcls,
294 	    sr->smb_reh,
295 	    sr->smb_err,
296 	    sr->smb_flg | SMB_FLAGS_REPLY,
297 	    sr->smb_flg2,
298 	    sr->smb_pid_high,
299 	    sr->smb_sig,
300 	    sr->smb_tid,
301 	    sr->smb_pid,
302 	    sr->smb_uid,
303 	    sr->smb_mid);
304 
305 	if (sr->session->signing.flags & SMB_SIGNING_ENABLED)
306 		smb_sign_reply(sr, NULL);
307 
308 	/* send the reply */
309 	DTRACE_PROBE1(ncr__reply, struct smb_request *, sr)
310 	(void) smb_session_send(sr->session, 0, &sr->reply);
311 	smbsr_cleanup(sr);
312 
313 	mutex_enter(&sr->sr_mutex);
314 	sr->sr_state = SMB_REQ_STATE_COMPLETED;
315 	mutex_exit(&sr->sr_mutex);
316 	smb_request_free(sr);
317 	return (0);
318 }
319 
320 /*
321  * smb_process_session_notify_change_queue
322  *
323  * This function traverses notify change request queue and sends
324  * cancel replies to all of requests that are related to a specific
325  * session.
326  */
327 void
328 smb_process_session_notify_change_queue(struct smb_session *session)
329 {
330 	smb_request_t	*sr;
331 	smb_request_t	*tmp;
332 	boolean_t	sig = B_FALSE;
333 
334 	smb_slist_enter(&smb_info.si_ncr_list);
335 	smb_slist_enter(&smb_info.si_nce_list);
336 	sr = smb_slist_head(&smb_info.si_ncr_list);
337 	while (sr) {
338 		ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
339 		tmp = smb_slist_next(&smb_info.si_ncr_list, sr);
340 		if (sr->session == session) {
341 			mutex_enter(&sr->sr_mutex);
342 			switch (sr->sr_state) {
343 			case SMB_REQ_STATE_WAITING_EVENT:
344 				smb_slist_obj_move(
345 				    &smb_info.si_nce_list,
346 				    &smb_info.si_ncr_list,
347 				    sr);
348 				sr->sr_state = SMB_REQ_STATE_CANCELED;
349 				sig = B_TRUE;
350 				break;
351 			default:
352 				ASSERT(0);
353 				break;
354 			}
355 			mutex_exit(&sr->sr_mutex);
356 		}
357 		sr = tmp;
358 	}
359 	smb_slist_exit(&smb_info.si_nce_list);
360 	smb_slist_exit(&smb_info.si_ncr_list);
361 	if (sig) {
362 		smb_thread_signal(&smb_info.si_thread_notify_change);
363 	}
364 }
365 
366 /*
367  * smb_process_file_notify_change_queue
368  *
369  * This function traverses notify change request queue and sends
370  * cancel replies to all of requests that are related to the
371  * specified file.
372  */
373 void
374 smb_process_file_notify_change_queue(struct smb_ofile *of)
375 {
376 	smb_request_t	*sr;
377 	smb_request_t	*tmp;
378 	boolean_t	sig = B_FALSE;
379 
380 	smb_slist_enter(&smb_info.si_ncr_list);
381 	smb_slist_enter(&smb_info.si_nce_list);
382 	sr = smb_slist_head(&smb_info.si_ncr_list);
383 	while (sr) {
384 		ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
385 		tmp = smb_slist_next(&smb_info.si_ncr_list, sr);
386 		if (sr->fid_ofile == of) {
387 			mutex_enter(&sr->sr_mutex);
388 			switch (sr->sr_state) {
389 			case SMB_REQ_STATE_WAITING_EVENT:
390 				smb_slist_obj_move(
391 				    &smb_info.si_nce_list,
392 				    &smb_info.si_ncr_list,
393 				    sr);
394 				sr->sr_state = SMB_REQ_STATE_CANCELED;
395 				sig = B_TRUE;
396 				break;
397 			default:
398 				ASSERT(0);
399 				break;
400 			}
401 			mutex_exit(&sr->sr_mutex);
402 		}
403 		sr = tmp;
404 	}
405 	smb_slist_exit(&smb_info.si_nce_list);
406 	smb_slist_exit(&smb_info.si_ncr_list);
407 	if (sig) {
408 		smb_thread_signal(&smb_info.si_thread_notify_change);
409 	}
410 }
411 
412 /*
413  * smb_reply_specific_cancel_request
414  *
415  * This function searches global request list for a specific request. If found,
416  * moves the request to event queue and kicks the notify change daemon.
417  */
418 
419 void
420 smb_reply_specific_cancel_request(struct smb_request *zsr)
421 {
422 	smb_request_t	*sr;
423 	smb_request_t	*tmp;
424 	boolean_t	sig = B_FALSE;
425 
426 	smb_slist_enter(&smb_info.si_ncr_list);
427 	smb_slist_enter(&smb_info.si_nce_list);
428 	sr = smb_slist_head(&smb_info.si_ncr_list);
429 	while (sr) {
430 		ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
431 		tmp = smb_slist_next(&smb_info.si_ncr_list, sr);
432 		if ((sr->session == zsr->session) &&
433 		    (sr->smb_sid == zsr->smb_sid) &&
434 		    (sr->smb_uid == zsr->smb_uid) &&
435 		    (sr->smb_pid == zsr->smb_pid) &&
436 		    (sr->smb_tid == zsr->smb_tid) &&
437 		    (sr->smb_mid == zsr->smb_mid)) {
438 			mutex_enter(&sr->sr_mutex);
439 			switch (sr->sr_state) {
440 			case SMB_REQ_STATE_WAITING_EVENT:
441 				smb_slist_obj_move(
442 				    &smb_info.si_nce_list,
443 				    &smb_info.si_ncr_list,
444 				    sr);
445 				sr->sr_state = SMB_REQ_STATE_CANCELED;
446 				sig = B_TRUE;
447 				break;
448 			default:
449 				ASSERT(0);
450 				break;
451 			}
452 			mutex_exit(&sr->sr_mutex);
453 		}
454 		sr = tmp;
455 	}
456 	smb_slist_exit(&smb_info.si_nce_list);
457 	smb_slist_exit(&smb_info.si_ncr_list);
458 	if (sig) {
459 		smb_thread_signal(&smb_info.si_thread_notify_change);
460 	}
461 }
462 
463 /*
464  * smb_process_node_notify_change_queue
465  *
466  * This function searches notify change request queue and sends
467  * 'NODE MODIFIED' reply to all requests which are related to a
468  * specific node.
469  * WatchTree flag: We handle this flag in a special manner just
470  * for DAVE clients. When something is changed, we notify all
471  * requests which came from DAVE clients on the same volume which
472  * has been modified. We don't care about the tree that they wanted
473  * us to monitor. any change in any part of the volume will lead
474  * to notifying all notify change requests from DAVE clients on the
475  * different parts of the volume hierarchy.
476  */
477 void
478 smb_process_node_notify_change_queue(struct smb_node *node)
479 {
480 	smb_request_t	*sr;
481 	smb_request_t	*tmp;
482 	boolean_t	sig = B_FALSE;
483 
484 	if (!(node->flags & NODE_FLAGS_NOTIFY_CHANGE))
485 		return;
486 
487 	node->flags |= NODE_FLAGS_CHANGED;
488 
489 	smb_slist_enter(&smb_info.si_ncr_list);
490 	smb_slist_enter(&smb_info.si_nce_list);
491 	sr = smb_slist_head(&smb_info.si_ncr_list);
492 	while (sr) {
493 		ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
494 		tmp = smb_slist_next(&smb_info.si_ncr_list, sr);
495 		/*
496 		 * send notify if:
497 		 * - it's a request for the same node or
498 		 * - it's a request from a DAVE client, its 'watch tree'
499 		 *   flag is set and monitors a tree on the same volume.
500 		 */
501 		if ((sr->sr_ncr.nc_node == node) ||
502 		    ((sr->sr_ncr.nc_flags & NODE_FLAGS_WATCH_TREE) &&
503 		    (sr->session->native_os == NATIVE_OS_MACOS) &&
504 		    !fsd_cmp(&sr->sr_ncr.nc_node->tree_fsd, &node->tree_fsd))) {
505 			mutex_enter(&sr->sr_mutex);
506 			switch (sr->sr_state) {
507 			case SMB_REQ_STATE_WAITING_EVENT:
508 				smb_slist_obj_move(
509 				    &smb_info.si_nce_list,
510 				    &smb_info.si_ncr_list,
511 				    sr);
512 				sr->sr_state = SMB_REQ_STATE_EVENT_OCCURRED;
513 				sig = B_TRUE;
514 				break;
515 			default:
516 				ASSERT(0);
517 				break;
518 			}
519 			mutex_exit(&sr->sr_mutex);
520 		}
521 		sr = tmp;
522 	}
523 	smb_slist_exit(&smb_info.si_nce_list);
524 	smb_slist_exit(&smb_info.si_ncr_list);
525 	if (sig) {
526 		smb_thread_signal(&smb_info.si_thread_notify_change);
527 	}
528 }
529 
530 /*
531  * smb_notify_change_daemon
532  *
533  * This function processes notify change event list and send appropriate
534  * responses to the requests. This function executes in the system as an
535  * indivdual thread.
536  */
537 
538 void
539 smb_notify_change_daemon(smb_thread_t *thread, void *si_void)
540 {
541 	smb_request_t	*sr;
542 	smb_request_t	*tmp;
543 	list_t		sr_list;
544 	smb_info_t	*si = si_void;
545 
546 	list_create(&sr_list, sizeof (smb_request_t),
547 	    offsetof(smb_request_t, sr_ncr.nc_lnd));
548 
549 	ASSERT(si != NULL);
550 
551 	while (smb_thread_continue(thread)) {
552 
553 		while (smb_slist_move_tail(&sr_list, &si->si_nce_list)) {
554 			sr = list_head(&sr_list);
555 			while (sr) {
556 				ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
557 				tmp = list_next(&sr_list, sr);
558 				list_remove(&sr_list, sr);
559 				(void) smb_reply_notify_change_request(sr);
560 				sr = tmp;
561 			}
562 		}
563 	}
564 
565 	list_destroy(&sr_list);
566 }
567 
568 /*
569  * smb_notify_change_event_queue_dump
570  *
571  * Dumps all requests in NCE queue to the system log.
572  */
573 void
574 smb_notify_change_event_queue_dump()
575 {
576 	smb_request_t	*sr;
577 	int		i = 0;
578 
579 	smb_slist_enter(&smb_info.si_nce_list);
580 	sr = smb_slist_head(&smb_info.si_nce_list);
581 	while (sr) {
582 		ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
583 		ASSERT((sr->sr_state == SMB_REQ_STATE_CANCELED) ||
584 		    (sr->sr_state == SMB_REQ_STATE_EVENT_OCCURRED));
585 		i++;
586 		sr = smb_slist_next(&smb_info.si_nce_list, sr);
587 	}
588 	smb_slist_exit(&smb_info.si_nce_list);
589 }
590 
591 /*
592  * smb_notify_change_req_queue_dump
593  *
594  * Dumps all requests in NCR queue to the system log.
595  */
596 void
597 smb_notify_change_req_queue_dump()
598 {
599 	smb_request_t	*sr;
600 	int		i = 0;
601 
602 	smb_slist_enter(&smb_info.si_ncr_list);
603 	sr = smb_slist_head(&smb_info.si_ncr_list);
604 	while (sr) {
605 		ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
606 		ASSERT(sr->sr_state == SMB_REQ_STATE_WAITING_EVENT);
607 		i++;
608 		sr = smb_slist_next(&smb_info.si_ncr_list, sr);
609 	}
610 	smb_slist_exit(&smb_info.si_ncr_list);
611 }
612