/* * Copyright (c) 2016-2021 Douglas Gilbert. * All rights reserved. * Use of this source code is governed by a BSD-style * license that can be found in the BSD_LICENSE file. * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #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.16 20211114"; #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 { bool cache; bool enumerate; bool do_raw; bool o_readonly; bool verbose_given; bool version_given; int elem_addr; int filter; int fai; int do_hex; int lvn; int maxlen; int pn; int quiet; 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. Even though it is " "defined in\nSPC-3 and later it is typically used on tape " "systems.\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, bool noisy, const struct opts_t * op) { int ret, res, sense_cat; uint8_t ra_cdb[SG_READ_ATTRIBUTE_CMDLEN] = {SG_READ_ATTRIBUTE_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT; struct sg_pt_base * ptvp; ra_cdb[1] = 0x1f & op->sa; if (op->elem_addr) sg_put_unaligned_be16(op->elem_addr, ra_cdb + 2); if (op->lvn) ra_cdb[5] = 0xff & op->lvn; if (op->pn) ra_cdb[7] = 0xff & op->pn; if (op->fai) sg_put_unaligned_be16(op->fai, ra_cdb + 8); sg_put_unaligned_be32((uint32_t)op->maxlen, ra_cdb + 10); if (op->cache) ra_cdb[14] |= 0x1; if (op->verbose) { char b[128]; pr2serr(" Read attribute cdb: %s\n", sg_get_command_str(ra_cdb, SG_READ_ATTRIBUTE_CMDLEN, false, sizeof(b), b)); } ptvp = construct_scsi_pt_obj(); if (NULL == ptvp) { pr2serr("%s: out of memory\n", __func__); return -1; } set_scsi_pt_cdb(ptvp, ra_cdb, sizeof(ra_cdb)); set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); set_scsi_pt_data_in(ptvp, (uint8_t *)resp, op->maxlen); res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, op->verbose); ret = sg_cmds_process_resp(ptvp, "read attribute", res, noisy, op->verbose, &sense_cat); if (-1 == ret) { if (get_scsi_pt_transport_err(ptvp)) ret = SG_LIB_TRANSPORT_ERROR; else ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); } 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((uint8_t)*mp) != (uint8_t)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); } /* Returns 1 if 'bp' 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 uint8_t * bp, int len) { for ( ; len > 0; ++bp, --len) { if (*bp < 0xfe) return 0; if (0xfe == *bp) 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 uint8_t * 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) { hex2stdout(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 uint8_t * alp, int len, int id, const struct attr_name_info_t * anip, const struct opts_t * op) { int k; const uint8_t * bp; 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"); hex2stdout((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"); hex2stdout(alp + 5, len - 5, 0); } } break; case 0x340: /* Medium Usage history */ bp = 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(bp + 0)); printf(" Current write retry count: %" PRIu64 "\n", sg_get_unaligned_be48(bp + 6)); printf(" Current amount of data read [MiB]: %" PRIu64 "\n", sg_get_unaligned_be48(bp + 12)); printf(" Current read retry count: %" PRIu64 "\n", sg_get_unaligned_be48(bp + 18)); printf(" Previous amount of data written [MiB]: %" PRIu64 "\n", sg_get_unaligned_be48(bp + 24)); printf(" Previous write retry count: %" PRIu64 "\n", sg_get_unaligned_be48(bp + 30)); printf(" Previous amount of data read [MiB]: %" PRIu64 "\n", sg_get_unaligned_be48(bp + 36)); printf(" Previous read retry count: %" PRIu64 "\n", sg_get_unaligned_be48(bp + 42)); printf(" Total amount of data written [MiB]: %" PRIu64 "\n", sg_get_unaligned_be48(bp + 48)); printf(" Total write retry count: %" PRIu64 "\n", sg_get_unaligned_be48(bp + 54)); printf(" Total amount of data read [MiB]: %" PRIu64 "\n", sg_get_unaligned_be48(bp + 60)); printf(" Total read retry count: %" PRIu64 "\n", sg_get_unaligned_be48(bp + 66)); printf(" Load count: %" PRIu64 "\n", sg_get_unaligned_be48(bp + 72)); printf(" Total change partition count: %" PRIu64 "\n", sg_get_unaligned_be48(bp + 78)); printf(" Total partition initialization count: %" PRIu64 "\n", sg_get_unaligned_be48(bp + 84)); break; case 0x341: /* Partition Usage history */ bp = 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(bp + 0)); printf(" Current write retry count: %" PRIu32 "\n", sg_get_unaligned_be32(bp + 4)); printf(" Current amount of data read [MiB]: %" PRIu32 "\n", sg_get_unaligned_be32(bp + 8)); printf(" Current read retry count: %" PRIu32 "\n", sg_get_unaligned_be32(bp + 12)); printf(" Previous amount of data written [MiB]: %" PRIu32 "\n", sg_get_unaligned_be32(bp + 16)); printf(" Previous write retry count: %" PRIu32 "\n", sg_get_unaligned_be32(bp + 20)); printf(" Previous amount of data read [MiB]: %" PRIu32 "\n", sg_get_unaligned_be32(bp + 24)); printf(" Previous read retry count: %" PRIu32 "\n", sg_get_unaligned_be32(bp + 28)); printf(" Total amount of data written [MiB]: %" PRIu32 "\n", sg_get_unaligned_be32(bp + 32)); printf(" Total write retry count: %" PRIu32 "\n", sg_get_unaligned_be32(bp + 36)); printf(" Total amount of data read [MiB]: %" PRIu32 "\n", sg_get_unaligned_be32(bp + 40)); printf(" Total read retry count: %" PRIu32 "\n", sg_get_unaligned_be32(bp + 44)); printf(" Load count: %" PRIu32 "\n", sg_get_unaligned_be32(bp + 48)); printf(" change partition count: %" PRIu32 "\n", sg_get_unaligned_be32(bp + 52)); printf(" partition initialization count: %" PRIu32 "\n", sg_get_unaligned_be32(bp + 56)); break; default: pr2serr("%s: unknown attribute id: 0x%x\n", __func__, id); printf(" In hex:\n"); hex2stdout(alp, len, 0); break; } } static void decode_attr_vals(const uint8_t * 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 */ hex2stdout(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"); hex2stdout(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"); hex2stdout(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 uint8_t * rabp, int len, const struct opts_t * op) { if (op->do_hex && (2 != op->do_hex)) { hex2stdout(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"); hex2stdout(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); hex2stdout(rabp, len, 0); break; } } int main(int argc, char * argv[]) { int sg_fd, res, c, len, resid, rlen; unsigned int ra_len; int in_len = 0; int ret = 0; const char * device_name = NULL; const char * fname = NULL; uint8_t * rabp = NULL; uint8_t * free_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 = true; break; case 'e': op->enumerate = true; break; case 'E': op->elem_addr = sg_get_num(optarg); if ((op->elem_addr < 0) || (op->elem_addr > 65535)) { pr2serr("bad argument to '--element=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 = true; break; case 'R': op->o_readonly = true; break; case 's': if (isdigit((uint8_t)*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_given = true; ++op->verbose; break; case 'V': op->version_given = true; break; default: pr2serr("unrecognised option code 0x%x ??\n", c); usage(); return SG_LIB_SYNTAX_ERROR; } } if (optind < argc) { if (NULL == 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; } } #ifdef DEBUG pr2serr("In DEBUG mode, "); if (op->verbose_given && op->version_given) { pr2serr("but override: '-vV' given, zero verbose and continue\n"); op->verbose_given = false; op->version_given = false; op->verbose = 0; } else if (! op->verbose_given) { pr2serr("set '-vv'\n"); op->verbose = 2; } else pr2serr("keep verbose=%d\n", op->verbose); #else if (op->verbose_given && op->version_given) pr2serr("Not in DEBUG mode, so '-vV' has no special action\n"); #endif if (op->version_given) { pr2serr("version: %s\n", version_str); return 0; } 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 = (uint8_t *)sg_memalign(op->maxlen, 0, &free_rabp, op->verbose > 3); if (NULL == rabp) { pr2serr("unable to sg_memalign %d bytes\n", op->maxlen); return sg_convert_errno(ENOMEM); } if (NULL == device_name) { if (fname) { if ((ret = sg_f2hex_arr(fname, op->do_raw, false /* no space */, rabp, &in_len, op->maxlen))) goto clean_up; if (op->do_raw) op->do_raw = false; /* can interfere on decode */ if (in_len < 4) { pr2serr("--in=%s only decoded %d bytes (needs 4 at least)\n", fname, in_len); ret = SG_LIB_SYNTAX_ERROR; goto clean_up; } decode_all_sa_s(rabp, in_len, op); goto clean_up; } pr2serr("missing device name!\n"); usage(); ret = SG_LIB_SYNTAX_ERROR; goto clean_up; } if (op->do_raw) { if (sg_set_binary_mode(STDOUT_FILENO) < 0) { perror("sg_set_binary_mode"); ret = SG_LIB_FILE_ERROR; goto clean_up; } } 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)); ret = sg_convert_errno(-sg_fd); goto clean_up; } res = sg_ll_read_attr(sg_fd, rabp, &resid, op->verbose > 0, 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_convert_errno(-res); } clean_up: if (free_rabp) free(free_rabp); if (0 == op->verbose) { if (! sg_if_can2stderr("sg_read_attr failed: ", ret)) pr2serr("Some error occurred, try again with '-v' or '-vv' for " "more information\n"); } return (ret >= 0) ? ret : SG_LIB_CAT_OTHER; }