diff options
Diffstat (limited to 'sbin/camcontrol')
-rw-r--r-- | sbin/camcontrol/camcontrol.8 | 192 | ||||
-rw-r--r-- | sbin/camcontrol/camcontrol.c | 662 | ||||
-rw-r--r-- | sbin/camcontrol/camcontrol.h | 29 | ||||
-rw-r--r-- | sbin/camcontrol/fwdownload.c | 886 | ||||
-rw-r--r-- | sbin/camcontrol/progress.c | 27 |
5 files changed, 1580 insertions, 216 deletions
diff --git a/sbin/camcontrol/camcontrol.8 b/sbin/camcontrol/camcontrol.8 index 289baa1..7a482a6 100644 --- a/sbin/camcontrol/camcontrol.8 +++ b/sbin/camcontrol/camcontrol.8 @@ -27,7 +27,7 @@ .\" .\" $FreeBSD$ .\" -.Dd March 19, 2015 +.Dd August 6, 2015 .Dt CAMCONTROL 8 .Os .Sh NAME @@ -253,8 +253,9 @@ .Op device id .Op generic args .Aq Fl f Ar fw_image -.Op Fl y +.Op Fl q .Op Fl s +.Op Fl y .Nm .Ic security .Op device id @@ -311,6 +312,14 @@ .Op Fl T Ar elem_type .Op Fl V Ar lv_num .Nm +.Ic opcodes +.Op device id +.Op generic args +.Op Fl o Ar opcode +.Op Fl s Ar service_action +.Op Fl N +.Op Fl T +.Nm .Ic help .Sh DESCRIPTION The @@ -1516,24 +1525,41 @@ without prompting for confirmation The password for all HPA commands is limited to 32 characters, longer passwords will fail. .It Ic fwdownload -Program firmware of the named SCSI device using the image file provided. -.Pp -Current list of supported vendors: -.Bl -bullet -offset indent -compact -.It -HITACHI -.It -HP -.It -IBM -.It -PLEXTOR -.It -QUANTUM -.It -SAMSUNG -.It -SEAGATE +Program firmware of the named +.Tn SCSI +or ATA device using the image file provided. +.Pp +If the device is a +.Tn SCSI +device and it provides a recommended timeout for the WRITE BUFFER command +(see the +.Nm +opcodes subcommand), that timeout will be used for the firmware download. +The drive-recommended timeout value may be overridden on the command line +with the +.Fl t +option. +.Pp +Current list of supported vendors for SCSI/SAS drives: +.Bl -tag -width 10n +.It HGST +Tested with 4TB SAS drives, model number HUS724040ALS640. +.It HITACHI +.It HP +.It IBM +Tested with LTO-5 (ULTRIUM-HH5) and LTO-6 (ULTRIUM-HH6) tape drives. +There is a separate table entry for hard drives, because the update method +for hard drives is different than the method for tape drives. +.It PLEXTOR +.It QUALSTAR +.It QUANTUM +.It SAMSUNG +Tested with SM1625 SSDs. +.It SEAGATE +Tested with Constellation ES (ST32000444SS), ES.2 (ST33000651SS) and +ES.3 (ST1000NM0023) drives. +.It SmrtStor +Tested with 400GB Optimus SSDs (TXA2D20400GA6001). .El .Pp .Em WARNING! WARNING! WARNING! @@ -1547,23 +1573,81 @@ Extra caution should be taken when using this command since there is no guarantee it will not break a device from the listed vendors. Ensure that you have a recent backup of the data on the device before performing a firmware update. +.Pp +Note that unknown +.Tn SCSI +protocol devices will not be programmed, since there is little chance of +the firmware download succeeding. +.Pp +.Nm +will currently attempt a firmware download to any +.Tn ATA +or +.Tn SATA +device, since the standard +.Tn ATA +DOWNLOAD MICROCODE command may work. +Firmware downloads to +.Tn ATA +and +.Tn SATA +devices are supported for devices connected +to standard +.Tn ATA +and +.Tn SATA +controllers, and devices connected to SAS controllers +with +.Tn SCSI +to +.Tn ATA +translation capability. +In the latter case, +.Nm +uses the +.Tn SCSI +.Tn ATA +PASS-THROUGH command to send the +.Tn ATA +DOWNLOAD MICROCODE command to the drive. +Some +.Tn SCSI +to +.Tn ATA +translation implementations don't work fully when translating +.Tn SCSI +WRITE BUFFER commands to +.Tn ATA +DOWNLOAD MICROCODE commands, but do support +.Tn ATA +passthrough well enough to do a firmware download. .Bl -tag -width 11n .It Fl f Ar fw_image Path to the firmware image file to be downloaded to the specified device. -.It Fl y -Do not ask for confirmation. +.It Fl q +Do not print informational messages, only print errors. +This option should be used with the +.Fl y +option to suppress all output. .It Fl s Run in simulation mode. -Packet sizes that will be sent are shown, but no actual packet is sent to the -device. -No confirmation is asked in simulation mode. +Device checks are run and the confirmation dialog is shown, but no firmware +download will occur. .It Fl v -Besides showing sense information in case of a failure, the verbose option -causes -.Nm -to output a line for every firmware segment that is sent to the device by the -fwdownload command --- the same as the ones shown in simulation mode. +Show +.Tn SCSI +or +.Tn ATA +errors in the event of a failure. +.Pp +In simulation mode, print out the +.Tn SCSI +CDB +or +.Tn ATA +register values that would be used for the firmware download command. +.It Fl y +Do not ask for confirmation. .El .It Ic persist Persistent reservation support. @@ -1958,6 +2042,52 @@ and Specify the number of the logical volume to operate on. If the media has multiple logical volumes, this will allow displaying or writing attributes on the given logical volume. +.It Ic opcodes +Issue the REPORT SUPPORTED OPCODES service action of the +.Tn SCSI +MAINTENANCE IN +command. +Without arguments, this command will return a list of all +.Tn SCSI +commands supported by the device, including service actions of commands +that support service actions. +It will also include the +.Tn SCSI +CDB (Command Data Block) length for each command, and the description of +each command if it is known. +.Bl -tag -width 18n +.It Fl o Ar opcode +Request information on a specific opcode instead of the list of supported +commands. +If supported, the target will return a CDB-like structure that indicates +the opcode, service action (if any), and a mask of bits that are supported +in that CDB. +.It Fl s Ar service_action +For commands that support a service action, specify the service action to +query. +.It Fl N +If a service action is specified for a given opcode, and the device does +not support the given service action, the device should not return a +.Tn SCSI +error, but rather indicate in the returned parameter data that the command +is not supported. +By default, if a service action is specified for an opcode, and service +actions are not supported for the opcode in question, the device will +return an error. +.It Fl T +Include timeout values. +This option works with the default display, which includes all commands +supported by the device, and with the +.Fl o +and +.Fl s +options, which request information on a specific command and service +action. +This requests that the device report Nominal and Recommended timeout values +for the given command or commands. +The timeout values are in seconds. +The timeout descriptor also includes a command-specific +.El .It Ic help Print out verbose usage information. .El diff --git a/sbin/camcontrol/camcontrol.c b/sbin/camcontrol/camcontrol.c index fefb695..8deef3b 100644 --- a/sbin/camcontrol/camcontrol.c +++ b/sbin/camcontrol/camcontrol.c @@ -99,7 +99,8 @@ typedef enum { CAM_CMD_PERSIST = 0x00000020, CAM_CMD_APM = 0x00000021, CAM_CMD_AAM = 0x00000022, - CAM_CMD_ATTRIB = 0x00000023 + CAM_CMD_ATTRIB = 0x00000023, + CAM_CMD_OPCODES = 0x00000024 } cam_cmdmask; typedef enum { @@ -221,11 +222,12 @@ static struct camcontrol_opts option_table[] = { {"sleep", CAM_CMD_SLEEP, CAM_ARG_NONE, ""}, {"apm", CAM_CMD_APM, CAM_ARG_NONE, "l:"}, {"aam", CAM_CMD_AAM, CAM_ARG_NONE, "l:"}, - {"fwdownload", CAM_CMD_DOWNLOAD_FW, CAM_ARG_NONE, "f:ys"}, + {"fwdownload", CAM_CMD_DOWNLOAD_FW, CAM_ARG_NONE, "f:qsy"}, {"security", CAM_CMD_SECURITY, CAM_ARG_NONE, "d:e:fh:k:l:qs:T:U:y"}, {"hpa", CAM_CMD_HPA, CAM_ARG_NONE, "Pflp:qs:U:y"}, {"persist", CAM_CMD_PERSIST, CAM_ARG_NONE, "ai:I:k:K:o:ps:ST:U"}, {"attrib", CAM_CMD_ATTRIB, CAM_ARG_NONE, "a:ce:F:p:r:s:T:w:V:"}, + {"opcodes", CAM_CMD_OPCODES, CAM_ARG_NONE, "No:s:T"}, #endif /* MINIMALISTIC */ {"help", CAM_CMD_USAGE, CAM_ARG_NONE, NULL}, {"-?", CAM_CMD_USAGE, CAM_ARG_NONE, NULL}, @@ -264,7 +266,6 @@ static int scsistart(struct cam_device *device, int startstop, int loadeject, int retry_count, int timeout); static int scsiinquiry(struct cam_device *device, int retry_count, int timeout); static int scsiserial(struct cam_device *device, int retry_count, int timeout); -static int camxferrate(struct cam_device *device); #endif /* MINIMALISTIC */ static int parse_btl(char *tstr, path_id_t *bus, target_id_t *target, lun_id_t *lun, cam_argmask *arglst); @@ -319,6 +320,14 @@ static int atasecurity(struct cam_device *device, int retry_count, int timeout, int argc, char **argv, char *combinedopt); static int atahpa(struct cam_device *device, int retry_count, int timeout, int argc, char **argv, char *combinedopt); +static int scsiprintoneopcode(struct cam_device *device, int req_opcode, + int sa_set, int req_sa, uint8_t *buf, + uint32_t valid_len); +static int scsiprintopcodes(struct cam_device *device, int td_req, uint8_t *buf, + uint32_t valid_len); +static int scsiopcodes(struct cam_device *device, int argc, char **argv, + char *combinedopt, int retry_count, int timeout, + int verbose); #endif /* MINIMALISTIC */ #ifndef min @@ -1018,7 +1027,7 @@ scsiserial(struct cam_device *device, int retry_count, int timeout) return(0); } -static int +int camxferrate(struct cam_device *device) { struct ccb_pathinq cpi; @@ -2034,7 +2043,7 @@ atahpa_freeze_lock(struct cam_device *device, int retry_count, } -static int +int ata_do_identify(struct cam_device *device, int retry_count, int timeout, union ccb *ccb, struct ata_params** ident_bufp) { @@ -4940,26 +4949,173 @@ get_cgd_bailout: return(retval); } -/* return the type of disk (really the command type) */ -static const char * -get_disk_type(struct cam_device *device) +/* + * Returns 1 if the device has the VPD page, 0 if it does not, and -1 on an + * error. + */ +int +dev_has_vpd_page(struct cam_device *dev, uint8_t page_id, int retry_count, + int timeout, int verbosemode) +{ + union ccb *ccb = NULL; + struct scsi_vpd_supported_page_list sup_pages; + int i; + int retval = 0; + + ccb = cam_getccb(dev); + if (ccb == NULL) { + warn("Unable to allocate CCB"); + retval = -1; + goto bailout; + } + + /* cam_getccb cleans up the header, caller has to zero the payload */ + bzero(&(&ccb->ccb_h)[1], + sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr)); + + bzero(&sup_pages, sizeof(sup_pages)); + + scsi_inquiry(&ccb->csio, + /*retries*/ retry_count, + /*cbfcnp*/ NULL, + /* tag_action */ MSG_SIMPLE_Q_TAG, + /* inq_buf */ (u_int8_t *)&sup_pages, + /* inq_len */ sizeof(sup_pages), + /* evpd */ 1, + /* page_code */ SVPD_SUPPORTED_PAGE_LIST, + /* sense_len */ SSD_FULL_SIZE, + /* timeout */ timeout ? timeout : 5000); + + /* Disable freezing the device queue */ + ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; + + if (retry_count != 0) + ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; + + if (cam_send_ccb(dev, ccb) < 0) { + cam_freeccb(ccb); + retval = -1; + goto bailout; + } + + if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { + if (verbosemode != 0) + cam_error_print(dev, ccb, CAM_ESF_ALL, + CAM_EPF_ALL, stderr); + retval = -1; + goto bailout; + } + + for (i = 0; i < sup_pages.length; i++) { + if (sup_pages.list[i] == page_id) { + retval = 1; + goto bailout; + } + } +bailout: + if (ccb != NULL) + cam_freeccb(ccb); + + return (retval); +} + +/* + * devtype is filled in with the type of device. + * Returns 0 for success, non-zero for failure. + */ +int +get_device_type(struct cam_device *dev, int retry_count, int timeout, + int verbosemode, camcontrol_devtype *devtype) { - struct ccb_getdev cgd; + struct ccb_getdev cgd; + int retval = 0; + + retval = get_cgd(dev, &cgd); + if (retval != 0) + goto bailout; - (void) memset(&cgd, 0x0, sizeof(cgd)); - get_cgd(device, &cgd); - switch(cgd.protocol) { + switch (cgd.protocol) { case PROTO_SCSI: - return "scsi"; + break; case PROTO_ATA: case PROTO_ATAPI: case PROTO_SATAPM: - return "ata"; + *devtype = CC_DT_ATA; + goto bailout; + break; /*NOTREACHED*/ default: - return "unknown"; + *devtype = CC_DT_UNKNOWN; + goto bailout; + break; /*NOTREACHED*/ } + + /* + * Check for the ATA Information VPD page (0x89). If this is an + * ATA device behind a SCSI to ATA translation layer, this VPD page + * should be present. + * + * If that VPD page isn't present, or we get an error back from the + * INQUIRY command, we'll just treat it as a normal SCSI device. + */ + retval = dev_has_vpd_page(dev, SVPD_ATA_INFORMATION, retry_count, + timeout, verbosemode); + if (retval == 1) + *devtype = CC_DT_ATA_BEHIND_SCSI; + else + *devtype = CC_DT_SCSI; + + retval = 0; + +bailout: + return (retval); } +void +build_ata_cmd(union ccb *ccb, uint32_t retry_count, uint32_t flags, + uint8_t tag_action, uint8_t protocol, uint8_t ata_flags, uint16_t features, + uint16_t sector_count, uint64_t lba, uint8_t command, uint8_t *data_ptr, + uint16_t dxfer_len, uint8_t sense_len, uint32_t timeout, + int is48bit, camcontrol_devtype devtype) +{ + if (devtype == CC_DT_ATA) { + cam_fill_ataio(&ccb->ataio, + /*retries*/ retry_count, + /*cbfcnp*/ NULL, + /*flags*/ flags, + /*tag_action*/ tag_action, + /*data_ptr*/ data_ptr, + /*dxfer_len*/ dxfer_len, + /*timeout*/ timeout); + if (is48bit || lba > ATA_MAX_28BIT_LBA) + ata_48bit_cmd(&ccb->ataio, command, features, lba, + sector_count); + else + ata_28bit_cmd(&ccb->ataio, command, features, lba, + sector_count); + } else { + if (is48bit || lba > ATA_MAX_28BIT_LBA) + protocol |= AP_EXTEND; + + scsi_ata_pass_16(&ccb->csio, + /*retries*/ retry_count, + /*cbfcnp*/ NULL, + /*flags*/ flags, + /*tag_action*/ tag_action, + /*protocol*/ protocol, + /*ata_flags*/ ata_flags, + /*features*/ features, + /*sector_count*/ sector_count, + /*lba*/ lba, + /*command*/ command, + /*control*/ 0, + /*data_ptr*/ data_ptr, + /*dxfer_len*/ dxfer_len, + /*sense_len*/ sense_len, + /*timeout*/ timeout); + } +} + + static void cpi_print(struct ccb_pathinq *cpi) { @@ -8050,6 +8206,461 @@ ataaxm(struct cam_device *device, int argc, char **argv, return (retval); } +int +scsigetopcodes(struct cam_device *device, int opcode_set, int opcode, + int show_sa_errors, int sa_set, int service_action, + int timeout_desc, int retry_count, int timeout, int verbosemode, + uint32_t *fill_len, uint8_t **data_ptr) +{ + union ccb *ccb = NULL; + uint8_t *buf = NULL; + uint32_t alloc_len = 0, num_opcodes; + uint32_t valid_len = 0; + uint32_t avail_len = 0; + struct scsi_report_supported_opcodes_all *all_hdr; + struct scsi_report_supported_opcodes_one *one; + int options = 0; + int retval = 0; + + /* + * Make it clear that we haven't yet allocated or filled anything. + */ + *fill_len = 0; + *data_ptr = NULL; + + ccb = cam_getccb(device); + if (ccb == NULL) { + warnx("couldn't allocate CCB"); + retval = 1; + goto bailout; + } + + /* cam_getccb cleans up the header, caller has to zero the payload */ + bzero(&(&ccb->ccb_h)[1], + sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr)); + + if (opcode_set != 0) { + options |= RSO_OPTIONS_OC; + num_opcodes = 1; + alloc_len = sizeof(*one) + CAM_MAX_CDBLEN; + } else { + num_opcodes = 256; + alloc_len = sizeof(*all_hdr) + (num_opcodes * + sizeof(struct scsi_report_supported_opcodes_descr)); + } + + if (timeout_desc != 0) { + options |= RSO_RCTD; + alloc_len += num_opcodes * + sizeof(struct scsi_report_supported_opcodes_timeout); + } + + if (sa_set != 0) { + options |= RSO_OPTIONS_OC_SA; + if (show_sa_errors != 0) + options &= ~RSO_OPTIONS_OC; + } + +retry_alloc: + if (buf != NULL) { + free(buf); + buf = NULL; + } + + buf = malloc(alloc_len); + if (buf == NULL) { + warn("Unable to allocate %u bytes", alloc_len); + retval = 1; + goto bailout; + } + bzero(buf, alloc_len); + + scsi_report_supported_opcodes(&ccb->csio, + /*retries*/ retry_count, + /*cbfcnp*/ NULL, + /*tag_action*/ MSG_SIMPLE_Q_TAG, + /*options*/ options, + /*req_opcode*/ opcode, + /*req_service_action*/ service_action, + /*data_ptr*/ buf, + /*dxfer_len*/ alloc_len, + /*sense_len*/ SSD_FULL_SIZE, + /*timeout*/ timeout ? timeout : 10000); + + ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; + + if (retry_count != 0) + ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; + + if (cam_send_ccb(device, ccb) < 0) { + perror("error sending REPORT SUPPORTED OPERATION CODES"); + retval = 1; + goto bailout; + } + + if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { + if (verbosemode != 0) + cam_error_print(device, ccb, CAM_ESF_ALL, + CAM_EPF_ALL, stderr); + + retval = 1; + goto bailout; + } + + valid_len = ccb->csio.dxfer_len - ccb->csio.resid; + + if (((options & RSO_OPTIONS_MASK) == RSO_OPTIONS_ALL) + && (valid_len >= sizeof(*all_hdr))) { + all_hdr = (struct scsi_report_supported_opcodes_all *)buf; + avail_len = scsi_4btoul(all_hdr->length) + sizeof(*all_hdr); + } else if (((options & RSO_OPTIONS_MASK) != RSO_OPTIONS_ALL) + && (valid_len >= sizeof(*one))) { + uint32_t cdb_length; + + one = (struct scsi_report_supported_opcodes_one *)buf; + cdb_length = scsi_2btoul(one->cdb_length); + avail_len = sizeof(*one) + cdb_length; + if (one->support & RSO_ONE_CTDP) { + struct scsi_report_supported_opcodes_timeout *td; + + td = (struct scsi_report_supported_opcodes_timeout *) + &buf[avail_len]; + if (valid_len >= (avail_len + sizeof(td->length))) { + avail_len += scsi_2btoul(td->length) + + sizeof(td->length); + } else { + avail_len += sizeof(*td); + } + } + } + + /* + * avail_len could be zero if we didn't get enough data back from + * thet target to determine + */ + if ((avail_len != 0) + && (avail_len > valid_len)) { + alloc_len = avail_len; + goto retry_alloc; + } + + *fill_len = valid_len; + *data_ptr = buf; +bailout: + if (retval != 0) + free(buf); + + cam_freeccb(ccb); + + return (retval); +} + +static int +scsiprintoneopcode(struct cam_device *device, int req_opcode, int sa_set, + int req_sa, uint8_t *buf, uint32_t valid_len) +{ + struct scsi_report_supported_opcodes_one *one; + struct scsi_report_supported_opcodes_timeout *td; + uint32_t cdb_len = 0, td_len = 0; + const char *op_desc = NULL; + unsigned int i; + int retval = 0; + + one = (struct scsi_report_supported_opcodes_one *)buf; + + /* + * If we don't have the full single opcode descriptor, no point in + * continuing. + */ + if (valid_len < __offsetof(struct scsi_report_supported_opcodes_one, + cdb_length)) { + warnx("Only %u bytes returned, not enough to verify support", + valid_len); + retval = 1; + goto bailout; + } + + op_desc = scsi_op_desc(req_opcode, &device->inq_data); + + printf("%s (0x%02x)", op_desc != NULL ? op_desc : "UNKNOWN", + req_opcode); + if (sa_set != 0) + printf(", SA 0x%x", req_sa); + printf(": "); + + switch (one->support & RSO_ONE_SUP_MASK) { + case RSO_ONE_SUP_UNAVAIL: + printf("No command support information currently available\n"); + break; + case RSO_ONE_SUP_NOT_SUP: + printf("Command not supported\n"); + retval = 1; + goto bailout; + break; /*NOTREACHED*/ + case RSO_ONE_SUP_AVAIL: + printf("Command is supported, complies with a SCSI standard\n"); + break; + case RSO_ONE_SUP_VENDOR: + printf("Command is supported, vendor-specific " + "implementation\n"); + break; + default: + printf("Unknown command support flags 0x%#x\n", + one->support & RSO_ONE_SUP_MASK); + break; + } + + /* + * If we don't have the CDB length, it isn't exactly an error, the + * command probably isn't supported. + */ + if (valid_len < __offsetof(struct scsi_report_supported_opcodes_one, + cdb_usage)) + goto bailout; + + cdb_len = scsi_2btoul(one->cdb_length); + + /* + * If our valid data doesn't include the full reported length, + * return. The caller should have detected this and adjusted his + * allocation length to get all of the available data. + */ + if (valid_len < sizeof(*one) + cdb_len) { + retval = 1; + goto bailout; + } + + /* + * If all we have is the opcode, there is no point in printing out + * the usage bitmap. + */ + if (cdb_len <= 1) { + retval = 1; + goto bailout; + } + + printf("CDB usage bitmap:"); + for (i = 0; i < cdb_len; i++) { + printf(" %02x", one->cdb_usage[i]); + } + printf("\n"); + + /* + * If we don't have a timeout descriptor, we're done. + */ + if ((one->support & RSO_ONE_CTDP) == 0) + goto bailout; + + /* + * If we don't have enough valid length to include the timeout + * descriptor length, we're done. + */ + if (valid_len < (sizeof(*one) + cdb_len + sizeof(td->length))) + goto bailout; + + td = (struct scsi_report_supported_opcodes_timeout *) + &buf[sizeof(*one) + cdb_len]; + td_len = scsi_2btoul(td->length); + td_len += sizeof(td->length); + + /* + * If we don't have the full timeout descriptor, we're done. + */ + if (td_len < sizeof(*td)) + goto bailout; + + /* + * If we don't have enough valid length to contain the full timeout + * descriptor, we're done. + */ + if (valid_len < (sizeof(*one) + cdb_len + td_len)) + goto bailout; + + printf("Timeout information:\n"); + printf("Command-specific: 0x%02x\n", td->cmd_specific); + printf("Nominal timeout: %u seconds\n", + scsi_4btoul(td->nominal_time)); + printf("Recommended timeout: %u seconds\n", + scsi_4btoul(td->recommended_time)); + +bailout: + return (retval); +} + +static int +scsiprintopcodes(struct cam_device *device, int td_req, uint8_t *buf, + uint32_t valid_len) +{ + struct scsi_report_supported_opcodes_all *hdr; + struct scsi_report_supported_opcodes_descr *desc; + uint32_t avail_len = 0, used_len = 0; + uint8_t *cur_ptr; + int retval = 0; + + if (valid_len < sizeof(*hdr)) { + warnx("%s: not enough returned data (%u bytes) opcode list", + __func__, valid_len); + retval = 1; + goto bailout; + } + hdr = (struct scsi_report_supported_opcodes_all *)buf; + avail_len = scsi_4btoul(hdr->length); + avail_len += sizeof(hdr->length); + /* + * Take the lesser of the amount of data the drive claims is + * available, and the amount of data the HBA says was returned. + */ + avail_len = MIN(avail_len, valid_len); + + used_len = sizeof(hdr->length); + + printf("%-6s %4s %8s ", + "Opcode", "SA", "CDB len" ); + + if (td_req != 0) + printf("%5s %6s %6s ", "CS", "Nom", "Rec"); + printf(" Description\n"); + + while ((avail_len - used_len) > sizeof(*desc)) { + struct scsi_report_supported_opcodes_timeout *td; + uint32_t td_len; + const char *op_desc = NULL; + + cur_ptr = &buf[used_len]; + desc = (struct scsi_report_supported_opcodes_descr *)cur_ptr; + + op_desc = scsi_op_desc(desc->opcode, &device->inq_data); + if (op_desc == NULL) + op_desc = "UNKNOWN"; + + printf("0x%02x %#4x %8u ", desc->opcode, + scsi_2btoul(desc->service_action), + scsi_2btoul(desc->cdb_length)); + + used_len += sizeof(*desc); + + if ((desc->flags & RSO_CTDP) == 0) { + printf(" %s\n", op_desc); + continue; + } + + /* + * If we don't have enough space to fit a timeout + * descriptor, then we're done. + */ + if (avail_len - used_len < sizeof(*td)) { + used_len = avail_len; + printf(" %s\n", op_desc); + continue; + } + cur_ptr = &buf[used_len]; + td = (struct scsi_report_supported_opcodes_timeout *)cur_ptr; + td_len = scsi_2btoul(td->length); + td_len += sizeof(td->length); + + used_len += td_len; + /* + * If the given timeout descriptor length is less than what + * we understand, skip it. + */ + if (td_len < sizeof(*td)) { + printf(" %s\n", op_desc); + continue; + } + + printf(" 0x%02x %6u %6u %s\n", td->cmd_specific, + scsi_4btoul(td->nominal_time), + scsi_4btoul(td->recommended_time), op_desc); + } +bailout: + return (retval); +} + +static int +scsiopcodes(struct cam_device *device, int argc, char **argv, + char *combinedopt, int retry_count, int timeout, int verbosemode) +{ + int c; + uint32_t opcode = 0, service_action = 0; + int td_set = 0, opcode_set = 0, sa_set = 0; + int show_sa_errors = 1; + uint32_t valid_len = 0; + uint8_t *buf = NULL; + char *endptr; + int retval = 0; + + while ((c = getopt(argc, argv, combinedopt)) != -1) { + switch (c) { + case 'N': + show_sa_errors = 0; + break; + case 'o': + opcode = strtoul(optarg, &endptr, 0); + if (*endptr != '\0') { + warnx("Invalid opcode \"%s\", must be a number", + optarg); + retval = 1; + goto bailout; + } + if (opcode > 0xff) { + warnx("Invalid opcode 0x%#x, must be between" + "0 and 0xff inclusive", opcode); + retval = 1; + goto bailout; + } + opcode_set = 1; + break; + case 's': + service_action = strtoul(optarg, &endptr, 0); + if (*endptr != '\0') { + warnx("Invalid service action \"%s\", must " + "be a number", optarg); + retval = 1; + goto bailout; + } + if (service_action > 0xffff) { + warnx("Invalid service action 0x%#x, must " + "be between 0 and 0xffff inclusive", + service_action); + retval = 1; + } + sa_set = 1; + break; + case 'T': + td_set = 1; + break; + default: + break; + } + } + + if ((sa_set != 0) + && (opcode_set == 0)) { + warnx("You must specify an opcode with -o if a service " + "action is given"); + retval = 1; + goto bailout; + } + retval = scsigetopcodes(device, opcode_set, opcode, show_sa_errors, + sa_set, service_action, td_set, retry_count, + timeout, verbosemode, &valid_len, &buf); + if (retval != 0) + goto bailout; + + if ((opcode_set != 0) + || (sa_set != 0)) { + retval = scsiprintoneopcode(device, opcode, sa_set, + service_action, buf, valid_len); + } else { + retval = scsiprintopcodes(device, td_set, buf, valid_len); + } + +bailout: + free(buf); + + return (retval); +} + #endif /* MINIMALISTIC */ void @@ -8109,7 +8720,8 @@ usage(int printlong) " camcontrol sleep [dev_id][generic args]\n" " camcontrol apm [dev_id][generic args][-l level]\n" " camcontrol aam [dev_id][generic args][-l level]\n" -" camcontrol fwdownload [dev_id][generic args] <-f fw_image> [-y][-s]\n" +" camcontrol fwdownload [dev_id][generic args] <-f fw_image> [-q]\n" +" [-s][-y]\n" " camcontrol security [dev_id][generic args]\n" " <-d pwd | -e pwd | -f | -h pwd | -k pwd>\n" " [-l <high|maximum>] [-q] [-s pwd] [-T timeout]\n" @@ -8122,6 +8734,8 @@ usage(int printlong) " camcontrol attrib [dev_id][generic args] <-r action|-w attr>\n" " [-a attr_num][-c][-e elem][-F form1,form1]\n" " [-p part][-s start][-T type][-V vol]\n" +" camcontrol opcodes [dev_id][generic args][-o opcode][-s SA]\n" +" [-N][-T]\n" #endif /* MINIMALISTIC */ " camcontrol help\n"); if (!printlong) @@ -8162,6 +8776,7 @@ usage(int printlong) "security report or send ATA security commands to the named device\n" "persist send the SCSI PERSISTENT RESERVE IN or OUT commands\n" "attrib send the SCSI READ or WRITE ATTRIBUTE commands\n" +"opcodes send the SCSI REPORT SUPPORTED OPCODES command\n" "help this message\n" "Device Identifiers:\n" "bus:target specify the bus and target, lun defaults to 0\n" @@ -8266,9 +8881,10 @@ usage(int printlong) "-t <arg> number of seconds before respective state.\n" "fwdownload arguments:\n" "-f fw_image path to firmware image file\n" -"-y don't ask any questions\n" +"-q don't print informational messages, only errors\n" "-s run in simulation mode\n" "-v print info for every firmware segment sent to device\n" +"-y don't ask any questions\n" "security arguments:\n" "-d pwd disable security using the given password for the selected\n" " user\n" @@ -8326,6 +8942,11 @@ usage(int printlong) "-s start_attr request attributes starting at the given number\n" "-T elem_type specify the element type (used with -e)\n" "-V logical_vol specify the logical volume ID\n" +"opcodes arguments:\n" +"-o opcode specify the individual opcode to list\n" +"-s service_action specify the service action for the opcode\n" +"-N do not return SCSI error for unsupported SA\n" +"-T request nominal and recommended timeout values\n" ); #endif /* MINIMALISTIC */ } @@ -8658,8 +9279,7 @@ main(int argc, char **argv) break; case CAM_CMD_DOWNLOAD_FW: error = fwdownload(cam_dev, argc, argv, combinedopt, - arglist & CAM_ARG_VERBOSE, retry_count, timeout, - get_disk_type(cam_dev)); + arglist & CAM_ARG_VERBOSE, retry_count, timeout); break; case CAM_CMD_SANITIZE: error = scsisanitize(cam_dev, argc, argv, @@ -8675,6 +9295,10 @@ main(int argc, char **argv) retry_count, timeout, arglist & CAM_ARG_VERBOSE, arglist & CAM_ARG_ERR_RECOVER); break; + case CAM_CMD_OPCODES: + error = scsiopcodes(cam_dev, argc, argv, combinedopt, + retry_count, timeout, arglist & CAM_ARG_VERBOSE); + break; #endif /* MINIMALISTIC */ case CAM_CMD_USAGE: usage(1); diff --git a/sbin/camcontrol/camcontrol.h b/sbin/camcontrol/camcontrol.h index 582b6a3..bb2fe4f 100644 --- a/sbin/camcontrol/camcontrol.h +++ b/sbin/camcontrol/camcontrol.h @@ -37,6 +37,14 @@ typedef enum { CC_OR_FOUND } camcontrol_optret; +typedef enum { + CC_DT_NONE, + CC_DT_SCSI, + CC_DT_ATA_BEHIND_SCSI, + CC_DT_ATA, + CC_DT_UNKNOWN +} camcontrol_devtype; + /* * get_hook: Structure for evaluating args in a callback. */ @@ -49,9 +57,22 @@ struct get_hook extern int verbose; +int ata_do_identify(struct cam_device *device, int retry_count, int timeout, + union ccb *ccb, struct ata_params **ident_bufp); +int dev_has_vpd_page(struct cam_device *dev, uint8_t page_id, int retry_count, + int timeout, int verbosemode); +int get_device_type(struct cam_device *dev, int retry_count, int timeout, + int verbosemode, camcontrol_devtype *devtype); +void build_ata_cmd(union ccb *ccb, uint32_t retry_count, uint32_t flags, + uint8_t tag_action, uint8_t protocol, uint8_t ata_flags, + uint16_t features, uint16_t sector_count, uint64_t lba, + uint8_t command, uint8_t *data_ptr, uint16_t dxfer_len, + uint8_t sense_len, uint32_t timeout, int is48bit, + camcontrol_devtype devtype); +int camxferrate(struct cam_device *device); int fwdownload(struct cam_device *device, int argc, char **argv, - char *combinedopt, int printerrors, int retry_count, int timeout, - const char */*type*/); + char *combinedopt, int printerrors, int retry_count, + int timeout); void mode_sense(struct cam_device *device, int mode_page, int page_control, int dbd, int retry_count, int timeout, u_int8_t *data, int datalen); @@ -63,6 +84,10 @@ void mode_list(struct cam_device *device, int page_control, int dbd, int retry_count, int timeout); int scsidoinquiry(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout); +int scsigetopcodes(struct cam_device *device, int opcode_set, int opcode, + int show_sa_errors, int sa_set, int service_action, + int timeout_desc, int retry_count, int timeout, + int verbosemode, uint32_t *fill_len, uint8_t **data_ptr); int scsipersist(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout, int verbose, int err_recover); diff --git a/sbin/camcontrol/fwdownload.c b/sbin/camcontrol/fwdownload.c index 181c234..2c2d12a 100644 --- a/sbin/camcontrol/fwdownload.c +++ b/sbin/camcontrol/fwdownload.c @@ -68,9 +68,10 @@ __FBSDID("$FreeBSD$"); #include "camcontrol.h" -#define CMD_TIMEOUT 50000 /* 50 seconds */ +#define WB_TIMEOUT 50000 /* 50 seconds */ typedef enum { + VENDOR_HGST, VENDOR_HITACHI, VENDOR_HP, VENDOR_IBM, @@ -79,32 +80,163 @@ typedef enum { VENDOR_QUANTUM, VENDOR_SAMSUNG, VENDOR_SEAGATE, + VENDOR_SMART, + VENDOR_ATA, VENDOR_UNKNOWN } fw_vendor_t; +/* + * FW_TUR_READY: The drive must return good status for a test unit ready. + * + * FW_TUR_NOT_READY: The drive must return not ready status for a test unit + * ready. You may want this in a removable media drive. + * + * FW_TUR_NA: It doesn't matter whether the drive is ready or not. + * This may be the case for a removable media drive. + */ +typedef enum { + FW_TUR_NONE, + FW_TUR_READY, + FW_TUR_NOT_READY, + FW_TUR_NA +} fw_tur_status; + +/* + * FW_TIMEOUT_DEFAULT: Attempt to probe for a WRITE BUFFER timeout + * value from the drive. If we get an answer, + * use the Recommended timeout. Otherwise, + * use the default value from the table. + * + * FW_TIMEOUT_DEV_REPORTED: The timeout value was probed directly from + * the device. + * + * FW_TIMEOUT_NO_PROBE: Do not ask the device for a WRITE BUFFER + * timeout value. Use the device-specific + * value. + * + * FW_TIMEOUT_USER_SPEC: The user specified a timeout on the command + * line with the -t option. This overrides any + * probe or default timeout. + */ +typedef enum { + FW_TIMEOUT_DEFAULT, + FW_TIMEOUT_DEV_REPORTED, + FW_TIMEOUT_NO_PROBE, + FW_TIMEOUT_USER_SPEC +} fw_timeout_type; + +/* + * type: Enumeration for the particular vendor. + * + * pattern: Pattern to match for the Vendor ID from the SCSI + * Inquiry data. + * + * dev_type: SCSI device type to match, or T_ANY to match any + * device from the given vendor. Note that if there + * is a specific device type listed for a particular + * vendor, it must be listed before a T_ANY entry. + * + * max_pkt_size: Maximum packet size when talking to a device. Note + * that although large data sizes may be supported by + * the target device, they may not be supported by the + * OS or the controller. + * + * cdb_byte2: This specifies byte 2 (byte 1 when counting from 0) + * of the CDB. This is generally the WRITE BUFFER mode. + * + * cdb_byte2_last: This specifies byte 2 for the last chunk of the + * download. + * + * inc_cdb_buffer_id: Increment the buffer ID by 1 for each chunk sent + * down to the drive. + * + * inc_cdb_offset: Increment the offset field in the CDB with the byte + * offset into the firmware file. + * + * tur_status: Pay attention to whether the device is ready before + * upgrading the firmware, or not. See above for the + * values. + */ struct fw_vendor { fw_vendor_t type; const char *pattern; + int dev_type; int max_pkt_size; u_int8_t cdb_byte2; u_int8_t cdb_byte2_last; int inc_cdb_buffer_id; int inc_cdb_offset; + fw_tur_status tur_status; + int timeout_ms; + fw_timeout_type timeout_type; +}; + +/* + * Vendor notes: + * + * HGST: The packets need to be sent in multiples of 4K. + * + * IBM: For LTO and TS drives, the buffer ID is ignored in mode 7 (and + * some other modes). It treats the request as a firmware download. + * The offset (and therefore the length of each chunk sent) needs + * to be a multiple of the offset boundary specified for firmware + * (buffer ID 4) in the read buffer command. At least for LTO-6, + * that seems to be 0, but using a 32K chunk size should satisfy + * most any alignment requirement. + * + * SmrtStor: Mode 5 is also supported, but since the firmware is 400KB or + * so, we can't fit it in a single request in most cases. + */ +static struct fw_vendor vendors_list[] = { + {VENDOR_HGST, "HGST", T_DIRECT, + 0x1000, 0x07, 0x07, 1, 0, FW_TUR_READY, WB_TIMEOUT, FW_TIMEOUT_DEFAULT}, + {VENDOR_HITACHI, "HITACHI", T_ANY, + 0x8000, 0x05, 0x05, 1, 0, FW_TUR_READY, WB_TIMEOUT, FW_TIMEOUT_DEFAULT}, + {VENDOR_HP, "HP", T_ANY, + 0x8000, 0x07, 0x07, 0, 1, FW_TUR_READY, WB_TIMEOUT, FW_TIMEOUT_DEFAULT}, + {VENDOR_IBM, "IBM", T_SEQUENTIAL, + 0x8000, 0x07, 0x07, 0, 1, FW_TUR_NA, 300 * 1000, FW_TIMEOUT_DEFAULT}, + {VENDOR_IBM, "IBM", T_ANY, + 0x8000, 0x05, 0x05, 1, 0, FW_TUR_READY, WB_TIMEOUT, FW_TIMEOUT_DEFAULT}, + {VENDOR_PLEXTOR, "PLEXTOR", T_ANY, + 0x2000, 0x04, 0x05, 0, 1, FW_TUR_READY, WB_TIMEOUT, FW_TIMEOUT_DEFAULT}, + {VENDOR_QUALSTAR, "QUALSTAR", T_ANY, + 0x2030, 0x05, 0x05, 0, 0, FW_TUR_READY, WB_TIMEOUT, FW_TIMEOUT_DEFAULT}, + {VENDOR_QUANTUM, "QUANTUM", T_ANY, + 0x2000, 0x04, 0x05, 0, 1, FW_TUR_READY, WB_TIMEOUT, FW_TIMEOUT_DEFAULT}, + {VENDOR_SAMSUNG, "SAMSUNG", T_ANY, + 0x8000, 0x07, 0x07, 0, 1, FW_TUR_READY, WB_TIMEOUT, FW_TIMEOUT_DEFAULT}, + {VENDOR_SEAGATE, "SEAGATE", T_ANY, + 0x8000, 0x07, 0x07, 0, 1, FW_TUR_READY, WB_TIMEOUT, FW_TIMEOUT_DEFAULT}, + {VENDOR_SMART, "SmrtStor", T_DIRECT, + 0x8000, 0x07, 0x07, 0, 1, FW_TUR_READY, WB_TIMEOUT, FW_TIMEOUT_DEFAULT}, + + /* + * We match any ATA device. This is really just a placeholder, + * since we won't actually send a WRITE BUFFER with any of the + * listed parameters. If a SATA device is behind a SAS controller, + * the SCSI to ATA translation code (at least for LSI) doesn't + * generaly translate a SCSI WRITE BUFFER into an ATA DOWNLOAD + * MICROCODE command. So, we use the SCSI ATA PASS_THROUGH command + * to send the ATA DOWNLOAD MICROCODE command instead. + */ + {VENDOR_ATA, "ATA", T_ANY, + 0x8000, 0x07, 0x07, 0, 1, FW_TUR_READY, WB_TIMEOUT, + FW_TIMEOUT_NO_PROBE}, + {VENDOR_UNKNOWN, NULL, T_ANY, + 0x0000, 0x00, 0x00, 0, 0, FW_TUR_NONE, WB_TIMEOUT, FW_TIMEOUT_DEFAULT} +}; + +struct fw_timeout_desc { + fw_timeout_type timeout_type; + const char *timeout_desc; }; -static const struct fw_vendor vendors_list[] = { - {VENDOR_HITACHI, "HITACHI", 0x8000, 0x05, 0x05, 1, 0}, - {VENDOR_HP, "HP", 0x8000, 0x07, 0x07, 0, 1}, - {VENDOR_IBM, "IBM", 0x8000, 0x05, 0x05, 1, 0}, - {VENDOR_PLEXTOR, "PLEXTOR", 0x2000, 0x04, 0x05, 0, 1}, - {VENDOR_QUALSTAR, "QUALSTAR", 0x2030, 0x05, 0x05, 0, 0}, - {VENDOR_QUANTUM, "QUANTUM", 0x2000, 0x04, 0x05, 0, 1}, - {VENDOR_SAMSUNG, "SAMSUNG", 0x8000, 0x07, 0x07, 0, 1}, - {VENDOR_SEAGATE, "SEAGATE", 0x8000, 0x07, 0x07, 0, 1}, - /* the next 2 are SATA disks going through SAS HBA */ - {VENDOR_SEAGATE, "ATA ST", 0x8000, 0x07, 0x07, 0, 1}, - {VENDOR_HITACHI, "ATA HDS", 0x8000, 0x05, 0x05, 1, 0}, - {VENDOR_UNKNOWN, NULL, 0x0000, 0x00, 0x00, 0, 0} +static const struct fw_timeout_desc fw_timeout_desc_table[] = { + { FW_TIMEOUT_DEFAULT, "the default" }, + { FW_TIMEOUT_DEV_REPORTED, "recommended by this particular device" }, + { FW_TIMEOUT_NO_PROBE, "the default" }, + { FW_TIMEOUT_USER_SPEC, "what was specified on the command line" } }; #ifndef ATA_DOWNLOAD_MICROCODE @@ -128,43 +260,308 @@ static const struct fw_vendor vendors_list[] = { #define UNKNOWN_MAX_PKT_SIZE 0x8000 #endif -static const struct fw_vendor *fw_get_vendor(struct cam_device *cam_dev); -static char *fw_read_img(const char *fw_img_path, - const struct fw_vendor *vp, int *num_bytes); -static int fw_download_img(struct cam_device *cam_dev, - const struct fw_vendor *vp, char *buf, int img_size, - int sim_mode, int printerrors, int retry_count, int timeout, - const char */*name*/, const char */*type*/); +static struct fw_vendor *fw_get_vendor(struct cam_device *cam_dev, + struct ata_params *ident_buf); +static int fw_get_timeout(struct cam_device *cam_dev, struct fw_vendor *vp, + int retry_count, int timeout); +static int fw_validate_ibm(struct cam_device *dev, int retry_count, + int timeout, int fd, char *buf, + const char *fw_img_path, int quiet); +static char *fw_read_img(struct cam_device *dev, int retry_count, + int timeout, int quiet, const char *fw_img_path, + struct fw_vendor *vp, int *num_bytes); +static int fw_check_device_ready(struct cam_device *dev, + camcontrol_devtype devtype, + struct fw_vendor *vp, int printerrors, + int timeout); +static int fw_download_img(struct cam_device *cam_dev, + struct fw_vendor *vp, char *buf, int img_size, + int sim_mode, int printerrors, int quiet, + int retry_count, int timeout, const char */*name*/, + camcontrol_devtype devtype); /* * Find entry in vendors list that belongs to * the vendor of given cam device. */ -static const struct fw_vendor * -fw_get_vendor(struct cam_device *cam_dev) +static struct fw_vendor * +fw_get_vendor(struct cam_device *cam_dev, struct ata_params *ident_buf) { - char vendor[SID_VENDOR_SIZE + 1]; - const struct fw_vendor *vp; + char vendor[42]; + struct fw_vendor *vp; if (cam_dev == NULL) return (NULL); - cam_strvis((u_char *)vendor, (u_char *)cam_dev->inq_data.vendor, - sizeof(cam_dev->inq_data.vendor), sizeof(vendor)); + + if (ident_buf != NULL) { + cam_strvis((u_char *)vendor, ident_buf->model, + sizeof(ident_buf->model), sizeof(vendor)); + for (vp = vendors_list; vp->pattern != NULL; vp++) { + if (vp->type == VENDOR_ATA) + return (vp); + } + } else { + cam_strvis((u_char *)vendor, (u_char *)cam_dev->inq_data.vendor, + sizeof(cam_dev->inq_data.vendor), sizeof(vendor)); + } for (vp = vendors_list; vp->pattern != NULL; vp++) { if (!cam_strmatch((const u_char *)vendor, - (const u_char *)vp->pattern, strlen(vendor))) - break; + (const u_char *)vp->pattern, strlen(vendor))) { + if ((vp->dev_type == T_ANY) + || (vp->dev_type == SID_TYPE(&cam_dev->inq_data))) + break; + } } return (vp); } +static int +fw_get_timeout(struct cam_device *cam_dev, struct fw_vendor *vp, + int retry_count, int timeout) +{ + struct scsi_report_supported_opcodes_one *one; + struct scsi_report_supported_opcodes_timeout *td; + uint8_t *buf = NULL; + uint32_t fill_len = 0, cdb_len = 0, rec_timeout = 0; + int retval = 0; + + /* + * If the user has specified a timeout on the command line, we let + * him override any default or probed value. + */ + if (timeout != 0) { + vp->timeout_type = FW_TIMEOUT_USER_SPEC; + vp->timeout_ms = timeout; + goto bailout; + } + + /* + * Check to see whether we should probe for a timeout for this + * device. + */ + if (vp->timeout_type == FW_TIMEOUT_NO_PROBE) + goto bailout; + + retval = scsigetopcodes(/*device*/ cam_dev, + /*opcode_set*/ 1, + /*opcode*/ WRITE_BUFFER, + /*show_sa_errors*/ 1, + /*sa_set*/ 0, + /*service_action*/ 0, + /*timeout_desc*/ 1, + /*retry_count*/ retry_count, + /*timeout*/ 10000, + /*verbose*/ 0, + /*fill_len*/ &fill_len, + /*data_ptr*/ &buf); + /* + * It isn't an error if we can't get a timeout descriptor. We just + * continue on with the default timeout. + */ + if (retval != 0) { + retval = 0; + goto bailout; + } + + /* + * Even if the drive didn't return a SCSI error, if we don't have + * enough data to contain the one opcode descriptor, the CDB + * structure and a timeout descriptor, we don't have the timeout + * value we're looking for. So we'll just fall back to the + * default value. + */ + if (fill_len < (sizeof(*one) + sizeof(struct scsi_write_buffer) + + sizeof(*td))) + goto bailout; + + one = (struct scsi_report_supported_opcodes_one *)buf; + + /* + * If the drive claims to not support the WRITE BUFFER command... + * fall back to the default timeout value and let things fail on + * the actual firmware download. + */ + if ((one->support & RSO_ONE_SUP_MASK) == RSO_ONE_SUP_NOT_SUP) + goto bailout; + + cdb_len = scsi_2btoul(one->cdb_length); + td = (struct scsi_report_supported_opcodes_timeout *) + &buf[sizeof(*one) + cdb_len]; + + rec_timeout = scsi_4btoul(td->recommended_time); + /* + * If the recommended timeout is 0, then the device has probably + * returned a bogus value. + */ + if (rec_timeout == 0) + goto bailout; + + /* CAM timeouts are in ms */ + rec_timeout *= 1000; + + vp->timeout_ms = rec_timeout; + vp->timeout_type = FW_TIMEOUT_DEV_REPORTED; + +bailout: + return (retval); +} + +#define SVPD_IBM_FW_DESIGNATION 0x03 + +/* + * IBM LTO and TS tape drives have an INQUIRY VPD page 0x3 with the following + * format: + */ +struct fw_ibm_tape_fw_designation { + uint8_t device; + uint8_t page_code; + uint8_t reserved; + uint8_t length; + uint8_t ascii_length; + uint8_t reserved2[3]; + uint8_t load_id[4]; + uint8_t fw_rev[4]; + uint8_t ptf_number[4]; + uint8_t patch_number[4]; + uint8_t ru_name[8]; + uint8_t lib_seq_num[5]; +}; + +/* + * The firmware for IBM tape drives has the following header format. The + * load_id and ru_name in the header file should match what is returned in + * VPD page 0x3. + */ +struct fw_ibm_tape_fw_header { + uint8_t unspec[4]; + uint8_t length[4]; /* Firmware and header! */ + uint8_t load_id[4]; + uint8_t fw_rev[4]; + uint8_t reserved[8]; + uint8_t ru_name[8]; +}; + +static int +fw_validate_ibm(struct cam_device *dev, int retry_count, int timeout, int fd, + char *buf, const char *fw_img_path, int quiet) +{ + union ccb *ccb; + struct fw_ibm_tape_fw_designation vpd_page; + struct fw_ibm_tape_fw_header *header; + char drive_rev[sizeof(vpd_page.fw_rev) + 1]; + char file_rev[sizeof(vpd_page.fw_rev) + 1]; + int retval = 1; + + ccb = cam_getccb(dev); + if (ccb == NULL) { + warnx("couldn't allocate CCB"); + goto bailout; + } + + /* cam_getccb cleans up the header, caller has to zero the payload */ + bzero(&(&ccb->ccb_h)[1], + sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr)); + + bzero(&vpd_page, sizeof(vpd_page)); + + scsi_inquiry(&ccb->csio, + /*retries*/ retry_count, + /*cbfcnp*/ NULL, + /* tag_action */ MSG_SIMPLE_Q_TAG, + /* inq_buf */ (u_int8_t *)&vpd_page, + /* inq_len */ sizeof(vpd_page), + /* evpd */ 1, + /* page_code */ SVPD_IBM_FW_DESIGNATION, + /* sense_len */ SSD_FULL_SIZE, + /* timeout */ timeout ? timeout : 5000); + + /* Disable freezing the device queue */ + ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; + + if (retry_count != 0) + ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; + + if (cam_send_ccb(dev, ccb) < 0) { + warn("error getting firmware designation page"); + + cam_error_print(dev, ccb, CAM_ESF_ALL, + CAM_EPF_ALL, stderr); + + cam_freeccb(ccb); + goto bailout; + } + + if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { + cam_error_print(dev, ccb, CAM_ESF_ALL, + CAM_EPF_ALL, stderr); + goto bailout; + } + + /* + * Read the firmware header only. + */ + if (read(fd, buf, sizeof(*header)) != sizeof(*header)) { + warn("unable to read %zu bytes from %s", sizeof(*header), + fw_img_path); + goto bailout; + } + + /* Rewind the file back to 0 for the full file read. */ + if (lseek(fd, 0, SEEK_SET) == -1) { + warn("Unable to lseek"); + goto bailout; + } + + header = (struct fw_ibm_tape_fw_header *)buf; + + bzero(drive_rev, sizeof(drive_rev)); + bcopy(vpd_page.fw_rev, drive_rev, sizeof(vpd_page.fw_rev)); + bzero(file_rev, sizeof(file_rev)); + bcopy(header->fw_rev, file_rev, sizeof(header->fw_rev)); + + if (quiet == 0) { + fprintf(stdout, "Current Drive Firmware version: %s\n", + drive_rev); + fprintf(stdout, "Firmware File version: %s\n", file_rev); + } + + /* + * For IBM tape drives the load ID and RU name reported by the + * drive should match what is in the firmware file. + */ + if (bcmp(vpd_page.load_id, header->load_id, + MIN(sizeof(vpd_page.load_id), sizeof(header->load_id))) != 0) { + warnx("Drive Firmware load ID 0x%x does not match firmware " + "file load ID 0x%x", scsi_4btoul(vpd_page.load_id), + scsi_4btoul(header->load_id)); + goto bailout; + } + + if (bcmp(vpd_page.ru_name, header->ru_name, + MIN(sizeof(vpd_page.ru_name), sizeof(header->ru_name))) != 0) { + warnx("Drive Firmware RU name 0x%jx does not match firmware " + "file RU name 0x%jx", + (uintmax_t)scsi_8btou64(vpd_page.ru_name), + (uintmax_t)scsi_8btou64(header->ru_name)); + goto bailout; + } + if (quiet == 0) + fprintf(stdout, "Firmware file is valid for this drive.\n"); + retval = 0; +bailout: + cam_freeccb(ccb); + + return (retval); +} + /* * Allocate a buffer and read fw image file into it * from given path. Number of bytes read is stored * in num_bytes. */ static char * -fw_read_img(const char *fw_img_path, const struct fw_vendor *vp, int *num_bytes) +fw_read_img(struct cam_device *dev, int retry_count, int timeout, int quiet, + const char *fw_img_path, struct fw_vendor *vp, int *num_bytes) { int fd; struct stat stbuf; @@ -207,6 +604,14 @@ fw_read_img(const char *fw_img_path, const struct fw_vendor *vp, int *num_bytes) case VENDOR_QUALSTAR: skip_bytes = img_size % 1030; break; + case VENDOR_IBM: { + if (vp->dev_type != T_SEQUENTIAL) + break; + if (fw_validate_ibm(dev, retry_count, timeout, fd, buf, + fw_img_path, quiet) != 0) + goto bailout; + break; + } default: break; } @@ -234,85 +639,162 @@ bailout1: return (NULL); } +/* + * Returns 0 for "success", where success means that the device has met the + * requirement in the vendor structure for being ready or not ready when + * firmware is downloaded. + * + * Returns 1 for a failure to be ready to accept a firmware download. + * (e.g., a drive needs to be ready, but returns not ready) + * + * Returns -1 for any other failure. + */ +static int +fw_check_device_ready(struct cam_device *dev, camcontrol_devtype devtype, + struct fw_vendor *vp, int printerrors, int timeout) +{ + union ccb *ccb; + int retval = 0; + int16_t *ptr = NULL; + size_t dxfer_len = 0; + + if ((ccb = cam_getccb(dev)) == NULL) { + warnx("Could not allocate CCB"); + retval = -1; + goto bailout; + } + + bzero(&(&ccb->ccb_h)[1], + sizeof(union ccb) - sizeof(struct ccb_hdr)); + + if (devtype != CC_DT_SCSI) { + dxfer_len = sizeof(struct ata_params); + + ptr = (uint16_t *)malloc(dxfer_len); + if (ptr == NULL) { + warnx("can't malloc memory for identify"); + retval = -1; + goto bailout; + } + bzero(ptr, dxfer_len); + } + + switch (devtype) { + case CC_DT_SCSI: + scsi_test_unit_ready(&ccb->csio, + /*retries*/ 0, + /*cbfcnp*/ NULL, + /*tag_action*/ MSG_SIMPLE_Q_TAG, + /*sense_len*/ SSD_FULL_SIZE, + /*timeout*/ 5000); + break; + case CC_DT_ATA_BEHIND_SCSI: + case CC_DT_ATA: { + build_ata_cmd(ccb, + /*retries*/ 1, + /*flags*/ CAM_DIR_IN, + /*tag_action*/ MSG_SIMPLE_Q_TAG, + /*protocol*/ AP_PROTO_PIO_IN, + /*ata_flags*/ AP_FLAG_BYT_BLOK_BYTES | + AP_FLAG_TLEN_SECT_CNT | + AP_FLAG_TDIR_FROM_DEV, + /*features*/ 0, + /*sector_count*/ (uint8_t) dxfer_len, + /*lba*/ 0, + /*command*/ ATA_ATA_IDENTIFY, + /*data_ptr*/ (uint8_t *)ptr, + /*dxfer_len*/ dxfer_len, + /*sense_len*/ SSD_FULL_SIZE, + /*timeout*/ timeout ? timeout : 30 * 1000, + /*is48bit*/ 0, + /*devtype*/ devtype); + break; + } + default: + warnx("Unknown disk type %d", devtype); + retval = -1; + goto bailout; + break; /*NOTREACHED*/ + } + + ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; + + retval = cam_send_ccb(dev, ccb); + if (retval != 0) { + warn("error sending %s CCB", (devtype == CC_DT_SCSI) ? + "Test Unit Ready" : "Identify"); + retval = -1; + goto bailout; + } + + if (((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) + && (vp->tur_status == FW_TUR_READY)) { + warnx("Device is not ready"); + if (printerrors) + cam_error_print(dev, ccb, CAM_ESF_ALL, + CAM_EPF_ALL, stderr); + retval = 1; + goto bailout; + } else if (((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) + && (vp->tur_status == FW_TUR_NOT_READY)) { + warnx("Device cannot have media loaded when firmware is " + "downloaded"); + retval = 1; + goto bailout; + } +bailout: + if (ccb != NULL) + cam_freeccb(ccb); + + return (retval); +} + /* * Download firmware stored in buf to cam_dev. If simulation mode * is enabled, only show what packet sizes would be sent to the * device but do not sent any actual packets */ static int -fw_download_img(struct cam_device *cam_dev, const struct fw_vendor *vp, - char *buf, int img_size, int sim_mode, int printerrors, int retry_count, - int timeout, const char *imgname, const char *type) +fw_download_img(struct cam_device *cam_dev, struct fw_vendor *vp, + char *buf, int img_size, int sim_mode, int printerrors, int quiet, + int retry_count, int timeout, const char *imgname, + camcontrol_devtype devtype) { struct scsi_write_buffer cdb; progress_t progress; - int size; - union ccb *ccb; + int size = 0; + union ccb *ccb = NULL; int pkt_count = 0; int max_pkt_size; u_int32_t pkt_size = 0; char *pkt_ptr = buf; u_int32_t offset; int last_pkt = 0; - int16_t *ptr; + int retval = 0; + + /* + * Check to see whether the device is ready to accept a firmware + * download. + */ + retval = fw_check_device_ready(cam_dev, devtype, vp, printerrors, + timeout); + if (retval != 0) + goto bailout; if ((ccb = cam_getccb(cam_dev)) == NULL) { warnx("Could not allocate CCB"); - return (1); + retval = 1; + goto bailout; } - if (strcmp(type, "scsi") == 0) { - scsi_test_unit_ready(&ccb->csio, 0, NULL, MSG_SIMPLE_Q_TAG, - SSD_FULL_SIZE, 5000); - } else if (strcmp(type, "ata") == 0) { - /* cam_getccb cleans up the header, caller has to zero the payload */ - bzero(&(&ccb->ccb_h)[1], - sizeof(struct ccb_ataio) - sizeof(struct ccb_hdr)); - ptr = (uint16_t *)malloc(sizeof(struct ata_params)); + bzero(&(&ccb->ccb_h)[1], + sizeof(union ccb) - sizeof(struct ccb_hdr)); - if (ptr == NULL) { - cam_freeccb(ccb); - warnx("can't malloc memory for identify\n"); - return(1); - } - bzero(ptr, sizeof(struct ata_params)); - cam_fill_ataio(&ccb->ataio, - 1, - NULL, - /*flags*/CAM_DIR_IN, - MSG_SIMPLE_Q_TAG, - /*data_ptr*/(uint8_t *)ptr, - /*dxfer_len*/sizeof(struct ata_params), - timeout ? timeout : 30 * 1000); - ata_28bit_cmd(&ccb->ataio, ATA_ATA_IDENTIFY, 0, 0, 0); - } else { - warnx("weird disk type '%s'", type); - cam_freeccb(ccb); - return 1; - } - /* Disable freezing the device queue. */ - ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; - if (cam_send_ccb(cam_dev, ccb) < 0) { - warnx("Error sending identify/test unit ready"); - if (printerrors) - cam_error_print(cam_dev, ccb, CAM_ESF_ALL, - CAM_EPF_ALL, stderr); - cam_freeccb(ccb); - return(1); - } - if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { - warnx("Device is not ready"); - if (printerrors) - cam_error_print(cam_dev, ccb, CAM_ESF_ALL, - CAM_EPF_ALL, stderr); - cam_freeccb(ccb); - return (1); - } max_pkt_size = vp->max_pkt_size; - if (vp->max_pkt_size == 0 && strcmp(type, "ata") == 0) { + if (max_pkt_size == 0) max_pkt_size = UNKNOWN_MAX_PKT_SIZE; - } - pkt_size = vp->max_pkt_size; + + pkt_size = max_pkt_size; progress_init(&progress, imgname, size = img_size); /* Download single fw packets. */ do { @@ -321,56 +803,73 @@ fw_download_img(struct cam_device *cam_dev, const struct fw_vendor *vp, pkt_size = img_size; } progress_update(&progress, size - img_size); - progress_draw(&progress); + if (((sim_mode == 0) && (quiet == 0)) + || ((sim_mode != 0) && (printerrors == 0))) + progress_draw(&progress); bzero(&cdb, sizeof(cdb)); - if (strcmp(type, "scsi") == 0) { + switch (devtype) { + case CC_DT_SCSI: cdb.opcode = WRITE_BUFFER; cdb.control = 0; /* Parameter list length. */ scsi_ulto3b(pkt_size, &cdb.length[0]); offset = vp->inc_cdb_offset ? (pkt_ptr - buf) : 0; scsi_ulto3b(offset, &cdb.offset[0]); - cdb.byte2 = last_pkt ? vp->cdb_byte2_last : vp->cdb_byte2; + cdb.byte2 = last_pkt ? vp->cdb_byte2_last : + vp->cdb_byte2; cdb.buffer_id = vp->inc_cdb_buffer_id ? pkt_count : 0; /* Zero out payload of ccb union after ccb header. */ - bzero((u_char *)ccb + sizeof(struct ccb_hdr), + bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr)); - /* Copy previously constructed cdb into ccb_scsiio struct. */ + /* + * Copy previously constructed cdb into ccb_scsiio + * struct. + */ bcopy(&cdb, &ccb->csio.cdb_io.cdb_bytes[0], sizeof(struct scsi_write_buffer)); /* Fill rest of ccb_scsiio struct. */ - if (!sim_mode) { - cam_fill_csio(&ccb->csio, /* ccb_scsiio */ - retry_count, /* retries */ - NULL, /* cbfcnp */ - CAM_DIR_OUT | CAM_DEV_QFRZDIS, /* flags */ - CAM_TAG_ACTION_NONE, /* tag_action */ - (u_char *)pkt_ptr, /* data_ptr */ - pkt_size, /* dxfer_len */ - SSD_FULL_SIZE, /* sense_len */ - sizeof(struct scsi_write_buffer), /* cdb_len */ - timeout ? timeout : CMD_TIMEOUT); /* timeout */ - } - } else if (strcmp(type, "ata") == 0) { - bzero(&(&ccb->ccb_h)[1], - sizeof(struct ccb_ataio) - sizeof(struct ccb_hdr)); - if (!sim_mode) { - uint32_t off; - - cam_fill_ataio(&ccb->ataio, - (last_pkt) ? 256 : retry_count, - NULL, - /*flags*/CAM_DIR_OUT | CAM_DEV_QFRZDIS, - CAM_TAG_ACTION_NONE, - /*data_ptr*/(uint8_t *)pkt_ptr, - /*dxfer_len*/pkt_size, - timeout ? timeout : 30 * 1000); - off = (uint32_t)(pkt_ptr - buf); - ata_28bit_cmd(&ccb->ataio, ATA_DOWNLOAD_MICROCODE, - USE_OFFSETS_FEATURE, - ATA_MAKE_LBA(off, pkt_size), - ATA_MAKE_SECTORS(pkt_size)); - } + cam_fill_csio(&ccb->csio, /* ccb_scsiio*/ + retry_count, /* retries*/ + NULL, /* cbfcnp*/ + CAM_DIR_OUT | CAM_DEV_QFRZDIS, /* flags*/ + CAM_TAG_ACTION_NONE, /* tag_action*/ + (u_char *)pkt_ptr, /* data_ptr*/ + pkt_size, /* dxfer_len*/ + SSD_FULL_SIZE, /* sense_len*/ + sizeof(struct scsi_write_buffer), /* cdb_len*/ + timeout ? timeout : WB_TIMEOUT); /* timeout*/ + break; + case CC_DT_ATA: + case CC_DT_ATA_BEHIND_SCSI: { + uint32_t off; + + off = (uint32_t)(pkt_ptr - buf); + + build_ata_cmd(ccb, + /*retry_count*/ retry_count, + /*flags*/ CAM_DIR_OUT | CAM_DEV_QFRZDIS, + /*tag_action*/ CAM_TAG_ACTION_NONE, + /*protocol*/ AP_PROTO_PIO_OUT, + /*ata_flags*/ AP_FLAG_BYT_BLOK_BYTES | + AP_FLAG_TLEN_SECT_CNT | + AP_FLAG_TDIR_TO_DEV, + /*features*/ USE_OFFSETS_FEATURE, + /*sector_count*/ ATA_MAKE_SECTORS(pkt_size), + /*lba*/ ATA_MAKE_LBA(off, pkt_size), + /*command*/ ATA_DOWNLOAD_MICROCODE, + /*data_ptr*/ (uint8_t *)pkt_ptr, + /*dxfer_len*/ pkt_size, + /*sense_len*/ SSD_FULL_SIZE, + /*timeout*/ timeout ? timeout : WB_TIMEOUT, + /*is48bit*/ 0, + /*devtype*/ devtype); + break; + } + default: + warnx("Unknown device type %d", devtype); + retval = 1; + goto bailout; + break; /*NOTREACHED*/ } if (!sim_mode) { /* Execute the command. */ @@ -379,47 +878,56 @@ fw_download_img(struct cam_device *cam_dev, const struct fw_vendor *vp, CAM_REQ_CMP) { warnx("Error writing image to device"); if (printerrors) - cam_error_print(cam_dev, ccb, CAM_ESF_ALL, - CAM_EPF_ALL, stderr); + cam_error_print(cam_dev, ccb, + CAM_ESF_ALL, CAM_EPF_ALL, stderr); + retval = 1; goto bailout; } + } else if (printerrors) { + cam_error_print(cam_dev, ccb, CAM_ESF_COMMAND, 0, + stdout); } + /* Prepare next round. */ pkt_count++; pkt_ptr += pkt_size; img_size -= pkt_size; } while(!last_pkt); - progress_complete(&progress, size - img_size); - cam_freeccb(ccb); - return (0); bailout: - progress_complete(&progress, size - img_size); - cam_freeccb(ccb); - return (1); + if (quiet == 0) + progress_complete(&progress, size - img_size); + if (ccb != NULL) + cam_freeccb(ccb); + return (retval); } int fwdownload(struct cam_device *device, int argc, char **argv, - char *combinedopt, int printerrors, int retry_count, int timeout, - const char *type) + char *combinedopt, int printerrors, int retry_count, int timeout) { - const struct fw_vendor *vp; + struct fw_vendor *vp; char *fw_img_path = NULL; - char *buf; + struct ata_params *ident_buf = NULL; + camcontrol_devtype devtype; + char *buf = NULL; int img_size; int c; int sim_mode = 0; int confirmed = 0; + int quiet = 0; + int retval = 0; while ((c = getopt(argc, argv, combinedopt)) != -1) { switch (c) { - case 's': - sim_mode = 1; - confirmed = 1; - break; case 'f': fw_img_path = optarg; break; + case 'q': + quiet = 1; + break; + case 's': + sim_mode = 1; + break; case 'y': confirmed = 1; break; @@ -429,42 +937,100 @@ fwdownload(struct cam_device *device, int argc, char **argv, } if (fw_img_path == NULL) - errx(1, "you must specify a firmware image file using -f option"); + errx(1, "you must specify a firmware image file using -f " + "option"); + + retval = get_device_type(device, retry_count, timeout, printerrors, + &devtype); + if (retval != 0) + errx(1, "Unable to determine device type"); + + if ((devtype == CC_DT_ATA) + || (devtype == CC_DT_ATA_BEHIND_SCSI)) { + union ccb *ccb; + + ccb = cam_getccb(device); + if (ccb == NULL) { + warnx("couldn't allocate CCB"); + retval = 1; + goto bailout; + } + + if (ata_do_identify(device, retry_count, timeout, ccb, + &ident_buf) != 0) { + cam_freeccb(ccb); + retval = 1; + goto bailout; + } + } else if (devtype != CC_DT_SCSI) + errx(1, "Unsupported device type %d", devtype); - vp = fw_get_vendor(device); - if (vp == NULL) - errx(1, "NULL vendor"); - if (vp->type == VENDOR_UNKNOWN) - warnx("Unsupported device - flashing through an HBA?"); + vp = fw_get_vendor(device, ident_buf); + /* + * Bail out if we have an unknown vendor and this isn't an ATA + * disk. For a SCSI disk, we have no chance of working properly + * with the default values in the VENDOR_UNKNOWN case. For an ATA + * disk connected via an ATA transport, we may work for drives that + * support the ATA_DOWNLOAD_MICROCODE command. + */ + if (((vp == NULL) + || (vp->type == VENDOR_UNKNOWN)) + && (devtype == CC_DT_SCSI)) + errx(1, "Unsupported device"); - buf = fw_read_img(fw_img_path, vp, &img_size); - if (buf == NULL) - goto fail; + retval = fw_get_timeout(device, vp, retry_count, timeout); + if (retval != 0) { + warnx("Unable to get a firmware download timeout value"); + goto bailout; + } + + buf = fw_read_img(device, retry_count, timeout, quiet, fw_img_path, + vp, &img_size); + if (buf == NULL) { + retval = 1; + goto bailout; + } if (!confirmed) { fprintf(stdout, "You are about to download firmware image (%s)" " into the following device:\n", fw_img_path); + if (devtype == CC_DT_SCSI) { + if (scsidoinquiry(device, argc, argv, combinedopt, 0, + 5000) != 0) { + warnx("Error sending inquiry"); + retval = 1; + goto bailout; + } + } else { + printf("%s%d: ", device->device_name, + device->dev_unit_num); + ata_print_ident(ident_buf); + camxferrate(device); + free(ident_buf); + } + fprintf(stdout, "Using a timeout of %u ms, which is %s.\n", + vp->timeout_ms, + fw_timeout_desc_table[vp->timeout_type].timeout_desc); fprintf(stdout, "\nIt may damage your drive. "); - if (!get_confirmation()) - goto fail; + if (!get_confirmation()) { + retval = 1; + goto bailout; + } } - if (sim_mode) + if ((sim_mode != 0) && (quiet == 0)) fprintf(stdout, "Running in simulation mode\n"); if (fw_download_img(device, vp, buf, img_size, sim_mode, printerrors, - retry_count, timeout, fw_img_path, type) != 0) { + quiet, retry_count, vp->timeout_ms, fw_img_path, devtype) != 0) { fprintf(stderr, "Firmware download failed\n"); - goto fail; - } - else + retval = 1; + goto bailout; + } else if (quiet == 0) fprintf(stdout, "Firmware download successful\n"); +bailout: free(buf); - return (0); -fail: - if (buf != NULL) - free(buf); - return (1); + return (retval); } diff --git a/sbin/camcontrol/progress.c b/sbin/camcontrol/progress.c index dc6109f..e4ee01d 100644 --- a/sbin/camcontrol/progress.c +++ b/sbin/camcontrol/progress.c @@ -124,6 +124,7 @@ progress_draw(progress_t *prog) * stars should contain at least * sizeof(buf) - BAROVERHEAD entries */ +#define MIN_BAR_LEN 10 static const char stars[] = "*****************************************************************************" "*****************************************************************************" @@ -134,13 +135,31 @@ progress_draw(progress_t *prog) uint64_t bytespersec; uint64_t abbrevsize; int64_t secsleft; - size_t barlength; + ssize_t barlength; size_t starc; char hours[12]; char buf[256]; int len; + int prefix_len; - barlength = MIN(sizeof(buf) - 1, (unsigned)prog->ttywidth) - BAROVERHEAD - strlen(prog->prefix); + prefix_len = strlen(prog->prefix); + barlength = MIN(sizeof(buf) - 1, (unsigned)prog->ttywidth) - + BAROVERHEAD - prefix_len; + if (barlength < MIN_BAR_LEN) { + int tmp_prefix_len; + /* + * In this case the TTY width is too small or the prefix is + * too large for a progress bar. We'll try decreasing the + * prefix length. + */ + barlength = MIN_BAR_LEN; + tmp_prefix_len = MIN(sizeof(buf) - 1,(unsigned)prog->ttywidth) - + BAROVERHEAD - MIN_BAR_LEN; + if (tmp_prefix_len > 0) + prefix_len = tmp_prefix_len; + else + prefix_len = 0; + } starc = (barlength * prog->percent) / 100; abbrevsize = prog->done; for (bytesabbrev = 0; abbrevsize >= 100000 && bytesabbrev < NSUFFIXES; bytesabbrev++) { @@ -171,8 +190,8 @@ progress_draw(progress_t *prog) } secs = secsleft % SECSPERHOUR; len = snprintf(buf, sizeof(buf), - "\r%s %3lld%% |%.*s%*s| %5lld %-3s %3lld.%02d %.2sB/s %s%02d:%02d ETA", - (prog->prefix) ? prog->prefix : "", + "\r%.*s %3lld%% |%.*s%*s| %5lld %-3s %3lld.%02d %.2sB/s %s%02d:%02d ETA", + prefix_len, (prog->prefix) ? prog->prefix : "", (long long)prog->percent, (int)starc, stars, (int)(barlength - starc), "", (long long)abbrevsize, |