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