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 29 ses_snap_page_t * 30 ses_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 42 static int 43 grow_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 61 static ses_snap_page_t * 62 alloc_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 80 static void 81 free_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 93 static void 94 free_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 */ 113 ses_snap_page_t * 114 ses_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 174 static int 175 read_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 203 again: 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 275 static int 276 send_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 319 static int 320 pages_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 393 static void 394 ses_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 402 static void 403 ses_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 421 ses_snap_t * 422 ses_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 434 void 435 ses_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 444 ses_snap_t * 445 ses_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 461 again: 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 610 int 611 ses_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; 630 error: 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 646 uint32_t 647 ses_snap_generation(ses_snap_t *sp) 648 { 649 return (sp->ss_generation); 650 } 651 652 static ses_walk_action_t 653 ses_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 672 int 673 ses_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*/ 681 static ses_walk_action_t 682 ses_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 */ 694 ses_node_t * 695 ses_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