smartmontools/smartmontools/scsicmds.cpp

3277 lines
114 KiB
C++
Raw Permalink Normal View History

/*
* 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_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;