aboutsummaryrefslogtreecommitdiff
path: root/src/sg_inq.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sg_inq.c')
-rw-r--r--src/sg_inq.c4881
1 files changed, 4881 insertions, 0 deletions
diff --git a/src/sg_inq.c b/src/sg_inq.c
new file mode 100644
index 00000000..bcf5960e
--- /dev/null
+++ b/src/sg_inq.c
@@ -0,0 +1,4881 @@
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ * Copyright (C) 2000-2022 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program outputs information provided by a SCSI INQUIRY command.
+ * It is mainly based on the SCSI SPC-6 document at https://www.t10.org .
+ *
+ * Acknowledgment:
+ * - Martin Schwenke <martin at meltin dot net> added the raw switch and
+ * other improvements [20020814]
+ * - Lars Marowsky-Bree <lmb at suse dot de> contributed Unit Path Report
+ * VPD page decoding for EMC CLARiiON devices [20041016]
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <errno.h>
+
+#ifdef SG_LIB_LINUX
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <linux/hdreg.h>
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+#include "sg_cmds_basic.h"
+#include "sg_pt.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+#if (HAVE_NVME && (! IGNORE_NVME))
+#include "sg_pt_nvme.h"
+#endif
+
+#include "sg_vpd_common.h" /* for shared VPD page processing with sg_vpd */
+
+static const char * version_str = "2.31 20220915"; /* spc6r06, sbc5r03 */
+
+#define MY_NAME "sg_inq"
+
+/* INQUIRY notes:
+ * It is recommended that the initial allocation length given to a
+ * standard INQUIRY is 36 (bytes), especially if this is the first
+ * SCSI command sent to a logical unit. This is compliant with SCSI-2
+ * and another major operating system. There are devices out there
+ * that use one of the SCSI commands sets and lock up if they receive
+ * an allocation length other than 36. This technique is sometimes
+ * referred to as a "36 byte INQUIRY".
+ *
+ * A "standard" INQUIRY is one that has the EVPD and the CmdDt bits
+ * clear.
+ *
+ * When doing device discovery on a SCSI transport (e.g. bus scanning)
+ * the first SCSI command sent to a device should be a standard (36
+ * byte) INQUIRY.
+ *
+ * The allocation length field in the INQUIRY command was changed
+ * from 1 to 2 bytes in SPC-3, revision 9, 17 September 2002.
+ * Be careful using allocation lengths greater than 252 bytes, especially
+ * if the lower byte is 0x0 (e.g. a 512 byte allocation length may
+ * not be a good arbitrary choice (as 512 == 0x200) ).
+ *
+ * From SPC-3 revision 16 the CmdDt bit in an INQUIRY is obsolete. There
+ * is now a REPORT SUPPORTED OPERATION CODES command that yields similar
+ * information [MAINTENANCE IN, service action = 0xc]; see sg_opcodes.
+ */
+
+// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< TESTING
+// #undef SG_SCSI_STRINGS
+// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< TESTING
+
+#define VPD_NOPE_WANT_STD_INQ -2 /* request for standard inquiry */
+
+/* Vendor specific VPD pages (typically >= 0xc0) */
+#define VPD_UPR_EMC 0xc0
+#define VPD_RDAC_VERS 0xc2
+#define VPD_RDAC_VAC 0xc9
+
+/* values for selection one or more associations (2**vpd_assoc),
+ except _AS_IS */
+#define VPD_DI_SEL_LU 1
+#define VPD_DI_SEL_TPORT 2
+#define VPD_DI_SEL_TARGET 4
+#define VPD_DI_SEL_AS_IS 32
+
+#define DEF_ALLOC_LEN 252 /* highest 1 byte value that is modulo 4 */
+#define SAFE_STD_INQ_RESP_LEN 36
+#define MX_ALLOC_LEN (0xc000 + 0x80)
+#define VPD_ATA_INFO_LEN 572
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define INQUIRY_CMD 0x12
+#define INQUIRY_CMDLEN 6
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+
+
+uint8_t * rsp_buff;
+
+static uint8_t * free_rsp_buff;
+static const int rsp_buff_sz = MX_ALLOC_LEN + 1;
+
+static char xtra_buff[MX_ALLOC_LEN + 1];
+static char usn_buff[MX_ALLOC_LEN + 1];
+
+static const char * find_version_descriptor_str(int value);
+static void decode_dev_ids(const char * leadin, uint8_t * buff, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static int vpd_decode(int sg_fd, struct opts_t * op, sgj_opaque_p jop,
+ int off);
+
+// Test define that will only work for Linux
+// #define HDIO_GET_IDENTITY 1
+
+#if defined(SG_LIB_LINUX) && defined(SG_SCSI_STRINGS) && \
+ defined(HDIO_GET_IDENTITY)
+#include <sys/ioctl.h>
+
+static int try_ata_identify(int ata_fd, int do_hex, int do_raw,
+ int verbose);
+static void prepare_ata_identify(const struct opts_t * op, int inhex_len);
+#endif
+
+
+/* Note that this table is sorted by acronym */
+static struct svpd_values_name_t t10_vpd_pg[] = {
+ {VPD_AUTOMATION_DEV_SN, 0, 1, "adsn", "Automation device serial "
+ "number (SSC)"},
+ {VPD_ATA_INFO, 0, -1, "ai", "ATA information (SAT)"},
+ {VPD_BLOCK_DEV_CHARS, 0, 0, "bdc",
+ "Block device characteristics (SBC)"},
+ {VPD_BLOCK_DEV_C_EXTENS, 0, 0, "bdce", "Block device characteristics "
+ "extension (SBC)"},
+ {VPD_BLOCK_LIMITS, 0, 0, "bl", "Block limits (SBC)"},
+ {VPD_BLOCK_LIMITS_EXT, 0, 0, "ble", "Block limits extension (SBC)"},
+ {VPD_CFA_PROFILE_INFO, 0, 0, "cfa", "CFA profile information"},
+ {VPD_CON_POS_RANGE, 0, 0, "cpr", "Concurrent positioning ranges "
+ "(SBC)"},
+ {VPD_DEVICE_CONSTITUENTS, 0, -1, "dc", "Device constituents"},
+ {VPD_DEVICE_ID, 0, -1, "di", "Device identification"},
+#if 0 /* following found in sg_vpd */
+ {VPD_DEVICE_ID, VPD_DI_SEL_AS_IS, -1, "di_asis", "Like 'di' "
+ "but designators ordered as found"},
+ {VPD_DEVICE_ID, VPD_DI_SEL_LU, -1, "di_lu", "Device identification, "
+ "lu only"},
+ {VPD_DEVICE_ID, VPD_DI_SEL_TPORT, -1, "di_port", "Device "
+ "identification, target port only"},
+ {VPD_DEVICE_ID, VPD_DI_SEL_TARGET, -1, "di_target", "Device "
+ "identification, target device only"},
+#endif
+ {VPD_EXT_INQ, 0, -1, "ei", "Extended inquiry data"},
+ {VPD_FORMAT_PRESETS, 0, 0, "fp", "Format presets"},
+ {VPD_LB_PROTECTION, 0, 0, "lbpro", "Logical block protection (SSC)"},
+ {VPD_LB_PROVISIONING, 0, 0, "lbpv", "Logical block provisioning "
+ "(SBC)"},
+ {VPD_MAN_ASS_SN, 0, 1, "mas", "Manufacturer assigned serial number (SSC)"},
+ {VPD_MAN_ASS_SN, 0, 0x12, "masa",
+ "Manufacturer assigned serial number (ADC)"},
+ {VPD_MAN_NET_ADDR, 0, -1, "mna", "Management network addresses"},
+ {VPD_MODE_PG_POLICY, 0, -1, "mpp", "Mode page policy"},
+ {VPD_POWER_CONDITION, 0, -1, "po", "Power condition"},/* "pc" in sg_vpd */
+ {VPD_POWER_CONSUMPTION, 0, -1, "psm", "Power consumption"},
+ {VPD_PROTO_LU, 0, -1, "pslu", "Protocol-specific logical unit "
+ "information"},
+ {VPD_PROTO_PORT, 0, -1, "pspo", "Protocol-specific port information"},
+ {VPD_REFERRALS, 0, 0, "ref", "Referrals (SBC)"},
+ {VPD_SA_DEV_CAP, 0, 1, "sad",
+ "Sequential access device capabilities (SSC)"},
+ {VPD_SUP_BLOCK_LENS, 0, 0, "sbl", "Supported block lengths and "
+ "protection types (SBC)"},
+ {VPD_SCSI_FEATURE_SETS, 0, -1, "sfs", "SCSI Feature sets"},
+ {VPD_SOFTW_INF_ID, 0, -1, "sii", "Software interface identification"},
+ {VPD_NOPE_WANT_STD_INQ, 0, -1, "sinq", "Standard inquiry data format"},
+ {VPD_UNIT_SERIAL_NUM, 0, -1, "sn", "Unit serial number"},
+ {VPD_SCSI_PORTS, 0, -1, "sp", "SCSI ports"},
+ {VPD_SUPPORTED_VPDS, 0, -1, "sv", "Supported VPD pages"},
+ {VPD_TA_SUPPORTED, 0, 1, "tas", "TapeAlert supported flags (SSC)"},
+ {VPD_3PARTY_COPY, 0, -1, "tpc", "Third party copy"},
+ {VPD_ZBC_DEV_CHARS, 0, 0, "zbdch", "Zoned block device "
+ "characteristics"},
+ {0, 0, 0, NULL, NULL},
+};
+
+/* Some alternate acronyms for T10 VPD pages (compatibility with sg_vpd) */
+static struct svpd_values_name_t alt_t10_vpd_pg[] = {
+ {VPD_NOPE_WANT_STD_INQ, 0, -1, "stdinq", "Standard inquiry data format"},
+ {VPD_POWER_CONDITION, 0, -1, "pc", "Power condition"},
+ {0, 0, 0, NULL, NULL},
+};
+
+static struct svpd_values_name_t vs_vpd_pg[] = {
+ /* Following are vendor specific */
+ {SG_NVME_VPD_NICR, 0, -1, "nicr",
+ "NVMe Identify Controller Response (sg3_utils)"},
+ {VPD_RDAC_VAC, 0, -1, "rdac_vac", "RDAC volume access control (RDAC)"},
+ {VPD_RDAC_VERS, 0, -1, "rdac_vers", "RDAC software version (RDAC)"},
+ {VPD_UPR_EMC, 0, -1, "upr", "Unit path report (EMC)"},
+ {0, 0, 0, NULL, NULL},
+};
+
+static struct option long_options[] = {
+#if defined(SG_LIB_LINUX) && defined(SG_SCSI_STRINGS) && \
+ defined(HDIO_GET_IDENTITY)
+ {"ata", no_argument, 0, 'a'},
+#endif
+ {"block", required_argument, 0, 'B'},
+ {"cmddt", no_argument, 0, 'c'},
+ {"descriptors", no_argument, 0, 'd'},
+ {"export", no_argument, 0, 'u'},
+ {"extended", no_argument, 0, 'x'},
+ {"force", no_argument, 0, 'f'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"id", no_argument, 0, 'i'},
+ {"inhex", required_argument, 0, 'I'},
+ {"len", required_argument, 0, 'l'},
+ {"long", no_argument, 0, 'L'},
+ {"maxlen", required_argument, 0, 'm'},
+#ifdef SG_SCSI_STRINGS
+ {"new", no_argument, 0, 'N'},
+ {"old", no_argument, 0, 'O'},
+#endif
+ {"only", no_argument, 0, 'o'},
+ {"page", required_argument, 0, 'p'},
+ {"raw", no_argument, 0, 'r'},
+ {"sinq_inraw", required_argument, 0, 'Q'},
+ {"sinq-inraw", required_argument, 0, 'Q'},
+ {"vendor", no_argument, 0, 's'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {"vpd", no_argument, 0, 'e'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage()
+{
+#if defined(SG_LIB_LINUX) && defined(SG_SCSI_STRINGS) && \
+ defined(HDIO_GET_IDENTITY)
+
+ pr2serr("Usage: sg_inq [--ata] [--block=0|1] [--cmddt] [--descriptors] "
+ "[--export]\n"
+ " [--extended] [--help] [--hex] [--id] "
+ "[--inhex=FN]\n"
+ " [--json[=JO]] [--len=LEN] [--long] "
+ "[--maxlen=LEN]\n"
+ " [--only] [--page=PG] [--raw] [--sinq_inraw=RFN] "
+ "[--vendor]\n"
+ " [--verbose] [--version] [--vpd] DEVICE\n"
+ " where:\n"
+ " --ata|-a treat DEVICE as (directly attached) ATA "
+ "device\n");
+#else
+ pr2serr("Usage: sg_inq [--block=0|1] [--cmddt] [--descriptors] "
+ "[--export]\n"
+ " [--extended] [--help] [--hex] [--id] "
+ "[--inhex=FN]\n"
+ " [--json[=JO]] [--len=LEN] [--long] "
+ "[--maxlen=LEN]\n"
+ " [--only] [--page=PG] [--raw] [--sinq_inraw=RFN] "
+ "[--verbose]\n"
+ " [--version] [--vpd] DEVICE\n"
+ " where:\n");
+#endif
+ pr2serr(" --block=0|1 0-> open(non-blocking); 1-> "
+ "open(blocking)\n"
+ " -B 0|1 (def: depends on OS; Linux pt: 0)\n"
+ " --cmddt|-c command support data mode (set opcode "
+ "with '--page=PG')\n"
+ " use twice for list of supported "
+ "commands; obsolete\n"
+ " --descriptors|-d fetch and decode version descriptors\n"
+ " --export|-u SCSI_IDENT_<assoc>_<type>=<ident> output "
+ "format.\n"
+ " Defaults to device id page (0x83) if --page "
+ "not given,\n"
+ " only supported for VPD pages 0x80 and 0x83\n"
+ " --extended|-E|-x decode extended INQUIRY data VPD page "
+ "(0x86)\n"
+ " --force|-f skip VPD page 0 check; directly fetch "
+ "requested page\n"
+ " --help|-h print usage message then exit\n"
+ " --hex|-H output response in hex\n"
+ " --id|-i decode device identification VPD page "
+ "(0x83)\n"
+ " --inhex=FN|-I FN read ASCII hex from file FN instead of "
+ "DEVICE;\n"
+ " if used with --raw then read binary "
+ "from FN\n"
+ " --json[=JO]|-j[JO] output in JSON instead of human "
+ "readable text.\n"
+ " Use --json=? for JSON help\n"
+ " --len=LEN|-l LEN requested response length (def: 0 "
+ "-> fetch 36\n"
+ " bytes first, then fetch again as "
+ "indicated)\n"
+ " --long|-L supply extra information on NVMe devices\n"
+ " --maxlen=LEN|-m LEN same as '--len='\n"
+ " --old|-O use old interface (use as first option)\n"
+ " --only|-o for std inquiry do not fetch serial number "
+ "vpd page;\n"
+ " for NVMe device only do Identify "
+ "controller\n"
+ " --page=PG|-p PG Vital Product Data (VPD) page number "
+ "or\n"
+ " abbreviation (opcode number if "
+ "'--cmddt' given)\n"
+ " --raw|-r output response in binary (to stdout)\n"
+ " --sinq_inraw=RFN|-Q RFN read raw (binary) standard "
+ "INQUIRY\n"
+ " response from the RFN filename\n"
+ " --vendor|-s show vendor specific fields in std "
+ "inquiry\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string then exit\n"
+ " --vpd|-e vital product data (set page with "
+ "'--page=PG')\n\n"
+ "Sends a SCSI INQUIRY command to the DEVICE and decodes the "
+ "response.\nAlternatively it decodes the INQUIRY response held "
+ "in file FN. If no\noptions given then it sends a 'standard' "
+ "INQUIRY command to DEVICE. Can\nlist VPD pages with '--vpd' or "
+ "'--page=PG' option.\n");
+}
+
+#ifdef SG_SCSI_STRINGS
+static void
+usage_old()
+{
+#ifdef SG_LIB_LINUX
+ pr2serr("Usage: sg_inq [-a] [-A] [-b] [-B=0|1] [-c] [-cl] [-d] [-e] "
+ "[-h]\n"
+ " [-H] [-i] [-I=FN] [-j[=JO]] [-l=LEN] [-L] [-m] "
+ "[-M]\n"
+ " [-o] [-p=VPD_PG] [-P] [-r] [-s] [-u] [-U] [-v] "
+ "[-V]\n"
+ " [-x] [-36] [-?] DEVICE\n"
+ " where:\n"
+ " -a decode ATA information VPD page (0x89)\n"
+ " -A treat <device> as (directly attached) ATA device\n");
+#else
+ pr2serr("Usage: sg_inq [-a] [-b] [-B 0|1] [-c] [-cl] [-d] [-e] [-h] "
+ "[-H]\n"
+ " [-i] [-l=LEN] [-L] [-m] [-M] [-o] "
+ "[-p=VPD_PG]\n"
+ " [-P] [-r] [-s] [-u] [-v] [-V] [-x] [-36] "
+ "[-?]\n"
+ " DEVICE\n"
+ " where:\n"
+ " -a decode ATA information VPD page (0x89)\n");
+
+#endif /* SG_LIB_LINUX */
+ pr2serr(" -b decode Block limits VPD page (0xb0) (SBC)\n"
+ " -B=0|1 0-> open(non-blocking); 1->open(blocking)\n"
+ " -c set CmdDt mode (use -o for opcode) [obsolete]\n"
+ " -cl list supported commands using CmdDt mode [obsolete]\n"
+ " -d decode: version descriptors or VPD page\n"
+ " -e set VPD mode (use -p for page code)\n"
+ " -h output in hex (ASCII to the right)\n"
+ " -H output in hex (ASCII to the right) [same as '-h']\n"
+ " -i decode device identification VPD page (0x83)\n"
+ " -I=FN use ASCII hex in file FN instead of DEVICE\n"
+ " -j[=JO] output in JSON instead of human readable "
+ "text.\n"
+ " -l=LEN requested response length (def: 0 "
+ "-> fetch 36\n"
+ " bytes first, then fetch again as "
+ "indicated)\n"
+ " -L supply extra information on NVMe devices\n"
+ " -m decode management network addresses VPD page "
+ "(0x85)\n"
+ " -M decode mode page policy VPD page (0x87)\n"
+ " -N|--new use new interface\n"
+ " -o for std inquiry only do that, not serial number vpd "
+ "page\n"
+ " -p=VPD_PG vpd page code in hex (def: 0)\n"
+ " -P decode Unit Path Report VPD page (0xc0) (EMC)\n"
+ " -r output response in binary ('-rr': output for hdparm)\n"
+ " -s decode SCSI Ports VPD page (0x88)\n"
+ " -u SCSI_IDENT_<assoc>_<type>=<ident> output format\n"
+ " -v verbose (output cdb and, if non-zero, resid)\n"
+ " -V output version string\n"
+ " -x decode extended INQUIRY data VPD page (0x86)\n"
+ " -36 perform standard INQUIRY with a 36 byte response\n"
+ " -? output this usage message\n\n"
+ "If no options given then sends a standard SCSI INQUIRY "
+ "command and\ndecodes the response.\n");
+}
+
+static void
+usage_for(const struct opts_t * op)
+{
+ if (op->opt_new)
+ usage();
+ else
+ usage_old();
+}
+
+#else /* SG_SCSI_STRINGS */
+
+static void
+usage_for(const struct opts_t * op)
+{
+ if (op) { } /* suppress warning */
+ usage();
+}
+
+#endif /* SG_SCSI_STRINGS */
+
+/* Processes command line options according to new option format. Returns
+ * 0 is ok, else SG_LIB_SYNTAX_ERROR is returned. */
+static int
+new_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ int c, n;
+
+ while (1) {
+ int option_index = 0;
+
+#ifdef SG_LIB_LINUX
+#ifdef SG_SCSI_STRINGS
+ c = getopt_long(argc, argv, "aB:cdeEfhHiI:j::l:Lm:M:NoOp:Q:rsuvVx",
+ long_options, &option_index);
+#else
+ c = getopt_long(argc, argv, "B:cdeEfhHiI:j::l:Lm:M:op:Q:rsuvVx",
+ long_options, &option_index);
+#endif /* SG_SCSI_STRINGS */
+#else /* SG_LIB_LINUX */
+#ifdef SG_SCSI_STRINGS
+ c = getopt_long(argc, argv, "B:cdeEfhHiI:j::l:Lm:M:NoOp:Q:rsuvVx",
+ long_options, &option_index);
+#else
+ c = getopt_long(argc, argv, "B:cdeEfhHiI:j::l:Lm:M:op:Q:rsuvVx",
+ long_options, &option_index);
+#endif /* SG_SCSI_STRINGS */
+#endif /* SG_LIB_LINUX */
+ if (c == -1)
+ break;
+
+ switch (c) {
+#if defined(SG_LIB_LINUX) && defined(SG_SCSI_STRINGS) && \
+ defined(HDIO_GET_IDENTITY)
+ case 'a':
+ op->do_ata = true;
+ break;
+#endif
+ case 'B':
+ if ('-' == optarg[0])
+ n = -1;
+ else {
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 1)) {
+ pr2serr("bad argument to '--block=' want 0 or 1\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ op->do_block = n;
+ break;
+ case 'c':
+ ++op->do_cmddt;
+ break;
+ case 'd':
+ op->do_descriptors = true;
+ break;
+ case 'e':
+ op->do_vpd = true;
+ break;
+ case 'E': /* --extended */
+ case 'x':
+ op->do_decode = true;
+ op->do_vpd = true;
+ op->vpd_pn = VPD_EXT_INQ;
+ op->page_given = true;
+ break;
+ case 'f':
+ op->do_force = true;
+ break;
+ case 'h':
+ ++op->do_help;
+ break;
+ case 'j':
+ if (! sgj_init_state(&op->json_st, optarg)) {
+ int bad_char = op->json_st.first_bad_char;
+ char e[1500];
+
+ if (bad_char) {
+ pr2serr("bad argument to --json= option, unrecognized "
+ "character '%c'\n\n", bad_char);
+ }
+ sg_json_usage(0, e, sizeof(e));
+ pr2serr("%s", e);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'o':
+ op->do_only = true;
+ break;
+ case '?':
+ if (! op->do_help)
+ ++op->do_help;
+ break;
+ case 'H':
+ ++op->do_hex;
+ break;
+ case 'i':
+ op->do_decode = true;
+ op->do_vpd = true;
+ op->vpd_pn = VPD_DEVICE_ID;
+ op->page_given = true;
+ break;
+ case 'I':
+ op->inhex_fn = optarg;
+ break;
+ case 'l':
+ case 'm':
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 65532)) {
+ pr2serr("bad argument to '--len='\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((n > 0) && (n < 4)) {
+ pr2serr("Changing that '--maxlen=' value to 4\n");
+ n = 4;
+ }
+ op->maxlen = n;
+ break;
+ case 'M':
+ if (op->vend_prod) {
+ pr2serr("only one '--vendor=' option permitted\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ } else
+ op->vend_prod = optarg;
+ break;
+ case 'L':
+ ++op->do_long;
+ break;
+#ifdef SG_SCSI_STRINGS
+ case 'N':
+ break; /* ignore */
+ case 'O':
+ op->opt_new = false;
+ return 0;
+#endif
+ case 'p':
+ op->page_str = optarg;
+ op->page_given = true;
+ break;
+ case 'Q':
+ op->sinq_inraw_fn = optarg;
+ break;
+ case 'r':
+ ++op->do_raw;
+ break;
+ case 's':
+ ++op->do_vendor;
+ break;
+ case 'u':
+ op->do_export = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ default:
+ pr2serr("unrecognised option code %c [0x%x]\n", c, c);
+ if (op->do_help)
+ break;
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == op->device_name) {
+ op->device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ return 0;
+}
+
+#ifdef SG_SCSI_STRINGS
+/* Processes command line options according to old option format. Returns
+ * 0 is ok, else SG_LIB_SYNTAX_ERROR is returned. */
+static int
+old_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ bool jmp_out;
+ int k, plen, num, n;
+ const char * cp;
+
+ for (k = 1; k < argc; ++k) {
+ cp = argv[k];
+ plen = strlen(cp);
+ if (plen <= 0)
+ continue;
+ if ('-' == *cp) {
+ for (--plen, ++cp, jmp_out = false; plen > 0; --plen, ++cp) {
+ switch (*cp) {
+ case '3':
+ if ('6' == *(cp + 1)) {
+ op->maxlen = 36;
+ --plen;
+ ++cp;
+ } else
+ jmp_out = true;
+ break;
+ case 'a':
+ op->vpd_pn = VPD_ATA_INFO;
+ op->do_vpd = true;
+ op->page_given = true;
+ ++op->num_pages;
+ break;
+#ifdef SG_LIB_LINUX
+ case 'A':
+ op->do_ata = true;
+ break;
+#endif
+ case 'b':
+ op->vpd_pn = VPD_BLOCK_LIMITS;
+ op->do_vpd = true;
+ op->page_given = true;
+ ++op->num_pages;
+ break;
+ case 'c':
+ ++op->do_cmddt;
+ if ('l' == *(cp + 1)) {
+ ++op->do_cmddt;
+ --plen;
+ ++cp;
+ }
+ break;
+ case 'd':
+ op->do_descriptors = true;
+ op->do_decode = true;
+ break;
+ case 'e':
+ op->do_vpd = true;
+ break;
+ case 'f':
+ op->do_force = true;
+ break;
+ case 'h':
+ case 'H':
+ ++op->do_hex;
+ break;
+ case 'i':
+ op->vpd_pn = VPD_DEVICE_ID;
+ op->do_vpd = true;
+ op->page_given = true;
+ ++op->num_pages;
+ break;
+ case 'L':
+ ++op->do_long;
+ break;
+ case 'm':
+ op->vpd_pn = VPD_MAN_NET_ADDR;
+ op->do_vpd = true;
+ ++op->num_pages;
+ op->page_given = true;
+ break;
+ case 'M':
+ op->vpd_pn = VPD_MODE_PG_POLICY;
+ op->do_vpd = true;
+ op->page_given = true;
+ ++op->num_pages;
+ break;
+ case 'N':
+ op->opt_new = true;
+ return 0;
+ case 'o':
+ op->do_only = true;
+ break;
+ case 'O':
+ break;
+ case 'P':
+ op->vpd_pn = VPD_UPR_EMC;
+ op->do_vpd = true;
+ op->page_given = true;
+ ++op->num_pages;
+ break;
+ case 'r':
+ ++op->do_raw;
+ break;
+ case 's':
+ op->vpd_pn = VPD_SCSI_PORTS;
+ op->do_vpd = true;
+ op->page_given = true;
+ ++op->num_pages;
+ break;
+ case 'u':
+ op->do_export = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case 'x':
+ op->vpd_pn = VPD_EXT_INQ;
+ op->do_vpd = true;
+ op->page_given = true;
+ ++op->num_pages;
+ break;
+ case '?':
+ if (! op->do_help)
+ ++op->do_help;
+ break;
+ default:
+ jmp_out = true;
+ break;
+ }
+ if (jmp_out)
+ break;
+ }
+ if (plen <= 0)
+ continue;
+ else if (0 == strncmp("B=", cp, 2)) {
+ num = sscanf(cp + 2, "%d", &n);
+ if ((1 != num) || (n < 0) || (n > 1)) {
+ pr2serr("'B=' option expects 0 or 1\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->do_block = n;
+ } else if (0 == strncmp("I=", cp, 2))
+ op->inhex_fn = cp + 2;
+ else if ('j' == *cp) { /* handle either '-j' or '-j=<JO>' */
+ const char * c2p = (('=' == *(cp + 1)) ? cp + 2 : NULL);
+
+ if (! sgj_init_state(&op->json_st, c2p)) {
+ int bad_char = op->json_st.first_bad_char;
+ char e[1500];
+
+ if (bad_char) {
+ pr2serr("bad argument to --json= option, unrecognized "
+ "character '%c'\n\n", bad_char);
+ }
+ sg_json_usage(0, e, sizeof(e));
+ pr2serr("%s", e);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strncmp("l=", cp, 2)) {
+ num = sscanf(cp + 2, "%d", &n);
+ if ((1 != num) || (n < 1)) {
+ pr2serr("Inappropriate value after 'l=' option\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ } else if (n > MX_ALLOC_LEN) {
+ pr2serr("value after 'l=' option too large\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((n > 0) && (n < 4)) {
+ pr2serr("Changing that '-l=' value to 4\n");
+ n = 4;
+ }
+ op->maxlen = n;
+ } else if (0 == strncmp("p=", cp, 2)) {
+ op->page_str = cp + 2;
+ op->page_given = true;
+ } else if (0 == strncmp("-old", cp, 4))
+ ;
+ else if (jmp_out) {
+ pr2serr("Unrecognized option: %s\n", cp);
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == op->device_name)
+ op->device_name = cp;
+ else {
+ pr2serr("too many arguments, got: %s, not expecting: %s\n",
+ op->device_name, cp);
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ return 0;
+}
+
+/* Process command line options. First check using new option format unless
+ * the SG3_UTILS_OLD_OPTS environment variable is defined which causes the
+ * old option format to be checked first. Both new and old format can be
+ * countermanded by a '-O' and '-N' options respectively. As soon as either
+ * of these options is detected (when processing the other format), processing
+ * stops and is restarted using the other format. Clear? */
+static int
+parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ int res;
+ char * cp;
+
+ cp = getenv("SG3_UTILS_OLD_OPTS");
+ if (cp) {
+ op->opt_new = false;
+ res = old_parse_cmd_line(op, argc, argv);
+ if ((0 == res) && op->opt_new)
+ res = new_parse_cmd_line(op, argc, argv);
+ } else {
+ op->opt_new = true;
+ res = new_parse_cmd_line(op, argc, argv);
+ if ((0 == res) && (! op->opt_new))
+ res = old_parse_cmd_line(op, argc, argv);
+ }
+ return res;
+}
+
+#else /* SG_SCSI_STRINGS */
+
+static int
+parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ return new_parse_cmd_line(op, argc, argv);
+}
+
+#endif /* SG_SCSI_STRINGS */
+
+
+static const struct svpd_values_name_t *
+sdp_find_vpd_by_acron(const char * ap)
+{
+ const struct svpd_values_name_t * vnp;
+
+ for (vnp = t10_vpd_pg; vnp->acron; ++vnp) {
+ if (0 == strcmp(vnp->acron, ap))
+ return vnp;
+ }
+ for (vnp = alt_t10_vpd_pg; vnp->acron; ++vnp) {
+ if (0 == strcmp(vnp->acron, ap))
+ return vnp;
+ }
+ for (vnp = vs_vpd_pg; vnp->acron; ++vnp) {
+ if (0 == strcmp(vnp->acron, ap))
+ return vnp;
+ }
+ return NULL;
+}
+
+static void
+enumerate_vpds()
+{
+ const struct svpd_values_name_t * vnp;
+
+ printf("T10 defined VPD pages:\n");
+ for (vnp = t10_vpd_pg; vnp->acron; ++vnp) {
+ if (vnp->name) {
+ if (vnp->value < 0)
+ printf(" %-10s -1 %s\n", vnp->acron, vnp->name);
+ else
+ printf(" %-10s 0x%02x %s\n", vnp->acron, vnp->value,
+ vnp->name);
+ }
+ }
+ printf("Vendor specific VPD pages:\n");
+ for (vnp = vs_vpd_pg; vnp->acron; ++vnp) {
+ if (vnp->name) {
+ if (vnp->value < 0)
+ printf(" %-10s -1 %s\n", vnp->acron, vnp->name);
+ else
+ printf(" %-10s 0x%02x %s\n", vnp->acron, vnp->value,
+ vnp->name);
+ }
+ }
+}
+
+static void
+dStrRaw(const char * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+/* Strip initial and trailing whitespaces; convert one or repeated
+ * whitespaces to a single "_"; convert non-printable characters to "."
+ * and if there are no valid (i.e. printable) characters return 0.
+ * Process 'str' in place (i.e. it's input and output) and return the
+ * length of the output, excluding the trailing '\0'. To cover any
+ * potential unicode string an intermediate zero is skipped; two
+ * consecutive zeroes indicate a string termination.
+ */
+static int
+encode_whitespaces(uint8_t *str, int inlen)
+{
+ int k, res;
+ int j;
+ bool valid = false;
+ int outlen = inlen, zeroes = 0;
+
+ /* Skip initial whitespaces */
+ for (j = 0; (j < inlen) && isblank(str[j]); ++j)
+ ;
+ if (j < inlen) {
+ /* Skip possible unicode prefix characters */
+ for ( ; (j < inlen) && (str[j] < 0x20); ++j)
+ ;
+ }
+ k = j;
+ /* Strip trailing whitespaces */
+ while ((outlen > k) &&
+ (isblank(str[outlen - 1]) || ('\0' == str[outlen - 1]))) {
+ str[outlen - 1] = '\0';
+ outlen--;
+ }
+ for (res = 0; k < outlen; ++k) {
+ if (isblank(str[k])) {
+ if ((res > 0) && ('_' != str[res - 1])) {
+ str[res++] = '_';
+ valid = true;
+ }
+ zeroes = 0;
+ } else if (! isprint(str[k])) {
+ if (str[k] == 0x00) {
+ /* Stop on more than one consecutive zero */
+ if (zeroes)
+ break;
+ zeroes++;
+ continue;
+ }
+ str[res++] = '.';
+ zeroes = 0;
+ } else {
+ str[res++] = str[k];
+ valid = true;
+ zeroes = 0;
+ }
+ }
+ if (! valid)
+ res = 0;
+ if (res < inlen)
+ str[res] = '\0';
+ return res;
+}
+
+static int
+encode_unicode(uint8_t *str, int inlen)
+{
+ int k = 0, res;
+ int zeroes = 0;
+
+ for (res = 0; k < inlen; ++k) {
+ if (str[k] == 0x00) {
+ if (zeroes) {
+ str[res++] = '\0';
+ break;
+ }
+ zeroes++;
+ } else {
+ zeroes = 0;
+ if (isprint(str[k]))
+ str[res++] = str[k];
+ else
+ str[res++] = ' ';
+ }
+ }
+
+ return res;
+}
+
+static int
+encode_string(char *out, const uint8_t *in, int inlen)
+{
+ int i, j = 0;
+
+ for (i = 0; (i < inlen); ++i) {
+ if (isblank(in[i]) || !isprint(in[i])) {
+ sprintf(&out[j], "\\x%02x", in[i]);
+ j += 4;
+ } else {
+ out[j] = in[i];
+ j++;
+ }
+ }
+ out[j] = '\0';
+ return j;
+}
+
+static const struct svpd_values_name_t *
+get_vpd_page_info(int vpd_page_num, int dev_pdt)
+{
+ int decay_pdt;
+ const struct svpd_values_name_t * vnp;
+ const struct svpd_values_name_t * prev_vnp;
+
+ if (vpd_page_num < 0xb0) { /* take T10 first match */
+ for (vnp = t10_vpd_pg; vnp->acron; ++vnp) {
+ if (vnp->value == vpd_page_num)
+ return vnp;
+ }
+ return NULL;
+ } else if (vpd_page_num < 0xc0) {
+ for (vnp = t10_vpd_pg; vnp->acron; ++vnp) {
+ if (vnp->value == vpd_page_num)
+ break;
+ }
+ if (NULL == vnp->acron)
+ return NULL;
+ if (vnp->pdt == dev_pdt) /* exact match */
+ return vnp;
+ prev_vnp = vnp;
+
+ for (++vnp; vnp->acron; ++vnp) {
+ if (vnp->value == vpd_page_num)
+ break;
+ }
+ decay_pdt = sg_lib_pdt_decay(dev_pdt);
+ if (NULL == vnp->acron) {
+ if (decay_pdt == prev_vnp->pdt)
+ return prev_vnp;
+ return NULL;
+ }
+ if ((vnp->pdt == dev_pdt) || (vnp->pdt == decay_pdt))
+ return vnp;
+ if (decay_pdt == prev_vnp->pdt)
+ return prev_vnp;
+
+ for (++vnp; vnp->acron; ++vnp) {
+ if (vnp->value == vpd_page_num)
+ break;
+ }
+ if (NULL == vnp->acron)
+ return NULL;
+ if ((vnp->pdt == dev_pdt) || (vnp->pdt == decay_pdt))
+ return vnp;
+ return NULL; /* give up */
+ } else { /* vendor specific: vpd >= 0xc0 */
+ for (vnp = vs_vpd_pg; vnp->acron; ++vnp) {
+ if (vnp->pdt == dev_pdt)
+ return vnp;
+ }
+ return NULL;
+ }
+}
+
+static int
+svpd_inhex_decode_all(struct opts_t * op, sgj_opaque_p jop)
+{
+ int k, res, pn;
+ int max_pn = 255;
+ int bump, off;
+ int in_len = op->maxlen;
+ int prev_pn = -1;
+ sgj_state * jsp = &op->json_st;
+ uint8_t vpd0_buff[512];
+ uint8_t * rp = vpd0_buff;
+
+ if (op->vpd_pn > 0)
+ max_pn = op->vpd_pn;
+
+ res = 0;
+ if (op->page_given && (VPD_NOPE_WANT_STD_INQ == op->vpd_pn))
+ return vpd_decode(-1, op, jop, 0);
+
+ for (k = 0, off = 0; off < in_len; ++k, off += bump) {
+ rp = rsp_buff + off;
+ pn = rp[1];
+ bump = sg_get_unaligned_be16(rp + 2) + 4;
+ if ((off + bump) > in_len) {
+ pr2serr("%s: page 0x%x size (%d) exceeds buffer\n", __func__,
+ pn, bump);
+ bump = in_len - off;
+ }
+ if (op->page_given && (pn != op->vpd_pn))
+ continue;
+ if (pn <= prev_pn) {
+ pr2serr("%s: prev_pn=0x%x, this pn=0x%x, not ascending so "
+ "exit\n", __func__, prev_pn, pn);
+ break;
+ }
+ prev_pn = pn;
+ op->vpd_pn = pn;
+ if (pn > max_pn) {
+ if (op->verbose > 2)
+ pr2serr("%s: skipping as this pn=0x%x exceeds "
+ "max_pn=0x%x\n", __func__, pn, max_pn);
+ continue;
+ }
+ if (op->do_long) {
+ if (jsp->pr_as_json)
+ sgj_pr_hr(jsp, "[0x%x]:\n", pn);
+ else
+ sgj_pr_hr(jsp, "[0x%x] ", pn);
+ }
+
+ res = vpd_decode(-1, op, jop, off);
+ if (SG_LIB_CAT_OTHER == res) {
+ if (op->verbose)
+ pr2serr("Can't decode VPD page=0x%x\n", pn);
+ }
+ }
+ return res;
+}
+
+static void
+decode_supported_vpd_4inq(uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap)
+{
+ int vpd, k, rlen, pdt;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ const struct svpd_values_name_t * vnp;
+ char b[64];
+
+ if (op->do_hex) {
+ hex2stdout(buff, len, no_ascii_4hex(op));
+ return;
+ }
+ if (len < 4) {
+ pr2serr("Supported VPD pages VPD page length too short=%d\n", len);
+ return;
+ }
+ pdt = PDT_MASK & buff[0];
+ rlen = buff[3] + 4;
+ if (rlen > len)
+ pr2serr("Supported VPD pages VPD page truncated, indicates %d, got "
+ "%d\n", rlen, len);
+ else
+ len = rlen;
+ sgj_pr_hr(jsp, " Supported VPD pages:\n");
+ for (k = 0; k < len - 4; ++k) {
+ vpd = buff[4 + k];
+ snprintf(b, sizeof(b), "0x%x", vpd);
+ vnp = get_vpd_page_info(vpd, pdt);
+ if (jsp->pr_as_json && jap) {
+ jo2p = sgj_new_unattached_object_r(jsp);
+ sgj_js_nv_i(jsp, jo2p, "i", vpd);
+ sgj_js_nv_s(jsp, jo2p, "hex", b + 2);
+ sgj_js_nv_s(jsp, jo2p, "name", vnp ? vnp->name : "unknown");
+ sgj_js_nv_s(jsp, jo2p, "acronym", vnp ? vnp->acron : "unknown");
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ }
+ if (vnp)
+ sgj_pr_hr(jsp, " %s\t%s\n", b, vnp->name);
+ else
+ sgj_pr_hr(jsp, " %s\n", b);
+ }
+}
+
+static bool
+vpd_page_is_supported(uint8_t * vpd_pg0, int v0_len, int pg_num, int vb)
+{
+ int k, rlen;
+
+ if (v0_len < 4)
+ return false;
+
+ rlen = vpd_pg0[3] + 4;
+ if (rlen > v0_len)
+ pr2serr("Supported VPD pages VPD page truncated, indicates %d, got "
+ "%d\n", rlen, v0_len);
+ else
+ v0_len = rlen;
+ if (vb > 1) {
+ pr2serr("Supported VPD pages, hex list: ");
+ hex2stderr(vpd_pg0 + 4, v0_len - 4, -1);
+ }
+ for (k = 4; k < v0_len; ++k) {
+ if(vpd_pg0[k] == pg_num)
+ return true;
+ }
+ return false;
+}
+
+/* ASCII Information VPD pages (page numbers: 0x1 to 0x7f) */
+static void
+decode_ascii_inf(uint8_t * buff, int len, struct opts_t * op)
+{
+ int al, k, bump;
+ uint8_t * bp;
+ uint8_t * p;
+ sgj_state * jsp = &op->json_st;
+
+ if (op->do_hex) {
+ hex2stdout(buff, len, no_ascii_4hex(op));
+ return;
+ }
+ if (len < 4) {
+ pr2serr("ASCII information VPD page length too short=%d\n", len);
+ return;
+ }
+ if (4 == len)
+ return;
+ al = buff[4];
+ if ((al + 5) > len)
+ al = len - 5;
+ for (k = 0, bp = buff + 5; k < al; k += bump, bp += bump) {
+ p = (uint8_t *)memchr(bp, 0, al - k);
+ if (! p) {
+ sgj_pr_hr(jsp, " %.*s\n", al - k, (const char *)bp);
+ break;
+ }
+ sgj_pr_hr(jsp, " %s\n", (const char *)bp);
+ bump = (p - bp) + 1;
+ }
+ bp = buff + 5 + al;
+ if (bp < (buff + len)) {
+ sgj_pr_hr(jsp, "Vendor specific information in hex:\n");
+ hex2stdout(bp, len - (al + 5), 0);
+ }
+}
+
+static void
+decode_id_vpd(uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jap)
+{
+ if (len < 4) {
+ pr2serr("Device identification VPD page length too "
+ "short=%d\n", len);
+ return;
+ }
+ decode_dev_ids("Device identification", buff + 4, len - 4, op, jap);
+}
+
+/* VPD_SCSI_PORTS 0x88 ["sp"] */
+static void
+decode_scsi_ports_vpd_4inq(uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap)
+{
+ int k, bump, rel_port, ip_tid_len, tpd_len;
+ uint8_t * bp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+
+ if (len < 4) {
+ pr2serr("SCSI Ports VPD page length too short=%d\n", len);
+ return;
+ }
+ if (op->do_hex > 2) {
+ hex2stdout(buff, len, -1);
+ return;
+ }
+ len -= 4;
+ bp = buff + 4;
+ for (k = 0; k < len; k += bump, bp += bump) {
+ jo2p = sgj_new_unattached_object_r(jsp);
+ rel_port = sg_get_unaligned_be16(bp + 2);
+ sgj_pr_hr(jsp, "Relative port=%d\n", rel_port);
+ sgj_js_nv_i(jsp, jo2p, "relative_port", rel_port);
+ ip_tid_len = sg_get_unaligned_be16(bp + 6);
+ bump = 8 + ip_tid_len;
+ if ((k + bump) > len) {
+ pr2serr("SCSI Ports VPD page, short descriptor "
+ "length=%d, left=%d\n", bump, (len - k));
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ return;
+ }
+ if (ip_tid_len > 0) {
+ if (op->do_hex) {
+ printf(" Initiator port transport id:\n");
+ hex2stdout((bp + 8), ip_tid_len, no_ascii_4hex(op));
+ } else {
+ char b[1024];
+
+ sg_decode_transportid_str(" ", bp + 8, ip_tid_len,
+ true, sizeof(b), b);
+ if (jsp->pr_as_json)
+ sgj_js_nv_s(jsp, jo2p, "initiator_port_transport_id", b);
+ sgj_pr_hr(jsp, "%s",
+ sg_decode_transportid_str(" ", bp + 8,
+ ip_tid_len, true, sizeof(b), b));
+ }
+ }
+ tpd_len = sg_get_unaligned_be16(bp + bump + 2);
+ if ((k + bump + tpd_len + 4) > len) {
+ pr2serr("SCSI Ports VPD page, short descriptor(tgt) "
+ "length=%d, left=%d\n", bump, (len - k));
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ return;
+ }
+ if (tpd_len > 0) {
+ sgj_pr_hr(jsp, " Target port descriptor(s):\n");
+ if (op->do_hex)
+ hex2stdout(bp + bump + 4, tpd_len, no_ascii_4hex(op));
+ else {
+ sgj_opaque_p ja2p = sgj_named_subarray_r(jsp, jo2p,
+ "target_port_descriptor_list");
+
+ decode_dev_ids("SCSI Ports", bp + bump + 4, tpd_len,
+ op, ja2p);
+ }
+ }
+ bump += tpd_len + 4;
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ }
+}
+
+/* These are target port, device server (i.e. target) and LU identifiers */
+static void
+decode_dev_ids(const char * leadin, uint8_t * buff, int len,
+ struct opts_t * op, sgj_opaque_p jap)
+{
+ int u, j, m, id_len, p_id, c_set, piv, assoc, desig_type, i_len;
+ int off, ci_off, c_id, d_id, naa, vsi, k, n;
+ uint64_t vsei, id_ext, ccc_id;
+ const uint8_t * bp;
+ const uint8_t * ip;
+ const char * cp;
+ sgj_state * jsp = &op->json_st;
+ char b[256];
+ char d[64];
+ static const int blen = sizeof(b);
+ static const int dlen = sizeof(d);
+
+ if (jsp->pr_as_json) {
+ int ret = filter_json_dev_ids(buff, len, -1, op, jap);
+
+ if (ret || (! jsp->pr_out_hr))
+ return;
+ }
+ if (buff[2] > 2) { /* SPC-3,4,5 buff[2] is upper byte of length */
+ /*
+ * Reference the 3rd byte of the first Identification descriptor
+ * of a page 83 reply to determine whether the reply is compliant
+ * with SCSI-2 or SPC-2/3 specifications. A zero value in the
+ * 3rd byte indicates an SPC-2/3 conforming reply ( the field is
+ * reserved ). This byte will be non-zero for a SCSI-2
+ * conforming page 83 reply from these EMC Symmetrix models since
+ * the 7th byte of the reply corresponds to the 4th and 5th
+ * nibbles of the 6-byte OUI for EMC, that is, 0x006048.
+ */
+ i_len = len;
+ ip = bp = buff;
+ c_set = 1;
+ assoc = 0;
+ piv = 0;
+ p_id = 0xf;
+ desig_type = 3;
+ j = 1;
+ off = 16;
+ sgj_pr_hr(jsp, " Pre-SPC descriptor, descriptor length: %d\n",
+ i_len);
+ goto decode;
+ }
+
+ for (j = 1, off = -1;
+ (u = sg_vpd_dev_id_iter(buff, len, &off, -1, -1, -1)) == 0;
+ ++j) {
+ bp = buff + off;
+ i_len = bp[3];
+ id_len = i_len + 4;
+ sgj_pr_hr(jsp, " Designation descriptor number %d, "
+ "descriptor length: %d\n", j, id_len);
+ if ((off + id_len) > len) {
+ pr2serr("%s VPD page error: designator length longer "
+ "than\n remaining response length=%d\n", leadin,
+ (len - off));
+ return;
+ }
+ ip = bp + 4;
+ p_id = ((bp[0] >> 4) & 0xf); /* protocol identifier */
+ c_set = (bp[0] & 0xf); /* code set */
+ piv = ((bp[1] & 0x80) ? 1 : 0); /* protocol identifier valid */
+ assoc = ((bp[1] >> 4) & 0x3);
+ desig_type = (bp[1] & 0xf);
+ decode:
+ if (piv && ((1 == assoc) || (2 == assoc)))
+ sgj_pr_hr(jsp, " transport: %s\n",
+ sg_get_trans_proto_str(p_id, dlen, d));
+ n = 0;
+ cp = sg_get_desig_type_str(desig_type);
+ n += sg_scnpr(b + n, blen - n, " designator_type: %s, ",
+ cp ? cp : "-");
+ cp = sg_get_desig_code_set_str(c_set);
+ sgj_pr_hr(jsp, "%scode_set: %s\n", b, cp ? cp : "-");
+ cp = sg_get_desig_assoc_str(assoc);
+ sgj_pr_hr(jsp, " associated with the %s\n", cp ? cp : "-");
+ if (op->do_hex) {
+ sgj_pr_hr(jsp, " designator header(hex): %.2x %.2x %.2x %.2x\n",
+ bp[0], bp[1], bp[2], bp[3]);
+ sgj_pr_hr(jsp, " designator:\n");
+ hex2stdout(ip, i_len, 0);
+ continue;
+ }
+ switch (desig_type) {
+ case 0: /* vendor specific */
+ k = 0;
+ if ((2 == c_set) || (3 == c_set)) { /* ASCII or UTF-8 */
+ for (k = 0; (k < i_len) && isprint(ip[k]); ++k)
+ ;
+ if (k >= i_len)
+ k = 1;
+ }
+ if (k)
+ sgj_pr_hr(jsp, " vendor specific: %.*s\n", i_len, ip);
+ else {
+ sgj_pr_hr(jsp, " vendor specific:\n");
+ hex2stdout(ip, i_len, -1);
+ }
+ break;
+ case 1: /* T10 vendor identification */
+ sgj_pr_hr(jsp, " vendor id: %.8s\n", ip);
+ if (i_len > 8) {
+ if ((2 == c_set) || (3 == c_set)) { /* ASCII or UTF-8 */
+ sgj_pr_hr(jsp, " vendor specific: %.*s\n", i_len - 8,
+ ip + 8);
+ } else {
+ n = 0;
+ n += sg_scnpr(b + n, blen - n,
+ " vendor specific: 0x");
+ for (m = 8; m < i_len; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", ip[m]);
+ sgj_pr_hr(jsp, "%s\n", b);
+ }
+ }
+ break;
+ case 2: /* EUI-64 based */
+ sgj_pr_hr(jsp, " EUI-64 based %d byte identifier\n", i_len);
+ if (1 != c_set) {
+ pr2serr(" << expected binary code_set (1)>>\n");
+ hex2stderr(ip, i_len, -1);
+ break;
+ }
+ ci_off = 0;
+ n = 0;
+ b[0] = '\0';
+ if (16 == i_len) {
+ ci_off = 8;
+ id_ext = sg_get_unaligned_be64(ip);
+ n += sg_scnpr(b + n, blen - n,
+ " Identifier extension: 0x%" PRIx64 "\n",
+ id_ext);
+ } else if ((8 != i_len) && (12 != i_len)) {
+ pr2serr(" << can only decode 8, 12 and 16 "
+ "byte ids>>\n");
+ hex2stderr(ip, i_len, -1);
+ break;
+ }
+ ccc_id = sg_get_unaligned_be64(ip + ci_off);
+ sgj_pr_hr(jsp, "%s IEEE identifier: 0x%" PRIx64 "\n", b,
+ ccc_id);
+ if (12 == i_len) {
+ d_id = sg_get_unaligned_be32(ip + 8);
+ sgj_pr_hr(jsp, " Directory ID: 0x%x\n", d_id);
+ }
+ n = 0;
+ n += sg_scnpr(b + n, blen - n, " [0x");
+ for (m = 0; m < i_len; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", ip[m]);
+ sgj_pr_hr(jsp, "%s]\n", b);
+ break;
+ case 3: /* NAA <n> */
+ naa = (ip[0] >> 4) & 0xff;
+ if (1 != c_set) {
+ pr2serr(" << expected binary code_set (1), got %d for "
+ "NAA=%d>>\n", c_set, naa);
+ hex2stderr(ip, i_len, -1);
+ break;
+ }
+ switch (naa) {
+ case 2: /* NAA 2: IEEE Extended */
+ if (8 != i_len) {
+ pr2serr(" << unexpected NAA 2 identifier "
+ "length: 0x%x>>\n", i_len);
+ hex2stderr(ip, i_len, -1);
+ break;
+ }
+ d_id = (((ip[0] & 0xf) << 8) | ip[1]);
+ c_id = sg_get_unaligned_be24(ip + 2);
+ vsi = sg_get_unaligned_be24(ip + 5);
+ sgj_pr_hr(jsp, " NAA 2, vendor specific identifier A: "
+ "0x%x\n", d_id);
+ sgj_pr_hr(jsp, " AOI: 0x%x\n", c_id);
+ sgj_pr_hr(jsp, " vendor specific identifier B: 0x%x\n",
+ vsi);
+ n = 0;
+ n += sg_scnpr(b + n, blen - n, " [0x");
+ for (m = 0; m < 8; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", ip[m]);
+ sgj_pr_hr(jsp, "%s]\n", b);
+ break;
+ case 3: /* NAA 3: Locally assigned */
+ if (8 != i_len) {
+ pr2serr(" << unexpected NAA 3 identifier "
+ "length: 0x%x>>\n", i_len);
+ hex2stderr(ip, i_len, -1);
+ break;
+ }
+ sgj_pr_hr(jsp, " NAA 3, Locally assigned:\n");
+ n = 0;
+ n += sg_scnpr(b + n, blen - n, " [0x");
+ for (m = 0; m < 8; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", ip[m]);
+ sgj_pr_hr(jsp, "%s]\n", b);
+ break;
+ case 5: /* NAA 5: IEEE Registered */
+ if (8 != i_len) {
+ pr2serr(" << unexpected NAA 5 identifier "
+ "length: 0x%x>>\n", i_len);
+ hex2stderr(ip, i_len, -1);
+ 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];
+ }
+ sgj_pr_hr(jsp, " NAA 5, AOI: 0x%x\n", c_id);
+ n = 0;
+ n += sg_scnpr(b + n, blen - n, " Vendor Specific "
+ "Identifier: 0x%" PRIx64 "\n", vsei);
+ n += sg_scnpr(b + n, blen - n, " [0x");
+ for (m = 0; m < 8; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", ip[m]);
+ sgj_pr_hr(jsp, "%s]\n", b);
+ break;
+ case 6: /* NAA 6: IEEE Registered extended */
+ if (16 != i_len) {
+ pr2serr(" << unexpected NAA 6 identifier "
+ "length: 0x%x>>\n", i_len);
+ hex2stderr(ip, i_len, 0);
+ 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];
+ }
+ sgj_pr_hr(jsp, " NAA 6, AOI: 0x%x\n", c_id);
+ sgj_pr_hr(jsp, " Vendor Specific Identifier: 0x%"
+ PRIx64 "\n", vsei);
+ vsei = sg_get_unaligned_be64(ip + 8);
+ sgj_pr_hr(jsp, " Vendor Specific Identifier Extension: "
+ "0x%" PRIx64 "\n", vsei);
+ n = 0;
+ n += sg_scnpr(b + n, blen - n, " [0x");
+ for (m = 0; m < 16; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", ip[m]);
+ sgj_pr_hr(jsp, "%s]\n", b);
+ break;
+ default:
+ pr2serr(" << bad NAA nibble , expect 2, 3, 5 or 6, "
+ "got %d>>\n", naa);
+ hex2stderr(ip, i_len, -1);
+ break;
+ }
+ break;
+ case 4: /* Relative target port */
+ if ((1 != c_set) || (1 != assoc) || (4 != i_len)) {
+ pr2serr(" << expected binary code_set, target "
+ "port association, length 4>>\n");
+ hex2stderr(ip, i_len, -1);
+ break;
+ }
+ d_id = sg_get_unaligned_be16(ip + 2);
+ sgj_pr_hr(jsp, " Relative target port: 0x%x\n", d_id);
+ break;
+ case 5: /* (primary) Target port group */
+ if ((1 != c_set) || (1 != assoc) || (4 != i_len)) {
+ pr2serr(" << expected binary code_set, target "
+ "port association, length 4>>\n");
+ hex2stderr(ip, i_len, -1);
+ break;
+ }
+ d_id = sg_get_unaligned_be16(ip + 2);
+ sgj_pr_hr(jsp, " Target port group: 0x%x\n", d_id);
+ break;
+ case 6: /* Logical unit group */
+ if ((1 != c_set) || (0 != assoc) || (4 != i_len)) {
+ pr2serr(" << expected binary code_set, logical "
+ "unit association, length 4>>\n");
+ hex2stderr(ip, i_len, -1);
+ break;
+ }
+ d_id = sg_get_unaligned_be16(ip + 2);
+ sgj_pr_hr(jsp, " Logical unit group: 0x%x\n", d_id);
+ break;
+ case 7: /* MD5 logical unit identifier */
+ if ((1 != c_set) || (0 != assoc)) {
+ pr2serr(" << expected binary code_set, logical "
+ "unit association>>\n");
+ hex2stderr(ip, i_len, -1);
+ break;
+ }
+ sgj_pr_hr(jsp, " MD5 logical unit identifier:\n");
+ if (jsp->pr_out_hr)
+ sgj_js_str_out(jsp, (const char *)ip, i_len);
+ else
+ hex2stdout(ip, i_len, -1);
+ break;
+ case 8: /* SCSI name string */
+ if (3 != c_set) {
+ if (2 == c_set) {
+ if (op->verbose)
+ pr2serr(" << expected UTF-8, use ASCII>>\n");
+ } else {
+ pr2serr(" << expected UTF-8 code_set>>\n");
+ hex2stderr(ip, i_len, -1);
+ break;
+ }
+ }
+ sgj_pr_hr(jsp, " SCSI name string:\n");
+ /* does %s print out UTF-8 ok??
+ * Seems to depend on the locale. Looks ok here with my
+ * locale setting: en_AU.UTF-8
+ */
+ sgj_pr_hr(jsp, " %.*s\n", i_len, (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)
+ pr2serr(" >>>> Protocol specific port identifier "
+ "expects protocol\n"
+ " identifier to be valid and it is not\n");
+ if (TPROTO_UAS == p_id) {
+ sgj_pr_hr(jsp, " USB device address: 0x%x\n",
+ 0x7f & ip[0]);
+ sgj_pr_hr(jsp, " USB interface number: 0x%x\n", ip[2]);
+ } else if (TPROTO_SOP == p_id) {
+ sgj_pr_hr(jsp, " PCIe routing ID, bus number: 0x%x\n",
+ ip[0]);
+ sgj_pr_hr(jsp, " function number: 0x%x\n", ip[1]);
+ sgj_pr_hr(jsp, " [or device number: 0x%x, function "
+ "number: 0x%x]\n", (0x1f & (ip[1] >> 3)),
+ 0x7 & ip[1]);
+ } else
+ sgj_pr_hr(jsp, " >>>> unexpected protocol identifier: "
+ "%s\n with Protocol specific port "
+ "identifier\n", sg_get_trans_proto_str(p_id, dlen,
+ d));
+ break;
+ case 0xa: /* UUID identifier [spc5r08] RFC 4122 */
+ if (1 != c_set) {
+ pr2serr(" << expected binary code_set >>\n");
+ hex2stderr(ip, i_len, 0);
+ break;
+ }
+ if ((1 != ((ip[0] >> 4) & 0xf)) || (18 != i_len)) {
+ pr2serr(" << expected locally assigned UUID, 16 bytes "
+ "long >>\n");
+ hex2stderr(ip, i_len, 0);
+ break;
+ }
+ n = 0;
+ n += sg_scnpr(b + n, blen - n, " Locally assigned UUID: ");
+ 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", ip[2 + m]);
+ }
+ sgj_pr_hr(jsp, "%s\n", b);
+ break;
+ default: /* reserved */
+ pr2serr(" reserved designator=0x%x\n", desig_type);
+ hex2stderr(ip, i_len, -1);
+ break;
+ }
+ }
+ if (-2 == u)
+ pr2serr("%s VPD page error: around offset=%d\n", leadin, off);
+}
+
+/* The --export and --json options are assumed to be mutually exclusive.
+ * Here the former takes precedence. */
+static void
+export_dev_ids(uint8_t * buff, int len, int verbose)
+{
+ int u, j, m, id_len, c_set, assoc, desig_type, i_len;
+ int off, d_id, naa, k, p_id;
+ uint8_t * bp;
+ uint8_t * ip;
+ const char * assoc_str;
+ const char * suffix;
+
+ if (buff[2] != 0) {
+ /*
+ * Cf decode_dev_ids() for details
+ */
+ i_len = len;
+ ip = buff;
+ c_set = 1;
+ assoc = 0;
+ p_id = 0xf;
+ desig_type = 3;
+ j = 1;
+ off = 16;
+ goto decode;
+ }
+
+ for (j = 1, off = -1;
+ (u = sg_vpd_dev_id_iter(buff, len, &off, -1, -1, -1)) == 0;
+ ++j) {
+ bp = buff + off;
+ i_len = bp[3];
+ id_len = i_len + 4;
+ if ((off + id_len) > len) {
+ if (verbose)
+ pr2serr("Device Identification VPD page error: designator "
+ "length longer than\n remaining response "
+ "length=%d\n", (len - off));
+ return;
+ }
+ ip = bp + 4;
+ p_id = ((bp[0] >> 4) & 0xf); /* protocol identifier */
+ c_set = (bp[0] & 0xf);
+ assoc = ((bp[1] >> 4) & 0x3);
+ desig_type = (bp[1] & 0xf);
+ decode:
+ switch (assoc) {
+ case 0:
+ assoc_str = "LUN";
+ break;
+ case 1:
+ assoc_str = "PORT";
+ break;
+ case 2:
+ assoc_str = "TARGET";
+ break;
+ default:
+ if (verbose)
+ pr2serr(" Invalid association %d\n", assoc);
+ return;
+ }
+ switch (desig_type) {
+ case 0: /* vendor specific */
+ if (i_len == 0 || i_len > 128)
+ break;
+ if ((2 == c_set) || (3 == c_set)) { /* ASCII or UTF-8 */
+ k = encode_whitespaces(ip, i_len);
+ /* udev-conforming character encoding */
+ if (k > 0) {
+ printf("SCSI_IDENT_%s_VENDOR=", assoc_str);
+ for (m = 0; m < k; ++m) {
+ if ((ip[m] >= '0' && ip[m] <= '9') ||
+ (ip[m] >= 'A' && ip[m] <= 'Z') ||
+ (ip[m] >= 'a' && ip[m] <= 'z') ||
+ strchr("#+-.:=@_", ip[m]) != NULL)
+ printf("%c", ip[m]);
+ else
+ printf("\\x%02x", ip[m]);
+ }
+ printf("\n");
+ }
+ } else {
+ printf("SCSI_IDENT_%s_VENDOR=", assoc_str);
+ for (m = 0; m < i_len; ++m)
+ printf("%02x", (unsigned int)ip[m]);
+ printf("\n");
+ }
+ break;
+ case 1: /* T10 vendor identification */
+ printf("SCSI_IDENT_%s_T10=", assoc_str);
+ if ((2 == c_set) || (3 == c_set)) {
+ k = encode_whitespaces(ip, i_len);
+ /* udev-conforming character encoding */
+ for (m = 0; m < k; ++m) {
+ if ((ip[m] >= '0' && ip[m] <= '9') ||
+ (ip[m] >= 'A' && ip[m] <= 'Z') ||
+ (ip[m] >= 'a' && ip[m] <= 'z') ||
+ strchr("#+-.:=@_", ip[m]) != NULL)
+ printf("%c", ip[m]);
+ else
+ printf("\\x%02x", ip[m]);
+ }
+ printf("\n");
+ if (!memcmp(ip, "ATA_", 4)) {
+ printf("SCSI_IDENT_%s_ATA=%.*s\n", assoc_str,
+ k - 4, ip + 4);
+ }
+ } else {
+ for (m = 0; m < i_len; ++m)
+ printf("%02x", (unsigned int)ip[m]);
+ printf("\n");
+ }
+ break;
+ case 2: /* EUI-64 based */
+ if (1 != c_set) {
+ if (verbose) {
+ pr2serr(" << expected binary code_set (1)>>\n");
+ hex2stderr(ip, i_len, 0);
+ }
+ break;
+ }
+ printf("SCSI_IDENT_%s_EUI64=", assoc_str);
+ for (m = 0; m < i_len; ++m)
+ printf("%02x", (unsigned int)ip[m]);
+ printf("\n");
+ break;
+ case 3: /* NAA */
+ if (1 != c_set) {
+ if (verbose) {
+ pr2serr(" << expected binary code_set (1)>>\n");
+ hex2stderr(ip, i_len, 0);
+ }
+ break;
+ }
+ /*
+ * Unfortunately, there are some (broken) implementations
+ * which return _several_ NAA descriptors.
+ * So add a suffix to differentiate between them.
+ */
+ naa = (ip[0] >> 4) & 0xff;
+ switch (naa) {
+ case 6:
+ suffix="REGEXT";
+ break;
+ case 5:
+ suffix="REG";
+ break;
+ case 2:
+ suffix="EXT";
+ break;
+ case 3:
+ default:
+ suffix="LOCAL";
+ break;
+ }
+ printf("SCSI_IDENT_%s_NAA_%s=", assoc_str, suffix);
+ for (m = 0; m < i_len; ++m)
+ printf("%02x", (unsigned int)ip[m]);
+ printf("\n");
+ break;
+ case 4: /* Relative target port */
+ if ((1 != c_set) || (1 != assoc) || (4 != i_len)) {
+ if (verbose) {
+ pr2serr(" << expected binary code_set, target "
+ "port association, length 4>>\n");
+ hex2stderr(ip, i_len, 0);
+ }
+ break;
+ }
+ d_id = sg_get_unaligned_be16(ip + 2);
+ printf("SCSI_IDENT_%s_RELATIVE=%d\n", assoc_str, d_id);
+ break;
+ case 5: /* (primary) Target port group */
+ if ((1 != c_set) || (1 != assoc) || (4 != i_len)) {
+ if (verbose) {
+ pr2serr(" << expected binary code_set, target "
+ "port association, length 4>>\n");
+ hex2stderr(ip, i_len, 0);
+ }
+ break;
+ }
+ d_id = sg_get_unaligned_be16(ip + 2);
+ printf("SCSI_IDENT_%s_TARGET_PORT_GROUP=0x%x\n", assoc_str, d_id);
+ break;
+ case 6: /* Logical unit group */
+ if ((1 != c_set) || (0 != assoc) || (4 != i_len)) {
+ if (verbose) {
+ pr2serr(" << expected binary code_set, logical "
+ "unit association, length 4>>\n");
+ hex2stderr(ip, i_len, 0);
+ }
+ break;
+ }
+ d_id = sg_get_unaligned_be16(ip + 2);
+ printf("SCSI_IDENT_%s_LOGICAL_UNIT_GROUP=0x%x\n", assoc_str, d_id);
+ break;
+ case 7: /* MD5 logical unit identifier */
+ if ((1 != c_set) || (0 != assoc)) {
+ if (verbose) {
+ pr2serr(" << expected binary code_set, logical "
+ "unit association>>\n");
+ hex2stderr(ip, i_len, 0);
+ }
+ break;
+ }
+ printf("SCSI_IDENT_%s_MD5=", assoc_str);
+ hex2stdout(ip, i_len, -1);
+ break;
+ case 8: /* SCSI name string */
+ if (3 != c_set) {
+ if (verbose) {
+ pr2serr(" << expected UTF-8 code_set>>\n");
+ hex2stderr(ip, i_len, -1);
+ }
+ break;
+ }
+ if (! (strncmp((const char *)ip, "eui.", 4) ||
+ strncmp((const char *)ip, "EUI.", 4) ||
+ strncmp((const char *)ip, "naa.", 4) ||
+ strncmp((const char *)ip, "NAA.", 4) ||
+ strncmp((const char *)ip, "iqn.", 4))) {
+ if (verbose) {
+ pr2serr(" << expected name string prefix>>\n");
+ hex2stderr(ip, i_len, -1);
+ }
+ break;
+ }
+
+ printf("SCSI_IDENT_%s_NAME=%.*s\n", assoc_str, i_len,
+ (const char *)ip);
+ break;
+ case 9: /* Protocol specific port identifier */
+ if (TPROTO_UAS == p_id) {
+ if ((4 != i_len) || (1 != assoc)) {
+ if (verbose) {
+ pr2serr(" << UAS (USB) expected target "
+ "port association>>\n");
+ hex2stderr(ip, i_len, 0);
+ }
+ break;
+ }
+ printf("SCSI_IDENT_%s_UAS_DEVICE_ADDRESS=0x%x\n", assoc_str,
+ ip[0] & 0x7f);
+ printf("SCSI_IDENT_%s_UAS_INTERFACE_NUMBER=0x%x\n", assoc_str,
+ ip[2]);
+ } else if (TPROTO_SOP == p_id) {
+ if ((4 != i_len) && (8 != i_len)) { /* spc4r36h confused */
+ if (verbose) {
+ pr2serr(" << SOP (PCIe) descriptor "
+ "length=%d >>\n", i_len);
+ hex2stderr(ip, i_len, 0);
+ }
+ break;
+ }
+ printf("SCSI_IDENT_%s_SOP_ROUTING_ID=0x%x\n", assoc_str,
+ sg_get_unaligned_be16(ip + 0));
+ } else {
+ pr2serr(" << Protocol specific port identifier "
+ "protocol_id=0x%x>>\n", p_id);
+ }
+ break;
+ case 0xa: /* UUID based */
+ if (1 != c_set) {
+ if (verbose) {
+ pr2serr(" << expected binary code_set (1)>>\n");
+ hex2stderr(ip, i_len, 0);
+ }
+ break;
+ }
+ if (i_len < 18) {
+ if (verbose) {
+ pr2serr(" << short UUID field expected 18 or more, "
+ "got %d >>\n", i_len);
+ hex2stderr(ip, i_len, 0);
+ }
+ break;
+ }
+ printf("SCSI_IDENT_%s_UUID=", assoc_str);
+ for (m = 2; m < i_len; ++m) {
+ if ((6 == m) || (8 == m) || (10 == m) || (12 == m))
+ printf("-%02x", (unsigned int)ip[m]);
+ else
+ printf("%02x", (unsigned int)ip[m]);
+ }
+ printf("\n");
+ break;
+ default: /* reserved */
+ if (verbose) {
+ pr2serr(" reserved designator=0x%x\n", desig_type);
+ hex2stderr(ip, i_len, -1);
+ }
+ break;
+ }
+ }
+ if (-2 == u && verbose)
+ pr2serr("Device identification VPD page error: "
+ "around offset=%d\n", off);
+}
+
+/* VPD_BLOCK_LIMITS 0xb0 ["bl"] (SBC) */
+/* VPD_SA_DEV_CAP 0xb0 ["sad"] (SSC) */
+/* Sequential access device characteristics, ssc+smc */
+/* OSD information, osd */
+static void
+decode_b0_vpd(uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jop)
+{
+ int pdt = PDT_MASK & buff[0];
+ sgj_state * jsp = &op->json_st;
+
+ if (op->do_hex) {
+ hex2stdout(buff, len, no_ascii_4hex(op));
+ return;
+ }
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ /* done by decode_block_limits_vpd() */
+ break;
+ case PDT_TAPE: case PDT_MCHANGER:
+ sgj_haj_vi_nex(jsp, jop, 2, "TSMC", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(buff[4] & 0x2), false, "Tape Stream Mirror "
+ "Capable");
+ sgj_haj_vi_nex(jsp, jop, 2, "WORM", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(buff[4] & 0x1), false, "Write Once Read Multiple "
+ "supported");
+
+ break;
+ case PDT_OSD:
+ default:
+ pr2serr(" Unable to decode pdt=0x%x, in hex:\n", pdt);
+ hex2stderr(buff, len, 0);
+ break;
+ }
+}
+
+/* VPD_BLOCK_DEV_CHARS sbc 0xb1 ["bdc"] */
+/* VPD_MAN_ASS_SN ssc */
+static void
+decode_b1_vpd(uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jop)
+{
+ int pdt;
+ sgj_state * jsp = &op->json_st;
+
+ if (op->do_hex) {
+ hex2stdout(buff, len, no_ascii_4hex(op));
+ return;
+ }
+ pdt = PDT_MASK & buff[0];
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ /* now done by decode_block_dev_ch_vpd() in sg_vpd_common.c */
+ break;
+ case PDT_TAPE: case PDT_MCHANGER: case PDT_ADC:
+ sgj_pr_hr(jsp, " Manufacturer-assigned serial number: %.*s\n",
+ len - 4, buff + 4);
+ sgj_js_nv_s_len(jsp, jop, "manufacturer_assigned_serial_number",
+ (const char *)buff + 4, len - 4);
+ break;
+ default:
+ pr2serr(" Unable to decode pdt=0x%x, in hex:\n", pdt);
+ hex2stderr(buff, len, 0);
+ break;
+ }
+}
+
+/* VPD_REFERRALS sbc 0xb3 ["ref"] */
+/* VPD_AUTOMATION_DEV_SN ssc 0xb3 ["adsn"] */
+static void
+decode_b3_vpd(uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jop)
+{
+ int pdt;
+ sgj_state * jsp = &op->json_st;
+
+ if (op->do_hex) {
+ hex2stdout(buff, len, no_ascii_4hex(op));
+ return;
+ }
+ pdt = buff[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ /* now done in decode_referrals_vpd() in sg_vpd_common.c */
+ break;
+ case PDT_TAPE: case PDT_MCHANGER:
+ sgj_pr_hr(jsp, " Automation device serial number: %.*s\n",
+ len - 4, buff + 4);
+ sgj_js_nv_s_len(jsp, jop, "automation_device_serial_number",
+ (const char *)buff + 4, len - 4);
+ break;
+ default:
+ pr2serr(" Unable to decode pdt=0x%x, in hex:\n", pdt);
+ hex2stderr(buff, len, 0);
+ break;
+ }
+}
+
+#if 0
+static void
+decode_rdac_vpd_c9_rtpg_data(uint8_t aas, uint8_t vendor)
+{
+ printf(" Asymmetric Access State:");
+ switch(aas & 0x0F) {
+ case 0x0:
+ printf(" Active/Optimized");
+ break;
+ case 0x1:
+ printf(" Active/Non-Optimized");
+ break;
+ case 0x2:
+ printf(" Standby");
+ break;
+ case 0x3:
+ printf(" Unavailable");
+ break;
+ case 0xE:
+ printf(" Offline");
+ break;
+ case 0xF:
+ printf(" Transitioning");
+ break;
+ default:
+ printf(" (unknown)");
+ break;
+ }
+ printf("\n");
+
+ printf(" Vendor Specific Field:");
+ switch(vendor) {
+ case 0x01:
+ printf(" Operating normally");
+ break;
+ case 0x02:
+ printf(" Non-responsive to queries");
+ break;
+ case 0x03:
+ printf(" Controller being held in reset");
+ break;
+ case 0x04:
+ printf(" Performing controller firmware download (1st "
+ "controller)");
+ break;
+ case 0x05:
+ printf(" Performing controller firmware download (2nd "
+ "controller)");
+ break;
+ case 0x06:
+ printf(" Quiesced as a result of an administrative request");
+ break;
+ case 0x07:
+ printf(" Service mode as a result of an administrative request");
+ break;
+ case 0xFF:
+ printf(" Details are not available");
+ break;
+ default:
+ printf(" (unknown)");
+ break;
+ }
+ printf("\n");
+}
+
+static void
+decode_rdac_vpd_c9(uint8_t * buff, int len, struct opts_t * op)
+{
+ if (len < 3) {
+ pr2serr("Volume Access Control VPD page length too short=%d\n", len);
+ return;
+ }
+ if (op->do_hex) {
+ hex2stdout(buff, len, no_ascii_4hex(op));
+ return;
+ }
+ if (buff[4] != 'v' && buff[5] != 'a' && buff[6] != 'c') {
+ pr2serr("Invalid page identifier %c%c%c%c, decoding "
+ "not possible.\n" , buff[4], buff[5], buff[6], buff[7]);
+ return;
+ }
+ if (buff[7] != '1') {
+ pr2serr("Invalid page version '%c' (should be 1)\n", buff[7]);
+ }
+ if ( (buff[8] & 0xE0) == 0xE0 ) {
+ printf(" IOShipping (ALUA): Enabled\n");
+ } else {
+ printf(" AVT:");
+ if (buff[8] & 0x80) {
+ printf(" Enabled");
+ if (buff[8] & 0x40)
+ printf(" (Allow reads on sector 0)");
+ printf("\n");
+ } else {
+ printf(" Disabled\n");
+ }
+ }
+ printf(" Volume Access via: ");
+ if (buff[8] & 0x01)
+ printf("primary controller\n");
+ else
+ printf("alternate controller\n");
+
+ if (buff[8] & 0x08) {
+ printf(" Path priority: %d ", buff[15] & 0xf);
+ switch(buff[15] & 0xf) {
+ case 0x1:
+ printf("(preferred path)\n");
+ break;
+ case 0x2:
+ printf("(secondary path)\n");
+ break;
+ default:
+ printf("(unknown)\n");
+ break;
+ }
+
+ printf(" Preferred Path Auto Changeable:");
+ switch(buff[14] & 0x3C) {
+ case 0x14:
+ printf(" No (User Disabled and Host Type Restricted)\n");
+ break;
+ case 0x18:
+ printf(" No (User Disabled)\n");
+ break;
+ case 0x24:
+ printf(" No (Host Type Restricted)\n");
+ break;
+ case 0x28:
+ printf(" Yes\n");
+ break;
+ default:
+ printf(" (Unknown)\n");
+ break;
+ }
+
+ printf(" Implicit Failback:");
+ switch(buff[14] & 0x03) {
+ case 0x1:
+ printf(" Disabled\n");
+ break;
+ case 0x2:
+ printf(" Enabled\n");
+ break;
+ default:
+ printf(" (Unknown)\n");
+ break;
+ }
+ } else {
+ printf(" Path priority: %d ", buff[9] & 0xf);
+ switch(buff[9] & 0xf) {
+ case 0x1:
+ printf("(preferred path)\n");
+ break;
+ case 0x2:
+ printf("(secondary path)\n");
+ break;
+ default:
+ printf("(unknown)\n");
+ break;
+ }
+ }
+
+ if (buff[8] & 0x80) {
+ printf(" Target Port Group Data (This controller):\n");
+ decode_rdac_vpd_c9_rtpg_data(buff[10], buff[11]);
+
+ printf(" Target Port Group Data (Alternate controller):\n");
+ decode_rdac_vpd_c9_rtpg_data(buff[12], buff[13]);
+ }
+
+ return;
+}
+#endif
+
+extern const char * sg_ansi_version_arr[];
+
+static const char *
+get_ansi_version_str(int version, char * b, int blen)
+{
+ version &= 0xf;
+ b[blen - 1] = '\0';
+ strncpy(b, sg_ansi_version_arr[version], blen - 1);
+ return b;
+}
+
+static void
+std_inq_decode(struct opts_t * op, sgj_opaque_p jop, int off)
+{
+ int len, pqual, pdt, ansi_version, k, j;
+ sgj_state * jsp = &op->json_st;
+ bool as_json = jsp->pr_as_json;
+ const char * cp;
+ const uint8_t * rp;
+ int vdesc_arr[8];
+ char b[128];
+ static const int blen = sizeof(b);
+
+ rp = rsp_buff + off;
+ memset(vdesc_arr, 0, sizeof(vdesc_arr));
+ if (op->do_raw) {
+ dStrRaw((const char *)rp, op->maxlen);
+ return;
+ } else if (op->do_hex) {
+ /* with -H, print with address, -HH without */
+ hex2stdout(rp, op->maxlen, no_ascii_4hex(op));
+ return;
+ }
+ pqual = (rp[0] & 0xe0) >> 5;
+ if (! op->do_raw && ! op->do_export) {
+ strcpy(b, "standard INQUIRY:");
+ if (0 == pqual)
+ sgj_pr_hr(jsp, "%s\n", b);
+ else if (1 == pqual)
+ sgj_pr_hr(jsp, "%s [PQ indicates LU temporarily unavailable]\n",
+ b);
+ else if (3 == pqual)
+ sgj_pr_hr(jsp, "%s [PQ indicates LU not accessible via this "
+ "port]\n", b);
+ else
+ sgj_pr_hr(jsp, "%s [reserved or vendor specific qualifier "
+ "[%d]]\n", b, pqual);
+ }
+ len = rp[4] + 5;
+ /* N.B. rp[2] full byte is 'version' in SPC-2,3,4 but in SPC
+ * [spc-r11a (1997)] bits 6,7: ISO/IEC version; bits 3-5: ECMA
+ * version; bits 0-2: SCSI version */
+ ansi_version = rp[2] & 0x7; /* Only take SCSI version */
+ pdt = rp[0] & PDT_MASK;
+ if (op->do_export) {
+ printf("SCSI_TPGS=%d\n", (rp[5] & 0x30) >> 4);
+ cp = sg_get_pdt_str(pdt, blen, b);
+ if (strlen(cp) > 0)
+ printf("SCSI_TYPE=%s\n", cp);
+ } else {
+ sgj_pr_hr(jsp, " PQual=%d PDT=%d RMB=%d LU_CONG=%d "
+ "hot_pluggable=%d version=0x%02x ", pqual, pdt,
+ !!(rp[1] & 0x80), !!(rp[1] & 0x40), (rp[1] >> 4) & 0x3,
+ (unsigned int)rp[2]);
+ sgj_pr_hr(jsp, " [%s]\n", get_ansi_version_str(ansi_version, b,
+ blen));
+ sgj_pr_hr(jsp, " [AERC=%d] [TrmTsk=%d] NormACA=%d HiSUP=%d "
+ " Resp_data_format=%d\n SCCS=%d ", !!(rp[3] & 0x80),
+ !!(rp[3] & 0x40), !!(rp[3] & 0x20), !!(rp[3] & 0x10),
+ rp[3] & 0x0f, !!(rp[5] & 0x80));
+ sgj_pr_hr(jsp, "ACC=%d TPGS=%d 3PC=%d Protect=%d ",
+ !!(rp[5] & 0x40), ((rp[5] & 0x30) >> 4), !!(rp[5] & 0x08),
+ !!(rp[5] & 0x01));
+ sgj_pr_hr(jsp, " [BQue=%d]\n EncServ=%d ", !!(rp[6] & 0x80),
+ !!(rp[6] & 0x40));
+ if (rp[6] & 0x10)
+ sgj_pr_hr(jsp, "MultiP=1 (VS=%d) ", !!(rp[6] & 0x20));
+ else
+ sgj_pr_hr(jsp, "MultiP=0 ");
+ sgj_pr_hr(jsp, "[MChngr=%d] [ACKREQQ=%d] Addr16=%d\n "
+ "[RelAdr=%d] ", !!(rp[6] & 0x08), !!(rp[6] & 0x04),
+ !!(rp[6] & 0x01), !!(rp[7] & 0x80));
+ sgj_pr_hr(jsp, "WBus16=%d Sync=%d [Linked=%d] [TranDis=%d] ",
+ !!(rp[7] & 0x20), !!(rp[7] & 0x10), !!(rp[7] & 0x08),
+ !!(rp[7] & 0x04));
+ sgj_pr_hr(jsp, "CmdQue=%d\n", !!(rp[7] & 0x02));
+ if (op->maxlen > 56)
+ sgj_pr_hr(jsp, " [SPI: Clocking=0x%x QAS=%d IUS=%d]\n",
+ (rp[56] & 0x0c) >> 2, !!(rp[56] & 0x2),
+ !!(rp[56] & 0x1));
+ if (op->maxlen >= len)
+ sgj_pr_hr(jsp, " length=%d (0x%x)", len, len);
+ else
+ sgj_pr_hr(jsp, " length=%d (0x%x), but only fetched %d bytes",
+ len, len, op->maxlen);
+ if ((ansi_version >= 2) && (len < SAFE_STD_INQ_RESP_LEN))
+ sgj_pr_hr(jsp, "\n [for SCSI>=2, len>=36 is expected]");
+ cp = sg_get_pdt_str(pdt, blen, b);
+ if (strlen(cp) > 0)
+ sgj_pr_hr(jsp, " Peripheral device type: %s\n", cp);
+ }
+ if (op->maxlen <= 8) {
+ if (! op->do_export)
+ sgj_pr_hr(jsp, " Inquiry response length=%d, no vendor, product "
+ "or revision data\n", op->maxlen);
+ } else {
+ int i;
+
+ memcpy(xtra_buff, &rp[8], 8);
+ xtra_buff[8] = '\0';
+ /* Fixup any tab characters */
+ for (i = 0; i < 8; ++i)
+ if (xtra_buff[i] == 0x09)
+ xtra_buff[i] = ' ';
+ if (op->do_export) {
+ len = encode_whitespaces((uint8_t *)xtra_buff, 8);
+ if (len > 0) {
+ printf("SCSI_VENDOR=%s\n", xtra_buff);
+ encode_string(xtra_buff, &rp[8], 8);
+ printf("SCSI_VENDOR_ENC=%s\n", xtra_buff);
+ }
+ } else
+ sgj_pr_hr(jsp, " Vendor identification: %s\n", xtra_buff);
+ if (op->maxlen <= 16) {
+ if (! op->do_export)
+ sgj_pr_hr(jsp, " Product identification: <none>\n");
+ } else {
+ memcpy(xtra_buff, &rp[16], 16);
+ xtra_buff[16] = '\0';
+ if (op->do_export) {
+ len = encode_whitespaces((uint8_t *)xtra_buff, 16);
+ if (len > 0) {
+ printf("SCSI_MODEL=%s\n", xtra_buff);
+ encode_string(xtra_buff, &rp[16], 16);
+ printf("SCSI_MODEL_ENC=%s\n", xtra_buff);
+ }
+ } else
+ sgj_pr_hr(jsp, " Product identification: %s\n", xtra_buff);
+ }
+ if (op->maxlen <= 32) {
+ if (! op->do_export)
+ sgj_pr_hr(jsp, " Product revision level: <none>\n");
+ } else {
+ memcpy(xtra_buff, &rp[32], 4);
+ xtra_buff[4] = '\0';
+ if (op->do_export) {
+ len = encode_whitespaces((uint8_t *)xtra_buff, 4);
+ if (len > 0)
+ printf("SCSI_REVISION=%s\n", xtra_buff);
+ } else
+ sgj_pr_hr(jsp, " Product revision level: %s\n", xtra_buff);
+ }
+ if (op->do_vendor && (op->maxlen > 36) && ('\0' != rp[36]) &&
+ (' ' != rp[36])) {
+ memcpy(xtra_buff, &rp[36], op->maxlen < 56 ? op->maxlen - 36 :
+ 20);
+ if (op->do_export) {
+ len = encode_whitespaces((uint8_t *)xtra_buff, 20);
+ if (len > 0)
+ printf("VENDOR_SPECIFIC=%s\n", xtra_buff);
+ } else
+ sgj_pr_hr(jsp, " Vendor specific: %s\n", xtra_buff);
+ }
+ if (op->do_descriptors) {
+ for (j = 0, k = 58; ((j < 8) && ((k + 1) < op->maxlen));
+ k +=2, ++j)
+ vdesc_arr[j] = sg_get_unaligned_be16(rp + k);
+ }
+ if ((op->do_vendor > 1) && (op->maxlen > 96)) {
+ memcpy(xtra_buff, &rp[96], op->maxlen - 96);
+ if (op->do_export) {
+ len = encode_whitespaces((uint8_t *)xtra_buff,
+ op->maxlen - 96);
+ if (len > 0)
+ printf("VENDOR_SPECIFIC=%s\n", xtra_buff);
+ } else
+ sgj_pr_hr(jsp, " Vendor specific: %s\n", xtra_buff);
+ }
+ if (op->do_vendor && (op->maxlen > 243) &&
+ (0 == strncmp("OPEN-V", (const char *)&rp[16], 6))) {
+ memcpy(xtra_buff, &rp[212], 32);
+ if (op->do_export) {
+ len = encode_whitespaces((uint8_t *)xtra_buff, 32);
+ if (len > 0)
+ printf("VENDOR_SPECIFIC_OPEN-V_LDEV_NAME=%s\n", xtra_buff);
+ } else
+ sgj_pr_hr(jsp, " Vendor specific OPEN-V LDEV Name: %s\n",
+ xtra_buff);
+ }
+ }
+ if (! op->do_export) {
+ sgj_opaque_p jo2p = NULL;
+
+ if (as_json)
+ jo2p = std_inq_decode_js(rp, op->maxlen, op, jop);
+ if ((0 == op->maxlen) && usn_buff[0])
+ sgj_pr_hr(jsp, " Unit serial number: %s\n", usn_buff);
+ if (op->do_descriptors) {
+ sgj_opaque_p jap = sgj_named_subarray_r(jsp, jo2p,
+ "version_descriptor_list");
+ if (0 == vdesc_arr[0]) {
+ sgj_pr_hr(jsp, "\n");
+ sgj_pr_hr(jsp, " No version descriptors available\n");
+ } else {
+ sgj_pr_hr(jsp, "\n");
+ sgj_pr_hr(jsp, " Version descriptors:\n");
+ for (k = 0; k < 8; ++k) {
+ sgj_opaque_p jo3p = sgj_new_unattached_object_r(jsp);
+ int vdv = vdesc_arr[k];
+
+ if (0 == vdv)
+ break;
+ cp = find_version_descriptor_str(vdv);
+ if (cp)
+ sgj_pr_hr(jsp, " %s\n", cp);
+ else
+ sgj_pr_hr(jsp, " [unrecognised version descriptor "
+ "code: 0x%x]\n", vdv);
+ sgj_js_nv_ihexstr(jsp, jo3p, "version_descriptor", vdv,
+ NULL, cp ? cp : "unknown");
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ }
+ }
+ }
+ }
+}
+
+/* Returns 0 if Unit Serial Number VPD page contents found, else see
+ * sg_ll_inquiry_v2() return values */
+static int
+fetch_unit_serial_num(int sg_fd, char * obuff, int obuff_len, int verbose)
+{
+ int len, k, res, c;
+ uint8_t * b;
+ uint8_t * free_b;
+
+ b = sg_memalign(DEF_ALLOC_LEN, 0, &free_b, false);
+ if (NULL == b) {
+ pr2serr("%s: unable to allocate on heap\n", __func__);
+ return sg_convert_errno(ENOMEM);
+ }
+ res = vpd_fetch_page(sg_fd, b, VPD_SUPPORTED_VPDS,
+ -1 /* 1 byte alloc_len */, false, verbose, &len);
+ if (res) {
+ if (verbose > 2)
+ pr2serr("%s: no supported VPDs page\n", __func__);
+ res = SG_LIB_CAT_MALFORMED;
+ goto fini;
+ }
+ if (! vpd_page_is_supported(b, len, VPD_UNIT_SERIAL_NUM, verbose)) {
+ res = sg_convert_errno(EDOM); /* was SG_LIB_CAT_ILLEGAL_REQ */
+ goto fini;
+ }
+
+ memset(b, 0xff, 4); /* guard against empty response */
+ res = vpd_fetch_page(sg_fd, b, VPD_UNIT_SERIAL_NUM, -1, false, verbose,
+ &len);
+ if ((0 == res) && (len > 3)) {
+ len -= 4;
+ len = (len < (obuff_len - 1)) ? len : (obuff_len - 1);
+ if (len > 0) {
+ /* replace non-printable characters (except NULL) with space */
+ for (k = 0; k < len; ++k) {
+ c = b[4 + k];
+ if (c)
+ obuff[k] = isprint(c) ? c : ' ';
+ else
+ break;
+ }
+ obuff[k] = '\0';
+ res = 0;
+ goto fini;
+ } else {
+ if (verbose > 2)
+ pr2serr("%s: bad sn VPD page\n", __func__);
+ res = SG_LIB_CAT_MALFORMED;
+ }
+ } else {
+ if (verbose > 2)
+ pr2serr("%s: no supported VPDs page\n", __func__);
+ res = SG_LIB_CAT_MALFORMED;
+ }
+fini:
+ if (free_b)
+ free(free_b);
+ return res;
+}
+
+
+/* Process a standard INQUIRY data format (response).
+ * Returns 0 if successful */
+static int
+std_inq_process(int sg_fd, struct opts_t * op, sgj_opaque_p jop, int off)
+{
+ int res, len, rlen, act_len;
+ int vb, resid;
+ char buff[48];
+
+ if (sg_fd < 0) { /* assume --inhex=FD usage */
+ std_inq_decode(op, jop, off);
+ return 0;
+ }
+ rlen = (op->maxlen > 0) ? op->maxlen : SAFE_STD_INQ_RESP_LEN;
+ vb = op->verbose;
+ res = sg_ll_inquiry_v2(sg_fd, false, 0, rsp_buff, rlen, DEF_PT_TIMEOUT,
+ &resid, false, vb);
+ if (0 == res) {
+ if ((vb > 4) && ((rlen - resid) > 0)) {
+ pr2serr("Safe (36 byte) Inquiry response:\n");
+ hex2stderr(rsp_buff, rlen - resid, 0);
+ }
+ len = rsp_buff[4] + 5;
+ if ((len > SAFE_STD_INQ_RESP_LEN) && (len < 256) &&
+ (0 == op->maxlen)) {
+ rlen = len;
+ memset(rsp_buff, 0, rlen);
+ if (sg_ll_inquiry_v2(sg_fd, false, 0, rsp_buff, rlen,
+ DEF_PT_TIMEOUT, &resid, true, vb)) {
+ pr2serr("second INQUIRY (%d byte) failed\n", len);
+ return SG_LIB_CAT_OTHER;
+ }
+ if (len != (rsp_buff[4] + 5)) {
+ pr2serr("strange, consecutive INQUIRYs yield different "
+ "'additional lengths'\n");
+ len = rsp_buff[4] + 5;
+ }
+ }
+ if (op->maxlen > 0)
+ act_len = rlen;
+ else
+ act_len = (rlen < len) ? rlen : len;
+ /* don't use more than HBA's resid says was transferred from LU */
+ if (act_len > (rlen - resid))
+ act_len = rlen - resid;
+ if (act_len < SAFE_STD_INQ_RESP_LEN)
+ rsp_buff[act_len] = '\0';
+ if ((! op->do_only) && (! op->do_export) && (0 == op->maxlen)) {
+ if (fetch_unit_serial_num(sg_fd, usn_buff, sizeof(usn_buff), vb))
+ usn_buff[0] = '\0';
+ }
+ op->maxlen = act_len;
+ std_inq_decode(op, jop, 0);
+ return 0;
+ } else if (res < 0) { /* could be an ATA device */
+#if defined(SG_LIB_LINUX) && defined(SG_SCSI_STRINGS) && \
+ defined(HDIO_GET_IDENTITY)
+ /* Try an ATA Identify Device command */
+ res = try_ata_identify(sg_fd, op->do_hex, op->do_raw, vb);
+ if (0 != res) {
+ pr2serr("SCSI INQUIRY, NVMe Identify and fetching ATA "
+ "information failed on %s\n", op->device_name);
+ return (res < 0) ? SG_LIB_CAT_OTHER : res;
+ }
+#else
+ pr2serr("SCSI INQUIRY failed on %s, res=%d\n",
+ op->device_name, res);
+ return res;
+#endif
+ } else {
+ char b[80];
+
+ if (vb > 0) {
+ pr2serr(" inquiry: failed requesting %d byte response: ", rlen);
+ if (resid && (vb > 1))
+ snprintf(buff, sizeof(buff), " [resid=%d]", resid);
+ else
+ buff[0] = '\0';
+ sg_get_category_sense_str(res, sizeof(b), b, vb);
+ pr2serr("%s%s\n", b, buff);
+ }
+ return res;
+ }
+ return 0;
+}
+
+#ifdef SG_SCSI_STRINGS
+/* Returns 0 if successful */
+static int
+cmddt_process(int sg_fd, const struct opts_t * op)
+{
+ int k, j, num, len, pdt, reserved_cmddt, support_num, res;
+ char op_name[128];
+
+ memset(rsp_buff, 0, rsp_buff_sz);
+ if (op->do_cmddt > 1) {
+ printf("Supported command list:\n");
+ for (k = 0; k < 256; ++k) {
+ res = sg_ll_inquiry(sg_fd, true /* cmddt */, false, k, rsp_buff,
+ DEF_ALLOC_LEN, true, op->verbose);
+ if (0 == res) {
+ pdt = rsp_buff[0] & PDT_MASK;
+ support_num = rsp_buff[1] & 7;
+ reserved_cmddt = rsp_buff[4];
+ if ((3 == support_num) || (5 == support_num)) {
+ num = rsp_buff[5];
+ for (j = 0; j < num; ++j)
+ printf(" %.2x", (int)rsp_buff[6 + j]);
+ if (5 == support_num)
+ printf(" [vendor specific manner (5)]");
+ sg_get_opcode_name((uint8_t)k, pdt,
+ sizeof(op_name) - 1, op_name);
+ op_name[sizeof(op_name) - 1] = '\0';
+ printf(" %s\n", op_name);
+ } else if ((4 == support_num) || (6 == support_num))
+ printf(" opcode=0x%.2x vendor specific (%d)\n",
+ k, support_num);
+ else if ((0 == support_num) && (reserved_cmddt > 0)) {
+ printf(" opcode=0x%.2x ignored cmddt bit, "
+ "given standard INQUIRY response, stop\n", k);
+ break;
+ }
+ } else if (SG_LIB_CAT_ILLEGAL_REQ == res)
+ break;
+ else {
+ pr2serr("CmdDt INQUIRY on opcode=0x%.2x: failed\n", k);
+ break;
+ }
+ }
+ }
+ else {
+ res = sg_ll_inquiry(sg_fd, true /* cmddt */, false, op->vpd_pn,
+ rsp_buff, DEF_ALLOC_LEN, true, op->verbose);
+ if (0 == res) {
+ pdt = rsp_buff[0] & PDT_MASK;
+ if (! op->do_raw) {
+ printf("CmdDt INQUIRY, opcode=0x%.2x: [", op->vpd_pn);
+ sg_get_opcode_name((uint8_t)op->vpd_pn, pdt,
+ sizeof(op_name) - 1, op_name);
+ op_name[sizeof(op_name) - 1] = '\0';
+ printf("%s]\n", op_name);
+ }
+ len = rsp_buff[5] + 6;
+ reserved_cmddt = rsp_buff[4];
+ if (op->do_hex)
+ hex2stdout(rsp_buff, len, no_ascii_4hex(op));
+ else if (op->do_raw)
+ dStrRaw((const char *)rsp_buff, len);
+ else {
+ bool prnt_cmd = false;
+ const char * desc_p;
+
+ support_num = rsp_buff[1] & 7;
+ num = rsp_buff[5];
+ switch (support_num) {
+ case 0:
+ if (0 == reserved_cmddt)
+ desc_p = "no data available";
+ else
+ desc_p = "ignored cmddt bit, standard INQUIRY "
+ "response";
+ break;
+ case 1: desc_p = "not supported"; break;
+ case 2: desc_p = "reserved (2)"; break;
+ case 3: desc_p = "supported as per standard";
+ prnt_cmd = true;
+ break;
+ case 4: desc_p = "vendor specific (4)"; break;
+ case 5: desc_p = "supported in vendor specific way";
+ prnt_cmd = true;
+ break;
+ case 6: desc_p = "vendor specific (6)"; break;
+ case 7: desc_p = "reserved (7)"; break;
+ }
+ if (prnt_cmd) {
+ printf(" Support field: %s [", desc_p);
+ for (j = 0; j < num; ++j)
+ printf(" %.2x", (int)rsp_buff[6 + j]);
+ printf(" ]\n");
+ } else
+ printf(" Support field: %s\n", desc_p);
+ }
+ } else if (SG_LIB_CAT_ILLEGAL_REQ != res) {
+ if (! op->do_raw) {
+ printf("CmdDt INQUIRY, opcode=0x%.2x: [", op->vpd_pn);
+ sg_get_opcode_name((uint8_t)op->vpd_pn, 0,
+ sizeof(op_name) - 1, op_name);
+ op_name[sizeof(op_name) - 1] = '\0';
+ printf("%s]\n", op_name);
+ }
+ pr2serr("CmdDt INQUIRY on opcode=0x%.2x: failed\n", op->vpd_pn);
+ }
+ }
+ return res;
+}
+
+#else /* SG_SCSI_STRINGS */
+
+/* Returns 0. */
+static int
+cmddt_process(int sg_fd, const struct opts_t * op)
+{
+ if (sg_fd) { } /* suppress warning */
+ if (op) { } /* suppress warning */
+ pr2serr("'--cmddt' not implemented, use sg_opcodes\n");
+ return 0;
+}
+
+#endif /* SG_SCSI_STRINGS */
+
+
+/* Returns 0 if successful */
+static int
+vpd_mainly_hex(int sg_fd, struct opts_t * op, sgj_opaque_p jap, int off)
+{
+ bool as_json;
+ bool json_o_hr;
+ int res, len, n;
+ char b[128];
+ sgj_state * jsp = &op->json_st;
+ const char * cp;
+ uint8_t * rp;
+
+ as_json = jsp->pr_as_json;
+ json_o_hr = as_json && jsp->pr_out_hr;
+ rp = rsp_buff + off;
+ if ((! op->do_raw) && (op->do_hex < 3)) {
+ if (op->do_hex)
+ printf("VPD INQUIRY, page code=0x%.2x:\n", op->vpd_pn);
+ else
+ sgj_pr_hr(jsp, "VPD INQUIRY, page code=0x%.2x:\n", op->vpd_pn);
+ }
+ if (sg_fd < 0) {
+ len = sg_get_unaligned_be16(rp + 2) + 4;
+ res = 0;
+ } else {
+ memset(rp, 0, DEF_ALLOC_LEN);
+ res = vpd_fetch_page(sg_fd, rp, op->vpd_pn, op->maxlen,
+ op->do_quiet, op->verbose, &len);
+ }
+ if (0 == res) {
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ int pdt = pdt = rp[0] & PDT_MASK;
+
+ if (0 == op->vpd_pn)
+ decode_supported_vpd_4inq(rp, len, op, jap);
+ else {
+ if (op->verbose) {
+ cp = sg_get_pdt_str(pdt, sizeof(b), b);
+ if (op->do_hex)
+ printf(" [PQual=%d Peripheral device type: %s]\n",
+ (rp[0] & 0xe0) >> 5, cp);
+ else
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device "
+ "type: %s]\n", (rp[0] & 0xe0) >> 5, cp);
+ }
+ if (json_o_hr && (0 == op->do_hex) && (len > 0) &&
+ (len < UINT16_MAX)) {
+ char * p;
+
+ n = len * 4;
+ p = malloc(n);
+ if (p) {
+ n = hex2str(rp, len, NULL, 1, n - 1, p);
+ sgj_js_str_out(jsp, p, n);
+ }
+ } else
+ hex2stdout(rp, len, no_ascii_4hex(op));
+ }
+ }
+ } else {
+ if (SG_LIB_CAT_ILLEGAL_REQ == res)
+ pr2serr(" inquiry: field in cdb illegal (page not "
+ "supported)\n");
+ else {
+ sg_get_category_sense_str(res, sizeof(b), b, op->verbose);
+ pr2serr(" inquiry: %s\n", b);
+ }
+ }
+ return res;
+}
+
+static int
+recurse_vpd_decode(struct opts_t * op, sgj_opaque_p jop, int off)
+{
+ return vpd_decode(-1, op, jop, off);
+}
+
+/* Returns 0 if successful */
+static int
+vpd_decode(int sg_fd, struct opts_t * op, sgj_opaque_p jop, int off)
+{
+ bool bad = false;
+ bool qt = op->do_quiet;
+ int len, pdt, pn, vb /*, pqual */;
+ int res = 0;
+ sgj_state * jsp = &op->json_st;
+ bool as_json = jsp->pr_as_json;
+ sgj_opaque_p jo2p = NULL;
+ sgj_opaque_p jap = NULL;
+ const char * np;
+ const char * ep = "";
+ uint8_t * rp;
+
+ rp = rsp_buff + off;
+ vb = op->verbose;
+ if ((off > 0) && (VPD_NOPE_WANT_STD_INQ != op->vpd_pn))
+ pn = rp[1];
+ else
+ pn = op->vpd_pn;
+ if (sg_fd != -1 && !op->do_force && pn != VPD_SUPPORTED_VPDS) {
+ res = vpd_fetch_page(sg_fd, rp, VPD_SUPPORTED_VPDS, op->maxlen,
+ qt, vb, &len);
+ if (res)
+ goto out;
+ if (! vpd_page_is_supported(rp, len, pn, vb)) {
+ if (vb)
+ pr2serr("Given VPD page not in supported list, use --force "
+ "to override this check\n");
+ res = sg_convert_errno(EDOM); /* was SG_LIB_CAT_ILLEGAL_REQ */
+ goto out;
+ }
+ }
+ switch (pn) {
+ case VPD_SUPPORTED_VPDS: /* 0x0 ["sv"] */
+ np = "Supported VPD pages VPD page";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else if (op->do_hex)
+ hex2stdout(rp, len, no_ascii_4hex(op));
+ else {
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "supported_vpd_page_list");
+ }
+ decode_supported_vpd_4inq(rp, len, op, jap);
+ }
+ break;
+ case VPD_UNIT_SERIAL_NUM: /* 0x80 ["sn"] */
+ np = "Unit serial number VPD page";
+ if (! op->do_raw && ! op->do_export && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else if (op->do_hex)
+ hex2stdout(rp, len, no_ascii_4hex(op));
+ else {
+ char obuff[DEF_ALLOC_LEN];
+ int k, m;
+
+ memset(obuff, 0, sizeof(obuff));
+ len -= 4;
+ if (len >= (int)sizeof(obuff))
+ len = sizeof(obuff) - 1;
+ memcpy(obuff, rp + 4, len);
+ if (op->do_export) {
+ k = encode_whitespaces((uint8_t *)obuff, len);
+ if (k > 0) {
+ printf("SCSI_IDENT_SERIAL=");
+ /* udev-conforming character encoding */
+ for (m = 0; m < k; ++m) {
+ if ((obuff[m] >= '0' && obuff[m] <= '9') ||
+ (obuff[m] >= 'A' && obuff[m] <= 'Z') ||
+ (obuff[m] >= 'a' && obuff[m] <= 'z') ||
+ strchr("#+-.:=@_", obuff[m]) != NULL)
+ printf("%c", obuff[m]);
+ else
+ printf("\\x%02x", obuff[m]);
+ }
+ printf("\n");
+ }
+ } else {
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ k = encode_unicode((uint8_t *)obuff, len);
+ if (k > 0) {
+ sgj_pr_hr(jsp, " Unit serial number: %s\n", obuff);
+ sgj_js_nv_s(jsp, jo2p, "unit_serial_number", obuff);
+ }
+ }
+ }
+ break;
+ case VPD_DEVICE_ID: /* 0x83 ["di"] */
+ np = "Device Identification VPD page";
+ if (! op->do_raw && ! op->do_export && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else if (op->do_hex > 2)
+ hex2stdout(rp, len, -1);
+ else if (op->do_export && (! as_json))
+ export_dev_ids(rp + 4, len - 4, op->verbose);
+ else {
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "designation_descriptor_list");
+ }
+ decode_id_vpd(rp, len, op, jap);
+ }
+ break;
+ case VPD_SOFTW_INF_ID: /* 0x84 ["sii"] */
+ np = "Software interface identification VPD page";
+ if (! op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "software_interface_identifier_list");
+ }
+ decode_softw_inf_id(rp, len, op, jap);
+ }
+ break;
+ case VPD_MAN_NET_ADDR: /* 0x85 ["mna"] */
+ np = "Management network addresses page";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ // pdt = rp[0] & PDT_MASK;
+ // pdt_str = sg_get_pdt_str(pdt, sizeof(d), d);
+ // pqual = (rp[0] & 0xe0) >> 5;
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "network_services_descriptor_list");
+ }
+ decode_net_man_vpd(rp, len, op, jap);
+ }
+ break;
+ case VPD_EXT_INQ: /* 0x86 ["ei"] */
+ np = "Extended INQUIRY data";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s page\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ bool protect = false;
+
+ op->protect_not_sure = false;
+ if ((sg_fd >= 0) && (! op->do_force)) {
+ struct sg_simple_inquiry_resp sir;
+
+ res = sg_simple_inquiry(sg_fd, &sir, false, vb);
+ if (res) {
+ if (op->verbose)
+ pr2serr("%s: sg_simple_inquiry() failed, res=%d\n",
+ __func__, res);
+ op->protect_not_sure = true;
+ } else
+ protect = !!(sir.byte_5 & 0x1); /* SPC-3 and later */
+ } else
+ op->protect_not_sure = true;
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ decode_x_inq_vpd(rp, len, protect, op, jo2p);
+ }
+ break;
+ case VPD_MODE_PG_POLICY: /* 0x87 ["mpp"] */
+ np = "Mode page policy";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "mode_page_policy_descriptor_list");
+ }
+ decode_mode_policy_vpd(rp, len, op, jap);
+ }
+ break;
+ case VPD_SCSI_PORTS: /* 0x88 ["sp"] */
+ np = "SCSI Ports VPD page";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "scsi_ports_descriptor_list");
+ }
+ decode_scsi_ports_vpd_4inq(rp, len, op, jap);
+ }
+ break;
+ case VPD_ATA_INFO: /* 0x89 ["ai"] */
+ np = "ATA information VPD page";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ /* format output for 'hdparm --Istdin' with '-rr' or '-HHH' */
+ if ((2 == op->do_raw) || (3 == op->do_hex))
+ dWordHex((const unsigned short *)(rp + 60), 256, -2,
+ sg_is_big_endian());
+ else if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ else
+ op->do_long = true;
+ decode_ata_info_vpd(rp, len, op, jo2p);
+ }
+ break;
+ case VPD_POWER_CONDITION: /* 0x8a ["pc"] */
+ np = "Power condition VPD page";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ decode_power_condition(rp, len, op, jo2p);
+ }
+ break;
+ case VPD_DEVICE_CONSTITUENTS: /* 0x8b ["dc"] */
+ np = "Device constituents page VPD page";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "constituent_descriptor_list");
+ }
+ decode_dev_constit_vpd(rp, len, op, jap, recurse_vpd_decode);
+ }
+ break;
+ case VPD_CFA_PROFILE_INFO: /* 0x8c ["cfa"] */
+ np = "CFA profile information VPD page";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "%s:\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "cfa_profile_descriptor_list");
+ }
+ decode_cga_profile_vpd(rp, len, op, jap);
+ }
+ }
+ break;
+ case VPD_POWER_CONSUMPTION: /* 0x8d ["psm"] */
+ np = "Power consumption VPD page";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "power_consumption_descriptor_list");
+ }
+ decode_power_consumption(rp, len, op, jap);
+ }
+ break;
+ case VPD_3PARTY_COPY: /* 0x8f ["tpc"] */
+ np = "Third party copy VPD page";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "third_party_copy_descriptor_list");
+ }
+ decode_3party_copy_vpd(rp, len, op, jap);
+ }
+ break;
+ case VPD_PROTO_LU: /* 0x90 ["pslu"] */
+ np = "Protocol specific logical unit information VPD page";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "logical_unit_information_descriptor_list");
+ }
+ decode_proto_lu_vpd(rp, len, op, jap);
+ }
+ break;
+ case VPD_PROTO_PORT: /* 0x91 ["pspo"] */
+ np = "Protocol specific port information VPD page";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "port_information_descriptor_list");
+ }
+ decode_proto_port_vpd(rp, len, op, jap);
+ }
+ break;
+ case VPD_SCSI_FEATURE_SETS: /* 0x92 ["sfs"] */
+ np = "SCSI Feature sets VPD page";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "feature_set_code_list");
+ }
+ decode_feature_sets_vpd(rp, len, op, jap);
+ }
+ break;
+ case 0xb0: /* VPD pages in B0h to BFh range depend on pdt */
+ np = NULL;
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool bl = false;
+ bool sad = false;
+ bool oi = false;
+
+ ep = "";
+ if (op->do_raw) {
+ dStrRaw((const char *)rp, len);
+ break;
+ }
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Block limits VPD page";
+ ep = "(SBC)";
+ bl = true;
+ break;
+ case PDT_TAPE: case PDT_MCHANGER:
+ np = "Sequential-access device capabilities VPD page";
+ ep = "(SSC)";
+ sad = true;
+ break;
+ case PDT_OSD:
+ np = "OSD information VPD page";
+ ep = "(OSD)";
+ oi = true;
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (op->do_hex < 3) {
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ }
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ if (bl)
+ decode_block_limits_vpd(rp, len, op, jo2p);
+ else if (sad || oi)
+ decode_b0_vpd(rp, len, op, jop);
+ } else if (! op->do_raw)
+ pr2serr("VPD INQUIRY: page=0xb0\n");
+ break;
+ case 0xb1: /* VPD pages in B0h to BFh range depend on pdt */
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool bdc = false;
+ static const char * masn =
+ "Manufactured-assigned serial number VPD page";
+
+ if (op->do_raw) {
+ dStrRaw((const char *)rp, len);
+ break;
+ }
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Block device characteristics VPD page";
+ ep = "(SBC)";
+ bdc = true;
+ break;
+ case PDT_TAPE: case PDT_MCHANGER:
+ np = masn;
+ ep = "(SSC)";
+ break;
+ case PDT_OSD:
+ np = "Security token VPD page";
+ ep = "(OSD)";
+ break;
+ case PDT_ADC:
+ np = masn;
+ ep = "(ADC)";
+ break;
+ default:
+ np = NULL;
+ printf("VPD INQUIRY: page=0x%x, pdt=0x%x\n", 0xb1, pdt);
+ break;
+ }
+ if (op->do_hex < 3) {
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ }
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ if (bdc)
+ decode_block_dev_ch_vpd(rp, len, op, jo2p);
+ else
+ decode_b1_vpd(rp, len, op, jo2p);
+ } else if (! op->do_raw)
+ pr2serr("VPD INQUIRY: page=0xb1\n");
+ break;
+ case 0xb2: /* VPD pages in B0h to BFh range depend on pdt */
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool lbpv = false;
+ bool tas = false;
+
+ if (op->do_raw) {
+ dStrRaw((const char *)rp, len);
+ break;
+ }
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Logical block provisioning VPD page";
+ ep = "(SBC)";
+ lbpv = true;
+ break;
+ case PDT_TAPE: case PDT_MCHANGER:
+ np = "TapeAlert supported flags VPD page";
+ ep = "(SSC)";
+ tas = true;
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (op->do_hex < 3) {
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ }
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ if (lbpv)
+ return decode_block_lb_prov_vpd(rp, len, op, jo2p);
+ else if (tas)
+ decode_tapealert_supported_vpd(rp, len, op, jo2p);
+ else
+ return vpd_mainly_hex(sg_fd, op, NULL, off);
+ } else if (! op->do_raw)
+ pr2serr("VPD INQUIRY: page=0xb2\n");
+ break;
+ case 0xb3:
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool ref = false;
+
+ if (op->do_raw) {
+ dStrRaw((const char *)rp, len);
+ break;
+ }
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Referrals VPD page";
+ ep = "(SBC)";
+ ref = true;
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (op->do_hex < 3) {
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ }
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ if (ref)
+ decode_referrals_vpd(rp, len, op, jo2p);
+ else
+ decode_b3_vpd(rp, len, op, jo2p);
+ return 0;
+ } else if (! op->do_raw)
+ pr2serr("VPD INQUIRY: page=0xb3\n");
+ break;
+ case 0xb4:
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool sbl = false;
+ bool dtde = false;
+
+ if (op->do_raw) {
+ dStrRaw((const char *)rp, len);
+ break;
+ }
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Supported block lengths and protection types VPD page";
+ ep = "(SBC)";
+ sbl = true;
+ break;
+ case PDT_TAPE: case PDT_MCHANGER:
+ np = "Device transfer data element VPD page";
+ ep = "(SSC)";
+ dtde = true;
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (op->do_hex < 3) {
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ }
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ if (sbl) {
+ if (as_json)
+ jap = sgj_named_subarray_r(jsp, jo2p, "logical_block_"
+ "length_and_protection_types_descriptor_list");
+ decode_sup_block_lens_vpd(rp, len, op, jap);
+ } else if (dtde) {
+ if (! jsp->pr_as_json)
+ hex2stdout(rp + 4, len - 4, 1);
+ sgj_js_nv_hex_bytes(jsp, jop, "device_transfer_data_element",
+ rp + 4, len - 4);
+ } else
+ return vpd_mainly_hex(sg_fd, op, NULL, off);
+ return 0;
+ } else if (! op->do_raw)
+ pr2serr("VPD INQUIRY: page=0xb4\n");
+ break;
+ case 0xb5:
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool bdce = false;
+ bool lbp = false;
+
+ if (op->do_raw) {
+ dStrRaw((const char *)rp, len);
+ break;
+ }
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Block device characteristics VPD page";
+ ep = "(SBC)";
+ bdce = true;
+ break;
+ case PDT_TAPE: case PDT_MCHANGER:
+ np = "Logical block protection VPD page";
+ ep = "(SSC)";
+ lbp = true;
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (op->do_hex < 3) {
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ }
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ if (bdce)
+ decode_block_dev_char_ext_vpd(rp, len, op, jo2p);
+ else if (lbp) { /* VPD_LB_PROTECTION 0xb5 ["lbpro"] (SSC) */
+ if (as_json)
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "logical_block_protection_method_descriptor_list");
+ decode_lb_protection_vpd(rp, len, op, jap);
+ } else
+ return vpd_mainly_hex(sg_fd, op, NULL, off);
+ return 0;
+ } else if (! op->do_raw)
+ pr2serr("VPD INQUIRY: page=0xb5\n");
+ break;
+ case 0xb6:
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool zbdch = false;
+
+ if (op->do_raw) {
+ dStrRaw((const char *)rp, len);
+ break;
+ }
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Zoned block device characteristics VPD page";
+ ep = "(SBC, ZBC)";
+ zbdch = true;
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (op->do_hex < 3) {
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ }
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ if (zbdch)
+ decode_zbdch_vpd(rp, len, op, jo2p);
+ else
+ return vpd_mainly_hex(sg_fd, op, NULL, off);
+ return 0;
+ } else if (! op->do_raw)
+ pr2serr("VPD INQUIRY: page=0xb6\n");
+ break;
+ case 0xb7:
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool ble = false;
+
+ if (op->do_raw) {
+ dStrRaw((const char *)rp, len);
+ break;
+ }
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Block limits extension VPD page";
+ ep = "(SBC)";
+ ble = true;
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (op->do_hex < 3) {
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ }
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ if (ble)
+ decode_block_limits_ext_vpd(rp, len, op, jo2p);
+ else
+ return vpd_mainly_hex(sg_fd, op, NULL, off);
+ return 0;
+ } else if (! op->do_raw)
+ pr2serr("VPD INQUIRY: page=0xb7\n");
+ break;
+ case 0xb8:
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool fp = false;
+
+ if (op->do_raw) {
+ dStrRaw((const char *)rp, len);
+ break;
+ }
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Format presets VPD page";
+ ep = "(SBC)";
+ fp = true;
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (op->do_hex < 3) {
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ }
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p, "format_preset_"
+ "descriptor_list");
+ }
+ if (fp)
+ decode_format_presets_vpd(rp, len, op, jap);
+ else
+ return vpd_mainly_hex(sg_fd, op, NULL, off);
+ return 0;
+ } else if (! op->do_raw)
+ pr2serr("VPD INQUIRY: page=0xb8\n");
+ break;
+ case 0xb9:
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool cpr = false;
+
+ if (op->do_raw) {
+ dStrRaw((const char *)rp, len);
+ break;
+ }
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Concurrent positioning LBAs VPD page";
+ ep = "(SBC)";
+ cpr = true;
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (op->do_hex < 3) {
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ }
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p, "lba_range_"
+ "descriptor_list");
+ }
+ if (cpr)
+ decode_con_pos_range_vpd(rp, len, op, jap);
+ else
+ return vpd_mainly_hex(sg_fd, op, NULL, off);
+ return 0;
+ } else if (! op->do_raw)
+ pr2serr("VPD INQUIRY: page=0xb8\n");
+ break;
+ /* Vendor specific VPD pages (>= 0xc0) */
+ case VPD_UPR_EMC: /* 0xc0 */
+ np = "Unit path report VPD page";
+ ep = "(EMC)";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ res = vpd_fetch_page(sg_fd, rp, pn, -1, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ decode_upr_vpd_c0_emc(rp, len, op, jo2p);
+ }
+ break;
+ case VPD_RDAC_VERS: /* 0xc2 */
+ np = "Software Version VPD page";
+ ep = "(RDAC)";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ res = vpd_fetch_page(sg_fd, rp, pn, -1, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ decode_rdac_vpd_c2(rp, len, op, jo2p);
+ }
+ break;
+ case VPD_RDAC_VAC: /* 0xc9 */
+ np = "Volume access control VPD page";
+ ep = "(RDAC)";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ res = vpd_fetch_page(sg_fd, rp, pn, -1, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ decode_rdac_vpd_c9(rp, len, op, jo2p);
+ }
+ break;
+ case SG_NVME_VPD_NICR: /* 0xde */
+ np = "NVMe Identify Controller Response VPD page";
+ /* NVMe: Identify Controller data structure (CNS 01h) */
+ ep = "(sg3_utils)";
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res) {
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ break;
+ }
+ if (op->do_raw) {
+ dStrRaw((const char *)rp, len);
+ break;
+ }
+ pdt = rp[0] & PDT_MASK;
+ if (op->do_hex < 3) {
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ }
+ if (len < 16) {
+ pr2serr("%s expected to be > 15 bytes long (got: %d)\n", ep, len);
+ break;
+ } else {
+ int n = len - 16;
+
+ if (n > 4096) {
+ pr2serr("NVMe Identify response expected to be <= 4096 "
+ "bytes (got: %d)\n", n);
+ break;
+ }
+ if (op->do_hex)
+ hex2stdout(rp, len, no_ascii_4hex(op));
+ else if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ sgj_js_nv_hex_bytes(jsp, jo2p, "response_bytes", rp + 16, n);
+ } else
+ hex2stdout(rp + 16, n, 1);
+ }
+ break;
+ default:
+ bad = true;
+ break;
+ }
+ if (bad) {
+ if ((pn > 0) && (pn < 0x80)) {
+ if (!op->do_raw && (op->do_hex < 3))
+ printf("VPD INQUIRY: ASCII information page, FRU code=0x%x\n",
+ pn);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else
+ decode_ascii_inf(rp, len, op);
+ }
+ } else {
+ if (op->do_hex < 3)
+ pr2serr(" Only hex output supported.\n");
+ return vpd_mainly_hex(sg_fd, op, NULL, off);
+ }
+ }
+out:
+ if (res) {
+ char b[80];
+
+ if (SG_LIB_CAT_ILLEGAL_REQ == res)
+ pr2serr(" inquiry: field in cdb illegal (page not "
+ "supported)\n");
+ else {
+ sg_get_category_sense_str(res, sizeof(b), b, vb);
+ pr2serr(" inquiry: %s\n", b);
+ }
+ }
+ return res;
+}
+
+#if (HAVE_NVME && (! IGNORE_NVME))
+
+static void
+nvme_hex_raw(const uint8_t * b, int b_len, const struct opts_t * op)
+{
+ if (op->do_raw)
+ dStrRaw((const char *)b, b_len);
+ else if (op->do_hex) {
+ if (op->do_hex < 3) {
+ printf("data_in buffer:\n");
+ hex2stdout(b, b_len, (2 == op->do_hex));
+ } else
+ hex2stdout(b, b_len, -1);
+ }
+}
+
+static const char * rperf[] = {"Best", "Better", "Good", "Degraded"};
+
+static void
+show_nvme_id_ns(const uint8_t * dinp, int do_long)
+{
+ bool got_eui_128 = false;
+ uint32_t u, k, off, num_lbaf, flbas, flba_info, md_size, lb_size;
+ uint64_t ns_sz, eui_64;
+
+ num_lbaf = dinp[25] + 1; /* spec says this is "0's based value" */
+ flbas = dinp[26] & 0xf; /* index of active LBA format (for this ns) */
+ ns_sz = sg_get_unaligned_le64(dinp + 0);
+ eui_64 = sg_get_unaligned_be64(dinp + 120); /* N.B. big endian */
+ if (! sg_all_zeros(dinp + 104, 16))
+ got_eui_128 = true;
+ printf(" Namespace size/capacity: %" PRIu64 "/%" PRIu64
+ " blocks\n", ns_sz, sg_get_unaligned_le64(dinp + 8));
+ printf(" Namespace utilization: %" PRIu64 " blocks\n",
+ sg_get_unaligned_le64(dinp + 16));
+ if (got_eui_128) { /* N.B. big endian */
+ printf(" NGUID: 0x%02x", dinp[104]);
+ for (k = 1; k < 16; ++k)
+ printf("%02x", dinp[104 + k]);
+ printf("\n");
+ } else if (do_long)
+ printf(" NGUID: 0x0\n");
+ if (eui_64)
+ printf(" EUI-64: 0x%" PRIx64 "\n", eui_64); /* N.B. big endian */
+ printf(" Number of LBA formats: %u\n", num_lbaf);
+ printf(" Index LBA size: %u\n", flbas);
+ for (k = 0, off = 128; k < num_lbaf; ++k, off += 4) {
+ printf(" LBA format %u support:", k);
+ if (k == flbas)
+ printf(" <-- active\n");
+ else
+ printf("\n");
+ flba_info = sg_get_unaligned_le32(dinp + off);
+ md_size = flba_info & 0xffff;
+ lb_size = flba_info >> 16 & 0xff;
+ if (lb_size > 31) {
+ pr2serr("%s: logical block size exponent of %u implies a LB "
+ "size larger than 4 billion bytes, ignore\n", __func__,
+ lb_size);
+ continue;
+ }
+ lb_size = 1U << lb_size;
+ ns_sz *= lb_size;
+ ns_sz /= 500*1000*1000;
+ if (ns_sz & 0x1)
+ ns_sz = (ns_sz / 2) + 1;
+ else
+ ns_sz = ns_sz / 2;
+ u = (flba_info >> 24) & 0x3;
+ printf(" Logical block size: %u bytes\n", lb_size);
+ printf(" Approximate namespace size: %" PRIu64 " GB\n", ns_sz);
+ printf(" Metadata size: %u bytes\n", md_size);
+ printf(" Relative performance: %s [0x%x]\n", rperf[u], u);
+ }
+}
+
+/* Send Identify(CNS=0, nsid) and decode the Identify namespace response */
+static int
+nvme_id_namespace(struct sg_pt_base * ptvp, uint32_t nsid,
+ struct sg_nvme_passthru_cmd * id_cmdp, uint8_t * id_dinp,
+ int id_din_len, const struct opts_t * op)
+{
+ int ret = 0;
+ int vb = op->verbose;
+ uint8_t resp[16];
+
+ clear_scsi_pt_obj(ptvp);
+ id_cmdp->nsid = nsid;
+ id_cmdp->cdw10 = 0x0; /* CNS=0x0 Identify NS (CNTID=0) */
+ id_cmdp->cdw11 = 0x0; /* NVMSETID=0 (only valid when CNS=0x4) */
+ id_cmdp->cdw14 = 0x0; /* UUID index (assume not supported) */
+ set_scsi_pt_data_in(ptvp, id_dinp, id_din_len);
+ set_scsi_pt_sense(ptvp, resp, sizeof(resp));
+ set_scsi_pt_cdb(ptvp, (const uint8_t *)id_cmdp, sizeof(*id_cmdp));
+ ret = do_scsi_pt(ptvp, -1, 0 /* timeout (def: 1 min) */, vb);
+ if (vb > 2)
+ pr2serr("%s: do_scsi_pt() result is %d\n", __func__, ret);
+ if (ret) {
+ if (SCSI_PT_DO_BAD_PARAMS == ret)
+ ret = SG_LIB_SYNTAX_ERROR;
+ else if (SCSI_PT_DO_TIMEOUT == ret)
+ ret = SG_LIB_CAT_TIMEOUT;
+ else if (ret < 0)
+ ret = sg_convert_errno(-ret);
+ return ret;
+ }
+ if (op->do_hex || op->do_raw) {
+ nvme_hex_raw(id_dinp, id_din_len, op);
+ return 0;
+ }
+ show_nvme_id_ns(id_dinp, op->do_long);
+ return 0;
+}
+
+static void
+show_nvme_id_ctrl(const uint8_t *dinp, const char *dev_name, int do_long)
+{
+ bool got_fguid;
+ uint8_t ver_min, ver_ter, mtds;
+ uint16_t ver_maj, oacs, oncs;
+ uint32_t k, ver, max_nsid, npss, j, n, m;
+ uint64_t sz1, sz2;
+ const uint8_t * up;
+
+ max_nsid = sg_get_unaligned_le32(dinp + 516); /* NN */
+ printf("Identify controller for %s:\n", dev_name);
+ printf(" Model number: %.40s\n", (const char *)(dinp + 24));
+ printf(" Serial number: %.20s\n", (const char *)(dinp + 4));
+ printf(" Firmware revision: %.8s\n", (const char *)(dinp + 64));
+ ver = sg_get_unaligned_le32(dinp + 80);
+ ver_maj = (ver >> 16);
+ ver_min = (ver >> 8) & 0xff;
+ ver_ter = (ver & 0xff);
+ printf(" Version: %u.%u", ver_maj, ver_min);
+ if ((ver_maj > 1) || ((1 == ver_maj) && (ver_min > 2)) ||
+ ((1 == ver_maj) && (2 == ver_min) && (ver_ter > 0)))
+ printf(".%u\n", ver_ter);
+ else
+ printf("\n");
+ oacs = sg_get_unaligned_le16(dinp + 256);
+ if (0x1ff & oacs) {
+ printf(" Optional admin command support:\n");
+ if (0x200 & oacs)
+ printf(" Get LBA status\n"); /* NVMe 1.4 */
+ if (0x100 & oacs)
+ printf(" Doorbell buffer config\n");
+ if (0x80 & oacs)
+ printf(" Virtualization management\n");
+ if (0x40 & oacs)
+ printf(" NVMe-MI send and NVMe-MI receive\n");
+ if (0x20 & oacs)
+ printf(" Directive send and directive receive\n");
+ if (0x10 & oacs)
+ printf(" Device self-test\n");
+ if (0x8 & oacs)
+ printf(" Namespace management and attachment\n");
+ if (0x4 & oacs)
+ printf(" Firmware download and commit\n");
+ if (0x2 & oacs)
+ printf(" Format NVM\n");
+ if (0x1 & oacs)
+ printf(" Security send and receive\n");
+ } else
+ printf(" No optional admin command support\n");
+ oncs = sg_get_unaligned_le16(dinp + 256);
+ if (0x7f & oncs) {
+ printf(" Optional NVM command support:\n");
+ if (0x80 & oncs)
+ printf(" Verify\n"); /* NVMe 1.4 */
+ if (0x40 & oncs)
+ printf(" Timestamp feature\n");
+ if (0x20 & oncs)
+ printf(" Reservations\n");
+ if (0x10 & oncs)
+ printf(" Save and Select fields non-zero\n");
+ if (0x8 & oncs)
+ printf(" Write zeroes\n");
+ if (0x4 & oncs)
+ printf(" Dataset management\n");
+ if (0x2 & oncs)
+ printf(" Write uncorrectable\n");
+ if (0x1 & oncs)
+ printf(" Compare\n");
+ } else
+ printf(" No optional NVM command support\n");
+ printf(" PCI vendor ID VID/SSVID: 0x%x/0x%x\n",
+ sg_get_unaligned_le16(dinp + 0),
+ sg_get_unaligned_le16(dinp + 2));
+ printf(" IEEE OUI Identifier: 0x%x\n", /* this has been renamed AOI */
+ sg_get_unaligned_le24(dinp + 73));
+ got_fguid = ! sg_all_zeros(dinp + 112, 16);
+ if (got_fguid) {
+ printf(" FGUID: 0x%02x", dinp[112]);
+ for (k = 1; k < 16; ++k)
+ printf("%02x", dinp[112 + k]);
+ printf("\n");
+ } else if (do_long)
+ printf(" FGUID: 0x0\n");
+ printf(" Controller ID: 0x%x\n", sg_get_unaligned_le16(dinp + 78));
+ if (do_long) { /* Bytes 240 to 255 are reserved for NVME-MI */
+ printf(" NVMe Management Interface [MI] settings:\n");
+ printf(" Enclosure: %d [NVMEE]\n", !! (0x2 & dinp[253]));
+ printf(" NVMe Storage device: %d [NVMESD]\n",
+ !! (0x1 & dinp[253]));
+ printf(" Management endpoint capabilities, over a PCIe port: %d "
+ "[PCIEME]\n",
+ !! (0x2 & dinp[255]));
+ printf(" Management endpoint capabilities, over a SMBus/I2C port: "
+ "%d [SMBUSME]\n", !! (0x1 & dinp[255]));
+ }
+ printf(" Number of namespaces: %u\n", max_nsid);
+ sz1 = sg_get_unaligned_le64(dinp + 280); /* lower 64 bits */
+ sz2 = sg_get_unaligned_le64(dinp + 288); /* upper 64 bits */
+ if (sz2)
+ printf(" Total NVM capacity: huge ...\n");
+ else if (sz1)
+ printf(" Total NVM capacity: %" PRIu64 " bytes\n", sz1);
+ mtds = dinp[77];
+ printf(" Maximum data transfer size: ");
+ if (mtds)
+ printf("%u pages\n", 1U << mtds);
+ else
+ printf("<unlimited>\n");
+
+ if (do_long) {
+ const char * const non_op = "does not process I/O";
+ const char * const operat = "processes I/O";
+ const char * cp;
+
+ printf(" Total NVM capacity: 0 bytes\n");
+ npss = dinp[263] + 1;
+ up = dinp + 2048;
+ for (k = 0; k < npss; ++k, up += 32) {
+ n = sg_get_unaligned_le16(up + 0);
+ n *= (0x1 & up[3]) ? 1 : 100; /* unit: 100 microWatts */
+ j = n / 10; /* unit: 1 milliWatts */
+ m = j % 1000;
+ j /= 1000;
+ cp = (0x2 & up[3]) ? non_op : operat;
+ printf(" Power state %u: Max power: ", k);
+ if (0 == j) {
+ m = n % 10;
+ n /= 10;
+ printf("%u.%u milliWatts, %s\n", n, m, cp);
+ } else
+ printf("%u.%03u Watts, %s\n", j, m, cp);
+ n = sg_get_unaligned_le32(up + 4);
+ if (0 == n)
+ printf(" [ENLAT], ");
+ else
+ printf(" ENLAT=%u, ", n);
+ n = sg_get_unaligned_le32(up + 8);
+ if (0 == n)
+ printf("[EXLAT], ");
+ else
+ printf("EXLAT=%u, ", n);
+ n = 0x1f & up[12];
+ printf("RRT=%u, ", n);
+ n = 0x1f & up[13];
+ printf("RRL=%u, ", n);
+ n = 0x1f & up[14];
+ printf("RWT=%u, ", n);
+ n = 0x1f & up[15];
+ printf("RWL=%u\n", n);
+ }
+ }
+}
+
+/* Send a NVMe Identify(CNS=1) and decode Controller info. If the
+ * device name includes a namespace indication (e.g. /dev/nvme0ns1) then
+ * an Identify namespace command is sent to that namespace (e.g. 1). If the
+ * device name does not contain a namespace indication (e.g. /dev/nvme0)
+ * and --only is not given then nvme_id_namespace() is sent for each
+ * namespace in the controller. Namespaces number sequentially starting at
+ * 1 . The CNS (Controller or Namespace Structure) field is CDW10 7:0, was
+ * only bit 0 in NVMe 1.0 and bits 1:0 in NVMe 1.1, thereafter 8 bits. */
+static int
+do_nvme_identify_ctrl(int pt_fd, const struct opts_t * op)
+{
+ int ret = 0;
+ int vb = op->verbose;
+ uint32_t k, nsid, max_nsid;
+ struct sg_pt_base * ptvp;
+ struct sg_nvme_passthru_cmd identify_cmd;
+ struct sg_nvme_passthru_cmd * id_cmdp = &identify_cmd;
+ uint8_t * id_dinp = NULL;
+ uint8_t * free_id_dinp = NULL;
+ const uint32_t pg_sz = sg_get_page_size();
+ uint8_t resp[16];
+
+ if (op->do_raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ return SG_LIB_FILE_ERROR;
+ }
+ }
+ ptvp = construct_scsi_pt_obj_with_fd(pt_fd, vb);
+ if (NULL == ptvp) {
+ pr2serr("%s: memory problem\n", __func__);
+ return sg_convert_errno(ENOMEM);
+ }
+ memset(id_cmdp, 0, sizeof(*id_cmdp));
+ id_cmdp->opcode = 0x6;
+ nsid = get_pt_nvme_nsid(ptvp);
+ id_cmdp->cdw10 = 0x1; /* CNS=0x1 --> Identify controller */
+ /* id_cmdp->nsid is a "don't care" when CNS=1, so leave as 0 */
+ id_dinp = sg_memalign(pg_sz, pg_sz, &free_id_dinp, false);
+ if (NULL == id_dinp) {
+ pr2serr("%s: sg_memalign problem\n", __func__);
+ return sg_convert_errno(ENOMEM);
+ }
+ set_scsi_pt_data_in(ptvp, id_dinp, pg_sz);
+ set_scsi_pt_cdb(ptvp, (const uint8_t *)id_cmdp, sizeof(*id_cmdp));
+ set_scsi_pt_sense(ptvp, resp, sizeof(resp));
+ ret = do_scsi_pt(ptvp, -1, 0 /* timeout (def: 1 min) */, vb);
+ if (vb > 2)
+ pr2serr("%s: do_scsi_pt result is %d\n", __func__, ret);
+ if (ret) {
+ if (SCSI_PT_DO_BAD_PARAMS == ret)
+ ret = SG_LIB_SYNTAX_ERROR;
+ else if (SCSI_PT_DO_TIMEOUT == ret)
+ ret = SG_LIB_CAT_TIMEOUT;
+ else if (ret < 0)
+ ret = sg_convert_errno(-ret);
+ goto err_out;
+ }
+ max_nsid = sg_get_unaligned_le32(id_dinp + 516); /* NN */
+ if (op->do_raw || op->do_hex) {
+ if (op->do_only || (SG_NVME_CTL_NSID == nsid ) ||
+ (SG_NVME_BROADCAST_NSID == nsid)) {
+ nvme_hex_raw(id_dinp, pg_sz, op);
+ goto fini;
+ }
+ goto skip1;
+ }
+ show_nvme_id_ctrl(id_dinp, op->device_name, op->do_long);
+skip1:
+ if (op->do_only)
+ goto fini;
+ if (nsid > 0) {
+ if (! (op->do_raw || (op->do_hex > 2))) {
+ printf(" Namespace %u (deduced from device name):\n", nsid);
+ if (nsid > max_nsid)
+ pr2serr("NSID from device (%u) should not exceed number of "
+ "namespaces (%u)\n", nsid, max_nsid);
+ }
+ ret = nvme_id_namespace(ptvp, nsid, id_cmdp, id_dinp, pg_sz, op);
+ if (ret)
+ goto err_out;
+
+ } else { /* nsid=0 so char device; loop over all namespaces */
+ for (k = 1; k <= max_nsid; ++k) {
+ if ((! op->do_raw) || (op->do_hex < 3))
+ printf(" Namespace %u (of %u):\n", k, max_nsid);
+ ret = nvme_id_namespace(ptvp, k, id_cmdp, id_dinp, pg_sz, op);
+ if (ret)
+ goto err_out;
+ if (op->do_raw || op->do_hex)
+ goto fini;
+ }
+ }
+fini:
+ ret = 0;
+err_out:
+ destruct_scsi_pt_obj(ptvp);
+ free(free_id_dinp);
+ return ret;
+}
+#endif /* (HAVE_NVME && (! IGNORE_NVME)) */
+
+
+int
+main(int argc, char * argv[])
+{
+ bool as_json;
+ int res, n, err;
+ int sg_fd = -1;
+ int ret = 0;
+ int subvalue = 0;
+ int inhex_len = 0;
+ int inraw_len = 0;
+ const char * cp;
+ const struct svpd_values_name_t * vnp;
+ sgj_state * jsp;
+ sgj_opaque_p jop = NULL;
+ struct opts_t opts SG_C_CPP_ZERO_INIT;
+ struct opts_t * op;
+
+ op = &opts;
+ op->invoker = SG_VPD_INV_SG_INQ;
+ op->vpd_pn = -1;
+ op->vend_prod_num = -1;
+ op->page_pdt = -1;
+ op->do_block = -1; /* use default for OS */
+ res = parse_cmd_line(op, argc, argv);
+ if (res)
+ return SG_LIB_SYNTAX_ERROR;
+ if (op->do_help) {
+ usage_for(op);
+ if (op->do_help > 1) {
+ pr2serr("\n>>> Available VPD page abbreviations:\n");
+ enumerate_vpds();
+ }
+ return 0;
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->verbose = 0;
+ } else if (! op->verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (op->version_given) {
+ pr2serr("Version string: %s\n", version_str);
+ return 0;
+ }
+ jsp = &op->json_st;
+ as_json = jsp->pr_as_json;
+ if (op->page_str) {
+ if (op->vpd_pn >= 0) {
+ pr2serr("Given '-p' option and another option that "
+ "implies a page\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if ('-' == op->page_str[0])
+ op->vpd_pn = VPD_NOPE_WANT_STD_INQ;
+ else if (isalpha((uint8_t)op->page_str[0])) {
+ vnp = sdp_find_vpd_by_acron(op->page_str);
+ if (NULL == vnp) {
+#ifdef SG_SCSI_STRINGS
+ if (op->opt_new)
+ pr2serr("abbreviation %s given to '--page=' "
+ "not recognized\n", op->page_str);
+ else
+ pr2serr("abbreviation %s given to '-p=' "
+ "not recognized\n", op->page_str);
+#else
+ pr2serr("abbreviation %s given to '--page=' "
+ "not recognized\n", op->page_str);
+#endif
+ pr2serr(">>> Available abbreviations:\n");
+ enumerate_vpds();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ // if ((1 != op->do_hex) && (0 == op->do_raw))
+ if (0 == op->do_raw)
+ op->do_decode = true;
+ op->vpd_pn = vnp->value;
+ subvalue = vnp->subvalue;
+ op->page_pdt = vnp->pdt;
+ } else {
+ cp = strchr(op->page_str, ',');
+ if (cp && op->vend_prod) {
+ pr2serr("the --page=pg,vp and the --vendor=vp forms overlap, "
+ "choose one or the other\n");
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ op->vpd_pn = sg_get_num_nomult(op->page_str);
+ if ((op->vpd_pn < 0) || (op->vpd_pn > 255)) {
+ pr2serr("Bad page code value after '-p' option\n");
+ printf("Available standard VPD pages:\n");
+ enumerate_vpds(/* 1, 1 */);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ if (cp) {
+ if (isdigit((uint8_t)*(cp + 1)))
+ op->vend_prod_num = sg_get_num_nomult(cp + 1);
+ else
+ op->vend_prod_num = svpd_find_vp_num_by_acron(cp + 1);
+ if ((op->vend_prod_num < 0) || (op->vend_prod_num > 255)) {
+ pr2serr("Bad vendor/product acronym after comma in '-p' "
+ "option\n");
+ if (op->vend_prod_num < 0)
+ svpd_enumerate_vendor(-1);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ subvalue = op->vend_prod_num;
+ } else if (op->vend_prod) {
+ if (isdigit((uint8_t)op->vend_prod[0]))
+ op->vend_prod_num = sg_get_num_nomult(op->vend_prod);
+ else
+ op->vend_prod_num =
+ svpd_find_vp_num_by_acron(op->vend_prod);
+ if ((op->vend_prod_num < 0) || (op->vend_prod_num > 255)) {
+ pr2serr("Bad vendor/product acronym after '--vendor=' "
+ "option\n");
+ svpd_enumerate_vendor(-1);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ subvalue = op->vend_prod_num;
+ }
+ }
+ if (op->verbose > 3)
+ pr2serr("'--page=' matched pn=%d [0x%x], subvalue=%d\n",
+ op->vpd_pn, op->vpd_pn, subvalue);
+#if 0
+ else {
+#ifdef SG_SCSI_STRINGS
+ if (op->opt_new) {
+ n = sg_get_num(op->page_str);
+ if ((n < 0) || (n > 255)) {
+ pr2serr("Bad argument to '--page=', "
+ "expecting 0 to 255 inclusive\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((1 != op->do_hex) && (0 == op->do_raw))
+ op->do_decode = true;
+ } else {
+ int num;
+ unsigned int u;
+
+ num = sscanf(op->page_str, "%x", &u);
+ if ((1 != num) || (u > 255)) {
+ pr2serr("Inappropriate value after '-o=' "
+ "or '-p=' option\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ n = u;
+ }
+#else
+ n = sg_get_num(op->page_str);
+ if ((n < 0) || (n > 255)) {
+ pr2serr("Bad argument to '--page=', "
+ "expecting 0 to 255 inclusive\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((1 != op->do_hex) && (0 == op->do_raw))
+ op->do_decode = true;
+#endif /* SG_SCSI_STRINGS */
+ op->vpd_pn = n;
+ }
+#endif
+ } else if (op->vend_prod) {
+ if (isdigit((uint8_t)op->vend_prod[0]))
+ op->vend_prod_num = sg_get_num_nomult(op->vend_prod);
+ else
+ op->vend_prod_num = svpd_find_vp_num_by_acron(op->vend_prod);
+ if ((op->vend_prod_num < 0) || (op->vend_prod_num > 255)) {
+ pr2serr("Bad vendor/product acronym after '--vendor=' "
+ "option\n");
+ svpd_enumerate_vendor(-1);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ subvalue = op->vend_prod_num;
+ }
+ if (as_json)
+ jop = sgj_start_r(MY_NAME, version_str, argc, argv, jsp);
+
+ rsp_buff = sg_memalign(rsp_buff_sz, 0 /* page align */, &free_rsp_buff,
+ false);
+ if (NULL == rsp_buff) {
+ pr2serr("Unable to allocate %d bytes on heap\n", rsp_buff_sz);
+ return sg_convert_errno(ENOMEM);
+ }
+ if (op->sinq_inraw_fn) {
+ if (op->do_cmddt) {
+ pr2serr("Don't support --cmddt with --sinq-inraw= option\n");
+ ret = SG_LIB_CONTRADICT;
+ goto err_out;
+ }
+ if ((ret = sg_f2hex_arr(op->sinq_inraw_fn, true, false, rsp_buff,
+ &inraw_len, rsp_buff_sz))) {
+ goto err_out;
+ }
+ if (inraw_len < 36) {
+ pr2serr("Unable to read 36 or more bytes from %s\n",
+ op->sinq_inraw_fn);
+ ret = SG_LIB_FILE_ERROR;
+ goto err_out;
+ }
+ memcpy(op->std_inq_a, rsp_buff, 36);
+ op->std_inq_a_valid = true;
+ }
+ if (op->inhex_fn) {
+ if (op->device_name) {
+ pr2serr("Cannot have both a DEVICE and --inhex= option\n");
+ ret = SG_LIB_CONTRADICT;
+ goto err_out;
+ }
+ if (op->do_cmddt) {
+ pr2serr("Don't support --cmddt with --inhex= option\n");
+ ret = SG_LIB_CONTRADICT;
+ goto err_out;
+ }
+ err = sg_f2hex_arr(op->inhex_fn, !!op->do_raw, false, rsp_buff,
+ &inhex_len, rsp_buff_sz);
+ if (err) {
+ if (err < 0)
+ err = sg_convert_errno(-err);
+ ret = err;
+ goto err_out;
+ }
+ op->do_raw = 0; /* don't want raw on output with --inhex= */
+ if (-1 == op->vpd_pn) { /* may be able to deduce VPD page */
+ if (op->page_pdt < 0)
+ op->page_pdt = PDT_MASK & rsp_buff[0];
+ if ((0x2 == (0xf & rsp_buff[3])) && (rsp_buff[2] > 2)) {
+ if (op->verbose)
+ pr2serr("Guessing from --inhex= this is a standard "
+ "INQUIRY\n");
+ } else if (rsp_buff[2] <= 2) {
+ /*
+ * Removable devices have the RMB bit set, which would
+ * present itself as vpd page 0x80 output if we're not
+ * careful
+ *
+ * Serial number must be right-aligned ASCII data in
+ * bytes 5-7; standard INQUIRY will have flags here.
+ */
+ if (rsp_buff[1] == 0x80 &&
+ (rsp_buff[5] < 0x20 || rsp_buff[5] > 0x80 ||
+ rsp_buff[6] < 0x20 || rsp_buff[6] > 0x80 ||
+ rsp_buff[7] < 0x20 || rsp_buff[7] > 0x80)) {
+ if (op->verbose)
+ pr2serr("Guessing from --inhex= this is a "
+ "standard INQUIRY\n");
+ } else {
+ if (op->verbose)
+ pr2serr("Guessing from --inhex= this is VPD "
+ "page 0x%x\n", rsp_buff[1]);
+ op->vpd_pn = rsp_buff[1];
+ op->do_vpd = true;
+ if ((1 != op->do_hex) && (0 == op->do_raw))
+ op->do_decode = true;
+ }
+ } else {
+ if (op->verbose)
+ pr2serr("page number unclear from --inhex, hope it's a "
+ "standard INQUIRY\n");
+ }
+ } else
+ op->do_vpd = true;
+ if (op->do_vpd) { /* Allow for multiple VPD pages from 'sg_vpd -a' */
+ op->maxlen = inhex_len;
+ ret = svpd_inhex_decode_all(op, jop);
+ goto fini2;
+ }
+ } else if (0 == op->device_name) {
+ pr2serr("No DEVICE argument given\n\n");
+ usage_for(op);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ if (VPD_NOPE_WANT_STD_INQ == op->vpd_pn)
+ op->vpd_pn = -1; /* now past guessing, set to normal indication */
+
+ if (op->do_export) {
+ if (op->vpd_pn != -1) {
+ if (op->vpd_pn != VPD_DEVICE_ID &&
+ op->vpd_pn != VPD_UNIT_SERIAL_NUM) {
+ pr2serr("Option '--export' only supported for VPD pages 0x80 "
+ "and 0x83\n");
+ usage_for(op);
+ ret = SG_LIB_CONTRADICT;
+ goto err_out;
+ }
+ op->do_decode = true;
+ op->do_vpd = true;
+ }
+ }
+
+ if ((0 == op->do_cmddt) && (op->vpd_pn >= 0) && op->page_given)
+ op->do_vpd = true;
+
+ if (op->do_raw && op->do_hex) {
+ pr2serr("Can't do hex and raw at the same time\n");
+ usage_for(op);
+ ret = SG_LIB_CONTRADICT;
+ goto err_out;
+ }
+ if (op->do_vpd && op->do_cmddt) {
+#ifdef SG_SCSI_STRINGS
+ if (op->opt_new)
+ pr2serr("Can't use '--cmddt' with VPD pages\n");
+ else
+ pr2serr("Can't have both '-e' and '-c' (or '-cl')\n");
+#else
+ pr2serr("Can't use '--cmddt' with VPD pages\n");
+#endif
+ usage_for(op);
+ ret = SG_LIB_CONTRADICT;
+ goto err_out;
+ }
+ if (((op->do_vpd || op->do_cmddt)) && (op->vpd_pn < 0))
+ op->vpd_pn = 0;
+ if (op->num_pages > 1) {
+ pr2serr("Can only fetch one page (VPD or Cmd) at a time\n");
+ usage_for(op);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ if (op->do_descriptors) {
+ if ((op->maxlen > 0) && (op->maxlen < 60)) {
+ pr2serr("version descriptors need INQUIRY response "
+ "length >= 60 bytes\n");
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ if (op->do_vpd || op->do_cmddt) {
+ pr2serr("version descriptors require standard INQUIRY\n");
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ }
+ if (op->num_pages && op->do_ata) {
+ pr2serr("Can't use '-A' with an explicit decode VPD page option\n");
+ ret = SG_LIB_CONTRADICT;
+ goto err_out;
+ }
+
+ if (op->do_raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ ret = SG_LIB_FILE_ERROR;
+ goto err_out;
+ }
+ }
+ if (op->inhex_fn) {
+ if (op->do_vpd) {
+ if (op->do_decode)
+ ret = vpd_decode(-1, op, jop, 0);
+ else
+ ret = vpd_mainly_hex(-1, op, NULL, 0);
+ goto err_out;
+ }
+#if defined(SG_LIB_LINUX) && defined(SG_SCSI_STRINGS) && \
+ defined(HDIO_GET_IDENTITY)
+ else if (op->do_ata) {
+ prepare_ata_identify(op, inhex_len);
+ ret = 0;
+ goto err_out;
+ }
+#endif
+ else {
+ op->maxlen = inhex_len;
+ ret = std_inq_process(-1, op, jop, 0);
+ goto err_out;
+ }
+ }
+
+#if defined(O_NONBLOCK) && defined(O_RDONLY)
+ if (op->do_block >= 0) {
+ n = O_RDONLY | (op->do_block ? 0 : O_NONBLOCK);
+ if ((sg_fd = sg_cmds_open_flags(op->device_name, n,
+ op->verbose)) < 0) {
+ pr2serr("sg_inq: error opening file: %s: %s\n",
+ op->device_name, safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ if (ret < 0)
+ ret = SG_LIB_FILE_ERROR;
+ goto err_out;
+ }
+
+ } else {
+ if ((sg_fd = sg_cmds_open_device(op->device_name, true /* ro */,
+ op->verbose)) < 0) {
+ pr2serr("sg_inq: error opening file: %s: %s\n",
+ op->device_name, safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ if (ret < 0)
+ ret = SG_LIB_FILE_ERROR;
+ goto err_out;
+ }
+ }
+#else
+ if ((sg_fd = sg_cmds_open_device(op->device_name, true /* ro */,
+ op->verbose)) < 0) {
+ pr2serr("sg_inq: error opening file: %s: %s\n",
+ op->device_name, safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ if (ret < 0)
+ ret = SG_LIB_FILE_ERROR;
+ goto err_out;
+ }
+#endif
+ memset(rsp_buff, 0, rsp_buff_sz);
+
+#if (HAVE_NVME && (! IGNORE_NVME))
+ n = check_pt_file_handle(sg_fd, op->device_name, op->verbose);
+ if (op->verbose > 1)
+ pr2serr("check_pt_file_handle()-->%d, page_given: %s\n", n,
+ (op->page_given ? "yes" : "no"));
+ if (n > 2) { /* NVMe char or NVMe block */
+ op->possible_nvme = true;
+ if (! op->page_given) {
+ ret = do_nvme_identify_ctrl(sg_fd, op);
+ goto fini2;
+ }
+ }
+#endif
+
+#if defined(SG_LIB_LINUX) && defined(SG_SCSI_STRINGS) && \
+ defined(HDIO_GET_IDENTITY)
+ if (op->do_ata) {
+ res = try_ata_identify(sg_fd, op->do_hex, op->do_raw,
+ op->verbose);
+ if (0 != res) {
+ pr2serr("fetching ATA information failed on %s\n",
+ op->device_name);
+ ret = SG_LIB_CAT_OTHER;
+ } else
+ ret = 0;
+ goto fini3;
+ }
+#endif
+
+ if ((! op->do_cmddt) && (! op->do_vpd)) {
+ /* So it's a standard INQUIRY, try ATA IDENTIFY if that fails */
+ ret = std_inq_process(sg_fd, op, jop, 0);
+ if (ret)
+ goto err_out;
+ } else if (op->do_cmddt) {
+ if (op->vpd_pn < 0)
+ op->vpd_pn = 0;
+ ret = cmddt_process(sg_fd, op);
+ if (ret)
+ goto err_out;
+ } else if (op->do_vpd) {
+ if (op->do_decode) {
+ ret = vpd_decode(sg_fd, op, jop, 0);
+ if (ret)
+ goto err_out;
+ } else {
+ ret = vpd_mainly_hex(sg_fd, op, NULL, 0);
+ if (ret)
+ goto err_out;
+ }
+ }
+
+#if (HAVE_NVME && (! IGNORE_NVME))
+fini2:
+#endif
+#if defined(SG_LIB_LINUX) && defined(SG_SCSI_STRINGS) && \
+ defined(HDIO_GET_IDENTITY)
+fini3:
+#endif
+
+err_out:
+ if (free_rsp_buff)
+ free(free_rsp_buff);
+ if ((0 == op->verbose) && (! op->do_export)) {
+ if (! sg_if_can2stderr("sg_inq failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' or '-vv' for "
+ "more information\n");
+ }
+ res = (sg_fd >= 0) ? sg_cmds_close_device(sg_fd) : 0;
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ ret = (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+ if (as_json) {
+ if (0 == op->do_hex)
+ sgj_js2file(jsp, NULL, ret, stdout);
+ sgj_finish(jsp);
+ }
+ return ret;
+}
+
+
+#if defined(SG_LIB_LINUX) && defined(SG_SCSI_STRINGS) && \
+ defined(HDIO_GET_IDENTITY)
+/* Following code permits ATA IDENTIFY commands to be performed on
+ ATA non "Packet Interface" devices (e.g. ATA disks).
+ GPL-ed code borrowed from smartmontools (smartmontools.sf.net).
+ Copyright (C) 2002-4 Bruce Allen
+ <smartmontools-support@lists.sourceforge.net>
+ */
+#ifndef ATA_IDENTIFY_DEVICE
+#define ATA_IDENTIFY_DEVICE 0xec
+#define ATA_IDENTIFY_PACKET_DEVICE 0xa1
+#endif
+#ifndef HDIO_DRIVE_CMD
+#define HDIO_DRIVE_CMD 0x031f
+#endif
+
+/* Needed parts of the ATA DRIVE IDENTIFY Structure. Those labeled
+ * word* are NOT used.
+ */
+struct ata_identify_device {
+ unsigned short words000_009[10];
+ uint8_t serial_no[20];
+ unsigned short words020_022[3];
+ uint8_t fw_rev[8];
+ uint8_t model[40];
+ unsigned short words047_079[33];
+ unsigned short major_rev_num;
+ unsigned short minor_rev_num;
+ unsigned short command_set_1;
+ unsigned short command_set_2;
+ unsigned short command_set_extension;
+ unsigned short cfs_enable_1;
+ unsigned short word086;
+ unsigned short csf_default;
+ unsigned short words088_255[168];
+};
+
+#define ATA_IDENTIFY_BUFF_SZ sizeof(struct ata_identify_device)
+#define HDIO_DRIVE_CMD_OFFSET 4
+
+static int
+ata_command_interface(int device, char *data, bool * atapi_flag, int verbose)
+{
+ int err;
+ uint8_t buff[ATA_IDENTIFY_BUFF_SZ + HDIO_DRIVE_CMD_OFFSET];
+ unsigned short get_ident[256];
+
+ if (atapi_flag)
+ *atapi_flag = false;
+ memset(buff, 0, sizeof(buff));
+ if (ioctl(device, HDIO_GET_IDENTITY, &get_ident) < 0) {
+ err = errno;
+ if (ENOTTY == err) {
+ if (verbose > 1)
+ pr2serr("HDIO_GET_IDENTITY failed with ENOTTY, "
+ "try HDIO_DRIVE_CMD ioctl ...\n");
+ buff[0] = ATA_IDENTIFY_DEVICE;
+ buff[3] = 1;
+ if (ioctl(device, HDIO_DRIVE_CMD, buff) < 0) {
+ if (verbose)
+ pr2serr("HDIO_DRIVE_CMD(ATA_IDENTIFY_DEVICE) "
+ "ioctl failed:\n\t%s [%d]\n",
+ safe_strerror(err), err);
+ return sg_convert_errno(err);
+ }
+ memcpy(data, buff + HDIO_DRIVE_CMD_OFFSET, ATA_IDENTIFY_BUFF_SZ);
+ return 0;
+ } else {
+ if (verbose)
+ pr2serr("HDIO_GET_IDENTITY ioctl failed:\n"
+ "\t%s [%d]\n", safe_strerror(err), err);
+ return sg_convert_errno(err);
+ }
+ } else if (verbose > 1)
+ pr2serr("HDIO_GET_IDENTITY succeeded\n");
+ if (0x2 == ((get_ident[0] >> 14) &0x3)) { /* ATAPI device */
+ if (verbose > 1)
+ pr2serr("assume ATAPI device from HDIO_GET_IDENTITY response\n");
+ memset(buff, 0, sizeof(buff));
+ buff[0] = ATA_IDENTIFY_PACKET_DEVICE;
+ buff[3] = 1;
+ if (ioctl(device, HDIO_DRIVE_CMD, buff) < 0) {
+ err = errno;
+ if (verbose)
+ pr2serr("HDIO_DRIVE_CMD(ATA_IDENTIFY_PACKET_DEVICE) ioctl "
+ "failed:\n\t%s [%d]\n", safe_strerror(err), err);
+ buff[0] = ATA_IDENTIFY_DEVICE;
+ buff[3] = 1;
+ if (ioctl(device, HDIO_DRIVE_CMD, buff) < 0) {
+ err = errno;
+ if (verbose)
+ pr2serr("HDIO_DRIVE_CMD(ATA_IDENTIFY_DEVICE) ioctl "
+ "failed:\n\t%s [%d]\n", safe_strerror(err), err);
+ return sg_convert_errno(err);
+ }
+ } else if (atapi_flag) {
+ *atapi_flag = true;
+ if (verbose > 1)
+ pr2serr("HDIO_DRIVE_CMD(ATA_IDENTIFY_DEVICE) succeeded\n");
+ }
+ } else { /* assume non-packet device */
+ buff[0] = ATA_IDENTIFY_DEVICE;
+ buff[3] = 1;
+ if (ioctl(device, HDIO_DRIVE_CMD, buff) < 0) {
+ err = errno;
+ if (verbose)
+ pr2serr("HDIO_DRIVE_CMD(ATA_IDENTIFY_DEVICE) ioctl failed:"
+ "\n\t%s [%d]\n", safe_strerror(err), err);
+ return sg_convert_errno(err);
+ } else if (verbose > 1)
+ pr2serr("HDIO_DRIVE_CMD(ATA_IDENTIFY_DEVICE) succeeded\n");
+ }
+ /* if the command returns data, copy it back */
+ memcpy(data, buff + HDIO_DRIVE_CMD_OFFSET, ATA_IDENTIFY_BUFF_SZ);
+ return 0;
+}
+
+static void
+show_ata_identify(const struct ata_identify_device * aidp, bool atapi,
+ int vb)
+{
+ int res;
+ char model[64];
+ char serial[64];
+ char firm[64];
+
+ printf("%s device: model, serial number and firmware revision:\n",
+ (atapi ? "ATAPI" : "ATA"));
+ res = sg_ata_get_chars((const unsigned short *)aidp->model,
+ 0, 20, sg_is_big_endian(), model);
+ model[res] = '\0';
+ res = sg_ata_get_chars((const unsigned short *)aidp->serial_no,
+ 0, 10, sg_is_big_endian(), serial);
+ serial[res] = '\0';
+ res = sg_ata_get_chars((const unsigned short *)aidp->fw_rev,
+ 0, 4, sg_is_big_endian(), firm);
+ firm[res] = '\0';
+ printf(" %s %s %s\n", model, serial, firm);
+ if (vb) {
+ if (atapi)
+ printf("ATA IDENTIFY PACKET DEVICE response "
+ "(256 words):\n");
+ else
+ printf("ATA IDENTIFY DEVICE response (256 words):\n");
+ dWordHex((const unsigned short *)aidp, 256, 0,
+ sg_is_big_endian());
+ }
+}
+
+static void
+prepare_ata_identify(const struct opts_t * op, int inhex_len)
+{
+ int n = inhex_len;
+ struct ata_identify_device ata_ident;
+
+ if (n < 16) {
+ pr2serr("%s: got only %d bytes, give up\n", __func__, n);
+ return;
+ } else if (n < 512)
+ pr2serr("%s: expect 512 bytes or more, got %d, continue\n", __func__,
+ n);
+ else if (n > 512)
+ n = 512;
+ memset(&ata_ident, 0, sizeof(ata_ident));
+ memcpy(&ata_ident, rsp_buff, n);
+ show_ata_identify(&ata_ident, false, op->verbose);
+}
+
+/* Returns 0 if successful, else errno of error */
+static int
+try_ata_identify(int ata_fd, int do_hex, int do_raw, int verbose)
+{
+ bool atapi;
+ int res;
+ struct ata_identify_device ata_ident;
+
+ memset(&ata_ident, 0, sizeof(ata_ident));
+ res = ata_command_interface(ata_fd, (char *)&ata_ident, &atapi, verbose);
+ if (res)
+ return res;
+ if ((2 == do_raw) || (3 == do_hex))
+ dWordHex((const unsigned short *)&ata_ident, 256, -2,
+ sg_is_big_endian());
+ else if (do_raw)
+ dStrRaw((const char *)&ata_ident, 512);
+ else {
+ if (do_hex) {
+ if (atapi)
+ printf("ATA IDENTIFY PACKET DEVICE response ");
+ else
+ printf("ATA IDENTIFY DEVICE response ");
+ if (do_hex > 1) {
+ printf("(512 bytes):\n");
+ hex2stdout((const uint8_t *)&ata_ident, 512, 0);
+ } else {
+ printf("(256 words):\n");
+ dWordHex((const unsigned short *)&ata_ident, 256, 0,
+ sg_is_big_endian());
+ }
+ } else
+ show_ata_identify(&ata_ident, atapi, verbose);
+ }
+ return 0;
+}
+#endif
+
+/* structure defined in sg_lib_data.h */
+extern struct sg_lib_simple_value_name_t sg_version_descriptor_arr[];
+
+
+static const char *
+find_version_descriptor_str(int value)
+{
+ int k;
+ const struct sg_lib_simple_value_name_t * vdp;
+
+ for (k = 0; ((vdp = sg_version_descriptor_arr + k) && vdp->name); ++k) {
+ if (value == vdp->value)
+ return vdp->name;
+ if (value < vdp->value)
+ break;
+ }
+ return NULL;
+}