1 /*
   2  * This file and its contents are supplied under the terms of the
   3  * Common Development and Distribution License ("CDDL"), version 1.0.
   4  * You may only use this file in accordance with the terms of version
   5  * 1.0 of the CDDL.
   6  *
   7  * A full copy of the text of the CDDL should have accompanied this
   8  * source.  A copy of the CDDL is also available via the Internet at
   9  * http://www.illumos.org/license/CDDL.
  10  */
  11 
  12 /*
  13  * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
  14  */
  15 
  16 #include <stdio.h>
  17 #include <unistd.h>
  18 #include <ctype.h>
  19 #include <sys/types.h>
  20 #include <stddef.h>
  21 #include <strings.h>
  22 #include <sys/list.h>
  23 #include <libdevinfo.h>
  24 #include <sys/stat.h>
  25 #include <sys/scsi/impl/uscsi.h>
  26 #include <sys/scsi/generic/inquiry.h>
  27 #include <fcntl.h>
  28 #include <sys/scsi/scsi.h>
  29 #include <assert.h>
  30 #include <libdiskmgt.h>
  31 #include <libintl.h>
  32 
  33 #define VERSION "1.2"
  34 
  35 #define DISK_DRIVER "sd"
  36 
  37 #define DEVICES_DIR "/devices"
  38 
  39 #define DEFAULT_WAIT 5
  40 
  41 /* download, save and activate microcode in one write buffer command */
  42 #define DEFAULT_WB_MODE 0x05
  43 
  44 /* will set timeout to 30 sec for all USCSI commands that we will issue */
  45 #define DISK_CMD_TIMEOUT 30
  46 
  47 #define IMPOSSIBLE_RQ_STATUS 0xff
  48 
  49 /* length bigger then this can not be represented by 3 bytes */
  50 #define MAX_FW_SIZE_IN_BYTES 16777215
  51 
  52 /* used for mode 7 write buffer (32K) */
  53 #define BYTES_IN_CHUNK 32768
  54 
  55 /* 10 is strlen("/dev/rdsk/") */
  56 #define CTD_START_IN_PATH(path) (path + 10)
  57 
  58 #define MAX_DISK_LEN 9 /* enough space for a million disk names: sd0-sd999999 */
  59 #define INQ_VENDOR_LEN 9 /* struct scsi_inquiry defines these fields at: */
  60 #define INQ_MODEL_LEN 17 /* 8, 16 and 4 chars */
  61 #define INQ_REV_LEN 5 /* without string terminating zeros */
  62 
  63 #define MAX_INUSE_LEN 255
  64 
  65 /* to reduce clutter and fprintf length */
  66 #define _(x) (gettext(x))
  67 
  68 /* struct to hold relevant disk info */
  69 typedef struct disk_info {
  70         list_node_t link;
  71         char device[MAX_DISK_LEN];
  72         char vendor[INQ_VENDOR_LEN];
  73         char model[INQ_MODEL_LEN];
  74         char rev[INQ_REV_LEN];
  75         char path[MAXPATHLEN];
  76         char inuse_msg[MAX_INUSE_LEN];
  77         boolean_t inuse;
  78         int bad_info;
  79 } disk_info_t;
  80 
  81 /* prints sense data and status */
  82 void
  83 print_status_and_sense(char *cmd, int rc, int status,
  84                 struct scsi_extended_sense *sense)
  85 {
  86         (void) fprintf(stdout, _("%s ioctl() returned: %d status: 0x%02x, "
  87             "sense - skey: 0x%02x, asc: 0x%02x, ascq: 0x%02x\n"), cmd, rc,
  88             status & STATUS_MASK, sense->es_key, sense->es_add_code,
  89             sense->es_qual_code);
  90 }
  91 
  92 /* determine if the uscsi cmd failed or not */
  93 boolean_t
  94 uscsi_parse_status(struct uscsi_cmd *ucmd, int rc, boolean_t verbose)
  95 {
  96         struct scsi_extended_sense *sense;
  97 
  98         if (rc == -1 && errno == EAGAIN) {
  99                 if (verbose)
 100                         (void) fprintf(stderr, _("Drive is temporarily "
 101                             "unavailable.\n"));
 102                 return (B_FALSE); /* unavailable */
 103         }
 104 
 105         if ((ucmd->uscsi_status & STATUS_MASK) == STATUS_RESERVATION_CONFLICT) {
 106                 if (verbose)
 107                         (void) fprintf(stderr, _("Drive is reserved.\n"));
 108                 return (B_FALSE); /* reserved by another system */
 109         }
 110 
 111         if (rc == -1 && ucmd->uscsi_status == 0 && errno == EIO) {
 112                 if (verbose)
 113                         (void) fprintf(stderr, _("Drive is unavailable.\n"));
 114                 return (B_FALSE); /* unavailable */
 115         }
 116 
 117         /* check if we have valid sense status */
 118         if (ucmd->uscsi_rqstatus == IMPOSSIBLE_RQ_STATUS) {
 119                 if (verbose) {
 120                         (void) fprintf(stderr, _("No sense data for command "
 121                             "0x%02x.\n"), ucmd->uscsi_cdb[0]);
 122                 }
 123                 return (B_FALSE);
 124         }
 125 
 126         if (ucmd->uscsi_rqstatus != STATUS_GOOD) {
 127                 if (verbose) {
 128                         (void) fprintf(stderr, _("Sense status for command "
 129                             "0x%02x: 0x%02x.\n"), ucmd->uscsi_cdb[0],
 130                             ucmd->uscsi_rqstatus);
 131                 }
 132                 return (B_FALSE);
 133         }
 134 
 135         sense = (struct scsi_extended_sense *)ucmd->uscsi_rqbuf;
 136         if ((sense->es_key != KEY_RECOVERABLE_ERROR) &&
 137             (sense->es_key != KEY_NO_SENSE)) {
 138                 if (verbose && sense->es_key == KEY_ILLEGAL_REQUEST &&
 139                     sense->es_add_code == 0x2C && sense->es_qual_code == 0x0) {
 140                         (void) fprintf(stderr, _(" Illegal Request - Command "
 141                             "0x%02x sequence error.\n"), ucmd->uscsi_cdb[0]);
 142                 } else if (verbose && sense->es_key == KEY_ILLEGAL_REQUEST &&
 143                     sense->es_add_code == 0x24 && sense->es_qual_code == 0x0) {
 144                         (void) fprintf(stderr, _(" cmd 0x%02x: Illegal Request"
 145                             " - Invalid field in CDB for command.\n"),
 146                             ucmd->uscsi_cdb[0]);
 147                 } else if (verbose && sense->es_key == KEY_UNIT_ATTENTION &&
 148                     sense->es_add_code == 0x3F && sense->es_qual_code == 0x01) {
 149                         (void) fprintf(stderr, _(" cmd 0x%02x: Unit Attention "
 150                             "- Microcode changed.\n"), ucmd->uscsi_cdb[0]);
 151                 } else if (verbose && sense->es_key == KEY_UNIT_ATTENTION &&
 152                     sense->es_add_code == 0x29 && sense->es_qual_code == 0x0) {
 153                         (void) fprintf(stderr, _(" cmd 0x%02x: Unit Attention "
 154                             "- Reset Occurred.\n"), ucmd->uscsi_cdb[0]);
 155                 } else if (verbose) {
 156                         (void) fprintf(stderr, _("Command 0x%02x produced "
 157                             "sense data that indicated an error.\n"),
 158                             ucmd->uscsi_cdb[0]);
 159                 }
 160                 return (B_FALSE);
 161         }
 162 
 163         if (rc == -1 && errno == EIO) {
 164                 if (verbose) {
 165                         (void) fprintf(stderr, _("Command 0x%02x resulted in "
 166                             "I/O error.\n"), ucmd->uscsi_cdb[0]);
 167                 }
 168                 return (B_FALSE);
 169         }
 170 
 171         if (rc != 0) {
 172                 if (verbose) {
 173                         (void) fprintf(stderr, _("cmd 0x%02x: Unknown "
 174                             "error.\n"), ucmd->uscsi_cdb[0]);
 175                 }
 176                 return (B_FALSE);
 177         }
 178 
 179         if (verbose) {
 180                 (void) fprintf(stderr, _("USCSI command 0x%02x completed "
 181                     "successfully.\n"), ucmd->uscsi_cdb[0]);
 182         }
 183         return (B_TRUE);
 184 }
 185 
 186 /* dump raw hex cdb */
 187 void
 188 print_cdb(union scsi_cdb *cdb, const char *cmd, int cdb_len)
 189 {
 190         int i;
 191 
 192         (void) fprintf(stderr, "\n%s CDB (hex): ", cmd);
 193         for (i = 0; i < cdb_len; i++)
 194                 (void) fprintf(stderr, "%02x ", cdb->cdb_opaque[i]);
 195         (void) fprintf(stderr, "\n");
 196 }
 197 
 198 /* will write microcode located in fw_img to the disk_fd in chunk_size chunks */
 199 boolean_t
 200 uscsi_write_buffer_mode7(int disk_fd, uint8_t *fw_img,
 201     size_t fw_len, struct scsi_extended_sense *sense, boolean_t verbose)
 202 {
 203         struct uscsi_cmd ucmd;
 204         union scsi_cdb cdb;
 205         int i;
 206         boolean_t rc = B_FALSE;
 207         size_t chunk_size = BYTES_IN_CHUNK;
 208         size_t xfer_len = chunk_size;
 209         size_t remainder = fw_len % xfer_len;
 210 
 211         for (i = 0; (int32_t)(fw_len - (i * chunk_size + remainder)) >= 0;
 212             i++) {
 213                 if (fw_len - i * chunk_size == remainder)
 214                         xfer_len = remainder;
 215                 (void) memset(&ucmd, 0, sizeof (struct uscsi_cmd));
 216                 (void) memset(sense, 0, sizeof (struct scsi_extended_sense));
 217                 (void) memset(&cdb, 0, sizeof (union scsi_cdb));
 218 
 219                 cdb.scc_cmd = SCMD_WRITE_BUFFER;
 220                 /* bits 0-4 (inclusive) of byte 1 contains mode field */
 221                 cdb.cdb_opaque[1] = (0x07 & 0x1f);
 222                 /* bytes 3-5 contain drive buffer offset */
 223                 cdb.cdb_opaque[3] = (uint8_t)((i * chunk_size >> 16) & 0xff);
 224                 cdb.cdb_opaque[4] = (uint8_t)((i * chunk_size >> 8) & 0xff);
 225                 cdb.cdb_opaque[5] = (uint8_t)(i * chunk_size & 0xff);
 226                 /* bytes 6-8 contain fw chunk length */
 227                 cdb.cdb_opaque[6] = (uint8_t)((xfer_len >> 16) & 0xff);
 228                 cdb.cdb_opaque[7] = (uint8_t)((xfer_len >> 8) & 0xff);
 229                 cdb.cdb_opaque[8] = (uint8_t)(xfer_len & 0xff);
 230 
 231                 ucmd.uscsi_cdb = (caddr_t)&cdb;
 232                 ucmd.uscsi_cdblen = CDB_GROUP1;
 233                 ucmd.uscsi_flags = USCSI_SILENT | USCSI_WRITE | USCSI_ISOLATE;
 234                 ucmd.uscsi_flags |= USCSI_RQENABLE | USCSI_DIAGNOSE;
 235                 ucmd.uscsi_bufaddr = (void *)(fw_img + i * chunk_size);
 236                 ucmd.uscsi_buflen = xfer_len;
 237                 ucmd.uscsi_timeout = DISK_CMD_TIMEOUT;
 238                 ucmd.uscsi_rqbuf = (caddr_t)sense;
 239                 ucmd.uscsi_rqlen = sizeof (struct  scsi_extended_sense);
 240                 ucmd.uscsi_rqstatus = IMPOSSIBLE_RQ_STATUS;
 241 
 242                 if (verbose)
 243                         print_cdb(&cdb, "write buffer mode 0x07", CDB_GROUP1);
 244 
 245                 rc = ioctl(disk_fd, USCSICMD, &ucmd);
 246                 if (verbose)
 247                         print_status_and_sense("write buf 0x07", rc,
 248                             ucmd.uscsi_status, sense);
 249 
 250                 rc = uscsi_parse_status(&ucmd, rc, verbose);
 251 
 252                 if (sense->es_key == KEY_ILLEGAL_REQUEST &&
 253                     sense->es_add_code == 0x2C && sense->es_qual_code == 0x0) {
 254                         (void) fprintf(stderr, _(" Downloading of microcode has"
 255                             " failed - Command sequence error.\n"));
 256                         return (B_FALSE);
 257                 } else if (sense->es_key == KEY_ILLEGAL_REQUEST &&
 258                     sense->es_add_code == 0x24 && sense->es_qual_code == 0x0) {
 259                         (void) fprintf(stderr, _(" Downloading of microcode has"
 260                             " failed - Invalid field in CDB.\n"));
 261                         return (B_FALSE);
 262                 }
 263                 if (sense->es_key == KEY_UNIT_ATTENTION &&
 264                     sense->es_add_code == 0x3F && sense->es_qual_code == 0x01) {
 265                         (void) fprintf(stderr, _(" Microcode download "
 266                             "successful\n"));
 267                         return (B_TRUE);
 268                 }
 269         }
 270         return (rc);
 271 }
 272 
 273 /* will write whole microcode located in fw_img to the disk_fd */
 274 boolean_t
 275 uscsi_write_buffer_mode5(int disk_fd, void * fw_img,
 276     size_t fw_len, struct scsi_extended_sense *sense, boolean_t verbose)
 277 {
 278         struct uscsi_cmd ucmd;
 279         union scsi_cdb cdb;
 280         int rc;
 281 
 282         (void) memset(&ucmd, 0, sizeof (struct uscsi_cmd));
 283         (void) memset(sense, 0, sizeof (struct scsi_extended_sense));
 284         (void) memset(&cdb, 0, sizeof (union scsi_cdb));
 285 
 286         cdb.scc_cmd = SCMD_WRITE_BUFFER;
 287         /* bits 0-4 (inclusive) of byte 1 contains mode field */
 288         cdb.cdb_opaque[1] = (0x05 & 0x1f);
 289         /* bytes 6-8 contain fw file length */
 290         cdb.cdb_opaque[6] = (uint8_t)((fw_len >> 16) & 0xff);
 291         cdb.cdb_opaque[7] = (uint8_t)((fw_len >> 8) & 0xff);
 292         cdb.cdb_opaque[8] = (uint8_t)(fw_len & 0xff);
 293 
 294         ucmd.uscsi_cdb = (caddr_t)&cdb;
 295         ucmd.uscsi_cdblen = CDB_GROUP1;
 296         ucmd.uscsi_flags = USCSI_SILENT | USCSI_WRITE | USCSI_ISOLATE;
 297         ucmd.uscsi_flags |= USCSI_RQENABLE | USCSI_DIAGNOSE;
 298         ucmd.uscsi_bufaddr = fw_img;
 299         ucmd.uscsi_buflen = fw_len;
 300         ucmd.uscsi_timeout = DISK_CMD_TIMEOUT;
 301         ucmd.uscsi_rqbuf = (caddr_t)sense;
 302         ucmd.uscsi_rqlen = sizeof (struct  scsi_extended_sense);
 303         ucmd.uscsi_rqstatus = IMPOSSIBLE_RQ_STATUS;
 304 
 305         if (verbose)
 306                 print_cdb(&cdb, "write buffer mode 0x05", CDB_GROUP1);
 307 
 308         rc = ioctl(disk_fd, USCSICMD, &ucmd);
 309         if (verbose)
 310                 print_status_and_sense("write buf 0x05", rc, ucmd.uscsi_status,
 311                     sense);
 312 
 313         rc = uscsi_parse_status(&ucmd, rc, verbose);
 314 
 315         if (sense->es_key == KEY_ILLEGAL_REQUEST &&
 316             sense->es_add_code == 0x2C && sense->es_qual_code == 0x0) {
 317                 (void) fprintf(stderr, _(" Downloading of microcode has failed"
 318                     " - Command sequence error.\n"));
 319                 rc = B_FALSE;
 320         } else if (sense->es_key == KEY_ILLEGAL_REQUEST &&
 321             sense->es_add_code == 0x24 && sense->es_qual_code == 0x0) {
 322                 (void) fprintf(stderr, _(" Downloading of microcode has failed"
 323                     " - Invalid field in CDB.\n"));
 324                 rc = B_FALSE;
 325         } else if (sense->es_key == KEY_UNIT_ATTENTION &&
 326             sense->es_add_code == 0x3F && sense->es_qual_code == 0x01) {
 327                 (void) fprintf(stderr, _(" Microcode download successful\n"));
 328                 rc = B_TRUE;
 329         }
 330 
 331         return (rc);
 332 }
 333 
 334 /* Issue TUR command to device identified by file descriptor disk_fd */
 335 boolean_t
 336 uscsi_test_unit_ready(int disk_fd, struct scsi_extended_sense *sense,
 337                 boolean_t verbose)
 338 {
 339         struct uscsi_cmd ucmd;
 340         int rc;
 341         union scsi_cdb cdb;
 342 
 343         (void) memset(&ucmd, 0, sizeof (struct uscsi_cmd));
 344         (void) memset(sense, 0, sizeof (struct scsi_extended_sense));
 345         (void) memset(&cdb, 0, sizeof (union scsi_cdb));
 346 
 347         cdb.scc_cmd = SCMD_TEST_UNIT_READY;
 348 
 349         ucmd.uscsi_cdb = (caddr_t)&cdb;
 350         ucmd.uscsi_cdblen = CDB_GROUP0;
 351         ucmd.uscsi_flags = USCSI_SILENT | USCSI_DIAGNOSE | USCSI_RQENABLE;
 352         ucmd.uscsi_timeout = DISK_CMD_TIMEOUT;
 353         ucmd.uscsi_rqbuf = (caddr_t)sense;
 354         ucmd.uscsi_rqlen = sizeof (struct scsi_extended_sense);
 355         ucmd.uscsi_rqstatus = IMPOSSIBLE_RQ_STATUS;
 356 
 357         if (verbose)
 358                 print_cdb(&cdb, "test unit ready", CDB_GROUP0);
 359 
 360         rc = ioctl(disk_fd, USCSICMD, &ucmd);
 361         if (verbose)
 362                 print_status_and_sense("test unit ready", rc, ucmd.uscsi_status,
 363                     sense);
 364 
 365         return (uscsi_parse_status(&ucmd, rc, verbose));
 366 }
 367 
 368 /* Execute a uscsi inquiry command and put the resulting data into inqbuf. */
 369 boolean_t
 370 uscsi_inquiry(int disk_fd, struct scsi_inquiry *inqbuf,
 371                 struct scsi_extended_sense *sense, boolean_t verbose)
 372 {
 373         struct uscsi_cmd ucmd;
 374         union scsi_cdb cdb;
 375         int rc;
 376 
 377         (void) memset(inqbuf, 0, sizeof (struct scsi_inquiry));
 378         (void) memset(sense, 0, sizeof (struct scsi_extended_sense));
 379         (void) memset(&ucmd, 0, sizeof (ucmd));
 380         (void) memset(&cdb, 0, sizeof (union scsi_cdb));
 381 
 382         cdb.scc_cmd = SCMD_INQUIRY;
 383         /* bytes 3-4 contain data-in buf len */
 384         cdb.cdb_opaque[3] = (uint8_t)((sizeof (struct scsi_inquiry) >> 8) &
 385             0xff);
 386         cdb.cdb_opaque[4] = (uint8_t)((sizeof (struct scsi_inquiry)) & 0xff);
 387 
 388         ucmd.uscsi_cdb = (caddr_t)&cdb;
 389         ucmd.uscsi_cdblen = CDB_GROUP0;
 390         ucmd.uscsi_flags = USCSI_READ | USCSI_SILENT | USCSI_ISOLATE;
 391         ucmd.uscsi_flags |= USCSI_RQENABLE | USCSI_DIAGNOSE;
 392         ucmd.uscsi_bufaddr = (caddr_t)inqbuf;
 393         ucmd.uscsi_buflen = sizeof (struct scsi_inquiry);
 394         ucmd.uscsi_timeout = DISK_CMD_TIMEOUT;
 395         ucmd.uscsi_rqbuf = (caddr_t)sense;
 396         ucmd.uscsi_rqlen = sizeof (struct scsi_extended_sense);
 397         ucmd.uscsi_rqstatus = IMPOSSIBLE_RQ_STATUS;
 398 
 399         if (verbose)
 400                 print_cdb(&cdb, "inquiry", CDB_GROUP1);
 401 
 402         rc = ioctl(disk_fd, USCSICMD, &ucmd);
 403         if (verbose)
 404                 print_status_and_sense("inquiry", rc, ucmd.uscsi_status, sense);
 405 
 406         return (uscsi_parse_status(&ucmd, rc, verbose));
 407 }
 408 
 409 /* Issue start command to device identified by file descriptor disk_fd */
 410 boolean_t
 411 uscsi_start_unit(int disk_fd, struct scsi_extended_sense *sense,
 412     boolean_t verbose)
 413 {
 414         struct uscsi_cmd ucmd;
 415         union scsi_cdb cdb;
 416         int rc;
 417 
 418         (void) memset(&ucmd, 0, sizeof (struct uscsi_cmd));
 419         (void) memset(&cdb, 0, sizeof (union scsi_cdb));
 420         (void) memset(sense, 0, sizeof (struct scsi_extended_sense));
 421 
 422         cdb.scc_cmd = SCMD_START_STOP;
 423         /* bit 0 of byte 4 - start bit. bits 4-7 - power condition field */
 424         cdb.cdb_opaque[4] = 0x01;
 425 
 426         ucmd.uscsi_cdb = (caddr_t)&cdb;
 427         ucmd.uscsi_cdblen = CDB_GROUP0;
 428         ucmd.uscsi_flags = USCSI_SILENT | USCSI_DIAGNOSE | USCSI_RQENABLE;
 429         ucmd.uscsi_flags |= USCSI_ISOLATE;
 430         ucmd.uscsi_timeout = DISK_CMD_TIMEOUT;
 431         ucmd.uscsi_rqbuf = (caddr_t)sense;
 432         ucmd.uscsi_rqlen = sizeof (struct scsi_extended_sense);
 433         ucmd.uscsi_rqstatus = IMPOSSIBLE_RQ_STATUS;
 434 
 435         if (verbose)
 436                 print_cdb(&cdb, "start", CDB_GROUP0);
 437 
 438         rc = ioctl(disk_fd, USCSICMD, &ucmd);
 439         if (verbose)
 440                 print_status_and_sense("start", rc, ucmd.uscsi_status, sense);
 441 
 442         return (uscsi_parse_status(&ucmd, rc, verbose));
 443 }
 444 
 445 /* copy chars to out without trailing and leading "space" chars */
 446 void
 447 mem_trim_and_cpy(char *out, const char *buf, size_t buf_len) {
 448 
 449         while (buf_len != 0 && isspace(*buf) != 0) {
 450                 /* ignore leading space(s) */
 451                 buf++;
 452                 buf_len--;
 453         }
 454         while (buf_len && isspace(buf[buf_len - 1]))
 455                 buf_len--; /* ignore trailing space(s) */
 456 
 457         (void) memcpy(out, buf, buf_len);
 458         out[buf_len] = '\0'; /* stringify */
 459 }
 460 
 461 /* given a disk - check if it's in use */
 462 /* define NOINUSE_CHECK environment variable to turn of disk inuse check. */
 463 void
 464 set_disk_inuse(disk_info_t *disk, boolean_t verbose)
 465 {
 466         char *msg, *slice, *character, *plz;
 467         char dev[MAXPATHLEN];
 468         int error = 0;
 469         int i;
 470         dm_descriptor_t *slices = NULL;
 471         /*
 472          * Let libdiskmgt think we are the zpool consumer, meaning slices will
 473          * be considered "inuse" if they have a filesystem or an exported zpool
 474          */
 475         dm_who_type_t who = DM_WHO_ZPOOL;
 476 
 477         /* need to give dm_get_slices() a "whole" disk name, ie - c#t...d#p0 */
 478         (void) strncpy(dev, CTD_START_IN_PATH(disk->path), MAXPATHLEN);
 479         if ((character = strrchr(dev, 'd')) != NULL) {
 480                 character++;
 481                 while (isdigit(*character))
 482                         character++;
 483                 *character = '\0'; /* terminate the string after d# */
 484         }
 485 
 486         disk->inuse = B_FALSE;
 487         disk->inuse_msg[0] = '\0';
 488         dm_get_slices(dev, &slices, &error);
 489         if (error != 0) {
 490                 if (verbose) {
 491                         (void) fprintf(stderr, _("dm_get_slices() failed: "
 492                             "%s.\n Marking drive in use.\n"), strerror(error));
 493                 }
 494                 /*
 495                  * Marking disk as "in use" on errors since it's
 496                  * better to not touch a disk that MAY be in use by something.
 497                  */
 498                 disk->inuse = B_TRUE;
 499                 return;
 500         }
 501 
 502         /* loop through slices - set inuse */
 503         for (i = 0; slices[i]; i++) {
 504                 slice = dm_get_name(slices[i], &error);
 505                 if (dm_inuse(slice, &msg, who, &error) != 0 || error != 0) {
 506                         /*
 507                          * Marking disk as "in use" even if we got here by way
 508                          * of (error != 0) since it's better to not touch a
 509                          * drive that MAY be in use by something. User can
 510                          * still use the disk by defining the NOINUSE_CHECK
 511                          * environment variable which will skip "in use"
 512                          * checking.
 513                          */
 514                         disk->inuse = B_TRUE;
 515                         if (error == 0) {
 516                                 if (verbose) {
 517                                         (void) fprintf(stderr, _("Drive '%s' is"
 518                                             " in use: %s"), disk->device, msg);
 519                                 }
 520                                 if (strlen(msg) < MAX_INUSE_LEN) {
 521                                         (void) strncpy(disk->inuse_msg, msg,
 522                                             MAX_INUSE_LEN);
 523                                         /* Remove 'Please see ...' from msg */
 524                                         plz = strstr(disk->inuse_msg, "Please");
 525                                         if (plz != NULL) {
 526                                                 plz--;
 527                                                 *plz = '\0';
 528                                         }
 529                                 }
 530                                 free(msg);
 531                         } else if (verbose) {
 532                                 (void) fprintf(stderr, _("dm_inuse() failed: "
 533                                     "%s.\nMarking drive in use.\n"),
 534                                     strerror(error));
 535                         }
 536                         dm_free_name(slice);
 537                         break;
 538                 }
 539 
 540                 dm_free_name(slice);
 541         }
 542         dm_free_descriptors(slices);
 543 }
 544 
 545 /* obtain devlink (c*d*t* path) from /device path */
 546 int
 547 devlink_walk_cb(di_devlink_t devlink, void *arg)
 548 {
 549         const char *path;
 550         if ((path = di_devlink_path(devlink)) != NULL) {
 551                 assert(strlen(path) < MAXPATHLEN);
 552                 (void) strncpy(arg, path, strlen(path)+1);
 553         } else {
 554                 (void) strcpy(arg, "unknown_device_path");
 555         }
 556 
 557         return (DI_WALK_TERMINATE);
 558 }
 559 
 560 /*  get relevant disk info for char devices */
 561 boolean_t
 562 set_disk_info(const di_node_t *node, disk_info_t *disk, boolean_t verbose)
 563 {
 564         int instance;
 565         char *m_path = NULL;
 566         di_minor_t di_minor;
 567         di_devlink_handle_t devlink_h;
 568         struct scsi_inquiry inq;
 569         int disk_fd;
 570         struct scsi_extended_sense sense;
 571 
 572         disk->bad_info = 1;
 573 
 574         /* populate device field with sd */
 575         assert(MAX_DISK_LEN > strlen(DISK_DRIVER) + sizeof (int) + 1);
 576         (void) strncpy(disk->device, DISK_DRIVER, strlen(DISK_DRIVER)+1);
 577 
 578         instance = di_instance(*node);
 579         if (instance == -1) {
 580                 (void) fprintf(stderr, _("Could not get the instance number "
 581                     "of the device - di_instance() failed\n"));
 582                 (void) strcpy(disk->device + strlen(DISK_DRIVER), "?");
 583                 disk->bad_info = -1;
 584                 return (B_FALSE);
 585         }
 586 
 587         /* append instance # to device name making sd# */
 588         (void) snprintf(disk->device + strlen(DISK_DRIVER),
 589             MAX_DISK_LEN - strlen(DISK_DRIVER), "%d", instance);
 590 
 591         /* take a snapshot of devlinks bound to sd driver */
 592         if ((devlink_h = di_devlink_init(DISK_DRIVER, DI_MAKE_LINK)) ==
 593             DI_LINK_NIL) {
 594                 (void) fprintf(stderr, _("di_link_init() failed for drive "
 595                     "%s.\n"), disk->device);
 596                 return (B_FALSE);
 597         }
 598         /* traverse devlink minor nodes */
 599         di_minor = di_minor_next(*node, DI_MINOR_NIL);
 600         for (; di_minor != DI_MINOR_NIL;
 601             di_minor = di_minor_next(*node, di_minor)) {
 602                 if (di_minor_spectype(di_minor) != S_IFCHR)
 603                         continue; /* skip minor nodes that are not char devs */
 604 
 605                 /* path to minor node (/devices/...) */
 606                 if ((m_path = di_devfs_minor_path(di_minor)) == NULL) {
 607                         (void)  fprintf(stderr, _("couldn't get path for a "
 608                             "minor node of drive '%s' - di_devfs_minor_path() "
 609                             "failed.\n"), disk->device);
 610                         break;
 611                 }
 612                 if (strstr(m_path, ":q,raw") == NULL) {
 613                         di_devfs_path_free(m_path);
 614                         continue; /* skip minor nodes that aren't partition 0 */
 615                 }
 616 
 617                 /*
 618                  * lookup /dev/rdsk/ path for minor node defined by m_path
 619                  * The reason we're not just using the /devices m_path for
 620                  * WRITE_BUFFER is we need the c#t...d#p0 lun to display to
 621                  * the user anyway
 622                  */
 623                 if (di_devlink_walk(devlink_h, "^rdsk/", m_path,
 624                     DI_PRIMARY_LINK, disk->path, devlink_walk_cb) == -1) {
 625                         (void) fprintf(stderr, _("di_devlink_walk() failed for"
 626                             " disk '%s'\n"), disk->device);
 627                         di_devfs_path_free(m_path);
 628                         break;
 629                 }
 630                 di_devfs_path_free(m_path);
 631 
 632                 /*
 633                  * check that the devlink (/dev/rdsk/) path was found by the
 634                  * above
 635                  */
 636                 if (strcmp(disk->path, "unknown_device_path") == 0) {
 637                         (void) fprintf(stderr, _("di_devlink_path() for '%s'"
 638                             " failed"), disk->device);
 639                         break;
 640                 }
 641 
 642                 disk_fd = open(disk->path, O_RDONLY | O_NDELAY);
 643                 if (disk_fd < 0) {
 644                         (void) fprintf(stderr, _("open() on drive '%s', dev "
 645                             "path '%s' failed: %s\n"), disk->device,
 646                             disk->path, strerror(errno));
 647                         break;
 648                 }
 649 
 650                 /* found p0 for a char disk, now get vendor, model, and rev */
 651                 if (uscsi_inquiry(disk_fd, &inq, &sense, verbose)) {
 652                         disk->bad_info = 0;
 653                         mem_trim_and_cpy(disk->vendor, inq.inq_vid,
 654                             sizeof (inq.inq_vid));
 655                         mem_trim_and_cpy(disk->model, inq.inq_pid,
 656                             sizeof (inq.inq_pid));
 657                         mem_trim_and_cpy(disk->rev, inq.inq_revision,
 658                             sizeof (inq.inq_revision));
 659                         set_disk_inuse(disk, verbose);
 660                 } else {
 661                         if (verbose) {
 662                                 (void) fprintf(stderr, _("uscsi_inquiry() "
 663                                     "failed, drive '%s' will be skipped\n"),
 664                                     disk->device);
 665                         }
 666                 }
 667                 (void) close(disk_fd);
 668                 break; /* have found slice 0 for a char device, can now exit */
 669         }
 670         (void) di_devlink_fini(&devlink_h);
 671 
 672         return (disk->bad_info == 0 ? B_TRUE : B_FALSE);
 673 }
 674 
 675 /* kernel device tree walk */
 676 int
 677 walk_nodes(di_node_t *root_node, list_t *disks, boolean_t verbose)
 678 {
 679         int *dtype;
 680         int disks_found = 0;
 681         disk_info_t *disk;
 682         di_node_t node;
 683 
 684         (void) fprintf(stdout, _("Searching for drives, found: "));
 685         /* start at root node and walk all sd nodes */
 686         node = di_drv_first_node(DISK_DRIVER, *root_node);
 687         for (; node != DI_NODE_NIL; node = di_drv_next_node(node)) {
 688                 (void) fflush(stdout);
 689                 if (di_prop_lookup_ints(DDI_DEV_T_ANY, node,
 690                     "inquiry-device-type", &dtype) >= 0) {
 691                         if (((*dtype) & DTYPE_MASK) != DTYPE_DIRECT)
 692                                 /* skip devs that are not type 0 (not disks) */
 693                                 continue;
 694                 } else {
 695                         if (verbose) {
 696                                 (void) fprintf(stderr,
 697                                     _("di_prop_lookup_ints(inquiry-device-type)"
 698                                     " failed, ignoring this node\n"));
 699                         }
 700                         continue;
 701                 }
 702                 disks_found++;
 703                 if ((disk = (disk_info_t *)malloc(sizeof (disk_info_t))) ==
 704                     NULL) {
 705                         if (verbose) {
 706                                 (void)  fprintf(stderr, _("malloc(%d) "
 707                                     "failed\n"), sizeof (disk_info_t));
 708                         }
 709                         return (-disks_found);
 710                 }
 711                 list_insert_tail(disks, disk); /* preserves discovery order */
 712                 if (set_disk_info(&node, disk, verbose) || verbose) {
 713                         (void) fprintf(stdout, "%s ", disk->device);
 714                         if ((disks_found % 10) == 0)
 715                                 (void) fprintf(stdout, "\n");
 716                 } else {
 717                         disks_found--;
 718                 }
 719         }
 720         (void) fprintf(stdout, _("(%d total).\n"), disks_found);
 721 
 722         return (disks_found);
 723 }
 724 
 725 /* allocate space and read into it the fw image */
 726 char *
 727 get_fw_image(const char *fw_file, size_t *fw_len, boolean_t verbose)
 728 {
 729         struct stat stat_buf;
 730         char *fw_buf = NULL;
 731         FILE *fw_stream = fopen(fw_file, "r");
 732 
 733         *fw_len = 0;
 734         if (fw_stream != NULL) {
 735                 if (fstat(fileno(fw_stream), &stat_buf) == -1) {
 736                         /* fstat failed */
 737                         if (verbose) {
 738                                 (void) fprintf(stderr, _("fstat() on file '%s'"
 739                                     " failed\n"), fw_file);
 740                         }
 741                 } else if (stat_buf.st_size < 1 ||
 742                     stat_buf.st_size > MAX_FW_SIZE_IN_BYTES) {
 743                         /*
 744                          * fw file empty or too big to fit into
 745                          * 1 SCSI WRITEBUFFER buffer
 746                          */
 747                         (void) fprintf(stderr, _("'%s' is of size %lld bytes."
 748                             "\ndisk-dmc v" VERSION " does not support fw files"
 749                             " bigger then %d bytes or smaller then 1 byte.\n"),
 750                             fw_file, (intmax_t)stat_buf.st_size,
 751                             MAX_FW_SIZE_IN_BYTES);
 752                 } else { /* alloc buffer to hold fw img */
 753                         if ((fw_buf = malloc(stat_buf.st_size)) != NULL)
 754                                 *fw_len = stat_buf.st_size;
 755                         else if (verbose)
 756                                 (void) fprintf(stderr, _("malloc() failed\n"));
 757                 }
 758         } else { /* fopen failed */
 759                 if (verbose) {
 760                         (void) fprintf(stderr, _("fopen() on fw image '%s' "
 761                             "failed: %s\n"), fw_file, strerror(errno));
 762                 }
 763         }
 764 
 765         if (fw_buf != NULL && fread(fw_buf, 1, *fw_len, fw_stream) != *fw_len) {
 766                 free(fw_buf);
 767                 fw_buf = NULL;
 768                 *fw_len = 0;
 769                 if (verbose)
 770                         (void) fprintf(stderr, _("fread() failed\n"));
 771         }
 772 
 773         if (fw_stream != NULL)
 774                 (void) fclose(fw_stream);
 775 
 776         return (fw_buf);
 777 }
 778 
 779 /*
 780  * print a dot for every second we wait as to not look like process is hanging
 781  */
 782 void
 783 wait_print(int sec)
 784 {
 785         (void) fprintf(stdout, _(" waiting for %d seconds:\n"), sec);
 786         while (sec--) {
 787                 (void) fprintf(stdout, " .");
 788                 (void) fflush(stdout);
 789                 if (! (sec % 30))
 790                         (void) fprintf(stdout, "\n");
 791                 (void) sleep(1);
 792         }
 793 }
 794 
 795 /* perform some housekeeping before write buffer */
 796 boolean_t
 797 prep_disk_for_fw_dl(int disk_fd, int wait, boolean_t verbose)
 798 {
 799         struct scsi_extended_sense sense;
 800 
 801         if (verbose)
 802                 (void) fprintf(stderr, _("  before dl prep\n"));
 803 
 804         sync(); /* schedule in flight stuff to be put onto disk before dmc */
 805 
 806         if (uscsi_test_unit_ready(disk_fd, &sense, verbose)) {
 807                 return (B_TRUE);
 808         } else {
 809                 if (sense.es_key == KEY_NOT_READY &&
 810                     sense.es_add_code == 0x04 && sense.es_qual_code == 0x02) {
 811                         /* above sense data indicates disk needs a start cmd */
 812                         (void) uscsi_start_unit(disk_fd, &sense, verbose);
 813                         wait_print(wait);
 814                 } else if (sense.es_key == KEY_UNIT_ATTENTION &&
 815                     sense.es_add_code == 0x29 && sense.es_qual_code == 0x0) {
 816                         wait_print(wait); /* reset occurred */
 817                 } else if (sense.es_key == KEY_NOT_READY &&
 818                     sense.es_add_code == 0x04 && sense.es_qual_code == 0x0) {
 819                         wait_print(wait); /* becoming ready */
 820                 } else {
 821                         wait_print(wait);
 822                 }
 823         }
 824 
 825         /* trying again */
 826         return (uscsi_test_unit_ready(disk_fd, &sense, verbose));
 827 }
 828 
 829 /* test disk after buffer write to make sure it's ready for operation */
 830 boolean_t
 831 after_dl_prep(int disk_fd, int wait, boolean_t verbose)
 832 {
 833         struct scsi_extended_sense sense;
 834 
 835         if (verbose)
 836                 (void) fprintf(stderr, _(" after dl prep\n"));
 837         wait_print(wait);       /* give disk time to become ready after dmc */
 838 
 839         if (uscsi_test_unit_ready(disk_fd, &sense, verbose)) {
 840                 return (B_TRUE);
 841         } else {
 842                 if (sense.es_key == KEY_NOT_READY &&
 843                     sense.es_add_code == 0x04 && sense.es_qual_code == 0x02) {
 844                         /* above sense data indicates disk needs a start cmd */
 845                         (void) uscsi_start_unit(disk_fd, &sense, verbose);
 846                         wait_print(wait);
 847                 } else if (sense.es_key == KEY_UNIT_ATTENTION &&
 848                     sense.es_add_code == 0x63 && sense.es_qual_code == 0x01) {
 849                         wait_print(wait); /* microcode changed */
 850                 } else if (sense.es_key == KEY_UNIT_ATTENTION &&
 851                     sense.es_add_code == 0x29 && sense.es_qual_code == 0x0) {
 852                         wait_print(wait); /* reset occurred */
 853                 } else if (sense.es_key == KEY_NOT_READY &&
 854                     sense.es_add_code == 0x04 && sense.es_qual_code == 0x0) {
 855                         wait_print(wait); /* becoming ready */
 856                 } else if (sense.es_key == KEY_UNIT_ATTENTION &&
 857                     sense.es_add_code == 0x3F && sense.es_qual_code == 0x01) {
 858                         (void) uscsi_test_unit_ready(disk_fd, &sense, verbose);
 859                 } else {
 860                         wait_print(wait);
 861                 }
 862         }
 863 
 864         /* trying again */
 865         return (uscsi_test_unit_ready(disk_fd, &sense, verbose));
 866 }
 867 
 868 /* check if the bad_info field is set, if so print the error */
 869 boolean_t
 870 has_bad_info(disk_info_t *disk, boolean_t verbose)
 871 {
 872         if (disk->bad_info == -1) {
 873                 if (verbose) {
 874                         (void) fprintf(stderr, _("Encountered a device "
 875                             "without an instance, it will be skipped.\n"));
 876                 }
 877                 return (B_TRUE);
 878         } else if (disk->bad_info == 1) {
 879                 if (verbose) {
 880                         (void) fprintf(stderr, _("Was not able to get all of "
 881                             "the needed info for drive '%s', it will be "
 882                             "skipped.\n"), disk->device);
 883                 }
 884                 return (B_TRUE);
 885         }
 886 
 887         return (B_FALSE);
 888 }
 889 
 890 /* print info for a single disk entry */
 891 void
 892 print_disk(const disk_info_t *disk, boolean_t verbose)
 893 {
 894         if (verbose)
 895                 (void) fprintf(stdout, "%-8s %-35s %-9s %-17s %-6s  %s\n",
 896                     disk->device, CTD_START_IN_PATH(disk->path), disk->vendor,
 897                     disk->model, disk->rev, disk->inuse_msg);
 898         else
 899                 (void) fprintf(stdout, "%-8s %-35s %-9s %-17s %-6s\n",
 900                     disk->device, CTD_START_IN_PATH(disk->path), disk->vendor,
 901                     disk->model, disk->rev);
 902 }
 903 
 904 /* print header and info for all disks */
 905 void
 906 print_disks(list_t *disks, boolean_t verbose)
 907 {
 908         disk_info_t *disk;
 909         if (verbose)
 910                 (void) fprintf(stdout, "%-8s %-35s %-9s %-17s %-6s  %s\n"
 911                     "==============================================="
 912                     "========================================\n",
 913                     "DEVICE", "LUN", "VENDOR", "MODEL", "FW REV", "INUSE");
 914         else
 915                 (void) fprintf(stdout, "%-8s %-35s %-9s %-17s %-6s\n"
 916                     "========================================"
 917                     "=======================================\n",
 918                     "DEVICE", "LUN", "VENDOR", "MODEL", "FW REV");
 919         for (disk = list_head(disks); disk; disk = list_next(disks, disk))
 920                 if (!has_bad_info(disk, verbose))
 921                         print_disk(disk, verbose);
 922 }
 923 
 924 /* perform fw update on one disk */
 925 boolean_t
 926 process_disk(disk_info_t *disk, uint8_t mode, char *fw_buf, size_t fw_len,
 927     int wait, boolean_t verbose)
 928 {
 929         int disk_fd;
 930         struct scsi_extended_sense sense;
 931         boolean_t status;
 932         struct scsi_inquiry inq;
 933 
 934         (void) fprintf(stdout, _("Processing drive %s:\n\n"), disk->device);
 935 
 936         if (disk == DI_NODE_NIL) {
 937                 (void) fprintf(stdout, _("Drive was not found, will not "
 938                     "continue.\n"));
 939                 return (B_FALSE);
 940         }
 941         assert(fw_buf != NULL);
 942         assert(fw_len > 0);
 943 
 944         if (disk->inuse) {
 945                 (void) fprintf(stdout, _("Drive '%s' is in use, will not "
 946                     "continue.\n"),
 947                     disk->device);
 948                 return (B_FALSE);
 949         }
 950 
 951         disk_fd = open(disk->path, O_RDWR | O_NONBLOCK);
 952         if (disk_fd < 0) {
 953                 (void) fprintf(stderr, _("open() on drive '%s' failed: %s\n"),
 954                     disk->path, strerror(errno));
 955                 return (B_FALSE);
 956         }
 957 
 958         status = prep_disk_for_fw_dl(disk_fd, wait, verbose);
 959         if (verbose) {
 960                 (void) fprintf(stderr, _(" Prep drive status: %s\n"),
 961                     status ? _("success") : _("failure"));
 962         }
 963 
 964         if (!status) {
 965                 (void) close(disk_fd);
 966                 return (B_FALSE);
 967         }
 968 
 969         /* write the fw onto the disk */
 970         if (mode == 0x05) {
 971                 status = uscsi_write_buffer_mode5(disk_fd, fw_buf, fw_len,
 972                     &sense, verbose);
 973         } else if (mode == 0x07) {
 974                 status = uscsi_write_buffer_mode7(disk_fd, (uint8_t *)fw_buf,
 975                     fw_len, &sense, verbose);
 976         } else {
 977                 (void) fprintf(stderr, _(" unexpected write buffer mode\n"));
 978                 (void) close(disk_fd);
 979                 return (B_FALSE);
 980         }
 981 
 982         if (after_dl_prep(disk_fd, wait, verbose) && verbose) {
 983                 (void) fprintf(stderr, _(" After fw download drive seems to be "
 984                     "online.\n"));
 985         } else if (verbose) {
 986                 (void) fprintf(stderr, _(" After fw download drive doesn't "
 987                     "seem to be online.\n"));
 988         }
 989 
 990         (void) fprintf(stdout, _("\nFlashing %s firmware: %s\n"), disk->device,
 991             status ? _("successful") : _("failed"));
 992 
 993         if (verbose) { /* get and display the "new" disk revision */
 994                 if (uscsi_inquiry(disk_fd, &inq, &sense, verbose)) {
 995                         mem_trim_and_cpy(disk->rev, inq.inq_revision,
 996                             sizeof (inq.inq_revision));
 997                         print_disk(disk, verbose);
 998                 } else {
 999                         (void) fprintf(stderr, _("uscsi_inquiry() failed on "
1000                             "disk '%s', can't get revision.\n"), disk->device);
1001                 }
1002         }
1003         (void) close(disk_fd);
1004 
1005         return (status);
1006 }
1007 
1008 /* get a path to the drive that we can feed to di_lookup_node() */
1009 boolean_t
1010 massage_drive_path(const char * const user_path, char *trgt_path, boolean_t
1011     verbose)
1012 {
1013         char *cp;
1014         size_t len;
1015 
1016         if (strlen(user_path) >= PATH_MAX) {
1017                 (void) fprintf(stderr, _("drive name given (%s) is too "
1018                     "long (%d)\n"), user_path, MAXPATHLEN);
1019                 return (B_FALSE);
1020         }
1021 
1022         if (strstr(user_path, "/dev/rdsk/") != NULL) {
1023                 if (realpath(user_path, trgt_path) == NULL) {
1024                         (void) fprintf(stderr, _("error processing drive "
1025                             "%s: %s.\n"), user_path, strerror(errno));
1026                         return (B_FALSE);
1027                 }
1028                 if (verbose) {
1029                         (void) fprintf(stderr, _("translated drive path %s to"
1030                             " %s.\n"), user_path, trgt_path);
1031                 }
1032         } else {
1033                 (void) fprintf(stderr, _("path to drive must include "
1034                     "'/dev/rdsk/'\n"));
1035                 return (B_FALSE);
1036         }
1037 
1038         if (verbose && strstr(trgt_path, "/scsi_vhci/") != NULL)
1039                 (void) fprintf(stderr, _("warning drive %s is multipathed.\n"),
1040                     trgt_path);
1041 
1042 
1043         /* Remove devices prefix (if any) */
1044         len = strlen(DEVICES_DIR);
1045         if (strncmp(trgt_path, DEVICES_DIR "/", len + strlen("/")) == 0) {
1046                 (void) memmove(trgt_path, trgt_path + len,
1047                     strlen(trgt_path + len) + 1);
1048         }
1049 
1050         /* Remove dynamic component if any */
1051         if ((cp = strstr(trgt_path, "::")) != NULL) {
1052                 *cp = '\0';
1053         }
1054 
1055         /* Remove minor name (if any) */
1056         if ((cp = strrchr(trgt_path, ':')) != NULL) {
1057                 *cp = '\0';
1058         }
1059 
1060         return (B_TRUE);
1061 }
1062 
1063 void
1064 usage(const char *prog_name)
1065 {
1066         (void) fprintf(stdout, _("%s v" VERSION "\n"
1067             "\nUsage: %s <-d /dev/rdsk/c#t...d# | "
1068             "-m model_string> <-p /path/to/fw/img> <-h> <-l> <-v> <-s 5|7> "
1069             "<-w #sec>\n"
1070             "\t-h\tPrint this help message and exit.\n"
1071             "\t-l\tList discovered drives.\n"
1072             "\t-d dsk\tSpecify single drive to use for firmware "
1073             "download in /dev/rdsk/c#t...d#p0 format.\n"
1074             "\t-s 5|7\tSpecify which write buffer mode to use. "
1075             "%d is the default.\n"
1076             "\t-m str\tSpecify model of drive(s) to download "
1077             "firmware to. Drives whose model exactly matches model str "
1078             "provided will be upgraded.\n\t-p img\tPath to the firmware file "
1079             "that will be downloaded onto the specified drive(s).\n"
1080             "\t-v\tVerbose mode: turns on extra debug messages.\n"
1081             "\t-w #sec\tNumber of seconds to delay checking "
1082             "for drive readiness after downloading the firmware. Also "
1083             "used for drive preparation timeouts. %d is the default.\n"),
1084             prog_name, prog_name, DEFAULT_WB_MODE, DEFAULT_WAIT);
1085         exit(1);
1086 }
1087 
1088 
1089 int
1090 main(int argc, char *argv[])
1091 {
1092         di_node_t root_node, node;
1093         int opt, i, wait = DEFAULT_WAIT;
1094         uint8_t mode = DEFAULT_WB_MODE;
1095         boolean_t ctd = B_FALSE, list = B_FALSE, model = B_FALSE;
1096         boolean_t change_mode = B_FALSE, user_wait = B_FALSE, verbose = B_FALSE;
1097         char *user_drive = NULL, *fw_img = NULL;
1098         size_t fw_len;
1099         char dl_fw_to_disk[MAXPATHLEN];
1100         char dl_fw_to_model[INQ_MODEL_LEN];
1101         list_t disks; /* list of disk_info_t structures */
1102         disk_info_t *disk = NULL;
1103 
1104         if (geteuid() > 1) {
1105                 (void) fprintf(stderr, _("This utility requires root level "
1106                     "permissions due to the use of USCSI(7i).\n"));
1107                 return (1);
1108         }
1109 
1110         /* if only verbose or no command line args received do a list */
1111         if (argc == 1 || (argc == 2 && strcmp("-v", argv[1]) == 0))
1112                 list = B_TRUE;
1113 
1114         while ((opt = getopt(argc, argv, "d:p:hils:m:vw:")) != -1) {
1115                 switch (opt) {
1116                 case 'd': /* single disk mode */
1117                         user_drive = optarg;
1118                         ctd = B_TRUE;
1119                         break;
1120                 case 'p': /* fw file */
1121                         if (strlen(optarg) < MAXPATHLEN) {
1122                                 fw_img = get_fw_image(optarg, &fw_len, verbose);
1123                                 if (fw_img == NULL) {
1124                                         (void) fprintf(stderr, _("could not use"
1125                                             " fw file '%s', check file size, "
1126                                             "permissions and/or existence.\n"),
1127                                             optarg);
1128                                         usage(argv[0]);
1129                                 }
1130                         } else {
1131                                 (void) fprintf(stderr, _("fw path provided to"
1132                                     " '-%c' is too long.\n"), optopt);
1133                                 usage(argv[0]);
1134                         }
1135                         break;
1136                 case 'h': /* print usage and exit */
1137                         usage(argv[0]);
1138                         break;
1139                 case 'i': /* ignore the lagacy -i flag */
1140                         break;
1141                 case 'l': /* list drives */
1142                         list = B_TRUE;
1143                         break;
1144                 case 's': /* change mode from default */
1145                         if (atoi(optarg) == 7)
1146                                 mode = 0x07;
1147                         else if (atoi(optarg) == 5)
1148                                 mode = 0x05;
1149                         else {
1150                                 (void) fprintf(stderr, _("mode provided to "
1151                                     "'-%c' (%s) is not supported. Supported "
1152                                     "modes are 5 and 7.\n"), optopt, optarg);
1153                                 usage(argv[0]);
1154                         }
1155                         change_mode = B_TRUE;
1156                         break;
1157                 case 'm': /* only flash this model */
1158                         if (strlen(optarg) < INQ_MODEL_LEN) {
1159                                 (void) strncpy(dl_fw_to_model, optarg,
1160                                     strlen(optarg) + 1);
1161                                 model = B_TRUE;
1162                         } else {
1163                                 (void) fprintf(stderr, _("model provided to "
1164                                     "'-%c' is too long (%d)\n"), optopt,
1165                                     strlen(optarg));
1166                                 usage(argv[0]);
1167                         }
1168                         break;
1169                 case 'v': /* verbose mode */
1170                         verbose = B_TRUE;
1171                         break;
1172                 case 'w': /* wait time after dl */
1173                         wait = atoi(optarg);
1174                         if (wait < 0 || wait > 300) {
1175                                 (void) fprintf(stderr, _("'-%c %d' - time to "
1176                                     "wait after microcode download is too long"
1177                                     " ( > 5min ) or negative.\n"),
1178                                     optopt, wait);
1179                                 usage(argv[0]);
1180                         }
1181                         user_wait = B_TRUE;
1182                         break;
1183                 case ':':
1184                 case '?':
1185                         usage(argv[0]);
1186                         break;
1187                 }
1188         }
1189 
1190         for (i = optind; i < argc; i++) {
1191                 (void) printf(_("Unexpected argument '%s'\n"), argv[i]);
1192                 usage(argv[0]);
1193         }
1194 
1195         if (fw_img && (!ctd && !model)) {
1196                 /* fw file was given, need to associate it w/ a disk or model */
1197                 (void) fprintf(stderr, _("Expected a drive specification "
1198                     "('-m model' or '-d dsk') to associate fw file with.\n"));
1199                 usage(argv[0]);
1200         }
1201         if (!fw_img && (ctd || model)) {
1202                 /*
1203                  * no fw file is given, having model or disk
1204                  * doesn't make sense
1205                  */
1206                 (void) fprintf(stderr, _("Expected a fw file "
1207                     "('-p /path/to/fw/file') to associate '-m model' or "
1208                     "'-d dsk' with.\n"));
1209                 usage(argv[0]);
1210         }
1211 
1212         if (model && ctd) {
1213                 /* can't have both disk and model for fw download specified */
1214                 (void) fprintf(stderr, _("-m and -d are mutually "
1215                     "exclusive.\n"));
1216                 usage(argv[0]);
1217         }
1218 
1219         if ((change_mode || user_wait) && !fw_img) {
1220                 /* need firmware to work with if above args are given */
1221                 (void) fprintf(stderr, _("Expected '-p /path/to/fw.'\n"));
1222                 usage(argv[0]);
1223         }
1224 
1225         /*
1226          * create a snapshot of kernel device tree include minor nodes,
1227          * properties and subtree
1228          */
1229         if ((root_node = di_init("/", DINFOCPYALL | DINFOPATH)) ==
1230             DI_NODE_NIL) {
1231                 (void) fprintf(stderr, _("di_init() failed\n"));
1232                 free(fw_img);
1233                 return (1);
1234         }
1235 
1236         if (ctd) { /* if we were given a disk */
1237                 if (!massage_drive_path(user_drive, dl_fw_to_disk, verbose)) {
1238                         free(fw_img);
1239                         usage(argv[0]);
1240                 }
1241                 node = di_lookup_node(root_node, dl_fw_to_disk);
1242                 if (node == DI_NODE_NIL) {
1243                         (void)  fprintf(stderr, _("di_lookup_node(%s) failed. "
1244                             "Please check path to drive.\n"), dl_fw_to_disk);
1245                         free(fw_img);
1246                         return (1);
1247                 }
1248                 if ((disk = (disk_info_t *)malloc(sizeof (disk_info_t)))
1249                     == NULL) {
1250                         if (verbose) {
1251                                 (void) fprintf(stderr, _("malloc(%d) failed\n"),
1252                                     sizeof (disk_info_t));
1253                         }
1254                         free(fw_img);
1255                         return (1);
1256                 } else {
1257                         if (!set_disk_info(&node, disk, verbose)) {
1258                                 if (verbose)
1259                                         (void) fprintf(stderr, _(
1260                                             "set_disk_info(%s) indicated a "
1261                                             "problem.\n"), dl_fw_to_disk);
1262                                 free(disk);
1263                                 free(fw_img);
1264                                 return (1);
1265                         }
1266                         (void) process_disk(disk, mode, fw_img, fw_len, wait,
1267                             verbose);
1268                 }
1269                 free(disk);
1270                 free(fw_img);
1271                 return (0);
1272         }
1273 
1274 
1275         list_create(&disks, sizeof (disk_info_t), offsetof(disk_info_t, link));
1276         i = walk_nodes(&root_node, &disks, verbose); /* walk device tree */
1277         di_fini(root_node);
1278 
1279         if (i < 1) {
1280                 while ((disk = (list_head(&disks))) != NULL) {
1281                         list_remove(&disks, disk);
1282                         free(disk);
1283                 }
1284                 if (fw_img)
1285                         free(fw_img);
1286         }
1287         if (i == 0) {
1288                 (void) fprintf(stdout, _("No drive(s) serviced by '"
1289                     DISK_DRIVER "' driver were found.\n)"));
1290                 return (1);
1291         }
1292         if (i < 0) {
1293                 (void) fprintf(stderr, _("malloc() failed so not all drive(s) "
1294                     "serviced by '" DISK_DRIVER "' driver were walked.\n"
1295                     "Encountered %d drive(s) before failure.\n"), -i);
1296                 return (1);
1297         }
1298         if (verbose) {
1299                 (void) fprintf(stderr, _("Found %d drive(s) serviced by '"
1300                     DISK_DRIVER "' driver.\n"), i);
1301         }
1302 
1303         if (list)
1304                 print_disks(&disks, verbose);
1305 
1306         if (model) { /* if we were given a model */
1307                 i = 0;
1308                 disk = list_head(&disks);
1309                 for (; disk; disk = list_next(&disks, disk)) {
1310                         if (has_bad_info(disk, verbose))
1311                                 continue;
1312                         if (strcmp(dl_fw_to_model, disk->model) == 0) {
1313                                 i++;
1314                                 if (verbose) {
1315                                         (void) fprintf(stderr, _("matched "
1316                                             "provided model: '%s' to drive "
1317                                             "'%s'.\n"), dl_fw_to_model,
1318                                             disk->device);
1319                                 }
1320                                 (void) process_disk(disk, mode, fw_img, fw_len,
1321                                     wait, verbose);
1322                         } else if (verbose) {
1323                                 (void) fprintf(stderr, _("did not match "
1324                                     "provided model: '%s' to drive '%s'.\n"),
1325                                     dl_fw_to_model, disk->device);
1326                         }
1327                 }
1328                 if (i == 0) {
1329                         (void) fprintf(stdout, _("drive model '%s' was not "
1330                             "found.\n"), dl_fw_to_model);
1331                 }
1332         }
1333 
1334         /* cleanup start */
1335         if (fw_img)
1336                 free(fw_img);
1337         while ((disk = (list_head(&disks))) != NULL) {
1338                 list_remove(&disks, disk);
1339                 free(disk);
1340         }
1341         list_destroy(&disks);
1342 
1343         return (0);
1344 }