/*
 * Copyright (c) 2006-2023 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_pt.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.96 20230622";  /* spc6r08 + sbc5r04 */

#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(struct sg_pt_base * ptvp, struct opts_t * op,
                           sgj_opaque_p jop, int subvalue, int off,
                           const char * prefix);

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 const struct option long_options[] = {
    {"all", no_argument, 0, 'a'},
    {"debug", no_argument, 0, 'D'},
    {"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, '^'},    /* short option is '-j' */
    {"js-file", required_argument, 0, 'J'},
    {"js_file", required_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 const 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_CAP_PROD_ID, 0, 0, "cap", "Capacity/Product identification mapping"},
    {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, "zbdc", "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] [--json[=JO]] "
            "[--js-file=JFN]\n"
            "               [--long] [--maxlen=LEN] [--page=PG] [--quiet] "
            "[--raw]\n"
            "               [--sinq_inraw=RFN] [--vendor=VP] [--verbose] "
            "[--version]\n"
            "               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 plain "
            "text\n"
            "                           Use --json=? for JSON help\n"
            "    --js-file=JFN|-J JFN    JFN is a filename to which JSON "
            "output is\n"
            "                            written (def: stdout); truncates "
            "then writes\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 decoding and error output\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 %ss:\n", vpd_pg_s);
            ++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 = sg_scnpr(c, clen, "EncServ=%d  ", !!(b[6] & 0x40));
    if (b[6] & 0x10)
        n += sg_scn3pr(c, clen, n, "MultiP=1 (VS=%d)  ", !!(b[6] & 0x20));
    else
        n += sg_scn3pr(c, clen, n, "MultiP=0  ");
    sg_scn3pr(c, 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("%s %s=%d\n", di_vpdp,  lts_s, 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);

    if (op->do_hex > 0) {
        if (op->do_hex > 2)
            named_hhh_output(svp_vpdp, buff, len, op);
        else
            hex2stdout(buff, len, no_ascii_4hex(op));
        return;
    }
    pdt = PDT_MASK & buff[0];
    rlen = buff[3] + 4;
    if (rlen > len)
        pr2serr("%s truncated, indicates %d, got %d\n", svp_vpdp, rlen,
                len);
    else
        len = rlen;
    if (len < 4) {
        pr2serr("%s %s=%d\n", svp_vpdp,  lts_s, 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, dhex;
    sgj_state * jsp = &op->json_st;
    sgj_opaque_p jo2p = NULL;
    sgj_opaque_p ja2p = NULL;
    uint8_t * bp;

    dhex = op->do_hex;
    if (dhex > 0) {
        if (dhex > 2)
            named_hhh_output(sp_vpdp, buff, len, op);
        else
            hex2stdout(buff, len, no_ascii_4hex(op));
        return;
    } else if (dhex < 0)
        dhex = -dhex;

    if (len < 4) {
        pr2serr("%s %s=%d\n", sp_vpdp,  lts_s, 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("%s, short descriptor length=%d, left=%d\n", sp_vpdp,
                    bump, (len - k));
            return;
        }
        if (ip_tid_len > 0) {
            if (dhex > 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("%s, short descriptor(tgt) length=%d, left=%d\n", sp_vpdp,
                    bump, (len - k));
            return;
        }
        if (tpd_len > 0) {
            if (dhex > 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("    %s error: designator length longer than\n"
                        "     remaining response length=%d\n", vpd_pg_s,
                        (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("%s error: short designator around offset %d\n", vpd_pg_s,
                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("    %s error: designator length longer than\n"
                    "     remaining response length=%d\n", vpd_pg_s,
                    (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_hr_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_hr_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_hr_str_out(jsp, b, strlen(b));
        else
            printf("%s", b);
    }
    if (-2 == u) {
        pr2serr("%s error: short designator around offset %d\n", vpd_pg_s,
                 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];
    int dhex = op->do_hex;
    const char * vpd_pp = NULL;
    sgj_state * jsp = &op->json_st;

    if (dhex < 0)
        dhex = -dhex;
    if (dhex < 3) {
        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:
        if (dhex > 2) {
            vpd_pp = sad_vpdp;
            break;
        }
        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:
        if (dhex > 2) {
            vpd_pp = osdi_vpdp;
            break;
        }
        /* fall-through */
    default:
        pr2serr("  Unable to decode pdt=0x%x, in hex:\n", pdt);
        hex2stderr(buff, len, no_ascii_4hex(op));
        break;
    }
    if (vpd_pp)
        named_hhh_output(vpd_pp, buff, len, op);
}

/* 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 > 0) {
        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 > 0) {
        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 > 0) {
        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 > 0) {
        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 pdt, struct opts_t * op)
{
    if (op->do_hex > 0) {
        hex2stdout(b, 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_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(struct sg_pt_base * ptvp, struct opts_t * op,
                      sgj_opaque_p jop, int subvalue, int off)
{
    bool as_json, json_o_hr, hex0;
    int res, len, n, pg_c, dhex;
    uint8_t * rp;
    sgj_state * jsp = &op->json_st;
    sgj_opaque_p jo2p = NULL;
    char b[128];
    static const int blen = sizeof(b);

    as_json = jsp->pr_as_json;
    json_o_hr = as_json && jsp->pr_out_hr;
    dhex = op->do_hex;
    if (dhex < 0)
        dhex = -dhex;
    hex0 = (0 == dhex);
    b[0] = '\0';
    pg_c = op->vpd_pn;
    rp = rsp_buff + off;
    if (hex0 && (! op->do_raw) && (! op->examine_given))
        sgj_pr_hr(jsp, "Only hex output supported\n");
    if (subvalue)
        snprintf(b, blen, "%s code=0x%.2x, subvalue=0x%.2x", vpd_pg_s,
                 pg_c, subvalue);
    else if (op->vpd_pn >= 0)
        snprintf(b, blen, "%s code=0x%.2x", vpd_pg_s, pg_c);
    else
        snprintf(b, blen, "%s code=%d", vpd_pg_s, pg_c);
    if ((!op->do_raw) && (dhex < 2) && (! op->examine_given))
        sgj_pr_hr(jsp, "%s:\n", b);

    res = vpd_fetch_page(ptvp, rp, pg_c, op->maxlen, op->do_quiet,
                         op->verbose, &len);
    if (0 == res) {
        int rlen;
        char * p;

        if (op->do_raw) {
            dStrRaw(rp, len);
            return 0;
        } else if (dhex > 0) {
            if (dhex > 2)
                named_hhh_output(b, rp, len, op);
            else
                hex2stdout(rp, len, no_ascii_4hex(op));
            return 0;
        }
        if (len < 4) {
            pr2serr("%s: response too short (< 4 bytes)\n", __func__);
            return SG_LIB_CAT_MALFORMED;
        }
        rlen = sg_get_unaligned_be16(rp + 2) + 4;
        if (len > rlen)
            len = rlen;

        n = len * 4;
        p = (char *)malloc(n);
        if (p) {
            hex2str(rp, len, NULL, 1, n - 1, p);
            if (json_o_hr)
                sgj_hr_str_out(jsp, p, strlen(p));
            else
                sgj_pr_hr(jsp, "%s\n", p);
            free(p);
        }
        if (as_json) {
            const char * ccp;

            snprintf(b, blen, "vpd_page_%02x", pg_c);
            jo2p = sg_vpd_js_hdr(jsp, jop, b, rp);
            snprintf(b, blen, "%d bytes long when 4 byte header included",
                     rlen);
            sgj_js_nv_ihexstr(jsp, jo2p, "page_length", rlen - 4, NULL, b);
            if (pg_c <= 0x80)
                ccp = "unimplemented";
            else if (pg_c <= 0x82)
                ccp = "obsolete";
            else if (pg_c <= 0x8f)
                ccp = "unimplemented";
            else if (pg_c <= 0xbf)
                ccp = "restricted";
            else
                ccp = "vendor_specific";
            sgj_js_nv_s(jsp, jo2p, "vpd_category", ccp);
            sgjv_js_hex_long(jsp, jo2p, rp, len);
        }
    } else if ((! op->do_quiet) && (! op->examine_given)) {
        if (pg_c >= 0)
            pr2serr("fetching %s code=0x%.2x: failed\n", vpd_pg_s, pg_c);
        else
            pr2serr("fetching %s code=%d: failed\n", vpd_pg_s, pg_c);
    }
    return res;
}

static int
recurse_vpd_decode(struct opts_t * op, sgj_opaque_p jop, int off)
{
    int res = svpd_decode_t10(NULL, op, jop, 0, off, NULL);

    if (SG_LIB_CAT_OTHER == res) {
        res = svpd_decode_vendor(NULL, op, jop, off);
        if (SG_LIB_CAT_OTHER == res)
            svpd_unable_to_decode(NULL, op, jop, 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(struct sg_pt_base * ptvp, 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 = (NULL == ptvp);
    bool exam_not_given = ! op->examine_given;
    int len, pdt, pqual, num, k, resid, alloc_len, pn, dhex, 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;
    dhex = op->do_hex;
    if (dhex < 0)
        dhex = -dhex;
    long_notquiet = op->do_long && (! op->do_quiet);
    if (op->do_raw || (op->do_quiet && (! op->do_long) && (! op->do_all)) ||
        (dhex >= 3) || op->examine_given)
        allow_name = false;
    else
        allow_name = true;
    allow_if_found = op->examine_given && (! op->do_quiet);
    rp = rsp_buff + off;
    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(ptvp, 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 %ss, hex list: ", vpd_pg_s);
            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 %s not in supported list, use --force to "
                        "override this check\n", vpd_pg_s);
            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) */
        np = "Standard Inquiry data format";
        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_pt(ptvp, 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 (dhex > 0) {
                if (! op->do_quiet && (dhex < 3))
                    sgj_pr_hr(jsp, "%s:\n", np);
                if (dhex > 2)
                    named_hhh_output(np, rp, alloc_len, op);
                else
                    hex2stdout(rp, alloc_len, (1 == dhex) ? 0 : -1);
            } else
                std_inq_decode(rp, alloc_len, op, jop);
            return 0;
        }
        break;
    case VPD_SUPPORTED_VPDS:    /* 0x0 ["sv"] */
        np = svp_vpdp;
        if (allow_name)
            sgj_pr_hr(jsp, "%s%s:\n", pre, np);
        res = vpd_fetch_page(ptvp, 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 (dhex) {
                if (dhex > 2)
                    named_hhh_output(np, rp, len, op);
                else
                    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,
                                               "supported_vpd_page_list");
                }
                decode_supported_vpd_4vpd(rp, len, op, jap);
            }
            return 0;
        }
        break;
    case VPD_UNIT_SERIAL_NUM:   /* 0x80 ["sn"] */
        np = usn_vpdp;
        if (allow_name && not_json)
            sgj_pr_hr(jsp, "%s%s:\n", pre, np);
        res = vpd_fetch_page(ptvp, 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 (dhex > 0) {
                if (dhex > 2)
                    named_hhh_output(np, rp, len, op);
                else
                    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, "Product serial number",
                           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(ptvp, 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 (dhex > 0) {
                if (dhex > 2)
                    named_hhh_output(np, rp, len, op);
                else
                    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(ptvp, 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 (dhex > 0) {
                if (dhex > 2)
                    named_hhh_output(np, rp, len, op);
                else
                    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,
                                      "software_interface_identifier_list");
                }
                decode_softw_inf_id(rp, len, op, jap);
            }
            return 0;
        }
        break;
    case VPD_MAN_NET_ADDR:      /* 0x85 ["mna"] */
        np= mna_vpdp;
        if (allow_name)
            sgj_pr_hr(jsp, "%s%s:\n", pre, np);
        res = vpd_fetch_page(ptvp, 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 (dhex > 0) {
                if (dhex > 2)
                    named_hhh_output(np, rp, len, op);
                else
                    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,
                                      "network_services_descriptor_list");
                }
                decode_man_net_vpd(rp, len, op, jap);
            }
            return 0;
        }
        break;
    case VPD_EXT_INQ:           /* 0x86 ["ei"] */
        np = eid_vpdp;
        if (allow_name)
            sgj_pr_hr(jsp, "%s%s:\n", pre, np);
        res = vpd_fetch_page(ptvp, 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 (dhex > 0) {
                if (dhex > 2)
                    named_hhh_output(np, rp, len, op);
                else
                    hex2stdout(rp, len, no_ascii_4hex(op));
            } else {
                bool protect = false;

                op->protect_not_sure = false;
                if (op->std_inq_a_valid)
                     protect = !! (0x1 & op->std_inq_a[5]);
                else if (ptvp && (! op->do_force)) {
                    struct sg_simple_inquiry_resp sir;

                    res = sg_simple_inquiry(get_pt_file_handle(ptvp), &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 = mpp_vpdp;
        if (allow_name)
            sgj_pr_hr(jsp, "%s%s:\n", pre, np);
        res = vpd_fetch_page(ptvp, 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 = sp_vpdp;
        if (allow_name)
            sgj_pr_hr(jsp, "%s%s:\n", pre, np);
        res = vpd_fetch_page(ptvp, 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 = ai_vpdp;
        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(ptvp, 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 == dhex)) {  /* for hdparm */
                if (len < (60 + 512))
                    pr2serr("%s len (%d) less than expected 572\n", np, 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 = pc_vpdp;
        if (allow_name)
            sgj_pr_hr(jsp, "%s%s\n", pre, np);
        res = vpd_fetch_page(ptvp, 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 = dc_vpdp;
        if (allow_name)
            sgj_pr_hr(jsp, "%s%s:\n", pre, np);
        res = vpd_fetch_page(ptvp, 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 = cpi_vpdp;
        if (allow_name)
            sgj_pr_hr(jsp, "%s%s:\n", pre, np);
        res = vpd_fetch_page(ptvp, 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 = psm_vpdp;
        if (allow_name)
            sgj_pr_hr(jsp, "%s%s:\n", pre, np);
        res = vpd_fetch_page(ptvp, 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 = tpc_vpdp;       /* ["tpc"] */
        if (allow_name)
            sgj_pr_hr(jsp, "%s%s:\n", pre, np);
        res = vpd_fetch_page(ptvp, 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 = pslu_vpdp;
        if (allow_name)
            sgj_pr_hr(jsp, "%s%s:\n", pre, np);
        res = vpd_fetch_page(ptvp, 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 = pspo_vpdp;
        if (allow_name)
            sgj_pr_hr(jsp, "%s%s\n", pre, np);
        res = vpd_fetch_page(ptvp, 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 = sfs_vpdp;
        if (allow_name)
            sgj_pr_hr(jsp, "%s%s:\n", pre, np);
        res = vpd_fetch_page(ptvp, 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(ptvp, 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 = bl_vpdp;
                ep = "(SBC)";
                bl = true;
                break;
            case PDT_TAPE: case PDT_MCHANGER:
                np = sad_vpdp;
                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, jo2p);
                } else if (oi) {
                    decode_b0_vpd(rp, len, op, jo2p);
                } else {

                }
            }
            return 0;
        } else if ((! op->do_raw) && (! op->do_quiet) && (dhex < 3) &&
                   exam_not_given)
            sgj_pr_hr(jsp, "%sVPD page=0xb0\n", pre);
        break;
    case 0xb1:  /* depends on pdt */
        res = vpd_fetch_page(ptvp, rp, pn, op->maxlen, qt, vb, &len);
        if (0 == res) {
            bool bdc = false;

            switch (pdt) {
            case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
                np = bdc_vpdp;
                ep = "(SBC)";
                bdc = true;
                break;
            case PDT_TAPE: case PDT_MCHANGER:
                np = masn_vpdp;
                ep = "(SSC)";
                break;
            case PDT_OSD:
                np = st_vpdp;
                ep = "(OSD)";
                break;
            case PDT_ADC:
                np = masn_vpdp;
                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) && (dhex < 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(ptvp, 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 = lbpv_vpdp;
                ep = "(SBC)";
                lbpv = true;
                break;
            case PDT_TAPE: case PDT_MCHANGER:
                np = lbpro_vpdp;
                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) && (dhex < 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(ptvp, 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 = ref_vpdp;
                ep = "(SBC)";
                ref = true;
                break;
            case PDT_TAPE: case PDT_MCHANGER:
                np = adsn_vpdp;
                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) && (dhex < 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(ptvp, 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 = sbl_vpdp;
                ep = "(SBC)";
                sbl = true;
                break;
            case PDT_TAPE: case PDT_MCHANGER:
                np = dtde_vpdp;
                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) && (dhex < 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(ptvp, 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 = bdce_vpdp;
                ep = "(SBC)";
                bdce = true;
                break;
            case PDT_TAPE: case PDT_MCHANGER:
                np = lbpro_vpdp;
                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, pdt, op);
            }
            return 0;
        } else if ((! op->do_raw) && (! op->do_quiet) && (dhex < 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(ptvp, 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 = zbdc_vpdp;
                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) && (dhex < 3) &&
                   exam_not_given)
            sgj_pr_hr(jsp, "%sVPD page=0xb6\n", pre);
        break;
    case 0xb7:
        res = vpd_fetch_page(ptvp, 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 = ble_vpdp;
                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) && (dhex < 3) &&
                   exam_not_given)
            sgj_pr_hr(jsp, "%sVPD page=0xb7\n", pre);
        break;
    case 0xb8:          /* VPD_FORMAT_PRESETS */
        res = vpd_fetch_page(ptvp, 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 = fp_vpdp;
                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) && (dhex < 3) &&
                   exam_not_given)
            sgj_pr_hr(jsp, "%sVPD page=0xb8\n", pre);
        break;
    case 0xb9:          /* VPD_CON_POS_RANGE */
        res = vpd_fetch_page(ptvp, 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 = cpr_vpdp;
                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) && (dhex < 3) &&
                   exam_not_given)
            sgj_pr_hr(jsp, "%sVPD page=0xb9\n", pre);
        break;
    case 0xba:          /* VPD_CAP_PROD_ID */
        res = vpd_fetch_page(ptvp, rp, pn, op->maxlen, qt, vb, &len);
        if (0 == res) {
            bool cap = false;           /* ["cap"] */

            pdt = rp[0] & PDT_MASK;
            switch (pdt) {
            case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
                np = cap_vpdp;
                ep = "(SBC)";
                cap = 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,
                                "capacity_product_identification_"
                                "descriptors_list");
                }
                if (cap)
                    decode_cap_prod_id_vpd(rp, len, op, jap);
                else
                    return SG_LIB_CAT_OTHER;
            }
            return 0;
        } else if ((! op->do_raw) && (! op->do_quiet) && (dhex < 3) &&
                   exam_not_given)
            sgj_pr_hr(jsp, "%sVPD page=0xba\n", pre);
        break;
    default:
        return SG_LIB_CAT_OTHER;
    }
    return res;
}

static int
svpd_decode_all(struct sg_pt_base * ptvp, 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 (ptvp) {   /* have valid open file descriptor (handle) */
        res = vpd_fetch_page(ptvp, 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(ptvp, op, jop, 0, 0, NULL);
            if (SG_LIB_CAT_OTHER == res) {
                res = svpd_decode_vendor(ptvp, op, jop, 0);
                if (SG_LIB_CAT_OTHER == res)
                    res = svpd_unable_to_decode(ptvp, op, jop, 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(NULL, op, jop, 0, 0, NULL);

        for (off = 0; off < in_len; 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 = svpd_decode_t10(NULL, op, jop, 0, off, NULL);
            if (SG_LIB_CAT_OTHER == res) {
                res = svpd_decode_vendor(NULL, op, jop, off);
                if (SG_LIB_CAT_OTHER == res)
                    res = svpd_unable_to_decode(NULL, op, jop, 0, off);
            }
        }
    }
    return res;
}

static int
svpd_examine_all(struct sg_pt_base * ptvp, 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(ptvp, op, jop, 0, 0, b);
        if (SG_LIB_CAT_OTHER == res) {
            res = svpd_decode_vendor(ptvp, op, jop, 0);
            if (SG_LIB_CAT_OTHER == res)
                res = svpd_unable_to_decode(ptvp, op, jop, 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;
}

/* Handles short options after '-j' including a sequence of short options
 * that include one 'j' (for JSON). Want optional argument to '-j' to be
 * prefixed by '='. Return 0 for good, SG_LIB_SYNTAX_ERROR for syntax error
 * and SG_LIB_OK_FALSE for exit with no error. */
static int
chk_short_opts(const char sopt_ch, struct opts_t * op)
{
    /* only need to process short, non-argument options */
    switch (sopt_ch) {
    case 'a':
        op->do_all = true;
        break;
    case 'D':
        op->do_debug = 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 SG_LIB_OK_FALSE;
    case 'H':
        ++op->do_hex;
        break;
    case 'i':
        ++op->do_ident;
        break;
    case 'j':
        break;  /* simply ignore second 'j' (e.g. '-jxj') */
    case 'l':
        op->do_long = true;
        break;
   case 'q':
        op->do_quiet = true;
        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", sopt_ch);
        if (op->do_help)
            break;
        usage();
        return SG_LIB_SYNTAX_ERROR;
    }
    return 0;
}


int
main(int argc, char * argv[])
{
    bool as_json = false;
    int c, k, n, q, res, matches, vb;
    int sg_fd = -1;
    int inhex_len = 0;
    int inraw_len = 0;
    int ret = 0;
    int subvalue = 0;
    const char * cp;
    struct sg_pt_base * ptvp = NULL;
    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;

    if (getenv("SG3_UTILS_INVOCATION"))
        sg_rep_invocation(MY_NAME, version_str, argc, argv, stderr);
    op->vend_prod_num = -1;
    while (1) {
        int option_index = 0;

        c = getopt_long(argc, argv, "^aDeEfhHiI:j::J:lm:M:p:qQ:rvV",
                        long_options, &option_index);
        if (c == -1)
            break;

        switch (c) {
        case 'a':
            op->do_all = true;
            break;
        case 'D':
            op->do_debug = 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':       /* for: -j[=JO] */
        case '^':       /* for: --json[=JO] */
            op->do_json = true;
            /* Now want '=' to precede all JSON optional arguments */
            if (optarg) {
                if ('^' == c) {
                    op->json_arg = optarg;
                    break;
                } else if ('=' == *optarg) {
                    op->json_arg = optarg + 1;
                    break;
                }
                n = strlen(optarg);
                for (k = 0; k < n; ++k) {
                    q = chk_short_opts(*(optarg + k), op);
                    if (SG_LIB_SYNTAX_ERROR == q)
                        return SG_LIB_SYNTAX_ERROR;
                    if (SG_LIB_OK_FALSE == q)
                        return 0;
                }
            } else
                op->json_arg = NULL;
            break;
        case 'J':
            op->do_json = true;
            op->js_file = optarg;
            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;
    }
    vb = op->verbose;

    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)
                printf("No matches found for VPD page number 0x%x\n",
                       op->vpd_pn);
        } else {        /* enumerate standard then vendor VPD pages */
            printf("Standard VPD pages:\n");
            enumerate_vpds(1, 1);
        }
        return 0;
    }

    if (op->do_debug) {
        if (vb > 0)
            pr2serr("debug option set: changes meaning of some --hex "
                    "options\n");
        if (op->do_hex > 0)
            op->do_hex = -op->do_hex;
    }
    jsp = &op->json_st;
    if (op->do_json) {
        if (! sgj_init_state(jsp, op->json_arg)) {
            int bad_char = jsp->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);
            ret = SG_LIB_SYNTAX_ERROR;
            goto err_out;
        }
        jop = sgj_start_r(MY_NAME, version_str, argc, argv, jsp);
    }
    as_json = jsp->pr_as_json;

    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 (vb > 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) {
        /* Note: want to support both --sinq_inraw= and --inhex= options */
        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 (vb > 2)
            pr2serr("Read %d [0x%x] bytes of user supplied data\n", inhex_len,
                    inhex_len);
        if (vb > 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 (vb)
                    pr2serr("Guessing from --inhex= this is a standard "
                            "INQUIRY\n");
            } else if (rsp_buff[2] <= 2) {
                if (vb)
                    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 (vb)
                        pr2serr("Guessing from --inhex this is VPD page "
                                "0x%x\n", rsp_buff[1]);
                } else {
                    op->vpd_pn = VPD_NOPE_WANT_STD_INQ;
                    if (vb)
                        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");
        pr2serr("Use '-h' or '--help' option for usage summary\n");
        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(NULL, op, jop);
        else {
            res = svpd_decode_t10(NULL, op, jop, subvalue, 0, NULL);
            if (SG_LIB_CAT_OTHER == res) {
                res = svpd_decode_vendor(NULL, op, jop, 0);
                if (SG_LIB_CAT_OTHER == res)
                    res = svpd_unable_to_decode(NULL, op, jop, 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 */, vb)) <
         0) {
        if (vb > 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;
    }
    ptvp = construct_scsi_pt_obj_with_fd(sg_fd, vb);
    if (NULL == ptvp) {
        pr2serr("memory problem from construct_scsi_pt_obj_with_fd()\n");
        ret = sg_convert_errno(ENOMEM);
        goto err_out;
    }
    if (op->examine_given) {
        ret = svpd_examine_all(ptvp, op, jop);
    } else if (op->do_all)
        ret = svpd_decode_all(ptvp, op, jop);
    else {
        memset(rsp_buff, 0, rsp_buff_sz);

        res = svpd_decode_t10(ptvp, op, jop, subvalue, 0, NULL);
        if (SG_LIB_CAT_OTHER == res) {
            res = svpd_decode_vendor(ptvp, op, jop, 0);
            if (SG_LIB_CAT_OTHER == res)
                res = svpd_unable_to_decode(ptvp, op, jop, 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, vb);
                pr2serr("fetching VPD page failed: %s\n", b);
            }
        }
        ret = res;
    }
err_out:
    if (free_rsp_buff)
        free(free_rsp_buff);
    if ((0 == vb) && (! 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:
    if (ptvp)
        destruct_scsi_pt_obj(ptvp);
    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 && jop) {
        FILE * fp = stdout;

        if (op->js_file) {
            if ((1 != strlen(op->js_file)) || ('-' != op->js_file[0])) {
                fp = fopen(op->js_file, "w");   /* truncate if exists */
                if (NULL == fp) {
                    int e = errno;

                    pr2serr("unable to open file: %s [%s]\n", op->js_file,
                            safe_strerror(e));
                    ret = sg_convert_errno(e);
                }
            }
            /* '--js-file=-' will send JSON output to stdout */
        }
        if (fp)
            sgj_js2file(jsp, NULL, ret, fp);
        if (op->js_file && fp && (stdout != fp))
            fclose(fp);
        sgj_finish(jsp);
    }
    return ret;
}
