1 /*- 2 * Copyright (c) 2008, 2009 Yahoo!, Inc. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. The names of the authors may not be used to endorse or promote 14 * products derived from this software without specific prior written 15 * permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 * 29 * $FreeBSD$ 30 */ 31 32 #include <sys/types.h> 33 #include <sys/errno.h> 34 #include <ctype.h> 35 #include <err.h> 36 #include <fcntl.h> 37 #include <libutil.h> 38 #include <limits.h> 39 #include <stdio.h> 40 #include <stdlib.h> 41 #include <string.h> 42 #include <strings.h> 43 #include <unistd.h> 44 #include <cam/scsi/scsi_all.h> 45 #include "mfiutil.h" 46 47 MFI_TABLE(top, drive); 48 49 /* 50 * Print the name of a drive either by drive number as %2u or by enclosure:slot 51 * as Exx:Sxx (or both). Use default unless command line options override it 52 * and the command allows this (which we usually do unless we already print 53 * both). We prefer pinfo if given, otherwise try to look it up by device_id. 54 */ 55 const char * 56 mfi_drive_name(struct mfi_pd_info *pinfo, uint16_t device_id, uint32_t def) 57 { 58 struct mfi_pd_info info; 59 static char buf[16]; 60 char *p; 61 int error, fd, len; 62 63 if ((def & MFI_DNAME_HONOR_OPTS) != 0 && 64 (mfi_opts & (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID)) != 0) 65 def = mfi_opts & (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID); 66 67 buf[0] = '\0'; 68 if (pinfo == NULL && def & MFI_DNAME_ES) { 69 /* Fallback in case of error, just ignore flags. */ 70 if (device_id == 0xffff) 71 snprintf(buf, sizeof(buf), "MISSING"); 72 else 73 snprintf(buf, sizeof(buf), "%2u", device_id); 74 75 fd = mfi_open(mfi_unit, O_RDWR); 76 if (fd < 0) { 77 warn("mfi_open"); 78 return (buf); 79 } 80 81 /* Get the info for this drive. */ 82 if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { 83 warn("Failed to fetch info for drive %2u", device_id); 84 close(fd); 85 return (buf); 86 } 87 88 close(fd); 89 pinfo = &info; 90 } 91 92 p = buf; 93 len = sizeof(buf); 94 if (def & MFI_DNAME_DEVICE_ID) { 95 if (device_id == 0xffff) 96 error = snprintf(p, len, "MISSING"); 97 else 98 error = snprintf(p, len, "%2u", device_id); 99 if (error >= 0) { 100 p += error; 101 len -= error; 102 } 103 } 104 if ((def & (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID)) == 105 (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID) && len >= 2) { 106 *p++ = ' '; 107 len--; 108 *p = '\0'; 109 len--; 110 } 111 if (def & MFI_DNAME_ES) { 112 if (pinfo->encl_device_id == 0xffff) 113 error = snprintf(p, len, "S%u", 114 pinfo->slot_number); 115 else if (pinfo->encl_device_id == pinfo->ref.v.device_id) 116 error = snprintf(p, len, "E%u", 117 pinfo->encl_index); 118 else 119 error = snprintf(p, len, "E%u:S%u", 120 pinfo->encl_index, pinfo->slot_number); 121 if (error >= 0) { 122 p += error; 123 len -= error; 124 } 125 } 126 127 return (buf); 128 } 129 130 const char * 131 mfi_pdstate(enum mfi_pd_state state) 132 { 133 static char buf[16]; 134 135 switch (state) { 136 case MFI_PD_STATE_UNCONFIGURED_GOOD: 137 return ("UNCONFIGURED GOOD"); 138 case MFI_PD_STATE_UNCONFIGURED_BAD: 139 return ("UNCONFIGURED BAD"); 140 case MFI_PD_STATE_HOT_SPARE: 141 return ("HOT SPARE"); 142 case MFI_PD_STATE_OFFLINE: 143 return ("OFFLINE"); 144 case MFI_PD_STATE_FAILED: 145 return ("FAILED"); 146 case MFI_PD_STATE_REBUILD: 147 return ("REBUILD"); 148 case MFI_PD_STATE_ONLINE: 149 return ("ONLINE"); 150 case MFI_PD_STATE_COPYBACK: 151 return ("COPYBACK"); 152 case MFI_PD_STATE_SYSTEM: 153 return ("JBOD"); 154 default: 155 sprintf(buf, "PSTATE 0x%04x", state); 156 return (buf); 157 } 158 } 159 160 int 161 mfi_lookup_drive(int fd, char *drive, uint16_t *device_id) 162 { 163 struct mfi_pd_list *list; 164 long val; 165 int error; 166 u_int i; 167 char *cp; 168 uint8_t encl, slot; 169 170 /* Look for a raw device id first. */ 171 val = strtol(drive, &cp, 0); 172 if (*cp == '\0') { 173 if (val < 0 || val >= 0xffff) 174 goto bad; 175 *device_id = val; 176 return (0); 177 } 178 179 /* Support for MegaCli style [Exx]:Syy notation. */ 180 if (toupper(drive[0]) == 'E' || toupper(drive[0]) == 'S') { 181 if (drive[1] == '\0') 182 goto bad; 183 cp = drive; 184 if (toupper(drive[0]) == 'E') { 185 cp++; /* Eat 'E' */ 186 val = strtol(cp, &cp, 0); 187 if (val < 0 || val > 0xff || *cp != ':') 188 goto bad; 189 encl = val; 190 cp++; /* Eat ':' */ 191 if (toupper(*cp) != 'S') 192 goto bad; 193 } else 194 encl = 0xff; 195 cp++; /* Eat 'S' */ 196 if (*cp == '\0') 197 goto bad; 198 val = strtol(cp, &cp, 0); 199 if (val < 0 || val > 0xff || *cp != '\0') 200 goto bad; 201 slot = val; 202 203 if (mfi_pd_get_list(fd, &list, NULL) < 0) { 204 error = errno; 205 warn("Failed to fetch drive list"); 206 return (error); 207 } 208 209 for (i = 0; i < list->count; i++) { 210 if (list->addr[i].scsi_dev_type != 0) 211 continue; 212 213 if (((encl == 0xff && 214 list->addr[i].encl_device_id == 0xffff) || 215 list->addr[i].encl_index == encl) && 216 list->addr[i].slot_number == slot) { 217 *device_id = list->addr[i].device_id; 218 free(list); 219 return (0); 220 } 221 } 222 free(list); 223 warnx("Unknown drive %s", drive); 224 return (EINVAL); 225 } 226 227 bad: 228 warnx("Invalid drive number %s", drive); 229 return (EINVAL); 230 } 231 232 static void 233 mbox_store_device_id(uint8_t *mbox, uint16_t device_id) 234 { 235 236 mbox[0] = device_id & 0xff; 237 mbox[1] = device_id >> 8; 238 } 239 240 void 241 mbox_store_pdref(uint8_t *mbox, union mfi_pd_ref *ref) 242 { 243 244 mbox[0] = ref->v.device_id & 0xff; 245 mbox[1] = ref->v.device_id >> 8; 246 mbox[2] = ref->v.seq_num & 0xff; 247 mbox[3] = ref->v.seq_num >> 8; 248 } 249 250 int 251 mfi_pd_get_list(int fd, struct mfi_pd_list **listp, uint8_t *statusp) 252 { 253 struct mfi_pd_list *list; 254 uint32_t list_size; 255 256 /* 257 * Keep fetching the list in a loop until we have a large enough 258 * buffer to hold the entire list. 259 */ 260 list = NULL; 261 list_size = 1024; 262 fetch: 263 list = reallocf(list, list_size); 264 if (list == NULL) 265 return (-1); 266 if (mfi_dcmd_command(fd, MFI_DCMD_PD_GET_LIST, list, list_size, NULL, 267 0, statusp) < 0) { 268 free(list); 269 return (-1); 270 } 271 272 if (list->size > list_size) { 273 list_size = list->size; 274 goto fetch; 275 } 276 277 *listp = list; 278 return (0); 279 } 280 281 int 282 mfi_pd_get_info(int fd, uint16_t device_id, struct mfi_pd_info *info, 283 uint8_t *statusp) 284 { 285 uint8_t mbox[2]; 286 287 mbox_store_device_id(&mbox[0], device_id); 288 return (mfi_dcmd_command(fd, MFI_DCMD_PD_GET_INFO, info, 289 sizeof(struct mfi_pd_info), mbox, 2, statusp)); 290 } 291 292 static void 293 cam_strvis(char *dst, const char *src, int srclen, int dstlen) 294 { 295 296 /* Trim leading/trailing spaces, nulls. */ 297 while (srclen > 0 && src[0] == ' ') 298 src++, srclen--; 299 while (srclen > 0 300 && (src[srclen-1] == ' ' || src[srclen-1] == '\0')) 301 srclen--; 302 303 while (srclen > 0 && dstlen > 1) { 304 char *cur_pos = dst; 305 306 if (*src < 0x20) { 307 /* SCSI-II Specifies that these should never occur. */ 308 /* non-printable character */ 309 if (dstlen > 4) { 310 *cur_pos++ = '\\'; 311 *cur_pos++ = ((*src & 0300) >> 6) + '0'; 312 *cur_pos++ = ((*src & 0070) >> 3) + '0'; 313 *cur_pos++ = ((*src & 0007) >> 0) + '0'; 314 } else { 315 *cur_pos++ = '?'; 316 } 317 } else { 318 /* normal character */ 319 *cur_pos++ = *src; 320 } 321 src++; 322 srclen--; 323 dstlen -= cur_pos - dst; 324 dst = cur_pos; 325 } 326 *dst = '\0'; 327 } 328 329 /* Borrowed heavily from scsi_all.c:scsi_print_inquiry(). */ 330 const char * 331 mfi_pd_inq_string(struct mfi_pd_info *info) 332 { 333 struct scsi_inquiry_data iqd, *inq_data = &iqd; 334 char vendor[16], product[48], revision[16], rstr[12], serial[SID_VENDOR_SPECIFIC_0_SIZE]; 335 static char inq_string[64]; 336 337 memcpy(inq_data, info->inquiry_data, 338 (sizeof (iqd) < sizeof (info->inquiry_data))? 339 sizeof (iqd) : sizeof (info->inquiry_data)); 340 if (SID_QUAL_IS_VENDOR_UNIQUE(inq_data)) 341 return (NULL); 342 if (SID_TYPE(inq_data) != T_DIRECT) 343 return (NULL); 344 if (SID_QUAL(inq_data) != SID_QUAL_LU_CONNECTED) 345 return (NULL); 346 347 cam_strvis(vendor, inq_data->vendor, sizeof(inq_data->vendor), 348 sizeof(vendor)); 349 cam_strvis(product, inq_data->product, sizeof(inq_data->product), 350 sizeof(product)); 351 cam_strvis(revision, inq_data->revision, sizeof(inq_data->revision), 352 sizeof(revision)); 353 cam_strvis(serial, (char *)inq_data->vendor_specific0, sizeof(inq_data->vendor_specific0), 354 sizeof(serial)); 355 356 /* Hack for SATA disks, no idea how to tell speed. */ 357 if (strcmp(vendor, "ATA") == 0) { 358 snprintf(inq_string, sizeof(inq_string), "<%s %s serial=%s> SATA", 359 product, revision, serial); 360 return (inq_string); 361 } 362 363 switch (SID_ANSI_REV(inq_data)) { 364 case SCSI_REV_CCS: 365 strcpy(rstr, "SCSI-CCS"); 366 break; 367 case 5: 368 strcpy(rstr, "SAS"); 369 break; 370 default: 371 snprintf(rstr, sizeof (rstr), "SCSI-%d", 372 SID_ANSI_REV(inq_data)); 373 break; 374 } 375 snprintf(inq_string, sizeof(inq_string), "<%s %s %s serial=%s> %s", vendor, 376 product, revision, serial, rstr); 377 return (inq_string); 378 } 379 380 /* Helper function to set a drive to a given state. */ 381 static int 382 drive_set_state(char *drive, uint16_t new_state) 383 { 384 struct mfi_pd_info info; 385 uint16_t device_id; 386 uint8_t mbox[6]; 387 int error, fd; 388 389 fd = mfi_open(mfi_unit, O_RDWR); 390 if (fd < 0) { 391 error = errno; 392 warn("mfi_open"); 393 return (error); 394 } 395 396 error = mfi_lookup_drive(fd, drive, &device_id); 397 if (error) { 398 close(fd); 399 return (error); 400 } 401 402 /* Get the info for this drive. */ 403 if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { 404 error = errno; 405 warn("Failed to fetch info for drive %u", device_id); 406 close(fd); 407 return (error); 408 } 409 410 /* Try to change the state. */ 411 if (info.fw_state == new_state) { 412 warnx("Drive %u is already in the desired state", device_id); 413 close(fd); 414 return (EINVAL); 415 } 416 417 mbox_store_pdref(&mbox[0], &info.ref); 418 mbox[4] = new_state & 0xff; 419 mbox[5] = new_state >> 8; 420 if (mfi_dcmd_command(fd, MFI_DCMD_PD_STATE_SET, NULL, 0, mbox, 6, 421 NULL) < 0) { 422 error = errno; 423 warn("Failed to set drive %u to %s", device_id, 424 mfi_pdstate(new_state)); 425 close(fd); 426 return (error); 427 } 428 429 close(fd); 430 431 return (0); 432 } 433 434 static int 435 fail_drive(int ac, char **av) 436 { 437 438 if (ac != 2) { 439 warnx("fail: %s", ac > 2 ? "extra arguments" : 440 "drive required"); 441 return (EINVAL); 442 } 443 444 return (drive_set_state(av[1], MFI_PD_STATE_FAILED)); 445 } 446 MFI_COMMAND(top, fail, fail_drive); 447 448 static int 449 good_drive(int ac, char **av) 450 { 451 452 if (ac != 2) { 453 warnx("good: %s", ac > 2 ? "extra arguments" : 454 "drive required"); 455 return (EINVAL); 456 } 457 458 return (drive_set_state(av[1], MFI_PD_STATE_UNCONFIGURED_GOOD)); 459 } 460 MFI_COMMAND(top, good, good_drive); 461 462 static int 463 rebuild_drive(int ac, char **av) 464 { 465 466 if (ac != 2) { 467 warnx("rebuild: %s", ac > 2 ? "extra arguments" : 468 "drive required"); 469 return (EINVAL); 470 } 471 472 return (drive_set_state(av[1], MFI_PD_STATE_REBUILD)); 473 } 474 MFI_COMMAND(top, rebuild, rebuild_drive); 475 476 static int 477 start_rebuild(int ac, char **av) 478 { 479 struct mfi_pd_info info; 480 uint16_t device_id; 481 uint8_t mbox[4]; 482 int error, fd; 483 484 if (ac != 2) { 485 warnx("start rebuild: %s", ac > 2 ? "extra arguments" : 486 "drive required"); 487 return (EINVAL); 488 } 489 490 fd = mfi_open(mfi_unit, O_RDWR); 491 if (fd < 0) { 492 error = errno; 493 warn("mfi_open"); 494 return (error); 495 } 496 497 error = mfi_lookup_drive(fd, av[1], &device_id); 498 if (error) { 499 close(fd); 500 return (error); 501 } 502 503 /* Get the info for this drive. */ 504 if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { 505 error = errno; 506 warn("Failed to fetch info for drive %u", device_id); 507 close(fd); 508 return (error); 509 } 510 511 /* Check the state, must be REBUILD. */ 512 if (info.fw_state != MFI_PD_STATE_REBUILD) { 513 warnx("Drive %d is not in the REBUILD state", device_id); 514 close(fd); 515 return (EINVAL); 516 } 517 518 /* Start the rebuild. */ 519 mbox_store_pdref(&mbox[0], &info.ref); 520 if (mfi_dcmd_command(fd, MFI_DCMD_PD_REBUILD_START, NULL, 0, mbox, 4, 521 NULL) < 0) { 522 error = errno; 523 warn("Failed to start rebuild on drive %u", device_id); 524 close(fd); 525 return (error); 526 } 527 close(fd); 528 529 return (0); 530 } 531 MFI_COMMAND(start, rebuild, start_rebuild); 532 533 static int 534 abort_rebuild(int ac, char **av) 535 { 536 struct mfi_pd_info info; 537 uint16_t device_id; 538 uint8_t mbox[4]; 539 int error, fd; 540 541 if (ac != 2) { 542 warnx("abort rebuild: %s", ac > 2 ? "extra arguments" : 543 "drive required"); 544 return (EINVAL); 545 } 546 547 fd = mfi_open(mfi_unit, O_RDWR); 548 if (fd < 0) { 549 error = errno; 550 warn("mfi_open"); 551 return (error); 552 } 553 554 error = mfi_lookup_drive(fd, av[1], &device_id); 555 if (error) { 556 close(fd); 557 return (error); 558 } 559 560 /* Get the info for this drive. */ 561 if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { 562 error = errno; 563 warn("Failed to fetch info for drive %u", device_id); 564 close(fd); 565 return (error); 566 } 567 568 /* Check the state, must be REBUILD. */ 569 if (info.fw_state != MFI_PD_STATE_REBUILD) { 570 warn("Drive %d is not in the REBUILD state", device_id); 571 close(fd); 572 return (EINVAL); 573 } 574 575 /* Abort the rebuild. */ 576 mbox_store_pdref(&mbox[0], &info.ref); 577 if (mfi_dcmd_command(fd, MFI_DCMD_PD_REBUILD_ABORT, NULL, 0, mbox, 4, 578 NULL) < 0) { 579 error = errno; 580 warn("Failed to abort rebuild on drive %u", device_id); 581 close(fd); 582 return (error); 583 } 584 close(fd); 585 586 return (0); 587 } 588 MFI_COMMAND(abort, rebuild, abort_rebuild); 589 590 static int 591 drive_progress(int ac, char **av) 592 { 593 struct mfi_pd_info info; 594 uint16_t device_id; 595 int error, fd; 596 597 if (ac != 2) { 598 warnx("drive progress: %s", ac > 2 ? "extra arguments" : 599 "drive required"); 600 return (EINVAL); 601 } 602 603 fd = mfi_open(mfi_unit, O_RDWR); 604 if (fd < 0) { 605 error = errno; 606 warn("mfi_open"); 607 return (error); 608 } 609 610 error = mfi_lookup_drive(fd, av[1], &device_id); 611 if (error) { 612 close(fd); 613 return (error); 614 } 615 616 /* Get the info for this drive. */ 617 if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { 618 error = errno; 619 warn("Failed to fetch info for drive %u", device_id); 620 close(fd); 621 return (error); 622 } 623 close(fd); 624 625 /* Display any of the active events. */ 626 if (info.prog_info.active & MFI_PD_PROGRESS_REBUILD) 627 mfi_display_progress("Rebuild", &info.prog_info.rbld); 628 if (info.prog_info.active & MFI_PD_PROGRESS_PATROL) 629 mfi_display_progress("Patrol Read", &info.prog_info.patrol); 630 if (info.prog_info.active & MFI_PD_PROGRESS_CLEAR) 631 mfi_display_progress("Clear", &info.prog_info.clear); 632 if ((info.prog_info.active & (MFI_PD_PROGRESS_REBUILD | 633 MFI_PD_PROGRESS_PATROL | MFI_PD_PROGRESS_CLEAR)) == 0) 634 printf("No activity in progress for drive %s.\n", 635 mfi_drive_name(NULL, device_id, 636 MFI_DNAME_DEVICE_ID|MFI_DNAME_HONOR_OPTS)); 637 638 return (0); 639 } 640 MFI_COMMAND(drive, progress, drive_progress); 641 642 static int 643 drive_clear(int ac, char **av) 644 { 645 struct mfi_pd_info info; 646 uint32_t opcode; 647 uint16_t device_id; 648 uint8_t mbox[4]; 649 char *s1; 650 int error, fd; 651 652 if (ac != 3) { 653 warnx("drive clear: %s", ac > 3 ? "extra arguments" : 654 "drive and action requires"); 655 return (EINVAL); 656 } 657 658 for (s1 = av[2]; *s1 != '\0'; s1++) 659 *s1 = tolower(*s1); 660 if (strcmp(av[2], "start") == 0) 661 opcode = MFI_DCMD_PD_CLEAR_START; 662 else if ((strcmp(av[2], "stop") == 0) || (strcmp(av[2], "abort") == 0)) 663 opcode = MFI_DCMD_PD_CLEAR_ABORT; 664 else { 665 warnx("drive clear: invalid action, must be 'start' or 'stop'\n"); 666 return (EINVAL); 667 } 668 669 fd = mfi_open(mfi_unit, O_RDWR); 670 if (fd < 0) { 671 error = errno; 672 warn("mfi_open"); 673 return (error); 674 } 675 676 error = mfi_lookup_drive(fd, av[1], &device_id); 677 if (error) { 678 close(fd); 679 return (error); 680 } 681 682 /* Get the info for this drive. */ 683 if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { 684 error = errno; 685 warn("Failed to fetch info for drive %u", device_id); 686 close(fd); 687 return (error); 688 } 689 690 mbox_store_pdref(&mbox[0], &info.ref); 691 if (mfi_dcmd_command(fd, opcode, NULL, 0, mbox, 4, NULL) < 0) { 692 error = errno; 693 warn("Failed to %s clear on drive %u", 694 opcode == MFI_DCMD_PD_CLEAR_START ? "start" : "stop", 695 device_id); 696 close(fd); 697 return (error); 698 } 699 700 close(fd); 701 return (0); 702 } 703 MFI_COMMAND(drive, clear, drive_clear); 704 705 static int 706 drive_locate(int ac, char **av) 707 { 708 uint16_t device_id; 709 uint32_t opcode; 710 int error, fd; 711 uint8_t mbox[4]; 712 713 if (ac != 3) { 714 warnx("locate: %s", ac > 3 ? "extra arguments" : 715 "drive and state required"); 716 return (EINVAL); 717 } 718 719 if (strcasecmp(av[2], "on") == 0 || strcasecmp(av[2], "start") == 0) 720 opcode = MFI_DCMD_PD_LOCATE_START; 721 else if (strcasecmp(av[2], "off") == 0 || 722 strcasecmp(av[2], "stop") == 0) 723 opcode = MFI_DCMD_PD_LOCATE_STOP; 724 else { 725 warnx("locate: invalid state %s", av[2]); 726 return (EINVAL); 727 } 728 729 fd = mfi_open(mfi_unit, O_RDWR); 730 if (fd < 0) { 731 error = errno; 732 warn("mfi_open"); 733 return (error); 734 } 735 736 error = mfi_lookup_drive(fd, av[1], &device_id); 737 if (error) { 738 close(fd); 739 return (error); 740 } 741 742 743 mbox_store_device_id(&mbox[0], device_id); 744 mbox[2] = 0; 745 mbox[3] = 0; 746 if (mfi_dcmd_command(fd, opcode, NULL, 0, mbox, 4, NULL) < 0) { 747 error = errno; 748 warn("Failed to %s locate on drive %u", 749 opcode == MFI_DCMD_PD_LOCATE_START ? "start" : "stop", 750 device_id); 751 close(fd); 752 return (error); 753 } 754 close(fd); 755 756 return (0); 757 } 758 MFI_COMMAND(top, locate, drive_locate); 759