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