diff options
Diffstat (limited to 'src/sg_vpd.c')
-rw-r--r-- | src/sg_vpd.c | 2770 |
1 files changed, 2770 insertions, 0 deletions
diff --git a/src/sg_vpd.c b/src/sg_vpd.c new file mode 100644 index 00000000..c8900fb7 --- /dev/null +++ b/src/sg_vpd.c @@ -0,0 +1,2770 @@ +/* + * Copyright (c) 2006-2022 Douglas Gilbert. + * All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the BSD_LICENSE file. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdbool.h> +#include <string.h> +#include <ctype.h> +#include <getopt.h> +#define __STDC_FORMAT_MACROS 1 +#include <inttypes.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.h" +#include "sg_cmds_basic.h" +#include "sg_unaligned.h" +#include "sg_pr2serr.h" + +#include "sg_vpd_common.h" /* shared with sg_inq */ + +/* This utility program was originally written for the Linux OS SCSI subsystem. + + This program fetches Vital Product Data (VPD) pages from the given + device and outputs it as directed. VPD pages are obtained via a + SCSI INQUIRY command. Most of the data in this program is obtained + from the SCSI SPC-4 document at https://www.t10.org . + +*/ + +static const char * version_str = "1.83 20220915"; /* spc6r06 + sbc5r03 */ + +#define MY_NAME "sg_vpd" + +/* Device identification VPD page associations */ +#define VPD_ASSOC_LU 0 +#define VPD_ASSOC_TPORT 1 +#define VPD_ASSOC_TDEVICE 2 + +/* 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 +#define MIN_MAXLEN 16 +#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 int svpd_decode_t10(int sg_fd, struct opts_t * op, sgj_opaque_p jop, + int subvalue, int off, const char * prefix); +static int svpd_unable_to_decode(int sg_fd, struct opts_t * op, int subvalue, + int off); + +static int filter_dev_ids(const char * print_if_found, int num_leading, + uint8_t * buff, int len, int m_assoc, + struct opts_t * op, sgj_opaque_p jop); + +static const int rsp_buff_sz = MX_ALLOC_LEN + 2; + +static uint8_t * free_rsp_buff; + +static struct option long_options[] = { + {"all", no_argument, 0, 'a'}, + {"enumerate", no_argument, 0, 'e'}, + {"examine", no_argument, 0, 'E'}, + {"force", no_argument, 0, 'f'}, + {"help", no_argument, 0, 'h'}, + {"hex", no_argument, 0, 'H'}, + {"ident", no_argument, 0, 'i'}, + {"inhex", required_argument, 0, 'I'}, + {"json", optional_argument, 0, 'j'}, + {"long", no_argument, 0, 'l'}, + {"maxlen", required_argument, 0, 'm'}, + {"page", required_argument, 0, 'p'}, + {"quiet", no_argument, 0, 'q'}, + {"raw", no_argument, 0, 'r'}, + {"sinq_inraw", required_argument, 0, 'Q'}, + {"sinq-inraw", required_argument, 0, 'Q'}, + {"vendor", required_argument, 0, 'M'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'V'}, + {0, 0, 0, 0}, +}; + + +/* arranged in alphabetical order by acronym */ +static struct svpd_values_name_t standard_vpd_pg[] = { + {VPD_AUTOMATION_DEV_SN, 0, 1, "adsn", "Automation device serial " + "number (SSC)"}, + {VPD_ATA_INFO, 0, -1, "ai", "ATA information (SAT)"}, + {VPD_ASCII_OP_DEF, 0, -1, "aod", + "ASCII implemented operating definition (obsolete)"}, + {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"}, + {VPD_DEVICE_CONSTITUENTS, 0, -1, "dc", "Device constituents"}, + {VPD_DEVICE_ID, 0, -1, "di", "Device identification"}, + {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"}, + {VPD_DTDE_ADDRESS, 0, 1, "dtde", + "Data transfer device element address (SSC)"}, + {VPD_EXT_INQ, 0, -1, "ei", "Extended inquiry data"}, + {VPD_FORMAT_PRESETS, 0, 0, "fp", "Format presets"}, + {VPD_IMP_OP_DEF, 0, -1, "iod", + "Implemented operating definition (obsolete)"}, + {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_OSD_INFO, 0, 0x11, "oi", "OSD information"}, + {VPD_POWER_CONDITION, 0, -1, "pc", "Power condition"},/* "po" in sg_inq */ + {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_SECURITY_TOKEN, 0, 0x11, "st", "Security token (OSD)"}, + {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, -1, "zbdch", "Zoned block device characteristics"}, + /* Use pdt of -1 since this page both for pdt=0 and pdt=0x14 */ + {0, 0, 0, NULL, NULL}, +}; + + +static void +usage() +{ + pr2serr("Usage: sg_vpd [--all] [--enumerate] [--examine] [--force] " + "[--help] [--hex]\n" + " [--ident] [--inhex=FN] [--long] [--maxlen=LEN] " + "[--page=PG]\n" + " [--quiet] [--raw] [--sinq_inraw=RFN] " + "[--vendor=VP] [--verbose]\n" + " [--version] DEVICE\n"); + pr2serr(" where:\n" + " --all|-a output all pages listed in the supported " + "pages VPD\n" + " page\n" + " --enumerate|-e enumerate known VPD pages names (ignore " + "DEVICE),\n" + " can be used with --page=num to search\n" + " --examine|-E starting at 0x80 scan pages code to 0xff\n" + " --force|-f skip VPD page 0 (supported VPD pages) " + "checking\n" + " --help|-h output this usage message then exit\n" + " --hex|-H output page in ASCII hexadecimal\n" + " --ident|-i output device identification VPD page, " + "twice for\n" + " short logical unit designator (equiv: " + "'-qp di_lu')\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" + " --long|-l perform extra decoding\n" + " --maxlen=LEN|-m LEN max response length (allocation " + "length in cdb)\n" + " (def: 0 -> 252 bytes)\n" + " --page=PG|-p PG fetch VPD page where PG is an " + "acronym, or a decimal\n" + " number unless hex indicator " + "is given (e.g. '0x83');\n" + " can also take PG,VP as an " + "operand\n" + " --quiet|-q suppress some output when decoding\n" + " --raw|-r output page in binary; if --inhex=FN is " + "also\n" + " given, FN is in binary (else FN is in " + "hex)\n" + " --sinq_inraw=RFN|-Q RFN read raw (binary) standard " + "INQUIRY\n" + " response from the RFN filename\n" + " --vendor=VP|-M VP vendor/product abbreviation [or " + "number]\n" + " --verbose|-v increase verbosity\n" + " --version|-V print version string and exit\n\n" + "Fetch Vital Product Data (VPD) page using SCSI INQUIRY or " + "decodes VPD\npage response held in file FN. To list available " + "pages use '-e'. Also\n'-p -1' or '-p sinq' yields the standard " + "INQUIRY response.\n"); +} + +static const struct svpd_values_name_t * +sdp_get_vpd_detail(int page_num, int subvalue, int pdt) +{ + const struct svpd_values_name_t * vnp; + int sv, ty; + + sv = (subvalue < 0) ? 1 : 0; + ty = (pdt < 0) ? 1 : 0; + for (vnp = standard_vpd_pg; vnp->acron; ++vnp) { + if ((page_num == vnp->value) && + (sv || (subvalue == vnp->subvalue)) && + (ty || (pdt == vnp->pdt))) + return vnp; + } + if (! ty) + return sdp_get_vpd_detail(page_num, subvalue, -1); + if (! sv) + return sdp_get_vpd_detail(page_num, -1, -1); + return NULL; +} + +static const struct svpd_values_name_t * +sdp_find_vpd_by_acron(const char * ap) +{ + const struct svpd_values_name_t * vnp; + + for (vnp = standard_vpd_pg; vnp->acron; ++vnp) { + if (0 == strcmp(vnp->acron, ap)) + return vnp; + } + return NULL; +} + +static void +enumerate_vpds(int standard, int vendor) +{ + const struct svpd_values_name_t * vnp; + + if (standard) { + for (vnp = standard_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); + } + } + } + if (vendor) + svpd_enumerate_vendor(-2); +} + +static int +count_standard_vpds(int vpd_pn) +{ + const struct svpd_values_name_t * vnp; + int matches = 0; + + for (vnp = standard_vpd_pg; vnp->acron; ++vnp) { + if ((vpd_pn == vnp->value) && vnp->name) { + if (0 == matches) + printf("Matching standard VPD pages:\n"); + ++matches; + 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); + } + } + return matches; +} + +static void +dStrRaw(const uint8_t * str, int len) +{ + int k; + + for (k = 0; k < len; ++k) + printf("%c", str[k]); +} + +/* Assume index is less than 16 */ +static const char * sg_ansi_version_arr[16] = +{ + "no conformance claimed", + "SCSI-1", /* obsolete, ANSI X3.131-1986 */ + "SCSI-2", /* obsolete, ANSI X3.131-1994 */ + "SPC", /* withdrawn, ANSI INCITS 301-1997 */ + "SPC-2", /* ANSI INCITS 351-2001, ISO/IEC 14776-452 */ + "SPC-3", /* ANSI INCITS 408-2005, ISO/IEC 14776-453 */ + "SPC-4", /* ANSI INCITS 513-2015 */ + "SPC-5", /* ANSI INCITS 502-2020 */ + "ecma=1, [8h]", + "ecma=1, [9h]", + "ecma=1, [Ah]", + "ecma=1, [Bh]", + "reserved [Ch]", + "reserved [Dh]", + "reserved [Eh]", + "reserved [Fh]", +}; + +static void +std_inq_decode(uint8_t * b, int len, struct opts_t * op, sgj_opaque_p jop) +{ + uint8_t ver; + int pqual, pdt, hp, j, n; + sgj_state * jsp = &op->json_st; + const char * cp; + char c[256]; + static const int clen = sizeof(c); + static const char * np = "Standard INQUIRY data format:"; + + if (len < 4) { + pr2serr("%s: len [%d] too short\n", __func__, len); + return; + } + pqual = (b[0] & 0xe0) >> 5; + pdt = b[0] & PDT_MASK; + hp = (b[1] >> 4) & 0x3; + ver = b[2]; + sgj_pr_hr(jsp, "%s", np); + if (0 == pqual) + sgj_pr_hr(jsp, "\n"); + else { + cp = pqual_str(pqual); + + if (pqual < 3) + sgj_pr_hr(jsp, " [PQ indicates %s]\n", cp); + else + sgj_pr_hr(jsp, " [PQ indicates %s [0x%x] ]\n", cp, pqual); + } + sgj_pr_hr(jsp, " PQual=%d PDT=%d RMB=%d LU_CONG=%d hot_pluggable=" + "%d version=0x%02x [%s]\n", pqual, pdt, !!(b[1] & 0x80), + !!(b[1] & 0x40), hp, ver, sg_ansi_version_arr[ver & 0xf]); + sgj_pr_hr(jsp, " [AERC=%d] [TrmTsk=%d] NormACA=%d HiSUP=%d " + " Resp_data_format=%d\n", + !!(b[3] & 0x80), !!(b[3] & 0x40), !!(b[3] & 0x20), + !!(b[3] & 0x10), b[3] & 0x0f); + if (len < 5) + goto skip1; + j = b[4] + 5; + if (op->verbose > 2) + pr2serr(">> requested %d bytes, %d bytes available\n", len, j); + sgj_pr_hr(jsp, " SCCS=%d ACC=%d TPGS=%d 3PC=%d Protect=%d " + "[BQue=%d]\n", !!(b[5] & 0x80), !!(b[5] & 0x40), + ((b[5] & 0x30) >> 4), !!(b[5] & 0x08), !!(b[5] & 0x01), + !!(b[6] & 0x80)); + n = 0; + n += sg_scnpr(c + n, clen - n, "EncServ=%d ", !!(b[6] & 0x40)); + if (b[6] & 0x10) + n += sg_scnpr(c + n, clen - n, "MultiP=1 (VS=%d) ", !!(b[6] & 0x20)); + else + n += sg_scnpr(c + n, clen - n, "MultiP=0 "); + n += sg_scnpr(c + n, clen - n, "[MChngr=%d] [ACKREQQ=%d] Addr16=%d", + !!(b[6] & 0x08), !!(b[6] & 0x04), !!(b[6] & 0x01)); + sgj_pr_hr(jsp, " %s\n", c); + sgj_pr_hr(jsp, " [RelAdr=%d] WBus16=%d Sync=%d [Linked=%d] " + "[TranDis=%d] CmdQue=%d\n", !!(b[7] & 0x80), !!(b[7] & 0x20), + !!(b[7] & 0x10), !!(b[7] & 0x08), !!(b[7] & 0x04), + !!(b[7] & 0x02)); + if (len < 36) + goto skip1; + sgj_pr_hr(jsp, " %s: %.8s\n", t10_vendor_id_hr, b + 8); + sgj_pr_hr(jsp, " %s: %.16s\n", product_id_hr, b + 16); + sgj_pr_hr(jsp, " %s: %.4s\n", product_rev_lev_hr, b + 32); +skip1: + if (! jsp->pr_as_json || (len < 8)) + return; + std_inq_decode_js(b, len, op, jop); +} + +/* VPD_DEVICE_ID 0x83 ["di, di_asis, di_lu, di_port, di_target"] */ +static void +device_id_vpd_variants(uint8_t * buff, int len, int subvalue, + struct opts_t * op, sgj_opaque_p jap) +{ + int m_a, blen; + uint8_t * b; + + if (len < 4) { + pr2serr("Device identification VPD page length too short=%d\n", len); + return; + } + blen = len - 4; + b = buff + 4; + m_a = -1; + if (0 == subvalue) { + filter_dev_ids(sg_get_desig_assoc_str(VPD_ASSOC_LU), 0, b, blen, + VPD_ASSOC_LU, op, jap); + filter_dev_ids(sg_get_desig_assoc_str(VPD_ASSOC_TPORT), 0, b, blen, + VPD_ASSOC_TPORT, op, jap); + filter_dev_ids(sg_get_desig_assoc_str(VPD_ASSOC_TDEVICE), 0, b, blen, + VPD_ASSOC_TDEVICE, op, jap); + } else if (VPD_DI_SEL_AS_IS == subvalue) + filter_dev_ids(NULL, 0, b, blen, m_a, op, jap); + else { + if (VPD_DI_SEL_LU & subvalue) + filter_dev_ids(sg_get_desig_assoc_str(VPD_ASSOC_LU), 0, b, blen, + VPD_ASSOC_LU, op, jap); + if (VPD_DI_SEL_TPORT & subvalue) + filter_dev_ids(sg_get_desig_assoc_str(VPD_ASSOC_TPORT), 0, b, + blen, VPD_ASSOC_TPORT, op, jap); + if (VPD_DI_SEL_TARGET & subvalue) + filter_dev_ids(sg_get_desig_assoc_str(VPD_ASSOC_TDEVICE), 0, + b, blen, VPD_ASSOC_TDEVICE, op, jap); + } +} + +static void /* VPD_SUPPORTED_VPDS ["sv"] */ +decode_supported_vpd_4vpd(uint8_t * buff, int len, struct opts_t * op, + sgj_opaque_p jap) +{ + uint8_t pn; + int k, rlen, pdt; + sgj_state * jsp = &op->json_st; + sgj_opaque_p jo2p; + const struct svpd_values_name_t * vnp; + uint8_t * bp; + char b[144]; + static const int blen = sizeof(b); + static const char * svps = "Supported VPD pages"; + + if ((1 == op->do_hex) || (op->do_hex > 2)) { + hex2stdout(buff, len, no_ascii_4hex(op)); + return; + } + pdt = PDT_MASK & buff[0]; + rlen = buff[3] + 4; + if (rlen > len) + pr2serr("%s VPD page truncated, indicates %d, got %d\n", svps, rlen, + len); + else + len = rlen; + if (len < 4) { + pr2serr("%s VPD page length too short=%d\n", svps, len); + return; + } + len -= 4; + bp = buff + 4; + + for (k = 0; k < len; ++k) { + pn = bp[k]; + snprintf(b, blen, "0x%02x", pn); + vnp = sdp_get_vpd_detail(pn, -1, pdt); + if (vnp) { + if (op->do_long) + sgj_pr_hr(jsp, " %s %s [%s]\n", b, vnp->name, vnp->acron); + else + sgj_pr_hr(jsp, " %s [%s]\n", vnp->name, vnp->acron); + } else if (op->vend_prod_num >= 0) { + vnp = svpd_find_vendor_by_num(pn, op->vend_prod_num); + if (vnp) { + if (op->do_long) + sgj_pr_hr(jsp, " %s %s [%s]\n", b, vnp->name, + vnp->acron); + else + sgj_pr_hr(jsp, " %s [%s]\n", vnp->name, vnp->acron); + } else + sgj_pr_hr(jsp, " %s\n", b); + } else + sgj_pr_hr(jsp, " %s\n", b); + if (jsp->pr_as_json) { + jo2p = sgj_new_unattached_object_r(jsp); + sgj_js_nv_i(jsp, jo2p, "i", pn); + sgj_js_nv_s(jsp, jo2p, "hex", b + 2); + if (vnp) { + sgj_js_nv_s(jsp, jo2p, "name", vnp->name); + sgj_js_nv_s(jsp, jo2p, "acronym", vnp->acron); + } else { + sgj_js_nv_s(jsp, jo2p, "name", "unknown"); + sgj_js_nv_s(jsp, jo2p, "acronym", "unknown"); + } + sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p); + } + } +} + +/* VPD_SCSI_PORTS 0x88 ["sp"] */ +static void +decode_scsi_ports_vpd_4vpd(uint8_t * buff, int len, struct opts_t * op, + sgj_opaque_p jap) +{ + int k, bump, rel_port, ip_tid_len, tpd_len; + sgj_state * jsp = &op->json_st; + sgj_opaque_p jo2p = NULL; + sgj_opaque_p ja2p = NULL; + uint8_t * bp; + + if ((1 == op->do_hex) || (op->do_hex > 2)) { + hex2stdout(buff, len, no_ascii_4hex(op)); + return; + } + if (len < 4) { + pr2serr("SCSI Ports VPD page length too short=%d\n", len); + return; + } + len -= 4; + bp = buff + 4; + for (k = 0; k < len; k += bump, bp += bump) { + rel_port = sg_get_unaligned_be16(bp + 2); + sgj_pr_hr(jsp, " Relative port=%d\n", rel_port); + jo2p = sgj_new_unattached_object_r(jsp); + 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)); + return; + } + if (ip_tid_len > 0) { + if (op->do_hex > 1) { + sgj_pr_hr(jsp, " Initiator port transport id:\n"); + hex2stdout((bp + 8), ip_tid_len, 1); + } 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)); + return; + } + if (tpd_len > 0) { + if (op->do_hex > 1) { + sgj_pr_hr(jsp, " Target port descriptor(s):\n"); + hex2stdout(bp + bump + 4, tpd_len, 1); + } else { + if ((0 == op->do_quiet) || (ip_tid_len > 0)) + sgj_pr_hr(jsp, " Target port descriptor(s):\n"); + if (jsp->pr_as_json) { + sgj_opaque_p jo3p = sgj_named_subobject_r(jsp, jo2p, + "target_port"); + + ja2p = sgj_named_subarray_r(jsp, jo3p, + "designation_descriptor_list"); + } + filter_dev_ids("", 2 /* leading spaces */, bp + bump + 4, + tpd_len, VPD_ASSOC_TPORT, op, ja2p); + } + } + bump += tpd_len + 4; + sgj_js_nv_o(jsp, jap, NULL, jo2p); + } +} + +/* Prints outs an abridged set of device identification designators + selected by association, designator type and/or code set. Not used + for JSON output. */ +static int +filter_dev_ids_quiet(uint8_t * buff, int len, int m_assoc) +{ + int k, m, p_id, c_set, piv, desig_type, i_len, naa, off, u; + int assoc, is_sas, rtp; + const uint8_t * bp; + const uint8_t * ip; + uint8_t sas_tport_addr[8]; + + rtp = 0; + memset(sas_tport_addr, 0, sizeof(sas_tport_addr)); + for (k = 0, off = -1; true; ++k) { + if ((0 == k) && (0 != buff[2])) { + /* first already in buff */ + if (m_assoc != VPD_ASSOC_LU) + return 0; + ip = buff; + c_set = 1; + assoc = VPD_ASSOC_LU; + is_sas = 0; + desig_type = 3; + i_len = 16; + } else { + u = sg_vpd_dev_id_iter(buff, len, &off, m_assoc, -1, -1); + if (0 != u) + break; + bp = buff + off; + i_len = bp[3]; + if ((off + i_len + 4) > len) { + pr2serr(" VPD page error: designator length longer than\n" + " remaining response length=%d\n", (len - off)); + return SG_LIB_CAT_MALFORMED; + } + ip = bp + 4; + p_id = ((bp[0] >> 4) & 0xf); + c_set = (bp[0] & 0xf); + piv = ((bp[1] & 0x80) ? 1 : 0); + is_sas = (piv && (6 == p_id)) ? 1 : 0; + assoc = ((bp[1] >> 4) & 0x3); + desig_type = (bp[1] & 0xf); + } + switch (desig_type) { + case 0: /* vendor specific */ + break; + case 1: /* T10 vendor identification */ + break; + case 2: /* EUI-64 based */ + if ((8 != i_len) && (12 != i_len) && (16 != i_len)) + pr2serr(" << expect 8, 12 and 16 byte " + "EUI, got %d>>\n", i_len); + printf(" 0x"); + for (m = 0; m < i_len; ++m) + printf("%02x", (unsigned int)ip[m]); + printf("\n"); + break; + case 3: /* NAA */ + 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, 0); + break; + } + switch (naa) { + case 2: /* NAA IEEE extended */ + if (8 != i_len) { + pr2serr(" << unexpected NAA 2 identifier " + "length: 0x%x>>\n", i_len); + hex2stderr(ip, i_len, 0); + break; + } + printf(" 0x"); + for (m = 0; m < 8; ++m) + printf("%02x", (unsigned int)ip[m]); + printf("\n"); + break; + case 3: /* Locally assigned */ + case 5: /* IEEE Registered */ + if (8 != i_len) { + pr2serr(" << unexpected NAA 3 or 5 " + "identifier length: 0x%x>>\n", i_len); + hex2stderr(ip, i_len, 0); + break; + } + if ((0 == is_sas) || (1 != assoc)) { + printf(" 0x"); + for (m = 0; m < 8; ++m) + printf("%02x", (unsigned int)ip[m]); + printf("\n"); + } else if (rtp) { + printf(" 0x"); + for (m = 0; m < 8; ++m) + printf("%02x", (unsigned int)ip[m]); + printf(",0x%x\n", rtp); + rtp = 0; + } else { + if (sas_tport_addr[0]) { + printf(" 0x"); + for (m = 0; m < 8; ++m) + printf("%02x", (unsigned int)sas_tport_addr[m]); + printf("\n"); + } + memcpy(sas_tport_addr, ip, sizeof(sas_tport_addr)); + } + break; + case 6: /* NAA IEEE registered extended */ + if (16 != i_len) { + pr2serr(" << unexpected NAA 6 identifier length: " + "0x%x>>\n", i_len); + hex2stderr(ip, i_len, 0); + break; + } + printf(" 0x"); + for (m = 0; m < 16; ++m) + printf("%02x", (unsigned int)ip[m]); + printf("\n"); + break; + default: + pr2serr(" << bad NAA nibble, expected 2, 3, 5 or 6, got " + "%d>>\n", naa); + hex2stderr(ip, i_len, 0); + break; + } + break; + case 4: /* Relative target port */ + if ((0 == is_sas) || (1 != c_set) || (1 != assoc) || (4 != i_len)) + break; + rtp = sg_get_unaligned_be16(ip + 2); + if (sas_tport_addr[0]) { + printf(" 0x"); + for (m = 0; m < 8; ++m) + printf("%02x", (unsigned int)sas_tport_addr[m]); + printf(",0x%x\n", rtp); + memset(sas_tport_addr, 0, sizeof(sas_tport_addr)); + rtp = 0; + } + break; + case 5: /* (primary) Target port group */ + break; + case 6: /* Logical unit group */ + break; + case 7: /* MD5 logical unit identifier */ + break; + case 8: /* SCSI name string */ + if (c_set < 2) { /* quietly accept ASCII for UTF-8 */ + pr2serr(" << expected UTF-8 code_set>>\n"); + hex2stderr(ip, i_len, 0); + 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))) { + pr2serr(" << expected name string prefix>>\n"); + hex2stderr(ip, i_len, -1); + break; + } + /* does %s print out UTF-8 ok?? + * Seems to depend on the locale. Looks ok here with my + * locale setting: en_AU.UTF-8 + */ + printf(" %.*s\n", i_len, (const char *)ip); + break; + case 9: /* Protocol specific port identifier */ + break; + case 0xa: /* UUID identifier [spc5r08] RFC 4122 */ + if ((1 != c_set) || (18 != i_len) || (1 != ((ip[0] >> 4) & 0xf))) + break; + for (m = 0; m < 16; ++m) { + if ((4 == m) || (6 == m) || (8 == m) || (10 == m)) + printf("-"); + printf("%02x", (unsigned int)ip[2 + m]); + } + printf("\n"); + break; + default: /* reserved */ + break; + } + } + if (sas_tport_addr[0]) { + printf(" 0x"); + for (m = 0; m < 8; ++m) + printf("%02x", (unsigned int)sas_tport_addr[m]); + printf("\n"); + } + if (-2 == u) { + pr2serr("VPD page error: short designator around offset %d\n", off); + return SG_LIB_CAT_MALFORMED; + } + return 0; +} + +/* Prints outs designation descriptors (dd_s) selected by association, + designator type and/or code set. VPD_DEVICE_ID and VPD_SCSI_PORTS */ +static int +filter_dev_ids(const char * print_if_found, int num_leading, uint8_t * buff, + int len, int m_assoc, struct opts_t * op, sgj_opaque_p jap) +{ + bool printed, sgj_out_hr; + int assoc, off, u, i_len; + const uint8_t * bp; + sgj_state * jsp = &op->json_st; + char b[1024]; + char sp[82]; + static const int blen = sizeof(b); + + if (op->do_quiet && (! jsp->pr_as_json)) + return filter_dev_ids_quiet(buff, len, m_assoc); + sgj_out_hr = false; + if (jsp->pr_as_json) { + int ret = filter_json_dev_ids(buff, len, m_assoc, op, jap); + + if (ret || (! jsp->pr_out_hr)) + return ret; + sgj_out_hr = true; + } + if (num_leading > (int)(sizeof(sp) - 2)) + num_leading = sizeof(sp) - 2; + if (num_leading > 0) + snprintf(sp, sizeof(sp), "%*c", num_leading, ' '); + else + sp[0] = '\0'; + if (buff[2] != 0) { /* all valid dd_s should have 0 in this byte */ + if (op->verbose) + pr2serr("%s: designation descriptors byte 2 should be 0\n" + "perhaps this is a standard inquiry response, ignore\n", + __func__); + return 0; + } + off = -1; + printed = false; + while ((u = sg_vpd_dev_id_iter(buff, len, &off, m_assoc, -1, -1)) == 0) { + bp = buff + off; + i_len = bp[3]; + if ((off + i_len + 4) > len) { + pr2serr(" VPD page error: designator length longer than\n" + " remaining response length=%d\n", (len - off)); + return SG_LIB_CAT_MALFORMED; + } + assoc = ((bp[1] >> 4) & 0x3); + if (print_if_found && (! printed)) { + printed = true; + if (strlen(print_if_found) > 0) { + snprintf(b, blen, " %s:", print_if_found); + if (sgj_out_hr) + sgj_js_str_out(jsp, b, strlen(b)); + else + printf("%s\n", b); + } + } + if (NULL == print_if_found) { + snprintf(b, blen, " %s%s:", sp, sg_get_desig_assoc_str(assoc)); + if (sgj_out_hr) + sgj_js_str_out(jsp, b, strlen(b)); + else + printf("%s\n", b); + } + sg_get_designation_descriptor_str(sp, bp, i_len + 4, false, + op->do_long, blen, b); + if (sgj_out_hr) + sgj_js_str_out(jsp, b, strlen(b)); + else + printf("%s", b); + } + if (-2 == u) { + pr2serr("VPD page error: short designator around offset %d\n", off); + return SG_LIB_CAT_MALFORMED; + } + return 0; +} + +/* VPD_BLOCK_LIMITS sbc */ +/* VPD_SA_DEV_CAP ssc */ +/* VPD_OSD_INFO 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: + /* now done by decode_block_limits_vpd() in sg_vpd_common.c */ + 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 */ +/* VPD_SECURITY_TOKEN osd */ +/* VPD_ES_DEV_CHARS ses-4 */ +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; + + pdt = buff[0] & PDT_MASK; + 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: + /* now done by decode_block_dev_ch_vpd() in sg_vpd_common.c */ + 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_LB_PROVISIONING sbc */ +/* VPD_TA_SUPPORTED ssc */ +static void +decode_b2_vpd(uint8_t * buff, int len, int pdt, struct opts_t * op) +{ + 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: + /* decode_block_lb_prov_vpd() is now in sg_vpd_common.c */ + break; + case PDT_TAPE: case PDT_MCHANGER: + /* decode_tapealert_supported_vpd() is now in sg_vpd_common.c */ + 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; + } +} + +/* VPD_SUP_BLOCK_LENS sbc ["sbl"] */ +/* VPD_DTDE_ADDRESS ssc */ +static void +decode_b4_vpd(uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jop) +{ + int pdt = buff[0] & PDT_MASK; + 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: + /* now done by decode_sup_block_lens_vpd() in sg_vpd_common.c */ + break; + case PDT_TAPE: case PDT_MCHANGER: + sgj_pr_hr(jsp, " Device transfer data element:\n"); + if (! jsp->pr_as_json) + hex2stdout(buff + 4, len - 4, 1); + sgj_js_nv_hex_bytes(jsp, jop, "device_transfer_data_element", + buff + 4, len - 4); + break; + default: + pr2serr(" Unable to decode pdt=0x%x, in hex:\n", pdt); + hex2stderr(buff, len, 0); + break; + } +} + +/* VPD_BLOCK_DEV_C_EXTENS sbc */ +/* VPD_LB_PROTECTION 0xb5 ["lbpro"] ssc */ +static void +decode_b5_vpd(uint8_t * b, int len, int do_hex, int pdt) +{ + if (do_hex) { + hex2stdout(b, len, (1 == do_hex) ? 0 : -1); + return; + } + switch (pdt) { + case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC: + /* now done by decode_block_dev_char_ext_vpd() in sg_vpd_common.c */ + break; + case PDT_TAPE: case PDT_MCHANGER: + /* now done by decode_lb_protection_vpd() in sg_vpd_common.c */ + break; + default: + pr2serr(" Unable to decode pdt=0x%x, in hex:\n", pdt); + hex2stderr(b, len, 0); + break; + } +} + +/* Returns 0 if successful */ +static int +svpd_unable_to_decode(int sg_fd, struct opts_t * op, int subvalue, int off) +{ + bool as_json, json_o_hr, hex0; + int res, len, n; + sgj_state * jsp = &op->json_st; + uint8_t * rp; + + as_json = jsp->pr_as_json; + json_o_hr = as_json && jsp->pr_out_hr; + hex0 = (0 == op->do_hex); + rp = rsp_buff + off; + if (hex0 && (! op->do_raw) && (! op->examine_given)) + sgj_pr_hr(jsp, "Only hex output supported\n"); + if ((!op->do_raw) && (op->do_hex < 2) && (! op->examine_given)) { + if (subvalue) { + if (hex0) + sgj_pr_hr(jsp, "VPD page code=0x%.2x, subvalue=0x%.2x:\n", + op->vpd_pn, subvalue); + else + printf("VPD page code=0x%.2x, subvalue=0x%.2x:\n", op->vpd_pn, + subvalue); + } else if (op->vpd_pn >= 0) { + if (hex0) + sgj_pr_hr(jsp, "VPD page code=0x%.2x:\n", op->vpd_pn); + else + printf("VPD page code=0x%.2x:\n", op->vpd_pn); + } else { + if (hex0) + sgj_pr_hr(jsp, "VPD page code=%d:\n", op->vpd_pn); + else + printf("VPD page code=%d:\n", op->vpd_pn); + } + } + + 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(rp, len); + else { + if (json_o_hr && hex0 && (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 ((! op->do_quiet) && (! op->examine_given)) { + if (op->vpd_pn >= 0) + pr2serr("fetching VPD page code=0x%.2x: failed\n", op->vpd_pn); + else + pr2serr("fetching VPD page code=%d: failed\n", op->vpd_pn); + } + return res; +} + +static int +recurse_vpd_decode(struct opts_t * op, sgj_opaque_p jop, int off) +{ + int res = svpd_decode_t10(-1, op, jop, 0, off, NULL); + + if (SG_LIB_CAT_OTHER == res) { + res = svpd_decode_vendor(-1, op, jop, off); + if (SG_LIB_CAT_OTHER == res) + svpd_unable_to_decode(-1, op, 0, off); + } + return res; +} + +/* Returns 0 if successful. If don't know how to decode, returns + * SG_LIB_CAT_OTHER else see sg_ll_inquiry(). */ +static int +svpd_decode_t10(int sg_fd, struct opts_t * op, sgj_opaque_p jop, + int subvalue, int off, const char * prefix) +{ + bool allow_name, allow_if_found, long_notquiet, qt; + bool vpd_supported = false; + bool inhex_active = (-1 == sg_fd); + bool exam_not_given = ! op->examine_given; + int len, pdt, pqual, num, k, resid, alloc_len, pn, vb; + int res = 0; + sgj_state * jsp = &op->json_st; + uint8_t * rp; + sgj_opaque_p jap = NULL; + sgj_opaque_p jo2p = NULL; + const char * np; + const char * ep; + const char * pre = (prefix ? prefix : ""); + const char * pdt_str; + bool as_json = jsp->pr_as_json; + bool not_json = ! as_json; + char obuff[DEF_ALLOC_LEN]; + char d[48]; + + vb = op->verbose; + qt = op->do_quiet; + long_notquiet = op->do_long && (! op->do_quiet); + if (op->do_raw || (op->do_quiet && (! op->do_long) && (! op->do_all)) || + (op->do_hex >= 3) || op->examine_given) + allow_name = false; + else + allow_name = true; + allow_if_found = op->examine_given && (! op->do_quiet); + rp = rsp_buff + off; + pn = op->vpd_pn; + if ((off > 0) && (VPD_NOPE_WANT_STD_INQ != op->vpd_pn)) + pn = rp[1]; + else + pn = op->vpd_pn; + if (!inhex_active && !op->do_force && exam_not_given && + pn != VPD_NOPE_WANT_STD_INQ && + pn != VPD_SUPPORTED_VPDS) { + res = vpd_fetch_page(sg_fd, rp, VPD_SUPPORTED_VPDS, op->maxlen, qt, + vb, &len); + if (res) + return res; + + num = rp[3]; + if (num > (len - 4)) + num = (len - 4); + if (vb > 1) { + pr2serr("Supported VPD pages, hex list: "); + hex2stderr(rp + 4, num, -1); + } + for (k = 0; k < num; ++k) { + if (pn == rp[4 + k]) { + vpd_supported = true; + break; + } + } + if (! vpd_supported) { /* get creative, was SG_LIB_CAT_ILLEGAL_REQ */ + if (vb) + pr2serr("Given VPD page not in supported list, use --force " + "to override this check\n"); + return sg_convert_errno(EDOM); + } + } + pdt = rp[0] & PDT_MASK; + pdt_str = sg_get_pdt_str(pdt, sizeof(d), d); + pqual = (rp[0] & 0xe0) >> 5; + + switch(pn) { + case VPD_NOPE_WANT_STD_INQ: /* -2 (want standard inquiry response) */ + if (!inhex_active) { + if (op->maxlen > 0) + alloc_len = op->maxlen; + else if (op->do_long) + alloc_len = DEF_ALLOC_LEN; + else + alloc_len = 36; + res = sg_ll_inquiry_v2(sg_fd, false, 0, rp, alloc_len, + DEF_PT_TIMEOUT, &resid, ! op->do_quiet, vb); + } else { + alloc_len = op->maxlen; + resid = 0; + res = 0; + } + if (0 == res) { + alloc_len -= resid; + if (op->do_raw) + dStrRaw(rp, alloc_len); + else if (op->do_hex) { + if (! op->do_quiet && (op->do_hex < 3)) + sgj_pr_hr(jsp, "Standard Inquiry data format:\n"); + hex2stdout(rp, alloc_len, (1 == op->do_hex) ? 0 : -1); + } else + std_inq_decode(rp, alloc_len, op, jop); + return 0; + } + break; + case VPD_SUPPORTED_VPDS: /* 0x0 ["sv"] */ + np = "Supported VPD pages VPD page"; + if (allow_name) + sgj_pr_hr(jsp, "%s%s:\n", pre, np); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (! allow_name && allow_if_found) + sgj_pr_hr(jsp, "%s%s:\n", pre, np); + if (op->do_raw) + dStrRaw(rp, len); + else if (op->do_hex) + hex2stdout(rp, len, no_ascii_4hex(op)); + else { + if (vb || long_notquiet) + sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + num = rp[3]; + if (num > (len - 4)) + num = (len - 4); + 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_4vpd(rp, len, op, jap); + } + return 0; + } + break; + case VPD_UNIT_SERIAL_NUM: /* 0x80 ["sn"] */ + np = "Unit serial number VPD page"; + if (allow_name && not_json) + sgj_pr_hr(jsp, "%s%s:\n", pre, np); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (! allow_name && allow_if_found) + sgj_pr_hr(jsp, "%s%s:\n", pre, np); + if (op->do_raw) + dStrRaw(rp, len); + else if (op->do_hex) + hex2stdout(rp, len, no_ascii_4hex(op)); + else { + if (vb || long_notquiet) + sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + memset(obuff, 0, sizeof(obuff)); + len -= 4; + if (len >= (int)sizeof(obuff)) + len = sizeof(obuff) - 1; + memcpy(obuff, rp + 4, len); + jo2p = sg_vpd_js_hdr(jsp, jop, np, rp); + sgj_haj_vs(jsp, jo2p, 2, np, SGJ_SEP_COLON_1_SPACE, obuff); + } + return 0; + } + break; + case VPD_DEVICE_ID: /* 0x83 ["di, di_asis, di_lu, di_port, di_target"] */ + np = "Device Identification VPD page"; + if (allow_name) + sgj_pr_hr(jsp, "%s%s:\n", pre, np); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (! allow_name && allow_if_found) + sgj_pr_hr(jsp, "%s%s:\n", pre, np); + if (op->do_raw) + dStrRaw(rp, len); + else if (op->do_hex) + hex2stdout(rp, len, no_ascii_4hex(op)); + else { + if (vb || long_notquiet) + sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + if (as_json) { + jo2p = sg_vpd_js_hdr(jsp, jop, np, rp); + jap = sgj_named_subarray_r(jsp, jo2p, + "designation_descriptor_list"); + } + device_id_vpd_variants(rp, len, subvalue, op, jap); + } + return 0; + } + break; + case VPD_SOFTW_INF_ID: /* 0x84 ["sii"] */ + np = "Software interface identification VPD page"; + if (allow_name) + sgj_pr_hr(jsp, "%s%s:\n", pre, np); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (! allow_name && allow_if_found) + sgj_pr_hr(jsp, "%s%s:\n", pre, np); + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + 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); + } + return 0; + } + break; + case VPD_MAN_NET_ADDR: /* 0x85 ["mna"] */ + np= "Management network addresses VPD page"; + if (allow_name) + sgj_pr_hr(jsp, "%s%s:\n", pre, np); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (! allow_name && allow_if_found) + sgj_pr_hr(jsp, "%s%s:\n", pre, np); + if (op->do_raw) + dStrRaw(rp, len); + else { + 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); + } + return 0; + } + break; + case VPD_EXT_INQ: /* 0x86 ["ei"] */ + np = "extended INQUIRY data VPD page"; + if (allow_name) + sgj_pr_hr(jsp, "%s%s:\n", pre, np); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (! allow_name && allow_if_found) + sgj_pr_hr(jsp, "%s%s:\n", pre, np); + if (op->do_raw) + dStrRaw(rp, len); + else { + bool protect = false; + + op->protect_not_sure = false; + if (op->std_inq_a_valid) + protect = !! (0x1 & op->std_inq_a[5]); + else 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 (vb || long_notquiet) + sgj_pr_hr(jsp," [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + if (as_json) + jo2p = sg_vpd_js_hdr(jsp, jop, np, rp); + decode_x_inq_vpd(rp, len, protect, op, jo2p); + } + return 0; + } + break; + case VPD_MODE_PG_POLICY: /* 0x87 */ + np = "Mode page policy VPD page"; + if (allow_name) + sgj_pr_hr(jsp, "%s%s:\n", pre, np); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (! allow_name && allow_if_found) + sgj_pr_hr(jsp, "%s%s:\n", (prefix ? prefix : ""), np); + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + 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); + } + return 0; + } + break; + case VPD_SCSI_PORTS: /* 0x88 ["sp"] */ + np = "SCSI Ports VPD page"; + if (allow_name) + sgj_pr_hr(jsp, "%s%s:\n", pre, np); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (! allow_name && allow_if_found) + sgj_pr_hr(jsp, "%s%s:\n", pre, np); + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + 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_4vpd(rp, len, op, jap); + } + return 0; + } + break; + case VPD_ATA_INFO: /* 0x89 ['ai"] */ + np = "ATA information VPD page"; + if (allow_name) + sgj_pr_hr(jsp, "%s%s:\n", pre, np); + alloc_len = op->maxlen ? op->maxlen : VPD_ATA_INFO_LEN; + res = vpd_fetch_page(sg_fd, rp, pn, alloc_len, qt, vb, &len); + if (0 == res) { + if (! allow_name && allow_if_found) + sgj_pr_hr(jsp, "%s%s:\n", (prefix ? prefix : ""), np); + if ((2 == op->do_raw) || (3 == op->do_hex)) { /* for hdparm */ + if (len < (60 + 512)) + pr2serr("ATA_INFO VPD page len (%d) less than expected " + "572\n", len); + else + dWordHex((const unsigned short *)(rp + 60), 256, -2, + sg_is_big_endian()); + } + else if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + if (as_json) + jo2p = sg_vpd_js_hdr(jsp, jop, np, rp); + decode_ata_info_vpd(rp, len, op, jo2p); + } + return 0; + } + break; + case VPD_POWER_CONDITION: /* 0x8a ["pc"] */ + np = "Power condition VPD page:"; + if (allow_name) + sgj_pr_hr(jsp, "%s%s\n", pre, np); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (! allow_name && allow_if_found) + sgj_pr_hr(jsp, "%s%s\n", pre, np); + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + if (as_json) + jo2p = sg_vpd_js_hdr(jsp, jop, np, rp); + decode_power_condition(rp, len, op, jo2p); + } + return 0; + } + break; + case VPD_DEVICE_CONSTITUENTS: /* 0x8b ["dc"] */ + np = "Device constituents VPD page"; + if (allow_name) + sgj_pr_hr(jsp, "%s%s:\n", pre, np); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (! allow_name && allow_if_found) + sgj_pr_hr(jsp, "%s%s:\n", pre, np); + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + 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); + } + return 0; + } + break; + case VPD_CFA_PROFILE_INFO: /* 0x8c ["cfa"] */ + np = "CFA profile information VPD page"; + if (allow_name) + sgj_pr_hr(jsp, "%s%s:\n", pre, np); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (! allow_name && allow_if_found) + sgj_pr_hr(jsp, "%s%s\n", pre, np); + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + 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); + } + return 0; + } + break; + case VPD_POWER_CONSUMPTION: /* 0x8d ["psm"] */ + np = "Power consumption VPD page"; + if (allow_name) + sgj_pr_hr(jsp, "%s%s:\n", pre, np); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (! allow_name && allow_if_found) + sgj_pr_hr(jsp, "%s%s\n", pre, np); + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + 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); + } + return 0; + } + break; + case VPD_3PARTY_COPY: /* 0x8f */ + np = "Third party copy VPD page"; /* ["tpc"] */ + if (allow_name) + sgj_pr_hr(jsp, "%s%s:\n", pre, np); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (! allow_name && allow_if_found) + sgj_pr_hr(jsp, "%s%s\n", pre, np); + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + if (as_json) { + jo2p = sg_vpd_js_hdr(jsp, jop, np, rp); + jap = sgj_named_subarray_r(jsp, jo2p, + "third_party_copy_descriptors"); + } + decode_3party_copy_vpd(rp, len, op, jap); + } + return 0; + } + break; + case VPD_PROTO_LU: /* 0x90 ["pslu"] */ + np = "Protocol-specific logical unit information VPD page"; + if (allow_name) + sgj_pr_hr(jsp, "%s%s:\n", pre, np); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (! allow_name && allow_if_found) + sgj_pr_hr(jsp, "%s%s:\n", pre, np); + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + 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); + } + return 0; + } + break; + case VPD_PROTO_PORT: /* 0x91 ["pspo"] */ + np = "Protocol-specific port VPD page"; + if (allow_name) + sgj_pr_hr(jsp, "%s%s\n", pre, np); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (! allow_name && allow_if_found) + sgj_pr_hr(jsp, "%s%s:\n", pre, np); + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + 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); + } + return 0; + } + break; + case VPD_SCSI_FEATURE_SETS: /* 0x92 ["sfs"] */ + np = "SCSI Feature sets VPD page"; + if (allow_name) + sgj_pr_hr(jsp, "%s%s:\n", pre, np); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (! allow_name && allow_if_found) + sgj_pr_hr(jsp, "%s%s\n", pre, np); + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + 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); + } + return 0; + } + break; + case 0xb0: /* depends on pdt */ + 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; + + 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 (NULL == np) + sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt); + else if (allow_name || allow_if_found) + sgj_pr_hr(jsp, "%s%s %s\n", pre, np, ep ? ep : ""); + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + 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) { + decode_b0_vpd(rp, len, op, jop); + } else if (oi) { + decode_b0_vpd(rp, len, op, jop); + } else { + + } + } + return 0; + } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3) && + exam_not_given) + sgj_pr_hr(jsp, "%sVPD page=0xb0\n", pre); + break; + case 0xb1: /* depends 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"; + + 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; + break; + } + if (NULL == np) + sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt); + else if (allow_name || allow_if_found) + sgj_pr_hr(jsp, "%s%s %s\n", pre, np, ep ? ep : ""); + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + 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); + } + return 0; + } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3) && + exam_not_given) + sgj_pr_hr(jsp, "%sVPD page=0xb1\n", pre); + break; + case 0xb2: /* VPD page depends on pdt */ + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + bool lbpv = false; + bool tas = false; + + 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 (NULL == np) + sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt); + else if (allow_name || allow_if_found) + sgj_pr_hr(jsp, "%s%s %s\n", pre, np, ep ? ep : ""); + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + if (as_json) + jo2p = sg_vpd_js_hdr(jsp, jop, np, rp); + if (lbpv) + decode_block_lb_prov_vpd(rp, len, op, jo2p); + else if (tas) + decode_tapealert_supported_vpd(rp, len, op, jo2p); + else + decode_b2_vpd(rp, len, pdt, op); + } + return 0; + } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3) && + exam_not_given) + sgj_pr_hr(jsp, "%sVPD page=0xb2\n", pre); + break; + case 0xb3: /* VPD page depends on pdt */ + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + bool ref = false; + + 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; + case PDT_TAPE: case PDT_MCHANGER: + np = "Automation device serial number VPD page"; + ep = "(SSC)"; + break; + default: + np = NULL; + break; + } + if (NULL == np) + sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt); + else if (allow_name || allow_if_found) + sgj_pr_hr(jsp, "%s%s %s\n", pre, np, ep ? ep : ""); + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + 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) && (! op->do_quiet) && (op->do_hex < 3) && + exam_not_given) + sgj_pr_hr(jsp, "%sVPD page=0xb3\n", pre); + break; + case 0xb4: /* VPD page depends on pdt */ + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + bool sbl = false; + + 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 = "Data transfer device element address"; + ep = "(SSC)"; + break; + default: + np = NULL; + break; + } + if (NULL == np) + sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt); + else if (allow_name || allow_if_found) + sgj_pr_hr(jsp, "%s%s %s\n", pre, np, ep ? ep : ""); + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + 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 + decode_b4_vpd(rp, len, op, jo2p); + } + return 0; + } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3) && + exam_not_given) + sgj_pr_hr(jsp, "%sVPD page=0xb4\n", pre); + break; + case 0xb5: /* VPD page depends on pdt */ + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + bool bdce = false; + bool lbp = false; + + pdt = rp[0] & PDT_MASK; + switch (pdt) { + case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC: + np = "Block device characteristics extension 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 (NULL == np) + sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt); + else if (allow_name || allow_if_found) + sgj_pr_hr(jsp, "%s%s %s\n", pre, np, ep ? ep : ""); + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + 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) { + 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 + decode_b5_vpd(rp, len, op->do_hex, pdt); + } + return 0; + } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3) && + exam_not_given) + sgj_pr_hr(jsp, "%sVPD page=0xb5\n", pre); + break; + case VPD_ZBC_DEV_CHARS: /* 0xb6 for both pdt=0 and pdt=0x14 */ + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + bool zbdch = false; + + 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 (NULL == np) + sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt); + else if (allow_name || allow_if_found) + sgj_pr_hr(jsp, "%s%s %s\n", pre, np, ep ? ep : ""); + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + if (as_json) + jo2p = sg_vpd_js_hdr(jsp, jop, np, rp); + if (zbdch) + decode_zbdch_vpd(rp, len, op, jo2p); + else + return SG_LIB_CAT_OTHER; + } + return 0; + } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3) && + exam_not_given) + sgj_pr_hr(jsp, "%sVPD page=0xb6\n", pre); + break; + case 0xb7: + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + bool ble = false; + + 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 (NULL == np) + sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt); + else if (allow_name || allow_if_found) + sgj_pr_hr(jsp, "%s%s %s:\n", pre, np, ep ? ep : ""); + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + 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 SG_LIB_CAT_OTHER; + } + return 0; + } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3) && + exam_not_given) + sgj_pr_hr(jsp, "%sVPD page=0xb7\n", pre); + break; + case 0xb8: /* VPD_FORMAT_PRESETS */ + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + bool fp = false; + + 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 (NULL == np) + sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt); + else if (allow_name || allow_if_found) + sgj_pr_hr(jsp, "%s%s %s:\n", pre, np, ep ? ep : ""); + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + 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 SG_LIB_CAT_OTHER; + } + return 0; + } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3) && + exam_not_given) + sgj_pr_hr(jsp, "%sVPD page=0xb8\n", pre); + break; + case 0xb9: /* VPD_CON_POS_RANGE */ + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + bool cpr = false; /* ["cpr"] */ + + pdt = rp[0] & PDT_MASK; + switch (pdt) { + case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC: + np = "Concurrent positioning ranges VPD page"; + ep = "(SBC)"; + cpr = true; + break; + default: + np = NULL; + break; + } + if (NULL == np) + sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt); + else if (allow_name || allow_if_found) + sgj_pr_hr(jsp, "%s%s %s:\n", pre, np, ep ? ep : ""); + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: " + "%s]\n", pqual, pdt_str); + 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 SG_LIB_CAT_OTHER; + } + return 0; + } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3) && + exam_not_given) + sgj_pr_hr(jsp, "%sVPD page=0xb8\n", pre); + break; + default: + return SG_LIB_CAT_OTHER; + } + return res; +} + +static int +svpd_decode_all(int sg_fd, struct opts_t * op, sgj_opaque_p jop) +{ + int k, res, rlen, n, pn; + int max_pn = 255; + int any_err = 0; + 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; + if (sg_fd >= 0) { /* have valid open file descriptor (handle) */ + res = vpd_fetch_page(sg_fd, rp, VPD_SUPPORTED_VPDS, op->maxlen, + op->do_quiet, op->verbose, &rlen); + if (res) { + if (! op->do_quiet) { + if (SG_LIB_CAT_ABORTED_COMMAND == res) + pr2serr("%s: VPD page 0, aborted command\n", __func__); + else if (res) { + char b[80]; + + sg_get_category_sense_str(res, sizeof(b), b, op->verbose); + pr2serr("%s: fetching VPD page 0 failed: %s\n", __func__, + b); + } + } + return res; + } + n = sg_get_unaligned_be16(rp + 2); + if (n > (rlen - 4)) { + if (op->verbose) + pr2serr("%s: rlen=%d > page0 size=%d\n", __func__, rlen, + n + 4); + n = (rlen - 4); + } + for (k = 0; k < n; ++k) { + pn = rp[4 + k]; + if (pn > max_pn) + continue; + op->vpd_pn = pn; + if (k > 0) + sgj_pr_hr(jsp, "\n"); + if (op->do_long) { + if (jsp->pr_as_json) + sgj_pr_hr(jsp, "[0x%x]:\n", pn); + else + printf("[0x%x] ", pn); + } + + res = svpd_decode_t10(sg_fd, op, jop, 0, 0, NULL); + if (SG_LIB_CAT_OTHER == res) { + res = svpd_decode_vendor(sg_fd, op, jop, 0); + if (SG_LIB_CAT_OTHER == res) + res = svpd_unable_to_decode(sg_fd, op, 0, 0); + } + if (! op->do_quiet) { + if (SG_LIB_CAT_ABORTED_COMMAND == res) + pr2serr("fetching VPD page failed, aborted command\n"); + else if (res) { + char b[80]; + + sg_get_category_sense_str(res, sizeof(b), b, op->verbose); + pr2serr("fetching VPD page failed: %s\n", b); + } + } + if (res) + any_err = res; + } + res = any_err; + } else { /* input is coming from --inhex=FN */ + int bump, off; + int in_len = op->maxlen; + int prev_pn = -1; + + res = 0; + if (op->page_given && (VPD_NOPE_WANT_STD_INQ == op->vpd_pn)) + return svpd_decode_t10(-1, op, jop, 0, 0, NULL); + + 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 + printf("[0x%x] ", pn); + } + + res = svpd_decode_t10(-1, op, jop, 0, off, NULL); + if (SG_LIB_CAT_OTHER == res) { + res = svpd_decode_vendor(-1, op, jop, off); + if (SG_LIB_CAT_OTHER == res) + res = svpd_unable_to_decode(-1, op, 0, off); + } + } + } + return res; +} + +static int +svpd_examine_all(int sg_fd, struct opts_t * op, sgj_opaque_p jop) +{ + bool first = true; + bool got_one = false; + int k, res, start; + int max_pn; + int any_err = 0; + sgj_state * jsp = &op->json_st; + char b[80]; + + max_pn = (op->page_given ? op->vpd_pn : 0xff); + switch (op->examine) { + case 1: + start = 0x80; + break; + case 2: + start = 0x0; + break; + default: + start = 0xc0; + break; + } + if (start > max_pn) { /* swap them around */ + k = start; + start = max_pn; + max_pn = k; + } + for (k = start; k <= max_pn; ++k) { + op->vpd_pn = k; + if (first) + first = false; + else if (got_one) { + sgj_pr_hr(jsp, "\n"); + got_one = false; + } + if (op->do_long) + snprintf(b, sizeof(b), "[0x%x] ", k); + else + b[0] = '\0'; + res = svpd_decode_t10(sg_fd, op, jop, 0, 0, b); + if (SG_LIB_CAT_OTHER == res) { + res = svpd_decode_vendor(sg_fd, op, jop, 0); + if (SG_LIB_CAT_OTHER == res) + res = svpd_unable_to_decode(sg_fd, op, 0, 0); + } + if (! op->do_quiet) { + if (SG_LIB_CAT_ABORTED_COMMAND == res) + pr2serr("fetching VPD page failed, aborted command\n"); + else if (res && (SG_LIB_CAT_ILLEGAL_REQ != res)) { + /* SG_LIB_CAT_ILLEGAL_REQ expected as well examine all */ + sg_get_category_sense_str(res, sizeof(b), b, op->verbose); + pr2serr("fetching VPD page failed: %s\n", b); + } + } + if (res && (SG_LIB_CAT_ILLEGAL_REQ != res)) + any_err = res; + if (0 == res) + got_one = true; + } + return any_err; +} + + +int +main(int argc, char * argv[]) +{ + bool as_json; + int c, res, matches; + int sg_fd = -1; + int inhex_len = 0; + int inraw_len = 0; + int ret = 0; + int subvalue = 0; + const char * cp; + sgj_state * jsp; + sgj_opaque_p jop = NULL; + const struct svpd_values_name_t * vnp; + struct opts_t opts SG_C_CPP_ZERO_INIT; + struct opts_t * op = &opts; + + op->invoker = SG_VPD_INV_SG_VPD; + dup_sanity_chk((int)sizeof(opts), (int)sizeof(*vnp)); + op->vend_prod_num = -1; + while (1) { + int option_index = 0; + + c = getopt_long(argc, argv, "aeEfhHiI:j::lm:M:p:qQ:rvV", long_options, + &option_index); + if (c == -1) + break; + + switch (c) { + case 'a': + op->do_all = true; + break; + case 'e': + op->do_enum = true; + break; + case 'E': + ++op->examine; + op->examine_given = true; + break; + case 'f': + op->do_force = true; + break; + case 'h': + case '?': + usage(); + return 0; + case 'H': + ++op->do_hex; + break; + case 'i': + ++op->do_ident; + break; + case 'I': + if (op->inhex_fn) { + pr2serr("only one '--inhex=' option permitted\n"); + usage(); + return SG_LIB_SYNTAX_ERROR; + } else + op->inhex_fn = optarg; + 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 'l': + op->do_long = true; + break; + case 'm': + op->maxlen = sg_get_num(optarg); + if ((op->maxlen < 0) || (op->maxlen > MX_ALLOC_LEN)) { + pr2serr("argument to '--maxlen' should be %d or less\n", + MX_ALLOC_LEN); + return SG_LIB_SYNTAX_ERROR; + } + if ((op->maxlen > 0) && (op->maxlen < MIN_MAXLEN)) { + pr2serr("Warning: overriding '--maxlen' < %d, using " + "default\n", MIN_MAXLEN); + op->maxlen = 0; + } + 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 'p': + if (op->page_str) { + pr2serr("only one '--page=' option permitted\n"); + usage(); + return SG_LIB_SYNTAX_ERROR; + } else + op->page_str = optarg; + op->page_given = true; + break; + case 'q': + op->do_quiet = true; + break; + case 'Q': + op->sinq_inraw_fn = optarg; + break; + case 'r': + ++op->do_raw; + break; + case 'v': + op->verbose_given = true; + ++op->verbose; + break; + case 'V': + op->version_given = true; + break; + default: + pr2serr("unrecognised option code 0x%x ??\n", c); + usage(); + 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(); + return SG_LIB_SYNTAX_ERROR; + } + } + +#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: %s\n", version_str); + return 0; + } + + jsp = &op->json_st; + if (op->do_enum) { + if (op->device_name) + pr2serr("Device name %s ignored when --enumerate given\n", + op->device_name); + if (op->vend_prod) { + if (isdigit((uint8_t)op->vend_prod[0])) { + op->vend_prod_num = sg_get_num_nomult(op->vend_prod); + if ((op->vend_prod_num < 0) || (op->vend_prod_num > 10)) { + pr2serr("Bad vendor/product number after '--vendor=' " + "option\n"); + return SG_LIB_SYNTAX_ERROR; + } + } else { + op->vend_prod_num = svpd_find_vp_num_by_acron(op->vend_prod); + if (op->vend_prod_num < 0) { + pr2serr("Bad vendor/product acronym after '--vendor=' " + "option\n"); + return SG_LIB_SYNTAX_ERROR; + } + } + svpd_enumerate_vendor(op->vend_prod_num); + return 0; + } + if (op->page_str) { + if ((0 == strcmp("-1", op->page_str)) || + (0 == strcmp("-2", op->page_str))) + op->vpd_pn = VPD_NOPE_WANT_STD_INQ; + else if (isdigit((uint8_t)op->page_str[0])) { + 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"); + return SG_LIB_SYNTAX_ERROR; + } + } else { + pr2serr("with --enumerate only search using VPD page " + "numbers\n"); + return SG_LIB_SYNTAX_ERROR; + } + matches = count_standard_vpds(op->vpd_pn); + if (0 == matches) + matches = svpd_count_vendor_vpds(op->vpd_pn, + op->vend_prod_num); + if (0 == matches) + sgj_pr_hr(jsp, "No matches found for VPD page number 0x%x\n", + op->vpd_pn); + } else { /* enumerate standard then vendor VPD pages */ + sgj_pr_hr(jsp, "Standard VPD pages:\n"); + enumerate_vpds(1, 1); + } + return 0; + } + + as_json = jsp->pr_as_json; + if (as_json) + jop = sgj_start_r(MY_NAME, version_str, argc, argv, jsp); + + if (op->page_str) { + 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) { + vnp = svpd_find_vendor_by_acron(op->page_str); + if (NULL == vnp) { + if (0 == strcmp("stdinq", op->page_str)) { + vnp = sdp_find_vpd_by_acron("sinq"); + } else { + pr2serr("abbreviation doesn't match a VPD page\n"); + sgj_pr_hr(jsp, "Available standard VPD pages:\n"); + enumerate_vpds(1, 1); + ret = SG_LIB_SYNTAX_ERROR; + goto fini; + } + } + } + op->vpd_pn = vnp->value; + subvalue = vnp->subvalue; + op->vend_prod_num = subvalue; + } 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 fini; + } + 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"); + sgj_pr_hr(jsp, "Available standard VPD pages:\n"); + enumerate_vpds(1, 1); + ret = SG_LIB_SYNTAX_ERROR; + goto fini; + } + 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 fini; + } + 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 fini; + } + 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); + } 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 fini; + } + subvalue = op->vend_prod_num; + } + + 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); + ret = sg_convert_errno(ENOMEM); + goto fini; + } + if (op->sinq_inraw_fn) { + 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_SYNTAX_ERROR; + goto err_out; + } + if ((ret = sg_f2hex_arr(op->inhex_fn, !!op->do_raw, false, rsp_buff, + &inhex_len, rsp_buff_sz))) { + goto err_out; + } + if (op->verbose > 2) + pr2serr("Read %d [0x%x] bytes of user supplied data\n", inhex_len, + inhex_len); + if (op->verbose > 3) + hex2stderr(rsp_buff, inhex_len, 0); + op->do_raw = 0; /* don't want raw on output with --inhex= */ + if ((NULL == op->page_str) && (! op->do_all)) { + /* may be able to deduce VPD page */ + 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) { + if (op->verbose) + pr2serr("Guessing from --inhex this is VPD page 0x%x\n", + rsp_buff[1]); + op->vpd_pn = rsp_buff[1]; + } else { + if (op->vpd_pn > 0x80) { + op->vpd_pn = rsp_buff[1]; + if (op->verbose) + pr2serr("Guessing from --inhex this is VPD page " + "0x%x\n", rsp_buff[1]); + } else { + op->vpd_pn = VPD_NOPE_WANT_STD_INQ; + if (op->verbose) + pr2serr("page number unclear from --inhex, hope " + "it's a standard INQUIRY response\n"); + } + } + } + } else if ((NULL == op->device_name) && (! op->std_inq_a_valid)) { + pr2serr("No DEVICE argument given\n\n"); + usage(); + ret = SG_LIB_SYNTAX_ERROR; + goto err_out; + } + + if (op->do_raw && op->do_hex) { + pr2serr("Can't do hex and raw at the same time\n"); + usage(); + ret = SG_LIB_SYNTAX_ERROR; + goto err_out; + } + if (op->do_ident) { + op->vpd_pn = VPD_DEVICE_ID; + if (op->do_ident > 1) { + if (! op->do_long) + op->do_quiet = true; + subvalue = VPD_DI_SEL_LU; + } + } + 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 ((0 == op->maxlen) || (inhex_len < op->maxlen)) + op->maxlen = inhex_len; + if (op->do_all || op->page_given) + res = svpd_decode_all(-1, op, jop); + else { + res = svpd_decode_t10(-1, op, jop, subvalue, 0, NULL); + if (SG_LIB_CAT_OTHER == res) { + res = svpd_decode_vendor(-1, op, jop, 0); + if (SG_LIB_CAT_OTHER == res) + res = svpd_unable_to_decode(-1, op, subvalue, 0); + } + } + ret = res; + goto err_out; + } else if (op->std_inq_a_valid && (NULL == op->device_name)) { + /* nothing else to do ... */ + /* --sinq_inraw=RFN contents still in rsp_buff */ + if (op->do_raw) + dStrRaw(rsp_buff, inraw_len); + else if (op->do_hex) { + if (! op->do_quiet && (op->do_hex < 3)) + sgj_pr_hr(jsp, "Standard Inquiry data format:\n"); + hex2stdout(rsp_buff, inraw_len, (1 == op->do_hex) ? 0 : -1); + } else + std_inq_decode(rsp_buff, inraw_len, op, jop); + ret = 0; + goto fini; + } + + if ((sg_fd = sg_cmds_open_device(op->device_name, true /* ro */, + op->verbose)) < 0) { + if (op->verbose > 0) + pr2serr("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; + } + + if (op->examine_given) { + ret = svpd_examine_all(sg_fd, op, jop); + } else if (op->do_all) + ret = svpd_decode_all(sg_fd, op, jop); + else { + memset(rsp_buff, 0, rsp_buff_sz); + + res = svpd_decode_t10(sg_fd, op, jop, subvalue, 0, NULL); + if (SG_LIB_CAT_OTHER == res) { + res = svpd_decode_vendor(sg_fd, op, jop, 0); + if (SG_LIB_CAT_OTHER == res) + res = svpd_unable_to_decode(sg_fd, op, subvalue, 0); + } + if (! op->do_quiet) { + if (SG_LIB_CAT_ABORTED_COMMAND == res) + pr2serr("fetching VPD page failed, aborted command\n"); + else if (res) { + char b[80]; + + sg_get_category_sense_str(res, sizeof(b), b, op->verbose); + pr2serr("fetching VPD page failed: %s\n", b); + } + } + ret = res; + } +err_out: + if (free_rsp_buff) + free(free_rsp_buff); + if ((0 == op->verbose) && (! op->do_quiet)) { + if (! sg_if_can2stderr("sg_vpd failed: ", ret)) + pr2serr("Some error occurred, try again with '-v' or '-vv' for " + "more information\n"); + } +fini: + 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; +} |