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