blob: 1385b400f5df020d8b813135f30fe8fc434c47fd [file] [log] [blame]
/*
* Copyright (c) 2006-2007 Douglas Gilbert.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*/
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <getopt.h>
#define __STDC_FORMAT_MACROS 1
#include <inttypes.h>
#include "sg_lib.h"
#include "sg_cmds_basic.h"
/* This utility program was originally written for the Linux SCSI subsystem.
This program fetches Vital Product Data (VPD) pages from the given
device and outputs it as directed. VPD pages are obtained via a
SCSI INQUIRY command. Most of the data in this program is obtained
from the SCSI SPC-4 document at http://www.t10.org .
*/
static char * version_str = "0.19 20070419"; /* spc-4 rev 9 */
extern void svpd_enumerate_vendor(void);
extern int svpd_decode_vendor(int sg_fd, int num_vpd, int subvalue,
int do_hex, int do_raw, int do_long,
int do_quiet, int verbose);
extern const struct svpd_values_name_t *
svpd_find_vendor_by_acron(const char * ap);
/* standard VPD pages */
#define VPD_SUPPORTED_VPDS 0x0
#define VPD_UNIT_SERIAL_NUM 0x80
#define VPD_IMP_OP_DEF 0x81 /* obsolete in SPC-2 */
#define VPD_ASCII_OP_DEF 0x82 /* obsolete in SPC-2 */
#define VPD_DEVICE_ID 0x83
#define VPD_SOFTW_INF_ID 0x84
#define VPD_MAN_NET_ADDR 0x85
#define VPD_EXT_INQ 0x86
#define VPD_MODE_PG_POLICY 0x87
#define VPD_SCSI_PORTS 0x88
#define VPD_ATA_INFO 0x89
#define VPD_BLOCK_LIMITS 0xb0 /* SBC-3 */
#define VPD_SA_DEV_CAP 0xb0 /* SSC-3 */
#define VPD_OSD_INFO 0xb0 /* OSD */
#define VPD_MAN_ASS_SN 0xb1 /* SSC-3 */
#define VPD_SECURITY_TOKEN 0xb1 /* OSD */
#define VPD_TA_SUPPORTED 0xb2 /* SSC-3 */
/* Device identification VPD page associations */
#define VPD_ASSOC_LU 0
#define VPD_ASSOC_TPORT 1
#define VPD_ASSOC_TDEVICE 2
/* values for selection one or more associations (2**vpd_assoc),
except _AS_IS */
#define VPD_DI_SEL_LU 1
#define VPD_DI_SEL_TPORT 2
#define VPD_DI_SEL_TARGET 4
#define VPD_DI_SEL_AS_IS 32
#define DEF_ALLOC_LEN 252
#define MX_ALLOC_LEN (0xc000 + 0x80)
#define VPD_ATA_INFO_LEN 572
/* This structure is a duplicate of one of the same name in sg_vpd_vendor.c .
Take care that both have the same fields (and types). */
struct svpd_values_name_t {
int value;
int subvalue;
int pdt; /* peripheral device type id, -1 is the default */
/* (all or not applicable) value */
int ro_vendor; /* read-only or vendor flag */
const char * acron;
const char * name;
};
static unsigned char rsp_buff[MX_ALLOC_LEN + 2];
static int decode_dev_ids(const char * print_if_found, unsigned char * buff,
int len, int m_assoc, int m_desig_type,
int m_code_set, int long_out, int quiet);
static void decode_transport_id(const char * leadin, unsigned char * ucp,
int len);
static struct option long_options[] = {
{"enumerate", 0, 0, 'e'},
{"help", 0, 0, 'h'},
{"hex", 0, 0, 'H'},
{"ident", 0, 0, 'i'},
{"long", 0, 0, 'l'},
{"page", 1, 0, 'p'},
{"quiet", 0, 0, 'q'},
{"raw", 0, 0, 'r'},
{"verbose", 0, 0, 'v'},
{"version", 0, 0, 'V'},
{0, 0, 0, 0},
};
static struct svpd_values_name_t standard_vpd_pg[] = {
{VPD_ATA_INFO, 0, -1, 0, "ai", "ATA information (SAT)"},
{VPD_ASCII_OP_DEF, 0, -1, 0, "aod",
"ASCII implemented operating definition (obs)"},
{VPD_BLOCK_LIMITS, 0, 0, 0, "bl", "Block limits (SBC)"},
{VPD_DEVICE_ID, 0, -1, 0, "di", "Device identification"},
{VPD_DEVICE_ID, VPD_DI_SEL_AS_IS, -1, 0, "di_asis", "Like 'di' "
"but designators ordered as found"},
{VPD_DEVICE_ID, VPD_DI_SEL_LU, -1, 0, "di_lu", "Device identification, "
"lu only"},
{VPD_DEVICE_ID, VPD_DI_SEL_TPORT, -1, 0, "di_port", "Device "
"identification, target port only"},
{VPD_DEVICE_ID, VPD_DI_SEL_TARGET, -1, 0, "di_target", "Device "
"identification, target device only"},
{VPD_EXT_INQ, 0, -1, 0, "ei", "Extended inquiry data"},
{VPD_IMP_OP_DEF, 0, -1, 0, "iod",
"Implemented operating definition (obs)"},
{VPD_MAN_ASS_SN, 0, 1, 0, "mas",
"Manufacturer assigned serial number (SSC)"},
{VPD_MAN_NET_ADDR, 0, -1, 0, "mna", "Management network addresses"},
{VPD_MODE_PG_POLICY, 0, -1, 0, "mpp", "Mode page policy"},
{VPD_OSD_INFO, 0, 0x11, 0, "oi", "OSD information"},
{VPD_SA_DEV_CAP, 0, 1, 0, "sad",
"Sequential access device capabilities (SSC)"},
{VPD_SOFTW_INF_ID, 0, -1, 0, "sii", "Software interface identification"},
{VPD_UNIT_SERIAL_NUM, 0, -1, 0, "sn", "Unit serial number"},
{VPD_SCSI_PORTS, 0, -1, 0, "sp", "SCSI ports"},
{VPD_SECURITY_TOKEN, 0, 0x11, 0, "st", "Security token (OSD)"},
{VPD_SUPPORTED_VPDS, 0, -1, 0, "sv", "Supported VPD pages"},
{VPD_TA_SUPPORTED, 0, 1, 0, "tas", "TapeAlert supported flags (SSC)"},
{0, 0, 0, 0, NULL, NULL},
};
static void usage()
{
fprintf(stderr,
"Usage: sg_vpd [--enumerate] [--help] [--hex] [--ident] "
"[--long] [--page=PG]\n"
" [--quiet] [--raw] [--verbose] [--version] "
"DEVICE\n");
fprintf(stderr,
" where:\n"
" --enumerate|-e enumerate known VPD pages names then "
"exit\n"
" --help|-h output this usage message then exit\n"
" --hex|-H output page in ASCII hexadecimal\n"
" --ident|-i output device identification VPD page, "
"twice for\n"
" short logical unit designator (equiv: "
"'-qp di_lu')\n"
" --long|-l perform extra decoding\n"
" --page=PG|-p PG fetch VPD page where PG is an "
"acronym, or a decimal\n"
" number unless hex indicator "
"is given (e.g. '0x83')\n"
" --quiet|-q suppress some output when decoding\n"
" --raw|-r output page in binary\n"
" --verbose|-v increase verbosity\n"
" --version|-V print version string and exit\n\n"
"Fetch Vital Product Data (VPD) page using SCSI INQUIRY\n");
}
static const struct svpd_values_name_t *
sdp_get_vpd_detail(int page_num, int subvalue, int pdt)
{
const struct svpd_values_name_t * vnp;
int sv, ty;
sv = (subvalue < 0) ? 1 : 0;
ty = (pdt < 0) ? 1 : 0;
for (vnp = standard_vpd_pg; vnp->acron; ++vnp) {
if ((page_num == vnp->value) &&
(sv || (subvalue == vnp->subvalue)) &&
(ty || (pdt == vnp->pdt)))
return vnp;
}
if (! ty)
return sdp_get_vpd_detail(page_num, subvalue, -1);
if (! sv)
return sdp_get_vpd_detail(page_num, -1, -1);
return NULL;
}
static const struct svpd_values_name_t *
sdp_find_vpd_by_acron(const char * ap)
{
const struct svpd_values_name_t * vnp;
for (vnp = standard_vpd_pg; vnp->acron; ++vnp) {
if (0 == strcmp(vnp->acron, ap))
return vnp;
}
return NULL;
}
static void enumerate_vpds(int standard, int vendor)
{
const struct svpd_values_name_t * vnp;
if (standard) {
for (vnp = standard_vpd_pg; vnp->acron; ++vnp) {
if (vnp->name && (0 == vnp->ro_vendor))
printf(" %-10s 0x%02x %s\n", vnp->acron, vnp->value,
vnp->name);
}
}
if (vendor)
svpd_enumerate_vendor();
}
/* Trying to decode multipliers as sg_get_num() [as sg_libs does] would
* only confuse things here, so use this local trimmed version */
static int get_num(const char * buf)
{
int res, len, num;
unsigned int unum;
const char * commap;
if ((NULL == buf) || ('\0' == buf[0]))
return -1;
len = strlen(buf);
commap = strchr(buf + 1, ',');
if (('0' == buf[0]) && (('x' == buf[1]) || ('X' == buf[1]))) {
res = sscanf(buf + 2, "%x", &unum);
num = unum;
} else if (commap && ('H' == toupper(*(commap - 1)))) {
res = sscanf(buf, "%x", &unum);
num = unum;
} else if ((NULL == commap) && ('H' == toupper(buf[len - 1]))) {
res = sscanf(buf, "%x", &unum);
num = unum;
} else
res = sscanf(buf, "%d", &num);
if (1 == res)
return num;
else
return -1;
}
static void dStrRaw(const char* str, int len)
{
int k;
for (k = 0 ; k < len; ++k)
printf("%c", str[k]);
}
static const char * assoc_arr[] =
{
"Addressed logical unit",
"Target port", /* that received request; unless SCSI ports VPD */
"Target device that contains addressed lu",
"Reserved [0x3]",
};
static void decode_id_vpd(unsigned char * buff, int len, int subvalue,
int do_long, int do_quiet)
{
int m_a, m_d, m_cs;
if (len < 4) {
fprintf(stderr, "Device identification VPD page length too "
"short=%d\n", len);
return;
}
m_a = -1;
m_d = -1;
m_cs = -1;
if (0 == subvalue) {
decode_dev_ids(assoc_arr[VPD_ASSOC_LU], buff + 4, len - 4,
VPD_ASSOC_LU, m_d, m_cs, do_long, do_quiet);
decode_dev_ids(assoc_arr[VPD_ASSOC_TPORT], buff + 4, len - 4,
VPD_ASSOC_TPORT, m_d, m_cs, do_long, do_quiet);
decode_dev_ids(assoc_arr[VPD_ASSOC_TDEVICE], buff + 4, len - 4,
VPD_ASSOC_TDEVICE, m_d, m_cs, do_long, do_quiet);
} else if (VPD_DI_SEL_AS_IS == subvalue)
decode_dev_ids(NULL, buff + 4, len - 4, m_a, m_d, m_cs, do_long,
do_quiet);
else {
if (VPD_DI_SEL_LU & subvalue)
decode_dev_ids(assoc_arr[VPD_ASSOC_LU], buff + 4, len - 4,
VPD_ASSOC_LU, m_d, m_cs, do_long, do_quiet);
if (VPD_DI_SEL_TPORT & subvalue)
decode_dev_ids(assoc_arr[VPD_ASSOC_TPORT], buff + 4, len - 4,
VPD_ASSOC_TPORT, m_d, m_cs, do_long, do_quiet);
if (VPD_DI_SEL_TARGET & subvalue)
decode_dev_ids(assoc_arr[VPD_ASSOC_TDEVICE], buff + 4, len - 4,
VPD_ASSOC_TDEVICE, m_d, m_cs, do_long, do_quiet);
}
}
static const char * network_service_type_arr[] =
{
"unspecified",
"storage configuration service",
"diagnostics",
"status",
"logging",
"code download",
"reserved[0x6]", "reserved[0x7]", "reserved[0x8]", "reserved[0x9]",
"reserved[0xa]", "reserved[0xb]", "reserved[0xc]", "reserved[0xd]",
"reserved[0xe]", "reserved[0xf]", "reserved[0x10]", "reserved[0x11]",
"reserved[0x12]", "reserved[0x13]", "reserved[0x14]", "reserved[0x15]",
"reserved[0x16]", "reserved[0x17]", "reserved[0x18]", "reserved[0x19]",
"reserved[0x1a]", "reserved[0x1b]", "reserved[0x1c]", "reserved[0x1d]",
"reserved[0x1e]", "reserved[0x1f]",
};
static void decode_net_man_vpd(unsigned char * buff, int len, int do_hex)
{
int k, bump, na_len;
unsigned char * ucp;
if (1 == do_hex) {
dStrHex((const char *)buff, len, 1);
return;
}
if (len < 4) {
fprintf(stderr, "Management network addresses VPD page length too "
"short=%d\n", len);
return;
}
len -= 4;
ucp = buff + 4;
for (k = 0; k < len; k += bump, ucp += bump) {
printf(" %s, Service type: %s\n",
assoc_arr[(ucp[0] >> 5) & 0x3],
network_service_type_arr[ucp[0] & 0x1f]);
na_len = (ucp[2] << 8) + ucp[3];
bump = 4 + na_len;
if ((k + bump) > len) {
fprintf(stderr, "Management network addresses VPD page, short "
"descriptor length=%d, left=%d\n", bump, (len - k));
return;
}
if (na_len > 0) {
if (do_hex > 1) {
printf(" Network address:\n");
dStrHex((const char *)(ucp + 4), na_len, 0);
} else
printf(" %s\n", ucp + 4);
}
}
}
static const char * mode_page_policy_arr[] =
{
"shared",
"per target port",
"per initiator port",
"per I_T nexus",
};
static void decode_mode_policy_vpd(unsigned char * buff, int len, int do_hex)
{
int k, bump;
unsigned char * ucp;
if (1 == do_hex) {
dStrHex((const char *)buff, len, 1);
return;
}
if (len < 4) {
fprintf(stderr, "Mode page policy VPD page length too short=%d\n",
len);
return;
}
len -= 4;
ucp = buff + 4;
for (k = 0; k < len; k += bump, ucp += bump) {
bump = 4;
if ((k + bump) > len) {
fprintf(stderr, "Mode page policy VPD page, short "
"descriptor length=%d, left=%d\n", bump, (len - k));
return;
}
if (do_hex > 1)
dStrHex((const char *)ucp, 4, 1);
else {
printf(" Policy page code: 0x%x", (ucp[0] & 0x3f));
if (ucp[1])
printf(", subpage code: 0x%x\n", ucp[1]);
else
printf("\n");
printf(" MLUS=%d, Policy: %s\n", !!(ucp[2] & 0x80),
mode_page_policy_arr[ucp[2] & 0x3]);
}
}
}
static void decode_scsi_ports_vpd(unsigned char * buff, int len, int do_hex,
int do_long, int do_quiet)
{
int k, bump, rel_port, ip_tid_len, tpd_len;
unsigned char * ucp;
if (1 == do_hex) {
dStrHex((const char *)buff, len, 1);
return;
}
if (len < 4) {
fprintf(stderr, "SCSI Ports VPD page length too short=%d\n", len);
return;
}
len -= 4;
ucp = buff + 4;
for (k = 0; k < len; k += bump, ucp += bump) {
rel_port = (ucp[2] << 8) + ucp[3];
printf("Relative port=%d\n", rel_port);
ip_tid_len = (ucp[6] << 8) + ucp[7];
bump = 8 + ip_tid_len;
if ((k + bump) > len) {
fprintf(stderr, "SCSI Ports VPD page, short descriptor "
"length=%d, left=%d\n", bump, (len - k));
return;
}
if (ip_tid_len > 0) {
if (do_hex > 1) {
printf(" Initiator port transport id:\n");
dStrHex((const char *)(ucp + 8), ip_tid_len, 1);
} else
decode_transport_id(" ", ucp + 8, ip_tid_len);
}
tpd_len = (ucp[bump + 2] << 8) + ucp[bump + 3];
if ((k + bump + tpd_len + 4) > len) {
fprintf(stderr, "SCSI Ports VPD page, short descriptor(tgt) "
"length=%d, left=%d\n", bump, (len - k));
return;
}
if (tpd_len > 0) {
if (do_hex > 1) {
printf(" Target port descriptor(s):\n");
dStrHex((const char *)(ucp + bump + 4), tpd_len, 1);
} else {
if ((0 == do_quiet) || (ip_tid_len > 0))
printf(" Target port descriptor(s):\n");
decode_dev_ids("SCSI Ports", ucp + bump + 4, tpd_len,
VPD_ASSOC_TPORT, -1, -1, do_long, do_quiet);
}
}
bump += tpd_len + 4;
}
}
static const char * transport_proto_arr[] =
{
"Fibre Channel (FCP-2)",
"Parallel SCSI (SPI-4)",
"SSA (SSA-S3P)",
"IEEE 1394 (SBP-3)",
"Remote Direct Memory Access (RDMA)",
"Internet SCSI (iSCSI)",
"Serial Attached SCSI (SAS)",
"Automation/Drive Interface (ADT)",
"ATA Packet Interface (ATA/ATAPI-7)",
"Ox9", "Oxa", "Oxb", "Oxc", "Oxd", "Oxe",
"No specific protocol"
};
static const char * code_set_arr[] =
{
"Reserved [0x0]",
"Binary",
"ASCII",
"UTF-8",
"Reserved [0x4]", "Reserved [0x5]", "Reserved [0x6]", "Reserved [0x7]",
"Reserved [0x8]", "Reserved [0x9]", "Reserved [0xa]", "Reserved [0xb]",
"Reserved [0xc]", "Reserved [0xd]", "Reserved [0xe]", "Reserved [0xf]",
};
static const char * desig_type_arr[] =
{
"vendor specific [0x0]",
"T10 vendor identification",
"EUI-64 based",
"NAA",
"Relative target port",
"Target port group", /* spc4r09: _primary_ target port group */
"Logical unit group",
"MD5 logical unit identifier",
"SCSI name string",
"Reserved [0x9]", "Reserved [0xa]", "Reserved [0xb]",
"Reserved [0xc]", "Reserved [0xd]", "Reserved [0xe]", "Reserved [0xf]",
};
/* Prints outs an abridged set of device identification designators
selected by association, designator type and/or code set. */
static int decode_dev_ids_quiet(unsigned char * buff, int len,
int m_assoc, int m_desig_type,
int m_code_set)
{
int m, p_id, c_set, piv, desig_type, i_len, naa, off, u;
int assoc, is_sas, rtp;
const unsigned char * ucp;
const unsigned char * ip;
unsigned char sas_tport_addr[8];
rtp = 0;
memset(sas_tport_addr, 0, sizeof(sas_tport_addr));
off = -1;
while ((u = sg_vpd_dev_id_iter(buff, len, &off, m_assoc, m_desig_type,
m_code_set)) == 0) {
ucp = buff + off;
i_len = ucp[3];
if ((off + i_len + 4) > len) {
fprintf(stderr, " VPD page error: designator length longer "
"than\n remaining response length=%d\n", (len - off));
return SG_LIB_CAT_MALFORMED;
}
ip = ucp + 4;
p_id = ((ucp[0] >> 4) & 0xf);
c_set = (ucp[0] & 0xf);
piv = ((ucp[1] & 0x80) ? 1 : 0);
is_sas = (piv && (6 == p_id)) ? 1 : 0;
assoc = ((ucp[1] >> 4) & 0x3);
desig_type = (ucp[1] & 0xf);
switch (desig_type) {
case 0: /* vendor specific */
break;
case 1: /* T10 vendor identification */
break;
case 2: /* EUI-64 based */
if ((8 != i_len) && (12 != i_len) && (16 != i_len))
fprintf(stderr, " << expect 8, 12 and 16 byte "
"EUI, got %d>>\n", i_len);
printf("0x");
for (m = 0; m < i_len; ++m)
printf("%02x", (unsigned int)ip[m]);
printf("\n");
break;
case 3: /* NAA */
if (1 != c_set) {
fprintf(stderr, " << unexpected code set %d for "
"NAA>>\n", c_set);
dStrHex((const char *)ip, i_len, 0);
break;
}
naa = (ip[0] >> 4) & 0xff;
if (! ((2 == naa) || (5 == naa) || (6 == naa))) {
fprintf(stderr, " << unexpected NAA [0x%x]>>\n", naa);
dStrHex((const char *)ip, i_len, 0);
break;
}
if (2 == naa) {
if (8 != i_len) {
fprintf(stderr, " << unexpected NAA 2 identifier "
"length: 0x%x>>\n", i_len);
dStrHex((const char *)ip, i_len, 0);
break;
}
printf("0x");
for (m = 0; m < 8; ++m)
printf("%02x", (unsigned int)ip[m]);
printf("\n");
} else if (5 == naa) {
if (8 != i_len) {
fprintf(stderr, " << unexpected NAA 5 identifier "
"length: 0x%x>>\n", i_len);
dStrHex((const char *)ip, i_len, 0);
break;
}
if ((0 == is_sas) || (1 != assoc)) {
printf("0x");
for (m = 0; m < 8; ++m)
printf("%02x", (unsigned int)ip[m]);
printf("\n");
} else if (rtp) {
printf("0x");
for (m = 0; m < 8; ++m)
printf("%02x", (unsigned int)ip[m]);
printf(",0x%x\n", rtp);
rtp = 0;
} else {
if (sas_tport_addr[0]) {
printf("0x");
for (m = 0; m < 8; ++m)
printf("%02x", (unsigned int)sas_tport_addr[m]);
printf("\n");
}
memcpy(sas_tport_addr, ip, sizeof(sas_tport_addr));
}
} else if (6 == naa) {
if (16 != i_len) {
fprintf(stderr, " << unexpected NAA 6 identifier "
"length: 0x%x>>\n", i_len);
dStrHex((const char *)ip, i_len, 0);
break;
}
printf("0x");
for (m = 0; m < 16; ++m)
printf("%02x", (unsigned int)ip[m]);
printf("\n");
}
break;
case 4: /* Relative target port */
if ((0 == is_sas) || (1 != c_set) || (1 != assoc) || (4 != i_len))
break;
rtp = ((ip[2] << 8) | ip[3]);
if (sas_tport_addr[0]) {
printf("0x");
for (m = 0; m < 8; ++m)
printf("%02x", (unsigned int)sas_tport_addr[m]);
printf(",0x%x\n", rtp);
memset(sas_tport_addr, 0, sizeof(sas_tport_addr));
rtp = 0;
}
break;
case 5: /* (primary) Target port group */
break;
case 6: /* Logical unit group */
break;
case 7: /* MD5 logical unit identifier */
break;
case 8: /* SCSI name string */
if (3 != c_set) {
fprintf(stderr, " << expected UTF-8 code_set>>\n");
dStrHex((const char *)ip, i_len, 0);
break;
}
/* does %s print out UTF-8 ok??
* Seems to depend on the locale. Looks ok here with my
* locale setting: en_AU.UTF-8
*/
printf("%s\n", (const char *)ip);
break;
default: /* reserved */
break;
}
}
if (sas_tport_addr[0]) {
printf("0x");
for (m = 0; m < 8; ++m)
printf("%02x", (unsigned int)sas_tport_addr[m]);
printf("\n");
}
if (-2 == u) {
fprintf(stderr, "VPD page error: short designator around "
"offset %d\n", off);
return SG_LIB_CAT_MALFORMED;
}
return 0;
}
/* Prints outs device identification designators selected by association,
designator type and/or code set. */
static int decode_dev_ids(const char * print_if_found, unsigned char * buff,
int len, int m_assoc, int m_desig_type,
int m_code_set, int long_out, int quiet)
{
int m, p_id, c_set, piv, assoc, desig_type, i_len;
int ci_off, c_id, d_id, naa, vsi, printed, off, u;
unsigned long long vsei;
unsigned long long id_ext;
const unsigned char * ucp;
const unsigned char * ip;
if (quiet)
return decode_dev_ids_quiet(buff, len, m_assoc, m_desig_type,
m_code_set);
off = -1;
printed = 0;
while ((u = sg_vpd_dev_id_iter(buff, len, &off, m_assoc, m_desig_type,
m_code_set)) == 0) {
ucp = buff + off;
i_len = ucp[3];
if ((off + i_len + 4) > len) {
fprintf(stderr, " VPD page error: designator length longer "
"than\n remaining response length=%d\n", (len - off));
return SG_LIB_CAT_MALFORMED;
}
ip = ucp + 4;
p_id = ((ucp[0] >> 4) & 0xf);
c_set = (ucp[0] & 0xf);
piv = ((ucp[1] & 0x80) ? 1 : 0);
assoc = ((ucp[1] >> 4) & 0x3);
desig_type = (ucp[1] & 0xf);
if (print_if_found && (0 == printed)) {
printed = 1;
printf(" %s:\n", print_if_found);
}
if (NULL == print_if_found)
printf(" %s:\n", assoc_arr[assoc]);
printf(" designator type: %s, code_set: %s\n",
desig_type_arr[desig_type], code_set_arr[c_set]);
if (piv && ((1 == assoc) || (2 == assoc)))
printf(" transport: %s\n", transport_proto_arr[p_id]);
/* printf(" associated with the %s\n", assoc_arr[assoc]); */
switch (desig_type) {
case 0: /* vendor specific */
dStrHex((const char *)ip, i_len, 0);
break;
case 1: /* T10 vendor identification */
printf(" vendor id: %.8s\n", ip);
if (i_len > 8)
printf(" vendor specific: %.*s\n", i_len - 8, ip + 8);
break;
case 2: /* EUI-64 based */
if (! long_out) {
if ((8 != i_len) && (12 != i_len) && (16 != i_len)) {
fprintf(stderr, " << expect 8, 12 and 16 byte "
"ids, got %d>>\n", i_len);
dStrHex((const char *)ip, i_len, 0);
break;
}
printf(" 0x");
for (m = 0; m < i_len; ++m)
printf("%02x", (unsigned int)ip[m]);
printf("\n");
break;
}
printf(" EUI-64 based %d byte identifier\n", i_len);
if (1 != c_set) {
fprintf(stderr, " << expected binary code_set (1)>>\n");
dStrHex((const char *)ip, i_len, 0);
break;
}
ci_off = 0;
if (16 == i_len) {
ci_off = 8;
id_ext = 0;
for (m = 0; m < 8; ++m) {
if (m > 0)
id_ext <<= 8;
id_ext |= ip[m];
}
printf(" Identifier extension: 0x%" PRIx64 "\n", id_ext);
} else if ((8 != i_len) && (12 != i_len)) {
fprintf(stderr, " << can only decode 8, 12 and 16 "
"byte ids>>\n");
dStrHex((const char *)ip, i_len, 0);
break;
}
c_id = ((ip[ci_off] << 16) | (ip[ci_off + 1] << 8) |
ip[ci_off + 2]);
printf(" IEEE Company_id: 0x%x\n", c_id);
vsei = 0;
for (m = 0; m < 5; ++m) {
if (m > 0)
vsei <<= 8;
vsei |= ip[ci_off + 3 + m];
}
printf(" Vendor Specific Extension Identifier: 0x%" PRIx64
"\n", vsei);
if (12 == i_len) {
d_id = ((ip[8] << 24) | (ip[9] << 16) | (ip[10] << 8) |
ip[11]);
printf(" Directory ID: 0x%x\n", d_id);
}
break;
case 3: /* NAA */
if (1 != c_set) {
fprintf(stderr, " << unexpected code set %d for "
"NAA>>\n", c_set);
dStrHex((const char *)ip, i_len, 0);
break;
}
naa = (ip[0] >> 4) & 0xff;
if (! ((2 == naa) || (5 == naa) || (6 == naa))) {
fprintf(stderr, " << unexpected NAA [0x%x]>>\n", naa);
dStrHex((const char *)ip, i_len, 0);
break;
}
if (2 == naa) {
if (8 != i_len) {
fprintf(stderr, " << unexpected NAA 2 identifier "
"length: 0x%x>>\n", i_len);
dStrHex((const char *)ip, i_len, 0);
break;
}
d_id = (((ip[0] & 0xf) << 8) | ip[1]);
c_id = ((ip[2] << 16) | (ip[3] << 8) | ip[4]);
vsi = ((ip[5] << 16) | (ip[6] << 8) | ip[7]);
if (long_out) {
printf(" NAA 2, vendor specific identifier A: "
"0x%x\n", d_id);
printf(" IEEE Company_id: 0x%x\n", c_id);
printf(" vendor specific identifier B: 0x%x\n", vsi);
printf(" [0x");
for (m = 0; m < 8; ++m)
printf("%02x", (unsigned int)ip[m]);
printf("]\n");
}
printf(" 0x");
for (m = 0; m < 8; ++m)
printf("%02x", (unsigned int)ip[m]);
printf("\n");
} else if (5 == naa) {
if (8 != i_len) {
fprintf(stderr, " << unexpected NAA 5 identifier "
"length: 0x%x>>\n", i_len);
dStrHex((const char *)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];
}
if (long_out) {
printf(" NAA 5, IEEE Company_id: 0x%x\n", c_id);
printf(" Vendor Specific Identifier: 0x%" PRIx64
"\n", vsei);
printf(" [0x");
for (m = 0; m < 8; ++m)
printf("%02x", (unsigned int)ip[m]);
printf("]\n");
} else {
printf(" 0x");
for (m = 0; m < 8; ++m)
printf("%02x", (unsigned int)ip[m]);
printf("\n");
}
} else if (6 == naa) {
if (16 != i_len) {
fprintf(stderr, " << unexpected NAA 6 identifier "
"length: 0x%x>>\n", i_len);
dStrHex((const char *)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];
}
if (long_out) {
printf(" NAA 6, IEEE Company_id: 0x%x\n", c_id);
printf(" Vendor Specific Identifier: 0x%" PRIx64
"\n", vsei);
vsei = 0;
for (m = 0; m < 8; ++m) {
if (m > 0)
vsei <<= 8;
vsei |= ip[8 + m];
}
printf(" Vendor Specific Identifier Extension: "
"0x%" PRIx64 "\n", vsei);
printf(" [0x");
for (m = 0; m < 16; ++m)
printf("%02x", (unsigned int)ip[m]);
printf("]\n");
} else {
printf(" 0x");
for (m = 0; m < 16; ++m)
printf("%02x", (unsigned int)ip[m]);
printf("\n");
}
}
break;
case 4: /* Relative target port */
if ((1 != c_set) || (1 != assoc) || (4 != i_len)) {
fprintf(stderr, " << expected binary code_set, target "
"port association, length 4>>\n");
dStrHex((const char *)ip, i_len, 0);
break;
}
d_id = ((ip[2] << 8) | ip[3]);
printf(" Relative target port: 0x%x\n", d_id);
break;
case 5: /* (primary) Target port group */
if ((1 != c_set) || (1 != assoc) || (4 != i_len)) {
fprintf(stderr, " << expected binary code_set, target "
"port association, length 4>>\n");
dStrHex((const char *)ip, i_len, 0);
break;
}
d_id = ((ip[2] << 8) | ip[3]);
printf(" Target port group: 0x%x\n", d_id);
break;
case 6: /* Logical unit group */
if ((1 != c_set) || (0 != assoc) || (4 != i_len)) {
fprintf(stderr, " << expected binary code_set, logical "
"unit association, length 4>>\n");
dStrHex((const char *)ip, i_len, 0);
break;
}
d_id = ((ip[2] << 8) | ip[3]);
printf(" Logical unit group: 0x%x\n", d_id);
break;
case 7: /* MD5 logical unit identifier */
if ((1 != c_set) || (0 != assoc)) {
printf(" << expected binary code_set, logical "
"unit association>>\n");
dStrHex((const char *)ip, i_len, 0);
break;
}
printf(" MD5 logical unit identifier:\n");
dStrHex((const char *)ip, i_len, 0);
break;
case 8: /* SCSI name string */
if (3 != c_set) {
fprintf(stderr, " << expected UTF-8 code_set>>\n");
dStrHex((const char *)ip, i_len, 0);
break;
}
printf(" 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
*/
printf(" %s\n", (const char *)ip);
break;
default: /* reserved */
dStrHex((const char *)ip, i_len, 0);
break;
}
}
if (-2 == u) {
fprintf(stderr, "VPD page error: short designator around "
"offset %d\n", off);
return SG_LIB_CAT_MALFORMED;
}
return 0;
}
/* Transport IDs are initiator port identifiers, typically other than the
initiator port issuing a SCSI command. Code borrowed from sg_persist.c */
static void decode_transport_id(const char * leadin, unsigned char * ucp,
int len)
{
int format_code, proto_id, num, j, k;
unsigned long long ull;
int bump;
for (k = 0, bump= 24; k < len; k += bump, ucp += bump) {
if ((len < 24) || (0 != (len % 4)))
printf("%sTransport Id short or not multiple of 4 "
"[length=%d]:\n", leadin, len);
else
printf("%sTransport Id of initiator:\n", leadin);
format_code = ((ucp[0] >> 6) & 0x3);
proto_id = (ucp[0] & 0xf);
switch (proto_id) {
case 0: /* Fibre channel */
printf("%s FCP-2 World Wide Name:\n", leadin);
if (0 != format_code)
printf("%s [Unexpected format code: %d]\n", leadin,
format_code);
dStrHex((const char *)&ucp[8], 8, 0);
bump = 24;
break;
case 1: /* Parallel SCSI */
printf("%s Parallel SCSI initiator SCSI address: 0x%x\n",
leadin, ((ucp[2] << 8) | ucp[3]));
if (0 != format_code)
printf("%s [Unexpected format code: %d]\n", leadin,
format_code);
printf("%s relative port number (of corresponding target): "
"0x%x\n", leadin, ((ucp[6] << 8) | ucp[7]));
bump = 24;
break;
case 2: /* SSA */
printf("%s SSA (transport id not defined):\n", leadin);
printf("%s format code: %d\n", leadin, format_code);
dStrHex((const char *)ucp, ((len > 24) ? 24 : len), 0);
bump = 24;
break;
case 3: /* IEEE 1394 */
printf("%s IEEE 1394 EUI-64 name:\n", leadin);
if (0 != format_code)
printf("%s [Unexpected format code: %d]\n", leadin,
format_code);
dStrHex((const char *)&ucp[8], 8, 0);
bump = 24;
break;
case 4: /* Remote Direct Memory Access (RDMA) */
printf("%s RDMA initiator port identifier:\n", leadin);
if (0 != format_code)
printf("%s [Unexpected format code: %d]\n", leadin,
format_code);
dStrHex((const char *)&ucp[8], 16, 0);
bump = 24;
break;
case 5: /* iSCSI */
printf("%s iSCSI ", leadin);
num = ((ucp[2] << 8) | ucp[3]);
if (0 == format_code)
printf("name: %.*s\n", num, &ucp[4]);
else if (1 == format_code)
printf("world wide unique port id: %.*s\n", num, &ucp[4]);
else {
printf(" [Unexpected format code: %d]\n", format_code);
dStrHex((const char *)ucp, num + 4, 0);
}
bump = (((num + 4) < 24) ? 24 : num + 4);
break;
case 6: /* SAS */
ull = 0;
for (j = 0; j < 8; ++j) {
if (j > 0)
ull <<= 8;
ull |= ucp[4 + j];
}
printf("%s SAS address: 0x%" PRIx64 "\n", leadin, ull);
if (0 != format_code)
printf("%s [Unexpected format code: %d]\n", leadin,
format_code);
bump = 24;
break;
case 7: /* Automation/Drive Interface Transport Protocol */
printf("%s ADT:\n", leadin);
printf("%s format code: %d\n", leadin, format_code);
dStrHex((const char *)ucp, ((len > 24) ? 24 : len), 0);
bump = 24;
break;
case 8: /* ATAPI */
printf("%s ATAPI:\n", leadin);
printf("%s format code: %d\n", leadin, format_code);
dStrHex((const char *)ucp, ((len > 24) ? 24 : len), 0);
bump = 24;
break;
default:
fprintf(stderr, "%s unknown protocol id=0x%x "
"format_code=%d\n", leadin, proto_id, format_code);
dStrHex((const char *)ucp, ((len > 24) ? 24 : len), 0);
bump = 24;
break;
}
}
}
static void decode_x_inq_vpd(unsigned char * buff, int len, int do_hex)
{
if (len < 7) {
fprintf(stderr, "Extended INQUIRY data VPD page length too "
"short=%d\n", len);
return;
}
if (do_hex) {
dStrHex((const char *)buff, len, 0);
return;
}
printf(" SPT=%d GRD_CHK=%d APP_CHK=%d REF_CHK=%d\n",
((buff[4] >> 3) & 0x7), !!(buff[4] & 0x4), !!(buff[4] & 0x2),
!!(buff[4] & 0x1));
printf(" GRP_SUP=%d PRIOR_SUP=%d HEADSUP=%d ORDSUP=%d SIMPSUP=%d\n",
!!(buff[5] & 0x10), !!(buff[5] & 0x8), !!(buff[5] & 0x4),
!!(buff[5] & 0x2), !!(buff[5] & 0x1));
printf(" CORR_D_SUP=%d NV_SUP=%d V_SUP=%d LUICLR=%d\n",
!!(buff[6] & 0x4), !!(buff[6] & 0x2), !!(buff[6] & 0x1),
!!(buff[7] & 0x1));
}
static void decode_softw_inf_id(unsigned char * buff, int len, int do_hex)
{
int k;
if (do_hex) {
dStrHex((const char *)buff, len, 0);
return;
}
len -= 4;
buff += 4;
for ( ; len > 5; len -= 6, buff += 6) {
printf(" ");
for (k = 0; k < 6; ++k)
printf("%02x", (unsigned int)buff[k]);
printf("\n");
}
}
static void decode_ata_info_vpd(unsigned char * buff, int len, int do_long,
int do_hex)
{
char b[80];
int num, is_be;
const char * cp;
if (len < 36) {
fprintf(stderr, "ATA information VPD page length too "
"short=%d\n", len);
return;
}
if (do_hex && (2 != do_hex)) {
dStrHex((const char *)buff, len, 0);
return;
}
memcpy(b, buff + 8, 8);
b[8] = '\0';
printf(" SAT Vendor identification: %s\n", b);
memcpy(b, buff + 16, 16);
b[16] = '\0';
printf(" SAT Product identification: %s\n", b);
memcpy(b, buff + 32, 4);
b[4] = '\0';
printf(" SAT Product revision level: %s\n", b);
if (len < 56)
return;
if (do_long) {
printf(" Signature (Device to host FIS):\n");
dStrHex((const char *)buff + 36, 20, 0);
}
if (len < 60)
return;
is_be = sg_is_big_endian();
if ((0xec == buff[56]) || (0xa1 == buff[56])) {
cp = (0xa1 == buff[56]) ? "PACKET " : "";
printf(" ATA command IDENTIFY %sDEVICE response summary:\n", cp);
num = sg_ata_get_chars((const unsigned short *)(buff + 60), 27, 20,
is_be, b);
b[num] = '\0';
printf(" model: %s\n", b);
num = sg_ata_get_chars((const unsigned short *)(buff + 60), 10, 10,
is_be, b);
b[num] = '\0';
printf(" serial number: %s\n", b);
num = sg_ata_get_chars((const unsigned short *)(buff + 60), 23, 4,
is_be, b);
b[num] = '\0';
printf(" firmware revision: %s\n", b);
if (do_long)
printf(" ATA command IDENTIFY %sDEVICE response in hex:\n", cp);
} else if (do_long)
printf(" ATA command 0x%x got following response:\n",
(unsigned int)buff[56]);
if (len < 572)
return;
if (2 == do_hex)
dStrHex((const char *)(buff + 60), 512, 0);
else if (do_long)
dWordHex((const unsigned short *)(buff + 60), 256, 0, is_be);
}
static void decode_b0_vpd(unsigned char * buff, int len, int do_hex, int pdt)
{
unsigned int u;
if (do_hex) {
dStrHex((const char *)buff, len, 0);
return;
}
switch (pdt) {
case 0: case 4: case 7:
if (len < 16) {
fprintf(stderr, "Block limits VPD page length too "
"short=%d\n", len);
return;
}
u = (buff[6] << 8) | buff[7];
printf(" Optimal transfer length granularity: %u blocks\n", u);
u = (buff[8] << 24) | (buff[9] << 16) | (buff[10] << 8) |
buff[11];
printf(" Maximum transfer length: %u blocks\n", u);
u = (buff[12] << 24) | (buff[13] << 16) | (buff[14] << 8) |
buff[15];
printf(" Optimal transfer length: %u blocks\n", u);
if (len > 19) { /* added in sbc3r09 */
u = (buff[16] << 24) | (buff[17] << 16) | (buff[18] << 8) |
buff[19];
printf(" Maximum prefetch, xdread, xdwrite transfer length: "
"%u blocks\n", u);
}
break;
case 1: case 8:
printf(" WORM=%d\n", !!(buff[4] & 0x1));
break;
case 0x11:
default:
printf(" Unable to decode pdt=0x%x, in hex:\n", pdt);
dStrHex((const char *)buff, len, 0);
break;
}
}
/* Returns 0 if successful */
static int svpd_unable_to_decode(int sg_fd, int num_vpd, int subvalue,
int do_hex, int do_raw, int do_long,
int do_quiet, int verbose)
{
int len, t, res;
t = do_quiet; /* suppress warning */
if ((! do_hex) && (! do_raw))
printf("Only hex output supported\n");
if (!do_raw) {
if (subvalue)
printf("VPD page code=0x%.2x, subvalue=0x%.2x:\n", num_vpd,
subvalue);
else
printf("VPD page code=0x%.2x:\n", num_vpd);
}
res = sg_ll_inquiry(sg_fd, 0, 1, num_vpd, rsp_buff, DEF_ALLOC_LEN,
1, verbose);
if (0 == res) {
len = ((rsp_buff[2] << 8) + rsp_buff[3]) + 4;
if (num_vpd != rsp_buff[1]) {
fprintf(stderr, "invalid VPD response; probably a STANDARD "
"INQUIRY response\n");
if (verbose) {
fprintf(stderr, "First 32 bytes of bad response\n");
dStrHex((const char *)rsp_buff, 32, 0);
}
return SG_LIB_CAT_MALFORMED;
}
if (len > MX_ALLOC_LEN) {
fprintf(stderr, "response length too long: %d > %d\n", len,
MX_ALLOC_LEN);
return SG_LIB_CAT_MALFORMED;
} else if (len > DEF_ALLOC_LEN) {
res = sg_ll_inquiry(sg_fd, 0, 1, num_vpd, rsp_buff, len, 1,
verbose);
if (res) {
fprintf(stderr, "fetching VPD page (2) code=0x%.2x: "
"failed\n", num_vpd);
return res;
}
}
if (do_raw)
dStrRaw((const char *)rsp_buff, len);
else
dStrHex((const char *)rsp_buff, len, (do_long ? 0 : 1));
return 0;
} else {
fprintf(stderr,
"fetching VPD page code=0x%.2x: failed\n", num_vpd);
return res;
}
}
/* Returns 0 if successful, else see sg_ll_inquiry() */
static int svpd_decode_standard(int sg_fd, int num_vpd, int subvalue,
int do_hex, int do_raw, int do_long,
int do_quiet, int verbose)
{
int len, pdt, num, k;
char buff[48];
const struct svpd_values_name_t * vnp;
int res = 0;
switch(num_vpd) {
case VPD_SUPPORTED_VPDS:
if ((! do_raw) && (! do_quiet))
printf("Supported VPD pages VPD page:\n");
res = sg_ll_inquiry(sg_fd, 0, 1, VPD_SUPPORTED_VPDS, rsp_buff,
DEF_ALLOC_LEN, 1, verbose);
if (0 == res) {
len = rsp_buff[3] + 4;
if (VPD_SUPPORTED_VPDS != rsp_buff[1]) {
fprintf(stderr, "invalid VPD response; probably a STANDARD "
"INQUIRY response\n");
if (verbose) {
fprintf(stderr, "First 32 bytes of bad response\n");
dStrHex((const char *)rsp_buff, 32, 0);
}
return SG_LIB_CAT_MALFORMED;
}
if (do_raw)
dStrRaw((const char *)rsp_buff, len);
else if (do_hex)
dStrHex((const char *)rsp_buff, len, 0);
else {
pdt = rsp_buff[0] & 0x1f;
if (verbose || do_long)
printf(" [PQual=%d Peripheral device type: %s]\n",
(rsp_buff[0] & 0xe0) >> 5,
sg_get_pdt_str(pdt, sizeof(buff), buff));
num = rsp_buff[3];
for (k = 0; k < num; ++k) {
vnp = sdp_get_vpd_detail(rsp_buff[4 + k], -1, pdt);
if (vnp)
printf(" %s [%s]\n", vnp->name, vnp->acron);
else
printf(" 0x%x\n", rsp_buff[4 + k]);
}
}
return 0;
}
break;
case VPD_UNIT_SERIAL_NUM:
if ((! do_raw) && (! do_quiet))
printf("Unit serial number VPD page:\n");
res = sg_ll_inquiry(sg_fd, 0, 1, VPD_UNIT_SERIAL_NUM, rsp_buff,
DEF_ALLOC_LEN, 1, verbose);
if (0 == res) {
len = rsp_buff[3] + 4;
if (VPD_UNIT_SERIAL_NUM != rsp_buff[1]) {
fprintf(stderr, "invalid VPD response; probably a STANDARD "
"INQUIRY response\n");
if (verbose) {
fprintf(stderr, "First 32 bytes of bad response\n");
dStrHex((const char *)rsp_buff, 32, 0);
}
return SG_LIB_CAT_MALFORMED;
}
if (do_raw)
dStrRaw((const char *)rsp_buff, len);
else if (do_hex)
dStrHex((const char *)rsp_buff, len, 0);
else {
char obuff[DEF_ALLOC_LEN];
pdt = rsp_buff[0] & 0x1f;
if (verbose || do_long)
printf(" [PQual=%d Peripheral device type: %s]\n",
(rsp_buff[0] & 0xe0) >> 5,
sg_get_pdt_str(pdt, sizeof(buff), buff));
memset(obuff, 0, sizeof(obuff));
len -= 4;
if (len >= (int)sizeof(obuff))
len = sizeof(obuff) - 1;
memcpy(obuff, rsp_buff + 4, len);
printf(" Unit serial number: %s\n", obuff);
}
return 0;
}
break;
case VPD_DEVICE_ID:
if ((! do_raw) && (! do_quiet))
printf("Device Identification VPD page:\n");
res = sg_ll_inquiry(sg_fd, 0, 1, VPD_DEVICE_ID, rsp_buff,
DEF_ALLOC_LEN, 1, verbose);
if (0 == res) {
len = ((rsp_buff[2] << 8) + rsp_buff[3]) + 4;
if (VPD_DEVICE_ID != rsp_buff[1]) {
fprintf(stderr, "invalid VPD response; probably a STANDARD "
"INQUIRY response\n");
if (verbose) {
fprintf(stderr, "First 32 bytes of bad response\n");
dStrHex((const char *)rsp_buff, 32, 0);
}
return SG_LIB_CAT_MALFORMED;
}
if (len > MX_ALLOC_LEN) {
fprintf(stderr, "response length too long: %d > %d\n", len,
MX_ALLOC_LEN);
return SG_LIB_CAT_MALFORMED;
} else if (len > DEF_ALLOC_LEN) {
if (sg_ll_inquiry(sg_fd, 0, 1, VPD_DEVICE_ID, rsp_buff, len,
1, verbose))
return SG_LIB_CAT_OTHER;
}
if (do_raw)
dStrRaw((const char *)rsp_buff, len);
else if (do_hex)
dStrHex((const char *)rsp_buff, len, 0);
else {
pdt = rsp_buff[0] & 0x1f;
if (verbose || do_long)
printf(" [PQual=%d Peripheral device type: %s]\n",
(rsp_buff[0] & 0xe0) >> 5,
sg_get_pdt_str(pdt, sizeof(buff), buff));
decode_id_vpd(rsp_buff, len, subvalue, do_long, do_quiet);
}
return 0;
}
break;
case VPD_SOFTW_INF_ID:
if ((! do_raw) && (! do_quiet))
printf("Software interface identification VPD page:\n");
res = sg_ll_inquiry(sg_fd, 0, 1, VPD_SOFTW_INF_ID, rsp_buff,
DEF_ALLOC_LEN, 1, verbose);
if (0 == res) {
len = rsp_buff[3] + 4;
if (VPD_SOFTW_INF_ID != rsp_buff[1]) {
fprintf(stderr, "invalid VPD response; probably a STANDARD "
"INQUIRY response\n");
if (verbose) {
fprintf(stderr, "First 32 bytes of bad response\n");
dStrHex((const char *)rsp_buff, 32, 0);
}
return SG_LIB_CAT_MALFORMED;
}
if (do_raw)
dStrRaw((const char *)rsp_buff, len);
else {
pdt = rsp_buff[0] & 0x1f;
if (verbose || do_long)
printf(" [PQual=%d Peripheral device type: %s]\n",
(rsp_buff[0] & 0xe0) >> 5,
sg_get_pdt_str(pdt, sizeof(buff), buff));
decode_softw_inf_id(rsp_buff, len, do_hex);
}
return 0;
}
break;
case VPD_MAN_NET_ADDR:
if ((! do_raw) && (! do_quiet))
printf("Management network addresses VPD page:\n");
res = sg_ll_inquiry(sg_fd, 0, 1, VPD_MAN_NET_ADDR, rsp_buff,
DEF_ALLOC_LEN, 1, verbose);
if (0 == res) {
len = ((rsp_buff[2] << 8) + rsp_buff[3]) + 4;
if (VPD_MAN_NET_ADDR != rsp_buff[1]) {
fprintf(stderr, "invalid VPD response; probably a STANDARD "
"INQUIRY response\n");
if (verbose) {
fprintf(stderr, "First 32 bytes of bad response\n");
dStrHex((const char *)rsp_buff, 32, 0);
}
return SG_LIB_CAT_MALFORMED;
}
if (len > MX_ALLOC_LEN) {
fprintf(stderr, "response length too long: %d > %d\n", len,
MX_ALLOC_LEN);
return SG_LIB_CAT_MALFORMED;
} else if (len > DEF_ALLOC_LEN) {
if (sg_ll_inquiry(sg_fd, 0, 1, VPD_MAN_NET_ADDR, rsp_buff,
len, 1, verbose))
return SG_LIB_CAT_OTHER;
}
if (do_raw)
dStrRaw((const char *)rsp_buff, len);
else
decode_net_man_vpd(rsp_buff, len, do_hex);
return 0;
}
break;
case VPD_MODE_PG_POLICY:
if ((! do_raw) && (! do_quiet))
printf("Mode page VPD policy:\n");
res = sg_ll_inquiry(sg_fd, 0, 1, VPD_MODE_PG_POLICY, rsp_buff,
DEF_ALLOC_LEN, 1, verbose);
if (0 == res) {
len = ((rsp_buff[2] << 8) + rsp_buff[3]) + 4;
if (VPD_MODE_PG_POLICY != rsp_buff[1]) {
fprintf(stderr, "invalid VPD response; probably a STANDARD "
"INQUIRY response\n");
if (verbose) {
fprintf(stderr, "First 32 bytes of bad response\n");
dStrHex((const char *)rsp_buff, 32, 0);
}
return SG_LIB_CAT_MALFORMED;
}
if (len > MX_ALLOC_LEN) {
fprintf(stderr, "response length too long: %d > %d\n", len,
MX_ALLOC_LEN);
return SG_LIB_CAT_MALFORMED;
} else if (len > DEF_ALLOC_LEN) {
if (sg_ll_inquiry(sg_fd, 0, 1, VPD_MODE_PG_POLICY, rsp_buff,
len, 1, verbose))
return SG_LIB_CAT_OTHER;
}
if (do_raw)
dStrRaw((const char *)rsp_buff, len);
else {
pdt = rsp_buff[0] & 0x1f;
if (verbose || do_long)
printf(" [PQual=%d Peripheral device type: %s]\n",
(rsp_buff[0] & 0xe0) >> 5,
sg_get_pdt_str(pdt, sizeof(buff), buff));
decode_mode_policy_vpd(rsp_buff, len, do_hex);
}
return 0;
}
break;
case VPD_EXT_INQ:
if ((! do_raw) && (! do_quiet))
printf("extended INQUIRY data VPD page:\n");
res = sg_ll_inquiry(sg_fd, 0, 1, VPD_EXT_INQ, rsp_buff,
DEF_ALLOC_LEN, 1, verbose);
if (0 == res) {
len = ((rsp_buff[2] << 8) + rsp_buff[3]) + 4;
if (VPD_EXT_INQ != rsp_buff[1]) {
fprintf(stderr, "invalid VPD response; probably a STANDARD "
"INQUIRY response\n");
if (verbose) {
fprintf(stderr, "First 32 bytes of bad response\n");
dStrHex((const char *)rsp_buff, 32, 0);
}
return SG_LIB_CAT_MALFORMED;
}
if (len > MX_ALLOC_LEN) {
fprintf(stderr, "response length too long: %d > %d\n", len,
MX_ALLOC_LEN);
return SG_LIB_CAT_MALFORMED;
} else if (len > DEF_ALLOC_LEN) {
if (sg_ll_inquiry(sg_fd, 0, 1, VPD_EXT_INQ, rsp_buff, len,
1, verbose))
return SG_LIB_CAT_OTHER;
}
if (do_raw)
dStrRaw((const char *)rsp_buff, len);
else {
pdt = rsp_buff[0] & 0x1f;
if (verbose || do_long)
printf(" [PQual=%d Peripheral device type: %s]\n",
(rsp_buff[0] & 0xe0) >> 5,
sg_get_pdt_str(pdt, sizeof(buff), buff));
decode_x_inq_vpd(rsp_buff, len, do_hex);
}
return 0;
}
break;
case VPD_ATA_INFO:
if ((! do_raw) && (3 != do_hex) && (! do_quiet))
printf("ATA information VPD page:\n");
res = sg_ll_inquiry(sg_fd, 0, 1, VPD_ATA_INFO, rsp_buff,
VPD_ATA_INFO_LEN, 1, verbose);
if (0 == res) {
len = ((rsp_buff[2] << 8) + rsp_buff[3]) + 4;
if (VPD_ATA_INFO != rsp_buff[1]) {
fprintf(stderr, "invalid VPD response; probably a STANDARD "
"INQUIRY response\n");
if (verbose) {
fprintf(stderr, "First 32 bytes of bad response\n");
dStrHex((const char *)rsp_buff, 32, 0);
}
return SG_LIB_CAT_MALFORMED;
}
if (len > MX_ALLOC_LEN) {
fprintf(stderr, "response length too long: %d > %d\n", len,
MX_ALLOC_LEN);
return SG_LIB_CAT_MALFORMED;
} else if (len > VPD_ATA_INFO_LEN) {
if (sg_ll_inquiry(sg_fd, 0, 1, VPD_ATA_INFO, rsp_buff, len,
1, verbose))
return SG_LIB_CAT_OTHER;
}
if ((2 == do_raw) || (3 == do_hex)) /* special for hdparm */
dWordHex((const unsigned short *)(rsp_buff + 60),
256, -2, sg_is_big_endian());
else if (do_raw)
dStrRaw((const char *)rsp_buff, len);
else {
pdt = rsp_buff[0] & 0x1f;
if (verbose || do_long)
printf(" [PQual=%d Peripheral device type: %s]\n",
(rsp_buff[0] & 0xe0) >> 5,
sg_get_pdt_str(pdt, sizeof(buff), buff));
decode_ata_info_vpd(rsp_buff, len, do_long, do_hex);
}
return 0;
}
break;
case 0xb0: /* could be BLOCK LIMITS but need to know pdt to find out */
res = sg_ll_inquiry(sg_fd, 0, 1, 0xb0, rsp_buff,
DEF_ALLOC_LEN, 1, verbose);
if (0 == res) {
pdt = rsp_buff[0] & 0x1f;
if ((! do_raw) && (! do_quiet)) {
switch (pdt) {
case 0: case 4: case 7:
printf("Block limits VPD page (SBC):\n");
break;
case 1: case 8:
printf("Sequential access device capabilities VPD page "
"(SSC):\n");
break;
case 0x11:
printf("OSD information (OSD) VPD page:\n");
break;
default:
printf("VPD page=0x%x, pdt=0x%x:\n", 0xb0, pdt);
break;
}
}
len = ((rsp_buff[2] << 8) + rsp_buff[3]) + 4;
if (0xb0 != rsp_buff[1]) {
fprintf(stderr, "invalid VPD response; probably a STANDARD "
"INQUIRY response\n");
if (verbose) {
fprintf(stderr, "First 32 bytes of bad response\n");
dStrHex((const char *)rsp_buff, 32, 0);
}
return SG_LIB_CAT_MALFORMED;
}
if (len > MX_ALLOC_LEN) {
fprintf(stderr, "response length too long: %d > %d\n", len,
MX_ALLOC_LEN);
return SG_LIB_CAT_MALFORMED;
} else if (len > DEF_ALLOC_LEN) {
if (sg_ll_inquiry(sg_fd, 0, 1, 0xb0, rsp_buff,
len, 1, verbose))
return SG_LIB_CAT_OTHER;
}
if (do_raw)
dStrRaw((const char *)rsp_buff, len);
else {
pdt = rsp_buff[0] & 0x1f;
if (verbose || do_long)
printf(" [PQual=%d Peripheral device type: %s]\n",
(rsp_buff[0] & 0xe0) >> 5,
sg_get_pdt_str(pdt, sizeof(buff), buff));
decode_b0_vpd(rsp_buff, len, do_hex, pdt);
}
return 0;
} else if (! do_raw)
printf("VPD page=0xb0\n");
break;
case VPD_SCSI_PORTS:
if ((! do_raw) && (! do_quiet))
printf("SCSI Ports VPD page:\n");
res = sg_ll_inquiry(sg_fd, 0, 1, VPD_SCSI_PORTS, rsp_buff,
DEF_ALLOC_LEN, 1, verbose);
if (0 == res) {
len = ((rsp_buff[2] << 8) + rsp_buff[3]) + 4;
if (VPD_SCSI_PORTS != rsp_buff[1]) {
fprintf(stderr, "invalid VPD response; probably a STANDARD "
"INQUIRY response\n");
if (verbose) {
fprintf(stderr, "First 32 bytes of bad response\n");
dStrHex((const char *)rsp_buff, 32, 0);
}
return SG_LIB_CAT_MALFORMED;
}
if (len > MX_ALLOC_LEN) {
fprintf(stderr, "response length too long: %d > %d\n", len,
MX_ALLOC_LEN);
return SG_LIB_CAT_MALFORMED;
} else if (len > DEF_ALLOC_LEN) {
if (sg_ll_inquiry(sg_fd, 0, 1, VPD_SCSI_PORTS, rsp_buff, len,
1, verbose))
return SG_LIB_CAT_OTHER;
}
if (do_raw)
dStrRaw((const char *)rsp_buff, len);
else {
pdt = rsp_buff[0] & 0x1f;
if (verbose || do_long)
printf(" [PQual=%d Peripheral device type: %s]\n",
(rsp_buff[0] & 0xe0) >> 5,
sg_get_pdt_str(pdt, sizeof(buff), buff));
decode_scsi_ports_vpd(rsp_buff, len, do_hex, do_long, do_quiet);
}
return 0;
}
break;
default:
return SG_LIB_SYNTAX_ERROR;
}
return res;
}
int main(int argc, char * argv[])
{
int sg_fd, c, res;
char device_name[256];
const struct svpd_values_name_t * vnp;
const char * page_str = NULL;
const char * cp;
int num_vpd = 0;
int do_hex = 0;
int do_ident = 0;
int do_long = 0;
int do_quiet = 0;
int do_raw = 0;
int do_verbose = 0;
int ret = 0;
int req_pdt = -1;
int subvalue = 0;
memset(device_name, 0, sizeof device_name);
while (1) {
int option_index = 0;
c = getopt_long(argc, argv, "ehHilp:qrvV", long_options,
&option_index);
if (c == -1)
break;
switch (c) {
case 'e':
printf("Standard VPD pages:\n");
enumerate_vpds(1, 1);
return 0;
case 'h':
case '?':
usage();
return 0;
case 'H':
++do_hex;
break;
case 'i':
++do_ident;
break;
case 'l':
++do_long;
break;
case 'p':
if (page_str) {
fprintf(stderr, "only one '--page=' option permitted\n");
usage();
return SG_LIB_SYNTAX_ERROR;
} else
page_str = optarg;
break;
case 'q':
++do_quiet;
break;
case 'r':
++do_raw;
break;
case 'v':
++do_verbose;
break;
case 'V':
fprintf(stderr, "version: %s\n", version_str);
return 0;
default:
fprintf(stderr, "unrecognised option code 0x%x ??\n", c);
usage();
return SG_LIB_SYNTAX_ERROR;
}
}
if (optind < argc) {
if ('\0' == device_name[0]) {
strncpy(device_name, argv[optind], sizeof(device_name) - 1);
device_name[sizeof(device_name) - 1] = '\0';
++optind;
}
if (optind < argc) {
for (; optind < argc; ++optind)
fprintf(stderr, "Unexpected extra argument: %s\n",
argv[optind]);
usage();
return SG_LIB_SYNTAX_ERROR;
}
}
if (page_str) {
if (isalpha(page_str[0])) {
vnp = sdp_find_vpd_by_acron(page_str);
if (NULL == vnp) {
vnp = svpd_find_vendor_by_acron(page_str);
if (NULL == vnp) {
fprintf(stderr, "abbreviation doesn't match a VPD "
"page\n");
printf("available VPD pages:\n");
enumerate_vpds(1, 1);
return SG_LIB_SYNTAX_ERROR;
}
}
num_vpd = vnp->value;
subvalue = vnp->subvalue;
req_pdt = vnp->pdt;
} else {
cp = strchr(page_str, ',');
num_vpd = get_num(page_str);
if ((num_vpd < 0) || (num_vpd > 255)) {
fprintf(stderr, "Bad page code value after '-p' "
"option\n");
printf("available VPD pages:\n");
enumerate_vpds(1, 1);
return SG_LIB_SYNTAX_ERROR;
}
if (cp) {
subvalue = get_num(cp + 1);
if ((subvalue < 0) || (subvalue > 255)) {
fprintf(stderr, "Bad subvalue code value after "
"'-p' option\n");
return SG_LIB_SYNTAX_ERROR;
}
}
}
}
if (do_raw && do_hex) {
fprintf(stderr, "Can't do hex and raw at the same time\n");
usage();
return SG_LIB_SYNTAX_ERROR;
}
if (do_ident) {
num_vpd = VPD_DEVICE_ID;
req_pdt = -1;
if (do_ident > 1) {
if (0 == do_long)
++do_quiet;
subvalue = VPD_DI_SEL_LU;
}
}
if ('\0' == device_name[0]) {
fprintf(stderr, "No DEVICE argument given\n");
usage();
return SG_LIB_SYNTAX_ERROR;
}
if ((sg_fd = sg_cmds_open_device(device_name, 1 /* ro */,
do_verbose)) < 0) {
fprintf(stderr, "error opening file: %s: %s\n",
device_name, safe_strerror(-sg_fd));
return SG_LIB_FILE_ERROR;
}
memset(rsp_buff, 0, sizeof(rsp_buff));
res = svpd_decode_standard(sg_fd, num_vpd, subvalue, do_hex, do_raw,
do_long, do_quiet, do_verbose);
if (SG_LIB_SYNTAX_ERROR == res) {
res = svpd_decode_vendor(sg_fd, num_vpd, subvalue, do_hex, do_raw,
do_long, do_quiet, do_verbose);
if (SG_LIB_SYNTAX_ERROR == res)
res = svpd_unable_to_decode(sg_fd, num_vpd, subvalue, do_hex,
do_raw, do_long, do_quiet,
do_verbose);
}
if (SG_LIB_CAT_ABORTED_COMMAND == res)
fprintf(stderr, "fetching VPD page failed, aborted command\n");
else if (res)
fprintf(stderr, "fetching VPD page failed\n");
ret = res;
res = sg_cmds_close_device(sg_fd);
if (res < 0) {
fprintf(stderr, "close error: %s\n", safe_strerror(-res));
if (0 == ret)
return SG_LIB_FILE_ERROR;
}
return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
}