From 2f1a9f5e1171839c853ca0e0d049a489876c90ff Mon Sep 17 00:00:00 2001 From: Douglas Gilbert Date: Mon, 8 Feb 2016 04:53:45 +0000 Subject: sg_read_attr: new, supported by tape drives git-svn-id: https://svn.bingwo.ca/repos/sg3_utils/trunk@660 6180dd3e-e324-4e3e-922d-17de1ae2f315 --- src/sg_read_attr.c | 1138 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1138 insertions(+) create mode 100644 src/sg_read_attr.c (limited to 'src/sg_read_attr.c') diff --git a/src/sg_read_attr.c b/src/sg_read_attr.c new file mode 100644 index 00000000..bd02a493 --- /dev/null +++ b/src/sg_read_attr.c @@ -0,0 +1,1138 @@ +/* + * Copyright (c) 2016 Douglas Gilbert. + * All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the BSD_LICENSE file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "sg_lib.h" +#include "sg_lib_data.h" +#include "sg_pt.h" +#include "sg_cmds_basic.h" +#include "sg_unaligned.h" +#include "sg_pr2serr.h" + +/* A utility program originally written for the Linux OS SCSI subsystem. + * + * + * This program issues the SCSI READ ATTRIBUTE command to the given SCSI device + * and decodes the response. Based on spc5r08.pdf + */ + +static const char * version_str = "1.00 20160207"; + +#define MAX_RATTR_BUFF_LEN (1024 * 1024) +#define DEF_RATTR_BUFF_LEN (1024 * 8) + +#define SG_READ_ATTRIBUTE_CMD 0x8c +#define SG_READ_ATTRIBUTE_CMDLEN 16 + +#define RA_ATTR_VAL_SA 0x0 +#define RA_ATTR_LIST_SA 0x1 +#define RA_LV_LIST_SA 0x2 +#define RA_PART_LIST_SA 0x3 +#define RA_SMC2_SA 0x4 +#define RA_SUP_ATTR_SA 0x5 +#define RA_HIGHEST_SA 0x5 + +#define RA_FMT_BINARY 0x0 +#define RA_FMT_ASCII 0x1 +#define RA_FMT_TEXT 0x2 /* takes into account locale */ +#define RA_FMT_RES 0x3 /* reserved */ + + +#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */ +#define DEF_PT_TIMEOUT 60 /* 60 seconds */ + +struct opts_t { + int cache; + int ea; + int enumerate; + int filter; + int fai; + int do_hex; + int lvn; + int maxlen; + int pn; + int quiet; + int do_raw; + int o_readonly; + int sa; + int verbose; +}; + +struct acron_nv_t { + const char * acron; + const char * name; + int val; +}; + +struct attr_name_info_t { + int id; + const char * name; /* tab ('\t') suggest line break */ + int format; /* RA_FMT_BINARY and friends, -1 --> unknown */ + int len; /* -1 --> not fixed (variable) */ + int process; /* 0 --> print decimal if binary, 1 --> print hex, + * 2 --> further processing */ +}; + +static struct option long_options[] = { + {"cache", no_argument, 0, 'c'}, + {"enumerate", no_argument, 0, 'e'}, + {"element", required_argument, 0, 'E'}, /* SMC-3 element address */ + {"filter", required_argument, 0, 'f'}, + {"first", required_argument, 0, 'F'}, + {"help", no_argument, 0, 'h'}, + {"hex", no_argument, 0, 'H'}, + {"in", required_argument, 0, 'i'}, + {"lvn", required_argument, 0, 'l'}, + {"maxlen", required_argument, 0, 'm'}, + {"partition", required_argument, 0, 'p'}, + {"quiet", required_argument, 0, 'q'}, + {"raw", no_argument, 0, 'r'}, + {"readonly", no_argument, 0, 'R'}, + {"sa", required_argument, 0, 's'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'V'}, + {0, 0, 0, 0}, /* sentinel */ +}; + +static struct acron_nv_t sa_acron_arr[] = { + {"av", "attribute values", 0}, + {"al", "attribute list", 1}, + {"lvl", "logical volume list", 2}, + {"pl", "partition list", 3}, + {"smc", "SMC-2 should define this", 4}, + {"sa", "supported attributes", 5}, + {NULL, NULL, -1}, /* sentinel */ +}; + +static struct attr_name_info_t attr_name_arr[] = { +/* Device type attributes */ + {0x0, "Remaining capacity in partition [MiB]", RA_FMT_BINARY, 8, 0}, + {0x1, "Maximum capacity in partition [MiB]", RA_FMT_BINARY, 8, 0}, + {0x2, "TapeAlert flags", RA_FMT_BINARY, 8, 0}, /* SSC-4 */ + {0x3, "Load count", RA_FMT_BINARY, 8, 0}, + {0x4, "MAM space remaining [B]", RA_FMT_BINARY, 8, 0}, + {0x5, "Assigning organization", RA_FMT_ASCII, 8, 0}, /* SSC-4 */ + {0x6, "Format density code", RA_FMT_BINARY, 1, 1}, /* SSC-4 */ + {0x7, "Initialization count", RA_FMT_BINARY, 2, 0}, + {0x8, "Volume identifier", RA_FMT_ASCII, 32, 0}, + {0x9, "Volume change reference", RA_FMT_BINARY, -1, 1}, /* SSC-4 */ + {0x20a, "Density vendor/serial number at last load", RA_FMT_ASCII, 40, 0}, + {0x20b, "Density vendor/serial number at load-1", RA_FMT_ASCII, 40, 0}, + {0x20c, "Density vendor/serial number at load-2", RA_FMT_ASCII, 40, 0}, + {0x20d, "Density vendor/serial number at load-3", RA_FMT_ASCII, 40, 0}, + {0x220, "Total MiB written in medium life", RA_FMT_BINARY, 8, 0}, + {0x221, "Total MiB read in medium life", RA_FMT_BINARY, 8, 0}, + {0x222, "Total MiB written in current/last load", RA_FMT_BINARY, 8, 0}, + {0x223, "Total MiB read in current/last load", RA_FMT_BINARY, 8, 0}, + {0x224, "Logical position of first encrypted block", RA_FMT_BINARY, 8, 2}, + {0x225, "Logical position of first unencrypted block\tafter first " + "encrypted block", RA_FMT_BINARY, 8, 2}, + {0x340, "Medium usage history", RA_FMT_BINARY, 90, 2}, + {0x341, "Partition usage history", RA_FMT_BINARY, 60, 2}, + +/* Medium type attributes */ + {0x400, "Medium manufacturer", RA_FMT_ASCII, 8, 0}, + {0x401, "Medium serial number", RA_FMT_ASCII, 32, 0}, + {0x402, "Medium length [m]", RA_FMT_BINARY, 4, 0}, /* SSC-4 */ + {0x403, "Medium width [0.1 mm]", RA_FMT_BINARY, 4, 0}, /* SSC-4 */ + {0x404, "Assigning organization", RA_FMT_ASCII, 8, 0}, /* SSC-4 */ + {0x405, "Medium density code", RA_FMT_BINARY, 1, 1}, /* SSC-4 */ + {0x406, "Medium manufacture date", RA_FMT_ASCII, 8, 0}, + {0x407, "MAM capacity [B]", RA_FMT_BINARY, 8, 0}, + {0x408, "Medium type", RA_FMT_BINARY, 1, 1}, + {0x409, "Medium type information", RA_FMT_BINARY, 2, 1}, + {0x40a, "Numeric medium serial number", -1, -1, 1}, + +/* Host type attributes */ + {0x800, "Application vendor", RA_FMT_ASCII, 8, 0}, + {0x801, "Application name", RA_FMT_ASCII, 32, 0}, + {0x802, "Application version", RA_FMT_ASCII, 8, 0}, + {0x803, "User medium text label", RA_FMT_TEXT, 160, 0}, + {0x804, "Date and time last written", RA_FMT_ASCII, 12, 0}, + {0x805, "Text localization identifier", RA_FMT_BINARY, 1, 0}, + {0x806, "Barcode", RA_FMT_ASCII, 32, 0}, + {0x807, "Owning host textual name", RA_FMT_TEXT, 80, 0}, + {0x808, "Media pool", RA_FMT_TEXT, 160, 0}, + {0x809, "Partition user text label", RA_FMT_ASCII, 16, 0}, + {0x80a, "Load/unload at partition", RA_FMT_BINARY, 1, 0}, + {0x80a, "Application format version", RA_FMT_ASCII, 16, 0}, + {0x80c, "Volume coherency information", RA_FMT_BINARY, -1, 1}, + /* SSC-5 */ + {0x820, "Medium globally unique identifier", RA_FMT_BINARY, 36, 1}, + {0x821, "Media pool globally unique identifier", RA_FMT_BINARY, 36, 1}, + + {-1, NULL, -1, -1, 0}, +}; + + +static void +usage() +{ + pr2serr("Usage: sg_read_attr [--cache] [--element=EA] [--enumerate] " + "[--filter=FL]\n" + " [--first=FAI] [--help] [--hex] [--in=FN] " + "[--lvn-LVN]\n" + " [--maxlen=LEN] [--partition=PN] [--quiet] " + "[--raw]\n" + " [--readonly] [--sa=SA] [--verbose] " + "[--version]\n" + " DEVICE\n"); + pr2serr(" where:\n" + " --cache|-c set CACHE bit in cdn (def: clear)\n" + " --enumerate|-e enumerate known attributes and service " + "actions\n" + " --element=EA|-E EA EA is placed in 'element address' " + "field in\n" + " cdb [SMC-3] (def: 0)\n" + " --filter=FL|-f FL FL is parameter code to match (def: " + "-1 -> all)\n" + " --first=FAI|-F FAI FAI is placed in 'first attribute " + "identifier'\n" + " field in cdb (def: 0)\n" + " --help|-h print out usage message\n" + " --hex|-H output response in hexadecimal; used " + "twice\n" + " shows decoded values in hex\n" + " --in=FN|-i FN FN is a filename containing attribute " + "values in\n" + " ASCII hex or binary if --raw also " + "given\n" + " --lvn=LVN|-l LVN logical volume number (LVN) (def:0)\n" + " --maxlen=LEN|-m LEN max response length (allocation " + "length in cdb)\n" + " (def: 0 -> 8192 bytes)\n" + " --partition=PN|-p PN partition number (PN) (def:0)\n" + " --quiet|-q reduce the amount of output, can use " + "more than once\n" + " --raw|-r output response in binary\n" + " --readonly|-R open DEVICE read-only (def: read-write)\n" + " --sa=SA|-s SA SA is service action (def: 0)\n" + " --verbose|-v increase verbosity\n" + " --version|-V print version string and exit\n\n" + "Performs a SCSI READ ATTRIBUTE command. It is typically used " + "on tape\nsystems.\n"); +} + +/* Invokes a SCSI READ ATTRIBUTE command (SPC+SMC). Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +static int +sg_ll_read_attr(int sg_fd, void * resp, int * residp, + const struct opts_t * op) +{ + int k, ret, res, sense_cat; + int noisy = 1; + unsigned char raCmdBlk[SG_READ_ATTRIBUTE_CMDLEN] = + {SG_READ_ATTRIBUTE_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0}; + unsigned char sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + raCmdBlk[1] = 0x1f & op->sa; + if (op->ea) + sg_put_unaligned_be16(op->ea, raCmdBlk + 2); + if (op->lvn) + raCmdBlk[5] = 0xff & op->lvn; + if (op->pn) + raCmdBlk[7] = 0xff & op->pn; + if (op->fai) + sg_put_unaligned_be16(op->fai, raCmdBlk + 8); + sg_put_unaligned_be32((uint32_t)op->maxlen, raCmdBlk + 10); + if (op->cache) + raCmdBlk[14] |= 0x1; + if (op->verbose) { + pr2serr(" Read attribute cdb: "); + for (k = 0; k < SG_READ_ATTRIBUTE_CMDLEN; ++k) + pr2serr("%02x ", raCmdBlk[k]); + pr2serr("\n"); + } + + ptvp = construct_scsi_pt_obj(); + if (NULL == ptvp) { + pr2serr("%s: out of memory\n", __func__); + return -1; + } + set_scsi_pt_cdb(ptvp, raCmdBlk, sizeof(raCmdBlk)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (unsigned char *)resp, op->maxlen); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, op->verbose); + ret = sg_cmds_process_resp(ptvp, "read attribute", res, op->maxlen, + sense_b, noisy, op->verbose, &sense_cat); + if (-1 == ret) + ; + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + if (residp) + *residp = get_scsi_pt_resid(ptvp); + destruct_scsi_pt_obj(ptvp); + return ret; +} + +static void +dStrRaw(const char* str, int len) +{ + int k; + + for (k = 0 ; k < len; ++k) + printf("%c", str[k]); +} + +static int +find_sa_acron(const char * cp) +{ + int k; + const struct acron_nv_t * anvp; + const char * mp; + + for (anvp = sa_acron_arr; anvp->acron ; ++anvp) { + for (mp = cp, k = 0; *mp; ++mp, ++k) { + if (0 == anvp->acron[k]) + return anvp->val; + if (tolower(*mp) != anvp->acron[k]) + break; + } + if ((0 == *mp) && (0 == anvp->acron[k])) + return anvp->val; + } + return -1; /* not found */ +} + +const char * a_format[] = { + "binary", + "ascii", + "text", + "format[0x3]", +}; + +static void +enum_attributes(void) +{ + const struct attr_name_info_t * anip; + const char * cp; + char b[32]; + + printf("Attribute ID\tLength\tFormat\tName\n"); + printf("------------------------------------------\n"); + for (anip = attr_name_arr; anip->name ; ++anip) { + if (anip->format < 0) + snprintf(b, sizeof(b), "unknown"); + else + snprintf(b, sizeof(b), "%s", a_format[0x3 & anip->format]); + printf(" 0x%04x:\t%d\t%s\t", anip->id, anip->len, b); + cp = strchr(anip->name, '\t'); + if (cp ) { + printf("%.*s\n", (int)(cp - anip->name), anip->name); + printf("\t\t\t\t%s\n", cp + 1); + } else + printf("%s\n", anip->name); + } +} + +static void +enum_sa_acrons(void) +{ + const struct acron_nv_t * anvp; + + printf("SA_value\tAcronym\tDescription\n"); + printf("------------------------------------------\n"); + for (anvp = sa_acron_arr; anvp->acron ; ++anvp) + printf(" %d:\t\t%s\t%s\n", anvp->val, anvp->acron, anvp->name); +} + +/* Read ASCII hex bytes or binary from fname (a file named '-' taken as + * stdin). If reading ASCII hex then there should be either one entry per + * line or a comma, space or tab separated list of bytes. If no_space is + * set then a string of ACSII hex digits is expected, 2 per byte. Everything + * from and including a '#' on a line is ignored. Returns 0 if ok, or 1 if + * error. */ +static int +f2hex_arr(const char * fname, int as_binary, int no_space, + uint8_t * mp_arr, int * mp_arr_len, int max_arr_len) +{ + int fn_len, in_len, k, j, m, split_line, fd, has_stdin; + unsigned int h; + const char * lcp; + FILE * fp; + char line[512]; + char carry_over[4]; + int off = 0; + + if ((NULL == fname) || (NULL == mp_arr) || (NULL == mp_arr_len)) + return 1; + fn_len = strlen(fname); + if (0 == fn_len) + return 1; + has_stdin = ((1 == fn_len) && ('-' == fname[0])); /* read from stdin */ + if (as_binary) { + if (has_stdin) { + fd = STDIN_FILENO; + if (sg_set_binary_mode(STDIN_FILENO) < 0) + perror("sg_set_binary_mode"); + } else { + fd = open(fname, O_RDONLY); + if (fd < 0) { + pr2serr("unable to open binary file %s: %s\n", fname, + safe_strerror(errno)); + return 1; + } else if (sg_set_binary_mode(fd) < 0) + perror("sg_set_binary_mode"); + } + k = read(fd, mp_arr, max_arr_len); + if (k <= 0) { + if (0 == k) + pr2serr("read 0 bytes from binary file %s\n", fname); + else + pr2serr("read from binary file %s: %s\n", fname, + safe_strerror(errno)); + if (! has_stdin) + close(fd); + return 1; + } + *mp_arr_len = k; + if (! has_stdin) + close(fd); + return 0; + } else { /* So read the file as ASCII hex */ + if (has_stdin) + fp = stdin; + else { + fp = fopen(fname, "r"); + if (NULL == fp) { + pr2serr("Unable to open %s for reading\n", fname); + return 1; + } + } + } + + carry_over[0] = 0; + for (j = 0; j < 512; ++j) { + if (NULL == fgets(line, sizeof(line), fp)) + break; + in_len = strlen(line); + if (in_len > 0) { + if ('\n' == line[in_len - 1]) { + --in_len; + line[in_len] = '\0'; + split_line = 0; + } else + split_line = 1; + } + if (in_len < 1) { + carry_over[0] = 0; + continue; + } + if (carry_over[0]) { + if (isxdigit(line[0])) { + carry_over[1] = line[0]; + carry_over[2] = '\0'; + if (1 == sscanf(carry_over, "%4x", &h)) + mp_arr[off - 1] = h; /* back up and overwrite */ + else { + pr2serr("%s: carry_over error ['%s'] around line %d\n", + __func__, carry_over, j + 1); + goto bad; + } + lcp = line + 1; + --in_len; + } else + lcp = line; + carry_over[0] = 0; + } else + lcp = line; + + m = strspn(lcp, " \t"); + if (m == in_len) + continue; + lcp += m; + in_len -= m; + if ('#' == *lcp) + continue; + k = strspn(lcp, "0123456789aAbBcCdDeEfF ,\t"); + if ((k < in_len) && ('#' != lcp[k]) && ('\r' != lcp[k])) { + pr2serr("%s: syntax error at line %d, pos %d\n", __func__, + j + 1, m + k + 1); + goto bad; + } + if (no_space) { + for (k = 0; isxdigit(*lcp) && isxdigit(*(lcp + 1)); + ++k, lcp += 2) { + if (1 != sscanf(lcp, "%2x", &h)) { + pr2serr("%s: bad hex number in line %d, pos %d\n", + __func__, j + 1, (int)(lcp - line + 1)); + goto bad; + } + if ((off + k) >= max_arr_len) { + pr2serr("%s: array length exceeded\n", __func__); + goto bad; + } + mp_arr[off + k] = h; + } + if (isxdigit(*lcp) && (! isxdigit(*(lcp + 1)))) + carry_over[0] = *lcp; + off += k; + } else { + for (k = 0; k < 1024; ++k) { + if (1 == sscanf(lcp, "%4x", &h)) { + if (h > 0xff) { + pr2serr("%s: hex number larger than 0xff in line %d, " + "pos %d\n", __func__, j + 1, + (int)(lcp - line + 1)); + goto bad; + } + if (split_line && (1 == strlen(lcp))) { + /* single trailing hex digit might be a split pair */ + carry_over[0] = *lcp; + } + if ((off + k) >= max_arr_len) { + pr2serr("%s: array length exceeded\n", __func__); + goto bad; + } + mp_arr[off + k] = h; + lcp = strpbrk(lcp, " ,\t"); + if (NULL == lcp) + break; + lcp += strspn(lcp, " ,\t"); + if ('\0' == *lcp) + break; + } else { + if (('#' == *lcp) || ('\r' == *lcp)) { + --k; + break; + } + pr2serr("%s: error in line %d, at pos %d\n", __func__, + j + 1, (int)(lcp - line + 1)); + goto bad; + } + } + off += (k + 1); + } + } + *mp_arr_len = off; + if (stdin != fp) + fclose(fp); + return 0; +bad: + if (stdin != fp) + fclose(fp); + return 1; +} + +/* Returns 1 if 'ucp' all 0xff bytes, returns 2 is all 0xff bytes apart + * from last being 0xfe; otherwise returns 0. */ +static int +all_ffs_or_last_fe(const unsigned char * ucp, int len) +{ + for ( ; len > 0; ++ucp, --len) { + if (*ucp < 0xfe) + return 0; + if (0xfe == *ucp) + return (1 == len) ? 2 : 0; + + } + return 1; +} + +static char * +attr_id_lookup(unsigned int id, const struct attr_name_info_t ** anipp, + int blen, char * b) +{ + const struct attr_name_info_t * anip; + + for (anip = attr_name_arr; anip->name; ++anip) { + if (id == (unsigned int)anip->id) + break; + } + if (anip->name) { + snprintf(b, blen, "%s", anip->name); + if (anipp) + *anipp = anip; + return b; + } + if (anipp) + *anipp = NULL; + if (id < 0x400) + snprintf(b, blen, "Unknown device attribute 0x%x", id); + else if (id < 0x800) + snprintf(b, blen, "Unknown medium attribute 0x%x", id); + else if (id < 0xc00) + snprintf(b, blen, "Unknown host attribute 0x%x", id); + else if (id < 0x1000) + snprintf(b, blen, "Vendor specific device attribute 0x%x", id); + else if (id < 0x1400) + snprintf(b, blen, "Vendor specific medium attribute 0x%x", id); + else if (id < 0x1800) + snprintf(b, blen, "Vendor specific host attribute 0x%x", id); + else + snprintf(b, blen, "Reserved attribute 0x%x", id); + return b; +} + +static void +decode_attr_list(const unsigned char * alp, int len, bool supported, + const struct opts_t * op) +{ + int id; + char b[160]; + char * cp; + char * c2p; + const char * leadin = supported ? "Supported a" : "A"; + + if (op->verbose) + printf("%sttribute list: [len=%d]\n", leadin, len); + else if (0 == op->quiet) + printf("%sttribute list:\n", leadin); + if (op->do_hex) { + dStrHex((const char *)alp, len, 0); + return; + } + for ( ; len > 0; alp += 2, len -= 2) { + id = sg_get_unaligned_be16(alp + 0); + if ((op->filter >= 0) && (op->filter != id)) + continue; + if (op->verbose) + printf(" 0x%.4x:\t", id); + cp = attr_id_lookup(id, NULL, sizeof(b), b); + c2p = strchr(cp, '\t'); + if (c2p) { + printf(" %.*s -\n", (int)(c2p - cp), cp); + if (op->verbose) + printf("\t\t %s\n", c2p + 1); + else + printf(" %s\n", c2p + 1); + } else + printf(" %s\n", cp); + } +} + +static void +helper_full_attr(const unsigned char * alp, int len, int id, + const struct attr_name_info_t * anip, + const struct opts_t * op) +{ + int k; + const unsigned char * ucp; + + if (op->verbose) + printf("[r%c] ", (0x80 & alp[2]) ? 'o' : 'w'); + if (op->verbose > 3) + pr2serr("%s: id=0x%x, len=%d, anip->format=%d, anip->len=%d\n", + __func__, id, len, anip->format, anip->len); + switch (id) { + case 0x224: /* logical position of first encrypted block */ + k = all_ffs_or_last_fe(alp + 5, len - 5); + if (1 == k) + printf(" [ff]\n"); + else if (2 == k) + printf("\n"); + else { + if ((len - 5) <= 8) + printf("%" PRIx64, sg_get_unaligned_be(len - 5, alp + 5)); + else { + printf("\n"); + dStrHex((const char *)(alp + 5), len - 5, 0); + } + } + break; + case 0x225: /* logical position of first unencrypted block + * after first encrypted block */ + k = all_ffs_or_last_fe(alp + 5, len - 5); + if (1 == k) + printf(" [ff]\n"); + else if (2 == k) + printf("\n"); + else { + if ((len - 5) <= 8) + printf("%" PRIx64, sg_get_unaligned_be(len - 5, alp + 5)); + else { + printf("\n"); + dStrHex((const char *)(alp + 5), len - 5, 0); + } + } + break; + case 0x340: /* Medium Usage history */ + ucp = alp + 5; + printf("\n"); + if ((len - 5) < 90) { + pr2serr("%s: expected 90 bytes, got %d\n", __func__, len - 5); + break; + } + printf(" Current amount of data written [MiB]: %" PRIu64 "\n", + sg_get_unaligned_be48(ucp + 0)); + printf(" Current write retry count: %" PRIu64 "\n", + sg_get_unaligned_be48(ucp + 6)); + printf(" Current amount of data read [MiB]: %" PRIu64 "\n", + sg_get_unaligned_be48(ucp + 12)); + printf(" Current read retry count: %" PRIu64 "\n", + sg_get_unaligned_be48(ucp + 18)); + printf(" Previous amount of data written [MiB]: %" PRIu64 "\n", + sg_get_unaligned_be48(ucp + 24)); + printf(" Previous write retry count: %" PRIu64 "\n", + sg_get_unaligned_be48(ucp + 30)); + printf(" Previous amount of data read [MiB]: %" PRIu64 "\n", + sg_get_unaligned_be48(ucp + 36)); + printf(" Previous read retry count: %" PRIu64 "\n", + sg_get_unaligned_be48(ucp + 42)); + printf(" Total amount of data written [MiB]: %" PRIu64 "\n", + sg_get_unaligned_be48(ucp + 48)); + printf(" Total write retry count: %" PRIu64 "\n", + sg_get_unaligned_be48(ucp + 54)); + printf(" Total amount of data read [MiB]: %" PRIu64 "\n", + sg_get_unaligned_be48(ucp + 60)); + printf(" Total read retry count: %" PRIu64 "\n", + sg_get_unaligned_be48(ucp + 66)); + printf(" Load count: %" PRIu64 "\n", + sg_get_unaligned_be48(ucp + 72)); + printf(" Total change partition count: %" PRIu64 "\n", + sg_get_unaligned_be48(ucp + 78)); + printf(" Total partition initialization count: %" PRIu64 "\n", + sg_get_unaligned_be48(ucp + 84)); + break; + case 0x341: /* Partition Usage history */ + ucp = alp + 5; + printf("\n"); + if ((len - 5) < 60) { + pr2serr("%s: expected 60 bytes, got %d\n", __func__, len - 5); + break; + } + printf(" Current amount of data written [MiB]: %" PRIu32 "\n", + sg_get_unaligned_be32(ucp + 0)); + printf(" Current write retry count: %" PRIu32 "\n", + sg_get_unaligned_be32(ucp + 4)); + printf(" Current amount of data read [MiB]: %" PRIu32 "\n", + sg_get_unaligned_be32(ucp + 8)); + printf(" Current read retry count: %" PRIu32 "\n", + sg_get_unaligned_be32(ucp + 12)); + printf(" Previous amount of data written [MiB]: %" PRIu32 "\n", + sg_get_unaligned_be32(ucp + 16)); + printf(" Previous write retry count: %" PRIu32 "\n", + sg_get_unaligned_be32(ucp + 20)); + printf(" Previous amount of data read [MiB]: %" PRIu32 "\n", + sg_get_unaligned_be32(ucp + 24)); + printf(" Previous read retry count: %" PRIu32 "\n", + sg_get_unaligned_be32(ucp + 28)); + printf(" Total amount of data written [MiB]: %" PRIu32 "\n", + sg_get_unaligned_be32(ucp + 32)); + printf(" Total write retry count: %" PRIu32 "\n", + sg_get_unaligned_be32(ucp + 36)); + printf(" Total amount of data read [MiB]: %" PRIu32 "\n", + sg_get_unaligned_be32(ucp + 40)); + printf(" Total read retry count: %" PRIu32 "\n", + sg_get_unaligned_be32(ucp + 44)); + printf(" Load count: %" PRIu32 "\n", + sg_get_unaligned_be32(ucp + 48)); + printf(" change partition count: %" PRIu32 "\n", + sg_get_unaligned_be32(ucp + 52)); + printf(" partition initialization count: %" PRIu32 "\n", + sg_get_unaligned_be32(ucp + 56)); + break; + default: + pr2serr("%s: unknown attribute id: 0x%x\n", __func__, id); + printf(" In hex:\n"); + dStrHex((const char *)alp, len, 0); + break; + } +} + +static void +decode_attr_vals(const unsigned char * alp, int len, const struct opts_t * op) +{ + int bump, id, alen; + uint64_t ull; + char * cp; + char * c2p; + const struct attr_name_info_t * anip; + char b[160]; + + if (op->verbose) + printf("Attribute values: [len=%d]\n", len); + else if (op->filter < 0) { + if (0 == op->quiet) + printf("Attribute values:\n"); + if (op->do_hex) { /* only expect -HH to get through here */ + dStrHex((const char *)alp, len, 0); + return; + } + } + for ( ; len > 4; alp += bump, len -= bump) { + id = sg_get_unaligned_be16(alp + 0); + bump = sg_get_unaligned_be16(alp + 3) + 5; + alen = bump - 5; + if ((op->filter >= 0) && (op->filter != id)) { + if (id < op->filter) + continue; + else + break; /* Assume array is ascending id order */ + } + anip = NULL; + cp = attr_id_lookup(id, &anip, sizeof(b), b); + if (op->quiet < 2) { + c2p = strchr(cp, '\t'); + if (c2p) { + printf(" %.*s -\n", (int)(c2p - cp), cp); + printf(" %s: ", c2p + 1); + } else + printf(" %s: ", cp); + } + if (op->verbose) + printf("[r%c] ", (0x80 & alp[2]) ? 'o' : 'w'); + if (anip) { + if ((RA_FMT_BINARY == anip->format) && (bump <= 13)) { + ull = sg_get_unaligned_be(alen, alp + 5); + if (0 == anip->process) + printf("%" PRIu64 "\n", ull); + else if (1 == anip->process) + printf("0x%" PRIx64 "\n", ull); + else + helper_full_attr(alp, bump, id, anip, op); + if (op->verbose) { + if ((anip->len > 0) && (alen > 0) && (alen != anip->len)) + printf(" <<< T10 length (%d) differs from length in " + "response (%d) >>>\n", anip->len, alen); + } + } else if (RA_FMT_BINARY == anip->format) { + if (2 == anip->process) + helper_full_attr(alp, bump, id, anip, op); + else { + printf("\n"); + dStrHex((const char *)(alp + 5), alen, 0); + } + } else { + if (2 == anip->process) + helper_full_attr(alp, bump, id, anip, op); + else { + printf("%.*s\n", alen, alp + 5); + if (op->verbose) { + if ((anip->len > 0) && (alen > 0) && + (alen != anip->len)) + printf(" <<< T10 length (%d) differs from length " + "in response (%d) >>>\n", anip->len, alen); + } + } + } + } else { + if (op->verbose > 1) + printf("Attribute id lookup failed, in hex:\n"); + else + printf("\n"); + dStrHex((const char *)(alp + 5), alen, 0); + } + } + if (op->verbose && (len > 0) && (len <= 4)) + pr2serr("warning: iterate of attributes should end a residual of " + "%d\n", len); +} + +static void +decode_all_sa_s(const unsigned char * rabp, int len, const struct opts_t * op) +{ + if (op->do_hex && (2 != op->do_hex)) { + dStrHex((const char *)rabp, len, ((1 == op->do_hex) ? 1 : -1)); + return; + } + switch (op->sa) { + case RA_ATTR_VAL_SA: + decode_attr_vals(rabp + 4, len - 4, op); + break; + case RA_ATTR_LIST_SA: + decode_attr_list(rabp + 4, len - 4, false, op); + break; + case RA_LV_LIST_SA: + if ((0 == op->quiet) || op->verbose) + printf("Logical volume list:\n"); + if (len < 4) { + pr2serr(">>> response length unexpectedly short: %d bytes\n", + len); + break; + } + printf(" First logical volume number: %u\n", rabp[2]); + printf(" Number of logical volumes available: %u\n", rabp[3]); + break; + case RA_PART_LIST_SA: + if ((0 == op->quiet) || op->verbose) + printf("Partition number list:\n"); + if (len < 4) { + pr2serr(">>> response length unexpectedly short: %d bytes\n", + len); + break; + } + printf(" First partition number: %u\n", rabp[2]); + printf(" Number of partitions available: %u\n", rabp[3]); + break; + case RA_SMC2_SA: + printf("Used by SMC-2, not information, output in hex:\n"); + dStrHex((const char *)rabp, len, 0); + break; + case RA_SUP_ATTR_SA: + decode_attr_list(rabp + 4, len - 4, true, op); + break; + default: + printf("Unrecognized service action [0x%x], response in hex:\n", + op->sa); + dStrHex((const char *)rabp, len, 0); + break; + } +} + +int +main(int argc, char * argv[]) +{ + int sg_fd, res, c, len, resid, rlen, in_len; + unsigned int ra_len; + int ret = 0; + const char * device_name = NULL; + const char * fname = NULL; + unsigned char * rabp = NULL; + struct opts_t opts; + struct opts_t * op; + char b[80]; + + op = &opts; + memset(op, 0, sizeof(opts)); + op->filter = -1; + while (1) { + int option_index = 0; + + c = getopt_long(argc, argv, "ceE:f:F:hHi:l:m:p:qrRs:vV", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'c': + ++op->cache; + break; + case 'e': + ++op->enumerate; + break; + case 'E': + op->ea = sg_get_num(optarg); + if ((op->ea < 0) || (op->ea > 65535)) { + pr2serr("bad argument to '--ea=EA', expect 0 to 65535\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'f': + op->filter = sg_get_num(optarg); + if ((op->filter < -3) || (op->filter > 65535)) { + pr2serr("bad argument to '--filter=FL', expect -3 to " + "65535\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'F': + op->fai = sg_get_num(optarg); + if ((op->fai < 0) || (op->fai > 65535)) { + pr2serr("bad argument to '--first=FAI', expect 0 to 65535\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'h': + case '?': + usage(); + return 0; + case 'H': + ++op->do_hex; + break; + case 'i': + fname = optarg; + break; + case 'l': + op->lvn = sg_get_num(optarg); + if ((op->lvn < 0) || (op->lvn > 255)) { + pr2serr("bad argument to '--lvn=LVN', expect 0 to 255\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'm': + op->maxlen = sg_get_num(optarg); + if ((op->maxlen < 0) || (op->maxlen > MAX_RATTR_BUFF_LEN)) { + pr2serr("argument to '--maxlen' should be %d or " + "less\n", MAX_RATTR_BUFF_LEN); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'p': + op->pn = sg_get_num(optarg); + if ((op->pn < 0) || (op->pn > 255)) { + pr2serr("bad argument to '--pn=PN', expect 0 to 255\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'q': + ++op->quiet; + break; + case 'r': + ++op->do_raw; + break; + case 'R': + ++op->o_readonly; + break; + case 's': + if (isdigit(*optarg)) { + op->sa = sg_get_num(optarg); + if ((op->sa < 0) || (op->sa > 63)) { + pr2serr("bad argument to '--sa=SA', expect 0 to 63\n"); + return SG_LIB_SYNTAX_ERROR; + } + } else { + res = find_sa_acron(optarg); + if (res < 0) { + enum_sa_acrons(); + return SG_LIB_SYNTAX_ERROR; + } + op->sa = res; + } + break; + case 'v': + ++op->verbose; + break; + case 'V': + pr2serr("version: %s\n", version_str); + return 0; + default: + pr2serr("unrecognised option code 0x%x ??\n", c); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + } + if (optind < argc) { + if (NULL == device_name) { + device_name = argv[optind]; + ++optind; + } + if (optind < argc) { + for (; optind < argc; ++optind) + pr2serr("Unexpected extra argument: %s\n", argv[optind]); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + } + + if (op->enumerate) { + enum_attributes(); + printf("\n"); + enum_sa_acrons(); + return 0; + } + + if (fname && device_name) { + pr2serr("since '--in=FN' given, ignoring DEVICE\n"); + device_name = NULL; + } + + if (0 == op->maxlen) + op->maxlen = DEF_RATTR_BUFF_LEN; + rabp = (unsigned char *)calloc(1, op->maxlen); + if (NULL == rabp) { + pr2serr("unable to calloc %d bytes\n", op->maxlen); + return SG_LIB_CAT_OTHER; + } + + if (NULL == device_name) { + if (fname) { + if (f2hex_arr(fname, op->do_raw, 0, rabp, &in_len, op->maxlen)) + return SG_LIB_FILE_ERROR; + if (op->do_raw) + op->do_raw = 0; /* can interfere on decode */ + if (in_len < 4) { + pr2serr("--in=%s only decoded %d bytes (needs 4 at least)\n", + fname, in_len); + return SG_LIB_SYNTAX_ERROR; + } + decode_all_sa_s(rabp, in_len, op); + goto clean_up; + } + pr2serr("missing device name!\n"); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + + if (op->do_raw) { + if (sg_set_binary_mode(STDOUT_FILENO) < 0) { + perror("sg_set_binary_mode"); + return SG_LIB_FILE_ERROR; + } + } + + sg_fd = sg_cmds_open_device(device_name, op->o_readonly, op->verbose); + if (sg_fd < 0) { + pr2serr("open error: %s: %s\n", device_name, + safe_strerror(-sg_fd)); + return SG_LIB_FILE_ERROR; + } + + res = sg_ll_read_attr(sg_fd, rabp, &resid, op); + ret = res; + if (0 == res) { + rlen = op->maxlen - resid; + if (rlen < 4) { + pr2serr("Response length (%d) too short\n", rlen); + ret = SG_LIB_CAT_MALFORMED; + goto close_then_end; + } + if ((op->sa <= RA_HIGHEST_SA) && (op->sa != RA_SMC2_SA)) { + ra_len = ((RA_LV_LIST_SA == op->sa) || + (RA_PART_LIST_SA == op->sa)) ? + (unsigned int)sg_get_unaligned_be16(rabp + 0) : + sg_get_unaligned_be32(rabp + 0) + 2; + ra_len += 2; + } else + ra_len = rlen; + if ((int)ra_len > rlen) { + if (op->verbose) + pr2serr("ra_len available is %d, response length is %d\n", + ra_len, rlen); + len = rlen; + } else + len = (int)ra_len; + if (op->do_raw) { + dStrRaw((const char *)rabp, len); + goto close_then_end; + } + decode_all_sa_s(rabp, len, op); + } else if (SG_LIB_CAT_INVALID_OP == res) + pr2serr("Read attribute command not supported\n"); + else { + sg_get_category_sense_str(res, sizeof(b), b, op->verbose); + pr2serr("Read attribute command: %s\n", b); + } + +close_then_end: + res = sg_cmds_close_device(sg_fd); + if (res < 0) { + pr2serr("close error: %s\n", safe_strerror(-res)); + if (0 == ret) + ret = SG_LIB_FILE_ERROR; + } +clean_up: + if (rabp) + free(rabp); + return (ret >= 0) ? ret : SG_LIB_CAT_OTHER; +} -- cgit v1.2.3