diff options
Diffstat (limited to 'src/sg_senddiag.c')
-rw-r--r-- | src/sg_senddiag.c | 971 |
1 files changed, 971 insertions, 0 deletions
diff --git a/src/sg_senddiag.c b/src/sg_senddiag.c new file mode 100644 index 00000000..7e82dd47 --- /dev/null +++ b/src/sg_senddiag.c @@ -0,0 +1,971 @@ +/* + * A utility program originally written for the Linux OS SCSI subsystem + * Copyright (C) 2003-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 issues the SCSI SEND DIAGNOSTIC command and in one case + the SCSI RECEIVE DIAGNOSTIC command to list supported diagnostic pages. +*/ + +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <getopt.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.h" +#include "sg_cmds_basic.h" +#include "sg_cmds_extra.h" +#if SG_LIB_WIN32 +#include "sg_pt.h" /* needed for scsi_pt_win32_direct() */ +#endif +#include "sg_unaligned.h" +#include "sg_pr2serr.h" + + +static const char * version_str = "0.65 20220128"; + +#define ME "sg_senddiag: " + +#define DEF_ALLOC_LEN (1024 * 4) + +static struct option long_options[] = { + {"doff", no_argument, 0, 'd'}, + {"extdur", no_argument, 0, 'e'}, + {"help", no_argument, 0, 'h'}, + {"hex", no_argument, 0, 'H'}, + {"list", no_argument, 0, 'l'}, + {"maxlen", required_argument, 0, 'm'}, + {"new", no_argument, 0, 'N'}, + {"old", no_argument, 0, 'O'}, + {"page", required_argument, 0, 'P'}, + {"pf", no_argument, 0, 'p'}, + {"raw", required_argument, 0, 'r'}, + {"selftest", required_argument, 0, 's'}, + {"test", no_argument, 0, 't'}, + {"timeout", required_argument, 0, 'T'}, + {"uoff", no_argument, 0, 'u'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'V'}, + {0, 0, 0, 0}, +}; + +struct opts_t { + bool do_deftest; + bool do_doff; + bool do_extdur; + bool do_list; + bool do_pf; + bool do_raw; + bool do_uoff; + bool opt_new; + bool verbose_given; + bool version_given; + int do_help; + int do_hex; + int maxlen; + int page_code; + int do_selftest; + int timeout; + int verbose; + const char * device_name; + const char * raw_arg; +}; + + +static void +usage() +{ + printf("Usage: sg_senddiag [--doff] [--extdur] [--help] [--hex] " + "[--list]\n" + " [--maxlen=LEN] [--page=PG] [--pf] " + "[--raw=H,H...]\n" + " [--selftest=ST] [--test] [--timeout=SECS] " + "[--uoff]\n" + " [--verbose] [--version] [DEVICE]\n" + " where:\n" + " --doff|-d device online (def: 0, only with '--test')\n" + " --extdur|-e duration of an extended self-test (from mode " + "page 0xa)\n" + " --help|-h print usage message then exit\n" + " --hex|-H output RDR in hex; twice: plus ASCII; thrice: " + "suitable\n" + " for '--raw=-' with later invocation\n" + " --list|-l list supported page codes (with or without " + "DEVICE)\n" + " --maxlen=LEN|-m LEN parameter list length or maximum " + "allocation\n" + " length (default: 4096 bytes)\n" + " --page=PG|-P PG do RECEIVE DIAGNOSTIC RESULTS only, set " + "PCV\n" + " --pf|-p set PF bit (def: 0)\n" + " --raw=H,H...|-r H,H... sequence of hex bytes to form " + "diag page to send\n" + " --raw=-|-r - read stdin for sequence of bytes to send\n" + " --selftest=ST|-s ST self-test code, default: 0 " + "(inactive)\n" + " 1->background short, 2->background " + "extended\n" + " 4->abort test\n" + " 5->foreground short, 6->foreground " + "extended\n" + " --test|-t default self-test\n" + " --timeout=SECS|-T SECS timeout for foreground self tests\n" + " unit: second (def: 7200 seconds)\n" + " --uoff|-u unit offline (def: 0, only with '--test')\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 SEND DIAGNOSTIC (and/or a RECEIVE DIAGNOSTIC " + "RESULTS) command\n" + ); +} + +static void +usage_old() +{ + printf("Usage: sg_senddiag [-doff] [-e] [-h] [-H] [-l] [-pf]" + " [-raw=H,H...]\n" + " [-s=SF] [-t] [-T=SECS] [-uoff] [-v] [-V] " + "[DEVICE]\n" + " where:\n" + " -doff device online (def: 0, only with '-t')\n" + " -e duration of an extended self-test (from mode page " + "0xa)\n" + " -h output in hex\n" + " -H output in hex (same as '-h')\n" + " -l list supported page codes\n" + " -pf set PF bit (def: 0)\n" + " -raw=H,H... sequence of bytes to form diag page to " + "send\n" + " -raw=- read stdin for sequence of bytes to send\n" + " -s=SF self-test code (def: 0)\n" + " 1->background short, 2->background extended," + " 4->abort test\n" + " 5->foreground short, 6->foreground extended\n" + " -t default self-test\n" + " -T SECS timeout for foreground self tests\n" + " -uoff unit offline (def: 0, only with '-t')\n" + " -v increase verbosity (print issued SCSI cmds)\n" + " -V output version string\n" + " -N|--new use new interface\n" + " -? output this usage message\n\n" + "Performs a SCSI SEND DIAGNOSTIC (and/or a RECEIVE DIAGNOSTIC " + "RESULTS) command\n" + ); +} + +static int +new_parse_cmd_line(struct opts_t * op, int argc, char * argv[]) +{ + int c, n; + + while (1) { + int option_index = 0; + + c = getopt_long(argc, argv, "dehHlm:NOpP:r:s:tT:uvV", long_options, + &option_index); + if (c == -1) + break; + + switch (c) { + case 'd': + op->do_doff = true; + break; + case 'e': + op->do_extdur = true; + break; + case 'h': + case '?': + ++op->do_help; + break; + case 'H': + ++op->do_hex; + break; + case 'l': + op->do_list = true; + break; + case 'm': + n = sg_get_num(optarg); + if ((n < 0) || (n > 0xffff)) { + pr2serr("bad argument to '--maxlen=' or greater than 65535 " + "[0xffff]\n"); + return SG_LIB_SYNTAX_ERROR; + } + op->maxlen = n; + break; + case 'N': + break; /* ignore */ + case 'O': + op->opt_new = false; + return 0; + case 'p': + op->do_pf = true; + break; + case 'P': + n = sg_get_num(optarg); + if ((n < 0) || (n > 0xff)) { + pr2serr("bad argument to '--page=' or greater than 255 " + "[0xff]\n"); + return SG_LIB_SYNTAX_ERROR; + } + op->page_code = n; + break; + case 'r': + op->raw_arg = optarg; + op->do_raw = true; + break; + case 's': + n = sg_get_num(optarg); + if ((n < 0) || (n > 7)) { + pr2serr("bad argument to '--selftest='\n"); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + op->do_selftest = n; + break; + case 't': + op->do_deftest = true; + break; + case 'T': + n = sg_get_num(optarg); + if (n < 0) { + pr2serr("bad argument to '--timeout=SECS'\n"); + return SG_LIB_SYNTAX_ERROR; + } + op->timeout = n; + break; + case 'u': + op->do_uoff = true; + break; + case 'v': + op->verbose_given = true; + ++op->verbose; + break; + case 'V': + op->version_given = true; + break; + default: + pr2serr("unrecognised option code %c [0x%x]\n", c, c); + if (op->do_help) + break; + usage(); + 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; +} + +static int +old_parse_cmd_line(struct opts_t * op, int argc, char * argv[]) +{ + bool jmp_out; + int k, plen, num, n; + unsigned int u; + 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 'd': + if (0 == strncmp("doff", cp, 4)) { + op->do_doff = true; + cp += 3; + plen -= 3; + } else + jmp_out = true; + break; + case 'e': + op->do_extdur = true; + break; + case 'h': + case 'H': + ++op->do_hex; + break; + case 'l': + op->do_list = true; + break; + case 'N': + op->opt_new = true; + return 0; + case 'O': + break; + case 'p': + if (0 == strncmp("pf", cp, 2)) { + op->do_pf = true; + ++cp; + --plen; + } else + jmp_out = true; + break; + case 't': + op->do_deftest = true; + break; + case 'u': + if (0 == strncmp("uoff", cp, 4)) { + op->do_uoff = true; + cp += 3; + plen -= 3; + } else + jmp_out = true; + 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("raw=", cp, 4)) { + op->raw_arg = cp + 4; + op->do_raw = true; + } else if (0 == strncmp("s=", cp, 2)) { + num = sscanf(cp + 2, "%x", &u); + if ((1 != num) || (u > 7)) { + printf("Bad page code after '-s=' option\n"); + usage_old(); + return SG_LIB_SYNTAX_ERROR; + } + op->do_selftest = u; + } else if (0 == strncmp("T=", cp, 2)) { + num = sscanf(cp + 2, "%d", &n); + if ((1 != num) || (n < 0)) { + printf("Bad page code after '-T=SECS' option\n"); + usage_old(); + return SG_LIB_SYNTAX_ERROR; + } + op->timeout = n; + } else if (0 == strncmp("-old", cp, 5)) + ; + 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; +} + +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; +} + +/* Return of 0 -> success, otherwise see sg_ll_send_diag() */ +static int +do_senddiag(int sg_fd, int sf_code, bool pf_bit, bool sf_bit, + bool devofl_bit, bool unitofl_bit, void * outgoing_pg, + int outgoing_len, int tmout, bool noisy, int verbose) +{ + int long_duration = 0; + + if ((0 == sf_bit) && ((5 == sf_code) || (6 == sf_code))) { + /* foreground self-tests */ + if (tmout <= 0) + long_duration = 1; + else + long_duration = tmout; + } + return sg_ll_send_diag(sg_fd, sf_code, pf_bit, sf_bit, devofl_bit, + unitofl_bit, long_duration, outgoing_pg, + outgoing_len, noisy, verbose); +} + +/* Get expected extended self-test time from mode page 0xa (for '-e') */ +static int +do_modes_0a(int sg_fd, void * resp, int mx_resp_len, bool mode6, bool noisy, + int verbose) +{ + int res; + int resid = 0; + + if (mode6) + res = sg_ll_mode_sense6(sg_fd, true /* dbd */, false /* pc */, + 0xa /* page */, false, resp, mx_resp_len, + noisy, verbose); + else + res = sg_ll_mode_sense10_v2(sg_fd, false /* llbaa */, true /* dbd */, + false, 0xa, false, resp, mx_resp_len, + 0, &resid, noisy, verbose); + if (res) { + char b[80]; + + sg_get_category_sense_str(res, sizeof(b), b, verbose); + pr2serr("Mode sense (%s): %s\n", (mode6 ? "6" : "10"), b); + } else { + mx_resp_len -= resid; + if (mx_resp_len < 4) { + pr2serr("%s: response length (%d) too small (resid=%d)\n", + __func__, mx_resp_len, resid); + res = SG_LIB_WILD_RESID; + } + } + return res; +} + +/* Read hex numbers from command line (comma separated list) or from */ +/* stdin (one per line, comma separated list or space separated list). */ +/* Returns 0 if ok, or 1 if error. */ +static int +build_diag_page(const char * inp, uint8_t * mp_arr, int * mp_arr_len, + int max_arr_len) +{ + int in_len, k, j, m; + unsigned int h; + const char * lcp; + char * cp; + char * c2p; + + if ((NULL == inp) || (NULL == mp_arr) || + (NULL == mp_arr_len)) + return 1; + lcp = inp; + in_len = strlen(inp); + if (0 == in_len) + *mp_arr_len = 0; + if ('-' == inp[0]) { /* read from stdin */ + bool split_line; + int off = 0; + char line[512]; + char carry_over[4]; + + carry_over[0] = 0; + for (j = 0; j < 512; ++j) { + if (NULL == fgets(line, sizeof(line), stdin)) + break; + in_len = strlen(line); + if (in_len > 0) { + if ('\n' == line[in_len - 1]) { + --in_len; + line[in_len] = '\0'; + split_line = false; + } else + split_line = true; + } + if (in_len < 1) { + carry_over[0] = 0; + continue; + } + if (carry_over[0]) { + if (isxdigit((uint8_t)line[0])) { + carry_over[1] = line[0]; + carry_over[2] = '\0'; + if (1 == sscanf(carry_over, "%x", &h)) + mp_arr[off - 1] = h; /* back up and overwrite */ + else { + pr2serr("build_diag_page: carry_over error ['%s'] " + "around line %d\n", carry_over, j + 1); + return 1; + } + 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])) { + pr2serr("build_diag_page: syntax error at line %d, pos %d\n", + j + 1, m + k + 1); + return 1; + } + for (k = 0; k < 1024; ++k) { + if (1 == sscanf(lcp, "%x", &h)) { + if (h > 0xff) { + pr2serr("build_diag_page: hex number larger than " + "0xff in line %d, pos %d\n", j + 1, + (int)(lcp - line + 1)); + return 1; + } + 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("build_diag_page: array length exceeded\n"); + return 1; + } + mp_arr[off + k] = h; + lcp = strpbrk(lcp, " ,\t"); + if (NULL == lcp) + break; + lcp += strspn(lcp, " ,\t"); + if ('\0' == *lcp) + break; + } else { + if ('#' == *lcp) { + --k; + break; + } + pr2serr("build_diag_page: error in line %d, at pos %d\n", + j + 1, (int)(lcp - line + 1)); + return 1; + } + } + off += (k + 1); + } + *mp_arr_len = off; + } else { /* hex string on command line */ + k = strspn(inp, "0123456789aAbBcCdDeEfF, "); + if (in_len != k) { + pr2serr("build_diag_page: error at pos %d\n", k + 1); + return 1; + } + for (k = 0; k < max_arr_len; ++k) { + if (1 == sscanf(lcp, "%x", &h)) { + if (h > 0xff) { + pr2serr("build_diag_page: hex number larger than 0xff at " + "pos %d\n", (int)(lcp - inp + 1)); + return 1; + } + mp_arr[k] = h; + cp = (char *)strchr(lcp, ','); + c2p = (char *)strchr(lcp, ' '); + if (NULL == cp) + cp = c2p; + if (NULL == cp) + break; + if (c2p && (c2p < cp)) + cp = c2p; + lcp = cp + 1; + } else { + pr2serr("build_diag_page: error at pos %d\n", + (int)(lcp - inp + 1)); + return 1; + } + } + *mp_arr_len = k + 1; + if (k == max_arr_len) { + pr2serr("build_diag_page: array length exceeded\n"); + return 1; + } + } + return 0; +} + + +struct page_code_desc { + int page_code; + const char * desc; +}; +static struct page_code_desc pc_desc_arr[] = { + {0x0, "Supported diagnostic pages"}, + {0x1, "Configuration (SES)"}, + {0x2, "Enclosure status/control (SES)"}, + {0x3, "Help text (SES)"}, + {0x4, "String In/Out (SES)"}, + {0x5, "Threshold In/Out (SES)"}, + {0x6, "Array Status/Control (SES, obsolete)"}, + {0x7, "Element descriptor (SES)"}, + {0x8, "Short enclosure status (SES)"}, + {0x9, "Enclosure busy (SES-2)"}, + {0xa, "Additional (device) element status (SES-2)"}, + {0xb, "Subenclosure help text (SES-2)"}, + {0xc, "Subenclosure string In/Out (SES-2)"}, + {0xd, "Supported SES diagnostic pages (SES-2)"}, + {0xe, "Download microcode diagnostic pages (SES-2)"}, + {0xf, "Subenclosure nickname diagnostic pages (SES-2)"}, + {0x3f, "Protocol specific (SAS transport)"}, + {0x40, "Translate address (direct access)"}, + {0x41, "Device status (direct access)"}, + {0x42, "Rebuild assist (direct access)"}, /* sbc3r31 */ +}; + +static const char * +find_page_code_desc(int page_num) +{ + int k; + int num = SG_ARRAY_SIZE(pc_desc_arr); + const struct page_code_desc * pcdp = &pc_desc_arr[0]; + + for (k = 0; k < num; ++k, ++pcdp) { + if (page_num == pcdp->page_code) + return pcdp->desc; + else if (page_num < pcdp->page_code) + return NULL; + } + return NULL; +} + +static void +list_page_codes() +{ + int k; + int num = SG_ARRAY_SIZE(pc_desc_arr); + const struct page_code_desc * pcdp = &pc_desc_arr[0]; + + printf("Page_Code Description\n"); + for (k = 0; k < num; ++k, ++pcdp) + printf(" 0x%02x %s\n", pcdp->page_code, + (pcdp->desc ? pcdp->desc : "<unknown>")); +} + + +int +main(int argc, char * argv[]) +{ + int k, num, rsp_len, res, rsp_buff_size, pg, bd_len, resid, vb; + int sg_fd = -1; + int read_in_len = 0; + int ret = 0; + struct opts_t opts; + struct opts_t * op; + uint8_t * rsp_buff = NULL; + uint8_t * free_rsp_buff = NULL; + const char * cp; + uint8_t * read_in = NULL; + uint8_t * free_read_in = NULL; + + op = &opts; + memset(op, 0, sizeof(opts)); + op->maxlen = DEF_ALLOC_LEN; + op->page_code = -1; + res = parse_cmd_line(op, argc, argv); + if (res) + return SG_LIB_SYNTAX_ERROR; + if (op->do_help) { + if (op->opt_new) + usage(); + else + usage_old(); + 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; + } + + rsp_buff_size = op->maxlen; + + if (NULL == op->device_name) { + if (op->do_list) { + list_page_codes(); + return 0; + } + pr2serr("No DEVICE argument given\n\n"); + if (op->opt_new) + usage(); + else + usage_old(); + return SG_LIB_SYNTAX_ERROR; + } + vb = op->verbose; + if (op->do_raw) { + read_in = sg_memalign(op->maxlen, 0, &free_read_in, vb > 3); + if (NULL == read_in) { + pr2serr("unable to allocate %d bytes\n", op->maxlen); + return SG_LIB_CAT_OTHER; + } + if (build_diag_page(op->raw_arg, read_in, &read_in_len, op->maxlen)) { + if (op->opt_new) { + printf("Bad sequence after '--raw=' option\n"); + usage(); + } else { + printf("Bad sequence after '-raw=' option\n"); + usage_old(); + } + ret = SG_LIB_SYNTAX_ERROR; + goto fini; + } + } + + if ((op->do_doff || op->do_uoff) && (! op->do_deftest)) { + if (op->opt_new) { + printf("setting --doff or --uoff only useful when -t is set\n"); + usage(); + } else { + printf("setting -doff or -uoff only useful when -t is set\n"); + usage_old(); + } + ret = SG_LIB_CONTRADICT; + goto fini; + } + if ((op->do_selftest > 0) && op->do_deftest) { + if (op->opt_new) { + printf("either set --selftest=SF or --test (not both)\n"); + usage(); + } else { + printf("either set -s=SF or -t (not both)\n"); + usage_old(); + } + ret = SG_LIB_CONTRADICT; + goto fini; + } + if (op->do_raw) { + if ((op->do_selftest > 0) || op->do_deftest || op->do_extdur || + op->do_list) { + if (op->opt_new) { + printf("'--raw=' cannot be used with self-tests, '-e' or " + "'-l'\n"); + usage(); + } else { + printf("'-raw=' cannot be used with self-tests, '-e' or " + "'-l'\n"); + usage_old(); + } + ret = SG_LIB_CONTRADICT; + goto fini; + } + if (! op->do_pf) { + if (op->opt_new) + printf(">>> warning, '--pf' probably should be used with " + "'--raw='\n"); + else + printf(">>> warning, '-pf' probably should be used with " + "'-raw='\n"); + } + } +#ifdef SG_LIB_WIN32 +#ifdef SG_LIB_WIN32_DIRECT + if (vb > 4) + pr2serr("Initial win32 SPT interface state: %s\n", + scsi_pt_win32_spt_state() ? "direct" : "indirect"); + if (op->maxlen >= 16384) + scsi_pt_win32_direct(SG_LIB_WIN32_DIRECT /* SPT pt interface */); +#endif +#endif + + if ((sg_fd = sg_cmds_open_device(op->device_name, false /* rw */, vb)) < + 0) { + if (vb) + pr2serr(ME "error opening file: %s: %s\n", op->device_name, + safe_strerror(-sg_fd)); + ret = sg_convert_errno(-sg_fd); + goto fini; + } + rsp_buff = sg_memalign(op->maxlen, 0, &free_rsp_buff, vb > 3); + if (NULL == rsp_buff) { + pr2serr("unable to allocate %d bytes (2)\n", op->maxlen); + ret = SG_LIB_CAT_OTHER; + goto close_fini; + } + if (op->do_extdur) { /* fetch Extended self-test time from Control + * mode page with Mode Sense(10) command*/ + res = do_modes_0a(sg_fd, rsp_buff, 32, false /* mode6 */, + true /* noisy */, vb); + if (0 == res) { + /* Mode sense(10) response, step over any block descriptors */ + num = sg_msense_calc_length(rsp_buff, 32, false, &bd_len); + num -= (8 /* MS(10) header length */ + bd_len); + if (num >= 0xc) { + int secs = sg_get_unaligned_be16(rsp_buff + 8 + bd_len + 10); + + if (0xffff == secs) { + if (op->verbose > 1) + printf("Expected extended self-test duration's value " + "[65535] indicates the\nsimilarly named field " + "in the Extended Inquiry VPD page should be " + "used\n"); + } else { +#ifdef SG_LIB_MINGW + printf("Expected extended self-test duration=%d seconds " + "(%g minutes)\n", secs, secs / 60.0); +#else + printf("Expected extended self-test duration=%d seconds " + "(%.2f minutes)\n", secs, secs / 60.0); +#endif + } + } else + printf("Extended self-test duration not available\n"); + } else { + ret = res; + printf("Extended self-test duration (mode page 0xa) failed\n"); + goto err_out9; + } + } else if (op->do_list || (op->page_code >= 0x0)) { + pg = op->page_code; + if (pg < 0) + res = do_senddiag(sg_fd, 0, true /* pf */, false, false, false, + rsp_buff, 4, op->timeout, 1, vb); + else + res = 0; + if (0 == res) { + resid = 0; + if (0 == sg_ll_receive_diag_v2(sg_fd, (pg >= 0x0), + ((pg >= 0x0) ? pg : 0), rsp_buff, + rsp_buff_size, 0, &resid, + true, vb)) { + rsp_buff_size -= resid; + if (rsp_buff_size < 4) { + pr2serr("RD resid (%d) indicates response too small " + "(lem=%d)\n", resid, rsp_buff_size); + goto err_out; + } + rsp_len = sg_get_unaligned_be16(rsp_buff + 2) + 4; + rsp_len= (rsp_len < rsp_buff_size) ? rsp_len : rsp_buff_size; + if (op->do_hex > 1) + hex2stdout(rsp_buff, rsp_len, + (2 == op->do_hex) ? 0 : -1); + else if (pg < 0x1) { + printf("Supported diagnostic pages response:\n"); + if (op->do_hex) + hex2stdout(rsp_buff, rsp_len, 1); + else { + for (k = 0; k < (rsp_len - 4); ++k) { + pg = rsp_buff[k + 4]; + cp = find_page_code_desc(pg); + if (NULL == cp) + cp = (pg < 0x80) ? "<unknown>" : + "<vendor specific>"; + printf(" 0x%02x %s\n", pg, cp); + } + } + } else { + cp = find_page_code_desc(pg); + if (cp) + printf("%s diagnostic page [0x%x] response in " + "hex:\n", cp, pg); + else + printf("diagnostic page 0x%x response in hex:\n", pg); + hex2stdout(rsp_buff, rsp_len, 1); + } + } else { + ret = res; + pr2serr("RECEIVE DIAGNOSTIC RESULTS command failed\n"); + goto err_out9; + } + } else { + ret = res; + goto err_out; + } + } else if (op->do_raw) { + res = do_senddiag(sg_fd, 0, op->do_pf, false, false, false, read_in, + read_in_len, op->timeout, 1, vb); + if (res) { + ret = res; + goto err_out; + } + } else { + res = do_senddiag(sg_fd, op->do_selftest, op->do_pf, op->do_deftest, + op->do_doff, op->do_uoff, NULL, 0, op->timeout, 1, + vb); + if (0 == res) { + if ((5 == op->do_selftest) || (6 == op->do_selftest)) + printf("Foreground self-test returned GOOD status\n"); + else if (op->do_deftest && (! op->do_doff) && (! op->do_uoff)) + printf("Default self-test returned GOOD status\n"); + } else { + ret = res; + goto err_out; + } + } + goto close_fini; + +err_out: + if (SG_LIB_CAT_UNIT_ATTENTION == res) + pr2serr("SEND DIAGNOSTIC, unit attention\n"); + else if (SG_LIB_CAT_ABORTED_COMMAND == res) + pr2serr("SEND DIAGNOSTIC, aborted command\n"); + else if (SG_LIB_CAT_NOT_READY == res) + pr2serr("SEND DIAGNOSTIC, device not ready\n"); + else + pr2serr("SEND DIAGNOSTIC command, failed\n"); +err_out9: + if (vb < 2) + pr2serr(" try again with '-vv' for more information\n"); +close_fini: + if (sg_fd >= 0) { + res = sg_cmds_close_device(sg_fd); + if (0 == ret) + ret = sg_convert_errno(-res); + } +fini: + if (free_read_in) + free(free_read_in); + if (free_rsp_buff) + free(free_rsp_buff); + if (0 == vb) { + if (! sg_if_can2stderr("sg_senddiag failed: ", ret)) + pr2serr("Some error occurred, try again with '-v' " + "or '-vv' for more information\n"); + } + return (ret >= 0) ? ret : SG_LIB_CAT_OTHER; +} |