diff options
Diffstat (limited to 'src/sg_modes.c')
-rw-r--r-- | src/sg_modes.c | 1579 |
1 files changed, 1579 insertions, 0 deletions
diff --git a/src/sg_modes.c b/src/sg_modes.c new file mode 100644 index 00000000..0be40ee4 --- /dev/null +++ b/src/sg_modes.c @@ -0,0 +1,1579 @@ +/* + * Copyright (C) 2000-2022 D. Gilbert + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This program outputs information provided by a SCSI MODE SENSE command. + * Does 10 byte MODE SENSE commands by default, Trent Piepho added a "-6" + * switch for force 6 byte mode sense commands. + * This utility cannot modify mode pages. See the sdparm utility for that. + */ + +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdbool.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <getopt.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "sg_lib.h" +#include "sg_cmds_basic.h" +#include "sg_unaligned.h" +#include "sg_pr2serr.h" + +static const char * version_str = "1.75 20220202"; + +#define DEF_ALLOC_LEN (1024 * 4) +#define DEF_6_ALLOC_LEN 252 +#define UNLIKELY_ABOVE_LEN 512 +#define PG_CODE_ALL 0x3f +#define PG_CODE_MASK 0x3f +#define PG_CODE_MAX 0x3f +#define SPG_CODE_ALL 0xff +#define PROTO_SPECIFIC_1 0x18 +#define PROTO_SPECIFIC_2 0x19 + +#define EBUFF_SZ 256 + + +struct opts_t { + bool do_dbd; + bool do_dbout; + bool do_examine; + bool do_flexible; + bool do_list; + bool do_llbaa; + bool do_six; + bool o_readwrite; + bool subpg_code_given; + bool opt_new; + bool verbose_given; + bool version_given; + int do_all; + int do_help; + int do_hex; + int maxlen; + int do_raw; + int verbose; + int page_control; + int pg_code; + int subpg_code; + const char * device_name; + const char * page_acron; +}; + +struct page_code_desc { + int page_code; + int subpage_code; + const char * acron; + const char * desc; +}; + +struct pc_desc_group { + struct page_code_desc * pcdp; + const char * group_name; +}; + +static struct option long_options[] = { + {"all", no_argument, 0, 'a'}, + {"control", required_argument, 0, 'c'}, + {"dbd", no_argument, 0, 'd'}, + {"dbout", no_argument, 0, 'D'}, + {"examine", no_argument, 0, 'e'}, + {"flexible", no_argument, 0, 'f'}, + {"help", no_argument, 0, 'h'}, + {"hex", no_argument, 0, 'H'}, + {"list", no_argument, 0, 'l'}, + {"llbaa", no_argument, 0, 'L'}, + {"maxlen", required_argument, 0, 'm'}, + {"new", no_argument, 0, 'N'}, + {"old", no_argument, 0, 'O'}, + {"page", required_argument, 0, 'p'}, + {"raw", no_argument, 0, 'r'}, + {"read-write", no_argument, 0, 'w'}, + {"read_write", no_argument, 0, 'w'}, + {"readwrite", no_argument, 0, 'w'}, + {"six", no_argument, 0, '6'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'V'}, + {0, 0, 0, 0}, +}; + +/* Common to all SCSI devices (found in SPCx). In numerical order */ +static struct page_code_desc pc_desc_common[] = { + {0x0, 0x0, "ua", "Unit Attention condition [vendor specific format]"}, + {0x2, 0x0, "dr", "Disconnect-Reconnect"}, + {0x9, 0x0, "pd", "Peripheral device (obsolete)"}, + {0xa, 0x0, "co", "Control"}, + {0xa, 0x1, "coe", "Control extension"}, + {0xa, 0x3, "cdla", "Command duration limit A"}, + {0xa, 0x4, "cdlb", "Command duration limit B"}, + {0xa, 0x7, "cdt2a", "Command duration limit T2A"}, /* spc6r01 */ + {0xa, 0x8, "cdt2b", "Command duration limit T2B"}, /* spc6r01 */ + {0x15, 0x0, "ext_", "Extended"}, + {0x16, 0x0, "edts", "Extended device-type specific"}, + {0x18, 0x0, "pslu", "Protocol specific lu"}, + {0x19, 0x0, "pspo", "Protocol specific port"}, + {0x1a, 0x0, "po", "Power condition"}, + {0x1a, 0x1, "ps", "Power consumption"}, + {0x1c, 0x0, "ie", "Informational exceptions control"}, + {PG_CODE_ALL, 0x0, "asmp", "[yields all supported pages]"}, + {PG_CODE_ALL, SPG_CODE_ALL,"asmsp", + "[yields all supported pages and subpages]"}, + {0x0, 0x0, NULL, NULL}, +}; + +static struct page_code_desc pc_desc_disk[] = { + {0x1, 0x0, "rw", "Read-Write error recovery"}, + {0x3, 0x0, "fo", "Format (obsolete)"}, + {0x4, 0x0, "rd", "Rigid disk geometry (obsolete)"}, + {0x5, 0x0, "fd", "Flexible disk (obsolete)"}, + {0x7, 0x0, "ve", "Verify error recovery"}, + {0x8, 0x0, "ca", "Caching"}, + {0xa, 0x2, "atag", "Application tag"}, + {0xa, 0x5, "ioad", "IO advice hints grouping"}, /* added sbc4r06 */ + {0xa, 0x6, "bop", "Background operation control"}, /* added sbc4r07 */ + {0xa, 0xf1, "pat", "Parallel ATA control (SAT)"}, + {0xa, 0xf2, "afc", "ATA feature control (SAT)"}, /* added 20-085r2 */ + {0xb, 0x0, "mts", "Medium types supported (obsolete)"}, + {0xc, 0x0, "not", "Notch and partition (obsolete)"}, + {0xd, 0x0, "pco", "Power condition (obsolete, moved to 0x1a)"}, + {0x10, 0x0, "xo", "XOR control"}, /* obsolete in sbc3r32 */ + {0x1a, 0xf1, "apo", "ATA Power condition"}, + {0x1c, 0x1, "bc", "Background control"}, + {0x1c, 0x2, "lbp", "Logical block provisioning"}, + {0x0, 0x0, NULL, NULL}, +}; + +static struct page_code_desc pc_desc_tape[] = { + {0x1, 0x0, "rw", "Read-Write error recovery"}, + {0xa, 0xf0, "cdp", "Control data protection"}, + {0xf, 0x0, "dac", "Data Compression"}, + {0x10, 0x0, "dc", "Device configuration"}, + {0x10, 0x1, "dcs", "Device configuration extension"}, + {0x11, 0x0, "mpa", "Medium Partition [1]"}, + {0x12, 0x0, "mpa2", "Medium Partition [2]"}, + {0x13, 0x0, "mpa3", "Medium Partition [3]"}, + {0x14, 0x0, "mpar", "Medium Partition [4]"}, + {0x1c, 0x0, "ie", "Informational exceptions control (tape version)"}, + {0x1d, 0x0, "mco", "Medium configuration"}, + {0x0, 0x0, NULL, NULL}, +}; + +static struct page_code_desc pc_desc_cddvd[] = { + {0x1, 0x0, "rw", "Read-Write error recovery"}, + {0x3, 0x0, "mrw", "Mount Rainer rewritable"}, + {0x5, 0x0, "wp", "Write parameters"}, + {0x7, 0x0, "ve", "Verify error recovery"}, + {0x8, 0x0, "ca", "Caching"}, + {0xd, 0x0, "cddp", "CD device parameters (obsolete)"}, + {0xe, 0x0, "cda", "CD audio"}, + {0x1a, 0x0, "po", "Power condition (mmc)"}, + {0x1c, 0x0, "ffrc", "Fault/failure reporting control (mmc)"}, + {0x1d, 0x0, "tp", "Timeout and protect"}, + {0x2a, 0x0, "cms", "MM capabilities and mechanical status (obsolete)"}, + {0x0, 0x0, NULL, NULL}, +}; + +static struct page_code_desc pc_desc_smc[] = { + {0x1d, 0x0, "eaa", "Element address assignment"}, + {0x1e, 0x0, "tgp", "Transport geometry parameters"}, + {0x1f, 0x0, "dcs", "Device capabilities"}, + {0x1f, 0x41, "edc", "Extended device capabilities"}, + {0x0, 0x0, NULL, NULL}, +}; + +static struct page_code_desc pc_desc_scc[] = { + {0x1b, 0x0, "sslm", "LUN mapping"}, + {0x0, 0x0, NULL, NULL}, +}; + +static struct page_code_desc pc_desc_ses[] = { + {0x14, 0x0, "esm", "Enclosure services management"}, + {0x0, 0x0, NULL, NULL}, +}; + +static struct page_code_desc pc_desc_rbc[] = { + {0x6, 0x0, "rbc", "RBC device parameters"}, + {0x0, 0x0, NULL, NULL}, +}; + +static struct page_code_desc pc_desc_adc[] = { + /* {0xe, 0x0, "ADC device configuration"}, */ + {0xe, 0x1, "adtd", "Target device"}, + {0xe, 0x2, "addp", "DT device primary port"}, + {0xe, 0x3, "adlu", "Logical unit"}, + {0xe, 0x4, "adts", "Target device serial number"}, + {0x0, 0x0, NULL, NULL}, +}; + + +/* Transport reated mode pages */ +static struct page_code_desc pc_desc_t_fcp[] = { + {0x18, 0x0, "pl", "LU control"}, + {0x19, 0x0, "pp", "Port control"}, + {0x0, 0x0, NULL, NULL}, +}; + +static struct page_code_desc pc_desc_t_spi4[] = { + {0x18, 0x0, "luc", "LU control"}, + {0x19, 0x0, "pp", "Port control short format"}, + {0x19, 0x1, "mc", "Margin control"}, + {0x19, 0x2, "stc", "Saved training configuration value"}, + {0x19, 0x3, "ns", "Negotiated settings"}, + {0x19, 0x4, "rtc", "Report transfer capabilities"}, + {0x0, 0x0, NULL, NULL}, +}; + +/* SAS protocol layers now in SPL standards */ +static struct page_code_desc pc_desc_t_sas[] = { + {0x18, 0x0, "pslu", "Protocol specific logical unit (SPL)"}, + {0x19, 0x0, "pspo", "Protocol specific port (SPL)"}, + {0x19, 0x1, "pcd", "Phy control and discover (SPL)"}, + {0x19, 0x2, "spc", "Shared port control (SPL)"}, + {0x19, 0x3, "sep", "Enhanced phy control (SPL)"}, + {0x19, 0x4, "oobm", "Out of band management control (SPL)"}, /* spl5r01 */ + {0x0, 0x0, NULL, NULL}, +}; + +static struct page_code_desc pc_desc_t_adc[] = { + {0xe, 0x1, "addt", "Target device"}, + {0xe, 0x2, "addp", "DT device primary port"}, + {0xe, 0x3, "adlu", "Logical unit"}, + {0x18, 0x0, "pslu", "Protocol specific lu"}, + {0x19, 0x0, "pspo", "Protocol specific port"}, + {0x0, 0x0, NULL, NULL}, +}; + +static struct page_code_desc pc_desc_zbc[] = { + {0x1, 0x0, "rw", "Read-Write error recovery"}, + {0x7, 0x0, "ve", "Verify error recovery"}, + {0x8, 0x0, "ca", "Caching"}, + {0xa, 0x2, "atag", "Application tag"}, + {0xa, 0xf, "zbdct", "Zoned block device control"}, /* zbc2r04a */ + {0x1c, 0x1, "bc", "Background control"}, + {0x0, 0x0, NULL, NULL}, +}; + +struct pc_desc_group pcd_gr_arr[] = { + {pc_desc_common, "common"}, + {pc_desc_disk, "disk"}, + {pc_desc_tape, "tape"}, + {pc_desc_cddvd, "cd/dvd"}, + {pc_desc_smc, "media changer"}, + {pc_desc_scc, "scsi controller"}, + {pc_desc_ses, "enclosure"}, + {pc_desc_rbc, "reduced block"}, + {pc_desc_adc, "adc"}, + {pc_desc_zbc, "zbc"}, + {pc_desc_t_fcp, "transport: FCP"}, + {pc_desc_t_spi4, "transport: SPI"}, + {pc_desc_t_sas, "transport: SAS"}, + {pc_desc_t_adc, "transport: ADC"}, + + {NULL, NULL}, +}; + + + +static void +usage() +{ + printf("Usage: sg_modes [--all] [--control=PC] [--dbd] [--dbout] " + "[--examine]\n" + " [--flexible] [--help] [--hex] [--list] " + "[--llbaa]\n" + " [--maxlen=LEN] [--page=PG[,SPG]] [--raw] [-R] " + "[--readwrite]\n" + " [--six] [--verbose] [--version] [DEVICE]\n" + " where:\n" + " --all|-a get all mode pages supported by device\n" + " use twice to get all mode pages and subpages\n" + " --control=PC|-c PC page control (default: 0)\n" + " 0: current, 1: changeable,\n" + " 2: (manufacturer's) defaults, 3: saved\n" + " --dbd|-d disable block descriptors (DBD field in cdb)\n" + " --dbout|-D disable block descriptor output\n" + " --examine|-e examine pages # 0 through to 0x3e, note if " + "found\n" + " --flexible|-f be flexible, cope with MODE SENSE 6/10 " + "response mixup\n"); + printf(" --help|-h print usage message then exit\n" + " --hex|-H output full response in hex\n" + " use twice to output page number and header " + "in hex\n" + " --list|-l list common page codes for device peripheral " + "type,\n" + " if no device given then assume disk type\n" + " --llbaa|-L set Long LBA Accepted (LLBAA field in mode " + "sense (10) cdb)\n" + " --maxlen=LEN|-m LEN max response length (allocation " + "length in cdb)\n" + " (def: 0 -> 4096 or 252 (for MODE " + "SENSE 6) bytes)\n" + " --page=PG|-p PG page code to fetch (def: 63). May be " + "acronym\n" + " --page=PG,SPG|-p PG,SPG\n" + " page code and subpage code to fetch " + "(defs: 63,0)\n" + " --raw|-r output response in binary to stdout\n" + " -R mode page response to stdout, a byte per " + "line in ASCII\n" + " hex (same result as '--raw --raw')\n" + " --readwrite|-w open DEVICE read-write (def: open " + "read-only)\n" + " --six|-6|-s use MODE SENSE(6), by default uses MODE " + "SENSE(10)\n" + " --verbose|-v increase verbosity\n" + " --old|-O use old interface (use as first option)\n" + " --version|-V output version string then exit\n\n" + "Performs a SCSI MODE SENSE (10 or 6) command. To access and " + "possibly change\nmode page fields see the sdparm utility.\n"); +} + +static void +usage_old() +{ + printf("Usage: sg_modes [-a] [-A] [-c=PC] [-d] [-D] [-e] [-f] [-h] " + "[-H] [-l] [-L]\n" + " [-m=LEN] [-p=PG[,SPG]] [-r] [-subp=SPG] [-v] " + "[-V] [-6]\n" + " [DEVICE]\n" + " where:\n" + " -a get all mode pages supported by device\n" + " -A get all mode pages and subpages supported by device\n" + " -c=PC page control (def: 0 [current]," + " 1 [changeable],\n" + " 2 [default], 3 [saved])\n" + " -d disable block descriptors (DBD field in cdb)\n" + " -D disable block descriptor output\n" + " -e examine pages # 0 through to 0x3e, note if found\n" + " -f be flexible, cope with MODE SENSE 6/10 response " + "mixup\n"); + printf(" -h output page number and header in hex\n" + " -H output page number and header in hex (same as '-h')\n" + " -l list common page codes for device peripheral type,\n" + " if no device given then assume disk type\n" + " -L set Long LBA Accepted (LLBAA field in mode sense " + "10 cdb)\n" + " -m=LEN max response length (allocation length in cdb)\n" + " (def: 0 -> 4096 or 252 (for MODE SENSE 6) bytes)\n" + " -p=PG page code in hex (def: 3f). No acronym allowed\n" + " -p=PG,SPG both in hex, (defs: 3f,0)\n" + " -r mode page output to stdout, a byte per line in " + "ASCII hex\n" + " -subp=SPG sub page code in hex (def: 0)\n" + " -v verbose\n" + " -V output version string\n" + " -6 Use MODE SENSE(6), by default uses MODE SENSE(10)\n" + " -N|--new use new interface\n" + " -? output this usage message\n\n" + "Performs a SCSI MODE SENSE (10 or 6) command\n"); +} + +static void +enum_pc_desc(void) +{ + bool first = true; + const struct pc_desc_group * pcd_grp = pcd_gr_arr; + char b[128]; + + for ( ; pcd_grp->pcdp; ++pcd_grp) { + const struct page_code_desc * pcdp = pcd_grp->pcdp; + + if (first) + first = false; + else + printf("\n"); + printf("Mode pages group: %s:\n", pcd_grp->group_name); + for ( ; pcdp->acron; ++pcdp) { + if (pcdp->subpage_code > 0) + snprintf(b, sizeof(b), "[0x%x,0x%x]", pcdp->page_code, + pcdp->subpage_code); + else + snprintf(b, sizeof(b), "[0x%x]", pcdp->page_code); + printf(" %s: %s %s\n", pcdp->acron, pcdp->desc, b); + } + } +} + +static const struct page_code_desc * +find_pc_desc(const char * acron) +{ + const struct pc_desc_group * pcd_grp = pcd_gr_arr; + + for ( ; pcd_grp->pcdp; ++pcd_grp) { + const struct page_code_desc * pcdp = pcd_grp->pcdp; + + for ( ; pcdp->acron; ++pcdp) { + if (0 == strcmp(acron, pcdp->acron)) + return pcdp; + } + } + return NULL; +} + +static void +usage_for(const struct opts_t * op) +{ + if (op->opt_new) + usage(); + else + usage_old(); +} + +/* Processes command line options according to new option format. Returns + * 0 is ok, else SG_LIB_SYNTAX_ERROR is returned. */ +static int +new_parse_cmd_line(struct opts_t * op, int argc, char * argv[]) +{ + int c, n, nn; + char * cp; + + while (1) { + int option_index = 0; + + c = getopt_long(argc, argv, "6aAc:dDefhHlLm:NOp:rRsvV", long_options, + &option_index); + if (c == -1) + break; + + switch (c) { + case '6': + op->do_six = true; + break; + case 'a': + ++op->do_all; + break; + case 'A': + op->do_all += 2; + break; + case 'c': + n = sg_get_num(optarg); + if ((n < 0) || (n > 3)) { + pr2serr("bad argument to '--control='\n"); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + op->page_control = n; + break; + case 'd': + op->do_dbd = true; + break; + case 'D': + op->do_dbout = true; + break; + case 'e': + op->do_examine = true; + break; + case 'f': + op->do_flexible = true; + break; + case 'h': + case '?': + ++op->do_help; + break; + case 'H': + ++op->do_hex; + break; + case 'l': + op->do_list = true; + break; + case 'L': + op->do_llbaa = true; + break; + case 'm': + n = sg_get_num(optarg); + if ((n < 0) || (n > 65535)) { + pr2serr("bad argument to '--maxlen='\n"); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + if ((n > 0) && (n < 4)) { + pr2serr("Changing that '--maxlen=' value to 4\n"); + n = 4; + } + op->maxlen = n; + break; + case 'N': + break; /* ignore */ + case 'O': + op->opt_new = false; + return 0; + case 'p': + if (isalpha((uint8_t)optarg[0])) { + const struct page_code_desc * pcdp; + + op->page_acron = optarg; + if (0 == memcmp("xxx", optarg, 3)) { + enum_pc_desc(); + return SG_LIB_OK_FALSE; /* for quick exit */ + } + pcdp = find_pc_desc(optarg); + if (pcdp) { + if (pcdp->subpage_code > 0) { + op->subpg_code = pcdp->subpage_code; + op->subpg_code_given = true; + } + op->pg_code = pcdp->page_code; + } else { + pr2serr(" Couldn't match acronym '%s', try '-p xxx' for " + "list\n", optarg); + return SG_LIB_SYNTAX_ERROR; + } + } else { + cp = strchr(optarg, ','); + n = sg_get_num_nomult(optarg); + if ((n < 0) || (n > 63)) { + pr2serr("Bad argument to '--page='\n"); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + if (cp) { + nn = sg_get_num_nomult(cp + 1); + if ((nn < 0) || (nn > 255)) { + pr2serr("Bad second value in argument to " + "'--page='\n"); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + op->subpg_code = nn; + op->subpg_code_given = true; + } + op->pg_code = n; + } + break; + case 'r': + ++op->do_raw; + break; + case 'R': + op->do_raw += 2; + break; + case 's': + op->do_six = true; + break; + case 'v': + op->verbose_given = true; + ++op->verbose; + break; + case 'V': + op->version_given = true; + break; + case 'w': + op->o_readwrite = true; + break; + default: + pr2serr("unrecognised option code %c [0x%x]\n", c, c); + if (op->do_help) + break; + usage(); + return SG_LIB_SYNTAX_ERROR; + } + } + if (optind < argc) { + if (NULL == op->device_name) { + op->device_name = argv[optind]; + ++optind; + } + if (optind < argc) { + for (; optind < argc; ++optind) + pr2serr("Unexpected extra argument: %s\n", + argv[optind]); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + } + return 0; +} + +/* Processes command line options according to old option format. Returns + * 0 is ok, else SG_LIB_SYNTAX_ERROR is returned. */ +static int +old_parse_cmd_line(struct opts_t * op, int argc, char * argv[]) +{ + bool jmp_out; + int k, plen, num, n; + char pc1; + unsigned int u, uu; + const char * cp; + + for (k = 1; k < argc; ++k) { + cp = argv[k]; + plen = strlen(cp); + if (plen <= 0) + continue; + if ('-' == *cp) { + for (--plen, ++cp, jmp_out = false; plen > 0; --plen, ++cp) { + switch (*cp) { + case '6': + op->do_six = true; + break; + case 'a': + ++op->do_all; + break; + case 'A': + op->do_all += 2; + break; + case 'd': + op->do_dbd = true; + break; + case 'D': + op->do_dbout = true; + break; + case 'e': + op->do_examine = true; + break; + case 'f': + op->do_flexible = true; + break; + case 'h': + case 'H': + op->do_hex += 2; + break; + case 'l': + op->do_list = true; + break; + case 'L': + op->do_llbaa = true; + break; + case 'N': + op->opt_new = true; + return 0; + case 'O': + break; + case 'r': + op->do_raw += 2; + break; + case 'v': + op->verbose_given = true; + ++op->verbose; + break; + case 'V': + op->version_given = true; + break; + case '?': + ++op->do_help; + break; + default: + jmp_out = true; + break; + } + if (jmp_out) + break; + } + if (plen <= 0) + continue; + if (0 == strncmp("c=", cp, 2)) { + num = sscanf(cp + 2, "%x", &u); + if ((1 != num) || (u > 3)) { + pr2serr("Bad page control after 'c=' option\n"); + usage_old(); + return SG_LIB_SYNTAX_ERROR; + } + op->page_control = u; + } else if (0 == strncmp("m=", cp, 2)) { + num = sscanf(cp + 2, "%d", &n); + if ((1 != num) || (n < 0) || (n > 65535)) { + pr2serr("Bad argument after 'm=' option\n"); + usage_old(); + return SG_LIB_SYNTAX_ERROR; + } + if ((n > 0) && (n < 4)) { + pr2serr("Changing that '-m=' value to 4\n"); + n = 4; + } + op->maxlen = n; + } else if (0 == strncmp("p=", cp, 2)) { + pc1 = *(cp + 2); + if (isalpha(pc1) && ((islower(pc1) && (pc1 > 'f')) || + (isupper(pc1) && (pc1 > 'F')))) { + pr2serr("Old format doesn't accept mode page acronyms: " + "%s\n", cp + 2); + return SG_LIB_SYNTAX_ERROR; + } + if (NULL == strchr(cp + 2, ',')) { + num = sscanf(cp + 2, "%x", &u); + if ((1 != num) || (u > 63)) { + pr2serr("Bad page code value after 'p=' option\n"); + usage_old(); + return SG_LIB_SYNTAX_ERROR; + } + op->pg_code = u; + } else if (2 == sscanf(cp + 2, "%x,%x", &u, &uu)) { + if (uu > 255) { + pr2serr("Bad subpage code value after 'p=' option\n"); + usage_old(); + return SG_LIB_SYNTAX_ERROR; + } + op->pg_code = u; + op->subpg_code = uu; + op->subpg_code_given = true; + } else { + pr2serr("Bad page code, subpage code sequence after 'p=' " + "option\n"); + usage_old(); + return SG_LIB_SYNTAX_ERROR; + } + } else if (0 == strncmp("subp=", cp, 5)) { + num = sscanf(cp + 5, "%x", &u); + if ((1 != num) || (u > 255)) { + pr2serr("Bad sub page code after 'subp=' option\n"); + usage_old(); + return SG_LIB_SYNTAX_ERROR; + } + op->subpg_code = u; + op->subpg_code_given = true; + if (-1 == op->pg_code) + op->pg_code = 0; + } else if (0 == strncmp("-old", cp, 4)) + ; + else if (jmp_out) { + pr2serr("Unrecognized option: %s\n", cp); + usage_old(); + return SG_LIB_SYNTAX_ERROR; + } + } else if (0 == op->device_name) + op->device_name = cp; + else { + pr2serr("too many arguments, got: %s, not expecting: %s\n", + op->device_name, cp); + usage_old(); + return SG_LIB_SYNTAX_ERROR; + } + } + return 0; +} + +/* Process command line options. First check using new option format unless + * the SG3_UTILS_OLD_OPTS environment variable is defined which causes the + * old option format to be checked first. Both new and old format can be + * countermanded by a '-O' and '-N' options respectively. As soon as either + * of these options is detected (when processing the other format), processing + * stops and is restarted using the other format. Clear? */ +static int +parse_cmd_line(struct opts_t * op, int argc, char * argv[]) +{ + int res; + char * cp; + + cp = getenv("SG3_UTILS_OLD_OPTS"); + if (cp) { + op->opt_new = false; + res = old_parse_cmd_line(op, argc, argv); + if ((0 == res) && op->opt_new) + res = new_parse_cmd_line(op, argc, argv); + } else { + op->opt_new = true; + res = new_parse_cmd_line(op, argc, argv); + if ((0 == res) && (! op->opt_new)) + res = old_parse_cmd_line(op, argc, argv); + } + return res; +} + +static void +dStrRaw(const uint8_t * str, int len) +{ + int k; + + for (k = 0; k < len; ++k) + printf("%c", str[k]); +} + +/* Note to coverity: this function is safe as long as the page_code_desc + * objects pointed to by pcdp have a sentinel object at the end of each + * array. And they do by design.*/ +static int +count_desc_elems(const struct page_code_desc * pcdp) +{ + int k; + + for (k = 0; k < 1024; ++k, ++pcdp) { + if (NULL == pcdp->acron) + return k; + } + pr2serr("%s: sanity check trip, invalid pc_desc table\n", __func__); + return k; +} + +/* Returns pointer to base of table for scsi_ptype or pointer to common + * table if scsi_ptype is -1. Yields numbers of elements in returned + * table via pointer sizep. If scsi_ptype not known then returns NULL + * with *sizep set to zero. */ +static struct page_code_desc * +get_mpage_tbl_size(int scsi_ptype, int * sizep) +{ + switch (scsi_ptype) + { + case -1: /* common list */ + *sizep = count_desc_elems(pc_desc_common); + return &pc_desc_common[0]; + case PDT_DISK: /* disk (direct access) type devices */ + case PDT_WO: + case PDT_OPTICAL: + *sizep = count_desc_elems(pc_desc_disk); + return &pc_desc_disk[0]; + case PDT_TAPE: /* tape devices */ + case PDT_PRINTER: + *sizep = count_desc_elems(pc_desc_tape); + return &pc_desc_tape[0]; + case PDT_MMC: /* cd/dvd/bd devices */ + *sizep = count_desc_elems(pc_desc_cddvd); + return &pc_desc_cddvd[0]; + case PDT_MCHANGER: /* medium changer devices */ + *sizep = count_desc_elems(pc_desc_smc); + return &pc_desc_smc[0]; + case PDT_SAC: /* storage array devices */ + *sizep = count_desc_elems(pc_desc_scc); + return &pc_desc_scc[0]; + case PDT_SES: /* enclosure services devices */ + *sizep = count_desc_elems(pc_desc_ses); + return &pc_desc_ses[0]; + case PDT_RBC: /* simplified direct access device */ + *sizep = count_desc_elems(pc_desc_rbc); + return &pc_desc_rbc[0]; + case PDT_ADC: /* automation device/interface */ + *sizep = count_desc_elems(pc_desc_adc); + return &pc_desc_adc[0]; + case PDT_ZBC: + *sizep = count_desc_elems(pc_desc_zbc); + return &pc_desc_zbc[0]; + } + *sizep = 0; + return NULL; +} + + +static struct page_code_desc * +get_mpage_trans_tbl_size(int t_proto, int * sizep) +{ + switch (t_proto) + { + case TPROTO_FCP: + *sizep = count_desc_elems(pc_desc_t_fcp); + return &pc_desc_t_fcp[0]; + case TPROTO_SPI: + *sizep = count_desc_elems(pc_desc_t_spi4); + return &pc_desc_t_spi4[0]; + case TPROTO_SAS: + *sizep = count_desc_elems(pc_desc_t_sas); + return &pc_desc_t_sas[0]; + case TPROTO_ADT: + *sizep = count_desc_elems(pc_desc_t_adc); + return &pc_desc_t_adc[0]; + } + *sizep = 0; + return NULL; +} + +static const char * +find_page_code_desc(int page_num, int subpage_num, int scsi_ptype, + bool encserv, bool mchngr, int t_proto) +{ + int k, num, decayed_pdt; + const struct page_code_desc * pcdp; + + if (t_proto >= 0) { + pcdp = get_mpage_trans_tbl_size(t_proto, &num); + if (pcdp) { + for (k = 0; k < num; ++k, ++pcdp) { + if ((page_num == pcdp->page_code) && + (subpage_num == pcdp->subpage_code)) + return pcdp->desc; + else if (page_num < pcdp->page_code) + break; + } + } + } +try_again: + pcdp = get_mpage_tbl_size(scsi_ptype, &num); + if (pcdp) { + for (k = 0; k < num; ++k, ++pcdp) { + if ((page_num == pcdp->page_code) && + (subpage_num == pcdp->subpage_code)) + return pcdp->desc; + else if (page_num < pcdp->page_code) + break; + } + } + decayed_pdt = sg_lib_pdt_decay(scsi_ptype); + if (decayed_pdt != scsi_ptype) { + scsi_ptype = decayed_pdt; + goto try_again; + } + if ((0xd != scsi_ptype) && encserv) { + /* check for attached enclosure services processor */ + pcdp = get_mpage_tbl_size(0xd, &num); + if (pcdp) { + for (k = 0; k < num; ++k, ++pcdp) { + if ((page_num == pcdp->page_code) && + (subpage_num == pcdp->subpage_code)) + return pcdp->desc; + else if (page_num < pcdp->page_code) + break; + } + } + } + if ((0x8 != scsi_ptype) && mchngr) { + /* check for attached medium changer device */ + pcdp = get_mpage_tbl_size(0x8, &num); + if (pcdp) { + for (k = 0; k < num; ++k, ++pcdp) { + if ((page_num == pcdp->page_code) && + (subpage_num == pcdp->subpage_code)) + return pcdp->desc; + else if (page_num < pcdp->page_code) + break; + } + } + } + pcdp = get_mpage_tbl_size(-1, &num); + for (k = 0; k < num; ++k, ++pcdp) { + if ((page_num == pcdp->page_code) && + (subpage_num == pcdp->subpage_code)) + return pcdp->desc; + else if (page_num < pcdp->page_code) + break; + } + return NULL; +} + +static void +list_page_codes(int scsi_ptype, bool encserv, bool mchngr, int t_proto) +{ + int num, num_ptype, pg, spg, c, d; + bool valid_transport; + const struct page_code_desc * dp; + const struct page_code_desc * pe_dp; + char b[64]; + + valid_transport = ((t_proto >= 0) && (t_proto <= 0xf)); + printf("Page[,subpage] Name\n"); + printf("=====================\n"); + dp = get_mpage_tbl_size(-1, &num); + pe_dp = get_mpage_tbl_size(scsi_ptype, &num_ptype); + while (1) { + pg = dp ? dp->page_code : PG_CODE_ALL + 1; + spg = dp ? dp->subpage_code : SPG_CODE_ALL; + c = (pg << 8) + spg; + pg = pe_dp ? pe_dp->page_code : PG_CODE_ALL + 1; + spg = pe_dp ? pe_dp->subpage_code : SPG_CODE_ALL; + d = (pg << 8) + spg; + if (valid_transport && + ((PROTO_SPECIFIC_1 == c) || (PROTO_SPECIFIC_2 == c))) + dp = (--num <= 0) ? NULL : (dp + 1); /* skip protocol specific */ + else if (c == d) { + if (pe_dp) { + if (pe_dp->subpage_code) + printf(" 0x%02x,0x%02x * %s\n", pe_dp->page_code, + pe_dp->subpage_code, pe_dp->desc); + else + printf(" 0x%02x * %s\n", pe_dp->page_code, + pe_dp->desc); + pe_dp = (--num_ptype <= 0) ? NULL : (pe_dp + 1); + } + if (dp) + dp = (--num <= 0) ? NULL : (dp + 1); + } else if (c < d) { + if (dp) { + if (dp->subpage_code) + printf(" 0x%02x,0x%02x %s\n", dp->page_code, + dp->subpage_code, dp->desc); + else + printf(" 0x%02x %s\n", dp->page_code, + dp->desc); + dp = (--num <= 0) ? NULL : (dp + 1); + } + } else { + if (pe_dp) { + if (pe_dp->subpage_code) + printf(" 0x%02x,0x%02x %s\n", pe_dp->page_code, + pe_dp->subpage_code, pe_dp->desc); + else + printf(" 0x%02x %s\n", pe_dp->page_code, + pe_dp->desc); + pe_dp = (--num_ptype <= 0) ? NULL : (pe_dp + 1); + } + } + if ((NULL == dp) && (NULL == pe_dp)) + break; + } + if ((0xd != scsi_ptype) && encserv) { + /* check for attached enclosure services processor */ + printf("\n Attached enclosure services processor\n"); + dp = get_mpage_tbl_size(0xd, &num); + while (dp) { + if (dp->subpage_code) + printf(" 0x%02x,0x%02x %s\n", dp->page_code, + dp->subpage_code, dp->desc); + else + printf(" 0x%02x %s\n", dp->page_code, + dp->desc); + dp = (--num <= 0) ? NULL : (dp + 1); + } + } + if ((0x8 != scsi_ptype) && mchngr) { + /* check for attached medium changer device */ + printf("\n Attached medium changer device\n"); + dp = get_mpage_tbl_size(0x8, &num); + while (dp) { + if (dp->subpage_code) + printf(" 0x%02x,0x%02x %s\n", dp->page_code, + dp->subpage_code, dp->desc); + else + printf(" 0x%02x %s\n", dp->page_code, + dp->desc); + dp = (--num <= 0) ? NULL : (dp + 1); + } + } + if (valid_transport) { + printf("\n Transport protocol: %s\n", + sg_get_trans_proto_str(t_proto, sizeof(b), b)); + dp = get_mpage_trans_tbl_size(t_proto, &num); + while (dp) { + if (dp->subpage_code) + printf(" 0x%02x,0x%02x %s\n", dp->page_code, + dp->subpage_code, dp->desc); + else + printf(" 0x%02x %s\n", dp->page_code, + dp->desc); + dp = (--num <= 0) ? NULL : (dp + 1); + } + } +} + +/* Returns 0 for ok, else error value */ +static int +examine_pages(int sg_fd, int inq_pdt, bool encserv, bool mchngr, + const struct opts_t * op) +{ + bool header_printed; + int k, mresp_len, len, resid; + int res = 0; + const int mx_len = op->do_six ? DEF_6_ALLOC_LEN : DEF_ALLOC_LEN; + const char * cp; + uint8_t * rbuf; + uint8_t * free_rbuf = NULL; + + rbuf = sg_memalign(mx_len, 0, &free_rbuf, false); + if (NULL == rbuf) { + pr2serr("%s: out of heap\n", __func__); + return sg_convert_errno(ENOMEM); + } + mresp_len = (op->do_raw || op->do_hex) ? mx_len : 4; + for (header_printed = false, k = 0; k < PG_CODE_MAX; ++k) { + resid = 0; + if (op->do_six) { + res = sg_ll_mode_sense6(sg_fd, 0, 0, k, 0, rbuf, mresp_len, + true, op->verbose); + if (SG_LIB_CAT_INVALID_OP == res) { + pr2serr(">>>>>> try again without the '-6' switch for a 10 " + "byte MODE SENSE command\n"); + goto out; + } else if (SG_LIB_CAT_NOT_READY == res) { + pr2serr("MODE SENSE (6) failed, device not ready\n"); + goto out; + } + } else { + res = sg_ll_mode_sense10_v2(sg_fd, 0, 0, 0, k, 0, rbuf, mresp_len, + 0, &resid, true, op->verbose); + if (SG_LIB_CAT_INVALID_OP == res) { + pr2serr(">>>>>> try again with a '-6' switch for a 6 byte " + "MODE SENSE command\n"); + goto out; + } else if (SG_LIB_CAT_NOT_READY == res) { + pr2serr("MODE SENSE (10) failed, device not ready\n"); + goto out; + } + } + if (0 == res) { + len = sg_msense_calc_length(rbuf, mresp_len, op->do_six, NULL); + if (resid > 0) { + mresp_len -= resid; + if (mresp_len < 0) { + pr2serr("%s: MS(10) resid=%d implies negative response " + "length (%d)\n", __func__, resid, mresp_len); + res = SG_LIB_WILD_RESID; + goto out; + } + } + if (len > mresp_len) + len = mresp_len; + if (op->do_raw) { + dStrRaw(rbuf, len); + continue; + } + if (op->do_hex > 2) { + hex2stdout(rbuf, len, -1); + continue; + } + if (! header_printed) { + printf("Discovered mode pages:\n"); + header_printed = true; + } + cp = find_page_code_desc(k, 0, inq_pdt, encserv, mchngr, -1); + if (cp) + printf(" %s\n", cp); + else + printf(" [0x%x]\n", k); + if (op->do_hex) + hex2stdout(rbuf, len, 1); + } else if (op->verbose) { + char b[80]; + + sg_get_category_sense_str(res, sizeof(b), b, op->verbose - 1); + pr2serr("MODE SENSE (%s) failed: %s\n", (op->do_six ? "6" : "10"), + b); + } + } +out: + if (free_rbuf) + free(free_rbuf); + return res; +} + +static const char * pg_control_str_arr[] = { + "current", + "changeable", + "default", + "saved", +}; + + +int +main(int argc, char * argv[]) +{ + bool resp_mode6, longlba, spf; + bool encserv = false; + bool mchngr = false; + uint8_t uc; + int k, num, len, res, md_len, bd_len, page_num, resid; + int density_code_off, t_proto, inq_pdt, num_ua_pages, vb; + int sg_fd = -1; + int ret = 0; + int rsp_buff_sz = DEF_ALLOC_LEN; + const char * descp; + struct opts_t * op; + uint8_t * rsp_buff = NULL; + uint8_t * free_rsp_buff = NULL; + uint8_t * bp; + const char * cdbLenStr; + struct sg_simple_inquiry_resp inq_out; + struct opts_t opts; + char b[80]; + char ebuff[EBUFF_SZ]; + char pdt_name[64]; + + op = &opts; + memset(op, 0, sizeof(opts)); + op->pg_code = -1; + res = parse_cmd_line(op, argc, argv); + if (res) + return (SG_LIB_OK_FALSE == res) ? 0 : res; + if (op->do_help) { + usage_for(op); + return 0; + } +#ifdef DEBUG + pr2serr("In DEBUG mode, "); + if (op->verbose_given && op->version_given) { + pr2serr("but override: '-vV' given, zero verbose and continue\n"); + op->verbose_given = false; + op->version_given = false; + op->verbose = 0; + } else if (! op->verbose_given) { + pr2serr("set '-vv'\n"); + op->verbose = 2; + } else + pr2serr("keep verbose=%d\n", op->verbose); +#else + if (op->verbose_given && op->version_given) + pr2serr("Not in DEBUG mode, so '-vV' has no special action\n"); +#endif + if (op->version_given) { + pr2serr("Version string: %s\n", version_str); + return 0; + } + vb = op->verbose; + if (vb && op->page_acron) { + pr2serr("page acronynm: '%s' maps to page_code=0x%x", + op->page_acron, op->pg_code); + if (op->subpg_code > 0) + pr2serr(", subpage_code=0x%x\n", op->subpg_code); + else + pr2serr("\n"); + } + + if (NULL == op->device_name) { + if (op->do_list) { + if ((op->pg_code < 0) || (op->pg_code > PG_CODE_MAX)) { + printf(" Assume peripheral device type: disk\n"); + list_page_codes(0, false, false, -1); + } else { + printf(" peripheral device type: %s\n", + sg_get_pdt_str(op->pg_code, sizeof(pdt_name), + pdt_name)); + if (op->subpg_code_given) + list_page_codes(op->pg_code, false, false, + op->subpg_code); + else + list_page_codes(op->pg_code, false, false, -1); + } + return 0; + } + pr2serr("No DEVICE argument given\n\n"); + usage_for(op); + return SG_LIB_SYNTAX_ERROR; + } + + if (op->do_examine && (op->pg_code >= 0)) { + pr2serr("can't give '-e' and a page number\n"); + return SG_LIB_CONTRADICT; + } + + if (op->do_six && op->do_llbaa) { + pr2serr("LLBAA not defined for MODE SENSE 6, try without '-L'\n"); + return SG_LIB_CONTRADICT; + } + if (op->maxlen > 0) { + if (op->do_six && (op->maxlen > 255)) { + pr2serr("For Mode Sense (6) maxlen cannot exceed 255\n"); + return SG_LIB_SYNTAX_ERROR; + } + rsp_buff = sg_memalign(op->maxlen, 0, &free_rsp_buff, false); + rsp_buff_sz = op->maxlen; + } else { /* maxlen == 0 */ + rsp_buff = sg_memalign(rsp_buff_sz, 0, &free_rsp_buff, false); + if (op->do_six) + rsp_buff_sz = DEF_6_ALLOC_LEN; + } + if (NULL == rsp_buff) { /* check for both sg_memalign()s */ + pr2serr("Unable to allocate %d bytes on heap\n", rsp_buff_sz); + return sg_convert_errno(ENOMEM); + } + /* If no pages or list selected than treat as 'a' */ + if (! ((op->pg_code >= 0) || op->do_all || op->do_list || op->do_examine)) + op->do_all = 1; + + if (op->do_raw) { + if (sg_set_binary_mode(STDOUT_FILENO) < 0) { + perror("sg_set_binary_mode"); + ret = SG_LIB_FILE_ERROR; + goto fini; + } + } + + if ((sg_fd = sg_cmds_open_device(op->device_name, ! op->o_readwrite, + vb)) < 0) { + pr2serr("error opening file: %s: %s\n", op->device_name, + safe_strerror(-sg_fd)); + ret = sg_convert_errno(-sg_fd); + goto fini; + } + + if ((res = sg_simple_inquiry(sg_fd, &inq_out, true, vb))) { + pr2serr("%s doesn't respond to a SCSI INQUIRY\n", op->device_name); + ret = (res > 0) ? res : sg_convert_errno(-res); + goto fini; + } + inq_pdt = inq_out.peripheral_type; + encserv = !! (0x40 & inq_out.byte_6); + mchngr = !! (0x8 & inq_out.byte_6); + if ((0 == op->do_raw) && (op->do_hex < 3)) + printf(" %.8s %.16s %.4s peripheral_type: %s [0x%x]\n", + inq_out.vendor, inq_out.product, inq_out.revision, + sg_get_pdt_str(inq_pdt, sizeof(pdt_name), pdt_name), inq_pdt); + if (op->do_list) { + if (op->subpg_code_given) + list_page_codes(inq_pdt, encserv, mchngr, op->subpg_code); + else + list_page_codes(inq_pdt, encserv, mchngr, -1); + goto fini; + } + if (op->do_examine) { + ret = examine_pages(sg_fd, inq_pdt, encserv, mchngr, op); + goto fini; + } + if (PG_CODE_ALL == op->pg_code) { + if (0 == op->do_all) + ++op->do_all; + } else if (op->do_all) + op->pg_code = PG_CODE_ALL; + if (op->do_all > 1) + op->subpg_code = SPG_CODE_ALL; + + if (op->do_raw > 1) { + if (op->do_all) { + if (op->opt_new) + pr2serr("'-R' requires a specific (sub)page, not all\n"); + else + pr2serr("'-r' requires a specific (sub)page, not all\n"); + usage_for(op); + ret = SG_LIB_CONTRADICT; + goto fini; + } + } + + resid = 0; + if (op->do_six) { + res = sg_ll_mode_sense6(sg_fd, op->do_dbd, op->page_control, + op->pg_code, op->subpg_code, rsp_buff, + rsp_buff_sz, true, vb); + if (SG_LIB_CAT_INVALID_OP == res) + pr2serr(">>>>>> try again without the '-6' switch for a 10 byte " + "MODE SENSE command\n"); + } else { + res = sg_ll_mode_sense10_v2(sg_fd, op->do_llbaa, op->do_dbd, + op->page_control, op->pg_code, + op->subpg_code, rsp_buff, rsp_buff_sz, + 0, &resid, true, vb); + if (SG_LIB_CAT_INVALID_OP == res) + pr2serr(">>>>>> try again with a '-6' switch for a 6 byte MODE " + "SENSE command\n"); + } + if (SG_LIB_CAT_ILLEGAL_REQ == res) { + if (op->subpg_code > 0) + pr2serr("invalid field in cdb (perhaps subpages not " + "supported)\n"); + else if (op->page_control > 0) + pr2serr("invalid field in cdb (perhaps page control (PC) not " + "supported)\n"); + else + pr2serr("invalid field in cdb (perhaps page 0x%x not " + "supported)\n", op->pg_code); + } else if (res) { + sg_get_category_sense_str(res, sizeof(b), b, vb); + pr2serr("%s\n", b); + } + ret = res; + if (0 == res) { + int medium_type, specific, headerlen; + + ret = 0; + resp_mode6 = op->do_six; + if (op->do_flexible) { + num = rsp_buff[0]; + if (op->do_six && (num < 3)) + resp_mode6 = false; + if ((! op->do_six) && (num > 5)) { + if ((num > 11) && (0 == (num % 2)) && (0 == rsp_buff[4]) && + (0 == rsp_buff[5]) && (0 == rsp_buff[6])) { + rsp_buff[1] = num; + rsp_buff[0] = 0; + pr2serr(">>> msense(10) but resp[0]=%d and not msense(6) " + "response so fix length\n", num); + } else + resp_mode6 = true; + } + } + cdbLenStr = resp_mode6 ? "6" : "10"; + if (op->do_raw || (1 == op->do_hex) || (op->do_hex > 2)) + ; + else { + if (resp_mode6 == op->do_six) + printf("Mode parameter header from MODE SENSE(%s):\n", + cdbLenStr); + else + printf(" >>> Mode parameter header from MODE SENSE(%s),\n" + " decoded as %s byte response:\n", + cdbLenStr, (resp_mode6 ? "6" : "10")); + } + rsp_buff_sz -= resid; + if (rsp_buff_sz < 0) { + pr2serr("MS(%s) resid=%d implies negative response length " + "(%d)\n", cdbLenStr, resid, rsp_buff_sz); + ret = SG_LIB_WILD_RESID; + goto fini; + } + if (resp_mode6) { + if (rsp_buff_sz < 4) { + pr2serr("MS(6) resid=%d implies abridged header length " + "(%d)\n", resid, rsp_buff_sz); + ret = SG_LIB_WILD_RESID; + goto fini; + } + headerlen = 4; + medium_type = rsp_buff[1]; + specific = rsp_buff[2]; + longlba = false; + } else { /* MODE SENSE(10) with resid */ + if (rsp_buff_sz < 8) { + pr2serr("MS(10) resid=%d implies abridged header length " + "(%d)\n", resid, rsp_buff_sz); + ret = SG_LIB_WILD_RESID; + goto fini; + } + headerlen = 8; + medium_type = rsp_buff[2]; + specific = rsp_buff[3]; + longlba = !!(rsp_buff[4] & 1); + } + md_len = sg_msense_calc_length(rsp_buff, rsp_buff_sz, resp_mode6, + &bd_len); + if (md_len < 0) { + pr2serr("MS(%s): sg_msense_calc_length() failed\n", cdbLenStr); + ret = SG_LIB_CAT_MALFORMED; + goto fini; + } + md_len = (md_len < rsp_buff_sz) ? md_len : rsp_buff_sz; + if ((bd_len + headerlen) > md_len) { + pr2serr("Invalid block descriptor length=%d, ignore\n", bd_len); + bd_len = 0; + } + if (op->do_raw || (op->do_hex > 2)) { + if (1 == op->do_raw) + dStrRaw(rsp_buff, md_len); + else if (op->do_raw > 1) { + bp = rsp_buff + bd_len + headerlen; + md_len -= bd_len + headerlen; + spf = !!(bp[0] & 0x40); + len = (spf ? (sg_get_unaligned_be16(bp + 2) + 4) : + (bp[1] + 2)); + len = (len < md_len) ? len : md_len; + for (k = 0; k < len; ++k) + printf("%02x\n", bp[k]); + } else + hex2stdout(rsp_buff, md_len, -1); + goto fini; + } + if (1 == op->do_hex) { + hex2stdout(rsp_buff, md_len, 1); + goto fini; + } else if (op->do_hex > 1) { + hex2stdout(rsp_buff, headerlen, 1); + goto fini; + } + if ((PDT_DISK == inq_pdt) || (PDT_ZBC == inq_pdt)) + printf(" Mode data length=%d, medium type=0x%.2x, WP=%d," + " DpoFua=%d, longlba=%d\n", md_len, medium_type, + !!(specific & 0x80), !!(specific & 0x10), (int)longlba); + else + printf(" Mode data length=%d, medium type=0x%.2x, specific" + " param=0x%.2x, longlba=%d\n", md_len, medium_type, + specific, (int)longlba); + if (md_len > rsp_buff_sz) { + printf("Only fetched %d bytes of response, truncate output\n", + rsp_buff_sz); + md_len = rsp_buff_sz; + if (bd_len + headerlen > rsp_buff_sz) + bd_len = rsp_buff_sz - headerlen; + } + if (! op->do_dbout) { + printf(" Block descriptor length=%d\n", bd_len); + if (bd_len > 0) { + len = 8; + density_code_off = 0; + num = bd_len; + if (longlba) { + printf("> longlba direct access device block " + "descriptors:\n"); + len = 16; + density_code_off = 8; + } + else if ((PDT_DISK == inq_pdt) || (PDT_ZBC == inq_pdt)) { + printf("> Direct access device block descriptors:\n"); + density_code_off = 4; + } + else + printf("> General mode parameter block descriptors:\n"); + + bp = rsp_buff + headerlen; + while (num > 0) { + printf(" Density code=0x%x\n", + *(bp + density_code_off)); + hex2stdout(bp, len, 1); + bp += len; + num -= len; + } + printf("\n"); + } + } + bp = rsp_buff + bd_len + headerlen; /* start of mode page(s) */ + md_len -= bd_len + headerlen; /* length of mode page(s) */ + num_ua_pages = 0; + for (k = 0; md_len > 0; ++k) { /* got mode page(s) */ + if ((k > 0) && (! op->do_all) && + (SPG_CODE_ALL != op->subpg_code)) { + pr2serr("Unexpectedly received extra mode page responses, " + "ignore\n"); + break; + } + uc = *bp; + spf = !!(uc & 0x40); + len = (spf ? (sg_get_unaligned_be16(bp + 2) + 4) : (bp[1] + 2)); + page_num = bp[0] & PG_CODE_MASK; + if (0x0 == page_num) { + ++num_ua_pages; + if((num_ua_pages > 3) && (md_len > 0xa00)) { + pr2serr(">>> Seen 3 unit attention pages (only one " + "should be at end)\n and mpage length=%d, " + "looks malformed, try '-f' option\n", md_len); + break; + } + } + if (op->do_hex) { + if (spf) + printf(">> page_code=0x%x, subpage_code=0x%x, page_cont" + "rol=%d\n", page_num, bp[1], op->page_control); + else + printf(">> page_code=0x%x, page_control=%d\n", page_num, + op->page_control); + } else { + descp = NULL; + if ((0x18 == page_num) || (0x19 == page_num)) { + t_proto = (spf ? bp[5] : bp[2]) & 0xf; + descp = find_page_code_desc(page_num, (spf ? bp[1] : 0), + inq_pdt, encserv, mchngr, + t_proto); + } else + descp = find_page_code_desc(page_num, (spf ? bp[1] : 0), + inq_pdt, encserv, mchngr, -1); + if (NULL == descp) { + if (spf) + snprintf(ebuff, EBUFF_SZ, "0x%x, subpage_code: 0x%x", + page_num, bp[1]); + else + snprintf(ebuff, EBUFF_SZ, "0x%x", page_num); + } + if (descp) + printf(">> %s, page_control: %s\n", descp, + pg_control_str_arr[op->page_control]); + else + printf(">> page_code: %s, page_control: %s\n", ebuff, + pg_control_str_arr[op->page_control]); + } + num = (len > md_len) ? md_len : len; + if ((k > 0) && (num > UNLIKELY_ABOVE_LEN)) { + num = UNLIKELY_ABOVE_LEN; + pr2serr(">>> page length (%d) > %d bytes, unlikely, trim\n" + " Try '-f' option\n", len, num); + } + hex2stdout(bp, num , 1); + bp += len; + md_len -= len; + } + } + +fini: + if (sg_fd >= 0) + sg_cmds_close_device(sg_fd); + if (free_rsp_buff) + free(free_rsp_buff); + if (0 == vb) { + if (! sg_if_can2stderr("sg_modes failed: ", ret)) + pr2serr("Some error occurred, try again with '-v' or '-vv' for " + "more information\n"); + } + return (ret >= 0) ? ret : SG_LIB_CAT_OTHER; +} |