/* * Copyright (c) 1999-2018 Douglas Gilbert. * All rights reserved. * Use of this source code is governed by a BSD-style * license that can be found in the BSD_LICENSE file. */ /* NOTICE: * On 5th October 2004 (v1.00) this file name was changed from sg_err.c * to sg_lib.c and the previous GPL was changed to a FreeBSD license. * The intention is to maintain this file and the related sg_lib.h file * as open source and encourage their unencumbered use. * * CONTRIBUTIONS: * This file started out as a copy of SCSI opcodes, sense keys and * additional sense codes (ASC/ASCQ) kept in the Linux SCSI subsystem * in the kernel source file: drivers/scsi/constant.c . That file * bore this notice: "Copyright (C) 1993, 1994, 1995 Eric Youngdale" * and a GPL notice. * * Much of the data in this file is derived from SCSI draft standards * found at http://www.t10.org with the "SCSI Primary Commands-4" (SPC-4) * being the central point of reference. * * Contributions: * sense key specific field decoding [Trent Piepho 20031116] * */ #define _POSIX_C_SOURCE 200809L /* for posix_memalign() */ #define __STDC_FORMAT_MACROS 1 #include #include #include #include #include #include #include #include #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "sg_lib.h" #include "sg_lib_data.h" #include "sg_unaligned.h" #include "sg_pr2serr.h" /* sg_lib_version_str (and datestamp) defined in sg_lib_data.c file */ #define ASCQ_ATA_PT_INFO_AVAILABLE 0x1d /* corresponding ASC is 0 */ FILE * sg_warnings_strm = NULL; /* would like to default to stderr */ #if defined(__GNUC__) || defined(__clang__) static int pr2ws(const char * fmt, ...) __attribute__ ((format (printf, 1, 2))); #else static int pr2ws(const char * fmt, ...); #endif static int pr2ws(const char * fmt, ...) { va_list args; int n; va_start(args, fmt); n = vfprintf(sg_warnings_strm ? sg_warnings_strm : stderr, fmt, args); va_end(args); return n; } #if defined(__GNUC__) || defined(__clang__) static int scnpr(char * cp, int cp_max_len, const char * fmt, ...) __attribute__ ((format (printf, 3, 4))); #else static int scnpr(char * cp, int cp_max_len, const char * fmt, ...); #endif /* Want safe, 'n += snprintf(b + n, blen - n, ...)' style sequence of * functions. Returns number of chars placed in cp excluding the * trailing null char. So for cp_max_len > 0 the return value is always * < cp_max_len; for cp_max_len <= 1 the return value is 0 and no chars are * written to cp. Note this means that when cp_max_len = 1, this function * assumes that cp[0] is the null character and does nothing (and returns * 0). Linux kernel has a similar function called scnprintf(). */ static int scnpr(char * cp, int cp_max_len, const char * fmt, ...) { va_list args; int n; if (cp_max_len < 2) return 0; va_start(args, fmt); n = vsnprintf(cp, cp_max_len, fmt, args); va_end(args); return (n < cp_max_len) ? n : (cp_max_len - 1); } /* Simple ASCII printable (does not use locale), includes space and excludes * DEL (0x7f). */ static inline int my_isprint(int ch) { return ((ch >= ' ') && (ch < 0x7f)); } /* Searches 'arr' for match on 'value' then 'peri_type'. If matches 'value' but not 'peri_type' then yields first 'value' match entry. Last element of 'arr' has NULL 'name'. If no match returns NULL. */ static const struct sg_lib_value_name_t * get_value_name(const struct sg_lib_value_name_t * arr, int value, int peri_type) { const struct sg_lib_value_name_t * vp = arr; const struct sg_lib_value_name_t * holdp; if (peri_type < 0) peri_type = 0; for (; vp->name; ++vp) { if (value == vp->value) { if (peri_type == vp->peri_dev_type) return vp; holdp = vp; while ((vp + 1)->name && (value == (vp + 1)->value)) { ++vp; if (peri_type == vp->peri_dev_type) return vp; } return holdp; } } return NULL; } /* If this function is not called, sg_warnings_strm will be NULL and all users * (mainly fprintf() ) need to check and substitute stderr as required */ void sg_set_warnings_strm(FILE * warnings_strm) { sg_warnings_strm = warnings_strm; } #define CMD_NAME_LEN 128 void sg_print_command(const unsigned char * command) { int k, sz; char buff[CMD_NAME_LEN]; sg_get_command_name(command, 0, CMD_NAME_LEN, buff); buff[CMD_NAME_LEN - 1] = '\0'; pr2ws("%s [", buff); if (SG_VARIABLE_LENGTH_CMD == command[0]) sz = command[7] + 8; else sz = sg_get_command_size(command[0]); for (k = 0; k < sz; ++k) pr2ws("%02x ", command[k]); pr2ws("]\n"); } void sg_get_scsi_status_str(int scsi_status, int buff_len, char * buff) { const char * ccp = NULL; bool unknown = false; if ((NULL == buff) || (buff_len < 1)) return; else if (1 == buff_len) { buff[0] = '\0'; return; } scsi_status &= 0x7e; /* sanitize as much as possible */ switch (scsi_status) { case 0: ccp = "Good"; break; case 0x2: ccp = "Check Condition"; break; case 0x4: ccp = "Condition Met"; break; case 0x8: ccp = "Busy"; break; case 0x10: ccp = "Intermediate (obsolete)"; break; case 0x14: ccp = "Intermediate-Condition Met (obsolete)"; break; case 0x18: ccp = "Reservation Conflict"; break; case 0x22: ccp = "Command Terminated (obsolete)"; break; case 0x28: ccp = "Task set Full"; break; case 0x30: ccp = "ACA Active"; break; case 0x40: ccp = "Task Aborted"; break; default: unknown = true; break; } if (unknown) scnpr(buff, buff_len, "Unknown status [0x%x]", scsi_status); else scnpr(buff, buff_len, "%s", ccp); } void sg_print_scsi_status(int scsi_status) { char buff[128]; sg_get_scsi_status_str(scsi_status, sizeof(buff) - 1, buff); buff[sizeof(buff) - 1] = '\0'; pr2ws("%s ", buff); } /* Get sense key from sense buffer. If successful returns a sense key value * between 0 and 15. If sense buffer cannot be decode, returns -1 . */ int sg_get_sense_key(const unsigned char * sbp, int sb_len) { if ((NULL == sbp) || (sb_len < 2)) return -1; switch (sbp[0] & 0x7f) { case 0x70: case 0x71: return (sb_len < 3) ? -1 : (sbp[2] & 0xf); case 0x72: case 0x73: return sbp[1] & 0xf; default: return -1; } } /* Yield string associated with sense_key value. Returns 'buff'. */ char * sg_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)) scnpr(buff, buff_len, "%s", sg_lib_sense_key_desc[sense_key]); else scnpr(buff, buff_len, "invalid value: 0x%x", sense_key); return buff; } /* Yield string associated with ASC/ASCQ values. Returns 'buff'. */ char * sg_get_asc_ascq_str(int asc, int ascq, int buff_len, char * buff) { int k, num, rlen; bool found = false; struct sg_lib_asc_ascq_t * eip; struct sg_lib_asc_ascq_range_t * ei2p; if (1 == buff_len) { buff[0] = '\0'; return buff; } for (k = 0; sg_lib_asc_ascq_range[k].text; ++k) { ei2p = &sg_lib_asc_ascq_range[k]; if ((ei2p->asc == asc) && (ascq >= ei2p->ascq_min) && (ascq <= ei2p->ascq_max)) { found = true; num = scnpr(buff, buff_len, "Additional sense: "); rlen = buff_len - num; scnpr(buff + num, ((rlen > 0) ? rlen : 0), ei2p->text, ascq); } } if (found) return buff; for (k = 0; sg_lib_asc_ascq[k].text; ++k) { eip = &sg_lib_asc_ascq[k]; if (eip->asc == asc && eip->ascq == ascq) { found = true; scnpr(buff, buff_len, "Additional sense: %s", eip->text); } } if (! found) { if (asc >= 0x80) scnpr(buff, buff_len, "vendor specific ASC=%02x, ASCQ=%02x " "(hex)", asc, ascq); else if (ascq >= 0x80) scnpr(buff, buff_len, "ASC=%02x, vendor specific qualification " "ASCQ=%02x (hex)", asc, ascq); else scnpr(buff, buff_len, "ASC=%02x, ASCQ=%02x (hex)", asc, ascq); } return buff; } /* Attempt to find the first SCSI sense data descriptor that matches the * given 'desc_type'. If found return pointer to start of sense data * descriptor; otherwise (including fixed format sense data) returns NULL. */ const unsigned char * sg_scsi_sense_desc_find(const unsigned char * sbp, int sb_len, int desc_type) { int add_sb_len, add_d_len, desc_len, k; const unsigned char * descp; if ((sb_len < 8) || (0 == (add_sb_len = sbp[7]))) return NULL; if ((sbp[0] < 0x72) || (sbp[0] > 0x73)) return NULL; add_sb_len = (add_sb_len < (sb_len - 8)) ? add_sb_len : (sb_len - 8); descp = &sbp[8]; for (desc_len = 0, k = 0; k < add_sb_len; k += desc_len) { descp += desc_len; add_d_len = (k < (add_sb_len - 1)) ? descp[1]: -1; desc_len = add_d_len + 2; if (descp[0] == desc_type) return descp; if (add_d_len < 0) /* short descriptor ?? */ break; } return NULL; } /* Returns true if valid bit set, false if valid bit clear. Irrespective the * information field is written out via 'info_outp' (except when it is * NULL). Handles both fixed and descriptor sense formats. */ bool sg_get_sense_info_fld(const unsigned char * sbp, int sb_len, uint64_t * info_outp) { const unsigned char * bp; uint64_t ull; if (info_outp) *info_outp = 0; if (sb_len < 7) return false; switch (sbp[0] & 0x7f) { case 0x70: case 0x71: if (info_outp) *info_outp = sg_get_unaligned_be32(sbp + 3); return !!(sbp[0] & 0x80); case 0x72: case 0x73: bp = sg_scsi_sense_desc_find(sbp, sb_len, 0 /* info desc */); if (bp && (0xa == bp[1])) { ull = sg_get_unaligned_be64(bp + 4); if (info_outp) *info_outp = ull; return !!(bp[2] & 0x80); /* since spc3r23 should be set */ } else return false; default: return false; } } /* Returns true if fixed format or command specific information descriptor * is found in the descriptor sense; else false. If available the command * specific information field (4 byte integer in fixed format, 8 byte * integer in descriptor format) is written out via 'cmd_spec_outp'. * Handles both fixed and descriptor sense formats. */ bool sg_get_sense_cmd_spec_fld(const unsigned char * sbp, int sb_len, uint64_t * cmd_spec_outp) { const unsigned char * bp; if (cmd_spec_outp) *cmd_spec_outp = 0; if (sb_len < 7) return false; switch (sbp[0] & 0x7f) { case 0x70: case 0x71: if (cmd_spec_outp) *cmd_spec_outp = sg_get_unaligned_be32(sbp + 8); return true; case 0x72: case 0x73: bp = sg_scsi_sense_desc_find(sbp, sb_len, 1 /* command specific info desc */); if (bp && (0xa == bp[1])) { if (cmd_spec_outp) *cmd_spec_outp = sg_get_unaligned_be64(bp + 4); return true; } else return false; default: return false; } } /* Returns true if any of the 3 bits (i.e. FILEMARK, EOM or ILI) are set. * In descriptor format if the stream commands descriptor not found * then returns false. Writes true or false corresponding to these bits to * the last three arguments if they are non-NULL. */ bool sg_get_sense_filemark_eom_ili(const unsigned char * sbp, int sb_len, bool * filemark_p, bool * eom_p, bool * ili_p) { const unsigned char * bp; if (sb_len < 7) return false; switch (sbp[0] & 0x7f) { case 0x70: case 0x71: if (sbp[2] & 0xe0) { if (filemark_p) *filemark_p = !!(sbp[2] & 0x80); if (eom_p) *eom_p = !!(sbp[2] & 0x40); if (ili_p) *ili_p = !!(sbp[2] & 0x20); return true; } else return false; case 0x72: case 0x73: /* Look for stream commands sense data descriptor */ bp = sg_scsi_sense_desc_find(sbp, sb_len, 4); if (bp && (bp[1] >= 2)) { if (bp[3] & 0xe0) { if (filemark_p) *filemark_p = !!(bp[3] & 0x80); if (eom_p) *eom_p = !!(bp[3] & 0x40); if (ili_p) *ili_p = !!(bp[3] & 0x20); return true; } } return false; default: return false; } } /* Returns true if SKSV is set and sense key is NO_SENSE or NOT_READY. Also * returns true if progress indication sense data descriptor found. Places * progress field from sense data where progress_outp points. If progress * field is not available returns false and *progress_outp is unaltered. * Handles both fixed and descriptor sense formats. * Hint: if true is returned *progress_outp may be multiplied by 100 then * divided by 65536 to get the percentage completion. */ bool sg_get_sense_progress_fld(const unsigned char * sbp, int sb_len, int * progress_outp) { const unsigned char * bp; int sk, sk_pr; if (sb_len < 7) return false; switch (sbp[0] & 0x7f) { case 0x70: case 0x71: sk = (sbp[2] & 0xf); if ((sb_len < 18) || ((SPC_SK_NO_SENSE != sk) && (SPC_SK_NOT_READY != sk))) return false; if (sbp[15] & 0x80) { /* SKSV bit set */ if (progress_outp) *progress_outp = sg_get_unaligned_be16(sbp + 16); return true; } else return false; case 0x72: case 0x73: /* sense key specific progress (0x2) or progress descriptor (0xa) */ sk = (sbp[1] & 0xf); sk_pr = (SPC_SK_NO_SENSE == sk) || (SPC_SK_NOT_READY == sk); if (sk_pr && ((bp = sg_scsi_sense_desc_find(sbp, sb_len, 2))) && (0x6 == bp[1]) && (0x80 & bp[4])) { if (progress_outp) *progress_outp = sg_get_unaligned_be16(bp + 5); return true; } else if (((bp = sg_scsi_sense_desc_find(sbp, sb_len, 0xa))) && ((0x6 == bp[1]))) { if (progress_outp) *progress_outp = sg_get_unaligned_be16(bp + 6); return true; } else return false; default: return false; } } char * sg_get_pdt_str(int pdt, int buff_len, char * buff) { if ((pdt < 0) || (pdt > 31)) scnpr(buff, buff_len, "bad pdt"); else scnpr(buff, buff_len, "%s", sg_lib_pdt_strs[pdt]); return buff; } int sg_lib_pdt_decay(int pdt) { if ((pdt < 0) || (pdt > 31)) return 0; return sg_lib_pdt_decay_arr[pdt]; } char * sg_get_trans_proto_str(int tpi, int buff_len, char * buff) { if ((tpi < 0) || (tpi > 15)) scnpr(buff, buff_len, "bad tpi"); else scnpr(buff, buff_len, "%s", sg_lib_transport_proto_strs[tpi]); return buff; } #define TRANSPORT_ID_MIN_LEN 24 char * sg_decode_transportid_str(const char * lip, unsigned char * bp, int bplen, bool only_one, int blen, char * b) { int proto_id, num, k, n, normal_len, tpid_format; uint64_t ull; int bump; if ((NULL == b) || (blen < 1)) return b; else if (1 == blen) { b[0] = '\0'; return b; } if (NULL == lip) lip = ""; bump = TRANSPORT_ID_MIN_LEN; /* should be overwritten in all loop paths */ for (k = 0, n = 0; bplen > 0; ++k, bp += bump, bplen -= bump) { if ((k > 0) && only_one) break; if ((bplen < 24) || (0 != (bplen % 4))) n += scnpr(b + n, blen - n, "%sTransport Id short or not " "multiple of 4 [length=%d]:\n", lip, blen); else n += scnpr(b + n, blen - n, "%sTransport Id of initiator:\n", lip); tpid_format = ((bp[0] >> 6) & 0x3); proto_id = (bp[0] & 0xf); normal_len = (bplen > TRANSPORT_ID_MIN_LEN) ? TRANSPORT_ID_MIN_LEN : bplen; switch (proto_id) { case TPROTO_FCP: /* Fibre channel */ n += scnpr(b + n, blen - n, "%s FCP-2 World Wide Name:\n", lip); if (0 != tpid_format) n += scnpr(b + n, blen - n, "%s [Unexpected TPID format: " "%d]\n", lip, tpid_format); n += hex2str(bp + 8, 8, lip, 1, blen - n, b + n); bump = TRANSPORT_ID_MIN_LEN; break; case TPROTO_SPI: /* Scsi Parallel Interface, obsolete */ n += scnpr(b + n, blen - n, "%s Parallel SCSI initiator SCSI " "address: 0x%x\n", lip, sg_get_unaligned_be16(bp + 2)); if (0 != tpid_format) n += scnpr(b + n, blen - n, "%s [Unexpected TPID format: " "%d]\n", lip, tpid_format); n += scnpr(b + n, blen - n, "%s relative port number (of " "corresponding target): 0x%x\n", lip, sg_get_unaligned_be16(bp + 6)); bump = TRANSPORT_ID_MIN_LEN; break; case TPROTO_SSA: n += scnpr(b + n, blen - n, "%s SSA (transport id not " "defined):\n", lip); n += scnpr(b + n, blen - n, "%s TPID format: %d\n", lip, tpid_format); n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); bump = TRANSPORT_ID_MIN_LEN; break; case TPROTO_1394: /* IEEE 1394 */ n += scnpr(b + n, blen - n, "%s IEEE 1394 EUI-64 name:\n", lip); if (0 != tpid_format) n += scnpr(b + n, blen - n, "%s [Unexpected TPID format: " "%d]\n", lip, tpid_format); n += hex2str(&bp[8], 8, lip, 1, blen - n, b + n); bump = TRANSPORT_ID_MIN_LEN; break; case TPROTO_SRP: /* SCSI over RDMA */ n += scnpr(b + n, blen - n, "%s RDMA initiator port " "identifier:\n", lip); if (0 != tpid_format) n += scnpr(b + n, blen - n, "%s [Unexpected TPID format: " "%d]\n", lip, tpid_format); n += hex2str(bp + 8, 16, lip, 1, blen - n, b + n); bump = TRANSPORT_ID_MIN_LEN; break; case TPROTO_ISCSI: n += scnpr(b + n, blen - n, "%s iSCSI ", lip); num = sg_get_unaligned_be16(bp + 2); if (0 == tpid_format) n += scnpr(b + n, blen - n, "name: %.*s\n", num, &bp[4]); else if (1 == tpid_format) n += scnpr(b + n, blen - n, "world wide unique port id: " "%.*s\n", num, &bp[4]); else { n += scnpr(b + n, blen - n, " [Unexpected TPID format: " "%d]\n", tpid_format); n += hex2str(bp, num + 4, lip, 0, blen - n, b + n); } bump = (((num + 4) < TRANSPORT_ID_MIN_LEN) ? TRANSPORT_ID_MIN_LEN : num + 4); break; case TPROTO_SAS: ull = sg_get_unaligned_be64(bp + 4); n += scnpr(b + n, blen - n, "%s SAS address: 0x%" PRIx64 "\n", lip, ull); if (0 != tpid_format) n += scnpr(b + n, blen - n, "%s [Unexpected TPID format: " "%d]\n", lip, tpid_format); bump = TRANSPORT_ID_MIN_LEN; break; case TPROTO_ADT: /* no TransportID defined by T10 yet */ n += scnpr(b + n, blen - n, "%s ADT:\n", lip); n += scnpr(b + n, blen - n, "%s TPID format: %d\n", lip, tpid_format); n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); bump = TRANSPORT_ID_MIN_LEN; break; case TPROTO_ATA: /* no TransportID defined by T10 yet */ n += scnpr(b + n, blen - n, "%s ATAPI:\n", lip); n += scnpr(b + n, blen - n, "%s TPID format: %d\n", lip, tpid_format); n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); bump = TRANSPORT_ID_MIN_LEN; break; case TPROTO_UAS: /* no TransportID defined by T10 yet */ n += scnpr(b + n, blen - n, "%s UAS:\n", lip); n += scnpr(b + n, blen - n, "%s TPID format: %d\n", lip, tpid_format); n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); bump = TRANSPORT_ID_MIN_LEN; break; case TPROTO_SOP: n += scnpr(b + n, blen - n, "%s SOP ", lip); num = sg_get_unaligned_be16(bp + 2); if (0 == tpid_format) n += scnpr(b + n, blen - n, "Routing ID: 0x%x\n", num); else { n += scnpr(b + n, blen - n, " [Unexpected TPID format: " "%d]\n", tpid_format); n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); } bump = TRANSPORT_ID_MIN_LEN; break; case TPROTO_PCIE: /* no TransportID defined by T10 yet */ n += scnpr(b + n, blen - n, "%s PCIE:\n", lip); n += scnpr(b + n, blen - n, "%s TPID format: %d\n", lip, tpid_format); n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); bump = TRANSPORT_ID_MIN_LEN; break; case TPROTO_NONE: /* no TransportID defined by T10 */ n += scnpr(b + n, blen - n, "%s No specified protocol\n", lip); /* n += hex2str(bp, ((bplen > 24) ? 24 : bplen), * lip, 0, blen - n, b + n); */ bump = TRANSPORT_ID_MIN_LEN; break; default: n += scnpr(b + n, blen - n, "%s unknown protocol id=0x%x " "TPID format=%d\n", lip, proto_id, tpid_format); n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); bump = TRANSPORT_ID_MIN_LEN; break; } } return b; } static const char * desig_code_set_str_arr[] = { "Reserved [0x0]", "Binary", "ASCII", "UTF-8", "Reserved [0x4]", "Reserved [0x5]", "Reserved [0x6]", "Reserved [0x7]", "Reserved [0x8]", "Reserved [0x9]", "Reserved [0xa]", "Reserved [0xb]", "Reserved [0xc]", "Reserved [0xd]", "Reserved [0xe]", "Reserved [0xf]", }; const char * sg_get_desig_code_set_str(int val) { if ((val >= 0) && (val < 16)) return desig_code_set_str_arr[val]; else return NULL; } static const char * desig_assoc_str_arr[] = { "Addressed logical unit", "Target port", /* that received request; unless SCSI ports VPD */ "Target device that contains addressed lu", "Reserved [0x3]", }; const char * sg_get_desig_assoc_str(int val) { if ((val >= 0) && (val < 4)) return desig_assoc_str_arr[val]; else return NULL; } static const char * desig_type_str_arr[] = { "vendor specific [0x0]", "T10 vendor identification", "EUI-64 based", "NAA", "Relative target port", "Target port group", /* spc4r09: _primary_ target port group */ "Logical unit group", "MD5 logical unit identifier", "SCSI name string", "Protocol specific port identifier", /* spc4r36 */ "UUID identifier", /* spc5r08 */ "Reserved [0xb]", "Reserved [0xc]", "Reserved [0xd]", "Reserved [0xe]", "Reserved [0xf]", }; const char * sg_get_desig_type_str(int val) { if ((val >= 0) && (val < 16)) return desig_type_str_arr[val]; else return NULL; } int sg_get_designation_descriptor_str(const char * lip, const unsigned char * ddp, int dd_len, bool print_assoc, bool do_long, int blen, char * b) { int m, p_id, piv, c_set, assoc, desig_type, ci_off, c_id, d_id, naa; int vsi, k, n, dlen; const unsigned char * ip; uint64_t vsei; uint64_t id_ext; char e[64]; const char * cp; n = 0; if (NULL == lip) lip = ""; if (dd_len < 4) { n += scnpr(b + n, blen - n, "%sdesignator desc too short: got " "length of %d want 4 or more\n", lip, dd_len); return n; } dlen = ddp[3]; if (dlen > (dd_len - 4)) { n += scnpr(b + n, blen - n, "%sdesignator too long: says it is %d " "bytes, but given %d bytes\n", lip, dlen, dd_len - 4); return n; } ip = ddp + 4; p_id = ((ddp[0] >> 4) & 0xf); c_set = (ddp[0] & 0xf); piv = ((ddp[1] & 0x80) ? 1 : 0); assoc = ((ddp[1] >> 4) & 0x3); desig_type = (ddp[1] & 0xf); if (print_assoc && ((cp = sg_get_desig_assoc_str(assoc)))) n += scnpr(b + n, blen - n, "%s %s:\n", lip, cp); n += scnpr(b + n, blen - n, "%s designator type: ", lip); cp = sg_get_desig_type_str(desig_type); if (cp) n += scnpr(b + n, blen - n, "%s", cp); n += scnpr(b + n, blen - n, ", code set: "); cp = sg_get_desig_code_set_str(c_set); if (cp) n += scnpr(b + n, blen - n, "%s", cp); n += scnpr(b + n, blen - n, "\n"); if (piv && ((1 == assoc) || (2 == assoc))) n += scnpr(b + n, blen - n, "%s transport: %s\n", lip, sg_get_trans_proto_str(p_id, sizeof(e), e)); /* printf(" associated with the %s\n", sdparm_assoc_arr[assoc]); */ switch (desig_type) { case 0: /* vendor specific */ k = 0; if ((1 == c_set) || (2 == c_set)) { /* ASCII or UTF-8 */ for (k = 0; (k < dlen) && my_isprint(ip[k]); ++k) ; if (k >= dlen) k = 1; } if (k) n += scnpr(b + n, blen - n, "%s vendor specific: %.*s\n", lip, dlen, ip); else { n += scnpr(b + n, blen - n, "%s vendor specific:\n", lip); n += hex2str(ip, dlen, lip, 0, blen - n, b + n); } break; case 1: /* T10 vendor identification */ n += scnpr(b + n, blen - n, "%s vendor id: %.8s\n", lip, ip); if (dlen > 8) { if ((2 == c_set) || (3 == c_set)) { /* ASCII or UTF-8 */ n += scnpr(b + n, blen - n, "%s vendor specific: " "%.*s\n", lip, dlen - 8, ip + 8); } else { n += scnpr(b + n, blen - n, "%s vendor specific: 0x", lip); for (m = 8; m < dlen; ++m) n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); n += scnpr(b + n, blen - n, "\n"); } } break; case 2: /* EUI-64 based */ if (! do_long) { if ((8 != dlen) && (12 != dlen) && (16 != dlen)) { n += scnpr(b + n, blen - n, "%s << expect 8, 12 and 16 " "byte EUI, got %d >>\n", lip, dlen); n += hex2str(ip, dlen, lip, 1, blen - n, b + n); break; } n += scnpr(b + n, blen - n, "%s 0x", lip); for (m = 0; m < dlen; ++m) n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); n += scnpr(b + n, blen - n, "\n"); break; } n += scnpr(b + n, blen - n, "%s EUI-64 based %d byte " "identifier\n", lip, dlen); if (1 != c_set) { n += scnpr(b + n, blen - n, "%s << expected binary code_set " "(1) >>\n", lip); n += hex2str(ip, dlen, lip, 1, blen - n, b + n); break; } ci_off = 0; if (16 == dlen) { ci_off = 8; id_ext = sg_get_unaligned_be64(ip); n += scnpr(b + n, blen - n, "%s Identifier extension: 0x%" PRIx64 "\n", lip, id_ext); } else if ((8 != dlen) && (12 != dlen)) { n += scnpr(b + n, blen - n, "%s << can only decode 8, 12 " "and 16 byte ids >>\n", lip); n += hex2str(ip, dlen, lip, 1, blen - n, b + n); break; } c_id = sg_get_unaligned_be24(ip + ci_off); n += scnpr(b + n, blen - n, "%s IEEE Company_id: 0x%x\n", lip, c_id); vsei = 0; for (m = 0; m < 5; ++m) { if (m > 0) vsei <<= 8; vsei |= ip[ci_off + 3 + m]; } n += scnpr(b + n, blen - n, "%s Vendor Specific Extension " "Identifier: 0x%" PRIx64 "\n", lip, vsei); if (12 == dlen) { d_id = sg_get_unaligned_be32(ip + 8); n += scnpr(b + n, blen - n, "%s Directory ID: 0x%x\n", lip, d_id); } break; case 3: /* NAA */ if (1 != c_set) { n += scnpr(b + n, blen - n, "%s << unexpected code set %d " "for NAA >>\n", lip, c_set); n += hex2str(ip, dlen, lip, 1, blen - n, b + n); break; } naa = (ip[0] >> 4) & 0xff; switch (naa) { case 2: /* NAA 2: IEEE Extended */ if (8 != dlen) { n += scnpr(b + n, blen - n, "%s << unexpected NAA 2 " "identifier length: 0x%x >>\n", lip, dlen); n += hex2str(ip, dlen, lip, 1, blen - n, b + n); break; } d_id = (((ip[0] & 0xf) << 8) | ip[1]); c_id = sg_get_unaligned_be24(ip + 2); vsi = sg_get_unaligned_be24(ip + 5); if (do_long) { n += scnpr(b + n, blen - n, "%s NAA 2, vendor specific " "identifier A: 0x%x\n", lip, d_id); n += scnpr(b + n, blen - n, "%s IEEE Company_id: 0x%x\n", lip, c_id); n += scnpr(b + n, blen - n, "%s vendor specific " "identifier B: 0x%x\n", lip, vsi); n += scnpr(b + n, blen - n, "%s [0x", lip); for (m = 0; m < 8; ++m) n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); n += scnpr(b + n, blen - n, "]\n"); } n += scnpr(b + n, blen - n, "%s 0x", lip); for (m = 0; m < 8; ++m) n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); n += scnpr(b + n, blen - n, "\n"); break; case 3: /* NAA 3: Locally assigned */ if (8 != dlen) { n += scnpr(b + n, blen - n, "%s << unexpected NAA 3 " "identifier length: 0x%x >>\n", lip, dlen); n += hex2str(ip, dlen, lip, 1, blen - n, b + n); break; } if (do_long) n += scnpr(b + n, blen - n, "%s NAA 3, Locally " "assigned:\n", lip); n += scnpr(b + n, blen - n, "%s 0x", lip); for (m = 0; m < 8; ++m) n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); n += scnpr(b + n, blen - n, "\n"); break; case 5: /* NAA 5: IEEE Registered */ if (8 != dlen) { n += scnpr(b + n, blen - n, "%s << unexpected NAA 5 " "identifier length: 0x%x >>\n", lip, dlen); n += hex2str(ip, dlen, lip, 1, blen - n, b + n); break; } c_id = (((ip[0] & 0xf) << 20) | (ip[1] << 12) | (ip[2] << 4) | ((ip[3] & 0xf0) >> 4)); vsei = ip[3] & 0xf; for (m = 1; m < 5; ++m) { vsei <<= 8; vsei |= ip[3 + m]; } if (do_long) { n += scnpr(b + n, blen - n, "%s NAA 5, IEEE " "Company_id: 0x%x\n", lip, c_id); n += scnpr(b + n, blen - n, "%s Vendor Specific " "Identifier: 0x%" PRIx64 "\n", lip, vsei); n += scnpr(b + n, blen - n, "%s [0x", lip); for (m = 0; m < 8; ++m) n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); n += scnpr(b + n, blen - n, "]\n"); } else { n += scnpr(b + n, blen - n, "%s 0x", lip); for (m = 0; m < 8; ++m) n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); n += scnpr(b + n, blen - n, "\n"); } break; case 6: /* NAA 6: IEEE Registered extended */ if (16 != dlen) { n += scnpr(b + n, blen - n, "%s << unexpected NAA 6 " "identifier length: 0x%x >>\n", lip, dlen); n += hex2str(ip, dlen, lip, 1, blen - n, b + n); break; } c_id = (((ip[0] & 0xf) << 20) | (ip[1] << 12) | (ip[2] << 4) | ((ip[3] & 0xf0) >> 4)); vsei = ip[3] & 0xf; for (m = 1; m < 5; ++m) { vsei <<= 8; vsei |= ip[3 + m]; } if (do_long) { n += scnpr(b + n, blen - n, "%s NAA 6, IEEE " "Company_id: 0x%x\n", lip, c_id); n += scnpr(b + n, blen - n, "%s Vendor Specific " "Identifier: 0x%" PRIx64 "\n", lip, vsei); vsei = sg_get_unaligned_be64(ip + 8); n += scnpr(b + n, blen - n, "%s Vendor Specific " "Identifier Extension: 0x%" PRIx64 "\n", lip, vsei); n += scnpr(b + n, blen - n, "%s [0x", lip); for (m = 0; m < 16; ++m) n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); n += scnpr(b + n, blen - n, "]\n"); } else { n += scnpr(b + n, blen - n, "%s 0x", lip); for (m = 0; m < 16; ++m) n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]); n += scnpr(b + n, blen - n, "\n"); } break; default: n += scnpr(b + n, blen - n, "%s << unexpected NAA [0x%x] " ">>\n", lip, naa); n += hex2str(ip, dlen, lip, 1, blen - n, b + n); break; } break; case 4: /* Relative target port */ if ((1 != c_set) || (1 != assoc) || (4 != dlen)) { n += scnpr(b + n, blen - n, "%s << expected binary " "code_set, target port association, length 4 >>\n", lip); n += hex2str(ip, dlen, "", 1, blen - n, b + n); break; } d_id = sg_get_unaligned_be16(ip + 2); n += scnpr(b + n, blen - n, "%s Relative target port: 0x%x\n", lip, d_id); break; case 5: /* (primary) Target port group */ if ((1 != c_set) || (1 != assoc) || (4 != dlen)) { n += scnpr(b + n, blen - n, "%s << expected binary " "code_set, target port association, length 4 >>\n", lip); n += hex2str(ip, dlen, lip, 1, blen - n, b + n); break; } d_id = sg_get_unaligned_be16(ip + 2); n += scnpr(b + n, blen - n, "%s Target port group: 0x%x\n", lip, d_id); break; case 6: /* Logical unit group */ if ((1 != c_set) || (0 != assoc) || (4 != dlen)) { n += scnpr(b + n, blen - n, "%s << expected binary " "code_set, logical unit association, length 4 >>\n", lip); n += hex2str(ip, dlen, lip, 1, blen - n, b + n); break; } d_id = sg_get_unaligned_be16(ip + 2); n += scnpr(b + n, blen - n, "%s Logical unit group: 0x%x\n", lip, d_id); break; case 7: /* MD5 logical unit identifier */ if ((1 != c_set) || (0 != assoc)) { n += scnpr(b + n, blen - n, "%s << expected binary " "code_set, logical unit association >>\n", lip); n += hex2str(ip, dlen, "", 1, blen - n, b + n); break; } n += scnpr(b + n, blen - n, "%s MD5 logical unit identifier:\n", lip); n += hex2str(ip, dlen, lip, 1, blen - n, b + n); break; case 8: /* SCSI name string */ if (3 != c_set) { /* accept ASCII as subset of UTF-8 */ if (2 == c_set) { if (do_long) n += scnpr(b + n, blen - n, "%s << expected UTF-8, " "use ASCII >>\n", lip); } else { n += scnpr(b + n, blen - n, "%s << expected UTF-8 " "code_set >>\n", lip); n += hex2str(ip, dlen, lip, 0, blen - n, b + n); break; } } n += scnpr(b + n, blen - n, "%s SCSI name string:\n", lip); /* does %s print out UTF-8 ok?? * Seems to depend on the locale. Looks ok here with my * locale setting: en_AU.UTF-8 */ n += scnpr(b + n, blen - n, "%s %.*s\n", lip, dlen, (const char *)ip); break; case 9: /* Protocol specific port identifier */ /* added in spc4r36, PIV must be set, proto_id indicates */ /* whether UAS (USB) or SOP (PCIe) or ... */ if (! piv) n += scnpr(b + n, blen - n, " %s >>>> Protocol specific " "port identifier expects protocol\n" "%s identifier to be valid and it is not\n", lip, lip); if (TPROTO_UAS == p_id) { n += scnpr(b + n, blen - n, "%s USB device address: 0x%x\n", lip, 0x7f & ip[0]); n += scnpr(b + n, blen - n, "%s USB interface number: " "0x%x\n", lip, ip[2]); } else if (TPROTO_SOP == p_id) { n += scnpr(b + n, blen - n, "%s PCIe routing ID, bus " "number: 0x%x\n", lip, ip[0]); n += scnpr(b + n, blen - n, "%s function number: 0x%x\n", lip, ip[1]); n += scnpr(b + n, blen - n, "%s [or device number: " "0x%x, function number: 0x%x]\n", lip, (0x1f & (ip[1] >> 3)), 0x7 & ip[1]); } else n += scnpr(b + n, blen - n, "%s >>>> unexpected protocol " "indentifier: %s\n%s with Protocol specific " "port identifier\n", lip, sg_get_trans_proto_str(p_id, sizeof(e), e), lip); break; case 0xa: /* UUID identifier */ if (1 != c_set) { n += scnpr(b + n, blen - n, "%s << expected binary " "code_set >>\n", lip); n += hex2str(ip, dlen, lip, 0, blen - n, b + n); break; } if ((1 != ((ip[0] >> 4) & 0xf)) || (18 != dlen)) { n += scnpr(b + n, blen - n, "%s << expected locally " "assigned UUID, 16 bytes long >>\n", lip); n += hex2str(ip, dlen, lip, 0, blen - n, b + n); break; } n += scnpr(b + n, blen - n, "%s Locally assigned UUID: ", lip); for (m = 0; m < 16; ++m) { if ((4 == m) || (6 == m) || (8 == m) || (10 == m)) n += scnpr(b + n, blen - n, "-"); n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[2 + m]); } n += scnpr(b + n, blen - n, "\n"); if (do_long) { n += scnpr(b + n, blen - n, "%s [0x", lip); for (m = 0; m < 16; ++m) n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[2 + m]); n += scnpr(b + n, blen - n, "]\n"); } break; default: /* reserved */ n += scnpr(b + n, blen - n, "%s reserved designator=0x%x\n", lip, desig_type); n += hex2str(ip, dlen, lip, 1, blen - n, b + n); break; } return n; } static int decode_sks(const char * lip, const unsigned char * descp, int add_d_len, int sense_key, bool * processedp, int blen, char * b) { int progress, pr, rem, n; n = 0; if (NULL == lip) lip = ""; switch (sense_key) { case SPC_SK_ILLEGAL_REQUEST: if (add_d_len < 6) { n += scnpr(b + n, blen - n, "Field pointer: "); goto too_short; } /* abbreviate to fit on one line */ n += scnpr(b + n, blen - n, "Field pointer:\n"); n += scnpr(b + n, blen - n, "%s Error in %s: byte %d", lip, (descp[4] & 0x40) ? "Command" : "Data parameters", sg_get_unaligned_be16(descp + 5)); if (descp[4] & 0x08) { n += scnpr(b + n, blen - n, " bit %d\n", descp[4] & 0x07); } else n += scnpr(b + n, blen - n, "\n"); break; case SPC_SK_HARDWARE_ERROR: case SPC_SK_MEDIUM_ERROR: case SPC_SK_RECOVERED_ERROR: n += scnpr(b + n, blen - n, "Actual retry count: "); if (add_d_len < 6) goto too_short; n += scnpr(b + n, blen - n,"%u\n", sg_get_unaligned_be16(descp + 5)); break; case SPC_SK_NO_SENSE: case SPC_SK_NOT_READY: n += scnpr(b + n, blen - n, "Progress indication: "); if (add_d_len < 6) goto too_short; progress = sg_get_unaligned_be16(descp + 5); pr = (progress * 100) / 65536; rem = ((progress * 100) % 65536) / 656; n += scnpr(b + n, blen - n, "%d.%02d%%\n", pr, rem); break; case SPC_SK_COPY_ABORTED: n += scnpr(b + n, blen - n, "Segment pointer:\n"); if (add_d_len < 6) goto too_short; n += scnpr(b + n, blen - n, "%s Relative to start of %s, byte " "%d", lip, (descp[4] & 0x20) ? "segment descriptor" : "parameter list", sg_get_unaligned_be16(descp + 5)); if (descp[4] & 0x08) n += scnpr(b + n, blen - n, " bit %d\n", descp[4] & 0x07); else n += scnpr(b + n, blen - n, "\n"); break; case SPC_SK_UNIT_ATTENTION: n += scnpr(b + n, blen - n, "Unit attention condition queue:\n"); n += scnpr(b + n, blen - n, "%s overflow flag is %d\n", lip, !!(descp[4] & 0x1)); break; default: n += scnpr(b + n, blen - n, "Sense_key: 0x%x unexpected\n", sense_key); *processedp = false; break; } return n; too_short: n += scnpr(b + n, blen - n, "%s\n", " >> descriptor too short"); *processedp = false; return n; } #define TPGS_STATE_OPTIMIZED 0x0 #define TPGS_STATE_NONOPTIMIZED 0x1 #define TPGS_STATE_STANDBY 0x2 #define TPGS_STATE_UNAVAILABLE 0x3 #define TPGS_STATE_OFFLINE 0xe #define TPGS_STATE_TRANSITIONING 0xf static int decode_tpgs_state(int st, char * b, int blen) { switch (st) { case TPGS_STATE_OPTIMIZED: return scnpr(b, blen, "active/optimized"); case TPGS_STATE_NONOPTIMIZED: return scnpr(b, blen, "active/non optimized"); case TPGS_STATE_STANDBY: return scnpr(b, blen, "standby"); case TPGS_STATE_UNAVAILABLE: return scnpr(b, blen, "unavailable"); case TPGS_STATE_OFFLINE: return scnpr(b, blen, "offline"); case TPGS_STATE_TRANSITIONING: return scnpr(b, blen, "transitioning between states"); default: return scnpr(b, blen, "unknown: 0x%x", st); } } static int uds_referral_descriptor_str(char * b, int blen, const unsigned char * dp, int alen, const char * lip) { int n = 0; int dlen = alen - 2; int k, j, g, f, tpgd; const unsigned char * tp; uint64_t ull; char c[40]; if (NULL == lip) lip = ""; n += scnpr(b + n, blen - n, "%s Not all referrals: %d\n", lip, !!(dp[2] & 0x1)); dp += 4; for (k = 0, f = 1; (k + 4) < dlen; k += g, dp += g, ++f) { tpgd = dp[3]; g = (tpgd * 4) + 20; n += scnpr(b + n, blen - n, "%s Descriptor %d\n", lip, f); if ((k + g) > dlen) { n += scnpr(b + n, blen - n, "%s truncated descriptor, " "stop\n", lip); return n; } ull = sg_get_unaligned_be64(dp + 4); n += scnpr(b + n, blen - n, "%s first uds LBA: 0x%" PRIx64 "\n", lip, ull); ull = sg_get_unaligned_be64(dp + 12); n += scnpr(b + n, blen - n, "%s last uds LBA: 0x%" PRIx64 "\n", lip, ull); for (j = 0; j < tpgd; ++j) { tp = dp + 20 + (j * 4); decode_tpgs_state(tp[0] & 0xf, c, sizeof(c)); n += scnpr(b + n, blen - n, "%s tpg: %d state: %s\n", lip, sg_get_unaligned_be16(tp + 2), c); } } return n; } static const char * dd_usage_reason_str_arr[] = { "Unknown", "resend this and further commands to:", "resend this command to:", "new subsiduary lu added to this administrative lu:", "administrative lu associated with a preferred binding:", }; /* Decode descriptor format sense descriptors (assumes sense buffer is * in descriptor format) */ int sg_get_sense_descriptors_str(const char * lip, const unsigned char * sbp, int sb_len, int blen, char * b) { int add_sb_len, add_d_len, desc_len, k, j, sense_key; int n, progress, pr, rem; bool processed; const unsigned char * descp; const char * dtsp = " >> descriptor too short"; const char * eccp = "Extended copy command"; const char * ddp = "destination device"; char z[64]; if ((NULL == b) || (blen <= 0)) return 0; b[0] = '\0'; if (lip) scnpr(z, sizeof(z), "%.60s ", lip); else scnpr(z, sizeof(z), " "); if ((sb_len < 8) || (0 == (add_sb_len = sbp[7]))) return 0; add_sb_len = (add_sb_len < (sb_len - 8)) ? add_sb_len : (sb_len - 8); sense_key = (sbp[1] & 0xf); for (descp = (sbp + 8), k = 0, n = 0; (k < add_sb_len) && (n < blen); k += desc_len, descp += desc_len) { add_d_len = (k < (add_sb_len - 1)) ? descp[1] : -1; if ((k + add_d_len + 2) > add_sb_len) add_d_len = add_sb_len - k - 2; desc_len = add_d_len + 2; n += scnpr(b + n, blen - n, "%s Descriptor type: ", lip); processed = true; switch (descp[0]) { case 0: n += scnpr(b + n, blen - n, "Information: "); if ((add_d_len >= 10) && (0x80 & descp[2])) { n += scnpr(b + n, blen - n, "0x"); for (j = 0; j < 8; ++j) n += scnpr(b + n, blen - n, "%02x", descp[4 + j]); n += scnpr(b + n, blen - n, "\n"); } else { n += scnpr(b + n, blen - n, "%s\n", dtsp); processed = false; } break; case 1: n += scnpr(b + n, blen - n, "Command specific: "); if (add_d_len >= 10) { n += scnpr(b + n, blen - n, "0x"); for (j = 0; j < 8; ++j) n += scnpr(b + n, blen - n, "%02x", descp[4 + j]); n += scnpr(b + n, blen - n, "\n"); } else { n += scnpr(b + n, blen - n, "%s\n", dtsp); processed = false; } break; case 2: /* Sense Key Specific */ n += scnpr(b + n, blen - n, "Sense key specific: "); n += decode_sks(lip, descp, add_d_len, sense_key, &processed, blen - n, b + n); break; case 3: n += scnpr(b + n, blen - n, "Field replaceable unit code: "); if (add_d_len >= 2) n += scnpr(b + n, blen - n, "0x%x\n", descp[3]); else { n += scnpr(b + n, blen - n, "%s\n", dtsp); processed = false; } break; case 4: n += scnpr(b + n, blen - n, "Stream commands: "); if (add_d_len >= 2) { if (descp[3] & 0x80) n += scnpr(b + n, blen - n, "FILEMARK"); if (descp[3] & 0x40) n += scnpr(b + n, blen - n, "End Of Medium (EOM)"); if (descp[3] & 0x20) n += scnpr(b + n, blen - n, "Incorrect Length Indicator " "(ILI)"); n += scnpr(b + n, blen - n, "\n"); } else { n += scnpr(b + n, blen - n, "%s\n", dtsp); processed = false; } break; case 5: n += scnpr(b + n, blen - n, "Block commands: "); if (add_d_len >= 2) n += scnpr(b + n, blen - n, "Incorrect Length Indicator " "(ILI) %s\n", (descp[3] & 0x20) ? "set" : "clear"); else { n += scnpr(b + n, blen - n, "%s\n", dtsp); processed = false; } break; case 6: n += scnpr(b + n, blen - n, "OSD object identification\n"); processed = false; break; case 7: n += scnpr(b + n, blen - n, "OSD response integrity check " "value\n"); processed = false; break; case 8: n += scnpr(b + n, blen - n, "OSD attribute identification\n"); processed = false; break; case 9: /* this is defined in SAT (SAT-2) */ n += scnpr(b + n, blen - n, "ATA Status Return: "); if (add_d_len >= 12) { int extend, count; extend = descp[2] & 1; count = descp[5] + (extend ? (descp[4] << 8) : 0); n += scnpr(b + n, blen - n, "extend=%d error=0x%x \n%s" " count=0x%x ", extend, descp[3], lip, count); if (extend) n += scnpr(b + n, blen - n, "lba=0x%02x%02x%02x%02x%02x%02x ", descp[10], descp[8], descp[6], descp[11], descp[9], descp[7]); else n += scnpr(b + n, blen - n, "lba=0x%02x%02x%02x ", descp[11], descp[9], descp[7]); n += scnpr(b + n, blen - n, "device=0x%x status=0x%x\n", descp[12], descp[13]); } else { n += scnpr(b + n, blen - n, "%s\n", dtsp); processed = false; } break; case 0xa: /* Added in SPC-4 rev 17, became 'Another ...' in rev 34 */ n += scnpr(b + n, blen - n, "Another progress indication: "); if (add_d_len < 6) { n += scnpr(b + n, blen - n, "%s\n", dtsp); processed = false; break; } progress = sg_get_unaligned_be16(descp + 6); pr = (progress * 100) / 65536; rem = ((progress * 100) % 65536) / 656; n += scnpr(b + n, blen - n, "%d.02%d%%\n", pr, rem); n += scnpr(b + n, blen - n, "%s [sense_key=0x%x " "asc,ascq=0x%x,0x%x]\n", lip, descp[2], descp[3], descp[4]); break; case 0xb: /* Added in SPC-4 rev 23, defined in SBC-3 rev 22 */ n += scnpr(b + n, blen - n, "User data segment referral: "); if (add_d_len < 2) { n += scnpr(b + n, blen - n, "%s\n", dtsp); processed = false; break; } n += scnpr(b + n, blen - n, "\n"); n += uds_referral_descriptor_str(b + n, blen - n, descp, add_d_len, lip); break; case 0xc: /* Added in SPC-4 rev 28 */ n += scnpr(b + n, blen - n, "Forwarded sense data\n"); if (add_d_len < 2) { n += scnpr(b + n, blen - n, "%s\n", dtsp); processed = false; break; } n += scnpr(b + n, blen - n, "%s FSDT: %s\n", lip, (descp[2] & 0x80) ? "set" : "clear"); j = descp[2] & 0xf; n += scnpr(b + n, blen - n, "%s Sense data source: ", lip); switch (j) { case 0: n += scnpr(b + n, blen - n, "%s source device\n", eccp); break; case 1: case 2: case 3: case 4: case 5: case 6: case 7: n += scnpr(b + n, blen - n, "%s %s %d\n", eccp, ddp, j - 1); break; default: n += scnpr(b + n, blen - n, "unknown [%d]\n", j); } { char c[480]; sg_get_scsi_status_str(descp[3], sizeof(c) - 1, c); c[sizeof(c) - 1] = '\0'; n += scnpr(b + n, blen - n, "%s Forwarded status: %s\n", lip, c); if (add_d_len > 2) { /* recursing; hope not to get carried away */ n += scnpr(b + n, blen - n, "%s vvvvvvvvvvvvvvvv\n", lip); sg_get_sense_str(lip, descp + 4, add_d_len - 2, false, sizeof(c), c); n += scnpr(b + n, blen - n, "%s", c); n += scnpr(b + n, blen - n, "%s ^^^^^^^^^^^^^^^^\n", lip); } } break; case 0xd: /* Added in SBC-3 rev 36d */ /* this descriptor combines descriptors 0, 1, 2 and 3 */ n += scnpr(b + n, blen - n, "Direct-access block device\n"); if (add_d_len < 28) { n += scnpr(b + n, blen - n, "%s\n", dtsp); processed = false; break; } if (0x20 & descp[2]) n += scnpr(b + n, blen - n, "%s ILI (incorrect length " "indication) set\n", lip); if (0x80 & descp[4]) { n += scnpr(b + n, blen - n, "%s Sense key specific: ", lip); n += decode_sks(lip, descp, add_d_len, sense_key, &processed, blen - n, b + n); } n += scnpr(b + n, blen - n, "%s Field replaceable unit code: " "0x%x\n", lip, descp[7]); if (0x80 & descp[2]) { n += scnpr(b + n, blen - n, "%s Information: 0x", lip); for (j = 0; j < 8; ++j) n += scnpr(b + n, blen - n, "%02x", descp[8 + j]); n += scnpr(b + n, blen - n, "\n"); } n += scnpr(b + n, blen - n, "%s Command specific: 0x", lip); for (j = 0; j < 8; ++j) n += scnpr(b + n, blen - n, "%02x", descp[16 + j]); n += scnpr(b + n, blen - n, "\n"); break; case 0xe: /* Added in SPC-5 rev 6 (for Bind/Unbind) */ n += scnpr(b + n, blen - n, "Device designation\n"); j = (int)(sizeof(dd_usage_reason_str_arr) / sizeof(dd_usage_reason_str_arr[0])); if (descp[3] < j) n += scnpr(b + n, blen - n, "%s Usage reason: %s\n", lip, dd_usage_reason_str_arr[descp[3]]); else n += scnpr(b + n, blen - n, "%s Usage reason: " "reserved[%d]\n", lip, descp[3]); n += sg_get_designation_descriptor_str(z, descp + 4, descp[1] - 2, true, false, blen - n, b + n); break; case 0xf: /* Added in SPC-5 rev 10 (for Write buffer) */ n += scnpr(b + n, blen - n, "Microcode activation "); if (add_d_len < 6) { n += scnpr(b + n, blen - n, "%s\n", dtsp); processed = false; break; } progress = sg_get_unaligned_be16(descp + 6); n += scnpr(b + n, blen - n, "time: "); if (0 == progress) n += scnpr(b + n, blen - n, "unknown\n"); else n += scnpr(b + n, blen - n, "%d seconds\n", progress); break; default: if (descp[0] >= 0x80) n += scnpr(b + n, blen - n, "Vendor specific [0x%x]\n", descp[0]); else n += scnpr(b + n, blen - n, "Unknown [0x%x]\n", descp[0]); processed = false; break; } if (! processed) { if (add_d_len > 0) { n += scnpr(b + n, blen - n, "%s ", lip); for (j = 0; j < add_d_len; ++j) { if ((j > 0) && (0 == (j % 24))) n += scnpr(b + n, blen - n, "\n%s ", lip); n += scnpr(b + n, blen - n, "%02x ", descp[j + 2]); } n += scnpr(b + n, blen - n, "\n"); } } if (add_d_len < 0) n += scnpr(b + n, blen - n, "%s short descriptor\n", lip); } return n; } /* Decode SAT ATA PASS-THROUGH fixed format sense. Shows "+" after 'count' * and/or 'lba' values to indicate that not all data in those fields is shown. * That extra field information may be available in the ATA pass-through * results log page parameter with the corresponding 'log_index'. */ static int sg_get_sense_sat_pt_fixed_str(const char * lip, const unsigned char * sp, int slen, int blen, char * b) { int n = 0; bool extend, count_upper_nz, lba_upper_nz; if ((blen < 1) || (slen < 12)) return n; if (NULL == lip) lip = ""; if (SPC_SK_RECOVERED_ERROR != (0xf & sp[2])) n += scnpr(b + n, blen - n, "%s >> expected Sense key: Recovered " "Error ??\n", lip); /* Fixed sense command-specific information field starts at sp + 8 */ extend = !!(0x80 & sp[8]); count_upper_nz = !!(0x40 & sp[8]); lba_upper_nz = !!(0x20 & sp[8]); /* Fixed sense information field starts at sp + 3 */ n += scnpr(b + n, blen - n, "%s error=0x%x, status=0x%x, device=0x%x, " "count(7:0)=0x%x%c\n", lip, sp[3], sp[4], sp[5], sp[6], (count_upper_nz ? '+' : ' ')); n += scnpr(b + n, blen - n, "%s extend=%d, log_index=0x%x, " "lba_high,mid,low(7:0)=0x%x,0x%x,0x%x%c\n", lip, (int)extend, (0xf & sp[8]), sp[9], sp[10], sp[11], (lba_upper_nz ? '+' : ' ')); return n; } /* Fetch sense information */ int sg_get_sense_str(const char * lip, const unsigned char * sbp, int sb_len, bool raw_sinfo, int cblen, char * cbp) { bool descriptor_format = false; bool sdat_ovfl = false; bool valid; int len, progress, n, r, pr, rem, blen; unsigned int info; uint8_t resp_code; const char * ebp = NULL; char ebuff[64]; char b[256]; struct sg_scsi_sense_hdr ssh; if ((NULL == cbp) || (cblen <= 0)) return 0; else if (1 == cblen) { cbp[0] = '\0'; return 0; } blen = sizeof(b); n = 0; if (NULL == lip) lip = ""; if ((NULL == sbp) || (sb_len < 1)) { n += scnpr(cbp, cblen, "%s >>> sense buffer empty\n", lip); return n; } resp_code = 0x7f & sbp[0]; valid = !!(sbp[0] & 0x80); len = sb_len; if (sg_scsi_normalize_sense(sbp, sb_len, &ssh)) { switch (ssh.response_code) { case 0x70: /* fixed, current */ ebp = "Fixed format, current"; len = (sb_len > 7) ? (sbp[7] + 8) : sb_len; len = (len > sb_len) ? sb_len : len; sdat_ovfl = (len > 2) ? !!(sbp[2] & 0x10) : false; break; case 0x71: /* fixed, deferred */ /* error related to a previous command */ ebp = "Fixed format, <<>>"; len = (sb_len > 7) ? (sbp[7] + 8) : sb_len; len = (len > sb_len) ? sb_len : len; sdat_ovfl = (len > 2) ? !!(sbp[2] & 0x10) : false; break; case 0x72: /* descriptor, current */ descriptor_format = true; ebp = "Descriptor format, current"; sdat_ovfl = (sb_len > 4) ? !!(sbp[4] & 0x80) : false; break; case 0x73: /* descriptor, deferred */ descriptor_format = true; ebp = "Descriptor format, <<>>"; sdat_ovfl = (sb_len > 4) ? !!(sbp[4] & 0x80) : false; break; case 0x0: ebp = "Response code: 0x0 (?)"; break; default: scnpr(ebuff, sizeof(ebuff), "Unknown response code: 0x%x", ssh.response_code); ebp = ebuff; break; } n += scnpr(cbp + n, cblen - n, "%s%s; Sense key: %s\n", lip, ebp, sg_lib_sense_key_desc[ssh.sense_key]); if (sdat_ovfl) n += scnpr(cbp + n, cblen - n, "%s<<>>\n", lip); if (descriptor_format) { n += scnpr(cbp + n, cblen - n, "%s%s\n", lip, sg_get_asc_ascq_str(ssh.asc, ssh.ascq, blen, b)); n += sg_get_sense_descriptors_str(lip, sbp, len, cblen - n, cbp + n); } else if ((len > 12) && (0 == ssh.asc) && (ASCQ_ATA_PT_INFO_AVAILABLE == ssh.ascq)) { /* SAT ATA PASS-THROUGH fixed format */ n += scnpr(cbp + n, cblen - n, "%s%s\n", lip, sg_get_asc_ascq_str(ssh.asc, ssh.ascq, blen, b)); n += sg_get_sense_sat_pt_fixed_str(lip, sbp, len, cblen - n, cbp + n); } else if (len > 2) { /* fixed format */ if (len > 12) n += scnpr(cbp + n, cblen - n, "%s%s\n", lip, sg_get_asc_ascq_str(ssh.asc, ssh.ascq, blen, b)); r = 0; if (strlen(lip) > 0) r += scnpr(b + r, blen - r, "%s", lip); if (len > 6) { info = sg_get_unaligned_be32(sbp + 3); if (valid) r += scnpr(b + r, blen - r, " Info fld=0x%x [%u] ", info, info); else if (info > 0) r += scnpr(b + r, blen - r, " Valid=0, Info fld=0x%x " "[%u] ", info, info); } else info = 0; if (sbp[2] & 0xe0) { if (sbp[2] & 0x80) r += scnpr(b + r, blen - r, " FMK"); /* current command has read a filemark */ if (sbp[2] & 0x40) r += scnpr(b + r, blen - r, " EOM"); /* end-of-medium condition exists */ if (sbp[2] & 0x20) r += scnpr(b + r, blen - r, " ILI"); /* incorrect block length requested */ r += scnpr(b + r, blen - r, "\n"); } else if (valid || (info > 0)) r += scnpr(b + r, blen - r, "\n"); if ((len >= 14) && sbp[14]) r += scnpr(b + r, blen - r, "%s Field replaceable unit " "code: %d\n", lip, sbp[14]); if ((len >= 18) && (sbp[15] & 0x80)) { /* sense key specific decoding */ switch (ssh.sense_key) { case SPC_SK_ILLEGAL_REQUEST: r += scnpr(b + r, blen - r, "%s Sense Key Specific: " "Error in %s: byte %d", lip, ((sbp[15] & 0x40) ? "Command" : "Data parameters"), sg_get_unaligned_be16(sbp + 16)); if (sbp[15] & 0x08) r += scnpr(b + r, blen - r, " bit %d\n", sbp[15] & 0x07); else r += scnpr(b + r, blen - r, "\n"); break; case SPC_SK_NO_SENSE: case SPC_SK_NOT_READY: progress = sg_get_unaligned_be16(sbp + 16); pr = (progress * 100) / 65536; rem = ((progress * 100) % 65536) / 656; r += scnpr(b + r, blen - r, "%s Progress indication: " "%d.%02d%%\n", lip, pr, rem); break; case SPC_SK_HARDWARE_ERROR: case SPC_SK_MEDIUM_ERROR: case SPC_SK_RECOVERED_ERROR: r += scnpr(b + r, blen - r, "%s Actual retry count: " "0x%02x%02x\n", lip, sbp[16], sbp[17]); break; case SPC_SK_COPY_ABORTED: r += scnpr(b + r, blen - r, "%s Segment pointer: ", lip); r += scnpr(b + r, blen - r, "Relative to start of %s, " "byte %d", ((sbp[15] & 0x20) ? "segment descriptor" : "parameter list"), sg_get_unaligned_be16(sbp + 16)); if (sbp[15] & 0x08) r += scnpr(b + r, blen - r, " bit %d\n", sbp[15] & 0x07); else r += scnpr(b + r, blen - r, "\n"); break; case SPC_SK_UNIT_ATTENTION: r += scnpr(b + r, blen - r, "%s Unit attention " "condition queue: ", lip); r += scnpr(b + r, blen - r, "overflow flag is %d\n", !!(sbp[15] & 0x1)); break; default: r += scnpr(b + r, blen - r, "%s Sense_key: 0x%x " "unexpected\n", lip, ssh.sense_key); break; } } if (r > 0) n += scnpr(cbp + n, cblen - n, "%s", b); } else n += scnpr(cbp + n, cblen - n, "%s fixed descriptor length " "too short, len=%d\n", lip, len); } else { /* unable to normalise sense buffer, something irregular */ if (sb_len < 4) { /* Too short */ n += scnpr(cbp + n, cblen - n, "%ssense buffer too short (4 " "byte minimum)\n", lip); goto check_raw; } if (0x7f == resp_code) { /* Vendor specific */ n += scnpr(cbp + n, cblen - n, "%sVendor specific sense buffer, " "in hex:\n", lip); n += hex2str(sbp, sb_len, lip, -1, cblen - n, cbp + n); return n; /* no need to check raw, just output in hex */ } /* non-extended SCSI-1 sense data ?? */ r = 0; if (strlen(lip) > 0) r += scnpr(b + r, blen - r, "%s", lip); r += scnpr(b + r, blen - r, "Probably uninitialized data.\n%s Try " "to view as SCSI-1 non-extended sense:\n", lip); r += scnpr(b + r, blen - r, " AdValid=%d Error class=%d Error " "code=%d\n", valid, ((sbp[0] >> 4) & 0x7), (sbp[0] & 0xf)); if (valid) scnpr(b + r, blen - r, "%s lba=0x%x\n", lip, sg_get_unaligned_be24(sbp + 1) & 0x1fffff); n += scnpr(cbp + n, cblen - n, "%s\n", b); len = sb_len; if (len > 32) len = 32; /* trim in case there is a lot of rubbish */ } check_raw: if (raw_sinfo) { char z[64]; n += scnpr(cbp + n, cblen - n, "%s Raw sense data (in hex):\n", lip); if (n >= (cblen - 1)) return n; scnpr(z, sizeof(z), "%.50s ", lip); n += hex2str(sbp, len, z, -1, cblen - n, cbp + n); } return n; } /* Print sense information */ void sg_print_sense(const char * leadin, const unsigned char * sbp, int sb_len, bool raw_sinfo) { uint32_t pg_sz = sg_get_page_size(); char *cp; uint8_t *free_cp; cp = (char *)sg_memalign(pg_sz, pg_sz, &free_cp, 0); if (NULL == cp) return; sg_get_sense_str(leadin, sbp, sb_len, raw_sinfo, pg_sz, cp); pr2ws("%s", cp); free(free_cp); } /* Following examines exit_status and outputs a clear error message to * warnings_strm (usually stderr) if one is known and returns true. * Otherwise it doesn't print anything and returns false. Note that * if exit_status==0 then returns true but prints nothing and if * exit_status<0 ("some error occurred") false is returned. If leadin is * non-NULL then it is printed before the error message. */ bool sg_if_can2stderr(const char * leadin, int exit_status) { const char * s = leadin ? leadin : ""; if (exit_status < 0) return false; else if (0 == exit_status) return true; switch (exit_status) { case SG_LIB_CAT_NOT_READY: /* 2 */ pr2ws("%sDevice not ready\n", s); return true; case SG_LIB_CAT_MEDIUM_HARD: /* 3 */ pr2ws("%sMedium or hardware error\n", s); /* 3 sense keys: Medium, */ return true; /* hardware error or 'Blank check' for tapes */ case SG_LIB_CAT_UNIT_ATTENTION: /* 6 */ pr2ws("%sDevice reported 'Unit attention'\n", s); return true; case SG_LIB_CAT_DATA_PROTECT: /* 7 */ pr2ws("%sDevice reported 'Data protect', read-only?\n", s); return true; case SG_LIB_CAT_COPY_ABORTED: /* 10 */ pr2ws("%sCopy aborted\n", s); return true; case SG_LIB_CAT_ABORTED_COMMAND: /* 11 */ pr2ws("%sCommand aborted\n", s); return true; case SG_LIB_CAT_MISCOMPARE: /* 14 */ pr2ws("%sMiscompare\n", s); return true; case SG_LIB_CAT_RES_CONFLICT: /* 24 */ pr2ws("%sReservation conflict\n", s); return true; case SG_LIB_CAT_BUSY: /* 26 */ pr2ws("%sDevice is busy, try again\n", s); return true; case SG_LIB_CAT_TASK_ABORTED: /* 29 */ pr2ws("%sTask aborted\n", s); return true; case SG_LIB_CAT_TIMEOUT: /* 33 */ pr2ws("%sTime out\n", s); return true; case SG_LIB_CAT_PROTECTION: /* 40 */ pr2ws("%sProtection error\n", s); return true; case SG_LIB_NVME_STATUS: /* 48 */ pr2ws("%sNVMe error (non-zero status)\n", s); return true; case SG_LIB_OS_BASE_ERR + EACCES: /* 50 + */ pr2ws("%sPermission denied\n", s); return true; case SG_LIB_OS_BASE_ERR + ENOMEM: pr2ws("%sUtility unable to allocate memory\n", s); return true; case SG_LIB_OS_BASE_ERR + ENOTTY: pr2ws("%sInappropriate I/O control operation\n", s); return true; case SG_LIB_OS_BASE_ERR + EPERM: pr2ws("%sNot permitted\n", s); return true; case SG_LIB_OS_BASE_ERR + EINTR: pr2ws("%sInterrupted system call\n", s); return true; case SG_LIB_OS_BASE_ERR + EIO: pr2ws("%sInput/output error\n", s); return true; case SG_LIB_OS_BASE_ERR + ENODEV: pr2ws("%sNo such device\n", s); return true; case SG_LIB_OS_BASE_ERR + ENOENT: pr2ws("%sNo such file or directory\n", s); return true; default: return false; } return false; } /* If os_err_num is within bounds then the returned value is 'os_err_num + * SG_LIB_OS_BASE_ERR' otherwise -1 is returned. If os_err_num is 0 then 0 * is returned. */ int sg_convert_errno(int os_err_num) { if (os_err_num <= 0) { if (os_err_num < -1) return -1; return os_err_num; } if (os_err_num < (SG_LIB_CAT_MALFORMED - SG_LIB_OS_BASE_ERR)) return SG_LIB_OS_BASE_ERR + os_err_num; return -1; } /* See description in sg_lib.h header file */ bool sg_scsi_normalize_sense(const unsigned char * sbp, int sb_len, struct sg_scsi_sense_hdr * sshp) { uint8_t resp_code; if (sshp) memset(sshp, 0, sizeof(struct sg_scsi_sense_hdr)); if ((NULL == sbp) || (sb_len < 1)) return false; resp_code = 0x7f & sbp[0]; if ((resp_code < 0x70) || (resp_code > 0x73)) return false; if (sshp) { sshp->response_code = resp_code; if (sshp->response_code >= 0x72) { /* descriptor format */ if (sb_len > 1) sshp->sense_key = (0xf & sbp[1]); if (sb_len > 2) sshp->asc = sbp[2]; if (sb_len > 3) sshp->ascq = sbp[3]; if (sb_len > 7) sshp->additional_length = sbp[7]; } else { /* fixed format */ if (sb_len > 2) sshp->sense_key = (0xf & sbp[2]); if (sb_len > 7) { sb_len = (sb_len < (sbp[7] + 8)) ? sb_len : (sbp[7] + 8); if (sb_len > 12) sshp->asc = sbp[12]; if (sb_len > 13) sshp->ascq = sbp[13]; } } } return true; } /* Returns a SG_LIB_CAT_* value. If cannot decode sense buffer (sbp) or a * less common sense key then return SG_LIB_CAT_SENSE .*/ int sg_err_category_sense(const unsigned char * sbp, int sb_len) { struct sg_scsi_sense_hdr ssh; if ((sbp && (sb_len > 2)) && (sg_scsi_normalize_sense(sbp, sb_len, &ssh))) { switch (ssh.sense_key) { /* 0 to 0x1f */ case SPC_SK_NO_SENSE: return SG_LIB_CAT_NO_SENSE; case SPC_SK_RECOVERED_ERROR: return SG_LIB_CAT_RECOVERED; case SPC_SK_NOT_READY: return SG_LIB_CAT_NOT_READY; case SPC_SK_MEDIUM_ERROR: case SPC_SK_HARDWARE_ERROR: case SPC_SK_BLANK_CHECK: return SG_LIB_CAT_MEDIUM_HARD; case SPC_SK_UNIT_ATTENTION: return SG_LIB_CAT_UNIT_ATTENTION; /* used to return SG_LIB_CAT_MEDIA_CHANGED when ssh.asc==0x28 */ case SPC_SK_ILLEGAL_REQUEST: if ((0x20 == ssh.asc) && (0x0 == ssh.ascq)) return SG_LIB_CAT_INVALID_OP; else return SG_LIB_CAT_ILLEGAL_REQ; break; case SPC_SK_ABORTED_COMMAND: if (0x10 == ssh.asc) return SG_LIB_CAT_PROTECTION; else return SG_LIB_CAT_ABORTED_COMMAND; case SPC_SK_MISCOMPARE: return SG_LIB_CAT_MISCOMPARE; case SPC_SK_DATA_PROTECT: return SG_LIB_CAT_DATA_PROTECT; case SPC_SK_COPY_ABORTED: return SG_LIB_CAT_COPY_ABORTED; case SPC_SK_COMPLETED: case SPC_SK_VOLUME_OVERFLOW: return SG_LIB_CAT_SENSE; default: ; /* reserved and vendor specific sense keys fall through */ } } return SG_LIB_CAT_SENSE; } /* Beware: gives wrong answer for variable length command (opcode=0x7f) */ int sg_get_command_size(unsigned char opcode) { switch ((opcode >> 5) & 0x7) { case 0: return 6; case 1: case 2: case 6: case 7: return 10; case 3: case 5: return 12; break; case 4: return 16; default: return 10; } } void sg_get_command_name(const unsigned char * cmdp, int peri_type, int buff_len, char * buff) { int service_action; if ((NULL == buff) || (buff_len < 1)) return; else if (1 == buff_len) { buff[0] = '\0'; return; } if (NULL == cmdp) { scnpr(buff, buff_len, "%s", " command pointer"); return; } service_action = (SG_VARIABLE_LENGTH_CMD == cmdp[0]) ? sg_get_unaligned_be16(cmdp + 8) : (cmdp[1] & 0x1f); sg_get_opcode_sa_name(cmdp[0], service_action, peri_type, buff_len, buff); } struct op_code2sa_t { int op_code; int pdt_match; /* -1->all; 0->disk,ZBC,RCB, 1->tape+adc+smc */ struct sg_lib_value_name_t * arr; const char * prefix; }; static struct op_code2sa_t op_code2sa_arr[] = { {SG_VARIABLE_LENGTH_CMD, -1, sg_lib_variable_length_arr, NULL}, {SG_MAINTENANCE_IN, -1, sg_lib_maint_in_arr, NULL}, {SG_MAINTENANCE_OUT, -1, sg_lib_maint_out_arr, NULL}, {SG_SERVICE_ACTION_IN_12, -1, sg_lib_serv_in12_arr, NULL}, {SG_SERVICE_ACTION_OUT_12, -1, sg_lib_serv_out12_arr, NULL}, {SG_SERVICE_ACTION_IN_16, -1, sg_lib_serv_in16_arr, NULL}, {SG_SERVICE_ACTION_OUT_16, -1, sg_lib_serv_out16_arr, NULL}, {SG_SERVICE_ACTION_BIDI, -1, sg_lib_serv_bidi_arr, NULL}, {SG_PERSISTENT_RESERVE_IN, -1, sg_lib_pr_in_arr, "Persistent reserve in"}, {SG_PERSISTENT_RESERVE_OUT, -1, sg_lib_pr_out_arr, "Persistent reserve out"}, {SG_3PARTY_COPY_OUT, -1, sg_lib_xcopy_sa_arr, NULL}, {SG_3PARTY_COPY_IN, -1, sg_lib_rec_copy_sa_arr, NULL}, {SG_READ_BUFFER, -1, sg_lib_read_buff_arr, "Read buffer(10)"}, {SG_READ_BUFFER_16, -1, sg_lib_read_buff_arr, "Read buffer(16)"}, {SG_READ_ATTRIBUTE, -1, sg_lib_read_attr_arr, "Read attribute"}, {SG_READ_POSITION, 1, sg_lib_read_pos_arr, "Read position"}, {SG_SANITIZE, 0, sg_lib_sanitize_sa_arr, "Sanitize"}, {SG_WRITE_BUFFER, -1, sg_lib_write_buff_arr, "Write buffer"}, {SG_ZONING_IN, 0, sg_lib_zoning_in_arr, NULL}, {SG_ZONING_OUT, 0, sg_lib_zoning_out_arr, NULL}, {0xffff, -1, NULL, NULL}, }; void sg_get_opcode_sa_name(unsigned char cmd_byte0, int service_action, int peri_type, int buff_len, char * buff) { int d_pdt; const struct sg_lib_value_name_t * vnp; const struct op_code2sa_t * osp; char b[80]; if ((NULL == buff) || (buff_len < 1)) return; else if (1 == buff_len) { buff[0] = '\0'; return; } if (peri_type < 0) peri_type = 0; d_pdt = sg_lib_pdt_decay(peri_type); for (osp = op_code2sa_arr; osp->arr; ++osp) { if ((int)cmd_byte0 == osp->op_code) { if ((osp->pdt_match < 0) || (d_pdt == osp->pdt_match)) { vnp = get_value_name(osp->arr, service_action, peri_type); if (vnp) { if (osp->prefix) scnpr(buff, buff_len, "%s, %s", osp->prefix, vnp->name); else scnpr(buff, buff_len, "%s", vnp->name); } else { sg_get_opcode_name(cmd_byte0, peri_type, sizeof(b), b); scnpr(buff, buff_len, "%s service action=0x%x", b, service_action); } } else sg_get_opcode_name(cmd_byte0, peri_type, buff_len, buff); return; } } sg_get_opcode_name(cmd_byte0, peri_type, buff_len, buff); } void sg_get_opcode_name(unsigned char cmd_byte0, int peri_type, int buff_len, char * buff) { const struct sg_lib_value_name_t * vnp; int grp; if ((NULL == buff) || (buff_len < 1)) return; else if (1 == buff_len) { buff[0] = '\0'; return; } if (SG_VARIABLE_LENGTH_CMD == cmd_byte0) { scnpr(buff, buff_len, "%s", "Variable length"); return; } grp = (cmd_byte0 >> 5) & 0x7; switch (grp) { case 0: case 1: case 2: case 4: case 5: vnp = get_value_name(sg_lib_normal_opcodes, cmd_byte0, peri_type); if (vnp) scnpr(buff, buff_len, "%s", vnp->name); else scnpr(buff, buff_len, "Opcode=0x%x", (int)cmd_byte0); break; case 3: scnpr(buff, buff_len, "Reserved [0x%x]", (int)cmd_byte0); break; case 6: case 7: scnpr(buff, buff_len, "Vendor specific [0x%x]", (int)cmd_byte0); break; default: scnpr(buff, buff_len, "Opcode=0x%x", (int)cmd_byte0); break; } } /* 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 sg_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) { bool fltr = ((m_assoc >= 0) || (m_desig_type >= 0) || (m_code_set >= 0)); int k = *off; const unsigned char * bp = initial_desig_desc; while ((k + 3) < page_len) { k = (k < 0) ? 0 : (k + bp[k + 3] + 4); if ((k + 4) > page_len) break; if (fltr) { if (m_code_set >= 0) { if ((bp[k] & 0xf) != m_code_set) continue; } if (m_assoc >= 0) { if (((bp[k + 1] >> 4) & 0x3) != m_assoc) continue; } if (m_desig_type >= 0) { if ((bp[k + 1] & 0xf) != m_desig_type) continue; } } *off = k; return 0; } return (k == page_len) ? -1 : -2; } static const char * const bad_sense_cat = "Bad sense category"; /* Yield string associated with sense category. Returns 'buff' (or pointer * to "Bad sense category" if 'buff' is NULL). If sense_cat unknown then * yield "Sense category: " string. */ const char * sg_get_category_sense_str(int sense_cat, int buff_len, char * buff, int verbose) { int n; if (NULL == buff) return bad_sense_cat; if (buff_len <= 0) return buff; switch (sense_cat) { case SG_LIB_CAT_CLEAN: /* 0 */ scnpr(buff, buff_len, "No errors"); break; case SG_LIB_SYNTAX_ERROR: /* 1 */ scnpr(buff, buff_len, "Syntax error"); break; case SG_LIB_CAT_NOT_READY: /* 2 */ n = scnpr(buff, buff_len, "Not ready"); if (verbose && (n < (buff_len - 1))) scnpr(buff + n, buff_len - n, " sense key"); break; case SG_LIB_CAT_MEDIUM_HARD: /* 3 */ n = scnpr(buff, buff_len, "Medium or hardware error"); if (verbose && (n < (buff_len - 1))) scnpr(buff + n, buff_len - n, " sense key (plus blank check)"); break; case SG_LIB_CAT_ILLEGAL_REQ: /* 5 */ n = scnpr(buff, buff_len, "Illegal request"); if (verbose && (n < (buff_len - 1))) scnpr(buff + n, buff_len - n, " sense key, apart from Invalid " "opcode"); break; case SG_LIB_CAT_UNIT_ATTENTION: /* 6 */ n = scnpr(buff, buff_len, "Unit attention"); if (verbose && (n < (buff_len - 1))) scnpr(buff + n, buff_len - n, " sense key"); break; case SG_LIB_CAT_DATA_PROTECT: /* 7 */ n = scnpr(buff, buff_len, "Data protect"); if (verbose && (n < (buff_len - 1))) scnpr(buff + n, buff_len - n, " sense key, write protected " "media?"); break; case SG_LIB_CAT_INVALID_OP: /* 9 */ n = scnpr(buff, buff_len, "Illegal request, invalid opcode"); if (verbose && (n < (buff_len - 1))) scnpr(buff + n, buff_len - n, " sense key"); break; case SG_LIB_CAT_COPY_ABORTED: /* 10 */ n = scnpr(buff, buff_len, "Copy aborted"); if (verbose && (n < (buff_len - 1))) scnpr(buff + n, buff_len - n, " sense key"); break; case SG_LIB_CAT_ABORTED_COMMAND: /* 11 */ n = scnpr(buff, buff_len, "Aborted command"); if (verbose && (n < (buff_len - 1))) scnpr(buff + n, buff_len - n, " sense key, other than " "protection related (asc=0x10)"); break; case SG_LIB_CAT_MISCOMPARE: /* 14 */ n = scnpr(buff, buff_len, "Miscompare"); if (verbose && (n < (buff_len - 1))) scnpr(buff + n, buff_len - n, " sense key"); break; case SG_LIB_FILE_ERROR: /* 15 */ scnpr(buff, buff_len, "File error"); break; case SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO: /* 17 */ scnpr(buff, buff_len, "Illegal request with info"); break; case SG_LIB_CAT_MEDIUM_HARD_WITH_INFO: /* 18 */ scnpr(buff, buff_len, "Medium or hardware error with info"); break; case SG_LIB_CAT_NO_SENSE: /* 20 */ n = scnpr(buff, buff_len, "No sense key"); if (verbose && (n < (buff_len - 1))) scnpr(buff + n, buff_len - n, " probably additional sense " "information"); break; case SG_LIB_CAT_RECOVERED: /* 21 */ n = scnpr(buff, buff_len, "Recovered error"); if (verbose && (n < (buff_len - 1))) scnpr(buff + n, buff_len - n, " sense key"); break; case SG_LIB_CAT_RES_CONFLICT: /* 24 */ n = scnpr(buff, buff_len, "Reservation conflict"); if (verbose && (n < (buff_len - 1))) scnpr(buff + n, buff_len - n, " SCSI status"); break; case SG_LIB_CAT_CONDITION_MET: /* 25 */ n = scnpr(buff, buff_len, "Condition met"); if (verbose && (n < (buff_len - 1))) scnpr(buff + n, buff_len - n, " SCSI status"); break; case SG_LIB_CAT_BUSY: /* 26 */ n = scnpr(buff, buff_len, "Busy"); if (verbose && (n < (buff_len - 1))) scnpr(buff + n, buff_len - n, " SCSI status"); break; case SG_LIB_CAT_TS_FULL: /* 27 */ n = scnpr(buff, buff_len, "Task set full"); if (verbose && (n < (buff_len - 1))) scnpr(buff + n, buff_len - n, " SCSI status"); break; case SG_LIB_CAT_ACA_ACTIVE: /* 28 */ n = scnpr(buff, buff_len, "ACA active"); if (verbose && (n < (buff_len - 1))) scnpr(buff + n, buff_len - n, " SCSI status"); break; case SG_LIB_CAT_TASK_ABORTED: /* 29 */ n = scnpr(buff, buff_len, "Task aborted"); if (verbose && (n < (buff_len - 1))) scnpr(buff + n, buff_len - n, " SCSI status"); break; case SG_LIB_CAT_TIMEOUT: /* 33 */ scnpr(buff, buff_len, "SCSI command timeout"); break; case SG_LIB_CAT_PROTECTION: /* 40 */ n = scnpr(buff, buff_len, "Aborted command, protection"); if (verbose && (n < (buff_len - 1))) scnpr(buff + n, buff_len - n, " information (PI) problem"); break; case SG_LIB_CAT_PROTECTION_WITH_INFO: /* 41 */ n = scnpr(buff, buff_len, "Aborted command with info, protection"); if (verbose && (n < (buff_len - 1))) scnpr(buff + n, buff_len - n, " information (PI) problem"); break; case SG_LIB_CAT_MALFORMED: /* 97 */ n = scnpr(buff, buff_len, "Malformed response"); if (verbose && (n < (buff_len - 1))) scnpr(buff + n, buff_len - n, " to SCSI command"); break; case SG_LIB_CAT_SENSE: /* 98 */ n = scnpr(buff, buff_len, "Some other sense data problem"); if (verbose && (n < (buff_len - 1))) scnpr(buff + n, buff_len - n, ", try '-v' option for more " "information"); break; case SG_LIB_CAT_OTHER: /* 99 */ n = scnpr(buff, buff_len, "Some other error/warning has occurred"); if ((0 == verbose) && (n < (buff_len - 1))) scnpr(buff + n, buff_len - n, ", possible transport of driver " "issue"); break; default: if ((sense_cat > SG_LIB_OS_BASE_ERR) && (sense_cat < (SG_LIB_OS_BASE_ERR + 47))) { int k = sense_cat - SG_LIB_OS_BASE_ERR; n = scnpr(buff, buff_len, "OS error: %s [%d]", safe_strerror(k), k); } else { n = scnpr(buff, buff_len, "Sense category: %d", sense_cat); if ((0 == verbose) && (n < (buff_len - 1))) scnpr(buff + n, buff_len - n, ", try '-v' option for more " "information"); } break; } return buff; } static const char * sg_sfs_spc_reserved = "SPC Reserved"; static const char * sg_sfs_sbc_reserved = "SBC Reserved"; static const char * sg_sfs_ssc_reserved = "SSC Reserved"; static const char * sg_sfs_zbc_reserved = "ZBC Reserved"; static const char * sg_sfs_reserved = "Reserved"; /* Yield SCSI Feature Set (sfs) string. When 'peri_type' is < -1 (or > 31) * returns pointer to string (same as 'buff') associated with 'sfs_code'. * When 'peri_type' is between -1 (for SPC) and 31 (inclusive) then a match * on both 'sfs_code' and 'peri_type' is required. If 'foundp' is not NULL * then where it points is set to true if a match is found else it is set to * false. If 'buff' is not NULL then in the case of a match a descriptive * string is written to 'buff' while if there is not a not then a string * ending in "Reserved" is written (and may be prefixed with SPC, SBC, SSC * or ZBC). Returns 'buff' (i.e. a pointer value) even if it is NULL. * Example: * char b[64]; * ... * printf("%s\n", sg_get_sfs_str(sfs_code, -2, sizeof(b), b, NULL, 0)); */ const char * sg_get_sfs_str(uint16_t sfs_code, int peri_type, int buff_len, char * buff, bool * foundp, int verbose) { const struct sg_lib_value_name_t * vnp = NULL; int n = 0; int my_pdt; if ((NULL == buff) || (buff_len < 1)) { if (foundp) *foundp = false; return NULL; } else if (1 == buff_len) { buff[0] = '\0'; if (foundp) *foundp = false; return NULL; } my_pdt = ((peri_type < -1) || (peri_type > 0x1f)) ? -2 : peri_type; vnp = get_value_name(sg_lib_scsi_feature_sets, sfs_code, my_pdt); if (vnp && (-2 != my_pdt)) { if (peri_type != vnp->peri_dev_type) vnp = NULL; /* shouldn't really happen */ } if (foundp) *foundp = vnp ? true : false; if (sfs_code < 0x100) { /* SPC Feature Sets */ if (vnp) { if (verbose) n += scnpr(buff, buff_len, "SPC %s", vnp->name); else n += scnpr(buff, buff_len, "%s", vnp->name); } else n += scnpr(buff, buff_len, "%s", sg_sfs_spc_reserved); } else if (sfs_code < 0x200) { /* SBC Feature Sets */ if (vnp) { if (verbose) n += scnpr(buff, buff_len, "SBC %s", vnp->name); else n += scnpr(buff, buff_len, "%s", vnp->name); } else n += scnpr(buff, buff_len, "%s", sg_sfs_sbc_reserved); } else if (sfs_code < 0x300) { /* SSC Feature Sets */ if (vnp) { if (verbose) n += scnpr(buff, buff_len, "SSC %s", vnp->name); else n += scnpr(buff, buff_len, "%s", vnp->name); } else n += scnpr(buff, buff_len, "%s", sg_sfs_ssc_reserved); } else if (sfs_code < 0x400) { /* ZBC Feature Sets */ if (vnp) { if (verbose) n += scnpr(buff, buff_len, "ZBC %s", vnp->name); else n += scnpr(buff, buff_len, "%s", vnp->name); } else n += scnpr(buff, buff_len, "%s", sg_sfs_zbc_reserved); } else { /* Other SCSI Feature Sets */ if (vnp) { if (verbose) n += scnpr(buff, buff_len, "[unrecognized PDT] %s", vnp->name); else n += scnpr(buff, buff_len, "%s", vnp->name); } else n += scnpr(buff, buff_len, "%s", sg_sfs_reserved); } if (verbose > 4) pr2serr("%s: length of returned string (n) %d\n", __func__, n); return buff; } /* 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, hopfully there is a low * probability this function will yield true in that case. */ bool sg_is_scsi_cdb(const uint8_t * cdbp, int clen) { int ilen, sa; uint8_t opcode; uint8_t top3bits; if (clen < 6) return false; opcode = cdbp[0]; top3bits = opcode >> 5; if (0x3 == top3bits) { 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; } /* Yield string associated with NVMe command status value in sct_sc. It * expects to decode DW3 bits 27:17 from the completion queue. Bits 27:25 * are the Status Code Type (SCT) and bits 24:17 are the Status Code (SC). * Bit 17 in DW3 should be bit 0 in sct_sc. If no status string is found * a string of the form "Reserved [0x]" is generated. * Returns 'buff'. Does nothing if buff_len<=0 or if buff is NULL.*/ char * sg_get_nvme_cmd_status_str(uint16_t sct_sc, int b_len, char * b) { int k; uint16_t s = 0x3ff & sct_sc; const struct sg_lib_value_name_t * vp = sg_lib_nvme_cmd_status_arr; if ((b_len <= 0) || (NULL == b)) return b; else if (1 == b_len) { b[0] = '\0'; return b; } for (k = 0; (vp->name && (k < 1000)); ++k, ++vp) { if (s == (uint16_t)vp->value) { strncpy(b, vp->name, b_len); b[b_len - 1] = '\0'; return b; } } if (k >= 1000) pr2ws("%s: where is sentinel for sg_lib_nvme_cmd_status_arr ??\n", __func__); snprintf(b, b_len, "Reserved [0x%x]", sct_sc); return b; } /* Attempts to map NVMe status value ((SCT << 8) | SC) to SCSI status, * sense_key, asc and ascq tuple. If successful returns true and writes to * non-NULL pointer arguments; otherwise returns false. */ bool sg_nvme_status2scsi(uint16_t sct_sc, uint8_t * status_p, uint8_t * sk_p, uint8_t * asc_p, uint8_t * ascq_p) { int k, ind; uint16_t s = 0x3ff & sct_sc; struct sg_lib_value_name_t * vp = sg_lib_nvme_cmd_status_arr; struct sg_lib_4tuple_u8 * mp = sg_lib_scsi_status_sense_arr; for (k = 0; (vp->name && (k < 1000)); ++k, ++vp) { if (s == (uint16_t)vp->value) break; } if (k >= 1000) { pr2ws("%s: where is sentinel for sg_lib_nvme_cmd_status_arr ??\n", __func__); return false; } if (NULL == vp->name) return false; ind = vp->peri_dev_type; for (k = 0; (0xff != mp->t2) && k < 1000; ++k, ++mp) ; /* count entries for valid index range */ if (k >= 1000) { pr2ws("%s: where is sentinel for sg_lib_scsi_status_sense_arr ??\n", __func__); return false; } else if (ind >= k) return false; mp = sg_lib_scsi_status_sense_arr + ind; if (status_p) *status_p = mp->t1; if (sk_p) *sk_p = mp->t2; if (asc_p) *asc_p = mp->t3; if (ascq_p) *ascq_p = mp->t4; return true; } /* safe_strerror() contributed by Clayton Weaver * Allows for situation in which strerror() is given a wild value (or the * C library is incomplete) and returns NULL. Still not thread safe. */ static char safe_errbuf[64] = {'u', 'n', 'k', 'n', 'o', 'w', 'n', ' ', 'e', 'r', 'r', 'n', 'o', ':', ' ', 0}; char * safe_strerror(int errnum) { size_t len; char * errstr; if (errnum < 0) errnum = -errnum; errstr = strerror(errnum); if (NULL == errstr) { len = strlen(safe_errbuf); scnpr(safe_errbuf + len, sizeof(safe_errbuf) - len, "%i", errnum); return safe_errbuf; } return errstr; } static void trimTrailingSpaces(char * b) { int k; for (k = ((int)strlen(b) - 1); k >= 0; --k) { if (' ' != b[k]) break; } if ('\0' != b[k + 1]) b[k + 1] = '\0'; } /* Note the ASCII-hex output goes to stdout. [Most other output from functions * in this file go to sg_warnings_strm (default stderr).] * 'no_ascii' allows for 3 output types: * > 0 each line has address then up to 16 ASCII-hex bytes * = 0 in addition, the bytes are listed in ASCII to the right * < 0 only the ASCII-hex bytes are listed (i.e. without address) */ static void dStrHexFp(const char* str, int len, int no_ascii, FILE * fp) { const char * p = str; const char * formatstr; unsigned char c; char buff[82]; int a = 0; int bpstart = 5; const int cpstart = 60; int cpos = cpstart; int bpos = bpstart; int i, k, blen; if (len <= 0) return; blen = (int)sizeof(buff); if (0 == no_ascii) /* address at left and ASCII at right */ formatstr = "%.76s\n"; else /* previously when > 0 str was "%.58s\n" */ formatstr = "%s\n"; /* when < 0 str was: "%.48s\n" */ memset(buff, ' ', 80); buff[80] = '\0'; if (no_ascii < 0) { bpstart = 0; bpos = bpstart; for (k = 0; k < len; k++) { c = *p++; if (bpos == (bpstart + (8 * 3))) bpos++; scnpr(&buff[bpos], blen - bpos, "%.2x", (int)(unsigned char)c); buff[bpos + 2] = ' '; if ((k > 0) && (0 == ((k + 1) % 16))) { trimTrailingSpaces(buff); fprintf(fp, formatstr, buff); bpos = bpstart; memset(buff, ' ', 80); } else bpos += 3; } if (bpos > bpstart) { buff[bpos + 2] = '\0'; trimTrailingSpaces(buff); fprintf(fp, "%s\n", buff); } return; } /* no_ascii>=0, start each line with address (offset) */ k = scnpr(buff + 1, blen - 1, "%.2x", a); buff[k + 1] = ' '; for (i = 0; i < len; i++) { c = *p++; bpos += 3; if (bpos == (bpstart + (9 * 3))) bpos++; scnpr(&buff[bpos], blen - bpos, "%.2x", (int)(unsigned char)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); fprintf(fp, formatstr, buff); bpos = bpstart; cpos = cpstart; a += 16; memset(buff, ' ', 80); k = scnpr(buff + 1, blen - 1, "%.2x", a); buff[k + 1] = ' '; } } if (cpos > cpstart) { buff[cpos] = '\0'; if (no_ascii) trimTrailingSpaces(buff); fprintf(fp, "%s\n", buff); } } void dStrHex(const char* str, int len, int no_ascii) { dStrHexFp(str, len, no_ascii, stdout); } void dStrHexErr(const char* str, int len, int no_ascii) { dStrHexFp(str, len, no_ascii, (sg_warnings_strm ? sg_warnings_strm : stderr)); } #define DSHS_LINE_BLEN 160 #define DSHS_BPL 16 /* Read 'len' bytes from 'str' and output as ASCII-Hex bytes (space * separated) to 'b' not to exceed 'b_len' characters. Each line * starts with 'leadin' (NULL for no leadin) and there are 16 bytes * per line with an extra space between the 8th and 9th bytes. 'format' * is 0 for repeat in printable ASCII ('.' for non printable) to * right of each line; 1 don't (so just output ASCII hex). Returns * number of bytes written to 'b' excluding the trailing '\0'. */ int dStrHexStr(const char * str, int len, const char * leadin, int format, int b_len, char * b) { unsigned char c; int bpstart, bpos, k, n, prior_ascii_len; bool want_ascii; char buff[DSHS_LINE_BLEN + 2]; char a[DSHS_BPL + 1]; const char * p = str; if (len <= 0) { if (b_len > 0) b[0] = '\0'; return 0; } if (b_len <= 0) return 0; want_ascii = !format; if (want_ascii) { memset(a, ' ', DSHS_BPL); a[DSHS_BPL] = '\0'; } if (leadin) { bpstart = strlen(leadin); /* Cap leadin at (DSHS_LINE_BLEN - 70) characters */ if (bpstart > (DSHS_LINE_BLEN - 70)) bpstart = DSHS_LINE_BLEN - 70; } else bpstart = 0; bpos = bpstart; prior_ascii_len = bpstart + (DSHS_BPL * 3) + 1; n = 0; memset(buff, ' ', DSHS_LINE_BLEN); buff[DSHS_LINE_BLEN] = '\0'; if (bpstart > 0) memcpy(buff, leadin, bpstart); for (k = 0; k < len; k++) { c = *p++; if (bpos == (bpstart + ((DSHS_BPL / 2) * 3))) bpos++; /* for extra space in middle of each line's hex */ scnpr(buff + bpos, (int)sizeof(buff) - bpos, "%.2x", (int)(unsigned char)c); buff[bpos + 2] = ' '; if (want_ascii) a[k % DSHS_BPL] = my_isprint(c) ? c : '.'; if ((k > 0) && (0 == ((k + 1) % DSHS_BPL))) { trimTrailingSpaces(buff); if (want_ascii) { n += scnpr(b + n, b_len - n, "%-*s %s\n", prior_ascii_len, buff, a); memset(a, ' ', DSHS_BPL); } else n += scnpr(b + n, b_len - n, "%s\n", buff); if (n >= (b_len - 1)) return n; memset(buff, ' ', DSHS_LINE_BLEN); bpos = bpstart; if (bpstart > 0) memcpy(buff, leadin, bpstart); } else bpos += 3; } if (bpos > bpstart) { trimTrailingSpaces(buff); if (want_ascii) n += scnpr(b + n, b_len - n, "%-*s %s\n", prior_ascii_len, buff, a); else n += scnpr(b + n, b_len - n, "%s\n", buff); } return n; } void hex2stdout(const uint8_t * b_str, int len, int no_ascii) { dStrHex((const char *)b_str, len, no_ascii); } void hex2stderr(const uint8_t * b_str, int len, int no_ascii) { dStrHexErr((const char *)b_str, len, no_ascii); } int hex2str(const uint8_t * b_str, int len, const char * leadin, int format, int b_len, char * b) { return dStrHexStr((const char *)b_str, len, leadin, format, b_len, b); } /* Returns true when executed on big endian machine; else returns false. * Useful for displaying ATA identify words (which need swapping on a * big endian machine). */ bool sg_is_big_endian() { union u_t { uint16_t s; unsigned char c[sizeof(uint16_t)]; } u; u.s = 0x0102; return (u.c[0] == 0x01); /* The lowest address contains the most significant byte */ } bool sg_all_zeros(const uint8_t * bp, int b_len) { if ((NULL == bp) || (b_len <= 0)) return false; for (--b_len; b_len >= 0; --b_len) { if (0x0 != bp[b_len]) return false; } return true; } bool sg_all_ffs(const uint8_t * bp, int b_len) { if ((NULL == bp) || (b_len <= 0)) return false; for (--b_len; b_len >= 0; --b_len) { if (0xff != bp[b_len]) return false; } return true; } static uint16_t swapb_uint16(uint16_t u) { uint16_t r; r = (u >> 8) & 0xff; r |= ((u & 0xff) << 8); return r; } /* Note the ASCII-hex output goes to stdout. [Most other output from functions * in this file go to sg_warnings_strm (default stderr).] * 'no_ascii' allows for 3 output types: * > 0 each line has address then up to 8 ASCII-hex 16 bit words * = 0 in addition, the ASCI bytes pairs are listed to the right * = -1 only the ASCII-hex words are listed (i.e. without address) * = -2 only the ASCII-hex words, formatted for "hdparm --Istdin" * < -2 same as -1 * If 'swapb' is true then bytes in each word swapped. Needs to be set * for ATA IDENTIFY DEVICE response on big-endian machines. */ void dWordHex(const uint16_t* words, int num, int no_ascii, bool swapb) { const uint16_t * p = words; uint16_t c; char buff[82]; unsigned char upp, low; int a = 0; const int bpstart = 3; const int cpstart = 52; int cpos = cpstart; int bpos = bpstart; int i, k, blen; if (num <= 0) return; blen = (int)sizeof(buff); memset(buff, ' ', 80); buff[80] = '\0'; if (no_ascii < 0) { for (k = 0; k < num; k++) { c = *p++; if (swapb) c = swapb_uint16(c); bpos += 5; scnpr(buff + bpos, blen - bpos, "%.4x", (unsigned int)c); buff[bpos + 4] = ' '; if ((k > 0) && (0 == ((k + 1) % 8))) { if (-2 == no_ascii) printf("%.39s\n", buff +8); else printf("%.47s\n", buff); bpos = bpstart; memset(buff, ' ', 80); } } if (bpos > bpstart) { if (-2 == no_ascii) printf("%.39s\n", buff +8); else printf("%.47s\n", buff); } return; } /* no_ascii>=0, start each line with address (offset) */ k = scnpr(buff + 1, blen - 1, "%.2x", a); buff[k + 1] = ' '; for (i = 0; i < num; i++) { c = *p++; if (swapb) c = swapb_uint16(c); bpos += 5; scnpr(buff + bpos, blen - bpos, "%.4x", (unsigned int)c); buff[bpos + 4] = ' '; if (no_ascii) { buff[cpos++] = ' '; buff[cpos++] = ' '; buff[cpos++] = ' '; } else { upp = (c >> 8) & 0xff; low = c & 0xff; if (! my_isprint(upp)) upp = '.'; buff[cpos++] = upp; if (! my_isprint(low)) low = '.'; buff[cpos++] = low; buff[cpos++] = ' '; } if (cpos > (cpstart + 23)) { printf("%.76s\n", buff); bpos = bpstart; cpos = cpstart; a += 8; memset(buff, ' ', 80); k = scnpr(buff + 1, blen - 1, "%.2x", a); buff[k + 1] = ' '; } } if (cpos > cpstart) printf("%.76s\n", buff); } /* If the number in 'buf' can be decoded or the multiplier is unknown * then -1 is returned. Accepts a hex prefix (0x or 0X) or a decimal * multiplier suffix (as per GNU's dd (since 2002: SI and IEC 60027-2)). * Main (SI) multipliers supported: K, M, G. Ignore leading spaces and * tabs; accept comma, hyphen, space, tab and hash as terminator. */ int sg_get_num(const char * buf) { int res, num, n, len; unsigned int unum; char * cp; const char * b; char c = 'c'; char c2 = '\0'; /* keep static checker happy */ char c3 = '\0'; /* keep static checker happy */ char lb[16]; if ((NULL == buf) || ('\0' == buf[0])) return -1; len = strlen(buf); n = strspn(buf, " \t"); if (n > 0) { if (n == len) return -1; buf += n; len -= n; } /* following hack to keep C++ happy */ cp = strpbrk((char *)buf, " \t,#-"); if (cp) { len = cp - buf; n = (int)sizeof(lb) - 1; len = (len < n) ? len : n; memcpy(lb, buf, len); lb[len] = '\0'; b = lb; } else b = buf; if (('0' == b[0]) && (('x' == b[1]) || ('X' == b[1]))) { res = sscanf(b + 2, "%x", &unum); num = unum; } else if ('H' == toupper((int)b[len - 1])) { res = sscanf(b, "%x", &unum); num = unum; } else res = sscanf(b, "%d%c%c%c", &num, &c, &c2, &c3); if (res < 1) return -1LL; else if (1 == res) return num; else { if (res > 2) c2 = toupper((int)c2); if (res > 3) c3 = toupper((int)c3); switch (toupper((int)c)) { case 'C': return num; case 'W': return num * 2; case 'B': return num * 512; case 'K': if (2 == res) return num * 1024; if (('B' == c2) || ('D' == c2)) return num * 1000; if (('I' == c2) && (4 == res) && ('B' == c3)) return num * 1024; return -1; case 'M': if (2 == res) return num * 1048576; if (('B' == c2) || ('D' == c2)) return num * 1000000; if (('I' == c2) && (4 == res) && ('B' == c3)) return num * 1048576; return -1; case 'G': if (2 == res) return num * 1073741824; if (('B' == c2) || ('D' == c2)) return num * 1000000000; if (('I' == c2) && (4 == res) && ('B' == c3)) return num * 1073741824; return -1; case 'X': cp = (char *)strchr(b, 'x'); if (NULL == cp) cp = (char *)strchr(b, 'X'); if (cp) { n = sg_get_num(cp + 1); if (-1 != n) return num * n; } return -1; default: pr2ws("unrecognized multiplier\n"); return -1; } } } /* If the number in 'buf' can not be decoded then -1 is returned. Accepts a * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"), * a whitespace or newline as terminator. */ int sg_get_num_nomult(const char * buf) { int res, len, num; unsigned int unum; char * commap; if ((NULL == buf) || ('\0' == buf[0])) return -1; len = strlen(buf); commap = (char *)strchr(buf + 1, ','); if (('0' == buf[0]) && (('x' == buf[1]) || ('X' == buf[1]))) { res = sscanf(buf + 2, "%x", &unum); num = unum; } else if (commap && ('H' == toupper((int)*(commap - 1)))) { res = sscanf(buf, "%x", &unum); num = unum; } else if ((NULL == commap) && ('H' == toupper((int)buf[len - 1]))) { res = sscanf(buf, "%x", &unum); num = unum; } else res = sscanf(buf, "%d", &num); if (1 == res) return num; else return -1; } /* If the number in 'buf' can be decoded or the multiplier is unknown * then -1LL is returned. Accepts a hex prefix (0x or 0X) or a decimal * multiplier suffix (as per GNU's dd (since 2002: SI and IEC 60027-2)). * Main (SI) multipliers supported: K, M, G, T, P. Ignore leading spaces * and tabs; accept comma, hyphen, space, tab and hash as terminator. */ int64_t sg_get_llnum(const char * buf) { int res, len, n; int64_t num, ll; uint64_t unum; char * cp; const char * b; char c = 'c'; char c2 = '\0'; /* keep static checker happy */ char c3 = '\0'; /* keep static checker happy */ char lb[32]; if ((NULL == buf) || ('\0' == buf[0])) return -1LL; len = strlen(buf); n = strspn(buf, " \t"); if (n > 0) { if (n == len) return -1LL; buf += n; len -= n; } /* following hack to keep C++ happy */ cp = strpbrk((char *)buf, " \t,#-"); if (cp) { len = cp - buf; n = (int)sizeof(lb) - 1; len = (len < n) ? len : n; memcpy(lb, buf, len); lb[len] = '\0'; b = lb; } else b = buf; if (('0' == b[0]) && (('x' == b[1]) || ('X' == b[1]))) { res = sscanf(b + 2, "%" SCNx64 , &unum); num = unum; } else if ('H' == toupper((int)b[len - 1])) { res = sscanf(b, "%" SCNx64 , &unum); num = unum; } else res = sscanf(b, "%" SCNd64 "%c%c%c", &num, &c, &c2, &c3); if (res < 1) return -1LL; else if (1 == res) return num; else { if (res > 2) c2 = toupper((int)c2); if (res > 3) c3 = toupper((int)c3); switch (toupper((int)c)) { case 'C': return num; case 'W': return num * 2; case 'B': return num * 512; case 'K': if (2 == res) return num * 1024; if (('B' == c2) || ('D' == c2)) return num * 1000; if (('I' == c2) && (4 == res) && ('B' == c3)) return num * 1024; return -1LL; case 'M': if (2 == res) return num * 1048576; if (('B' == c2) || ('D' == c2)) return num * 1000000; if (('I' == c2) && (4 == res) && ('B' == c3)) return num * 1048576; return -1LL; case 'G': if (2 == res) return num * 1073741824; if (('B' == c2) || ('D' == c2)) return num * 1000000000; if (('I' == c2) && (4 == res) && ('B' == c3)) return num * 1073741824; return -1LL; case 'T': if (2 == res) return num * 1099511627776LL; if (('B' == c2) || ('D' == c2)) return num * 1000000000000LL; if (('I' == c2) && (4 == res) && ('B' == c3)) return num * 1099511627776LL; return -1LL; case 'P': if (2 == res) return num * 1099511627776LL * 1024; if (('B' == c2) || ('D' == c2)) return num * 1000000000000LL * 1000; if (('I' == c2) && (4 == res) && ('B' == c3)) return num * 1099511627776LL * 1024; return -1LL; case 'X': cp = (char *)strchr(b, 'x'); if (NULL == cp) cp = (char *)strchr(b, 'X'); if (cp) { ll = sg_get_llnum(cp + 1); if (-1LL != ll) return num * ll; } return -1LL; default: pr2ws("unrecognized multiplier\n"); return -1LL; } } } /* If the number in 'buf' can not be decoded then -1 is returned. Accepts a * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"), * a whitespace or newline as terminator. Only decimal numbers can represent * negative numbers and '-1' must be treated separately. */ int64_t sg_get_llnum_nomult(const char * buf) { int res, len; int64_t num; uint64_t unum; if ((NULL == buf) || ('\0' == buf[0])) return -1; len = strlen(buf); if (('0' == buf[0]) && (('x' == buf[1]) || ('X' == buf[1]))) { res = sscanf(buf + 2, "%" SCNx64 "", &unum); num = unum; } else if ('H' == toupper(buf[len - 1])) { res = sscanf(buf, "%" SCNx64 "", &unum); num = unum; } else res = sscanf(buf, "%" SCNd64 "", &num); return (1 == res) ? num : -1; } /* Extract character sequence from ATA words as in the model string * in a IDENTIFY DEVICE response. Returns number of characters * written to 'ochars' before 0 character is found or 'num' words * are processed. */ int sg_ata_get_chars(const uint16_t * word_arr, int start_word, int num_words, bool is_big_endian, char * ochars) { int k; uint16_t s; char a, b; char * op = ochars; for (k = start_word; k < (start_word + num_words); ++k) { s = word_arr[k]; if (is_big_endian) { a = s & 0xff; b = (s >> 8) & 0xff; } else { a = (s >> 8) & 0xff; b = s & 0xff; } if (a == 0) break; *op++ = a; if (b == 0) break; *op++ = b; } return op - ochars; } int pr2serr(const char * fmt, ...) { va_list args; int n; va_start(args, fmt); n = vfprintf(stderr, fmt, args); va_end(args); return n; } #ifdef SG_LIB_FREEBSD #include #elif defined(SG_LIB_WIN32) #include static bool got_page_size = false; static uint32_t win_page_size; #endif uint32_t sg_get_page_size(void) { #if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE) return sysconf(_SC_PAGESIZE); /* POSIX.1 (was getpagesize()) */ #elif defined(SG_LIB_WIN32) if (! got_page_size) { SYSTEM_INFO si; GetSystemInfo(&si); win_page_size = si.dwPageSize; got_page_size = true; } return win_page_size; #elif defined(SG_LIB_FREEBSD) return PAGE_SIZE; #else return 4096; /* give up, pick likely figure */ #endif } /* Returns pointer to heap (or NULL) that is aligned to a align_to byte * boundary. Sends back *buff_to_free pointer in third argument that may be * different from the return value. If it is different then the *buff_to_free * pointer should be freed (rather than the returned value) when the heap is * no longer needed. If align_to is 0 then aligns to OS's page size. Sets all * returned heap to zeros. If num_bytes is 0 then set to page size. */ uint8_t * sg_memalign(uint32_t num_bytes, uint32_t align_to, uint8_t ** buff_to_free, bool vb) { size_t psz; uint8_t * res; if (buff_to_free) /* make sure buff_to_free is NULL if alloc fails */ *buff_to_free = NULL; psz = (align_to > 0) ? align_to : sg_get_page_size(); if (0 == num_bytes) num_bytes = psz; /* ugly to handle otherwise */ #ifdef HAVE_POSIX_MEMALIGN { int err; void * wp = NULL; err = posix_memalign(&wp, psz, num_bytes); if (err || (NULL == wp)) { pr2ws("%s: posix_memalign: error [%d], out of memory?\n", __func__, err); return NULL; } memset(wp, 0, num_bytes); if (buff_to_free) *buff_to_free = (uint8_t *)wp; res = (uint8_t *)wp; if (vb) { pr2ws("%s: posix_ma, len=%d, ", __func__, num_bytes); if (buff_to_free) pr2ws("wrkBuffp=%p, ", (void *)res); pr2ws("psz=%u, rp=%p\n", (unsigned int)psz, (void *)res); } return res; } #else { void * wrkBuff; sg_uintptr_t align_1 = psz - 1; wrkBuff = (uint8_t *)calloc(num_bytes + psz, 1); if (NULL == wrkBuff) { if (buff_to_free) *buff_to_free = NULL; return NULL; } else if (buff_to_free) *buff_to_free = (uint8_t *)wrkBuff; res = (uint8_t *)(void *) (((sg_uintptr_t)wrkBuff + align_1) & (~align_1)); if (vb) { pr2ws("%s: hack, len=%d, ", __func__, num_bytes); if (buff_to_free) pr2ws("buff_to_free=%p, ", wrkBuff); pr2ws("align_1=%lu, rp=%p\n", (unsigned long)align_1, (void *)res); } return res; } #endif } const char * sg_lib_version() { return sg_lib_version_str; } #ifdef SG_LIB_MINGW /* Non Unix OSes distinguish between text and binary files. Set text mode on fd. Does nothing in Unix. Returns negative number on failure. */ #include #include int sg_set_text_mode(int fd) { return setmode(fd, O_TEXT); } /* Set binary mode on fd. Does nothing in Unix. Returns negative number on failure. */ int sg_set_binary_mode(int fd) { return setmode(fd, O_BINARY); } #else /* For Unix the following functions are dummies. */ int sg_set_text_mode(int fd) { return fd; /* fd should be >= 0 */ } int sg_set_binary_mode(int fd) { return fd; } #endif