aboutsummaryrefslogtreecommitdiff
path: root/lib/sg_lib.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sg_lib.c')
-rw-r--r--lib/sg_lib.c4088
1 files changed, 4088 insertions, 0 deletions
diff --git a/lib/sg_lib.c b/lib/sg_lib.c
new file mode 100644
index 00000000..36fcf5cd
--- /dev/null
+++ b/lib/sg_lib.c
@@ -0,0 +1,4088 @@
+/*
+ * Copyright (c) 1999-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/* 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 https://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() */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <ctype.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#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 */
+
+typedef unsigned int my_uint; /* convenience to save a few line wraps */
+
+FILE * sg_warnings_strm = NULL; /* would like to default to stderr */
+
+
+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;
+}
+
+/* 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(). Public
+ * declaration in sg_pr2serr.h header */
+int
+sg_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));
+}
+
+/* DSENSE is 'descriptor sense' as opposed to the older 'fixed sense'.
+ * Only (currently) used in SNTL. */
+bool
+sg_get_initial_dsense(void)
+{
+ int k;
+ const char * cp;
+
+ cp = getenv("SG3_UTILS_DSENSE");
+ if (cp) {
+ if (1 == sscanf(cp, "%d", &k))
+ return k ? true : false;
+ }
+ return false;
+}
+
+/* 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 (sg_pdt_s_eq(peri_type, vp->peri_dev_type))
+ return vp;
+ holdp = vp;
+ while ((vp + 1)->name && (value == (vp + 1)->value)) {
+ ++vp;
+ if (sg_pdt_s_eq(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;
+}
+
+/* Take care to minimize printf() parsing delays when printing commands */
+static char bin2hexascii[] = {'0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+
+/* Given a SCSI command pointed to by cdbp of sz bytes this function forms
+ * a SCSI command in ASCII surrounded by square brackets in 'b'. 'b' is at
+ * least blen bytes long. If cmd_name is true then the command is prefixed
+ * by its SCSI command name (e.g. "VERIFY(10) [2f ...]". The command is
+ * shown as spaced separated pairs of hexadecimal digits (i.e. 0-9, a-f).
+ * Each pair represents byte. The leftmost pair of digits is cdbp[0] . If
+ * sz <= 0 then this function tries to guess the length of the command. */
+char *
+sg_get_command_str(const uint8_t * cdbp, int sz, bool cmd_name, int blen,
+ char * b)
+{
+ int k, j, jj;
+
+ if ((cdbp == NULL) || (b == NULL) || (blen < 1))
+ return b;
+ if (cmd_name && (blen > 16)) {
+ sg_get_command_name(cdbp, 0, blen, b);
+ j = (int)strlen(b);
+ if (j < (blen - 1))
+ b[j++] = ' ';
+ } else
+ j = 0;
+ if (j >= blen)
+ goto fini;
+ b[j++] = '[';
+ if (j >= blen)
+ goto fini;
+ if (sz <= 0) {
+ if (SG_VARIABLE_LENGTH_CMD == cdbp[0])
+ sz = cdbp[7] + 8;
+ else
+ sz = sg_get_command_size(cdbp[0]);
+ }
+ jj = j;
+ for (k = 0; (k < sz) && (j < (blen - 3)); ++k, j += 3, ++cdbp) {
+ b[j] = bin2hexascii[(*cdbp >> 4) & 0xf];
+ b[j + 1] = bin2hexascii[*cdbp & 0xf];
+ b[j + 2] = ' ';
+ }
+ if (j > jj)
+ --j; /* don't want trailing space before ']' */
+ if (j >= blen)
+ goto fini;
+ b[j++] = ']';
+fini:
+ if (j >= blen)
+ b[blen - 1] = '\0'; /* truncated string */
+ else
+ b[j] = '\0';
+ return b;
+}
+
+#define CMD_NAME_LEN 128
+
+void
+sg_print_command_len(const uint8_t * cdbp, int sz)
+{
+ char buff[CMD_NAME_LEN];
+
+ sg_get_command_str(cdbp, sz, true, sizeof(buff), buff);
+ pr2ws("%s\n", buff);
+}
+
+void
+sg_print_command(const uint8_t * cdbp)
+{
+ sg_print_command_len(cdbp, 0);
+}
+
+bool
+sg_scsi_status_is_good(int sstatus)
+{
+ sstatus &= 0xfe;
+ switch (sstatus) {
+ case SAM_STAT_GOOD:
+ case SAM_STAT_CONDITION_MET:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool
+sg_scsi_status_is_bad(int sstatus)
+{
+ sstatus &= 0xfe;
+ switch (sstatus) {
+ case SAM_STAT_GOOD:
+ case SAM_STAT_CONDITION_MET:
+ return false;
+ default:
+ return true;
+ }
+}
+
+void
+sg_get_scsi_status_str(int scsi_status, int buff_len, char * buff)
+{
+ const struct sg_lib_simple_value_name_t * sstatus_p;
+
+ if ((NULL == buff) || (buff_len < 1))
+ return;
+ else if (1 == buff_len) {
+ buff[0] = '\0';
+ return;
+ }
+ scsi_status &= 0x7e; /* sanitize as much as possible */
+ for (sstatus_p = sg_lib_sstatus_str_arr; sstatus_p->name; ++sstatus_p) {
+ if (scsi_status == sstatus_p->value)
+ break;
+ }
+ if (sstatus_p->name)
+ sg_scnpr(buff, buff_len, "%s", sstatus_p->name);
+ else
+ sg_scnpr(buff, buff_len, "Unknown status [0x%x]", scsi_status);
+}
+
+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 uint8_t * 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))
+ sg_scnpr(buff, buff_len, "%s", sg_lib_sense_key_desc[sense_key]);
+ else
+ sg_scnpr(buff, buff_len, "invalid value: 0x%x", sense_key);
+ return buff;
+}
+
+/* Yield string associated with ASC/ASCQ values. Returns 'buff'. */
+char *
+sg_get_additional_sense_str(int asc, int ascq, bool add_sense_leadin,
+ int buff_len, char * buff)
+{
+ int k, num, rlen;
+ bool found = false;
+
+ if (1 == buff_len) {
+ buff[0] = '\0';
+ return buff;
+ }
+ for (k = 0; sg_lib_asc_ascq_range[k].text; ++k) {
+ struct sg_lib_asc_ascq_range_t * ei2p = &sg_lib_asc_ascq_range[k];
+
+ if ((ei2p->asc == asc) &&
+ (ascq >= ei2p->ascq_min) &&
+ (ascq <= ei2p->ascq_max)) {
+ found = true;
+ if (add_sense_leadin)
+ num = sg_scnpr(buff, buff_len, "Additional sense: ");
+ else
+ num = 0;
+ rlen = buff_len - num;
+ sg_scnpr(buff + num, ((rlen > 0) ? rlen : 0), ei2p->text, ascq);
+ }
+ }
+ if (found)
+ return buff;
+
+ for (k = 0; sg_lib_asc_ascq[k].text; ++k) {
+ struct sg_lib_asc_ascq_t * eip = &sg_lib_asc_ascq[k];
+
+ if (eip->asc == asc &&
+ eip->ascq == ascq) {
+ found = true;
+ if (add_sense_leadin)
+ sg_scnpr(buff, buff_len, "Additional sense: %s", eip->text);
+ else
+ sg_scnpr(buff, buff_len, "%s", eip->text);
+ }
+ }
+ if (! found) {
+ if (asc >= 0x80)
+ sg_scnpr(buff, buff_len, "vendor specific ASC=%02x, ASCQ=%02x "
+ "(hex)", asc, ascq);
+ else if (ascq >= 0x80)
+ sg_scnpr(buff, buff_len, "ASC=%02x, vendor specific qualification "
+ "ASCQ=%02x (hex)", asc, ascq);
+ else
+ sg_scnpr(buff, buff_len, "ASC=%02x, ASCQ=%02x (hex)", asc, ascq);
+ }
+ 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)
+{
+ return sg_get_additional_sense_str(asc, ascq, true, buff_len, 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 uint8_t *
+sg_scsi_sense_desc_find(const uint8_t * sbp, int sb_len,
+ int desc_type)
+{
+ int add_sb_len, desc_len, k;
+ const uint8_t * 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) {
+ int add_d_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 uint8_t * sbp, int sb_len,
+ uint64_t * info_outp)
+{
+ const uint8_t * bp;
+
+ 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])) {
+ uint64_t 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 uint8_t * sbp, int sb_len,
+ uint64_t * cmd_spec_outp)
+{
+ const uint8_t * 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 uint8_t * sbp, int sb_len,
+ bool * filemark_p, bool * eom_p, bool * ili_p)
+{
+ const uint8_t * 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 uint8_t * sbp, int sb_len,
+ int * progress_outp)
+{
+ const uint8_t * 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 > PDT_MAX))
+ sg_scnpr(buff, buff_len, "bad pdt");
+ else
+ sg_scnpr(buff, buff_len, "%s", sg_lib_pdt_strs[pdt]);
+ return buff;
+}
+
+/* Returns true if left argument is "equal" to the right argument. l_pdt_s
+ * is a compound PDT (SCSI Peripheral Device Type) or a negative number
+ * which represents a wildcard (i.e. match anything). r_pdt_s has a similar
+ * form. PDT values are 5 bits long (0 to 31) and a compound pdt_s is
+ * formed by shifting the second (upper) PDT by eight bits to the left and
+ * OR-ing it with the first PDT. The pdt_s values must be defined so
+ * PDT_DISK (0) is _not_ the upper value in a compound pdt_s. */
+bool
+sg_pdt_s_eq(int l_pdt_s, int r_pdt_s)
+{
+ bool upper_l = !!(l_pdt_s & PDT_UPPER_MASK);
+ bool upper_r = !!(r_pdt_s & PDT_UPPER_MASK);
+
+ if ((l_pdt_s < 0) || (r_pdt_s < 0))
+ return true;
+ if (!upper_l && !upper_r)
+ return l_pdt_s == r_pdt_s;
+ else if (upper_l && upper_r)
+ return (((PDT_UPPER_MASK & l_pdt_s) == (PDT_UPPER_MASK & r_pdt_s)) ||
+ ((PDT_LOWER_MASK & l_pdt_s) == (PDT_LOWER_MASK & r_pdt_s)));
+ else if (upper_l)
+ return (((PDT_LOWER_MASK & l_pdt_s) == r_pdt_s) ||
+ ((PDT_UPPER_MASK & l_pdt_s) >> 8) == r_pdt_s);
+ return (((PDT_LOWER_MASK & r_pdt_s) == l_pdt_s) ||
+ ((PDT_UPPER_MASK & r_pdt_s) >> 8) == l_pdt_s);
+}
+
+int
+sg_lib_pdt_decay(int pdt)
+{
+ if ((pdt < 0) || (pdt > PDT_MAX))
+ 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))
+ sg_scnpr(buff, buff_len, "bad tpi");
+ else
+ sg_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, uint8_t * bp, int bplen,
+ bool only_one, int blen, char * b)
+{
+ int num, k, n;
+ 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; // some old compilers insisted on this */
+ for (k = 0, n = 0; bplen > 0; ++k, bp += bump, bplen -= bump) {
+ int proto_id, normal_len, tpid_format;
+
+ if ((k > 0) && only_one)
+ break;
+ if ((bplen < 24) || (0 != (bplen % 4)))
+ n += sg_scnpr(b + n, blen - n, "%sTransport Id short or not "
+ "multiple of 4 [length=%d]:\n", lip, blen);
+ else
+ n += sg_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 += sg_scnpr(b + n, blen - n, "%s FCP-2 World Wide Name:\n",
+ lip);
+ if (0 != tpid_format)
+ n += sg_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 += sg_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 += sg_scnpr(b + n, blen - n, "%s [Unexpected TPID format: "
+ "%d]\n", lip, tpid_format);
+ n += sg_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 += sg_scnpr(b + n, blen - n, "%s SSA (transport id not "
+ "defined):\n", lip);
+ n += sg_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 += sg_scnpr(b + n, blen - n, "%s IEEE 1394 EUI-64 name:\n",
+ lip);
+ if (0 != tpid_format)
+ n += sg_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 += sg_scnpr(b + n, blen - n, "%s RDMA initiator port "
+ "identifier:\n", lip);
+ if (0 != tpid_format)
+ n += sg_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 += sg_scnpr(b + n, blen - n, "%s iSCSI ", lip);
+ num = sg_get_unaligned_be16(bp + 2);
+ if (0 == tpid_format)
+ n += sg_scnpr(b + n, blen - n, "name: %.*s\n", num, &bp[4]);
+ else if (1 == tpid_format)
+ n += sg_scnpr(b + n, blen - n, "world wide unique port id: "
+ "%.*s\n", num, &bp[4]);
+ else {
+ n += sg_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 += sg_scnpr(b + n, blen - n, "%s SAS address: 0x%" PRIx64 "\n",
+ lip, ull);
+ if (0 != tpid_format)
+ n += sg_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 += sg_scnpr(b + n, blen - n, "%s ADT:\n", lip);
+ n += sg_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 += sg_scnpr(b + n, blen - n, "%s ATAPI:\n", lip);
+ n += sg_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 += sg_scnpr(b + n, blen - n, "%s UAS:\n", lip);
+ n += sg_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 += sg_scnpr(b + n, blen - n, "%s SOP ", lip);
+ num = sg_get_unaligned_be16(bp + 2);
+ if (0 == tpid_format)
+ n += sg_scnpr(b + n, blen - n, "Routing ID: 0x%x\n", num);
+ else {
+ n += sg_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 += sg_scnpr(b + n, blen - n, "%s PCIE:\n", lip);
+ n += sg_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 += sg_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 += sg_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 < (int)SG_ARRAY_SIZE(desig_code_set_str_arr)))
+ 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 < (int)SG_ARRAY_SIZE(desig_assoc_str_arr)))
+ 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 < (int)SG_ARRAY_SIZE(desig_type_str_arr)))
+ return desig_type_str_arr[val];
+ else
+ return NULL;
+}
+
+char *
+sg_get_zone_type_str(uint8_t zt, int buff_len, char * buff)
+{
+ if ((NULL == buff) || (buff_len < 1))
+ return NULL;
+ switch (zt) {
+ case 1:
+ sg_scnpr(buff, buff_len, "conventional");
+ break;
+ case 2:
+ sg_scnpr(buff, buff_len, "sequential write required");
+ break;
+ case 3:
+ sg_scnpr(buff, buff_len, "sequential write preferred");
+ break;
+ case 4:
+ sg_scnpr(buff, buff_len, "sequential or before required");
+ break;
+ case 5:
+ sg_scnpr(buff, buff_len, "gap");
+ break;
+ default:
+ sg_scnpr(buff, buff_len, "unknown [0x%x]", zt);
+ break;
+ }
+ return buff;
+}
+
+
+/* Expects a T10 UUID designator (as found in the Device Identification VPD
+ * page) pointed to by 'dp'. To not produce an error string in 'b', c_set
+ * should be 1 (binary) and dlen should be 18. Currently T10 only supports
+ * locally assigned UUIDs. Writes output to string 'b' of no more than blen
+ * bytes and returns the number of bytes actually written to 'b' but doesn't
+ * count the trailing null character it always appends (if blen > 0). 'lip'
+ * is lead-in string (on each line) than may be NULL. skip_prefix avoids
+ * outputting: ' Locally assigned UUID: ' before the UUID. */
+int
+sg_t10_uuid_desig2str(const uint8_t *dp, int dlen, int c_set, bool do_long,
+ bool skip_prefix, const char * lip /* lead-in */,
+ int blen, char * b)
+{
+ int m;
+ int n = 0;
+
+ if (NULL == lip)
+ lip = "";
+ if (1 != c_set) {
+ n += sg_scnpr(b + n, blen - n, "%s << expected binary "
+ "code_set >>\n", lip);
+ n += hex2str(dp, dlen, lip, 0, blen - n, b + n);
+ return n;
+ }
+ if ((1 != ((dp[0] >> 4) & 0xf)) || (18 != dlen)) {
+ n += sg_scnpr(b + n, blen - n, "%s << expected locally "
+ "assigned UUID, 16 bytes long >>\n", lip);
+ n += hex2str(dp, dlen, lip, 0, blen - n, b + n);
+ return n;
+ }
+ if (skip_prefix) {
+ if (strlen(lip) > 0)
+ n += sg_scnpr(b + n, blen - n, "%s", lip);
+ } else
+ n += sg_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 += sg_scnpr(b + n, blen - n, "-");
+ n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)dp[2 + m]);
+ }
+ n += sg_scnpr(b + n, blen - n, "\n");
+ if (do_long) {
+ n += sg_scnpr(b + n, blen - n, "%s [0x", lip);
+ for (m = 0; m < 16; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)dp[2 + m]);
+ n += sg_scnpr(b + n, blen - n, "]\n");
+ }
+ return n;
+}
+
+int
+sg_get_designation_descriptor_str(const char * lip, const uint8_t * 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;
+ uint64_t ccc_id, vsei;
+ const uint8_t * ip;
+ char e[64];
+ const char * cp;
+
+ n = 0;
+ if (NULL == lip)
+ lip = "";
+ if (dd_len < 4) {
+ n += sg_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 += sg_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 += sg_scnpr(b + n, blen - n, "%s %s:\n", lip, cp);
+ n += sg_scnpr(b + n, blen - n, "%s designator type: ", lip);
+ cp = sg_get_desig_type_str(desig_type);
+ if (cp)
+ n += sg_scnpr(b + n, blen - n, "%s", cp);
+ n += sg_scnpr(b + n, blen - n, ", code set: ");
+ cp = sg_get_desig_code_set_str(c_set);
+ if (cp)
+ n += sg_scnpr(b + n, blen - n, "%s", cp);
+ n += sg_scnpr(b + n, blen - n, "\n");
+ if (piv && ((1 == assoc) || (2 == assoc)))
+ n += sg_scnpr(b + n, blen - n, "%s transport: %s\n", lip,
+ sg_get_trans_proto_str(p_id, sizeof(e), e));
+ 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 += sg_scnpr(b + n, blen - n, "%s vendor specific: %.*s\n",
+ lip, dlen, ip);
+ else {
+ n += sg_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 += sg_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 += sg_scnpr(b + n, blen - n, "%s vendor specific: "
+ "%.*s\n", lip, dlen - 8, ip + 8);
+ } else {
+ n += sg_scnpr(b + n, blen - n, "%s vendor specific: 0x",
+ lip);
+ for (m = 8; m < dlen; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
+ n += sg_scnpr(b + n, blen - n, "\n");
+ }
+ }
+ break;
+ case 2: /* EUI-64 based */
+ if (! do_long) {
+ if ((8 != dlen) && (12 != dlen) && (16 != dlen)) {
+ n += sg_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 += sg_scnpr(b + n, blen - n, "%s 0x", lip);
+ for (m = 0; m < dlen; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
+ n += sg_scnpr(b + n, blen - n, "\n");
+ break;
+ }
+ n += sg_scnpr(b + n, blen - n, "%s EUI-64 based %d byte "
+ "identifier\n", lip, dlen);
+ if (1 != c_set) {
+ n += sg_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) { /* first 8 bytes are 'Identifier Extension' */
+ uint64_t id_ext = sg_get_unaligned_be64(ip);
+
+ ci_off = 8;
+ n += sg_scnpr(b + n, blen - n, "%s Identifier extension: 0x%"
+ PRIx64 "\n", lip, id_ext);
+ } else if ((8 != dlen) && (12 != dlen)) {
+ n += sg_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;
+ }
+ ccc_id = sg_get_unaligned_be64(ip + ci_off);
+ n += sg_scnpr(b + n, blen - n, "%s IEEE identifier: 0x%"
+ PRIx64 "\n", lip, ccc_id);
+ if (12 == dlen) {
+ d_id = sg_get_unaligned_be32(ip + 8);
+ n += sg_scnpr(b + n, blen - n, "%s Directory ID: 0x%x\n",
+ lip, d_id);
+ }
+ break;
+ case 3: /* NAA <n> */
+ if (1 != c_set) {
+ n += sg_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 += sg_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 += sg_scnpr(b + n, blen - n, "%s NAA 2, vendor "
+ "specific identifier A: 0x%x\n", lip, d_id);
+ n += sg_scnpr(b + n, blen - n, "%s AOI: 0x%x\n", lip,
+ c_id);
+ n += sg_scnpr(b + n, blen - n, "%s vendor specific "
+ "identifier B: 0x%x\n", lip, vsi);
+ n += sg_scnpr(b + n, blen - n, "%s [0x", lip);
+ for (m = 0; m < 8; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
+ n += sg_scnpr(b + n, blen - n, "]\n");
+ }
+ n += sg_scnpr(b + n, blen - n, "%s 0x", lip);
+ for (m = 0; m < 8; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
+ n += sg_scnpr(b + n, blen - n, "\n");
+ break;
+ case 3: /* NAA 3: Locally assigned */
+ if (8 != dlen) {
+ n += sg_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 += sg_scnpr(b + n, blen - n, "%s NAA 3, Locally "
+ "assigned:\n", lip);
+ n += sg_scnpr(b + n, blen - n, "%s 0x", lip);
+ for (m = 0; m < 8; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
+ n += sg_scnpr(b + n, blen - n, "\n");
+ break;
+ case 5: /* NAA 5: IEEE Registered */
+ if (8 != dlen) {
+ n += sg_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 += sg_scnpr(b + n, blen - n, "%s NAA 5, AOI: 0x%x\n",
+ lip, c_id);
+ n += sg_scnpr(b + n, blen - n, "%s Vendor Specific "
+ "Identifier: 0x%" PRIx64 "\n", lip, vsei);
+ n += sg_scnpr(b + n, blen - n, "%s [0x", lip);
+ for (m = 0; m < 8; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
+ n += sg_scnpr(b + n, blen - n, "]\n");
+ } else {
+ n += sg_scnpr(b + n, blen - n, "%s 0x", lip);
+ for (m = 0; m < 8; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
+ n += sg_scnpr(b + n, blen - n, "\n");
+ }
+ break;
+ case 6: /* NAA 6: IEEE Registered extended */
+ if (16 != dlen) {
+ n += sg_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 += sg_scnpr(b + n, blen - n, "%s NAA 6, AOI: 0x%x\n",
+ lip, c_id);
+ n += sg_scnpr(b + n, blen - n, "%s Vendor Specific "
+ "Identifier: 0x%" PRIx64 "\n", lip, vsei);
+ vsei = sg_get_unaligned_be64(ip + 8);
+ n += sg_scnpr(b + n, blen - n, "%s Vendor Specific "
+ "Identifier Extension: 0x%" PRIx64 "\n", lip,
+ vsei);
+ n += sg_scnpr(b + n, blen - n, "%s [0x", lip);
+ for (m = 0; m < 16; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
+ n += sg_scnpr(b + n, blen - n, "]\n");
+ } else {
+ n += sg_scnpr(b + n, blen - n, "%s 0x", lip);
+ for (m = 0; m < 16; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
+ n += sg_scnpr(b + n, blen - n, "\n");
+ }
+ break;
+ default:
+ n += sg_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 += sg_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 += sg_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 += sg_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 += sg_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 += sg_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 += sg_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 += sg_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 += sg_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 += sg_scnpr(b + n, blen - n, "%s << expected "
+ "UTF-8, use ASCII >>\n", lip);
+ } else {
+ n += sg_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 += sg_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 += sg_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 += sg_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 += sg_scnpr(b + n, blen - n, "%s USB device address: "
+ "0x%x\n", lip, 0x7f & ip[0]);
+ n += sg_scnpr(b + n, blen - n, "%s USB interface number: "
+ "0x%x\n", lip, ip[2]);
+ } else if (TPROTO_SOP == p_id) {
+ n += sg_scnpr(b + n, blen - n, "%s PCIe routing ID, bus "
+ "number: 0x%x\n", lip, ip[0]);
+ n += sg_scnpr(b + n, blen - n, "%s function number: "
+ "0x%x\n", lip, ip[1]);
+ n += sg_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 += sg_scnpr(b + n, blen - n, "%s >>>> unexpected protocol "
+ "identifier: %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 */
+ n += sg_t10_uuid_desig2str(ip, dlen, c_set, do_long, false, lip,
+ blen - n, b + n);
+ break;
+ default: /* reserved */
+ n += sg_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 uint8_t * 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 += sg_scnpr(b + n, blen - n, "Field pointer: ");
+ goto too_short;
+ }
+ /* abbreviate to fit on one line */
+ n += sg_scnpr(b + n, blen - n, "Field pointer:\n");
+ n += sg_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 += sg_scnpr(b + n, blen - n, " bit %d\n", descp[4] & 0x07);
+ } else
+ n += sg_scnpr(b + n, blen - n, "\n");
+ break;
+ case SPC_SK_HARDWARE_ERROR:
+ case SPC_SK_MEDIUM_ERROR:
+ case SPC_SK_RECOVERED_ERROR:
+ n += sg_scnpr(b + n, blen - n, "Actual retry count: ");
+ if (add_d_len < 6)
+ goto too_short;
+ n += sg_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 += sg_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 += sg_scnpr(b + n, blen - n, "%d.%02d%%\n", pr, rem);
+ break;
+ case SPC_SK_COPY_ABORTED:
+ n += sg_scnpr(b + n, blen - n, "Segment pointer:\n");
+ if (add_d_len < 6)
+ goto too_short;
+ n += sg_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 += sg_scnpr(b + n, blen - n, " bit %d\n", descp[4] & 0x07);
+ else
+ n += sg_scnpr(b + n, blen - n, "\n");
+ break;
+ case SPC_SK_UNIT_ATTENTION:
+ n += sg_scnpr(b + n, blen - n, "Unit attention condition queue:\n");
+ n += sg_scnpr(b + n, blen - n, "%s overflow flag is %d\n", lip,
+ !!(descp[4] & 0x1));
+ break;
+ default:
+ n += sg_scnpr(b + n, blen - n, "Sense_key: 0x%x unexpected\n",
+ sense_key);
+ *processedp = false;
+ break;
+ }
+ return n;
+
+too_short:
+ n += sg_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 sg_scnpr(b, blen, "active/optimized");
+ case TPGS_STATE_NONOPTIMIZED:
+ return sg_scnpr(b, blen, "active/non optimized");
+ case TPGS_STATE_STANDBY:
+ return sg_scnpr(b, blen, "standby");
+ case TPGS_STATE_UNAVAILABLE:
+ return sg_scnpr(b, blen, "unavailable");
+ case TPGS_STATE_OFFLINE:
+ return sg_scnpr(b, blen, "offline");
+ case TPGS_STATE_TRANSITIONING:
+ return sg_scnpr(b, blen, "transitioning between states");
+ default:
+ return sg_scnpr(b, blen, "unknown: 0x%x", st);
+ }
+}
+
+static int
+uds_referral_descriptor_str(char * b, int blen, const uint8_t * dp,
+ int alen, const char * lip)
+{
+ int n = 0;
+ int dlen = alen - 2;
+ int k, j, g, f;
+ const uint8_t * tp;
+ char c[40];
+
+ if (NULL == lip)
+ lip = "";
+ n += sg_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) {
+ int ntpgd = dp[3];
+ uint64_t ull;
+
+ g = (ntpgd * 4) + 20;
+ n += sg_scnpr(b + n, blen - n, "%s Descriptor %d\n", lip, f);
+ if ((k + g) > dlen) {
+ n += sg_scnpr(b + n, blen - n, "%s truncated descriptor, "
+ "stop\n", lip);
+ return n;
+ }
+ ull = sg_get_unaligned_be64(dp + 4);
+ n += sg_scnpr(b + n, blen - n, "%s first uds LBA: 0x%" PRIx64
+ "\n", lip, ull);
+ ull = sg_get_unaligned_be64(dp + 12);
+ n += sg_scnpr(b + n, blen - n, "%s last uds LBA: 0x%" PRIx64
+ "\n", lip, ull);
+ for (j = 0; j < ntpgd; ++j) {
+ tp = dp + 20 + (j * 4);
+ decode_tpgs_state(tp[0] & 0xf, c, sizeof(c));
+ n += sg_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 subsidiary 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). 'leadin' is string prepended to each line written
+ * to 'b', NULL treated as "". Returns the number of bytes written to 'b'
+ * excluding the trailing '\0'. If problem, returns 0. */
+int
+sg_get_sense_descriptors_str(const char * lip, const uint8_t * sbp,
+ int sb_len, int blen, char * b)
+{
+ int add_sb_len, desc_len, k, j, sense_key;
+ int n, progress, pr, rem;
+ uint16_t sct_sc;
+ bool processed;
+ const uint8_t * descp;
+ char z[64];
+ static const char * dtsp = " >> descriptor too short";
+ static const char * eccp = "Extended copy command";
+ static const char * ddp = "destination device";
+
+ if ((NULL == b) || (blen <= 0))
+ return 0;
+ b[0] = '\0';
+ if (lip)
+ sg_scnpr(z, sizeof(z), "%.60s ", lip);
+ else
+ sg_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) {
+ int 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 += sg_scnpr(b + n, blen - n, "%s Descriptor type: ", lip);
+ processed = true;
+ switch (descp[0]) {
+ case 0:
+ n += sg_scnpr(b + n, blen - n, "Information: ");
+ if (add_d_len >= 10) {
+ if (! (0x80 & descp[2]))
+ n += sg_scnpr(b + n, blen - n, "Valid=0 (-> vendor "
+ "specific) ");
+ n += sg_scnpr(b + n, blen - n, "0x");
+ for (j = 0; j < 8; ++j)
+ n += sg_scnpr(b + n, blen - n, "%02x", descp[4 + j]);
+ n += sg_scnpr(b + n, blen - n, "\n");
+ } else {
+ n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
+ processed = false;
+ }
+ break;
+ case 1:
+ n += sg_scnpr(b + n, blen - n, "Command specific: ");
+ if (add_d_len >= 10) {
+ n += sg_scnpr(b + n, blen - n, "0x");
+ for (j = 0; j < 8; ++j)
+ n += sg_scnpr(b + n, blen - n, "%02x", descp[4 + j]);
+ n += sg_scnpr(b + n, blen - n, "\n");
+ } else {
+ n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
+ processed = false;
+ }
+ break;
+ case 2: /* Sense Key Specific */
+ n += sg_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 += sg_scnpr(b + n, blen - n, "Field replaceable unit code: ");
+ if (add_d_len >= 2)
+ n += sg_scnpr(b + n, blen - n, "0x%x\n", descp[3]);
+ else {
+ n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
+ processed = false;
+ }
+ break;
+ case 4:
+ n += sg_scnpr(b + n, blen - n, "Stream commands: ");
+ if (add_d_len >= 2) {
+ if (descp[3] & 0x80)
+ n += sg_scnpr(b + n, blen - n, "FILEMARK");
+ if (descp[3] & 0x40)
+ n += sg_scnpr(b + n, blen - n, "End Of Medium (EOM)");
+ if (descp[3] & 0x20)
+ n += sg_scnpr(b + n, blen - n, "Incorrect Length "
+ "Indicator (ILI)");
+ n += sg_scnpr(b + n, blen - n, "\n");
+ } else {
+ n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
+ processed = false;
+ }
+ break;
+ case 5:
+ n += sg_scnpr(b + n, blen - n, "Block commands: ");
+ if (add_d_len >= 2)
+ n += sg_scnpr(b + n, blen - n, "Incorrect Length Indicator "
+ "(ILI) %s\n",
+ (descp[3] & 0x20) ? "set" : "clear");
+ else {
+ n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
+ processed = false;
+ }
+ break;
+ case 6:
+ n += sg_scnpr(b + n, blen - n, "OSD object identification\n");
+ processed = false;
+ break;
+ case 7:
+ n += sg_scnpr(b + n, blen - n, "OSD response integrity check "
+ "value\n");
+ processed = false;
+ break;
+ case 8:
+ n += sg_scnpr(b + n, blen - n, "OSD attribute identification\n");
+ processed = false;
+ break;
+ case 9: /* this is defined in SAT (SAT-2) */
+ n += sg_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 += sg_scnpr(b + n, blen - n, "extend=%d error=0x%x \n%s"
+ " count=0x%x ", extend, descp[3], lip,
+ count);
+ if (extend)
+ n += sg_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 += sg_scnpr(b + n, blen - n, "lba=0x%02x%02x%02x ",
+ descp[11], descp[9], descp[7]);
+ n += sg_scnpr(b + n, blen - n, "device=0x%x status=0x%x\n",
+ descp[12], descp[13]);
+ } else {
+ n += sg_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 += sg_scnpr(b + n, blen - n, "Another progress indication: ");
+ if (add_d_len < 6) {
+ n += sg_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 += sg_scnpr(b + n, blen - n, "%d.02%d%%\n", pr, rem);
+ n += sg_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 += sg_scnpr(b + n, blen - n, "User data segment referral: ");
+ if (add_d_len < 2) {
+ n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
+ processed = false;
+ break;
+ }
+ n += sg_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 += sg_scnpr(b + n, blen - n, "Forwarded sense data\n");
+ if (add_d_len < 2) {
+ n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
+ processed = false;
+ break;
+ }
+ n += sg_scnpr(b + n, blen - n, "%s FSDT: %s\n", lip,
+ (descp[2] & 0x80) ? "set" : "clear");
+ j = descp[2] & 0xf;
+ n += sg_scnpr(b + n, blen - n, "%s Sense data source: ", lip);
+ switch (j) {
+ case 0:
+ n += sg_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 += sg_scnpr(b + n, blen - n, "%s %s %d\n", eccp, ddp, j - 1);
+ break;
+ default:
+ n += sg_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 += sg_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 += sg_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 += sg_scnpr(b + n, blen - n, "%s", c);
+ n += sg_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 += sg_scnpr(b + n, blen - n, "Direct-access block device\n");
+ if (add_d_len < 28) {
+ n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
+ processed = false;
+ break;
+ }
+ if (0x20 & descp[2])
+ n += sg_scnpr(b + n, blen - n, "%s ILI (incorrect length "
+ "indication) set\n", lip);
+ if (0x80 & descp[4]) {
+ n += sg_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 += sg_scnpr(b + n, blen - n, "%s Field replaceable unit "
+ "code: 0x%x\n", lip, descp[7]);
+ if (0x80 & descp[2]) {
+ n += sg_scnpr(b + n, blen - n, "%s Information: 0x", lip);
+ for (j = 0; j < 8; ++j)
+ n += sg_scnpr(b + n, blen - n, "%02x", descp[8 + j]);
+ n += sg_scnpr(b + n, blen - n, "\n");
+ }
+ n += sg_scnpr(b + n, blen - n, "%s Command specific: 0x", lip);
+ for (j = 0; j < 8; ++j)
+ n += sg_scnpr(b + n, blen - n, "%02x", descp[16 + j]);
+ n += sg_scnpr(b + n, blen - n, "\n");
+ break;
+ case 0xe: /* Added in SPC-5 rev 6 (for Bind/Unbind) */
+ n += sg_scnpr(b + n, blen - n, "Device designation\n");
+ j = (int)SG_ARRAY_SIZE(dd_usage_reason_str_arr);
+ if (descp[3] < j)
+ n += sg_scnpr(b + n, blen - n, "%s Usage reason: %s\n",
+ lip, dd_usage_reason_str_arr[descp[3]]);
+ else
+ n += sg_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 += sg_scnpr(b + n, blen - n, "Microcode activation ");
+ if (add_d_len < 6) {
+ n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
+ processed = false;
+ break;
+ }
+ progress = sg_get_unaligned_be16(descp + 6);
+ n += sg_scnpr(b + n, blen - n, "time: ");
+ if (0 == progress)
+ n += sg_scnpr(b + n, blen - n, "unknown\n");
+ else
+ n += sg_scnpr(b + n, blen - n, "%d seconds\n", progress);
+ break;
+ case 0xde: /* NVME Status Field; vendor (sg3_utils) specific */
+ n += sg_scnpr(b + n, blen - n, "NVMe Status: ");
+ if (add_d_len < 6) {
+ n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
+ processed = false;
+ break;
+ }
+ n += sg_scnpr(b + n, blen - n, "DNR=%d, M=%d, ",
+ (int)!!(0x80 & descp[5]), (int)!!(0x40 & descp[5]));
+ sct_sc = sg_get_unaligned_be16(descp + 6);
+ n += sg_scnpr(b + n, blen - n, "SCT_SC=0x%x\n", sct_sc);
+ if (sct_sc > 0) {
+ char d[80];
+
+ n += sg_scnpr(b + n, blen - n, " %s\n",
+ sg_get_nvme_cmd_status_str(sct_sc, sizeof(d), d));
+ }
+ break;
+ default:
+ if (descp[0] >= 0x80)
+ n += sg_scnpr(b + n, blen - n, "Vendor specific [0x%x]\n",
+ descp[0]);
+ else
+ n += sg_scnpr(b + n, blen - n, "Unknown [0x%x]\n", descp[0]);
+ processed = false;
+ break;
+ }
+ if (! processed) {
+ if (add_d_len > 0) {
+ n += sg_scnpr(b + n, blen - n, "%s ", lip);
+ for (j = 0; j < add_d_len; ++j) {
+ if ((j > 0) && (0 == (j % 24)))
+ n += sg_scnpr(b + n, blen - n, "\n%s ", lip);
+ n += sg_scnpr(b + n, blen - n, "%02x ", descp[j + 2]);
+ }
+ n += sg_scnpr(b + n, blen - n, "\n");
+ }
+ }
+ if (add_d_len < 0)
+ n += sg_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 uint8_t * 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 += sg_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 += sg_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 += sg_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[11], sp[10], sp[9],
+ (lba_upper_nz ? '+' : ' '));
+ return n;
+}
+
+/* Fetch sense information */
+int
+sg_get_sense_str(const char * lip, const uint8_t * sbp, int sb_len,
+ bool raw_sinfo, int cblen, char * cbp)
+{
+ bool descriptor_format = false;
+ bool sdat_ovfl = false;
+ bool valid_info_fld;
+ 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 += sg_scnpr(cbp, cblen, "%s >>> sense buffer empty\n", lip);
+ return n;
+ }
+ resp_code = 0x7f & sbp[0];
+ valid_info_fld = !!(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, <<<deferred>>>";
+ 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, <<<deferred>>>";
+ sdat_ovfl = (sb_len > 4) ? !!(sbp[4] & 0x80) : false;
+ break;
+ case 0x0:
+ ebp = "Response code: 0x0 (?)";
+ break;
+ default:
+ sg_scnpr(ebuff, sizeof(ebuff), "Unknown response code: 0x%x",
+ ssh.response_code);
+ ebp = ebuff;
+ break;
+ }
+ n += sg_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 += sg_scnpr(cbp + n, cblen - n, "%s<<<Sense data overflow "
+ "(SDAT_OVFL)>>>\n", lip);
+ if (descriptor_format) {
+ n += sg_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 += sg_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 += sg_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 += sg_scnpr(b + r, blen - r, "%s", lip);
+ if (len > 6) {
+ info = sg_get_unaligned_be32(sbp + 3);
+ if (valid_info_fld)
+ r += sg_scnpr(b + r, blen - r, " Info fld=0x%x [%u] ",
+ info, info);
+ else if (info > 0)
+ r += sg_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 += sg_scnpr(b + r, blen - r, " FMK");
+ /* current command has read a filemark */
+ if (sbp[2] & 0x40)
+ r += sg_scnpr(b + r, blen - r, " EOM");
+ /* end-of-medium condition exists */
+ if (sbp[2] & 0x20)
+ r += sg_scnpr(b + r, blen - r, " ILI");
+ /* incorrect block length requested */
+ r += sg_scnpr(b + r, blen - r, "\n");
+ } else if (valid_info_fld || (info > 0))
+ r += sg_scnpr(b + r, blen - r, "\n");
+ if ((len >= 14) && sbp[14])
+ r += sg_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 += sg_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 += sg_scnpr(b + r, blen - r, " bit %d\n",
+ sbp[15] & 0x07);
+ else
+ r += sg_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 += sg_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 += sg_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 += sg_scnpr(b + r, blen - r, "%s Segment pointer: ",
+ lip);
+ r += sg_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 += sg_scnpr(b + r, blen - r, " bit %d\n",
+ sbp[15] & 0x07);
+ else
+ r += sg_scnpr(b + r, blen - r, "\n");
+ break;
+ case SPC_SK_UNIT_ATTENTION:
+ r += sg_scnpr(b + r, blen - r, "%s Unit attention "
+ "condition queue: ", lip);
+ r += sg_scnpr(b + r, blen - r, "overflow flag is %d\n",
+ !!(sbp[15] & 0x1));
+ break;
+ default:
+ r += sg_scnpr(b + r, blen - r, "%s Sense_key: 0x%x "
+ "unexpected\n", lip, ssh.sense_key);
+ break;
+ }
+ }
+ if (r > 0)
+ n += sg_scnpr(cbp + n, cblen - n, "%s", b);
+ } else
+ n += sg_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 += sg_scnpr(cbp + n, cblen - n, "%ssense buffer too short (4 "
+ "byte minimum)\n", lip);
+ goto check_raw;
+ }
+ if (0x7f == resp_code) { /* Vendor specific */
+ n += sg_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 += sg_scnpr(b + r, blen - r, "%s", lip);
+ r += sg_scnpr(b + r, blen - r, "Probably uninitialized data.\n%s "
+ "Try to view as SCSI-1 non-extended sense:\n", lip);
+ r += sg_scnpr(b + r, blen - r, " AdValid=%d Error class=%d Error "
+ "code=%d\n", valid_info_fld, ((sbp[0] >> 4) & 0x7),
+ (sbp[0] & 0xf));
+ if (valid_info_fld)
+ sg_scnpr(b + r, blen - r, "%s lba=0x%x\n", lip,
+ sg_get_unaligned_be24(sbp + 1) & 0x1fffff);
+ n += sg_scnpr(cbp + n, cblen - n, "%s\n", b);
+ }
+check_raw:
+ if (raw_sinfo) {
+ int calculated_len;
+ char z[64];
+
+ n += sg_scnpr(cbp + n, cblen - n, "%s Raw sense data (in hex), "
+ "sb_len=%d", lip, sb_len);
+ if (n >= (cblen - 1))
+ return n;
+ if ((sb_len > 7) && (sbp[0] >= 0x70) && (sbp[0] < 0x74)) {
+ calculated_len = sbp[7] + 8;
+ n += sg_scnpr(cbp + n, cblen - n, ", calculated_len=%d\n",
+ calculated_len);
+ } else {
+ calculated_len = sb_len;
+ n += sg_scnpr(cbp + n, cblen - n, "\n");
+ }
+ if (n >= (cblen - 1))
+ return n;
+
+ sg_scnpr(z, sizeof(z), "%.50s ", lip);
+ n += hex2str(sbp, calculated_len, z, -1, cblen - n, cbp + n);
+ }
+ return n;
+}
+
+/* Print sense information */
+void
+sg_print_sense(const char * leadin, const uint8_t * 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, false);
+ if (NULL == cp)
+ return;
+ sg_get_sense_str(leadin, sbp, sb_len, raw_sinfo, pg_sz, cp);
+ pr2ws("%s", cp);
+ free(free_cp);
+}
+
+/* This examines exit_status and if an error message is known it is output
+ * as a string to 'b' and true is returned. If 'longer' is true and extra
+ * information is available then it is added to the output. If no error
+ * message is available a null character is output and false is returned.
+ * If exit_status is zero (no error) and 'longer' is true then the string
+ * 'No errors' is output; if 'longer' is false then a null character is
+ * output; in both cases true is returned. If exit_status is negative then
+ * a null character is output and false is returned. All messages are a
+ * single line (less than 80 characters) with no trailing LF. The output
+ * string including the trailing null character is no longer than b_len.
+ * exit_status represents the Unix exit status available after a utility
+ * finishes executing (for whatever reason). */
+bool sg_exit2str(int exit_status, bool longer, int b_len, char *b)
+{
+ const struct sg_value_2names_t * ess = sg_exit_str_arr;
+
+ if ((b_len < 1) || (NULL == b))
+ return false;
+ /* if there is a valid buffer, initialize it to a valid empty string */
+ b[0] = '\0';
+ if (exit_status < 0)
+ return false;
+ else if ((0 == exit_status) || (SG_LIB_OK_FALSE == exit_status)) {
+ if (longer)
+ goto fini;
+ return true;
+ }
+
+ if ((exit_status > SG_LIB_OS_BASE_ERR) && /* 51 to 96 inclusive */
+ (exit_status < SG_LIB_CAT_MALFORMED)) {
+ snprintf(b, b_len, "%s%s", (longer ? "OS error: " : ""),
+ safe_strerror(exit_status - SG_LIB_OS_BASE_ERR));
+ return true;
+ } else if ((exit_status > 128) && (exit_status < 255)) {
+ snprintf(b, b_len, "Utility stopped/aborted by signal number: %d",
+ exit_status - 128);
+ return true;
+ }
+fini:
+ for ( ; ess->name; ++ess) {
+ if (exit_status == ess->value)
+ break;
+ }
+ if (ess->name) {
+ if (longer && ess->name2)
+ snprintf(b, b_len, "%s, %s", ess->name, ess->name2);
+ else
+ snprintf(b, b_len, "%s", ess->name);
+ return true;
+ }
+ return false;
+}
+
+static bool
+sg_if_can2fp(const char * leadin, int exit_status, FILE * fp)
+{
+ char b[256];
+ const char * s = leadin ? leadin : "";
+
+ if ((0 == exit_status) || (SG_LIB_OK_FALSE == exit_status))
+ return true; /* don't print anything */
+ else if (sg_exit2str(exit_status, false, sizeof(b), b)) {
+ fprintf(fp, "%s%s\n", s, b);
+ return true;
+ } else
+ return false;
+}
+
+/* This examines exit_status and if an error message is known it is output
+ * to stdout/stderr and true is returned. If no error message is
+ * available nothing is output and false is returned. If exit_status is
+ * zero (no error) nothing is output and true is returned. If exit_status
+ * is negative then nothing is output and false is returned. If leadin is
+ * non-NULL then it is printed before the error message. All messages are
+ * a single line with a trailing LF. */
+bool
+sg_if_can2stdout(const char * leadin, int exit_status)
+{
+ return sg_if_can2fp(leadin, exit_status, stdout);
+}
+
+/* See sg_if_can2stdout() comments */
+bool
+sg_if_can2stderr(const char * leadin, int exit_status)
+{
+ return sg_if_can2fp(leadin, exit_status,
+ sg_warnings_strm ? sg_warnings_strm : stderr);
+}
+
+/* If os_err_num is within bounds then the returned value is 'os_err_num +
+ * SG_LIB_OS_BASE_ERR' otherwise SG_LIB_OS_BASE_ERR 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 < 0)
+ return SG_LIB_OS_BASE_ERR;
+ return os_err_num; /* os_err_num of 0 maps to 0 */
+ }
+ if (os_err_num < (SG_LIB_CAT_MALFORMED - SG_LIB_OS_BASE_ERR))
+ return SG_LIB_OS_BASE_ERR + os_err_num;
+ return SG_LIB_OS_BASE_ERR;
+}
+
+static const char * const bad_sense_cat = "Bad sense category";
+
+/* Yield string associated with sense category. Returns 'b' (or pointer
+ * to "Bad sense category" if 'b' is NULL). If sense_cat unknown then
+ * yield "Sense category: <sense_cat_val>" string. The original 'sense
+ * category' concept has been expanded to most detected errors and is
+ * returned by these utilities as their exit status value (an (unsigned)
+ * 8 bit value where 0 means good (i.e. no errors)). Uses sg_exit2str()
+ * function. */
+const char *
+sg_get_category_sense_str(int sense_cat, int b_len, char * b, int verbose)
+{
+ if (NULL == b)
+ return bad_sense_cat;
+ if (b_len <= 0)
+ return b;
+ if (! sg_exit2str(sense_cat, (verbose > 0), b_len, b)) {
+ int n = sg_scnpr(b, b_len, "Sense category: %d", sense_cat);
+
+ if ((0 == verbose) && (n < (b_len - 1)))
+ sg_scnpr(b + n, b_len - n, ", try '-v' option for more "
+ "information");
+ }
+ return b; /* Note that a valid C string is returned in all cases */
+}
+
+/* See description in sg_lib.h header file */
+bool
+sg_scsi_normalize_sense(const uint8_t * 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];
+ sshp->byte4 = sbp[4]; /* bit 7: SDAT_OVFL bit */
+ /* sbp[5] and sbp[6] reserved for descriptor format */
+ } 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];
+ }
+ if (sb_len > 6) { /* lower 3 bytes of INFO field */
+ sshp->byte4 = sbp[4];
+ sshp->byte5 = sbp[5];
+ sshp->byte6 = sbp[6];
+ }
+ }
+ }
+ 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 uint8_t * 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:
+ if ((0x04 == ssh.asc) && (0x0b == ssh.ascq))
+ return SG_LIB_CAT_STANDBY;
+ if ((0x04 == ssh.asc) && (0x0c == ssh.ascq))
+ return SG_LIB_CAT_UNAVAILABLE;
+ 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 if ((0x21 == ssh.asc) && (0x0 == ssh.ascq))
+ return SG_LIB_LBA_OUT_OF_RANGE;
+ 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(uint8_t opcode)
+{
+ switch ((opcode >> 5) & 0x7) {
+ case 0:
+ return 6;
+ case 3: case 5:
+ return 12;
+ case 4:
+ return 16;
+ default: /* 1, 2, 6, 7 */
+ return 10;
+ }
+}
+
+void
+sg_get_command_name(const uint8_t * cdbp, 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 == cdbp) {
+ sg_scnpr(buff, buff_len, "%s", "<null> command pointer");
+ return;
+ }
+ service_action = (SG_VARIABLE_LENGTH_CMD == cdbp[0]) ?
+ sg_get_unaligned_be16(cdbp + 8) : (cdbp[1] & 0x1f);
+ sg_get_opcode_sa_name(cdbp[0], service_action, peri_type, buff_len, buff);
+}
+
+struct op_code2sa_t {
+ int op_code;
+ int pdt_s;
+ struct sg_lib_value_name_t * arr;
+ const char * prefix;
+};
+
+static struct op_code2sa_t op_code2sa_arr[] = {
+ {SG_VARIABLE_LENGTH_CMD, PDT_ALL, sg_lib_variable_length_arr, NULL},
+ {SG_MAINTENANCE_IN, PDT_ALL, sg_lib_maint_in_arr, NULL},
+ {SG_MAINTENANCE_OUT, PDT_ALL, sg_lib_maint_out_arr, NULL},
+ {SG_SERVICE_ACTION_IN_12, PDT_ALL, sg_lib_serv_in12_arr, NULL},
+ {SG_SERVICE_ACTION_OUT_12, PDT_ALL, sg_lib_serv_out12_arr, NULL},
+ {SG_SERVICE_ACTION_IN_16, PDT_ALL, sg_lib_serv_in16_arr, NULL},
+ {SG_SERVICE_ACTION_OUT_16, PDT_ALL, sg_lib_serv_out16_arr, NULL},
+ {SG_SERVICE_ACTION_BIDI, PDT_ALL, sg_lib_serv_bidi_arr, NULL},
+ {SG_PERSISTENT_RESERVE_IN, PDT_ALL, sg_lib_pr_in_arr,
+ "Persistent reserve in"},
+ {SG_PERSISTENT_RESERVE_OUT, PDT_ALL, sg_lib_pr_out_arr,
+ "Persistent reserve out"},
+ {SG_3PARTY_COPY_OUT, PDT_ALL, sg_lib_xcopy_sa_arr, NULL},
+ {SG_3PARTY_COPY_IN, PDT_ALL, sg_lib_rec_copy_sa_arr, NULL},
+ {SG_READ_BUFFER, PDT_ALL, sg_lib_read_buff_arr, "Read buffer(10)"},
+ {SG_READ_BUFFER_16, PDT_ALL, sg_lib_read_buff_arr, "Read buffer(16)"},
+ {SG_READ_ATTRIBUTE, PDT_ALL, sg_lib_read_attr_arr, "Read attribute"},
+ {SG_READ_POSITION, PDT_TAPE, sg_lib_read_pos_arr, "Read position"},
+ {SG_SANITIZE, PDT_DISK_ZBC, sg_lib_sanitize_sa_arr, "Sanitize"},
+ {SG_WRITE_BUFFER, PDT_ALL, sg_lib_write_buff_arr, "Write buffer"},
+ {SG_ZONING_IN, PDT_DISK_ZBC, sg_lib_zoning_in_arr, NULL},
+ {SG_ZONING_OUT, PDT_DISK_ZBC, sg_lib_zoning_out_arr, NULL},
+ {0xffff, -1, NULL, NULL},
+};
+
+void
+sg_get_opcode_sa_name(uint8_t 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 (sg_pdt_s_eq(osp->pdt_s, d_pdt)) {
+ vnp = get_value_name(osp->arr, service_action, peri_type);
+ if (vnp) {
+ if (osp->prefix)
+ sg_scnpr(buff, buff_len, "%s, %s", osp->prefix,
+ vnp->name);
+ else
+ sg_scnpr(buff, buff_len, "%s", vnp->name);
+ } else {
+ sg_get_opcode_name(cmd_byte0, peri_type, sizeof(b), b);
+ sg_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(uint8_t 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) {
+ sg_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)
+ sg_scnpr(buff, buff_len, "%s", vnp->name);
+ else
+ sg_scnpr(buff, buff_len, "Opcode=0x%x", (int)cmd_byte0);
+ break;
+ case 3:
+ sg_scnpr(buff, buff_len, "Reserved [0x%x]", (int)cmd_byte0);
+ break;
+ case 6:
+ case 7:
+ sg_scnpr(buff, buff_len, "Vendor specific [0x%x]", (int)cmd_byte0);
+ break;
+ }
+}
+
+/* Fetch NVMe command name given first byte (byte offset 0 in 64 byte
+ * command) of command. Gets Admin NVMe command name if 'admin' is true
+ * (e.g. opcode=0x6 -> Identify), otherwise gets NVM command set name
+ * (e.g. opcode=0 -> Flush). Returns 'buff'. */
+char *
+sg_get_nvme_opcode_name(uint8_t cmd_byte0, bool admin, int buff_len,
+ char * buff)
+{
+ const struct sg_lib_simple_value_name_t * vnp = admin ?
+ sg_lib_nvme_admin_cmd_arr : sg_lib_nvme_nvm_cmd_arr;
+
+ if ((NULL == buff) || (buff_len < 1))
+ return buff;
+ else if (1 == buff_len) {
+ buff[0] = '\0';
+ return buff;
+ }
+ for ( ; vnp->name; ++vnp) {
+ if (cmd_byte0 == (uint8_t)vnp->value) {
+ snprintf(buff, buff_len, "%s", vnp->name);
+ return buff;
+ }
+ }
+ if (admin) {
+ if (cmd_byte0 >= 0xc0)
+ snprintf(buff, buff_len, "Vendor specific opcode: 0x%x",
+ cmd_byte0);
+ else if (cmd_byte0 >= 0x80)
+ snprintf(buff, buff_len, "Command set specific opcode: 0x%x",
+ cmd_byte0);
+ else
+ snprintf(buff, buff_len, "Unknown opcode: 0x%x", cmd_byte0);
+ } else { /* NVM (non-Admin) command set */
+ if (cmd_byte0 >= 0x80)
+ snprintf(buff, buff_len, "Vendor specific opcode: 0x%x",
+ cmd_byte0);
+ else
+ snprintf(buff, buff_len, "Unknown opcode: 0x%x", cmd_byte0);
+ }
+ return buff;
+}
+
+/* Iterates to next designation descriptor in the device identification
+ * VPD page. The 'initial_desig_desc' should point to start of first
+ * descriptor with 'page_len' being the number of valid bytes in that
+ * and following descriptors. To start, 'off' should point to a negative
+ * value, thereafter it should point to the value yielded by the previous
+ * call. If 0 returned then 'initial_desig_desc + *off' should be a valid
+ * descriptor; returns -1 if normal end condition and -2 for an abnormal
+ * termination. Matches association, designator_type and/or code_set when
+ * any of those values are greater than or equal to zero. */
+int
+sg_vpd_dev_id_iter(const uint8_t * 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 uint8_t * 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 * 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 > PDT_MAX)) ? -2 : peri_type;
+ vnp = get_value_name(sg_lib_scsi_feature_sets, sfs_code, my_pdt);
+ if (vnp && (-2 != my_pdt)) {
+ if (! sg_pdt_s_eq(my_pdt, 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 += sg_scnpr(buff, buff_len, "SPC %s", vnp->name);
+ else
+ n += sg_scnpr(buff, buff_len, "%s", vnp->name);
+ } else
+ n += sg_scnpr(buff, buff_len, "%s", sg_sfs_spc_reserved);
+ } else if (sfs_code < 0x200) { /* SBC Feature Sets */
+ if (vnp) {
+ if (verbose)
+ n += sg_scnpr(buff, buff_len, "SBC %s", vnp->name);
+ else
+ n += sg_scnpr(buff, buff_len, "%s", vnp->name);
+ } else
+ n += sg_scnpr(buff, buff_len, "%s", sg_sfs_sbc_reserved);
+ } else if (sfs_code < 0x300) { /* SSC Feature Sets */
+ if (vnp) {
+ if (verbose)
+ n += sg_scnpr(buff, buff_len, "SSC %s", vnp->name);
+ else
+ n += sg_scnpr(buff, buff_len, "%s", vnp->name);
+ } else
+ n += sg_scnpr(buff, buff_len, "%s", sg_sfs_ssc_reserved);
+ } else if (sfs_code < 0x400) { /* ZBC Feature Sets */
+ if (vnp) {
+ if (verbose)
+ n += sg_scnpr(buff, buff_len, "ZBC %s", vnp->name);
+ else
+ n += sg_scnpr(buff, buff_len, "%s", vnp->name);
+ } else
+ n += sg_scnpr(buff, buff_len, "%s", sg_sfs_zbc_reserved);
+ } else { /* Other SCSI Feature Sets */
+ if (vnp) {
+ if (verbose)
+ n += sg_scnpr(buff, buff_len, "[unrecognized PDT] %s",
+ vnp->name);
+ else
+ n += sg_scnpr(buff, buff_len, "%s", vnp->name);
+ } else
+ n += sg_scnpr(buff, buff_len, "%s", sg_sfs_reserved);
+
+ }
+ if (verbose > 4)
+ pr2ws("%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, hopefully there is a low
+ * probability this function will yield true in that case. */
+bool
+sg_is_scsi_cdb(const uint8_t * cdbp, int clen)
+{
+ uint8_t opcode;
+ uint8_t top3bits;
+
+ if (clen < 6)
+ return false;
+ opcode = cdbp[0];
+ top3bits = opcode >> 5;
+ if (0x3 == top3bits) {
+ int ilen, sa;
+
+ if ((clen < 12) || (clen % 4))
+ return false; /* must be modulo 4 and 12 or more bytes */
+ switch (opcode) {
+ case 0x7e: /* Extended cdb (XCDB) */
+ ilen = 4 + sg_get_unaligned_be16(cdbp + 2);
+ return (ilen == clen);
+ case 0x7f: /* Variable Length cdb */
+ ilen = 8 + cdbp[7];
+ sa = sg_get_unaligned_be16(cdbp + 8);
+ /* service action (sa) 0x0 is reserved */
+ return ((ilen == clen) && sa);
+ default:
+ return false;
+ }
+ } else if (clen <= 16) {
+ switch (clen) {
+ case 6:
+ if (top3bits > 0x5) /* vendor */
+ return true;
+ return (0x0 == top3bits); /* 6 byte cdb */
+ case 10:
+ if (top3bits > 0x5) /* vendor */
+ return true;
+ return ((0x1 == top3bits) || (0x2 == top3bits)); /* 10 byte cdb */
+ case 16:
+ if (top3bits > 0x5) /* vendor */
+ return true;
+ return (0x4 == top3bits); /* 16 byte cdb */
+ case 12:
+ if (top3bits > 0x5) /* vendor */
+ return true;
+ return (0x5 == top3bits); /* 12 byte cdb */
+ default:
+ return false;
+ }
+ }
+ /* NVMe probably falls out here, clen > 16 and (opcode < 0x60 or
+ * opcode > 0x7f). */
+ return false;
+}
+
+/* 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<sct_sc_in_hex>]" 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;
+}
+
+/* Add vendor (sg3_utils) specific sense descriptor for the NVMe Status
+ * field. Assumes descriptor (i.e. not fixed) sense. Assumes sbp has room. */
+void
+sg_nvme_desc2sense(uint8_t * sbp, bool dnr, bool more, uint16_t sct_sc)
+{
+ int len = sbp[7] + 8;
+
+ sbp[len] = 0xde; /* vendor specific descriptor type */
+ sbp[len + 1] = 6; /* descriptor is 8 bytes long */
+ memset(sbp + len + 2, 0, 6);
+ if (dnr)
+ sbp[len + 5] = 0x80;
+ if (more)
+ sbp[len + 5] |= 0x40;
+ sg_put_unaligned_be16(sct_sc, sbp + len + 6);
+ sbp[7] += 8;
+}
+
+/* Build minimum sense buffer, either descriptor type (desc=true) or fixed
+ * type (desc=false). Assume sbp has enough room (8 or 14 bytes
+ * respectively). sbp should have room for 32 or 18 bytes respectively */
+void
+sg_build_sense_buffer(bool desc, uint8_t *sbp, uint8_t skey, uint8_t asc,
+ uint8_t ascq)
+{
+ if (desc) {
+ sbp[0] = 0x72; /* descriptor, current */
+ sbp[1] = skey;
+ sbp[2] = asc;
+ sbp[3] = ascq;
+ sbp[7] = 0;
+ } else {
+ sbp[0] = 0x70; /* fixed, current */
+ sbp[2] = skey;
+ sbp[7] = 0xa; /* Assumes length is 18 bytes */
+ sbp[12] = asc;
+ sbp[13] = ascq;
+ }
+}
+
+/* safe_strerror() contributed by Clayton Weaver <cgweav at email dot com>
+ * 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)
+{
+ char * errstr;
+
+ if (errnum < 0)
+ errnum = -errnum;
+ errstr = strerror(errnum);
+ if (NULL == errstr) {
+ size_t len = strlen(safe_errbuf);
+
+ sg_scnpr(safe_errbuf + len, sizeof(safe_errbuf) - len, "%i", errnum);
+ return safe_errbuf;
+ }
+ return errstr;
+}
+
+static int
+trimTrailingSpaces(char * b)
+{
+ int n = strlen(b);
+
+ while ((n > 0) && (' ' == b[n - 1]))
+ b[--n] = '\0';
+ return n;
+}
+
+/* Read binary starting at 'str' for 'len' bytes and output as ASCII
+ * hexadecinal into file pointer (fp). 16 bytes per line are output with an
+ * additional space between 8th and 9th byte on each line (for readability).
+ * 'no_ascii' selects one of 3 output format types:
+ * > 0 each line has address then up to 16 ASCII-hex bytes
+ * = 0 in addition, the bytes are listed in ASCII to the right
+ * < 0 only the ASCII-hex bytes are listed (i.e. without address) */
+void
+dStrHexFp(const char* str, int len, int no_ascii, FILE * fp)
+{
+ const char * p = str;
+ const char * formatstr;
+ uint8_t 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++;
+ sg_scnpr(&buff[bpos], blen - bpos, "%.2x", (int)(uint8_t)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 = sg_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++;
+ sg_scnpr(&buff[bpos], blen - bpos, "%.2x", (int)(uint8_t)c);
+ buff[bpos + 2] = ' ';
+ if (no_ascii)
+ buff[cpos++] = ' ';
+ else {
+ if (! my_isprint(c))
+ c = '.';
+ buff[cpos++] = c;
+ }
+ if (cpos > (cpstart + 15)) {
+ if (no_ascii)
+ trimTrailingSpaces(buff);
+ fprintf(fp, formatstr, buff);
+ bpos = bpstart;
+ cpos = cpstart;
+ a += 16;
+ memset(buff, ' ', 80);
+ k = sg_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 /* maximum characters per line */
+#define DSHS_BPL 16 /* bytes per line */
+
+/* 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. 'oformat' is 0 for repeat in printable ASCII
+ * ('.' for non printable chars) to right of each line; 1 don't (so just
+ * output ASCII hex). If 'oformat' is 2 output same as 1 but any LFs are
+ * replaced by space (and trailing spaces are trimmed). Note that an address
+ * is not printed on each line preceding the hex data. Returns number of bytes
+ * written to 'b' excluding the trailing '\0'. The only difference between
+ * dStrHexStr() and hex2str() is the type of the first argument. */
+int
+dStrHexStr(const char * str, int len, const char * leadin, int oformat,
+ int b_len, char * b)
+{
+ bool want_ascii = (0 == oformat);
+ int bpstart, bpos, k, n, prior_ascii_len;
+ char buff[DSHS_LINE_BLEN + 2]; /* allow for trailing null */
+ char a[DSHS_BPL + 1]; /* printable ASCII bytes or '.' */
+ const char * p = str;
+ const char * lf_or = (oformat > 1) ? " " : "\n";
+
+ if (len <= 0) {
+ if (b_len > 0)
+ b[0] = '\0';
+ return 0;
+ }
+ if (b_len <= 0)
+ return 0;
+ if (want_ascii) {
+ memset(a, ' ', DSHS_BPL);
+ a[DSHS_BPL] = '\0';
+ }
+ n = 0;
+ bpstart = 0;
+ if (leadin) {
+ if (oformat > 1)
+ n += sg_scnpr(b + n, b_len - n, "%s", leadin);
+ else {
+ bpstart = strlen(leadin);
+ /* Cap leadin at (DSHS_LINE_BLEN - 70) characters */
+ if (bpstart > (DSHS_LINE_BLEN - 70))
+ bpstart = DSHS_LINE_BLEN - 70;
+ }
+ }
+ bpos = bpstart;
+ prior_ascii_len = bpstart + (DSHS_BPL * 3) + 1;
+ memset(buff, ' ', DSHS_LINE_BLEN);
+ buff[DSHS_LINE_BLEN] = '\0';
+ if (bpstart > 0)
+ memcpy(buff, leadin, bpstart);
+ for (k = 0; k < len; k++) {
+ uint8_t c = *p++;
+
+ if (bpos == (bpstart + ((DSHS_BPL / 2) * 3)))
+ bpos++; /* for extra space in middle of each line's hex */
+ sg_scnpr(buff + bpos, (int)sizeof(buff) - bpos, "%.2x",
+ (int)(uint8_t)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 += sg_scnpr(b + n, b_len - n, "%-*s %s\n",
+ prior_ascii_len, buff, a);
+ memset(a, ' ', DSHS_BPL);
+ } else
+ n += sg_scnpr(b + n, b_len - n, "%s%s", buff, lf_or);
+ if (n >= (b_len - 1))
+ goto fini;
+ 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 += sg_scnpr(b + n, b_len - n, "%-*s %s\n", prior_ascii_len,
+ buff, a);
+ else
+ n += sg_scnpr(b + n, b_len - n, "%s%s", buff, lf_or);
+ }
+fini:
+ if (oformat > 1)
+ n = trimTrailingSpaces(b);
+ 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 oformat,
+ int b_len, char * b)
+{
+ return dStrHexStr((const char *)b_str, len, leadin, oformat, b_len, b);
+}
+
+void
+hex2fp(const uint8_t * b_str, int len, const char * leadin, int oformat,
+ FILE * fp)
+{
+ int k, num;
+ char b[800]; /* allow for 4 lines of 16 bytes (in hex) each */
+
+ if (leadin && (strlen(leadin) > 118)) {
+ fprintf(fp, ">>> leadin parameter is too large\n");
+ return;
+ }
+ for (k = 0; k < len; k += num) {
+ num = ((k + 64) < len) ? 64 : (len - k);
+ hex2str(b_str + k, num, leadin, oformat, sizeof(b), b);
+ fprintf(fp, "%s", 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;
+ uint8_t 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];
+ uint8_t 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;
+ sg_scnpr(buff + bpos, blen - bpos, "%.4x", (my_uint)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 = sg_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;
+ sg_scnpr(buff + bpos, blen - bpos, "%.4x", (my_uint)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 = sg_scnpr(buff + 1, blen - 1, "%.2x", a);
+ buff[k + 1] = ' ';
+ }
+ }
+ if (cpos > cpstart)
+ printf("%.76s\n", buff);
+}
+
+/* If the number in 'buf' can not 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.
+ * Handles zero and positive values up to 2**31-1 .
+ * Experimental: left argument (must in with hexadecimal digit) added
+ * to, or multiplied, by right argument. No embedded spaces.
+ * Examples: '3+1k' (evaluates to 1027) and '0x34+1m'. */
+int
+sg_get_num(const char * buf)
+{
+ bool is_hex = false;
+ int res, num, n, len;
+ unsigned int unum;
+ char * cp;
+ const char * b;
+ const char * b2p;
+ 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;
+
+ b2p = b;
+ if (('0' == b[0]) && (('x' == b[1]) || ('X' == b[1]))) {
+ res = sscanf(b + 2, "%x%c", &unum, &c);
+ num = unum;
+ is_hex = true;
+ b2p = b + 2;
+ } 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 -1;
+ else if (1 == res)
+ return num;
+ else {
+ c = toupper((int)c);
+ if (is_hex) {
+ if (! ((c == '+') || (c == 'X')))
+ return -1;
+ }
+ if (res > 2)
+ c2 = toupper((int)c2);
+ if (res > 3)
+ c3 = toupper((int)c3);
+
+ switch (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': /* experimental: multiplication */
+ /* left argument must end with hexadecimal digit */
+ cp = (char *)strchr(b2p, 'x');
+ if (NULL == cp)
+ cp = (char *)strchr(b2p, 'X');
+ if (cp) {
+ n = sg_get_num(cp + 1);
+ if (-1 != n)
+ return num * n;
+ }
+ return -1;
+ case '+': /* experimental: addition */
+ /* left argument must end with hexadecimal digit */
+ cp = (char *)strchr(b2p, '+');
+ 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 not be decoded or the multiplier is unknown
+ * then -1LL is returned. Accepts a hex prefix (0x or 0X), hex suffix
+ * (h or H), 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
+ * and E. Ignore leading spaces and tabs; accept comma, hyphen, space, tab
+ * and hash as terminator. Handles zero and positive values up to 2**63-1 .
+ * Experimental: left argument (must in with hexadecimal digit) added
+ * to, or multiplied by right argument. No embedded spaces.
+ * Examples: '3+1k' (evaluates to 1027) and '0x34+1m'. */
+int64_t
+sg_get_llnum(const char * buf)
+{
+ bool is_hex = false;
+ int res, len, n;
+ int64_t num, ll;
+ uint64_t unum;
+ char * cp;
+ const char * b;
+ const char * b2p;
+ 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 cast 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;
+
+ b2p = b;
+ if (('0' == b[0]) && (('x' == b[1]) || ('X' == b[1]))) {
+ res = sscanf(b + 2, "%" SCNx64 "%c", &unum, &c);
+ num = unum;
+ is_hex = true;
+ b2p = b + 2;
+ } 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 {
+ c = toupper((int)c);
+ if (is_hex) {
+ if (! ((c == '+') || (c == 'X')))
+ return -1;
+ }
+ if (res > 2)
+ c2 = toupper((int)c2);
+ if (res > 3)
+ c3 = toupper((int)c3);
+
+ switch (c) {
+ case 'C':
+ return num;
+ case 'W':
+ return num * 2;
+ case 'B':
+ return num * 512;
+ case 'K': /* kilo or kibi */
+ if (2 == res)
+ return num * 1024;
+ if (('B' == c2) || ('D' == c2))
+ return num * 1000;
+ if (('I' == c2) && (4 == res) && ('B' == c3))
+ return num * 1024; /* KiB */
+ return -1LL;
+ case 'M': /* mega or mebi */
+ if (2 == res)
+ return num * 1048576; /* M */
+ if (('B' == c2) || ('D' == c2))
+ return num * 1000000; /* MB */
+ if (('I' == c2) && (4 == res) && ('B' == c3))
+ return num * 1048576; /* MiB */
+ return -1LL;
+ case 'G': /* giga or gibi */
+ if (2 == res)
+ return num * 1073741824; /* G */
+ if (('B' == c2) || ('D' == c2))
+ return num * 1000000000; /* GB */
+ if (('I' == c2) && (4 == res) && ('B' == c3))
+ return num * 1073741824; /* GiB */
+ return -1LL;
+ case 'T': /* tera or tebi */
+ if (2 == res)
+ return num * 1099511627776LL; /* T */
+ if (('B' == c2) || ('D' == c2))
+ return num * 1000000000000LL; /* TB */
+ if (('I' == c2) && (4 == res) && ('B' == c3))
+ return num * 1099511627776LL; /* TiB */
+ return -1LL;
+ case 'P': /* peta or pebi */
+ 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 'E': /* exa or exbi */
+ if (2 == res)
+ return num * 1099511627776LL * 1024 * 1024;
+ if (('B' == c2) || ('D' == c2))
+ return num * 1000000000000LL * 1000 * 1000;
+ if (('I' == c2) && (4 == res) && ('B' == c3))
+ return num * 1099511627776LL * 1024 * 1024;
+ return -1LL;
+ case 'X': /* experimental: decimal (left arg) multiplication */
+ cp = (char *)strchr(b2p, 'x');
+ if (NULL == cp)
+ cp = (char *)strchr(b2p, 'X');
+ if (cp) {
+ ll = sg_get_llnum(cp + 1);
+ if (-1LL != ll)
+ return num * ll;
+ }
+ return -1LL;
+ case '+': /* experimental: decimal (left arg) addition */
+ cp = (char *)strchr(b2p, '+');
+ 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;
+}
+
+#define MAX_NUM_ASCII_LINES 1048576
+
+/* Read ASCII hex bytes or binary from fname (a file named '-' taken as
+ * stdin). If reading ASCII hex then there should be either one entry per
+ * line or a comma, space, hyphen or tab separated list of bytes. If no_space
+ * is set then a string of ACSII hex digits is expected, 2 perbyte.
+ * Everything from and including a '#' on a line is ignored. Returns 0 if ok,
+ * or an error code. If the error code is SG_LIB_LBA_OUT_OF_RANGE then mp_arr
+ * would be exceeded and both mp_arr and mp_arr_len are written to.
+ * The max_arr_len_and argument may carry extra information: when it
+ * is negative its absolute value is used for the maximum number of bytes to
+ * write to mp_arr _and_ the first hexadecimal value on each line is skipped.
+ * Many hexadecimal output programs place a running address (index) as the
+ * first field on each line. When as_binary and/or no_space are true, the
+ * absolute value of max_arr_len_and is used. */
+int
+sg_f2hex_arr(const char * fname, bool as_binary, bool no_space,
+ uint8_t * mp_arr, int * mp_arr_len, int max_arr_len_and)
+{
+ bool has_stdin, split_line, skip_first, redo_first;
+ int fn_len, in_len, k, j, m, fd, err, max_arr_len;
+ int off = 0;
+ int ret = 0;
+ unsigned int h;
+ const char * lcp;
+ FILE * fp = NULL;
+ struct stat a_stat;
+ char line[512];
+ char carry_over[4];
+
+ if ((NULL == fname) || (NULL == mp_arr) || (NULL == mp_arr_len)) {
+ pr2ws("%s: bad arguments\n", __func__);
+ return SG_LIB_LOGIC_ERROR;
+ }
+ if (max_arr_len_and < 0) {
+ skip_first = true;
+ max_arr_len = -max_arr_len_and;
+ } else {
+ skip_first = false;
+ max_arr_len = max_arr_len_and;
+ }
+ fn_len = strlen(fname);
+ if (0 == fn_len)
+ return SG_LIB_SYNTAX_ERROR;
+ has_stdin = ((1 == fn_len) && ('-' == fname[0])); /* read from stdin */
+ if (as_binary) {
+ if (has_stdin)
+ fd = STDIN_FILENO;
+ else {
+ fd = open(fname, O_RDONLY);
+ if (fd < 0) {
+ err = errno;
+ pr2ws("unable to open binary file %s: %s\n", fname,
+ safe_strerror(err));
+ return sg_convert_errno(err);
+ }
+ }
+ k = read(fd, mp_arr, max_arr_len);
+ if (k <= 0) {
+ if (0 == k) {
+ ret = SG_LIB_FILE_ERROR;
+ pr2ws("read 0 bytes from binary file %s\n", fname);
+ } else {
+ ret = sg_convert_errno(errno);
+ pr2ws("read from binary file %s: %s\n", fname,
+ safe_strerror(errno));
+ }
+ } else if ((k < max_arr_len) && (0 == fstat(fd, &a_stat)) &&
+ S_ISFIFO(a_stat.st_mode)) {
+ /* pipe; keep reading till error or 0 read */
+ while (k < max_arr_len) {
+ m = read(fd, mp_arr + k, max_arr_len - k);
+ if (0 == m)
+ break;
+ if (m < 0) {
+ err = errno;
+ pr2ws("read from binary pipe %s: %s\n", fname,
+ safe_strerror(err));
+ ret = sg_convert_errno(err);
+ break;
+ }
+ k += m;
+ }
+ }
+ if (k >= 0)
+ *mp_arr_len = k;
+ if ((fd >= 0) && (! has_stdin))
+ close(fd);
+ return ret;
+ }
+
+ /* So read the file as ASCII hex */
+ if (has_stdin)
+ fp = stdin;
+ else {
+ fp = fopen(fname, "r");
+ if (NULL == fp) {
+ err = errno;
+ pr2ws("Unable to open %s for reading: %s\n", fname,
+ safe_strerror(err));
+ ret = sg_convert_errno(err);
+ goto fini;
+ }
+ }
+
+ carry_over[0] = 0;
+ for (j = 0; j < MAX_NUM_ASCII_LINES; ++j) {
+ if (NULL == fgets(line, sizeof(line), fp))
+ break;
+ in_len = strlen(line);
+ if (in_len > 0) {
+ if ('\n' == line[in_len - 1]) {
+ --in_len;
+ line[in_len] = '\0';
+ split_line = false;
+ } else
+ split_line = true;
+ }
+ if (in_len < 1) {
+ carry_over[0] = 0;
+ continue;
+ }
+ if (carry_over[0]) {
+ if (isxdigit(line[0])) {
+ carry_over[1] = line[0];
+ carry_over[2] = '\0';
+ if (1 == sscanf(carry_over, "%4x", &h)) {
+ if (off > 0) {
+ if (off > max_arr_len) {
+ pr2ws("%s: array length exceeded\n", __func__);
+ ret = SG_LIB_LBA_OUT_OF_RANGE;
+ *mp_arr_len = max_arr_len;
+ goto fini;
+ } else
+ mp_arr[off - 1] = h; /* back up and overwrite */
+ }
+ } else {
+ pr2ws("%s: carry_over error ['%s'] around line %d\n",
+ __func__, carry_over, j + 1);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto fini;
+ }
+ lcp = line + 1;
+ --in_len;
+ } else
+ lcp = line;
+ carry_over[0] = 0;
+ } else
+ lcp = line;
+
+ m = strspn(lcp, " \t");
+ if (m == in_len)
+ continue;
+ lcp += m;
+ in_len -= m;
+ if ('#' == *lcp)
+ continue;
+ k = strspn(lcp, "0123456789aAbBcCdDeEfF ,-\t");
+ if ((k < in_len) && ('#' != lcp[k]) && ('\r' != lcp[k])) {
+ pr2ws("%s: syntax error at line %d, pos %d\n", __func__,
+ j + 1, m + k + 1);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto fini;
+ }
+ if (no_space) {
+ for (k = 0; isxdigit(*lcp) && isxdigit(*(lcp + 1));
+ ++k, lcp += 2) {
+ if (1 != sscanf(lcp, "%2x", &h)) {
+ pr2ws("%s: bad hex number in line %d, pos %d\n",
+ __func__, j + 1, (int)(lcp - line + 1));
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto fini;
+ }
+ if ((off + k) >= max_arr_len) {
+ pr2ws("%s: array length exceeded\n", __func__);
+ *mp_arr_len = max_arr_len;
+ ret = SG_LIB_LBA_OUT_OF_RANGE;
+ goto fini;
+ } else
+ mp_arr[off + k] = h;
+ }
+ if (isxdigit(*lcp) && (! isxdigit(*(lcp + 1))))
+ carry_over[0] = *lcp;
+ off += k;
+ } else { /* (white)space separated ASCII hexadecimal bytes */
+ for (redo_first = false, k = 0; k < 1024;
+ k = (redo_first ? k : k + 1)) {
+ if (1 == sscanf(lcp, "%10x", &h)) {
+ if (h > 0xff) {
+ pr2ws("%s: hex number larger than 0xff in line "
+ "%d, pos %d\n", __func__, j + 1,
+ (int)(lcp - line + 1));
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto fini;
+ }
+ if (split_line && (1 == strlen(lcp))) {
+ /* single trailing hex digit might be a split pair */
+ carry_over[0] = *lcp;
+ }
+ if ((off + k) >= max_arr_len) {
+ pr2ws("%s: array length exceeded\n", __func__);
+ ret = SG_LIB_LBA_OUT_OF_RANGE;
+ *mp_arr_len = max_arr_len;
+ goto fini;
+ } else if ((0 == k) && skip_first && (! redo_first))
+ redo_first = true;
+ else {
+ redo_first = false;
+ mp_arr[off + k] = h;
+ }
+ lcp = strpbrk(lcp, " ,-\t");
+ if (NULL == lcp)
+ break;
+ lcp += strspn(lcp, " ,-\t");
+ if ('\0' == *lcp)
+ break;
+ } else {
+ if (('#' == *lcp) || ('\r' == *lcp)) {
+ --k;
+ break;
+ }
+ pr2ws("%s: error in line %d, at pos %d\n", __func__,
+ j + 1, (int)(lcp - line + 1));
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto fini;
+ }
+ }
+ off += (k + 1);
+ }
+ } /* end of per line loop */
+ if (j >= MAX_NUM_ASCII_LINES) {
+ pr2ws("%s: wow, more than %d lines of ASCII, give up\n", __func__,
+ SG_LIB_LBA_OUT_OF_RANGE);
+ return SG_LIB_LBA_OUT_OF_RANGE;
+ }
+ *mp_arr_len = off;
+ if (fp && (! has_stdin))
+ fclose(fp);
+ return 0;
+fini:
+ if (fp && (! has_stdin))
+ fclose(fp);
+ return ret;
+}
+
+/* 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;
+ char * op = ochars;
+
+ for (k = start_word; k < (start_word + num_words); ++k) {
+ char a, b;
+ uint16_t 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;
+}
+
+#ifdef SG_LIB_FREEBSD
+#include <sys/param.h>
+#elif defined(SG_LIB_WIN32)
+#include <windows.h>
+#endif
+
+uint32_t
+sg_get_page_size(void)
+{
+#if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE)
+ {
+ long res = sysconf(_SC_PAGESIZE); /* POSIX.1 (was getpagesize()) */
+
+ return (res <= 0) ? 4096 : res;
+ }
+#elif defined(SG_LIB_WIN32)
+ static bool got_page_size = false;
+ static uint32_t win_page_size;
+
+ 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
+}
+
+#if defined(SG_LIB_WIN32)
+#if defined(MSC_VER) || defined(__MINGW32__)
+/* windows.h already included above */
+#define sg_sleep_for(seconds) Sleep( (seconds) * 1000)
+#else
+#define sg_sleep_for(seconds) sleep(seconds)
+#endif
+#else
+#define sg_sleep_for(seconds) sleep(seconds)
+#endif
+
+void
+sg_sleep_secs(int num_secs)
+{
+ sg_sleep_for(num_secs);
+}
+
+void
+sg_warn_and_wait(const char * cmd_name, const char * dev_name,
+ bool stress_all)
+{
+ int k, j;
+ const char * stressp = stress_all ? "ALL d" : "D";
+ const char * will_mayp = stress_all ? "will" : "may";
+
+ for (k = 0, j = 15; k < 3; ++k, j -= 5) {
+ printf("\nA %s command will commence in %d seconds\n", cmd_name, j);
+ printf(" %sata on %s %s be DESTROYED%s\n", stressp, dev_name,
+ will_mayp, (stress_all ? "" : " or modified"));
+ printf(" Press control-C to abort\n");
+ sg_sleep_secs(5);
+ }
+ sg_sleep_secs(1);
+}
+
+/* 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;
+
+ 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;
+ uint8_t * res;
+ 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;
+ uint8_t * res;
+ 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=%" PRIuPTR "u, rp=%p\n", align_1, (void *)res);
+ }
+ return res;
+ }
+#endif
+}
+
+/* If byte_count is 0 or less then the OS page size is used as denominator.
+ * Returns true if the remainder of ((unsigned)pointer % byte_count) is 0,
+ * else returns false. */
+bool
+sg_is_aligned(const void * pointer, int byte_count)
+{
+ return 0 == ((sg_uintptr_t)pointer %
+ ((byte_count > 0) ? (uint32_t)byte_count :
+ sg_get_page_size()));
+}
+
+/* Does similar job to sg_get_unaligned_be*() but this function starts at
+ * a given start_bit (i.e. within byte, so 7 is MSbit of byte and 0 is LSbit)
+ * offset. Maximum number of num_bits is 64. For example, these two
+ * invocations are equivalent (and should yield the same result);
+ * sg_get_big_endian(from_bp, 7, 16)
+ * sg_get_unaligned_be16(from_bp) */
+uint64_t
+sg_get_big_endian(const uint8_t * from_bp, int start_bit /* 0 to 7 */,
+ int num_bits /* 1 to 64 */)
+{
+ uint64_t res;
+ int sbit_o1 = start_bit + 1;
+
+ res = (*from_bp++ & ((1 << sbit_o1) - 1));
+ num_bits -= sbit_o1;
+ while (num_bits > 0) {
+ res <<= 8;
+ res |= *from_bp++;
+ num_bits -= 8;
+ }
+ if (num_bits < 0)
+ res >>= (-num_bits);
+ return res;
+}
+
+/* Does similar job to sg_put_unaligned_be*() but this function starts at
+ * a given start_bit offset. Maximum number of num_bits is 64. Preserves
+ * residual bits in partially written bytes. start_bit 7 is MSb. */
+void
+sg_set_big_endian(uint64_t val, uint8_t * to,
+ int start_bit /* 0 to 7 */, int num_bits /* 1 to 64 */)
+{
+ int sbit_o1 = start_bit + 1;
+ int mask, num, k, x;
+
+ if ((NULL == to) || (start_bit > 7) || (num_bits > 64)) {
+ pr2ws("%s: bad args: start_bit=%d, num_bits=%d\n", __func__,
+ start_bit, num_bits);
+ return;
+ }
+ mask = (8 != sbit_o1) ? ((1 << sbit_o1) - 1) : 0xff;
+ k = start_bit - ((num_bits - 1) % 8);
+ if (0 != k)
+ val <<= ((k > 0) ? k : (8 + k));
+ num = (num_bits + 15 - sbit_o1) / 8;
+ for (k = 0; k < num; ++k) {
+ if ((sbit_o1 - num_bits) > 0)
+ mask &= ~((1 << (sbit_o1 - num_bits)) - 1);
+ if (k < (num - 1))
+ x = (val >> ((num - k - 1) * 8)) & 0xff;
+ else
+ x = val & 0xff;
+ to[k] = (to[k] & ~mask) | (x & mask);
+ mask = 0xff;
+ num_bits -= sbit_o1;
+ sbit_o1 = 8;
+ }
+}
+
+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 <fcntl.h>
+
+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