diff options
Diffstat (limited to 'src/sg_inq.c')
-rw-r--r-- | src/sg_inq.c | 4881 |
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; +} |