summaryrefslogtreecommitdiffstats
path: root/scsicmds.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'scsicmds.cpp')
-rw-r--r--scsicmds.cpp3276
1 files changed, 3276 insertions, 0 deletions
diff --git a/scsicmds.cpp b/scsicmds.cpp
new file mode 100644
index 0000000..2d11906
--- /dev/null
+++ b/scsicmds.cpp
@@ -0,0 +1,3276 @@
+/*
+ * scsicmds.cpp
+ *
+ * Home page of code is: https://www.smartmontools.org
+ *
+ * Copyright (C) 2002-8 Bruce Allen
+ * Copyright (C) 1999-2000 Michael Cornwell <cornwell@acm.org>
+ * Copyright (C) 2003-2023 Douglas Gilbert <dgilbert@interlog.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ *
+ * In the SCSI world "SMART" is a dead or withdrawn standard. In recent
+ * SCSI standards (since SCSI-3) it goes under the awkward name of
+ * "Informational Exceptions" ["IE" or "IEC" (with the "C" for "control")].
+ * The relevant information is spread around several SCSI draft
+ * standards available at http://www.t10.org . Reference is made in the
+ * code to the following acronyms:
+ * - SAM [SCSI Architectural model, versions 2 or 3]
+ * - SPC [SCSI Primary commands, versions 2 or 3]
+ * - SBC [SCSI Block commands, versions 2]
+ *
+ * Some SCSI disk vendors have snippets of "SMART" information in their
+ * product manuals.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "config.h"
+
+#include "scsicmds.h"
+#include "dev_interface.h"
+#include "utility.h"
+#include "sg_unaligned.h"
+
+const char *scsicmds_c_cvsid="$Id: scsicmds.cpp 5470 2023-03-21 19:47:46Z dpgilbert $"
+ SCSICMDS_H_CVSID;
+
+static const char * logSenStr = "Log Sense";
+
+// Print SCSI debug messages?
+unsigned char scsi_debugmode = 0;
+
+supported_vpd_pages * supported_vpd_pages_p = nullptr;
+
+#define RSOC_RESP_SZ 4096
+#define RSOC_ALL_CMDS_CTDP_0 8
+#define RSOC_ALL_CMDS_CTDP_1 20
+#define RSOC_1_CMD_CTDP_0 36
+
+// Check if LOG SENSE cdb supports changing the Subpage Code field
+static scsi_cmd_support
+chk_lsense_spc(scsi_device * device)
+{
+ int r_len = 0;
+ int err;
+ uint8_t rsoc_1cmd_rsp[RSOC_1_CMD_CTDP_0] = {};
+ uint8_t * rp = rsoc_1cmd_rsp;
+
+ err = scsiRSOCcmd(device, false /*rctd */ , 1 /* '1 cmd' format */,
+ LOG_SENSE, 0, rp, RSOC_1_CMD_CTDP_0, r_len);
+ if (err) {
+ if (scsi_debugmode)
+ pout("%s Failed [%s]\n", __func__, scsiErrString(err));
+ return SC_NO_SUPPORT;
+ }
+ if (r_len < 8) {
+ if (scsi_debugmode)
+ pout("%s response to short [%d]\n", __func__, r_len);
+ return SC_NO_SUPPORT;
+ }
+ /* check the "subpage code" field in LOG SENSE cdb usage data */
+ return rp[7] ? SC_SUPPORT : SC_NO_SUPPORT; /* 4 + ls_cdb_byte3 */
+}
+
+bool
+scsi_device::query_cmd_support()
+{
+ bool res = true;
+ int k, err, cd_len, bump;
+ int r_len = 0;
+ uint8_t * rp = (uint8_t *)calloc(sizeof(uint8_t), RSOC_RESP_SZ);
+ const uint8_t * last_rp;
+ uint8_t * cmdp;
+ static const int max_bytes_of_cmds = RSOC_RESP_SZ - 4;
+
+ if (nullptr == rp)
+ return false;
+ rsoc_queried = true;
+ /* request 'all commands' format: 4 bytes header, 20 bytes per command */
+ err = scsiRSOCcmd(this, false /* rctd */, 0 /* 'all' format */, 0, 0,
+ rp, RSOC_RESP_SZ, r_len);
+ if (err) {
+ rsoc_sup = SC_NO_SUPPORT;
+ if (scsi_debugmode)
+ pout("%s Failed [%s]\n", __func__, scsiErrString(err));
+ res = false;
+ goto fini;
+ }
+ if (r_len < 4) {
+ pout("%s response too short\n", __func__);
+ res = false;
+ goto fini;
+ }
+ rsoc_sup = SC_SUPPORT;
+ cd_len = sg_get_unaligned_be32(rp + 0);
+ if (cd_len > max_bytes_of_cmds) {
+ if (scsi_debugmode)
+ pout("%s: truncate %d byte response to %d bytes\n", __func__,
+ cd_len, max_bytes_of_cmds);
+ cd_len = max_bytes_of_cmds;
+ }
+ last_rp = rp + cd_len;
+ logsense_sup = SC_NO_SUPPORT;
+ logsense_spc_sup = SC_NO_SUPPORT;
+ rdefect10_sup = SC_NO_SUPPORT;
+ rdefect12_sup = SC_NO_SUPPORT;
+ rcap16_sup = SC_NO_SUPPORT;
+
+ for (k = 0, cmdp = rp + 4; cmdp < last_rp; ++k, cmdp += bump) {
+ bool sa_valid = !! (0x1 & cmdp[5]);
+ bool ctdp = !! (0x2 & cmdp[5]);
+ uint8_t opcode = cmdp[0];
+ uint16_t sa;
+
+ bump = ctdp ? RSOC_ALL_CMDS_CTDP_1 : RSOC_ALL_CMDS_CTDP_0;
+ sa = sa_valid ? sg_get_unaligned_be16(cmdp + 2) : 0;
+
+ switch (opcode) {
+ case LOG_SENSE:
+ logsense_sup = SC_SUPPORT;
+ logsense_spc_sup = chk_lsense_spc(this);
+ break;
+ case READ_DEFECT_10:
+ rdefect10_sup = SC_SUPPORT;
+ break;
+ case READ_DEFECT_12:
+ rdefect12_sup = SC_SUPPORT;
+ break;
+ case SERVICE_ACTION_IN_16:
+ if (sa_valid && (SAI_READ_CAPACITY_16 == sa))
+ rcap16_sup = SC_SUPPORT;
+ break;
+ default:
+ break;
+ }
+ }
+ if (scsi_debugmode > 3) {
+ pout("%s: decoded %d supported commands\n", __func__, k);
+ pout(" LOG SENSE %ssupported\n",
+ (SC_SUPPORT == logsense_sup) ? "" : "not ");
+ pout(" LOG SENSE subpage code %ssupported\n",
+ (SC_SUPPORT == logsense_spc_sup) ? "" : "not ");
+ pout(" READ DEFECT 10 %ssupported\n",
+ (SC_SUPPORT == rdefect10_sup) ? "" : "not ");
+ pout(" READ DEFECT 12 %ssupported\n",
+ (SC_SUPPORT == rdefect12_sup) ? "" : "not ");
+ pout(" READ CAPACITY 16 %ssupported\n",
+ (SC_SUPPORT == rcap16_sup) ? "" : "not ");
+ }
+
+fini:
+ free(rp);
+ return res;
+}
+
+/* May track more in the future */
+enum scsi_cmd_support
+scsi_device::cmd_support_level(uint8_t opcode, bool sa_valid,
+ uint16_t sa, bool for_lsense_spc) const
+{
+ enum scsi_cmd_support scs = SC_SUPPORT_UNKNOWN;
+
+ switch (opcode) {
+ case LOG_SENSE: /* checking if LOG SENSE _subpages_ supported */
+ scs = for_lsense_spc ? logsense_spc_sup : logsense_sup;
+ break;
+ case READ_DEFECT_10:
+ scs = rdefect10_sup;
+ break;
+ case READ_DEFECT_12:
+ scs = rdefect12_sup;
+ break;
+ case SERVICE_ACTION_IN_16:
+ if (sa_valid && (SAI_READ_CAPACITY_16 == sa))
+ scs = rcap16_sup;
+ break;
+ case MAINTENANCE_IN_12:
+ if (sa_valid && (MI_REP_SUP_OPCODES == sa))
+ scs = rsoc_sup;
+ break;
+ default:
+ break;
+ }
+ return scs;
+}
+
+supported_vpd_pages::supported_vpd_pages(scsi_device * device) : num_valid(0)
+{
+ unsigned char b[0xfc] = {}; /* pre SPC-3 INQUIRY max response size */
+
+ if (device && (0 == scsiInquiryVpd(device, SCSI_VPD_SUPPORTED_VPD_PAGES,
+ b, sizeof(b)))) {
+ num_valid = sg_get_unaligned_be16(b + 2);
+ int n = sizeof(pages);
+ if (num_valid > n)
+ num_valid = n;
+ memcpy(pages, b + 4, num_valid);
+ }
+}
+
+bool
+supported_vpd_pages::is_supported(int vpd_page_num) const
+{
+ /* Supported VPD pages numbers start at offset 4 and should be in
+ * ascending order but don't assume that. */
+ for (int k = 0; k < num_valid; ++k) {
+ if (vpd_page_num == pages[k])
+ return true;
+ }
+ return false;
+}
+
+/* Simple ASCII printable (does not use locale), includes space and excludes
+ * DEL (0x7f). Note all UTF-8 encoding apart from <= 0x7f have top bit set. */
+static inline int
+my_isprint(uint8_t ch)
+{
+ return ((ch >= ' ') && (ch < 0x7f));
+}
+
+static int
+trimTrailingSpaces(char * b)
+{
+ int n = strlen(b);
+
+ while ((n > 0) && (' ' == b[n - 1]))
+ b[--n] = '\0';
+ return n;
+}
+
+/* Read binary starting at 'up' for 'len' bytes and output as ASCII
+ * hexadecimal to the passed function 'out'. See dStrHex() below for more. */
+static void
+dStrHexHelper(const uint8_t * up, int len, int no_ascii,
+ void (*out)(const char * s, void * ctx), void * ctx = nullptr)
+{
+ static const int line_len = 80;
+ static const int cpstart = 60; // offset of start of ASCII rendering
+ uint8_t c;
+ char buff[line_len + 2]; // room for trailing null
+ int a = 0;
+ int bpstart = 5;
+ int cpos = cpstart;
+ int bpos = bpstart;
+ int i, k, blen;
+ char e[line_len + 4];
+ static const int elen = sizeof(e);
+
+ if (len <= 0)
+ return;
+ blen = (int)sizeof(buff);
+ memset(buff, ' ', line_len);
+ buff[line_len] = '\0';
+ if (no_ascii < 0) {
+ bpstart = 0;
+ bpos = bpstart;
+ for (k = 0; k < len; k++) {
+ c = *up++;
+ if (bpos == (bpstart + (8 * 3)))
+ bpos++;
+ snprintf(&buff[bpos], blen - bpos, "%.2x", (int)(uint8_t)c);
+ buff[bpos + 2] = ' ';
+ if ((k > 0) && (0 == ((k + 1) % 16))) {
+ trimTrailingSpaces(buff);
+ if (no_ascii)
+ snprintf(e, elen, "%s\n", buff);
+ else
+ snprintf(e, elen, "%.76s\n", buff);
+ out(e, ctx);
+ bpos = bpstart;
+ memset(buff, ' ', line_len);
+ } else
+ bpos += 3;
+ }
+ if (bpos > bpstart) {
+ buff[bpos + 2] = '\0';
+ trimTrailingSpaces(buff);
+ snprintf(e, elen, "%s\n", buff);
+ out(e, ctx);
+ }
+ return;
+ }
+ /* no_ascii>=0, start each line with address (offset) */
+ k = snprintf(buff + 1, blen - 1, "%.2x", a);
+ buff[k + 1] = ' ';
+
+ for (i = 0; i < len; i++) {
+ c = *up++;
+ bpos += 3;
+ if (bpos == (bpstart + (9 * 3)))
+ bpos++;
+ snprintf(&buff[bpos], blen - bpos, "%.2x", (int)(uint8_t)c);
+ buff[bpos + 2] = ' ';
+ if (no_ascii)
+ buff[cpos++] = ' ';
+ else {
+ if (! my_isprint(c))
+ c = '.';
+ buff[cpos++] = c;
+ }
+ if (cpos > (cpstart + 15)) {
+ if (no_ascii)
+ trimTrailingSpaces(buff);
+ if (no_ascii)
+ snprintf(e, elen, "%s\n", buff);
+ else
+ snprintf(e, elen, "%.76s\n", buff);
+ out(e, ctx);
+ bpos = bpstart;
+ cpos = cpstart;
+ a += 16;
+ memset(buff, ' ', line_len);
+ k = snprintf(buff + 1, blen - 1, "%.2x", a);
+ buff[k + 1] = ' ';
+ }
+ }
+ if (cpos > cpstart) {
+ buff[cpos] = '\0';
+ if (no_ascii)
+ trimTrailingSpaces(buff);
+ snprintf(e, elen, "%s\n", buff);
+ out(e, ctx);
+ }
+}
+
+/* Read binary starting at 'up' for 'len' bytes and output as ASCII
+ * hexadecimal into file pointer (fp). If fp is nullptr, then send to
+ * pout(). See dStrHex() below for more. */
+void
+dStrHexFp(const uint8_t * up, int len, int no_ascii, FILE * fp)
+{
+ /* N.B. Use of lamba requires C++11 or later. */
+ if ((nullptr == up) || (len < 1))
+ return;
+ else if (fp)
+ dStrHexHelper(up, len, no_ascii,
+ [](const char * s, void * ctx)
+ { fputs(s, reinterpret_cast<FILE *>(ctx)); },
+ fp);
+ else
+ dStrHexHelper(up, len, no_ascii,
+ [](const char * s, void *){ pout("%s", s); });
+}
+
+/* Read binary starting at 'up' for 'len' bytes and output as ASCII
+ * hexadecimal into pout(). 16 bytes per line are output with an
+ * additional space between 8th and 9th byte on each line (for readability).
+ * 'no_ascii' selects one of 3 output format types:
+ * > 0 each line has address then up to 16 ASCII-hex bytes
+ * = 0 in addition, the bytes are rendered in ASCII to the right
+ * of each line, non-printable characters shown as '.'
+ * < 0 only the ASCII-hex bytes are listed (i.e. without address) */
+void
+dStrHex(const uint8_t * up, int len, int no_ascii)
+{
+ /* N.B. Use of lamba requires C++11 or later. */
+ dStrHexHelper(up, len, no_ascii,
+ [](const char * s, void *){ pout("%s", s); });
+}
+
+/* This is a heuristic that takes into account the command bytes and length
+ * to decide whether the presented unstructured sequence of bytes could be
+ * a SCSI command. If so it returns true otherwise false. Vendor specific
+ * SCSI commands (i.e. opcodes from 0xc0 to 0xff), if presented, are assumed
+ * to follow SCSI conventions (i.e. length of 6, 10, 12 or 16 bytes). The
+ * only SCSI commands considered above 16 bytes of length are the Variable
+ * Length Commands (opcode 0x7f) and the XCDB wrapped commands (opcode 0x7e).
+ * Both have an inbuilt length field which can be cross checked with clen.
+ * No NVMe commands (64 bytes long plus some extra added by some OSes) have
+ * opcodes 0x7e or 0x7f yet. ATA is register based but SATA has FIS
+ * structures that are sent across the wire. The FIS register structure is
+ * used to move a command from a SATA host to device, but the ATA 'command'
+ * is not the first byte. So it is harder to say what will happen if a
+ * FIS structure is presented as a SCSI command, hopefully there is a low
+ * probability this function will yield true in that case. */
+bool
+is_scsi_cdb(const uint8_t * cdbp, int clen)
+{
+ if (clen < 6)
+ return false;
+ uint8_t opcode = cdbp[0];
+ uint8_t top3bits = opcode >> 5;
+ if (0x3 == top3bits) { /* Opcodes 0x60 to 0x7f */
+ int ilen, sa;
+ if ((clen < 12) || (clen % 4))
+ return false; /* must be modulo 4 and 12 or more bytes */
+ switch (opcode) {
+ case 0x7e: /* Extended cdb (XCDB) */
+ ilen = 4 + sg_get_unaligned_be16(cdbp + 2);
+ return (ilen == clen);
+ case 0x7f: /* Variable Length cdb */
+ ilen = 8 + cdbp[7];
+ sa = sg_get_unaligned_be16(cdbp + 8);
+ /* service action (sa) 0x0 is reserved */
+ return ((ilen == clen) && sa);
+ default:
+ return false;
+ }
+ } else if (clen <= 16) {
+ switch (clen) {
+ case 6:
+ if (top3bits > 0x5) /* vendor */
+ return true;
+ return (0x0 == top3bits); /* 6 byte cdb */
+ case 10:
+ if (top3bits > 0x5) /* vendor */
+ return true;
+ return ((0x1 == top3bits) || (0x2 == top3bits)); /* 10 byte cdb */
+ case 16:
+ if (top3bits > 0x5) /* vendor */
+ return true;
+ return (0x4 == top3bits); /* 16 byte cdb */
+ case 12:
+ if (top3bits > 0x5) /* vendor */
+ return true;
+ return (0x5 == top3bits); /* 12 byte cdb */
+ default:
+ return false;
+ }
+ }
+ /* NVMe probably falls out here, clen > 16 and (opcode < 0x60 or
+ * opcode > 0x7f). */
+ return false;
+}
+
+enum scsi_sa_t {
+ scsi_sa_none = 0,
+ scsi_sa_b1b4n5, /* for cdb byte 1, bit 4, number 5 bits */
+ scsi_sa_b8b7n16,
+};
+
+struct scsi_sa_var_map {
+ uint8_t cdb0;
+ enum scsi_sa_t sa_var;
+};
+
+static struct scsi_sa_var_map sa_var_a[] = {
+ {0x3b, scsi_sa_b1b4n5}, /* Write buffer modes_s */
+ {0x3c, scsi_sa_b1b4n5}, /* Read buffer(10) modes_s */
+ {0x48, scsi_sa_b1b4n5}, /* Sanitize sa_s */
+ {0x5e, scsi_sa_b1b4n5}, /* Persistent reserve in sa_s */
+ {0x5f, scsi_sa_b1b4n5}, /* Persistent reserve out sa_s */
+ {0x7f, scsi_sa_b8b7n16}, /* Variable length commands */
+ {0x83, scsi_sa_b1b4n5}, /* Extended copy out/cmd sa_s */
+ {0x84, scsi_sa_b1b4n5}, /* Extended copy in sa_s */
+ {0x8c, scsi_sa_b1b4n5}, /* Read attribute sa_s */
+ {0x9b, scsi_sa_b1b4n5}, /* Read buffer(16) modes_s */
+ {0x9e, scsi_sa_b1b4n5}, /* Service action in (16) */
+ {0x9f, scsi_sa_b1b4n5}, /* Service action out (16) */
+ {0xa3, scsi_sa_b1b4n5}, /* Maintenance in */
+ {0xa4, scsi_sa_b1b4n5}, /* Maintenance out */
+ {0xa9, scsi_sa_b1b4n5}, /* Service action out (12) */
+ {0xab, scsi_sa_b1b4n5}, /* Service action in (12) */
+};
+
+struct scsi_opcode_name {
+ uint8_t opcode;
+ bool sa_valid; /* Service action (next field) valid */
+ uint16_t sa;
+ const char * name;
+};
+
+/* Array assumed to be sorted by opcode then service action (sa) */
+static struct scsi_opcode_name opcode_name_arr[] = {
+ /* in ascending opcode order */
+ {TEST_UNIT_READY, false, 0, "test unit ready"}, /* 0x00 */
+ {REQUEST_SENSE, false, 0, "request sense"}, /* 0x03 */
+ {INQUIRY, false, 0, "inquiry"}, /* 0x12 */
+ {MODE_SELECT_6, false, 0, "mode select(6)"}, /* 0x15 */
+ {MODE_SENSE_6, false, 0, "mode sense(6)"}, /* 0x1a */
+ {START_STOP_UNIT, false, 0, "start stop unit"}, /* 0x1b */
+ {RECEIVE_DIAGNOSTIC, false, 0, "receive diagnostic"}, /* 0x1c */
+ {SEND_DIAGNOSTIC, false, 0, "send diagnostic"}, /* 0x1d */
+ {READ_CAPACITY_10, false, 0, "read capacity(10)"}, /* 0x25 */
+ {READ_DEFECT_10, false, 0, "read defect list(10)"}, /* 0x37 */
+ {LOG_SELECT, false, 0, "log select"}, /* 0x4c */
+ {LOG_SENSE, false, 0, "log sense"}, /* 0x4d */
+ {MODE_SELECT_10, false, 0, "mode select(10)"}, /* 0x55 */
+ {MODE_SENSE_10, false, 0, "mode sense(10)"}, /* 0x5a */
+ {SAT_ATA_PASSTHROUGH_16, false, 0, "ata pass-through(16)"}, /* 0x85 */
+ {SERVICE_ACTION_IN_16, true, SAI_READ_CAPACITY_16, "read capacity(16)"},
+ /* 0x9e,0x10 */
+ {SERVICE_ACTION_IN_16, true, SAI_GET_PHY_ELEM_STATUS,
+ "get physical element status"}, /* 0x9e,0x17 */
+ {REPORT_LUNS, false, 0, "report luns"}, /* 0xa0 */
+ {SAT_ATA_PASSTHROUGH_12, false, 0, "ata pass-through(12)"}, /* 0xa1 */
+ {MAINTENANCE_IN_12, true, MI_REP_SUP_OPCODES,
+ "report supported operation codes"}, /* 0xa3,0xc */
+ {READ_DEFECT_12, false, 0, "read defect list(12)"}, /* 0xb7 */
+};
+
+static const char * vendor_specific = "<vendor specific>";
+
+/* Need to expand to take service action into account. For commands
+ * of interest the service action is in the 2nd command byte */
+const char *
+scsi_get_opcode_name(const uint8_t * cdbp)
+{
+ uint8_t opcode = cdbp[0];
+ uint8_t cdb0;
+ enum scsi_sa_t sa_var = scsi_sa_none;
+ bool sa_valid = false;
+ uint16_t sa = 0;
+ int k;
+ static const int sa_var_len = sizeof(sa_var_a) /
+ sizeof(sa_var_a[0]);
+ static const int len = sizeof(opcode_name_arr) /
+ sizeof(opcode_name_arr[0]);
+
+ if (opcode >= 0xc0)
+ return vendor_specific;
+ for (k = 0; k < sa_var_len; ++k) {
+ cdb0 = sa_var_a[k].cdb0;
+ if (opcode == cdb0) {
+ sa_var = sa_var_a[k].sa_var;
+ break;
+ }
+ if (opcode < cdb0)
+ break;
+ }
+ switch (sa_var) {
+ case scsi_sa_none:
+ break;
+ case scsi_sa_b1b4n5:
+ sa_valid = true;
+ sa = cdbp[1] & 0x1f;
+ break;
+ case scsi_sa_b8b7n16:
+ sa_valid = true;
+ sa = sg_get_unaligned_be16(cdbp + 8);
+ break;
+ }
+ for (k = 0; k < len; ++k) {
+ struct scsi_opcode_name * onp = &opcode_name_arr[k];
+
+ if (opcode == onp->opcode) {
+ if ((! sa_valid) && (! onp->sa_valid))
+ return onp->name;
+ if (sa_valid && onp->sa_valid) {
+ if (sa == onp->sa)
+ return onp->name;
+ }
+ /* should not see sa_valid and ! onp->sa_valid (or vice versa) */
+ } else if (opcode < onp->opcode)
+ return nullptr;
+ }
+ return nullptr;
+}
+
+void
+scsi_do_sense_disect(const struct scsi_cmnd_io * io_buf,
+ struct scsi_sense_disect * out)
+{
+ memset(out, 0, sizeof(struct scsi_sense_disect));
+ if (SCSI_STATUS_CHECK_CONDITION == io_buf->scsi_status) {
+ int resp_code = (io_buf->sensep[0] & 0x7f);
+ out->resp_code = resp_code;
+ if (resp_code >= 0x72) {
+ out->sense_key = (io_buf->sensep[1] & 0xf);
+ out->asc = io_buf->sensep[2];
+ out->ascq = io_buf->sensep[3];
+ } else if (resp_code >= 0x70) {
+ out->sense_key = (io_buf->sensep[2] & 0xf);
+ if (io_buf->resp_sense_len > 13) {
+ out->asc = io_buf->sensep[12];
+ out->ascq = io_buf->sensep[13];
+ }
+ }
+ }
+}
+
+int
+scsiSimpleSenseFilter(const struct scsi_sense_disect * sinfo)
+{
+ switch (sinfo->sense_key) {
+ case SCSI_SK_NO_SENSE:
+ case SCSI_SK_RECOVERED_ERR:
+ case SCSI_SK_COMPLETED:
+ return SIMPLE_NO_ERROR;
+ case SCSI_SK_NOT_READY:
+ if (SCSI_ASC_NO_MEDIUM == sinfo->asc)
+ return SIMPLE_ERR_NO_MEDIUM;
+ else if (SCSI_ASC_NOT_READY == sinfo->asc) {
+ if (0x1 == sinfo->ascq)
+ return SIMPLE_ERR_BECOMING_READY;
+ else
+ return SIMPLE_ERR_NOT_READY;
+ } else
+ return SIMPLE_ERR_NOT_READY;
+ case SCSI_SK_MEDIUM_ERROR:
+ case SCSI_SK_HARDWARE_ERROR:
+ return SIMPLE_ERR_MEDIUM_HARDWARE;
+ case SCSI_SK_ILLEGAL_REQUEST:
+ if (SCSI_ASC_UNKNOWN_OPCODE == sinfo->asc)
+ return SIMPLE_ERR_BAD_OPCODE;
+ else if (SCSI_ASC_INVALID_FIELD == sinfo->asc)
+ return SIMPLE_ERR_BAD_FIELD;
+ else if (SCSI_ASC_UNKNOWN_PARAM == sinfo->asc)
+ return SIMPLE_ERR_BAD_PARAM;
+ else
+ return SIMPLE_ERR_BAD_PARAM; /* all other illegal request */
+ case SCSI_SK_UNIT_ATTENTION:
+ return SIMPLE_ERR_TRY_AGAIN;
+ case SCSI_SK_ABORTED_COMMAND:
+ return SIMPLE_ERR_ABORTED_COMMAND;
+ case SCSI_SK_DATA_PROTECT:
+ return SIMPLE_ERR_PROTECTION;
+ case SCSI_SK_MISCOMPARE:
+ return SIMPLE_ERR_MISCOMPARE;
+ default:
+ return SIMPLE_ERR_UNKNOWN;
+ }
+}
+
+const char *
+scsiErrString(int scsiErr)
+{
+ if (scsiErr < 0)
+ return strerror(-scsiErr);
+ switch (scsiErr) {
+ case SIMPLE_NO_ERROR:
+ return "no error";
+ case SIMPLE_ERR_NOT_READY:
+ return "device not ready";
+ case SIMPLE_ERR_BAD_OPCODE:
+ return "unsupported scsi opcode";
+ case SIMPLE_ERR_BAD_FIELD:
+ return "unsupported field in scsi command";
+ case SIMPLE_ERR_BAD_PARAM:
+ return "badly formed scsi parameters";
+ case SIMPLE_ERR_BAD_RESP:
+ return "scsi response fails sanity test";
+ case SIMPLE_ERR_NO_MEDIUM:
+ return "no medium present";
+ case SIMPLE_ERR_BECOMING_READY:
+ return "device will be ready soon";
+ case SIMPLE_ERR_TRY_AGAIN:
+ return "unit attention reported, try again";
+ case SIMPLE_ERR_MEDIUM_HARDWARE:
+ return "medium or hardware error (serious)";
+ case SIMPLE_ERR_UNKNOWN:
+ return "unknown error (unexpected sense key)";
+ case SIMPLE_ERR_ABORTED_COMMAND:
+ return "aborted command";
+ case SIMPLE_ERR_PROTECTION:
+ return "data protection error";
+ case SIMPLE_ERR_MISCOMPARE:
+ return "miscompare";
+ default:
+ return "unknown error";
+ }
+}
+
+static const char * sense_key_desc[] = {
+ "No Sense", /* Filemark, ILI and/or EOM; progress
+ indication (during FORMAT); power
+ condition sensing (REQUEST SENSE) */
+ "Recovered Error", /* The last command completed successfully
+ but used error correction */
+ "Not Ready", /* The addressed target is not ready */
+ "Medium Error", /* Data error detected on the medium */
+ "Hardware Error", /* Controller or device failure */
+ "Illegal Request",
+ "Unit Attention", /* Removable medium was changed, or
+ the target has been reset */
+ "Data Protect", /* Access to the data is blocked */
+ "Blank Check", /* Reached unexpected written or unwritten
+ region of the medium */
+ "Vendor specific(9)", /* Vendor specific */
+ "Copy Aborted", /* COPY or COMPARE was aborted */
+ "Aborted Command", /* The target aborted the command */
+ "Equal", /* SEARCH DATA found data equal (obsolete) */
+ "Volume Overflow", /* Medium full with data to be written */
+ "Miscompare", /* Source data and data on the medium
+ do not agree */
+ "Completed" /* may occur for successful cmd (spc4r23) */
+};
+
+/* Yield string associated with sense_key value. Returns 'buff'. */
+char *
+scsi_get_sense_key_str(int sense_key, int buff_len, char * buff)
+{
+ if (1 == buff_len) {
+ buff[0] = '\0';
+ return buff;
+ }
+ if ((sense_key >= 0) && (sense_key < 16))
+ snprintf(buff, buff_len, "%s", sense_key_desc[sense_key]);
+ else
+ snprintf(buff, buff_len, "invalid value: 0x%x", sense_key);
+ return buff;
+}
+
+/* Iterates to next designation descriptor in the device identification
+ * VPD page. The 'initial_desig_desc' should point to start of first
+ * descriptor with 'page_len' being the number of valid bytes in that
+ * and following descriptors. To start, 'off' should point to a negative
+ * value, thereafter it should point to the value yielded by the previous
+ * call. If 0 returned then 'initial_desig_desc + *off' should be a valid
+ * descriptor; returns -1 if normal end condition and -2 for an abnormal
+ * termination. Matches association, designator_type and/or code_set when
+ * any of those values are greater than or equal to zero. */
+int
+scsi_vpd_dev_id_iter(const unsigned char * initial_desig_desc, int page_len,
+ int * off, int m_assoc, int m_desig_type, int m_code_set)
+{
+ const unsigned char * ucp;
+ int k;
+
+ for (k = *off, ucp = initial_desig_desc ; (k + 3) < page_len; ) {
+ k = (k < 0) ? 0 : (k + ucp[k + 3] + 4);
+ if ((k + 4) > page_len)
+ break;
+ int c_set = (ucp[k] & 0xf);
+ if ((m_code_set >= 0) && (m_code_set != c_set))
+ continue;
+ int assoc = ((ucp[k + 1] >> 4) & 0x3);
+ if ((m_assoc >= 0) && (m_assoc != assoc))
+ continue;
+ int desig_type = (ucp[k + 1] & 0xf);
+ if ((m_desig_type >= 0) && (m_desig_type != desig_type))
+ continue;
+ *off = k;
+ return 0;
+ }
+ return (k == page_len) ? -1 : -2;
+}
+
+/* Decode VPD page 0x83 logical unit designator into a string. If both
+ * numeric address and SCSI name string present, prefer the former.
+ * Returns 0 on success, -1 on error with error string in s. */
+int
+scsi_decode_lu_dev_id(const unsigned char * b, int blen, char * s, int slen,
+ int * transport)
+{
+ if (transport)
+ *transport = -1;
+ if (slen < 32) {
+ if (slen > 0)
+ s[0] = '\0';
+ return -1;
+ }
+
+ s[0] = '\0';
+ int si = 0;
+ int have_scsi_ns = 0;
+ int off = -1;
+ int u;
+ while ((u = scsi_vpd_dev_id_iter(b, blen, &off, -1, -1, -1)) == 0) {
+ const unsigned char * ucp = b + off;
+ int i_len = ucp[3];
+ if ((off + i_len + 4) > blen) {
+ snprintf(s+si, slen-si, "error: designator length");
+ return -1;
+ }
+ int assoc = ((ucp[1] >> 4) & 0x3);
+ if (transport && assoc && (ucp[1] & 0x80) && (*transport < 0))
+ *transport = (ucp[0] >> 4) & 0xf;
+ if (0 != assoc)
+ continue;
+ const unsigned char * ip = ucp + 4;
+ int c_set = (ucp[0] & 0xf);
+ int desig_type = (ucp[1] & 0xf);
+
+ int naa;
+ switch (desig_type) {
+ case 0: /* vendor specific */
+ case 1: /* T10 vendor identification */
+ break;
+ case 2: /* EUI-64 based */
+ if ((8 != i_len) && (12 != i_len) && (16 != i_len)) {
+ snprintf(s+si, slen-si, "error: EUI-64 length");
+ return -1;
+ }
+ if (have_scsi_ns)
+ si = 0;
+ si += snprintf(s+si, slen-si, "0x");
+ for (int m = 0; m < i_len; ++m)
+ si += snprintf(s+si, slen-si, "%02x", (unsigned int)ip[m]);
+ break;
+ case 3: /* NAA */
+ if (1 != c_set) {
+ snprintf(s+si, slen-si, "error: NAA bad code_set");
+ return -1;
+ }
+ naa = (ip[0] >> 4) & 0xff;
+ if ((naa < 2) || (naa > 6) || (4 == naa)) {
+ snprintf(s+si, slen-si, "error: unexpected NAA");
+ return -1;
+ }
+ if (have_scsi_ns)
+ si = 0;
+ if (2 == naa) { /* NAA IEEE Extended */
+ if (8 != i_len) {
+ snprintf(s+si, slen-si, "error: NAA 2 length");
+ return -1;
+ }
+ si += snprintf(s+si, slen-si, "0x");
+ for (int m = 0; m < 8; ++m)
+ si += snprintf(s+si, slen-si, "%02x", (unsigned int)ip[m]);
+ } else if ((3 == naa ) || (5 == naa)) {
+ /* NAA=3 Locally assigned; NAA=5 IEEE Registered */
+ if (8 != i_len) {
+ snprintf(s+si, slen-si, "error: NAA 3 or 5 length");
+ return -1;
+ }
+ si += snprintf(s+si, slen-si, "0x");
+ for (int m = 0; m < 8; ++m)
+ si += snprintf(s+si, slen-si, "%02x", (unsigned int)ip[m]);
+ } else if (6 == naa) { /* NAA IEEE Registered extended */
+ if (16 != i_len) {
+ snprintf(s+si, slen-si, "error: NAA 6 length");
+ return -1;
+ }
+ si += snprintf(s+si, slen-si, "0x");
+ for (int m = 0; m < 16; ++m)
+ si += snprintf(s+si, slen-si, "%02x", (unsigned int)ip[m]);
+ }
+ break;
+ case 4: /* Relative target port */
+ case 5: /* (primary) Target port group */
+ case 6: /* Logical unit group */
+ case 7: /* MD5 logical unit identifier */
+ break;
+ case 8: /* SCSI name string */
+ if (3 != c_set) {
+ snprintf(s+si, slen-si, "error: SCSI name string");
+ return -1;
+ }
+ /* does %s print out UTF-8 ok?? */
+ if (si == 0) {
+ si += snprintf(s+si, slen-si, "%s", (const char *)ip);
+ ++have_scsi_ns;
+ }
+ break;
+ default: /* reserved */
+ break;
+ }
+ }
+ if (-2 == u) {
+ snprintf(s+si, slen-si, "error: bad structure");
+ return -1;
+ }
+ return 0;
+}
+
+/* Sends LOG SENSE command. Returns 0 if ok, 1 if device NOT READY, 2 if
+ * command not supported, 3 if field (within command) not supported or
+ * returns negated errno. SPC-3 sections 6.6 and 7.2 (rec 22a).
+ * N.B. Sets PC==1 to fetch "current cumulative" log pages.
+ * If known_resp_len > 0 then a single fetch is done for this response
+ * length. If known_resp_len == 0 then twin fetches are performed, the
+ * first to deduce the response length, then send the same command again
+ * requesting the deduced response length. This protects certain fragile
+ * HBAs. The twin fetch technique should not be used with the TapeAlert
+ * log page since it clears its state flags after each fetch. If
+ * known_resp_len < 0 then does single fetch for BufLen bytes. */
+int
+scsiLogSense(scsi_device * device, int pagenum, int subpagenum, uint8_t *pBuf,
+ int bufLen, int known_resp_len)
+{
+ int pageLen;
+ struct scsi_cmnd_io io_hdr = {};
+ struct scsi_sense_disect sinfo;
+ uint8_t cdb[10] = {};
+ uint8_t sense[32];
+
+ if (known_resp_len > bufLen)
+ return -EIO;
+ if (known_resp_len > 0)
+ pageLen = known_resp_len;
+ else if (known_resp_len < 0)
+ pageLen = bufLen;
+ else { /* 0 == known_resp_len */
+ /* Twin fetch strategy: first fetch to find response length */
+ pageLen = 4;
+ if (pageLen > bufLen)
+ return -EIO;
+ else
+ memset(pBuf, 0, pageLen);
+
+ io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
+ io_hdr.dxfer_len = pageLen;
+ io_hdr.dxferp = pBuf;
+ cdb[0] = LOG_SENSE;
+ cdb[2] = 0x40 | (pagenum & 0x3f); /* Page control (PC)==1 */
+ cdb[3] = subpagenum; /* 0 for no sub-page */
+ sg_put_unaligned_be16(pageLen, cdb + 7);
+ io_hdr.cmnd = cdb;
+ io_hdr.cmnd_len = sizeof(cdb);
+ io_hdr.sensep = sense;
+ io_hdr.max_sense_len = sizeof(sense);
+ io_hdr.timeout = SCSI_TIMEOUT_DEFAULT;
+
+ if (! scsi_pass_through_yield_sense(device, &io_hdr, sinfo))
+ return -device->get_errno();
+ int res;
+ if ((res = scsiSimpleSenseFilter(&sinfo)))
+ return res;
+ /* sanity check on response */
+ if ((SUPPORTED_LPAGES != pagenum) && ((pBuf[0] & 0x3f) != pagenum))
+ return SIMPLE_ERR_BAD_RESP;
+ uint16_t u = sg_get_unaligned_be16(pBuf + 2);
+ if (0 == u)
+ return SIMPLE_ERR_BAD_RESP;
+ pageLen = u + 4;
+ /* some SCSI HBA don't like "odd" length transfers */
+ if (pageLen % 2)
+ pageLen += 1;
+ if (pageLen > bufLen)
+ pageLen = bufLen;
+ }
+ memset(pBuf, 0, 4);
+ memset(&io_hdr, 0, sizeof(io_hdr));
+ memset(cdb, 0, sizeof(cdb));
+ io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
+ io_hdr.dxfer_len = pageLen;
+ io_hdr.dxferp = pBuf;
+ cdb[0] = LOG_SENSE;
+ cdb[2] = 0x40 | (pagenum & 0x3f); /* Page control (PC)==1 */
+ cdb[3] = subpagenum;
+ sg_put_unaligned_be16(pageLen, cdb + 7);
+ io_hdr.cmnd = cdb;
+ io_hdr.cmnd_len = sizeof(cdb);
+ io_hdr.sensep = sense;
+ io_hdr.max_sense_len = sizeof(sense);
+ io_hdr.timeout = SCSI_TIMEOUT_DEFAULT;
+
+ if (! scsi_pass_through_yield_sense(device, &io_hdr, sinfo))
+ return -device->get_errno();
+ int status = scsiSimpleSenseFilter(&sinfo);
+ if (0 != status)
+ return status;
+ /* sanity check on response */
+ if ((SUPPORTED_LPAGES != pagenum) && ((pBuf[0] & 0x3f) != pagenum))
+ return SIMPLE_ERR_BAD_RESP;
+ if (0 == sg_get_unaligned_be16(pBuf + 2))
+ return SIMPLE_ERR_BAD_RESP;
+ return 0;
+}
+
+/* Sends a LOG SELECT command. Can be used to set log page values
+ * or reset one log page (or all of them) to its defaults (typically zero).
+ * Returns 0 if ok, 1 if NOT READY, 2 if command not supported, * 3 if
+ * field in command not supported, * 4 if bad parameter to command or
+ * returns negated errno. SPC-4 sections 6.5 and 7.2 (rev 20) */
+int
+scsiLogSelect(scsi_device * device, int pcr, int sp, int pc, int pagenum,
+ int subpagenum, uint8_t *pBuf, int bufLen)
+{
+ struct scsi_cmnd_io io_hdr = {};
+ struct scsi_sense_disect sinfo;
+ uint8_t cdb[10] = {};
+ uint8_t sense[32];
+
+ io_hdr.dxfer_dir = DXFER_TO_DEVICE;
+ io_hdr.dxfer_len = bufLen;
+ io_hdr.dxferp = pBuf;
+ cdb[0] = LOG_SELECT;
+ cdb[1] = (pcr ? 2 : 0) | (sp ? 1 : 0);
+ cdb[2] = ((pc << 6) & 0xc0) | (pagenum & 0x3f);
+ cdb[3] = (subpagenum & 0xff);
+ sg_put_unaligned_be16(bufLen, cdb + 7);
+ io_hdr.cmnd = cdb;
+ io_hdr.cmnd_len = sizeof(cdb);
+ io_hdr.sensep = sense;
+ io_hdr.max_sense_len = sizeof(sense);
+ io_hdr.timeout = SCSI_TIMEOUT_DEFAULT;
+
+ if (! scsi_pass_through_yield_sense(device, &io_hdr, sinfo))
+ return -device->get_errno();
+ return scsiSimpleSenseFilter(&sinfo);
+}
+
+/* Send MODE SENSE (6 byte) command. Returns 0 if ok, 1 if NOT READY,
+ * 2 if command not supported (then MODE SENSE(10) should be supported),
+ * 3 if field in command not supported or returns negated errno.
+ * SPC-3 sections 6.9 and 7.4 (rev 22a) [mode subpage==0] */
+int
+scsiModeSense(scsi_device * device, int pagenum, int subpagenum, int pc,
+ uint8_t *pBuf, int bufLen)
+{
+ struct scsi_cmnd_io io_hdr = {};
+ struct scsi_sense_disect sinfo;
+ uint8_t cdb[6] = {};
+ uint8_t sense[32];
+
+ if ((bufLen < 0) || (bufLen > 255))
+ return -EINVAL;
+ io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
+ io_hdr.dxfer_len = bufLen;
+ io_hdr.dxferp = pBuf;
+ cdb[0] = MODE_SENSE_6;
+ cdb[2] = (pc << 6) | (pagenum & 0x3f);
+ cdb[3] = subpagenum;
+ cdb[4] = bufLen;
+ io_hdr.cmnd = cdb;
+ io_hdr.cmnd_len = sizeof(cdb);
+ io_hdr.sensep = sense;
+ io_hdr.max_sense_len = sizeof(sense);
+ io_hdr.timeout = SCSI_TIMEOUT_DEFAULT;
+
+ if (! scsi_pass_through_yield_sense(device, &io_hdr, sinfo))
+ return -device->get_errno();
+ int status = scsiSimpleSenseFilter(&sinfo);
+ if ((0 == status) && (ALL_MODE_PAGES != pagenum)) {
+ int offset;
+
+ offset = scsiModePageOffset(pBuf, bufLen, 0);
+ if (offset < 0)
+ return SIMPLE_ERR_BAD_RESP;
+ else if (pagenum != (pBuf[offset] & 0x3f))
+ return SIMPLE_ERR_BAD_RESP;
+ }
+ return status;
+}
+
+/* Sends a 6 byte MODE SELECT command. Assumes given pBuf is the response
+ * from a corresponding 6 byte MODE SENSE command. Such a response should
+ * have a 4 byte header followed by 0 or more 8 byte block descriptors
+ * (normally 1) and then 1 mode page. Returns 0 if ok, 1 if NOT READY,
+ * 2 if command not supported (then MODE SELECT(10) may be supported),
+ * 3 if field in command not supported, 4 if bad parameter to command
+ * or returns negated errno. SPC-3 sections 6.7 and 7.4 (rev 22a) */
+int
+scsiModeSelect(scsi_device * device, int sp, uint8_t *pBuf, int bufLen)
+{
+ struct scsi_cmnd_io io_hdr = {};
+ struct scsi_sense_disect sinfo;
+ uint8_t cdb[6] = {};
+ uint8_t sense[32];
+ int pg_offset, pg_len, hdr_plus_1_pg;
+
+ pg_offset = 4 + pBuf[3];
+ if (pg_offset + 2 >= bufLen)
+ return -EINVAL;
+ pg_len = pBuf[pg_offset + 1] + 2;
+ hdr_plus_1_pg = pg_offset + pg_len;
+ if (hdr_plus_1_pg > bufLen)
+ return -EINVAL;
+ pBuf[0] = 0; /* Length of returned mode sense data reserved for SELECT */
+ pBuf[pg_offset] &= 0x7f; /* Mask out PS bit from byte 0 of page data */
+ io_hdr.dxfer_dir = DXFER_TO_DEVICE;
+ io_hdr.dxfer_len = hdr_plus_1_pg;
+ io_hdr.dxferp = pBuf;
+ cdb[0] = MODE_SELECT_6;
+ cdb[1] = 0x10 | (sp & 1); /* set PF (page format) bit always */
+ cdb[4] = hdr_plus_1_pg; /* make sure only one page sent */
+ io_hdr.cmnd = cdb;
+ io_hdr.cmnd_len = sizeof(cdb);
+ io_hdr.sensep = sense;
+ io_hdr.max_sense_len = sizeof(sense);
+ io_hdr.timeout = SCSI_TIMEOUT_DEFAULT;
+
+ if (! scsi_pass_through_yield_sense(device, &io_hdr, sinfo))
+ return -device->get_errno();
+ return scsiSimpleSenseFilter(&sinfo);
+}
+
+/* MODE SENSE (10 byte). Returns 0 if ok, 1 if NOT READY, 2 if command
+ * not supported (then MODE SENSE(6) might be supported), 3 if field in
+ * command not supported or returns negated errno.
+ * SPC-3 sections 6.10 and 7.4 (rev 22a) [mode subpage==0] */
+int
+scsiModeSense10(scsi_device * device, int pagenum, int subpagenum, int pc,
+ uint8_t *pBuf, int bufLen)
+{
+ struct scsi_cmnd_io io_hdr = {};
+ struct scsi_sense_disect sinfo;
+ uint8_t cdb[10] = {};
+ uint8_t sense[32];
+
+ io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
+ io_hdr.dxfer_len = bufLen;
+ io_hdr.dxferp = pBuf;
+ cdb[0] = MODE_SENSE_10;
+ cdb[2] = (pc << 6) | (pagenum & 0x3f);
+ cdb[3] = subpagenum;
+ sg_put_unaligned_be16(bufLen, cdb + 7);
+ io_hdr.cmnd = cdb;
+ io_hdr.cmnd_len = sizeof(cdb);
+ io_hdr.sensep = sense;
+ io_hdr.max_sense_len = sizeof(sense);
+ io_hdr.timeout = SCSI_TIMEOUT_DEFAULT;
+
+ if (! scsi_pass_through_yield_sense(device, &io_hdr, sinfo))
+ return -device->get_errno();
+ int status = scsiSimpleSenseFilter(&sinfo);
+ if ((0 == status) && (ALL_MODE_PAGES != pagenum)) {
+ int offset;
+
+ offset = scsiModePageOffset(pBuf, bufLen, 1);
+ if (offset < 0)
+ return SIMPLE_ERR_BAD_RESP;
+ else if (pagenum != (pBuf[offset] & 0x3f))
+ return SIMPLE_ERR_BAD_RESP;
+ }
+ return status;
+}
+
+/* Sends a 10 byte MODE SELECT command. Assumes given pBuf is the response
+ * from a corresponding 10 byte MODE SENSE command. Such a response should
+ * have a 8 byte header followed by 0 or more 8 byte block descriptors
+ * (normally 1) and then 1 mode page. Returns 0 if ok, 1 NOT REAFY, 2 if
+ * command not supported (then MODE SELECT(6) may be supported), 3 if field
+ * in command not supported, 4 if bad parameter to command or returns
+ * negated errno. SPC-3 sections 6.8 and 7.4 (rev 22a) */
+int
+scsiModeSelect10(scsi_device * device, int sp, uint8_t *pBuf, int bufLen)
+{
+ struct scsi_cmnd_io io_hdr = {};
+ struct scsi_sense_disect sinfo;
+ uint8_t cdb[10] = {};
+ uint8_t sense[32];
+ int pg_offset, pg_len, hdr_plus_1_pg;
+
+ pg_offset = 8 + sg_get_unaligned_be16(pBuf + 6);
+ if (pg_offset + 2 >= bufLen)
+ return -EINVAL;
+ pg_len = pBuf[pg_offset + 1] + 2;
+ hdr_plus_1_pg = pg_offset + pg_len;
+ if (hdr_plus_1_pg > bufLen)
+ return -EINVAL;
+ pBuf[0] = 0;
+ pBuf[1] = 0; /* Length of returned mode sense data reserved for SELECT */
+ pBuf[pg_offset] &= 0x7f; /* Mask out PS bit from byte 0 of page data */
+ io_hdr.dxfer_dir = DXFER_TO_DEVICE;
+ io_hdr.dxfer_len = hdr_plus_1_pg;
+ io_hdr.dxferp = pBuf;
+ cdb[0] = MODE_SELECT_10;
+ cdb[1] = 0x10 | (sp & 1); /* set PF (page format) bit always */
+ /* make sure only one page sent */
+ sg_put_unaligned_be16(hdr_plus_1_pg, cdb + 7);
+ io_hdr.cmnd = cdb;
+ io_hdr.cmnd_len = sizeof(cdb);
+ io_hdr.sensep = sense;
+ io_hdr.max_sense_len = sizeof(sense);
+ io_hdr.timeout = SCSI_TIMEOUT_DEFAULT;
+
+ if (! scsi_pass_through_yield_sense(device, &io_hdr, sinfo))
+ return -device->get_errno();
+ return scsiSimpleSenseFilter(&sinfo);
+}
+
+/* Standard INQUIRY returns 0 for ok, anything else is a major problem.
+ * bufLen should be 36 for unsafe devices (like USB mass storage stuff)
+ * otherwise they can lock up! SPC-3 sections 6.4 and 7.6 (rev 22a) */
+int
+scsiStdInquiry(scsi_device * device, uint8_t *pBuf, int bufLen)
+{
+ struct scsi_sense_disect sinfo;
+ struct scsi_cmnd_io io_hdr = {};
+ int res;
+ uint8_t cdb[6] = {};
+ uint8_t sense[32];
+
+ if ((bufLen < 0) || (bufLen > 1023))
+ return -EINVAL;
+ if (bufLen >= 36) /* normal case */
+ memset(pBuf, 0, 36);
+ io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
+ io_hdr.dxfer_len = bufLen;
+ io_hdr.dxferp = pBuf;
+ cdb[0] = INQUIRY;
+ sg_put_unaligned_be16(bufLen, cdb + 3);
+ io_hdr.cmnd = cdb;
+ io_hdr.cmnd_len = sizeof(cdb);
+ io_hdr.sensep = sense;
+ io_hdr.max_sense_len = sizeof(sense);
+ io_hdr.timeout = SCSI_TIMEOUT_DEFAULT;
+
+ if (! scsi_pass_through_yield_sense(device, &io_hdr, sinfo))
+ return -device->get_errno();
+ res = scsiSimpleSenseFilter(&sinfo);
+ if ((SIMPLE_NO_ERROR == res) && (! device->is_spc4_or_higher())) {
+ if (((bufLen - io_hdr.resid) >= 36) &&
+ (pBuf[2] >= 6) && /* VERSION field >= SPC-4 */
+ ((pBuf[3] & 0xf) == 2)) { /* RESPONSE DATA field == 2 */
+ uint8_t pdt = pBuf[0] & 0x1f;
+
+ if ((SCSI_PT_DIRECT_ACCESS == pdt) ||
+ (SCSI_PT_HOST_MANAGED == pdt) ||
+ (SCSI_PT_SEQUENTIAL_ACCESS == pdt) ||
+ (SCSI_PT_MEDIUM_CHANGER == pdt))
+ device->set_spc4_or_higher();
+ }
+ }
+ return res;
+}
+
+/* INQUIRY to fetch Vital Page Data. Returns 0 if ok, 1 if NOT READY
+ * (unlikely), 2 if command not supported, 3 if field in command not
+ * supported, 5 if response indicates that EVPD bit ignored or returns
+ * negated errno. SPC-3 section 6.4 and 7.6 (rev 22a) */
+int
+scsiInquiryVpd(scsi_device * device, int vpd_page, uint8_t *pBuf, int bufLen)
+{
+ struct scsi_cmnd_io io_hdr = {};
+ struct scsi_sense_disect sinfo;
+ uint8_t cdb[6] = {};
+ uint8_t sense[32];
+ int res;
+
+ /* Assume SCSI_VPD_SUPPORTED_VPD_PAGES is first VPD page fetched */
+ if ((SCSI_VPD_SUPPORTED_VPD_PAGES != vpd_page) &&
+ supported_vpd_pages_p &&
+ (! supported_vpd_pages_p->is_supported(vpd_page)))
+ return 3;
+
+ if ((bufLen < 0) || (bufLen > 1023))
+ return -EINVAL;
+try_again:
+ if (bufLen > 1)
+ pBuf[1] = 0x0;
+ io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
+ io_hdr.dxfer_len = bufLen;
+ io_hdr.dxferp = pBuf;
+ cdb[0] = INQUIRY;
+ cdb[1] = 0x1; /* set EVPD bit (enable Vital Product Data) */
+ cdb[2] = vpd_page;
+ sg_put_unaligned_be16(bufLen, cdb + 3);
+ io_hdr.cmnd = cdb;
+ io_hdr.cmnd_len = sizeof(cdb);
+ io_hdr.sensep = sense;
+ io_hdr.max_sense_len = sizeof(sense);
+ io_hdr.timeout = SCSI_TIMEOUT_DEFAULT;
+
+ if (! scsi_pass_through_yield_sense(device, &io_hdr, sinfo))
+ return -device->get_errno();
+ if ((SCSI_STATUS_CHECK_CONDITION == io_hdr.scsi_status) &&
+ (SCSI_SK_ILLEGAL_REQUEST == sinfo.sense_key) &&
+ (SCSI_ASC_INVALID_FIELD == sinfo.asc) &&
+ (cdb[3] > 0)) {
+ bufLen &= 0xff; /* make sure cdb[3] is 0 next time around */
+ goto try_again;
+ }
+
+ if ((res = scsiSimpleSenseFilter(&sinfo)))
+ return res;
+ /* Guard against devices that ignore EVPD bit and do standard INQUIRY */
+ if (bufLen > 1) {
+ if (vpd_page == pBuf[1]) {
+ if ((0x80 == vpd_page) && (bufLen > 2) && (0x0 != pBuf[2]))
+ return SIMPLE_ERR_BAD_RESP;
+ } else
+ return SIMPLE_ERR_BAD_RESP;
+ }
+ return 0;
+}
+
+/* REQUEST SENSE command. Returns 0 if ok, anything else major problem.
+ * SPC-3 section 6.27 (rev 22a) */
+int
+scsiRequestSense(scsi_device * device, struct scsi_sense_disect * sense_info)
+{
+ struct scsi_cmnd_io io_hdr = {};
+ uint8_t cdb[6] = {};
+ uint8_t sense[32];
+ uint8_t buff[18] = {};
+ bool ok;
+ static const int sz_buff = sizeof(buff);
+
+ io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
+ io_hdr.dxfer_len = sz_buff;
+ io_hdr.dxferp = buff;
+ cdb[0] = REQUEST_SENSE;
+ cdb[4] = sz_buff;
+ io_hdr.cmnd = cdb;
+ io_hdr.cmnd_len = sizeof(cdb);
+ io_hdr.sensep = sense;
+ io_hdr.max_sense_len = sizeof(sense);
+ io_hdr.timeout = SCSI_TIMEOUT_DEFAULT;
+
+ if (sense_info)
+ ok = scsi_pass_through_yield_sense(device, &io_hdr, *sense_info);
+ else {
+ scsi_sense_disect dummy_sense;
+ ok = scsi_pass_through_yield_sense(device, &io_hdr, dummy_sense);
+ }
+ if (! ok)
+ return -device->get_errno();
+ if (sense_info) {
+ uint8_t resp_code = buff[0] & 0x7f;
+ sense_info->resp_code = resp_code;
+ sense_info->sense_key = buff[2] & 0xf;
+ sense_info->asc = 0;
+ sense_info->ascq = 0;
+ if ((0x70 == resp_code) || (0x71 == resp_code)) {
+ int len = buff[7] + 8;
+ if (len > 13) {
+ sense_info->asc = buff[12];
+ sense_info->ascq = buff[13];
+ }
+ }
+ // fill progress indicator, if available
+ sense_info->progress = -1;
+ switch (resp_code) {
+ const unsigned char * ucp;
+ int sk, sk_pr;
+ case 0x70:
+ case 0x71:
+ sk = (buff[2] & 0xf);
+ if (! ((SCSI_SK_NO_SENSE == sk) || (SCSI_SK_NOT_READY == sk))) {
+ break;
+ }
+ if (buff[15] & 0x80) { /* SKSV bit set */
+ sense_info->progress = sg_get_unaligned_be16(buff + 16);
+ break;
+ } else {
+ break;
+ }
+ case 0x72:
+ case 0x73:
+ /* sense key specific progress (0x2) or progress descriptor (0xa) */
+ sk = (buff[1] & 0xf);
+ sk_pr = (SCSI_SK_NO_SENSE == sk) || (SCSI_SK_NOT_READY == sk);
+ if (sk_pr && ((ucp = sg_scsi_sense_desc_find(buff, sz_buff, 2))) &&
+ (0x6 == ucp[1]) && (0x80 & ucp[4])) {
+ sense_info->progress = sg_get_unaligned_be16(ucp + 5);
+ break;
+ } else if (((ucp = sg_scsi_sense_desc_find(buff, sz_buff, 0xa))) &&
+ ((0x6 == ucp[1]))) {
+ sense_info->progress = sg_get_unaligned_be16(ucp + 6);
+ break;
+ } else
+ break;
+ default:
+ return 0;
+ }
+ }
+ return 0;
+}
+
+/* Send Start Stop Unit command with power_condition setting and
+ * Power condition command. Returns 0 if ok, anything else major problem.
+ * If power_cond is 0, treat as SSU(START) as that is better than
+ * SSU(STOP) which would be the case if byte 4 of the cdb was zero.
+ * Ref: SBC-4 revision 22, section 4.20 SSU and power conditions.
+ *
+ * SCSI_POW_COND_ACTIVE 0x1
+ * SCSI_POW_COND_IDLE 0x2
+ * SCSI_POW_COND_STANDBY 0x3
+ *
+ */
+
+int
+scsiSetPowerCondition(scsi_device * device, int power_cond, int pcond_modifier)
+{
+ struct scsi_cmnd_io io_hdr = {};
+ struct scsi_sense_disect sinfo;
+ uint8_t cdb[6] = {};
+ uint8_t sense[32];
+
+ io_hdr.dxfer_dir = DXFER_NONE;
+ cdb[0] = START_STOP_UNIT;
+ /* IMMED bit (cdb[1] = 0x1) not set, therefore will wait */
+ if (power_cond > 0) {
+ cdb[3] = pcond_modifier & 0xf;
+ cdb[4] = power_cond << 4;
+ } else
+ cdb[4] = 0x1; /* START */
+ io_hdr.cmnd = cdb;
+ io_hdr.cmnd_len = sizeof(cdb);
+ io_hdr.sensep = sense;
+ io_hdr.max_sense_len = sizeof(sense);
+ io_hdr.timeout = SCSI_TIMEOUT_DEFAULT;
+
+ if (! scsi_pass_through_yield_sense(device, &io_hdr, sinfo))
+ return -device->get_errno();
+
+ return scsiSimpleSenseFilter(&sinfo);
+}
+
+/* SEND DIAGNOSTIC command. Returns 0 if ok, 1 if NOT READY, 2 if command
+ * not supported, 3 if field in command not supported or returns negated
+ * errno. SPC-3 section 6.28 (rev 22a) */
+int
+scsiSendDiagnostic(scsi_device * device, int functioncode, uint8_t *pBuf,
+ int bufLen)
+{
+ struct scsi_cmnd_io io_hdr = {};
+ struct scsi_sense_disect sinfo;
+ uint8_t cdb[6] = {};
+ uint8_t sense[32];
+
+ io_hdr.dxfer_dir = bufLen ? DXFER_TO_DEVICE: DXFER_NONE;
+ io_hdr.dxfer_len = bufLen;
+ io_hdr.dxferp = pBuf;
+ cdb[0] = SEND_DIAGNOSTIC;
+ if (SCSI_DIAG_DEF_SELF_TEST == functioncode)
+ cdb[1] = 0x4; /* SelfTest bit */
+ else if (SCSI_DIAG_NO_SELF_TEST != functioncode)
+ cdb[1] = (functioncode & 0x7) << 5; /* SelfTest _code_ */
+ else /* SCSI_DIAG_NO_SELF_TEST == functioncode */
+ cdb[1] = 0x10; /* PF bit */
+ sg_put_unaligned_be16(bufLen, cdb + 3);
+ io_hdr.cmnd = cdb;
+ io_hdr.cmnd_len = sizeof(cdb);
+ io_hdr.sensep = sense;
+ io_hdr.max_sense_len = sizeof(sense);
+ /* worst case is an extended foreground self test on a big disk */
+ io_hdr.timeout = SCSI_TIMEOUT_SELF_TEST;
+
+ if (! scsi_pass_through_yield_sense(device, &io_hdr, sinfo))
+ return -device->get_errno();
+ return scsiSimpleSenseFilter(&sinfo);
+}
+
+/* TEST UNIT READY command. SPC-3 section 6.33 (rev 22a) */
+static int
+_testunitready(scsi_device * device, struct scsi_sense_disect * sinfop)
+{
+ struct scsi_cmnd_io io_hdr = {};
+ bool ok;
+ uint8_t cdb[6] = {};
+ uint8_t sense[32];
+
+ io_hdr.dxfer_dir = DXFER_NONE;
+ io_hdr.dxfer_len = 0;
+ io_hdr.dxferp = nullptr;
+ cdb[0] = TEST_UNIT_READY;
+ io_hdr.cmnd = cdb;
+ io_hdr.cmnd_len = sizeof(cdb);
+ io_hdr.sensep = sense;
+ io_hdr.max_sense_len = sizeof(sense);
+ io_hdr.timeout = SCSI_TIMEOUT_DEFAULT;
+
+ if (sinfop)
+ ok = scsi_pass_through_yield_sense(device, &io_hdr, *sinfop);
+ else {
+ struct scsi_sense_disect dummy_si;
+ ok = scsi_pass_through_yield_sense(device, &io_hdr, dummy_si);
+ }
+ if (! ok)
+ return -device->get_errno();
+ return 0;
+}
+
+/* Returns 0 for device responds and media ready, 1 for device responds and
+ media not ready, or returns a negated errno value */
+int
+scsiTestUnitReady(scsi_device * device)
+{
+ struct scsi_sense_disect sinfo;
+ int status;
+
+ status = _testunitready(device, &sinfo);
+ if (0 != status)
+ return status;
+ return scsiSimpleSenseFilter(&sinfo);
+}
+
+/* READ DEFECT (10) command. Returns 0 if ok, 1 if NOT READY, 2 if
+ * command not supported, 3 if field in command not supported, 101 if
+ * defect list not found (e.g. SSD may not have defect list) or returns
+ * negated errno. SBC-2 section 5.12 (rev 16) */
+int
+scsiReadDefect10(scsi_device * device, int req_plist, int req_glist,
+ int dl_format, uint8_t *pBuf, int bufLen)
+{
+ struct scsi_cmnd_io io_hdr = {};
+ struct scsi_sense_disect sinfo;
+ uint8_t cdb[10] = {};
+ uint8_t sense[32];
+
+ io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
+ io_hdr.dxfer_len = bufLen;
+ io_hdr.dxferp = pBuf;
+ cdb[0] = READ_DEFECT_10;
+ cdb[2] = (unsigned char)(((req_plist << 4) & 0x10) |
+ ((req_glist << 3) & 0x8) | (dl_format & 0x7));
+ sg_put_unaligned_be16(bufLen, cdb + 7);
+ io_hdr.cmnd = cdb;
+ io_hdr.cmnd_len = sizeof(cdb);
+ io_hdr.sensep = sense;
+ io_hdr.max_sense_len = sizeof(sense);
+ io_hdr.timeout = SCSI_TIMEOUT_DEFAULT;
+
+ if (! scsi_pass_through_yield_sense(device, &io_hdr, sinfo))
+ return -device->get_errno();
+ /* Look for "(Primary|Grown) defect list not found" */
+ if ((sinfo.resp_code >= 0x70) && (0x1c == sinfo.asc))
+ return 101;
+ return scsiSimpleSenseFilter(&sinfo);
+}
+
+/* READ DEFECT (12) command. Returns 0 if ok, 1 if NOT READY, 2 if
+ * command not supported, 3 if field in command not supported, 101 if
+ * defect list not found (e.g. SSD may not have defect list) or returns
+ * negated errno. SBC-3 section 5.18 (rev 35; vale Mark Evans) */
+int
+scsiReadDefect12(scsi_device * device, int req_plist, int req_glist,
+ int dl_format, int addrDescIndex, uint8_t *pBuf, int bufLen)
+{
+ struct scsi_cmnd_io io_hdr = {};
+ struct scsi_sense_disect sinfo;
+ uint8_t cdb[12] = {};
+ uint8_t sense[32];
+
+ io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
+ io_hdr.dxfer_len = bufLen;
+ io_hdr.dxferp = pBuf;
+ cdb[0] = READ_DEFECT_12;
+ cdb[1] = (unsigned char)(((req_plist << 4) & 0x10) |
+ ((req_glist << 3) & 0x8) | (dl_format & 0x7));
+ sg_put_unaligned_be32(addrDescIndex, cdb + 2);
+ sg_put_unaligned_be32(bufLen, cdb + 6);
+ io_hdr.cmnd = cdb;
+ io_hdr.cmnd_len = sizeof(cdb);
+ io_hdr.sensep = sense;
+ io_hdr.max_sense_len = sizeof(sense);
+ io_hdr.timeout = SCSI_TIMEOUT_DEFAULT;
+
+ if (! scsi_pass_through_yield_sense(device, &io_hdr, sinfo))
+ return -device->get_errno();
+ /* Look for "(Primary|Grown) defect list not found" */
+ if ((sinfo.resp_code >= 0x70) && (0x1c == sinfo.asc))
+ return 101;
+ return scsiSimpleSenseFilter(&sinfo);
+}
+
+/* Call scsi_pass_through, and retry only if a UNIT_ATTENTION (UA) is raised.
+ * When false returned, the caller should invoke device->get_error().
+ * When true returned, the caller should check sinfo.
+ * All SCSI commands can receive pending Unit Attentions, apart from:
+ * INQUIRY, REPORT LUNS, REQUEST SENSE and NOTIFY DATA TRANSFER DEVICE
+ * (ADC-3 spec). The first three are the important ones. */
+bool
+scsi_pass_through_yield_sense(scsi_device * device, scsi_cmnd_io * iop,
+ /* OUT param */ scsi_sense_disect & sinfo)
+{
+ int k;
+ uint32_t opcode = (iop->cmnd_len > 0) ? iop->cmnd[0] : 0xffff;
+
+ if (scsi_debugmode > 2) {
+ bool dout = false;
+ const char * ddir = "none";
+ const char * np;
+
+ if (iop->dxfer_len > 0) {
+ dout = (DXFER_TO_DEVICE == iop->dxfer_dir);
+ ddir = dout ? "out" : "in";
+ }
+ np = scsi_get_opcode_name(iop->cmnd);
+ pout(" [%s: ", np ? np : "<unknown opcode>");
+ pout("SCSI opcode=0x%x, CDB length=%u, data length=0x%u, data "
+ "dir=%s]\n", opcode, (unsigned int)iop->cmnd_len,
+ (unsigned int)iop->dxfer_len, ddir);
+ if (dout && (scsi_debugmode > 3)) /* output hex without address */
+ dStrHexFp(iop->dxferp, iop->dxfer_len, -1, nullptr);
+ }
+
+ if (! device->scsi_pass_through(iop))
+ return false; // this will be missing device, timeout, etc
+
+ if (scsi_debugmode > 3) {
+ unsigned int req_len = iop->dxfer_len;
+ unsigned int act_len;
+
+ if ((req_len > 0) && (DXFER_FROM_DEVICE == iop->dxfer_dir) &&
+ (iop->resid >= 0) && (req_len >= (unsigned int)iop->resid)) {
+ act_len = req_len - (unsigned int)iop->resid;
+ pout(" [data-in buffer: req_len=%u, resid=%d, gives %u "
+ "bytes]\n", req_len, iop->resid, act_len);
+ dStrHexFp(iop->dxferp, act_len, -1, nullptr);
+ }
+ }
+ scsi_do_sense_disect(iop, &sinfo);
+
+ switch (opcode) {
+ case INQUIRY:
+ case REPORT_LUNS:
+ case REQUEST_SENSE:
+ return true; /* in these cases, it shouldn't be a UA */
+ default:
+ break; /* continue on for all other SCSI commands to check for UA */
+ }
+
+ /* There can be multiple UAs pending, allow for three */
+ for (k = 0; (k < 3) && (SCSI_SK_UNIT_ATTENTION == sinfo.sense_key); ++k) {
+ if (scsi_debugmode > 0)
+ pout("%s Unit Attention %d: asc/ascq=0x%x,0x%x, retrying\n",
+ __func__, k + 1, sinfo.asc, sinfo.ascq);
+ if (! device->scsi_pass_through(iop))
+ return false;
+ scsi_do_sense_disect(iop, &sinfo);
+ }
+ return true;
+}
+
+/* READ CAPACITY (10) command. Returns 0 if ok, 1 if NOT READY, 2 if
+ * command not supported, 3 if field in command not supported or returns
+ * negated errno. SBC-3 section 5.15 (rev 26) */
+int
+scsiReadCapacity10(scsi_device * device, unsigned int * last_lbap,
+ unsigned int * lb_sizep)
+{
+ int res;
+ struct scsi_cmnd_io io_hdr = {};
+ struct scsi_sense_disect sinfo;
+ uint8_t cdb[10] = {};
+ uint8_t sense[32];
+ uint8_t resp[8] = {};
+
+ io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
+ io_hdr.dxfer_len = sizeof(resp);
+ io_hdr.dxferp = resp;
+ cdb[0] = READ_CAPACITY_10;
+ io_hdr.cmnd = cdb;
+ io_hdr.cmnd_len = sizeof(cdb);
+ io_hdr.sensep = sense;
+ io_hdr.max_sense_len = sizeof(sense);
+ io_hdr.timeout = SCSI_TIMEOUT_DEFAULT;
+
+ if (! scsi_pass_through_yield_sense(device, &io_hdr, sinfo))
+ return -device->get_errno();
+ res = scsiSimpleSenseFilter(&sinfo);
+ if (res)
+ return res;
+ if (last_lbap)
+ *last_lbap = sg_get_unaligned_be32(resp + 0);
+ if (lb_sizep)
+ *lb_sizep = sg_get_unaligned_be32(resp + 4);
+ return 0;
+}
+
+/* READ CAPACITY (16) command. The bufLen argument should be 32. Returns 0
+ * if ok, 1 if NOT READY, 2 if command not supported, 3 if field in command
+ * not supported or returns negated errno. SBC-3 section 5.16 (rev 26) */
+int
+scsiReadCapacity16(scsi_device * device, uint8_t *pBuf, int bufLen)
+{
+ struct scsi_cmnd_io io_hdr = {};
+ struct scsi_sense_disect sinfo;
+ uint8_t cdb[16] = {};
+ uint8_t sense[32];
+
+ io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
+ io_hdr.dxfer_len = bufLen;
+ io_hdr.dxferp = pBuf;
+ cdb[0] = SERVICE_ACTION_IN_16;
+ cdb[1] = SAI_READ_CAPACITY_16;
+ sg_put_unaligned_be32(bufLen, cdb + 10);
+ io_hdr.cmnd = cdb;
+ io_hdr.cmnd_len = sizeof(cdb);
+ io_hdr.sensep = sense;
+ io_hdr.max_sense_len = sizeof(sense);
+ io_hdr.timeout = SCSI_TIMEOUT_DEFAULT;
+
+ if (!scsi_pass_through_yield_sense(device, &io_hdr, sinfo))
+ return -device->get_errno();
+ return scsiSimpleSenseFilter(&sinfo);
+}
+
+/* REPORT SUPPORTED OPERATION CODES [RSOC] command. If SIMPLE_NO_ERROR is
+ * returned then the response length is written to rspLen. */
+int
+scsiRSOCcmd(scsi_device * device, bool rctd, uint8_t rep_opt, uint8_t opcode,
+ uint16_t serv_act, uint8_t *pBuf, int bufLen, int & rspLen)
+{
+ struct scsi_cmnd_io io_hdr = {};
+ struct scsi_sense_disect sinfo;
+ int res;
+ uint8_t cdb[12] = {};
+ uint8_t sense[32];
+
+ io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
+ io_hdr.dxfer_len = bufLen;
+ io_hdr.dxferp = pBuf;
+ cdb[0] = MAINTENANCE_IN_12;
+ cdb[1] = MI_REP_SUP_OPCODES;
+ if (rctd)
+ cdb[2] |= 0x80;
+ if (rep_opt > 0)
+ cdb[2] |= (0x7 & rep_opt);
+ cdb[3] = opcode;
+ sg_put_unaligned_be16(serv_act, cdb + 4);
+ sg_put_unaligned_be32(bufLen, cdb + 6);
+ io_hdr.cmnd = cdb;
+ io_hdr.cmnd_len = sizeof(cdb);
+ io_hdr.sensep = sense;
+ io_hdr.max_sense_len = sizeof(sense);
+ io_hdr.timeout = SCSI_TIMEOUT_DEFAULT;
+
+ if (!scsi_pass_through_yield_sense(device, &io_hdr, sinfo))
+ return -device->get_errno();
+ res = scsiSimpleSenseFilter(&sinfo);
+ if (SIMPLE_NO_ERROR == res)
+ rspLen = bufLen - io_hdr.resid;
+ return res;
+}
+
+/* Return number of bytes of storage in 'device' or 0 if error. If
+ * successful and lb_sizep is not nullptr then the logical block size in bytes
+ * is written to the location pointed to by lb_sizep. If the 'Logical Blocks
+ * per Physical Block Exponent' pointer (lb_per_pb_expp,) is non-null then
+ * the value is written. If 'Protection information Intervals Exponent'*/
+uint64_t
+scsiGetSize(scsi_device * device, bool avoid_rcap16,
+ struct scsi_readcap_resp * srrp)
+{
+ bool try_16 = false;
+ bool try_12 = false;
+ unsigned int last_lba = 0, lb_size = 0;
+ int res;
+ uint64_t ret_val = 0;
+ uint8_t rc16resp[32];
+
+ if (avoid_rcap16) {
+ res = scsiReadCapacity10(device, &last_lba, &lb_size);
+ if (res) {
+ if (scsi_debugmode)
+ pout("%s: READ CAPACITY(10) failed, res=%d\n", __func__, res);
+ try_16 = true;
+ } else { /* rcap10 succeeded */
+ if (0xffffffff == last_lba) {
+ /* so number of blocks needs > 32 bits to represent */
+ try_16 = true;
+ device->set_rcap16_first();
+ } else {
+ ret_val = last_lba + 1;
+ if (srrp) {
+ memset(srrp, 0, sizeof(*srrp));
+ srrp->num_lblocks = ret_val;
+ srrp->lb_size = lb_size;
+ }
+ }
+ }
+ } else if (SC_SUPPORT ==
+ device->cmd_support_level(SERVICE_ACTION_IN_16, true,
+ SAI_READ_CAPACITY_16))
+ try_16 = true;
+
+ if (try_16 || (! avoid_rcap16)) {
+ res = scsiReadCapacity16(device, rc16resp, sizeof(rc16resp));
+ if (res) {
+ if (scsi_debugmode)
+ pout("%s: READ CAPACITY(16) failed, res=%d\n", __func__, res);
+ if (try_16) /* so already tried rcap10 */
+ return 0;
+ try_12 = true;
+ } else { /* rcap16 succeeded */
+ ret_val = sg_get_unaligned_be64(rc16resp + 0) + 1;
+ lb_size = sg_get_unaligned_be32(rc16resp + 8);
+ if (srrp) { /* writes to all fields */
+ srrp->num_lblocks = ret_val;
+ srrp->lb_size = lb_size;
+ bool prot_en = !!(0x1 & rc16resp[12]);
+ uint8_t p_type = ((rc16resp[12] >> 1) & 0x7);
+ srrp->prot_type = prot_en ? (1 + p_type) : 0;
+ srrp->p_i_exp = ((rc16resp[13] >> 4) & 0xf);
+ srrp->lb_p_pb_exp = (rc16resp[13] & 0xf);
+ srrp->lbpme = !!(0x80 & rc16resp[14]);
+ srrp->lbprz = !!(0x40 & rc16resp[14]);
+ srrp->l_a_lba = sg_get_unaligned_be16(rc16resp + 14) & 0x3fff;
+ }
+ }
+ }
+ if (try_12) { /* case where only rcap16 has been tried and failed */
+ res = scsiReadCapacity10(device, &last_lba, &lb_size);
+ if (res) {
+ if (scsi_debugmode)
+ pout("%s: 2nd READ CAPACITY(10) failed, res=%d\n", __func__,
+ res);
+ return 0;
+ } else { /* rcap10 succeeded */
+ ret_val = (uint64_t)last_lba + 1;
+ if (srrp) {
+ memset(srrp, 0, sizeof(*srrp));
+ srrp->num_lblocks = ret_val;
+ srrp->lb_size = lb_size;
+ }
+ }
+ }
+ return (ret_val * lb_size);
+}
+
+/* Offset into mode sense (6 or 10 byte) response that actual mode page
+ * starts at (relative to resp[0]). Returns -1 if problem */
+int
+scsiModePageOffset(const uint8_t * resp, int len, int modese_len)
+{
+ int offset = -1;
+
+ if (resp) {
+ int resp_len, bd_len;
+ if (10 == modese_len) {
+ resp_len = sg_get_unaligned_be16(resp + 0) + 2;
+ bd_len = sg_get_unaligned_be16(resp + 6);
+ offset = bd_len + 8;
+ } else {
+ resp_len = resp[0] + 1;
+ bd_len = resp[3];
+ offset = bd_len + 4;
+ }
+ if ((offset + 2) > len) {
+ pout("scsiModePageOffset: raw_curr too small, offset=%d "
+ "resp_len=%d bd_len=%d\n", offset, resp_len, bd_len);
+ offset = -1;
+ } else if ((offset + 2) > resp_len) {
+ if ((resp_len > 2) || scsi_debugmode)
+ pout("scsiModePageOffset: response length too short, "
+ "resp_len=%d offset=%d bd_len=%d\n", resp_len,
+ offset, bd_len);
+ offset = -1;
+ }
+ }
+ return offset;
+}
+
+/* IEC mode page byte 2 bit masks */
+#define DEXCPT_ENABLE 0x08
+#define EWASC_ENABLE 0x10
+#define DEXCPT_DISABLE 0xf7
+#define EWASC_DISABLE 0xef
+#define TEST_DISABLE 0xfb
+
+/* Fetches the Informational Exceptions Control mode page. First tries
+ * the 6 byte MODE SENSE command and if that fails with an illegal opcode
+ * tries a 10 byte MODE SENSE command. Returns 0 if successful, a positive
+ * number if a known error (see SIMPLE_ERR_ ...) or a negative errno
+ * value. */
+int
+scsiFetchIECmpage(scsi_device * device, struct scsi_iec_mode_page *iecp,
+ int modese_len)
+{
+ int err = 0;
+
+ memset(iecp, 0, sizeof(*iecp));
+ iecp->modese_len = modese_len;
+ iecp->requestedCurrent = 1;
+ if (iecp->modese_len <= 6) {
+ if ((err = scsiModeSense(device, INFORMATIONAL_EXCEPTIONS_CONTROL_PAGE,
+ 0, MPAGE_CONTROL_CURRENT,
+ iecp->raw_curr, sizeof(iecp->raw_curr)))) {
+ if (SIMPLE_ERR_BAD_OPCODE == err)
+ iecp->modese_len = 10;
+ else {
+ iecp->modese_len = 0;
+ return err;
+ }
+ } else if (0 == iecp->modese_len)
+ iecp->modese_len = 6;
+ }
+ if (10 == iecp->modese_len) {
+ err = scsiModeSense10(device, INFORMATIONAL_EXCEPTIONS_CONTROL_PAGE,
+ 0, MPAGE_CONTROL_CURRENT,
+ iecp->raw_curr, sizeof(iecp->raw_curr));
+ if (err) {
+ iecp->modese_len = 0;
+ return err;
+ }
+ }
+ iecp->gotCurrent = 1;
+ iecp->requestedChangeable = 1;
+ if (10 == iecp->modese_len)
+ err = scsiModeSense10(device, INFORMATIONAL_EXCEPTIONS_CONTROL_PAGE,
+ 0, MPAGE_CONTROL_CHANGEABLE,
+ iecp->raw_chg, sizeof(iecp->raw_chg));
+ else if (6 == iecp->modese_len)
+ err = scsiModeSense(device, INFORMATIONAL_EXCEPTIONS_CONTROL_PAGE,
+ 0, MPAGE_CONTROL_CHANGEABLE,
+ iecp->raw_chg, sizeof(iecp->raw_chg));
+ if (err)
+ return err;
+ iecp->gotChangeable = 1;
+ return 0;
+}
+
+int
+scsi_IsExceptionControlEnabled(const struct scsi_iec_mode_page *iecp)
+{
+ if (iecp && iecp->gotCurrent) {
+ int offset = scsiModePageOffset(iecp->raw_curr, sizeof(iecp->raw_curr),
+ iecp->modese_len);
+ if (offset >= 0)
+ return (iecp->raw_curr[offset + 2] & DEXCPT_ENABLE) ? 0 : 1;
+ else
+ return 0;
+ } else
+ return 0;
+}
+
+int
+scsi_IsWarningEnabled(const struct scsi_iec_mode_page *iecp)
+{
+ if (iecp && iecp->gotCurrent) {
+ int offset = scsiModePageOffset(iecp->raw_curr, sizeof(iecp->raw_curr),
+ iecp->modese_len);
+ if (offset >= 0)
+ return (iecp->raw_curr[offset + 2] & EWASC_ENABLE) ? 1 : 0;
+ else
+ return 0;
+ } else
+ return 0;
+}
+
+/* set EWASC and clear PERF, EBF, DEXCPT TEST and LOGERR */
+#define SCSI_IEC_MP_BYTE2_ENABLED 0x10
+#define SCSI_IEC_MP_BYTE2_TEST_MASK 0x4
+/* exception/warning via an unrequested REQUEST SENSE command */
+#define SCSI_IEC_MP_MRIE 6
+#define SCSI_IEC_MP_INTERVAL_T 0
+#define SCSI_IEC_MP_REPORT_COUNT 1
+
+/* Try to set (or clear) both Exception Control and Warning in the IE
+ * mode page subject to the "changeable" mask. The object pointed to
+ * by iecp is (possibly) inaccurate after this call, therefore
+ * scsiFetchIECmpage() should be called again if the IEC mode page
+ * is to be re-examined.
+ * When -r ioctl is invoked 3 or more time on 'smartctl -s on ...'
+ * then set the TEST bit (causes asc,ascq pair of 0x5d,0xff). */
+int
+scsiSetExceptionControlAndWarning(scsi_device * device, int enabled,
+ const struct scsi_iec_mode_page *iecp)
+{
+ int offset, resp_len;
+ int err = 0;
+ uint8_t rout[SCSI_IECMP_RAW_LEN];
+
+ if ((! iecp) || (! iecp->gotCurrent))
+ return -EINVAL;
+ offset = scsiModePageOffset(iecp->raw_curr, sizeof(iecp->raw_curr),
+ iecp->modese_len);
+ if (offset < 0)
+ return -EINVAL;
+ memcpy(rout, iecp->raw_curr, SCSI_IECMP_RAW_LEN);
+ /* mask out DPOFUA device specific (disk) parameter bit */
+ if (10 == iecp->modese_len) {
+ resp_len = sg_get_unaligned_be16(rout + 0) + 2;
+ rout[3] &= 0xef;
+ } else {
+ resp_len = rout[0] + 1;
+ rout[2] &= 0xef;
+ }
+ int sp = !! (rout[offset] & 0x80); /* PS bit becomes 'SELECT's SP bit */
+ if (enabled) {
+ rout[offset + 2] = SCSI_IEC_MP_BYTE2_ENABLED;
+ if (scsi_debugmode > 2)
+ rout[offset + 2] |= SCSI_IEC_MP_BYTE2_TEST_MASK;
+ rout[offset + 3] = SCSI_IEC_MP_MRIE;
+ sg_put_unaligned_be32(SCSI_IEC_MP_INTERVAL_T, rout + offset + 4);
+ sg_put_unaligned_be32(SCSI_IEC_MP_REPORT_COUNT, rout + offset + 8);
+ if (iecp->gotChangeable) {
+ uint8_t chg2 = iecp->raw_chg[offset + 2];
+
+ rout[offset + 2] = chg2 ? (rout[offset + 2] & chg2) :
+ iecp->raw_curr[offset + 2];
+ for (int k = 3; k < 12; ++k) {
+ if (0 == iecp->raw_chg[offset + k])
+ rout[offset + k] = iecp->raw_curr[offset + k];
+ }
+ }
+ if (0 == memcmp(&rout[offset + 2], &iecp->raw_chg[offset + 2], 10)) {
+ if (scsi_debugmode > 0)
+ pout("scsiSetExceptionControlAndWarning: already enabled\n");
+ return 0;
+ }
+ } else { /* disabling Exception Control and (temperature) Warnings */
+ int eCEnabled = (rout[offset + 2] & DEXCPT_ENABLE) ? 0 : 1;
+ int wEnabled = (rout[offset + 2] & EWASC_ENABLE) ? 1 : 0;
+ if ((! eCEnabled) && (! wEnabled)) {
+ if (scsi_debugmode > 0)
+ pout("scsiSetExceptionControlAndWarning: already disabled\n");
+ return 0; /* nothing to do, leave other setting alone */
+ }
+ if (wEnabled)
+ rout[offset + 2] &= EWASC_DISABLE;
+ if (eCEnabled) {
+ if (iecp->gotChangeable &&
+ (iecp->raw_chg[offset + 2] & DEXCPT_ENABLE))
+ rout[offset + 2] |= DEXCPT_ENABLE;
+ rout[offset + 2] &= TEST_DISABLE; /* clear TEST bit for spec */
+ }
+ }
+ if (10 == iecp->modese_len)
+ err = scsiModeSelect10(device, sp, rout, resp_len);
+ else if (6 == iecp->modese_len)
+ err = scsiModeSelect(device, sp, rout, resp_len);
+ return err;
+}
+
+int
+scsiGetTemp(scsi_device * device, uint8_t *currenttemp, uint8_t *triptemp)
+{
+ uint8_t tBuf[252] = {};
+ int err;
+
+ if ((err = scsiLogSense(device, TEMPERATURE_LPAGE, 0, tBuf,
+ sizeof(tBuf), 0))) {
+ *currenttemp = 0;
+ *triptemp = 0;
+ pout("%s for temperature failed [%s]\n", logSenStr,
+ scsiErrString(err));
+ return err;
+ }
+ *currenttemp = tBuf[9];
+ *triptemp = tBuf[15];
+ return 0;
+}
+
+/* Informational Exception conditions specified by spc6r06.pdf seem to be
+ * associated with ASC values 0xb (warnings) and 0x5d (impending failures).
+ * The asc/accq value 0x5d,0xff is reported in response to setting the TEST
+ * bit in the Informationl Exception Control mode page. */
+
+/* Read informational exception log page or Request Sense response.
+ * Fetching asc/ascq code potentially flagging an exception or warning.
+ * Returns 0 if ok, else error number. A current temperature of 255
+ * (Celsius) implies that the temperature not available. */
+int
+scsiCheckIE(scsi_device * device, int hasIELogPage, int hasTempLogPage,
+ uint8_t *asc, uint8_t *ascq, uint8_t *currenttemp,
+ uint8_t *triptemp)
+{
+ uint8_t tBuf[252] = {};
+ struct scsi_sense_disect sense_info;
+ int err;
+ uint8_t currTemp, trTemp;
+
+ memset(&sense_info, 0, sizeof(sense_info));
+ *asc = 0;
+ *ascq = 0;
+ *currenttemp = 0;
+ *triptemp = 0;
+ if (hasIELogPage) {
+ if ((err = scsiLogSense(device, IE_LPAGE, 0, tBuf,
+ sizeof(tBuf), 0))) {
+ pout("%s failed, IE page [%s]\n", logSenStr, scsiErrString(err));
+ return err;
+ }
+ // pull out page size from response, don't forget to add 4
+ unsigned short pagesize = sg_get_unaligned_be16(tBuf + 2) + 4;
+ if ((pagesize < 4) || tBuf[4] || tBuf[5]) {
+ pout("%s failed, IE page, bad parameter code or length\n",
+ logSenStr);
+ return SIMPLE_ERR_BAD_PARAM;
+ }
+ if (tBuf[7] > 1) {
+ sense_info.asc = tBuf[8];
+ sense_info.ascq = tBuf[9];
+ if (! hasTempLogPage) {
+ if (tBuf[7] > 2)
+ *currenttemp = tBuf[10];
+ if (tBuf[7] > 3) /* IBM extension in SMART (IE) lpage */
+ *triptemp = tBuf[11];
+ }
+ }
+ }
+ if (0 == sense_info.asc) {
+ /* ties in with MRIE field of 6 in IEC mode page (0x1c) */
+ if ((err = scsiRequestSense(device, &sense_info))) {
+ pout("Request Sense failed, [%s]\n", scsiErrString(err));
+ return err;
+ }
+ }
+ *asc = sense_info.asc;
+ *ascq = sense_info.ascq;
+ if (hasTempLogPage) {
+ if (0 == scsiGetTemp(device, &currTemp, &trTemp)) {
+ *currenttemp = currTemp;
+ *triptemp = trTemp;
+ }
+ }
+ return 0;
+}
+
+// The first character (W, C, I) tells the severity
+static const char * TapeAlertsMessageTable[]= {
+ " ",
+ /* 0x01 */
+ "W: The tape drive is having problems reading data. No data has been "
+ "lost,\n"
+ " but there has been a reduction in the performance of the tape.",
+ /* 0x02 */
+ "W: The tape drive is having problems writing data. No data has been "
+ "lost,\n"
+ " but there has been a reduction in the capacity of the tape.",
+ /* 0x03 */
+ "W: The operation has stopped because an error has occurred while "
+ "reading\n"
+ " or writing data that the drive cannot correct.",
+ /* 0x04 */
+ "C: Your data is at risk:\n"
+ " 1. Copy any data you require from this tape. \n"
+ " 2. Do not use this tape again.\n"
+ " 3. Restart the operation with a different tape.",
+ /* 0x05 */
+ "C: The tape is damaged or the drive is faulty. Call the tape drive\n"
+ " supplier helpline.",
+ /* 0x06 */
+ "C: The tape is from a faulty batch or the tape drive is faulty:\n"
+ " 1. Use a good tape to test the drive.\n"
+ " 2. If problem persists, call the tape drive supplier helpline.",
+ /* 0x07 */
+ "W: The tape cartridge has reached the end of its calculated useful "
+ "life:\n"
+ " 1. Copy data you need to another tape.\n"
+ " 2. Discard the old tape.",
+ /* 0x08 */
+ "W: The tape cartridge is not data-grade. Any data you back up to the "
+ "tape\n"
+ " is at risk. Replace the cartridge with a data-grade tape.",
+ /* 0x09 */
+ "C: You are trying to write to a write-protected cartridge. Remove the\n"
+ " write-protection or use another tape.",
+ /* 0x0a */
+ "I: You cannot eject the cartridge because the tape drive is in use. "
+ "Wait\n"
+ " until the operation is complete before ejecting the cartridge.",
+ /* 0x0b */
+ "I: The tape in the drive is a cleaning cartridge.",
+ /* 0x0c */
+ "I: You have tried to load a cartridge of a type which is not supported\n"
+ " by this drive.",
+ /* 0x0d */
+ "C: The operation has failed because the tape in the drive has "
+ "experienced\n"
+ " a mechanical failure:\n"
+ " 1. Discard the old tape.\n"
+ " 2. Restart the operation with a different tape.",
+ /* 0x0e */
+ "C: The operation has failed because the tape in the drive has "
+ "experienced\n"
+ " a mechanical failure:\n"
+ " 1. Do not attempt to extract the tape cartridge\n"
+ " 2. Call the tape drive supplier helpline.",
+ /* 0x0f */
+ "W: The memory in the tape cartridge has failed, which reduces\n"
+ " performance. Do not use the cartridge for further write "
+ "operations.",
+ /* 0x10 */
+ "C: The operation has failed because the tape cartridge was manually\n"
+ " de-mounted while the tape drive was actively writing or reading.",
+ /* 0x11 */
+ "W: You have loaded a cartridge of a type that is read-only in this "
+ "drive.\n"
+ " The cartridge will appear as write-protected.",
+ /* 0x12 */
+ "W: The tape directory on the tape cartridge has been corrupted. File\n"
+ " search performance will be degraded. The tape directory can be "
+ "rebuilt\n"
+ " by reading all the data on the cartridge.",
+ /* 0x13 */
+ "I: The tape cartridge is nearing the end of its calculated life. It is\n"
+ " recommended that you:\n"
+ " 1. Use another tape cartridge for your next backup.\n"
+ " 2. Store this tape in a safe place in case you need to restore "
+ " data from it.",
+ /* 0x14 */
+ "C: The tape drive needs cleaning:\n"
+ " 1. If the operation has stopped, eject the tape and clean the "
+ "drive.\n"
+ " 2. If the operation has not stopped, wait for it to finish and "
+ "then\n"
+ " clean the drive.\n"
+ " Check the tape drive users manual for device specific cleaning "
+ "instructions.",
+ /* 0x15 */
+ "W: The tape drive is due for routine cleaning:\n"
+ " 1. Wait for the current operation to finish.\n"
+ " 2. The use a cleaning cartridge.\n"
+ " Check the tape drive users manual for device specific cleaning "
+ "instructions.",
+ /* 0x16 */
+ "C: The last cleaning cartridge used in the tape drive has worn out:\n"
+ " 1. Discard the worn out cleaning cartridge.\n"
+ " 2. Wait for the current operation to finish.\n"
+ " 3. Then use a new cleaning cartridge.",
+ /* 0x17 */
+ "C: The last cleaning cartridge used in the tape drive was an invalid\n"
+ " type:\n"
+ " 1. Do not use this cleaning cartridge in this drive.\n"
+ " 2. Wait for the current operation to finish.\n"
+ " 3. Then use a new cleaning cartridge.",
+ /* 0x18 */
+ "W: The tape drive has requested a retention operation",
+ /* 0x19 */
+ "W: A redundant interface port on the tape drive has failed",
+ /* 0x1a */
+ "W: A tape drive cooling fan has failed",
+ /* 0x1b */
+ "W: A redundant power supply has failed inside the tape drive enclosure.\n"
+ " Check the enclosure users manual for instructions on replacing "
+ "the\n"
+ " failed power supply.",
+ /* 0x1c */
+ "W: The tape drive power consumption is outside the specified range.",
+ /* 0x1d */
+ "W: Preventive maintenance of the tape drive is required. Check the tape\n"
+ " drive users manual for device specific preventive maintenance\n"
+ " tasks or call the tape drive supplier helpline.",
+ /* 0x1e */
+ "C: The tape drive has a hardware fault:\n"
+ " 1. Eject the tape or magazine.\n"
+ " 2. Reset the drive.\n"
+ " 3. Restart the operation.",
+ /* 0x1f */
+ "C: The tape drive has a hardware fault:\n"
+ " 1. Turn the tape drive off and then on again.\n"
+ " 2. Restart the operation.\n"
+ " 3. If the problem persists, call the tape drive supplier helpline.",
+ /* 0x20 */
+ "W: The tape drive has a problem with the application client interface:\n"
+ " 1. Check the cables and cable connections.\n"
+ " 2. Restart the operation.",
+ /* 0x21 */
+ "C: The operation has failed:\n"
+ " 1. Eject the tape or magazine.\n"
+ " 2. Insert the tape or magazine again.\n"
+ " 3. Restart the operation.",
+ /* 0x22 */
+ "W: The firmware download has failed because you have tried to use the\n"
+ " incorrect firmware for this tape drive. Obtain the correct\n"
+ " firmware and try again.",
+ /* 0x23 */
+ "W: Environmental conditions inside the tape drive are outside the\n"
+ " specified humidity range.",
+ /* 0x24 */
+ "W: Environmental conditions inside the tape drive are outside the\n"
+ " specified temperature range.",
+ /* 0x25 */
+ "W: The voltage supply to the tape drive is outside the specified range.",
+ /* 0x26 */
+ "C: A hardware failure of the tape drive is predicted. Call the tape\n"
+ " drive supplier helpline.",
+ /* 0x27 */
+ "W: The tape drive may have a hardware fault. Run extended diagnostics to\n"
+ " verify and diagnose the problem. Check the tape drive users manual "
+ "for\n"
+ " device specific instructions on running extended diagnostic tests.",
+ /* 0x28 */
+ "C: The changer mechanism is having difficulty communicating with the "
+ "tape\n"
+ " drive:\n"
+ " 1. Turn the autoloader off then on.\n"
+ " 2. Restart the operation.\n"
+ " 3. If problem persists, call the tape drive supplier helpline.",
+ /* 0x29 */
+ "C: A tape has been left in the autoloader by a previous hardware fault:\n"
+ " 1. Insert an empty magazine to clear the fault.\n"
+ " 2. If the fault does not clear, turn the autoloader off and then\n"
+ " on again.\n"
+ " 3. If the problem persists, call the tape drive supplier helpline.",
+ /* 0x2a */
+ "W: There is a problem with the autoloader mechanism.",
+ /* 0x2b */
+ "C: The operation has failed because the autoloader door is open:\n"
+ " 1. Clear any obstructions from the autoloader door.\n"
+ " 2. Eject the magazine and then insert it again.\n"
+ " 3. If the fault does not clear, turn the autoloader off and then\n"
+ " on again.\n"
+ " 4. If the problem persists, call the tape drive supplier helpline.",
+ /* 0x2c */
+ "C: The autoloader has a hardware fault:\n"
+ " 1. Turn the autoloader off and then on again.\n"
+ " 2. Restart the operation.\n"
+ " 3. If the problem persists, call the tape drive supplier helpline.\n"
+ " Check the autoloader users manual for device specific instructions\n"
+ " on turning the device power on and off.",
+ /* 0x2d */
+ "C: The autoloader cannot operate without the magazine,\n"
+ " 1. Insert the magazine into the autoloader.\n"
+ " 2. Restart the operation.",
+ /* 0x2e */
+ "W: A hardware failure of the changer mechanism is predicted. Call the\n"
+ " tape drive supplier helpline.",
+ /* 0x2f */
+ "I: Reserved.",
+ /* 0x30 */
+ "I: Reserved.",
+ /* 0x31 */
+ "I: Reserved.",
+ /* 0x32 */
+ "W: Media statistics have been lost at some time in the past",
+ /* 0x33 */
+ "W: The tape directory on the tape cartridge just unloaded has been\n"
+ " corrupted. File search performance will be degraded. The tape\n"
+ " directory can be rebuilt by reading all the data.",
+ /* 0x34 */
+ "C: The tape just unloaded could not write its system area successfully:\n"
+ " 1. Copy data to another tape cartridge.\n"
+ " 2. Discard the old cartridge.",
+ /* 0x35 */
+ "C: The tape system are could not be read successfully at load time:\n"
+ " 1. Copy data to another tape cartridge.\n",
+ /* 0x36 */
+ "C: The start or data could not be found on the tape:\n"
+ " 1. Check you are using the correct format tape.\n"
+ " 2. Discard the tape or return the tape to your supplier",
+ /* 0x37 */
+ "C: The operation has failed because the media cannot be loaded\n"
+ " and threaded.\n"
+ " 1. Remove the cartridge, inspect it as specified in the product\n"
+ " manual, and retry the operation.\n"
+ " 2. If the problem persists, call the tape drive supplier help "
+ "line.",
+ /* 0x38 */
+ "C: The operation has failed because the medium cannot be unloaded:\n"
+ " 1. Do not attempt to extract the tape cartridge.\n"
+ " 2. Call the tape driver supplier help line.",
+ /* 0x39 */
+ "C: The tape drive has a problem with the automation interface:\n"
+ " 1. Check the power to the automation system.\n"
+ " 2. Check the cables and cable connections.\n"
+ " 3. Call the supplier help line if problem persists.",
+ /* 0x3a */
+ "W: The tape drive has reset itself due to a detected firmware\n"
+ " fault. If problem persists, call the supplier help line.",
+ };
+
+const char *
+scsiTapeAlertsTapeDevice(unsigned short code)
+{
+ static const int num = sizeof(TapeAlertsMessageTable) /
+ sizeof(TapeAlertsMessageTable[0]);
+
+ return (code < num) ? TapeAlertsMessageTable[code] : "Unknown Alert";
+}
+
+// The first character (W, C, I) tells the severity
+static const char * ChangerTapeAlertsMessageTable[]= {
+ " ",
+ /* 0x01 */
+ "C: The library mechanism is having difficulty communicating with the\n"
+ " drive:\n"
+ " 1. Turn the library off then on.\n"
+ " 2. Restart the operation.\n"
+ " 3. If the problem persists, call the library supplier help line.",
+ /* 0x02 */
+ "W: There is a problem with the library mechanism. If problem persists,\n"
+ " call the library supplier help line.",
+ /* 0x03 */
+ "C: The library has a hardware fault:\n"
+ " 1. Reset the library.\n"
+ " 2. Restart the operation.\n"
+ " Check the library users manual for device specific instructions on "
+ "resetting\n"
+ " the device.",
+ /* 0x04 */
+ "C: The library has a hardware fault:\n"
+ " 1. Turn the library off then on again.\n"
+ " 2. Restart the operation.\n"
+ " 3. If the problem persists, call the library supplier help line.\n"
+ " Check the library users manual for device specific instructions on "
+ "turning the\n"
+ " device power on and off.",
+ /* 0x05 */
+ "W: The library mechanism may have a hardware fault.\n"
+ " Run extended diagnostics to verify and diagnose the problem. "
+ "Check the library\n"
+ " users manual for device specific instructions on running extended "
+ "diagnostic\n"
+ " tests.",
+ /* 0x06 */
+ "C: The library has a problem with the host interface:\n"
+ " 1. Check the cables and connections.\n"
+ " 2. Restart the operation.",
+ /* 0x07 */
+ "W: A hardware failure of the library is predicted. Call the library\n"
+ " supplier help line.",
+ /* 0x08 */
+ "W: Preventive maintenance of the library is required.\n"
+ " Check the library users manual for device specific preventative "
+ "maintenance\n"
+ " tasks, or call your library supplier help line.",
+ /* 0x09 */
+ "C: General environmental conditions inside the library are outside the\n"
+ " specified humidity range.",
+ /* 0x0a */
+ "C: General environmental conditions inside the library are outside the\n"
+ " specified temperature range.",
+ /* 0x0b */
+ "C: The voltage supply to the library is outside the specified range.\n"
+ " There is a potential problem with the power supply or failure of\n"
+ " a redundant power supply.",
+ /* 0x0c */
+ "C: A cartridge has been left inside the library by a previous hardware\n"
+ " fault:\n"
+ " 1. Insert an empty magazine to clear the fault.\n"
+ " 2. If the fault does not clear, turn the library off and then on "
+ "again.\n"
+ " 3. If the problem persists, call the library supplier help line.",
+ /* 0x0d */
+ "W: There is a potential problem with the drive ejecting cartridges or\n"
+ " with the library mechanism picking a cartridge from a slot.\n"
+ " 1. No action needs to be taken at this time.\n"
+ " 2. If the problem persists, call the library supplier help line.",
+ /* 0x0e */
+ "W: There is a potential problem with the library mechanism placing a\n"
+ " cartridge into a slot.\n"
+ " 1. No action needs to be taken at this time.\n"
+ " 2. If the problem persists, call the library supplier help line.",
+ /* 0x0f */
+ "W: There is a potential problem with the drive or the library mechanism\n"
+ " loading cartridges, or an incompatible cartridge.",
+ /* 0x10 */
+ "C: The library has failed because the door is open:\n"
+ " 1. Clear any obstructions from the library door.\n"
+ " 2. Close the library door.\n"
+ " 3. If the problem persists, call the library supplier help line.",
+ /* 0x11 */
+ "C: There is a mechanical problem with the library media import/export\n"
+ " mailslot.",
+ /* 0x12 */
+ "C: The library cannot operate without the magazine.\n"
+ " 1. Insert the magazine into the library.\n"
+ " 2. Restart the operation.",
+ /* 0x13 */
+ "W: Library security has been compromised.",
+ /* 0x14 */
+ "I: The library security mode has been changed.\n"
+ " The library has either been put into secure mode, or the library "
+ "has exited\n"
+ " the secure mode.\n"
+ " This is for information purposes only. No action is required.",
+ /* 0x15 */
+ "I: The library has been manually turned offline and is unavailable for "
+ "use.",
+ /* 0x16 */
+ "I: A drive inside the library has been taken offline.\n"
+ " This is for information purposes only. No action is required.",
+ /* 0x17 */
+ "W: There is a potential problem with the bar code label or the scanner\n"
+ " hardware in the library mechanism.\n"
+ " 1. No action needs to be taken at this time.\n"
+ " 2. If the problem persists, call the library supplier help line.",
+ /* 0x18 */
+ "C: The library has detected an inconsistency in its inventory.\n"
+ " 1. Redo the library inventory to correct inconsistency.\n"
+ " 2. Restart the operation.\n"
+ " Check the applications users manual or the hardware users manual "
+ "for\n"
+ " specific instructions on redoing the library inventory.",
+ /* 0x19 */
+ "W: A library operation has been attempted that is invalid at this time.",
+ /* 0x1a */
+ "W: A redundant interface port on the library has failed.",
+ /* 0x1b */
+ "W: A library cooling fan has failed.",
+ /* 0x1c */
+ "W: A redundant power supply has failed inside the library. Check the\n"
+ " library users manual for instructions on replacing the failed "
+ "power supply.",
+ /* 0x1d */
+ "W: The library power consumption is outside the specified range.",
+ /* 0x1e */
+ "C: A failure has occurred in the cartridge pass-through mechanism "
+ "between\n"
+ " two library modules.",
+ /* 0x1f */
+ "C: A cartridge has been left in the pass-through mechanism from a\n"
+ " previous hardware fault. Check the library users guide for "
+ "instructions on\n"
+ " clearing this fault.",
+ /* 0x20 */
+ "I: The library was unable to read the bar code on a cartridge.",
+};
+
+const char *
+scsiTapeAlertsChangerDevice(unsigned short code)
+{
+ static const int num = sizeof(ChangerTapeAlertsMessageTable) /
+ sizeof(ChangerTapeAlertsMessageTable[0]);
+
+ return (code < num) ? ChangerTapeAlertsMessageTable[code] :
+ "Unknown Alert";
+}
+
+int
+scsiSmartDefaultSelfTest(scsi_device * device)
+{
+ int res;
+
+ res = scsiSendDiagnostic(device, SCSI_DIAG_DEF_SELF_TEST, nullptr, 0);
+ if (res)
+ pout("Default self test failed [%s]\n", scsiErrString(res));
+ return res;
+}
+
+int
+scsiSmartShortSelfTest(scsi_device * device)
+{
+ int res;
+
+ res = scsiSendDiagnostic(device, SCSI_DIAG_BG_SHORT_SELF_TEST, nullptr, 0);
+ if (res)
+ pout("Short offline self test failed [%s]\n", scsiErrString(res));
+ return res;
+}
+
+int
+scsiSmartExtendSelfTest(scsi_device * device)
+{
+ int res;
+
+ res = scsiSendDiagnostic(device, SCSI_DIAG_BG_EXTENDED_SELF_TEST, nullptr, 0);
+ if (res)
+ pout("Long (extended) offline self test failed [%s]\n",
+ scsiErrString(res));
+ return res;
+}
+
+int
+scsiSmartShortCapSelfTest(scsi_device * device)
+{
+ int res;
+
+ res = scsiSendDiagnostic(device, SCSI_DIAG_FG_SHORT_SELF_TEST, nullptr, 0);
+ if (res)
+ pout("Short foreground self test failed [%s]\n", scsiErrString(res));
+ return res;
+}
+
+int
+scsiSmartExtendCapSelfTest(scsi_device * device)
+{
+ int res;
+
+ res = scsiSendDiagnostic(device, SCSI_DIAG_FG_EXTENDED_SELF_TEST, nullptr, 0);
+ if (res)
+ pout("Long (extended) foreground self test failed [%s]\n",
+ scsiErrString(res));
+ return res;
+}
+
+int
+scsiSmartSelfTestAbort(scsi_device * device)
+{
+ int res;
+
+ res = scsiSendDiagnostic(device, SCSI_DIAG_ABORT_SELF_TEST, nullptr, 0);
+ if (res)
+ pout("Abort self test failed [%s]\n", scsiErrString(res));
+ return res;
+}
+
+/* Returns 0 and the expected duration of an extended self test (in seconds)
+ if successful; any other return value indicates a failure. */
+int
+scsiFetchExtendedSelfTestTime(scsi_device * device, int * durationSec,
+ int modese_len)
+{
+ int err, offset;
+ uint8_t buff[64] = {};
+
+ if (modese_len <= 6) {
+ if ((err = scsiModeSense(device, CONTROL_MODE_PAGE, 0,
+ MPAGE_CONTROL_CURRENT,
+ buff, sizeof(buff)))) {
+ if (SIMPLE_ERR_BAD_OPCODE == err)
+ modese_len = 10;
+ else
+ return err;
+ } else if (0 == modese_len)
+ modese_len = 6;
+ }
+ if (10 == modese_len) {
+ err = scsiModeSense10(device, CONTROL_MODE_PAGE, 0,
+ MPAGE_CONTROL_CURRENT,
+ buff, sizeof(buff));
+ if (err)
+ return err;
+ }
+ offset = scsiModePageOffset(buff, sizeof(buff), modese_len);
+ if (offset < 0)
+ return -EINVAL;
+ if (buff[offset + 1] >= 0xa) {
+ int res = sg_get_unaligned_be16(buff + offset + 10);
+
+ if (res < 0xffff) {
+ *durationSec = res;
+ return 0;
+ }
+ /* The value 0xffff (all bits set in 16 bit field) indicates that
+ * the Extended Inquiry VPD page should be consulted, it has a
+ * similarly named 16 bit field, but the unit is minutes. */
+ uint8_t b[64];
+
+ if ((0 == scsiInquiryVpd(device, SCSI_VPD_EXTENDED_INQUIRY_DATA,
+ b, sizeof(b))) &&
+ ((sg_get_unaligned_be16(b + 2)) > 11)) {
+ res = sg_get_unaligned_be16(b + 10);
+ *durationSec = res * 60; /* VPD field is in minutes */
+ return 0;
+ } else
+ return -EINVAL;
+ } else
+ return -EINVAL;
+}
+
+void
+scsiDecodeErrCounterPage(unsigned char * resp, struct scsiErrorCounter *ecp,
+ int allocLen)
+{
+ memset(ecp, 0, sizeof(*ecp));
+ int num = sg_get_unaligned_be16(resp + 2);
+ unsigned char * ucp = &resp[0] + 4;
+
+ /* allocLen is length of whole log page including 4 byte log page header */
+ num = num < allocLen - 4 ? num : allocLen - 4;
+ while (num >= 4) { /* header of each parameter takes 4 bytes */
+ int pc = sg_get_unaligned_be16(ucp + 0);
+ int pl = ucp[3] + 4;
+ uint64_t * ullp;
+
+ if (num < pl) /* remaining length less than a complete parameter */
+ break;
+ switch (pc) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ ecp->gotPC[pc] = 1;
+ ullp = &ecp->counter[pc];
+ break;
+ default:
+ ecp->gotExtraPC = 1;
+ ullp = &ecp->counter[7];
+ break;
+ }
+ int k = pl - 4;
+ unsigned char * xp = ucp + 4;
+ if (k > (int)sizeof(*ullp)) {
+ xp += (k - sizeof(*ullp));
+ k = sizeof(*ullp);
+ }
+ *ullp = sg_get_unaligned_be(k, xp);
+ num -= pl;
+ ucp += pl;
+ }
+}
+
+void
+scsiDecodeNonMediumErrPage(unsigned char *resp,
+ struct scsiNonMediumError *nmep,
+ int allocLen)
+{
+ memset(nmep, 0, sizeof(*nmep));
+ int num = sg_get_unaligned_be16(resp + 2);
+ unsigned char * ucp = &resp[0] + 4;
+ static int szof = sizeof(nmep->counterPC0);
+
+ /* allocLen is length of whole log page including 4 byte log page header */
+ num = num < allocLen - 4 ? num : allocLen - 4;
+ while (num >= 4) { /* header of each parameter takes 4 bytes */
+ int pc = sg_get_unaligned_be16(ucp + 0);
+ int pl = ucp[3] + 4;
+ int k;
+ unsigned char * xp;
+
+ if (num < pl) /* remaining length less than a complete parameter */
+ break;
+ switch (pc) {
+ case 0:
+ nmep->gotPC0 = 1;
+ k = pl - 4;
+ xp = ucp + 4;
+ if (k > szof) {
+ xp += (k - szof);
+ k = szof;
+ }
+ nmep->counterPC0 = sg_get_unaligned_be(k, xp + 0);
+ break;
+ case 0x8009:
+ nmep->gotTFE_H = 1;
+ k = pl - 4;
+ xp = ucp + 4;
+ if (k > szof) {
+ xp += (k - szof);
+ k = szof;
+ }
+ nmep->counterTFE_H = sg_get_unaligned_be(k, xp + 0);
+ break;
+ case 0x8015:
+ nmep->gotPE_H = 1;
+ k = pl - 4;
+ xp = ucp + 4;
+ if (k > szof) {
+ xp += (k - szof);
+ k = szof;
+ }
+ nmep->counterPE_H = sg_get_unaligned_be(k, xp + 0);
+ break;
+ default:
+ nmep->gotExtraPC = 1;
+ break;
+ }
+ num -= pl;
+ ucp += pl;
+ }
+}
+
+/* Counts number of failed self-tests. Also encodes the poweron_hour
+ of the most recent failed self-test. Return value is negative if
+ this function has a problem (typically -1), otherwise the bottom 8
+ bits are the number of failed self tests and the 16 bits above that
+ are the poweron hour of the most recent failure. Note: aborted self
+ tests (typically by the user) and self tests in progress are not
+ considered failures. See Working Draft SCSI Primary Commands - 3
+ (SPC-3) section 7.2.10 T10/1416-D (rev 22a) */
+int
+scsiCountFailedSelfTests(scsi_device * fd, int noisy)
+{
+ int num, k, err, fails, fail_hour;
+ uint8_t * ucp;
+ unsigned char resp[LOG_RESP_SELF_TEST_LEN];
+
+ if ((err = scsiLogSense(fd, SELFTEST_RESULTS_LPAGE, 0, resp,
+ LOG_RESP_SELF_TEST_LEN, 0))) {
+ if (noisy)
+ pout("scsiCountSelfTests Failed [%s]\n", scsiErrString(err));
+ return -1;
+ }
+ if ((resp[0] & 0x3f) != SELFTEST_RESULTS_LPAGE) {
+ if (noisy)
+ pout("Self-test %s Failed, page mismatch\n", logSenStr);
+ return -1;
+ }
+ // compute page length
+ num = sg_get_unaligned_be16(resp + 2);
+ // Log sense page length 0x190 bytes
+ if (num != 0x190) {
+ if (noisy)
+ pout("Self-test %s length is 0x%x not 0x190 bytes\n", logSenStr,
+ num);
+ return -1;
+ }
+ fails = 0;
+ fail_hour = 0;
+ // loop through the twenty possible entries
+ for (k = 0, ucp = resp + 4; k < 20; ++k, ucp += 20 ) {
+
+ // timestamp in power-on hours (or zero if test in progress)
+ int n = sg_get_unaligned_be16(ucp + 6);
+
+ // The spec says "all 20 bytes will be zero if no test" but
+ // DG has found otherwise. So this is a heuristic.
+ if ((0 == n) && (0 == ucp[4]))
+ break;
+ int res = ucp[4] & 0xf;
+ if ((res > 2) && (res < 8)) {
+ fails++;
+ if (1 == fails)
+ fail_hour = sg_get_unaligned_be16(ucp + 6);
+ }
+ }
+ return (fail_hour << 8) + fails;
+}
+
+/* Returns 0 if able to read self test log page; then outputs 1 into
+ *inProgress if self test still in progress, else outputs 0. */
+int
+scsiSelfTestInProgress(scsi_device * fd, int * inProgress)
+{
+ int num;
+ uint8_t * ucp;
+ unsigned char resp[LOG_RESP_SELF_TEST_LEN];
+
+ if (scsiLogSense(fd, SELFTEST_RESULTS_LPAGE, 0, resp,
+ LOG_RESP_SELF_TEST_LEN, 0))
+ return -1;
+ if (resp[0] != SELFTEST_RESULTS_LPAGE)
+ return -1;
+ // compute page length
+ num = sg_get_unaligned_be16(resp + 2);
+ // Log sense page length 0x190 bytes
+ if (num != 0x190) {
+ return -1;
+ }
+ ucp = resp + 4;
+ if (inProgress)
+ *inProgress = (0xf == (ucp[4] & 0xf)) ? 1 : 0;
+ return 0;
+}
+
+/* Returns a negative value if failed to fetch Control mode page or it was
+ malformed. Returns 0 if GLTSD bit is zero and returns 1 if the GLTSD
+ bit is set. Examines default mode page when current==0 else examines
+ current mode page. */
+int
+scsiFetchControlGLTSD(scsi_device * device, int modese_len, int current)
+{
+ int err, offset;
+ uint8_t buff[64] = {};
+ int pc = current ? MPAGE_CONTROL_CURRENT : MPAGE_CONTROL_DEFAULT;
+
+ if (modese_len <= 6) {
+ if ((err = scsiModeSense(device, CONTROL_MODE_PAGE, 0, pc,
+ buff, sizeof(buff)))) {
+ if (SIMPLE_ERR_BAD_OPCODE == err)
+ modese_len = 10;
+ else
+ return -EINVAL;
+ } else if (0 == modese_len)
+ modese_len = 6;
+ }
+ if (10 == modese_len) {
+ err = scsiModeSense10(device, CONTROL_MODE_PAGE, 0, pc,
+ buff, sizeof(buff));
+ if (err)
+ return -EINVAL;
+ }
+ offset = scsiModePageOffset(buff, sizeof(buff), modese_len);
+ if ((offset >= 0) && (buff[offset + 1] >= 0xa))
+ return (buff[offset + 2] & 2) ? 1 : 0;
+ return -EINVAL;
+}
+
+/* Returns a negative value on error, 0 if unknown and 1 if SSD,
+ * otherwise the positive returned value is the speed in rpm. First checks
+ * the Block Device Characteristics VPD page and if that fails it tries the
+ * RIGID_DISK_DRIVE_GEOMETRY_PAGE mode page.
+ * In SBC-4 the 2 bit ZONED field in this VPD page is written to *haw_zbcp
+ * if haw_zbcp is non-nullptr. In SBC-5 the ZONED field is now obsolete,
+ * the Zoned block device characteristics VPD page should be used instead. */
+
+int
+scsiGetRPM(scsi_device * device, int modese_len, int * form_factorp,
+ int * haw_zbcp)
+{
+ int err, offset;
+ uint8_t buff[64] = {};
+ int pc = MPAGE_CONTROL_DEFAULT;
+
+ if ((0 == scsiInquiryVpd(device, SCSI_VPD_BLOCK_DEVICE_CHARACTERISTICS,
+ buff, sizeof(buff))) &&
+ ((sg_get_unaligned_be16(buff + 2)) > 2)) {
+ int speed = sg_get_unaligned_be16(buff + 4);
+ if (form_factorp)
+ *form_factorp = buff[7] & 0xf;
+ if (haw_zbcp)
+ *haw_zbcp = (buff[8] >> 4) & 0x3;
+ return speed;
+ }
+ if (form_factorp)
+ *form_factorp = 0;
+ if (haw_zbcp)
+ *haw_zbcp = 0;
+ if (modese_len <= 6) {
+ if ((err = scsiModeSense(device, RIGID_DISK_DRIVE_GEOMETRY_PAGE, 0, pc,
+ buff, sizeof(buff)))) {
+ if (SIMPLE_ERR_BAD_OPCODE == err)
+ modese_len = 10;
+ else
+ return -EINVAL;
+ } else if (0 == modese_len)
+ modese_len = 6;
+ }
+ if (10 == modese_len) {
+ err = scsiModeSense10(device, RIGID_DISK_DRIVE_GEOMETRY_PAGE, 0, pc,
+ buff, sizeof(buff));
+ if (err)
+ return -EINVAL;
+ }
+ offset = scsiModePageOffset(buff, sizeof(buff), modese_len);
+ return sg_get_unaligned_be16(buff + offset + 20);
+}
+
+/* Returns a non-zero value in case of error, wcep/rcdp == -1 - get value,
+ 0 - clear bit, 1 - set bit */
+
+int
+scsiGetSetCache(scsi_device * device, int modese_len, short int * wcep,
+ short int * rcdp)
+{
+ int err, offset, resp_len, sp;
+ uint8_t buff[64] = {};
+ uint8_t ch_buff[64];
+ short set_wce = *wcep;
+ short set_rcd = *rcdp;
+
+ if (modese_len <= 6) {
+ err = scsiModeSense(device, CACHING_PAGE, 0, MPAGE_CONTROL_CURRENT,
+ buff, sizeof(buff));
+ if (err) {
+ if (SIMPLE_ERR_BAD_OPCODE == err)
+ modese_len = 10;
+ else {
+ device->set_err(EINVAL, "SCSI MODE SENSE failed");
+ return -EINVAL;
+ }
+ } else if (0 == modese_len)
+ modese_len = 6;
+ }
+
+ if (10 == modese_len) {
+ err = scsiModeSense10(device, CACHING_PAGE, 0, MPAGE_CONTROL_CURRENT,
+ buff, sizeof(buff));
+ if (err) {
+ device->set_err(EINVAL, "SCSI MODE SENSE failed");
+ return -EINVAL;
+ }
+ }
+ offset = scsiModePageOffset(buff, sizeof(buff), modese_len);
+ if ((offset < 0) || (buff[offset + 1] < 0xa)) {
+ device->set_err(EINVAL, "Bad response");
+ return SIMPLE_ERR_BAD_RESP;
+ }
+
+ *wcep = ((buff[offset + 2] & 0x04) != 0);
+ *rcdp = ((buff[offset + 2] & 0x01) != 0);
+
+ if((*wcep == set_wce || set_wce == -1)
+ && ((*rcdp == set_rcd) || set_rcd == -1))
+ return 0; // no changes needed or nothing to set
+
+ if (modese_len == 6)
+ err = scsiModeSense(device, CACHING_PAGE, 0,
+ MPAGE_CONTROL_CHANGEABLE,
+ ch_buff, sizeof(ch_buff));
+ else
+ err = scsiModeSense10(device, CACHING_PAGE, 0,
+ MPAGE_CONTROL_CHANGEABLE,
+ ch_buff, sizeof(ch_buff));
+ if (err) {
+ device->set_err(EINVAL, "WCE/RCD bits not changeable");
+ return err;
+ }
+
+ // set WCE bit
+ if(set_wce >= 0 && *wcep != set_wce) {
+ if (0 == (ch_buff[offset + 2] & 0x04)) {
+ device->set_err(EINVAL, "WCE bit not changeable");
+ return 1;
+ }
+ if(set_wce)
+ buff[offset + 2] |= 0x04; // set bit
+ else
+ buff[offset + 2] &= 0xfb; // clear bit
+ }
+ // set RCD bit
+ if(set_rcd >= 0 && *rcdp != set_rcd) {
+ if (0 == (ch_buff[offset + 2] & 0x01)) {
+ device->set_err(EINVAL, "RCD bit not changeable");
+ return 1;
+ }
+ if(set_rcd)
+ buff[offset + 2] |= 0x01; // set bit
+ else
+ buff[offset + 2] &= 0xfe; // clear bit
+ }
+
+ /* mask out DPOFUA device specific (disk) parameter bit */
+ if (10 == modese_len) {
+ resp_len = sg_get_unaligned_be16(buff + 0) + 2;
+ buff[3] &= 0xef;
+ } else {
+ resp_len = buff[0] + 1;
+ buff[2] &= 0xef;
+ }
+ sp = 0; /* Do not change saved values */
+ if (10 == modese_len)
+ err = scsiModeSelect10(device, sp, buff, resp_len);
+ else if (6 == modese_len)
+ err = scsiModeSelect(device, sp, buff, resp_len);
+ if(err)
+ device->set_err(EINVAL, "MODE SELECT command failed");
+ return err;
+}
+
+
+/* Attempts to set or clear GLTSD bit in Control mode page. If enabled is
+ 0 attempts to clear GLTSD otherwise it attempts to set it. Returns 0 if
+ successful, negative if low level error, > 0 if higher level error (e.g.
+ SIMPLE_ERR_BAD_PARAM if GLTSD bit is not changeable). */
+int
+scsiSetControlGLTSD(scsi_device * device, int enabled, int modese_len)
+{
+ int err, offset, resp_len, sp;
+ uint8_t buff[64] = {};
+ uint8_t ch_buff[64];
+
+ if (modese_len <= 6) {
+ if ((err = scsiModeSense(device, CONTROL_MODE_PAGE, 0,
+ MPAGE_CONTROL_CURRENT,
+ buff, sizeof(buff)))) {
+ if (SIMPLE_ERR_BAD_OPCODE == err)
+ modese_len = 10;
+ else
+ return err;
+ } else if (0 == modese_len)
+ modese_len = 6;
+ }
+ if (10 == modese_len) {
+ err = scsiModeSense10(device, CONTROL_MODE_PAGE, 0,
+ MPAGE_CONTROL_CURRENT,
+ buff, sizeof(buff));
+ if (err)
+ return err;
+ }
+ offset = scsiModePageOffset(buff, sizeof(buff), modese_len);
+ if ((offset < 0) || (buff[offset + 1] < 0xa))
+ return SIMPLE_ERR_BAD_RESP;
+
+ if (enabled)
+ enabled = 2;
+ if (enabled == (buff[offset + 2] & 2))
+ return 0; /* GLTSD already in wanted state so nothing to do */
+
+ if (modese_len == 6)
+ err = scsiModeSense(device, CONTROL_MODE_PAGE, 0,
+ MPAGE_CONTROL_CHANGEABLE,
+ ch_buff, sizeof(ch_buff));
+ else
+ err = scsiModeSense10(device, CONTROL_MODE_PAGE, 0,
+ MPAGE_CONTROL_CHANGEABLE,
+ ch_buff, sizeof(ch_buff));
+ if (err)
+ return err;
+ if (0 == (ch_buff[offset + 2] & 2))
+ return SIMPLE_ERR_BAD_PARAM; /* GLTSD bit not changeable */
+
+ /* mask out DPOFUA device specific (disk) parameter bit */
+ if (10 == modese_len) {
+ resp_len = sg_get_unaligned_be16(buff + 0) + 2;
+ buff[3] &= 0xef;
+ } else {
+ resp_len = buff[0] + 1;
+ buff[2] &= 0xef;
+ }
+ sp = (buff[offset] & 0x80) ? 1 : 0; /* PS bit becomes 'SELECT's SP bit */
+ if (enabled)
+ buff[offset + 2] |= 0x2; /* set GLTSD bit */
+ else
+ buff[offset + 2] &= 0xfd; /* clear GLTSD bit */
+ if (10 == modese_len)
+ err = scsiModeSelect10(device, sp, buff, resp_len);
+ else if (6 == modese_len)
+ err = scsiModeSelect(device, sp, buff, resp_len);
+ return err;
+}
+
+/* Returns a negative value if failed to fetch Protocol specific port mode
+ page or it was malformed. Returns transport protocol identifier when
+ value >= 0 . */
+int
+scsiFetchTransportProtocol(scsi_device * device, int modese_len)
+{
+ int err, offset;
+ uint8_t buff[64] {};
+
+ if (modese_len <= 6) {
+ if ((err = scsiModeSense(device, PROTOCOL_SPECIFIC_PORT_PAGE, 0,
+ MPAGE_CONTROL_CURRENT,
+ buff, sizeof(buff)))) {
+ if (SIMPLE_ERR_BAD_OPCODE == err)
+ modese_len = 10;
+ else
+ return -EINVAL;
+ } else if (0 == modese_len)
+ modese_len = 6;
+ }
+ if (10 == modese_len) {
+ err = scsiModeSense10(device, PROTOCOL_SPECIFIC_PORT_PAGE, 0,
+ MPAGE_CONTROL_CURRENT,
+ buff, sizeof(buff));
+ if (err)
+ return -EINVAL;
+ }
+ offset = scsiModePageOffset(buff, sizeof(buff), modese_len);
+ if ((offset >= 0) && (buff[offset + 1] > 1)) {
+ if ((0 == (buff[offset] & 0x40)) && /* SPF==0 */
+ (PROTOCOL_SPECIFIC_PORT_PAGE == (buff[offset] & 0x3f)))
+ return (buff[offset + 2] & 0xf);
+ }
+ return -EINVAL;
+}
+
+const unsigned char *
+sg_scsi_sense_desc_find(const unsigned char * sensep, int sense_len,
+ int desc_type)
+{
+ int add_sen_len;
+ const unsigned char * descp;
+
+ if ((sense_len < 8) || (0 == (add_sen_len = sensep[7])))
+ return nullptr;
+ if ((sensep[0] < 0x72) || (sensep[0] > 0x73))
+ return nullptr;
+ add_sen_len = (add_sen_len < (sense_len - 8)) ?
+ add_sen_len : (sense_len - 8);
+ descp = &sensep[8];
+ for (int desc_len = 0, k = 0; k < add_sen_len; k += desc_len) {
+ descp += desc_len;
+ int add_len = (k < (add_sen_len - 1)) ? descp[1]: -1;
+ desc_len = add_len + 2;
+ if (descp[0] == desc_type)
+ return descp;
+ if (add_len < 0) /* short descriptor ?? */
+ break;
+ }
+ return nullptr;
+}
+
+// Convenience function for formatting strings from SCSI identify
+void
+scsi_format_id_string(char * out, const uint8_t * in, int n)
+{
+ char tmp[65];
+ n = n > 64 ? 64 : n;
+ strncpy(tmp, (const char *)in, n);
+ tmp[n] = '\0';
+
+ // Find the first non-space character (maybe none).
+ int first = -1;
+ int i;
+ for (i = 0; tmp[i]; i++)
+ if (!isspace((int)tmp[i])) {
+ first = i;
+ break;
+ }
+
+ if (first == -1) {
+ // There are only space characters.
+ out[0] = '\0';
+ return;
+ }
+
+ // Find the last non-space character.
+ for (i = strlen(tmp)-1; i >= first && isspace((int)tmp[i]); i--);
+ int last = i;
+
+ strncpy(out, tmp+first, last-first+1);
+ out[last-first+1] = '\0';
+}
+
+static const char * wn = "Warning";
+
+static const char * wn1_9[] = {
+ "specified temperature exceeded",
+ "enclosure degraded",
+ "background self-test failed",
+ "background pre-scan detected medium error",
+ "background medium scan detected medium error",
+ "non-volatile cache now volatile",
+ "degraded power to non-volatile cache",
+ "power loss expected",
+ "device statistics notification active",
+};
+
+static const char * five_d_t[] = {
+ "Hardware",
+ "Controller",
+ "Data channel",
+ "Servo",
+ "Spindle",
+ "Firmware",
+};
+
+static const char * impfail = "impending failure";
+
+static const char * impending0_c[] = {
+ "general hard drive failure",
+ "drive error rate too high",
+ "data error rate too high",
+ "seek error rate too high",
+ "too many block reassigns",
+ "access times too high",
+ "start unit times too high",
+ "channel parametrics",
+ "controller detected",
+ "throughput performance",
+ "seek time performance",
+ "spin-up retry count",
+ "drive calibration retry count",
+};
+
+static const char * pred = "prediction threshold exceeded";
+
+/* The SCSI Informational Exceptions log page and various other mechanisms
+ * yield an additional sense code (and its qualifier) [asc and ascq] when
+ * triggered. It seems only two asc values are involved: 0xb and 0xd.
+ * If asc,ascq strings are known (in spc6r06.pdf) for asc 0xb and 0x5d
+ * then a pointer to that string is returned, else nullptr is returned. The
+ * caller provides a buffer (b) and its length (blen) that a string (if
+ * found) is placed in. So if a match is found b is returned. */
+char *
+scsiGetIEString(uint8_t asc, uint8_t ascq, char * b, int blen)
+{
+ if (asc == 0xb) {
+ switch (ascq) {
+ case 0:
+ snprintf(b, blen, "%s", wn);
+ return b;
+ case 0x1:
+ case 0x2:
+ case 0x3:
+ case 0x4:
+ case 0x5:
+ case 0x6:
+ case 0x7:
+ case 0x8:
+ case 0x9:
+ snprintf(b, blen, "%s - %s", wn, wn1_9[ascq - 1]);
+ return b;
+ case 0x12:
+ snprintf(b, blen, "%s - microcode security at risk", wn);
+ return b;
+ case 0x13:
+ snprintf(b, blen, "%s - microcode digital signature validation "
+ "failure", wn);
+ return b;
+ case 0x14:
+ snprintf(b, blen, "%s - physical element status change", wn);
+ return b;
+ default:
+ if ((ascq >= 0xa) && (ascq <= 0x11)) {
+ uint8_t q = ascq - 0xa;
+
+ snprintf(b, blen, "%s - %s %s %s limit exceeded", wn,
+ (((q % 2) == 0) ? "high" : "low"),
+ ((((q / 2) % 2) == 0) ? "critical" : "operating"),
+ ((((q / 4) % 2) == 0) ? "temperature" : "humidity"));
+ return b;
+ } else
+ return nullptr;
+ }
+ } else if (asc == 0x5d) {
+ switch (ascq) {
+ case 0:
+ snprintf(b, blen, "Failure %s", pred);
+ return b;
+ case 1:
+ snprintf(b, blen, "Media failure %s", pred);
+ return b;
+ case 2:
+ snprintf(b, blen, "Logical unit failure %s", pred);
+ return b;
+ case 3:
+ snprintf(b, blen, "spare area exhaustion failure %s", pred);
+ return b;
+ case 0x1d:
+ snprintf(b, blen, "%s %s power loss protection circuit area "
+ "exhaustion failure", five_d_t[0], impfail);
+ return b;
+ case 0x73:
+ snprintf(b, blen, "Media %s endurance limit met", impfail);
+ return b;
+ case 0xff:
+ snprintf(b, blen, "Failure %s (false)", pred);
+ return b;
+ default:
+ if ((ascq >= 0x10) && (ascq <= 0x6c)) {
+ uint8_t q = ascq - 0x10;
+ uint8_t rem = q % 0x10;
+
+ if (rem <= 0xc) {
+ snprintf(b, blen, "%s %s %s", five_d_t[q / 0x10], impfail,
+ impending0_c[rem]);
+ return b;
+ } else
+ return nullptr;
+ } else
+ return nullptr;
+ }
+ } else
+ return nullptr;
+}