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