ses_snap.c revision 12126:60364f3f65c7
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) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
24 */
25
26#include <scsi/libses.h>
27#include "ses_impl.h"
28
29ses_snap_page_t *
30ses_snap_find_page(ses_snap_t *sp, ses2_diag_page_t page, boolean_t ctl)
31{
32	ses_snap_page_t *pp;
33
34	for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next)
35		if (pp->ssp_num == page && pp->ssp_control == ctl &&
36		    (pp->ssp_len > 0 || pp->ssp_control))
37			return (pp);
38
39	return (NULL);
40}
41
42static int
43grow_snap_page(ses_snap_page_t *pp, size_t min)
44{
45	uint8_t *newbuf;
46
47	if (min == 0 || min < pp->ssp_alloc)
48		min = pp->ssp_alloc * 2;
49
50	if ((newbuf = ses_realloc(pp->ssp_page, min)) == NULL)
51		return (-1);
52
53	pp->ssp_page = newbuf;
54	pp->ssp_alloc = min;
55
56	bzero(newbuf + pp->ssp_len, pp->ssp_alloc - pp->ssp_len);
57
58	return (0);
59}
60
61static ses_snap_page_t *
62alloc_snap_page(void)
63{
64	ses_snap_page_t *pp;
65
66	if ((pp = ses_zalloc(sizeof (ses_snap_page_t))) == NULL)
67		return (NULL);
68
69	if ((pp->ssp_page = ses_zalloc(SES2_MIN_DIAGPAGE_ALLOC)) == NULL) {
70		ses_free(pp);
71		return (NULL);
72	}
73
74	pp->ssp_num = -1;
75	pp->ssp_alloc = SES2_MIN_DIAGPAGE_ALLOC;
76
77	return (pp);
78}
79
80static void
81free_snap_page(ses_snap_page_t *pp)
82{
83	if (pp == NULL)
84		return;
85
86	if (pp->ssp_mmap_base)
87		(void) munmap(pp->ssp_mmap_base, pp->ssp_mmap_len);
88	else
89		ses_free(pp->ssp_page);
90	ses_free(pp);
91}
92
93static void
94free_all_snap_pages(ses_snap_t *sp)
95{
96	ses_snap_page_t *pp, *np;
97
98	for (pp = sp->ss_pages; pp != NULL; pp = np) {
99		np = pp->ssp_next;
100		free_snap_page(pp);
101	}
102
103	sp->ss_pages = NULL;
104}
105
106/*
107 * Grow (if needed) the control page buffer, fill in the page code, page
108 * length, and generation count, and return a pointer to the page.  The
109 * caller is responsible for filling in the rest of the page data.  If 'unique'
110 * is specified, then a new page instance is created instead of sharing the
111 * current one.
112 */
113ses_snap_page_t *
114ses_snap_ctl_page(ses_snap_t *sp, ses2_diag_page_t page, size_t dlen,
115    boolean_t unique)
116{
117	ses_target_t *tp = sp->ss_target;
118	spc3_diag_page_impl_t *pip;
119	ses_snap_page_t *pp, *up, **loc;
120	ses_pagedesc_t *dp;
121	size_t len;
122
123	pp = ses_snap_find_page(sp, page, B_TRUE);
124	if (pp == NULL) {
125		(void) ses_set_errno(ESES_NOTSUP);
126		return (NULL);
127	}
128
129	if (pp->ssp_initialized && !unique)
130		return (pp);
131
132	if (unique) {
133		/*
134		 * The user has requested a unique instance of the page.  Create
135		 * a new ses_snap_page_t instance and chain it off the
136		 * 'ssp_instances' list of the master page.  These must be
137		 * appended to the end of the chain, as the order of operations
138		 * may be important (i.e. microcode download).
139		 */
140		if ((up = alloc_snap_page()) == NULL)
141			return (NULL);
142
143		up->ssp_num = pp->ssp_num;
144		up->ssp_control = B_TRUE;
145
146		for (loc = &pp->ssp_unique; *loc != NULL;
147		    loc = &(*loc)->ssp_next)
148			;
149
150		*loc = up;
151		pp = up;
152	}
153
154	dp = ses_get_pagedesc(tp, page, SES_PAGE_CTL);
155	ASSERT(dp != NULL);
156
157	len = dp->spd_ctl_len(sp->ss_n_elem, page, dlen);
158	if (pp->ssp_alloc < len && grow_snap_page(pp, len) != 0)
159		return (NULL);
160	pp->ssp_len = len;
161	bzero(pp->ssp_page, len);
162	pp->ssp_initialized = B_TRUE;
163
164	pip = (spc3_diag_page_impl_t *)pp->ssp_page;
165	pip->sdpi_page_code = (uint8_t)page;
166	SCSI_WRITE16(&pip->sdpi_page_length,
167	    len - offsetof(spc3_diag_page_impl_t, sdpi_data[0]));
168	if (dp->spd_gcoff != -1)
169		SCSI_WRITE32((uint8_t *)pip + dp->spd_gcoff, sp->ss_generation);
170
171	return (pp);
172}
173
174static int
175read_status_page(ses_snap_t *sp, ses2_diag_page_t page)
176{
177	libscsi_action_t *ap;
178	ses_snap_page_t *pp;
179	ses_target_t *tp;
180	spc3_diag_page_impl_t *pip;
181	spc3_receive_diagnostic_results_cdb_t *cp;
182	uint_t flags;
183	uint8_t *buf;
184	size_t alloc;
185	uint_t retries = 0;
186	ses2_diag_page_t retpage;
187
188	for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next)
189		if (pp->ssp_num == page && !pp->ssp_control)
190			break;
191
192	/*
193	 * No matching page.  Since the page number is not under consumer or
194	 * device control, this must be a bug.
195	 */
196	ASSERT(pp != NULL);
197
198	tp = sp->ss_target;
199
200	flags = LIBSCSI_AF_READ | LIBSCSI_AF_SILENT | LIBSCSI_AF_DIAGNOSE |
201	    LIBSCSI_AF_RQSENSE;
202
203again:
204	ap = libscsi_action_alloc(tp->st_scsi_hdl,
205	    SPC3_CMD_RECEIVE_DIAGNOSTIC_RESULTS, flags, pp->ssp_page,
206	    pp->ssp_alloc);
207
208	if (ap == NULL)
209		return (ses_libscsi_error(tp->st_scsi_hdl, "failed to "
210		    "allocate SCSI action"));
211
212	cp = (spc3_receive_diagnostic_results_cdb_t *)
213	    libscsi_action_get_cdb(ap);
214
215	cp->rdrc_page_code = pp->ssp_num;
216	cp->rdrc_pcv = 1;
217	SCSI_WRITE16(&cp->rdrc_allocation_length,
218	    MIN(pp->ssp_alloc, UINT16_MAX));
219
220	if (libscsi_exec(ap, tp->st_target) != 0) {
221		libscsi_action_free(ap);
222		return (ses_libscsi_error(tp->st_scsi_hdl,
223		    "receive diagnostic results failed"));
224	}
225
226	if (libscsi_action_get_status(ap) != 0) {
227		(void) ses_scsi_error(ap,
228		    "receive diagnostic results failed");
229		libscsi_action_free(ap);
230		return (-1);
231	}
232
233	(void) libscsi_action_get_buffer(ap, &buf, &alloc, &pp->ssp_len);
234	libscsi_action_free(ap);
235
236	ASSERT(buf == pp->ssp_page);
237	ASSERT(alloc == pp->ssp_alloc);
238
239	if (pp->ssp_alloc - pp->ssp_len < 0x80 && pp->ssp_alloc < UINT16_MAX) {
240		bzero(pp->ssp_page, pp->ssp_len);
241		pp->ssp_len = 0;
242		if (grow_snap_page(pp, 0) != 0)
243			return (-1);
244		goto again;
245	}
246
247	if (pp->ssp_len < offsetof(spc3_diag_page_impl_t, sdpi_data)) {
248		bzero(pp->ssp_page, pp->ssp_len);
249		pp->ssp_len = 0;
250		return (ses_error(ESES_BAD_RESPONSE, "target returned "
251		    "truncated page 0x%x (length %d)", page, pp->ssp_len));
252	}
253
254	pip = (spc3_diag_page_impl_t *)buf;
255
256	if (pip->sdpi_page_code == page)
257		return (0);
258
259	retpage = pip->sdpi_page_code;
260
261	bzero(pp->ssp_page, pp->ssp_len);
262	pp->ssp_len = 0;
263
264	if (retpage == SES2_DIAGPAGE_ENCLOSURE_BUSY) {
265		if (++retries > LIBSES_MAX_BUSY_RETRIES)
266			return (ses_error(ESES_BUSY, "too many "
267			    "enclosure busy responses for page 0x%x", page));
268		goto again;
269	}
270
271	return (ses_error(ESES_BAD_RESPONSE, "target returned page 0x%x "
272	    "instead of the requested page 0x%x", retpage, page));
273}
274
275static int
276send_control_page(ses_snap_t *sp, ses_snap_page_t *pp)
277{
278	ses_target_t *tp;
279	libscsi_action_t *ap;
280	spc3_send_diagnostic_cdb_t *cp;
281	uint_t flags;
282
283	tp = sp->ss_target;
284
285	flags = LIBSCSI_AF_WRITE | LIBSCSI_AF_SILENT | LIBSCSI_AF_DIAGNOSE |
286	    LIBSCSI_AF_RQSENSE;
287
288	ap = libscsi_action_alloc(tp->st_scsi_hdl, SPC3_CMD_SEND_DIAGNOSTIC,
289	    flags, pp->ssp_page, pp->ssp_len);
290
291	if (ap == NULL)
292		return (ses_libscsi_error(tp->st_scsi_hdl, "failed to "
293		    "allocate SCSI action"));
294
295	cp = (spc3_send_diagnostic_cdb_t *)libscsi_action_get_cdb(ap);
296
297	cp->sdc_pf = 1;
298	SCSI_WRITE16(&cp->sdc_parameter_list_length, pp->ssp_len);
299
300	if (libscsi_exec(ap, tp->st_target) != 0) {
301		libscsi_action_free(ap);
302		return (ses_libscsi_error(tp->st_scsi_hdl,
303		    "SEND DIAGNOSTIC command failed for page 0x%x",
304		    pp->ssp_num));
305	}
306
307	if (libscsi_action_get_status(ap) != 0) {
308		(void) ses_scsi_error(ap, "SEND DIAGNOSTIC command "
309		    "failed for page 0x%x", pp->ssp_num);
310		libscsi_action_free(ap);
311		return (-1);
312	}
313
314	libscsi_action_free(ap);
315
316	return (0);
317}
318
319static int
320pages_skel_create(ses_snap_t *sp)
321{
322	ses_snap_page_t *pp, *np;
323	ses_target_t *tp = sp->ss_target;
324	ses2_supported_ses_diag_page_impl_t *pip;
325	ses2_diag_page_t page;
326	size_t npages;
327	size_t pagelen;
328	off_t i;
329
330	ASSERT(sp->ss_pages == NULL);
331
332	if ((pp = alloc_snap_page()) == NULL)
333		return (-1);
334
335	pp->ssp_num = SES2_DIAGPAGE_SUPPORTED_PAGES;
336	pp->ssp_control = B_FALSE;
337	sp->ss_pages = pp;
338
339	if (read_status_page(sp, SES2_DIAGPAGE_SUPPORTED_PAGES) != 0) {
340		free_snap_page(pp);
341		sp->ss_pages = NULL;
342		return (-1);
343	}
344
345	pip = pp->ssp_page;
346	pagelen = pp->ssp_len;
347
348	npages = SCSI_READ16(&pip->sssdpi_page_length);
349
350	for (i = 0; i < npages; i++) {
351		if (!SES_WITHIN_PAGE(pip->sssdpi_pages + i, 1, pip,
352		    pagelen))
353			break;
354
355		page = (ses2_diag_page_t)pip->sssdpi_pages[i];
356		/*
357		 * Skip the page we already added during the bootstrap.
358		 */
359		if (page == SES2_DIAGPAGE_SUPPORTED_PAGES)
360			continue;
361		/*
362		 * The end of the page list may be padded with zeros; ignore
363		 * them all.
364		 */
365		if (page == 0 && i > 0)
366			break;
367		if ((np = alloc_snap_page()) == NULL) {
368			free_all_snap_pages(sp);
369			return (-1);
370		}
371		np->ssp_num = page;
372		pp->ssp_next = np;
373		pp = np;
374
375		/*
376		 * Allocate a control page as well, if we can use it.
377		 */
378		if (ses_get_pagedesc(tp, page, SES_PAGE_CTL) != NULL) {
379			if ((np = alloc_snap_page()) == NULL) {
380				free_all_snap_pages(sp);
381				return (-1);
382			}
383			np->ssp_num = page;
384			np->ssp_control = B_TRUE;
385			pp->ssp_next = np;
386			pp = np;
387		}
388	}
389
390	return (0);
391}
392
393static void
394ses_snap_free(ses_snap_t *sp)
395{
396	free_all_snap_pages(sp);
397	ses_node_teardown(sp->ss_root);
398	ses_free(sp->ss_nodes);
399	ses_free(sp);
400}
401
402static void
403ses_snap_rele_unlocked(ses_snap_t *sp)
404{
405	ses_target_t *tp = sp->ss_target;
406
407	if (--sp->ss_refcnt != 0)
408		return;
409
410	if (sp->ss_next != NULL)
411		sp->ss_next->ss_prev = sp->ss_prev;
412
413	if (sp->ss_prev != NULL)
414		sp->ss_prev->ss_next = sp->ss_next;
415	else
416		tp->st_snapshots = sp->ss_next;
417
418	ses_snap_free(sp);
419}
420
421ses_snap_t *
422ses_snap_hold(ses_target_t *tp)
423{
424	ses_snap_t *sp;
425
426	(void) pthread_mutex_lock(&tp->st_lock);
427	sp = tp->st_snapshots;
428	sp->ss_refcnt++;
429	(void) pthread_mutex_unlock(&tp->st_lock);
430
431	return (sp);
432}
433
434void
435ses_snap_rele(ses_snap_t *sp)
436{
437	ses_target_t *tp = sp->ss_target;
438
439	(void) pthread_mutex_lock(&tp->st_lock);
440	ses_snap_rele_unlocked(sp);
441	(void) pthread_mutex_unlock(&tp->st_lock);
442}
443
444ses_snap_t *
445ses_snap_new(ses_target_t *tp)
446{
447	ses_snap_t *sp;
448	ses_snap_page_t *pp;
449	uint32_t gc;
450	uint_t retries = 0;
451	ses_pagedesc_t *dp;
452	size_t pages, pagesize, pagelen;
453	char *scratch;
454	boolean_t simple;
455
456	if ((sp = ses_zalloc(sizeof (ses_snap_t))) == NULL)
457		return (NULL);
458
459	sp->ss_target = tp;
460
461again:
462	free_all_snap_pages(sp);
463
464	if (pages_skel_create(sp) != 0) {
465		free(sp);
466		return (NULL);
467	}
468
469	sp->ss_generation = (uint32_t)-1;
470	sp->ss_time = gethrtime();
471
472	/*
473	 * First check for the short enclosure status diagnostic page and
474	 * determine if this is a simple subenclosure or not.
475	 */
476	simple = B_FALSE;
477	for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) {
478		if (pp->ssp_num == SES2_DIAGPAGE_SHORT_STATUS)
479			simple = B_TRUE;
480	}
481
482	for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) {
483		/*
484		 * We skip all of:
485		 *
486		 * - Control pages
487		 * - Pages we've already filled in
488		 * - Pages we don't understand (those with no descriptor)
489		 */
490		if (pp->ssp_len > 0 || pp->ssp_control)
491			continue;
492		if ((dp = ses_get_pagedesc(tp, pp->ssp_num,
493		    SES_PAGE_DIAG)) == NULL)
494			continue;
495
496		if (read_status_page(sp, pp->ssp_num) != 0)  {
497			/*
498			 * If this page is required, and this is not a simple
499			 * subenclosure, then fail the entire snapshot.
500			 */
501			if (dp->spd_req == SES_REQ_MANDATORY_ALL ||
502			    (dp->spd_req == SES_REQ_MANDATORY_STANDARD &&
503			    !simple)) {
504				ses_snap_free(sp);
505				return (NULL);
506			}
507
508			continue;
509		}
510
511		/*
512		 * If the generation code has changed, we don't have a valid
513		 * snapshot.  Start over.
514		 */
515		if (dp->spd_gcoff != -1 &&
516		    dp->spd_gcoff + 4 <= pp->ssp_len) {
517			gc = SCSI_READ32((uint8_t *)pp->ssp_page +
518			    dp->spd_gcoff);
519			if (sp->ss_generation == (uint32_t)-1) {
520				sp->ss_generation = gc;
521			} else if (sp->ss_generation != gc) {
522				if (++retries > LIBSES_MAX_GC_RETRIES) {
523					(void) ses_error(ESES_TOOMUCHCHANGE,
524					    "too many generation count "
525					    "mismatches: page 0x%x gc %u "
526					    "previous page %u", dp->spd_gcoff,
527					    gc, sp->ss_generation);
528					ses_snap_free((ses_snap_t *)sp);
529					return (NULL);
530				}
531				goto again;
532			}
533		}
534	}
535
536	/*
537	 * The LIBSES_TRUNCATE environment variable is a debugging tool which,
538	 * if set, randomly truncates all pages (except
539	 * SES2_DIAGPAGE_SUPPORTED_PAGES).  In order to be truly evil, we
540	 * mmap() each page with enough space after it so we can move the data
541	 * up to the end of a page and unmap the following page so that any
542	 * attempt to read past the end of the page results in a segfault.
543	 */
544	if (sp->ss_target->st_truncate) {
545		pagesize = PAGESIZE;
546
547		/*
548		 * Count the maximum number of pages we will need and allocate
549		 * the necessary space.
550		 */
551		pages = 0;
552		for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) {
553			if (pp->ssp_control || pp->ssp_len == 0)
554				continue;
555
556			pages += (P2ROUNDUP(pp->ssp_len, pagesize) /
557			    pagesize) + 1;
558		}
559
560		if ((scratch = mmap(NULL, pages * pagesize,
561		    PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE,
562		    -1, 0)) == MAP_FAILED) {
563			(void) ses_error(ESES_NOMEM,
564			    "failed to mmap() pages for truncation");
565			ses_snap_free(sp);
566			return (NULL);
567		}
568
569		for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) {
570			if (pp->ssp_control || pp->ssp_len == 0)
571				continue;
572
573			pages = P2ROUNDUP(pp->ssp_len, pagesize) / pagesize;
574			pp->ssp_mmap_base = scratch;
575			pp->ssp_mmap_len = pages * pagesize;
576
577			pagelen = lrand48() % pp->ssp_len;
578			(void) memcpy(pp->ssp_mmap_base + pp->ssp_mmap_len -
579			    pagelen, pp->ssp_page, pagelen);
580			ses_free(pp->ssp_page);
581			pp->ssp_page = pp->ssp_mmap_base + pp->ssp_mmap_len -
582			    pagelen;
583			pp->ssp_len = pagelen;
584
585			(void) munmap(pp->ssp_mmap_base + pages * pagesize,
586			    pagesize);
587			scratch += (pages + 1) * pagesize;
588		}
589	}
590
591
592	if (ses_fill_snap(sp) != 0) {
593		ses_snap_free(sp);
594		return (NULL);
595	}
596
597	(void) pthread_mutex_lock(&tp->st_lock);
598	if (tp->st_snapshots != NULL)
599		ses_snap_rele_unlocked(tp->st_snapshots);
600	sp->ss_next = tp->st_snapshots;
601	if (tp->st_snapshots != NULL)
602		tp->st_snapshots->ss_prev = sp;
603	tp->st_snapshots = sp;
604	sp->ss_refcnt = 2;
605	(void) pthread_mutex_unlock(&tp->st_lock);
606
607	return (sp);
608}
609
610int
611ses_snap_do_ctl(ses_snap_t *sp)
612{
613	ses_snap_page_t *pp, *up;
614	int ret = -1;
615
616	for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) {
617		if (!pp->ssp_control)
618			continue;
619
620		if (pp->ssp_initialized && send_control_page(sp, pp) != 0)
621			goto error;
622
623		for (up = pp->ssp_unique; up != NULL; up = up->ssp_next) {
624			if (send_control_page(sp, up) != 0)
625				goto error;
626		}
627	}
628
629	ret = 0;
630error:
631	for (pp = sp->ss_pages; pp != NULL; pp = pp->ssp_next) {
632		if (!pp->ssp_control)
633			continue;
634
635		pp->ssp_initialized = B_FALSE;
636		while ((up = pp->ssp_unique) != NULL) {
637			pp->ssp_unique = up->ssp_next;
638			free_snap_page(up);
639		}
640	}
641
642
643	return (ret);
644}
645
646uint32_t
647ses_snap_generation(ses_snap_t *sp)
648{
649	return (sp->ss_generation);
650}
651
652static ses_walk_action_t
653ses_walk_node(ses_node_t *np, ses_walk_f func, void *arg)
654{
655	ses_walk_action_t action;
656
657	for (; np != NULL; np = ses_node_sibling(np)) {
658		action = func(np, arg);
659		if (action == SES_WALK_ACTION_TERMINATE)
660			return (SES_WALK_ACTION_TERMINATE);
661		if (action == SES_WALK_ACTION_PRUNE ||
662		    ses_node_child(np) == NULL)
663			continue;
664		if (ses_walk_node(ses_node_child(np), func, arg) ==
665		    SES_WALK_ACTION_TERMINATE)
666			return (SES_WALK_ACTION_TERMINATE);
667	}
668
669	return (SES_WALK_ACTION_CONTINUE);
670}
671
672int
673ses_walk(ses_snap_t *sp, ses_walk_f func, void *arg)
674{
675	(void) ses_walk_node(ses_root_node(sp), func, arg);
676
677	return (0);
678}
679
680/*ARGSUSED*/
681static ses_walk_action_t
682ses_fill_nodes(ses_node_t *np, void *unused)
683{
684	np->sn_snapshot->ss_nodes[np->sn_id] = np;
685
686	return (SES_WALK_ACTION_CONTINUE);
687}
688
689/*
690 * Given an ID returned by ses_node_id(), lookup and return the corresponding
691 * node in the snapshot.  If the snapshot generation count has changed, then
692 * return failure.
693 */
694ses_node_t *
695ses_node_lookup(ses_snap_t *sp, uint64_t id)
696{
697	uint32_t gen = (id >> 32);
698	uint32_t idx = (id & 0xFFFFFFFF);
699
700	if (sp->ss_generation != gen) {
701		(void) ses_set_errno(ESES_CHANGED);
702		return (NULL);
703	}
704
705	if (idx >= sp->ss_n_nodes) {
706		(void) ses_error(ESES_BAD_NODE,
707		    "no such node in snapshot");
708		return (NULL);
709	}
710
711	/*
712	 * If this is our first lookup attempt, construct the array for fast
713	 * lookups.
714	 */
715	if (sp->ss_nodes == NULL) {
716		if ((sp->ss_nodes = ses_zalloc(
717		    sp->ss_n_nodes * sizeof (void *))) == NULL)
718			return (NULL);
719
720		(void) ses_walk(sp, ses_fill_nodes, NULL);
721	}
722
723	if (sp->ss_nodes[idx] == NULL)
724		(void) ses_error(ESES_BAD_NODE,
725		    "no such node in snapshot");
726	return (sp->ss_nodes[idx]);
727}
728