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