diff options
Diffstat (limited to 'src/sg_raw.c')
-rw-r--r-- | src/sg_raw.c | 849 |
1 files changed, 849 insertions, 0 deletions
diff --git a/src/sg_raw.c b/src/sg_raw.c new file mode 100644 index 00000000..a0254cd9 --- /dev/null +++ b/src/sg_raw.c @@ -0,0 +1,849 @@ +/* + * A utility program originally written for the Linux OS SCSI subsystem. + * + * Copyright (C) 2000-2022 Ingo van Lil <inguin@gmx.de> + * + * 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 can be used to send raw SCSI commands (with an optional + * data phase) through a Generic SCSI interface. + */ + +#define _XOPEN_SOURCE 600 /* clear up posix_memalign() warning */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdbool.h> +#include <ctype.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <getopt.h> +#include <inttypes.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "sg_lib.h" +#include "sg_pt.h" +#include "sg_pt_nvme.h" +#include "sg_pr2serr.h" +#include "sg_unaligned.h" + +#define SG_RAW_VERSION "0.4.39 (2022-04-25)" + +#define DEFAULT_TIMEOUT 20 +#define MIN_SCSI_CDBSZ 6 +#define MAX_SCSI_CDBSZ 260 +#define MAX_SCSI_DXLEN (1024 * 1024) + +#define NVME_ADDR_DATA_IN 0xfffffffffffffffe +#define NVME_ADDR_DATA_OUT 0xfffffffffffffffd +#define NVME_DATA_LEN_DATA_IN 0xfffffffe +#define NVME_DATA_LEN_DATA_OUT 0xfffffffd + +static struct option long_options[] = { + { "binary", no_argument, NULL, 'b' }, + { "cmdfile", required_argument, NULL, 'c' }, + { "cmdset", required_argument, NULL, 'C' }, + { "enumerate", no_argument, NULL, 'e' }, + { "help", no_argument, NULL, 'h' }, + { "infile", required_argument, NULL, 'i' }, + { "skip", required_argument, NULL, 'k' }, + { "nosense", no_argument, NULL, 'n' }, + { "nvm", no_argument, NULL, 'N' }, + { "outfile", required_argument, NULL, 'o' }, + { "raw", no_argument, NULL, 'w' }, + { "request", required_argument, NULL, 'r' }, + { "readonly", no_argument, NULL, 'R' }, + { "scan", required_argument, NULL, 'Q' }, + { "send", required_argument, NULL, 's' }, + { "timeout", required_argument, NULL, 't' }, + { "verbose", no_argument, NULL, 'v' }, + { "version", no_argument, NULL, 'V' }, + { 0, 0, 0, 0 } +}; + +struct opts_t { + bool cmdfile_given; + bool do_datain; + bool datain_binary; + bool do_dataout; + bool do_enumerate; + bool no_sense; + bool do_nvm; /* the NVMe command set: NVM containing its READ+WRITE */ + bool do_help; + bool verbose_given; + bool version_given; + int cdb_length; + int cmdset; + int datain_len; + int dataout_len; + int timeout; + int raw; + int readonly; + int scan_first; + int scan_last; + int verbose; + off_t dataout_offset; + uint8_t cdb[MAX_SCSI_CDBSZ]; /* might be NVMe command (64 byte) */ + const char *cmd_file; + const char *datain_file; + const char *dataout_file; + char *device_name; +}; + + +static void +pr_version() +{ + pr2serr("sg_raw " SG_RAW_VERSION "\n" + "Copyright (C) 2007-2021 Ingo van Lil <inguin@gmx.de>\n" + "This is free software. You may redistribute copies of it " + "under the terms of\n" + "the GNU General Public License " + "<https://www.gnu.org/licenses/gpl.html>.\n" + "There is NO WARRANTY, to the extent permitted by law.\n"); +} + +static void +usage() +{ + pr2serr("Usage: sg_raw [OPTION]* DEVICE [CDB0 CDB1 ...]\n" + "\n" + "Options:\n" + " --binary|-b Dump data in binary form, even when " + "writing to\n" + " stdout\n" + " --cmdfile=CF|-c CF CF is file containing command in hex " + "bytes\n" + " --cmdset=CS|-C CS CS is 0 (def) heuristic chooses " + "command set;\n" + " 1: force SCSI; 2: force NVMe\n" + " --enumerate|-e Decodes cdb name then exits; requires " + "DEVICE but\n" + " ignores it\n" + " --help|-h Show this message and exit\n" + " --infile=IFILE|-i IFILE Read binary data to send (i.e. " + "data-out)\n" + " from IFILE (default: stdin)\n" + " --nosense|-n Don't display sense information\n" + " --nvm|-N command is for NVM command set (e.g. " + "Read);\n" + " default, if NVMe fd, Admin command " + "set\n" + " --outfile=OFILE|-o OFILE Write binary data from device " + "(i.e. data-in)\n" + " to OFILE (def: hexdump to " + "stdout)\n" + " --raw|-w interpret CF (command file) as " + "binary (def:\n" + " interpret as ASCII hex)\n" + " --readonly|-R Open DEVICE read-only (default: " + "read-write)\n" + " --request=RLEN|-r RLEN Request up to RLEN bytes of data " + "(data-in)\n" + " --scan=FO,LO|-Q FO,LO scan command set from FO (first " + "opcode)\n" + " to LO (last opcode) inclusive. Uses " + "given\n" + " command bytes, varying the opcode\n" + " --send=SLEN|-s SLEN Send SLEN bytes of data (data-out)\n" + " --skip=KLEN|-k KLEN Skip the first KLEN bytes when " + "reading\n" + " data to send (default: 0)\n" + " --timeout=SECS|-t SECS Timeout in seconds (default: 20)\n" + " --verbose|-v Increase verbosity\n" + " --version|-V Show version information and exit\n" + "\n" + "Between 6 and 260 command bytes (two hex digits each) can be " + "specified\nand will be sent to DEVICE. Lengths RLEN, SLEN and " + "KLEN are decimal by\ndefault. Bidirectional commands " + "accepted.\n\nSimple example: Perform INQUIRY on /dev/sg0:\n" + " sg_raw -r 1k /dev/sg0 12 00 00 00 60 00\n"); +} + +static int +parse_cmd_line(struct opts_t * op, int argc, char *argv[]) +{ + while (1) { + int c, n; + const char * cp; + + c = getopt_long(argc, argv, "bc:C:ehi:k:nNo:Q:r:Rs:t:vVw", + long_options, NULL); + if (c == -1) + break; + + switch (c) { + case 'b': + op->datain_binary = true; + break; + case 'c': + op->cmd_file = optarg; + op->cmdfile_given = true; + break; + case 'C': + n = sg_get_num(optarg); + if ((n < 0) || (n > 2)) { + pr2serr("Invalid argument to --cmdset= expect 0, 1 or 2\n"); + return SG_LIB_SYNTAX_ERROR; + } + op->cmdset = n; + break; + case 'e': + op->do_enumerate = true; + break; + case 'h': + case '?': + op->do_help = true; + return 0; + case 'i': + if (op->dataout_file) { + pr2serr("Too many '--infile=' options\n"); + return SG_LIB_CONTRADICT; + } + op->dataout_file = optarg; + break; + case 'k': + n = sg_get_num(optarg); + if (n < 0) { + pr2serr("Invalid argument to '--skip'\n"); + return SG_LIB_SYNTAX_ERROR; + } + op->dataout_offset = n; + break; + case 'n': + op->no_sense = true; + break; + case 'N': + op->do_nvm = true; + break; + case 'o': + if (op->datain_file) { + pr2serr("Too many '--outfile=' options\n"); + return SG_LIB_CONTRADICT; + } + op->datain_file = optarg; + break; + case 'Q': /* --scan=FO,LO */ + cp = strchr(optarg, ','); + if (NULL == cp) { + pr2serr("--scan= expects two numbers, comma separated\n"); + return SG_LIB_SYNTAX_ERROR; + } + n = sg_get_num(optarg); + if ((n < 0) || (n > 255)) { + pr2serr("Invalid first number to --scan= expect 0 to 255\n"); + return SG_LIB_SYNTAX_ERROR; + } + op->scan_first = n; + n = sg_get_num(cp + 1); + if ((n < 0) || (n > 255)) { + pr2serr("Invalid second number to --scan= expect 0 to 255\n"); + return SG_LIB_SYNTAX_ERROR; + } + op->scan_last = n; + if (op->scan_first >= n) + pr2serr("Warning: scan range degenerate, ignore\n"); + break; + case 'r': + op->do_datain = true; + n = sg_get_num(optarg); + if (n < 0 || n > MAX_SCSI_DXLEN) { + pr2serr("Invalid argument to '--request'\n"); + return SG_LIB_SYNTAX_ERROR; + } + op->datain_len = n; + break; + case 'R': + ++op->readonly; + break; + case 's': + op->do_dataout = true; + n = sg_get_num(optarg); + if (n < 0 || n > MAX_SCSI_DXLEN) { + pr2serr("Invalid argument to '--send'\n"); + return SG_LIB_SYNTAX_ERROR; + } + op->dataout_len = n; + break; + case 't': + n = sg_get_num(optarg); + if (n < 0) { + pr2serr("Invalid argument to '--timeout'\n"); + return SG_LIB_SYNTAX_ERROR; + } + op->timeout = n; + break; + case 'v': + op->verbose_given = true; + ++op->verbose; + break; + case 'V': + op->version_given = true; + break; + case 'w': /* -r and -R already in use, this is --raw */ + ++op->raw; + break; + default: + return SG_LIB_SYNTAX_ERROR; + } + } + + if (op->version_given +#ifdef DEBUG + && ! op->verbose_given +#endif + ) + return 0; + + if (optind >= argc) { + pr2serr("No device specified\n"); + return SG_LIB_SYNTAX_ERROR; + } + op->device_name = argv[optind]; + ++optind; + + while (optind < argc) { + char *opt = argv[optind++]; + char *endptr; + int cmd = strtol(opt, &endptr, 16); + + if (*opt == '\0' || *endptr != '\0' || cmd < 0x00 || cmd > 0xff) { + pr2serr("Invalid command byte '%s'\n", opt); + return SG_LIB_SYNTAX_ERROR; + } + + if (op->cdb_length >= MAX_SCSI_CDBSZ) { + pr2serr("CDB too long (max. %d bytes)\n", MAX_SCSI_CDBSZ); + return SG_LIB_SYNTAX_ERROR; + } + op->cdb[op->cdb_length] = cmd; + ++op->cdb_length; + } + + if (op->cmdfile_given) { + int err; + + err = sg_f2hex_arr(op->cmd_file, (op->raw > 0) /* as_binary */, + false /* no_space */, op->cdb, &op->cdb_length, + MAX_SCSI_CDBSZ); + if (err) { + pr2serr("Unable to parse: %s as %s\n", op->cmd_file, + (op->raw > 0) ? "binary" : "hex"); + return SG_LIB_SYNTAX_ERROR; + } + if (op->verbose > 2) { + pr2serr("Read %d from %s . They are in hex:\n", op->cdb_length, + op->cmd_file); + hex2stderr(op->cdb, op->cdb_length, -1); + } + } + if (op->cdb_length < MIN_SCSI_CDBSZ) { + pr2serr("CDB too short (min. %d bytes)\n", MIN_SCSI_CDBSZ); + return SG_LIB_SYNTAX_ERROR; + } + if (op->do_enumerate || (op->verbose > 1)) { + bool is_scsi_cdb = sg_is_scsi_cdb(op->cdb, op->cdb_length); + int sa; + char b[80]; + + if ((1 == op->cmdset) && !is_scsi_cdb) { + is_scsi_cdb = true; + if (op->verbose > 3) + printf(">>> overriding cmdset guess to SCSI\n"); + } + if ((2 == op->cmdset) && is_scsi_cdb) { + is_scsi_cdb = false; + if (op->verbose > 3) + printf(">>> overriding cmdset guess to NVMe\n"); + } + if (is_scsi_cdb) { + if (op->cdb_length > 16) { + sa = sg_get_unaligned_be16(op->cdb + 8); + if ((0x7f != op->cdb[0]) && (0x7e != op->cdb[0])) + printf(">>> Unlikely to be SCSI CDB since all over 16 " + "bytes long should\n>>> start with 0x7f or " + "0x7e\n"); + } else + sa = op->cdb[1] & 0x1f; + sg_get_opcode_sa_name(op->cdb[0], sa, 0, sizeof(b), b); + printf("Attempt to decode cdb name: %s\n", b); + } else + printf(">>> Seems to be NVMe %s command\n", + sg_get_nvme_opcode_name(op->cdb[0], ! op->do_nvm, + sizeof(b), b)); + } + return 0; +} + +static int +skip(int fd, off_t offset) +{ + int err; + off_t remain; + char buffer[512]; + + if (lseek(fd, offset, SEEK_SET) >= 0) + return 0; + + // lseek failed; fall back to reading and discarding data + remain = offset; + while (remain > 0) { + ssize_t amount, done; + amount = (remain < (off_t)sizeof(buffer)) ? remain + : (off_t)sizeof(buffer); + done = read(fd, buffer, amount); + if (done < 0) { + err = errno; + perror("Error reading input data to skip"); + return sg_convert_errno(err); + } else if (done == 0) { + pr2serr("EOF on input file/stream\n"); + return SG_LIB_FILE_ERROR; + } else + remain -= done; + } + return 0; +} + +static uint8_t * +fetch_dataout(struct opts_t * op, uint8_t ** free_buf, int * errp) +{ + bool ok = false; + int fd, len, tot_len, boff, err; + uint8_t *buf = NULL; + + *free_buf = NULL; + if (errp) + *errp = 0; + if (op->dataout_file) { + fd = open(op->dataout_file, O_RDONLY); + if (fd < 0) { + err = errno; + if (errp) + *errp = sg_convert_errno(err); + perror(op->dataout_file); + goto bail; + } + } else + fd = STDIN_FILENO; + if (sg_set_binary_mode(fd) < 0) { + err = errno; + if (errp) + *errp = err; + perror("sg_set_binary_mode"); + goto bail; + } + + if (op->dataout_offset > 0) { + err = skip(fd, op->dataout_offset); + if (err != 0) { + if (errp) + *errp = err; + goto bail; + } + } + + tot_len = op->dataout_len; + buf = sg_memalign(tot_len, 0 /* page_size */, free_buf, false); + if (buf == NULL) { + pr2serr("sg_memalign: failed to get %d bytes of memory\n", tot_len); + if (errp) + *errp = sg_convert_errno(ENOMEM); + goto bail; + } + + for (boff = 0; boff < tot_len; boff += len) { + len = read(fd, buf + boff , tot_len - boff); + if (len < 0) { + err = errno; + if (errp) + *errp = sg_convert_errno(err); + perror("Failed to read input data"); + goto bail; + } else if (0 == len) { + if (errp) + *errp = SG_LIB_FILE_ERROR; + pr2serr("EOF on input file/stream at buffer offset %d\n", boff); + goto bail; + } + } + ok = true; + +bail: + if (fd >= 0 && fd != STDIN_FILENO) + close(fd); + if (! ok) { + if (*free_buf) { + free(*free_buf); + *free_buf = NULL; + } + return NULL; + } + return buf; +} + +static int +write_dataout(const char *filename, uint8_t *buf, int len) +{ + int ret = SG_LIB_FILE_ERROR; + int fd; + + if ((filename == NULL) || + ((1 == strlen(filename)) && ('-' == filename[0]))) + fd = STDOUT_FILENO; + else { + fd = creat(filename, 0666); + if (fd < 0) { + ret = sg_convert_errno(errno); + perror(filename); + goto bail; + } + } + if (sg_set_binary_mode(fd) < 0) { + perror("sg_set_binary_mode"); + goto bail; + } + + if (write(fd, buf, len) != len) { + ret = sg_convert_errno(errno); + perror(filename ? filename : "stdout"); + goto bail; + } + + ret = 0; + +bail: + if (fd >= 0 && fd != STDOUT_FILENO) + close(fd); + return ret; +} + + +int +main(int argc, char *argv[]) +{ + bool is_scsi_cdb = true; + bool do_scan = false; + int ret = 0; + int err = 0; + int res_cat, status, s_len, k, ret2; + int sg_fd = -1; + uint16_t sct_sc; + uint32_t result; + struct sg_pt_base *ptvp = NULL; + uint8_t sense_buffer[32] SG_C_CPP_ZERO_INIT; + uint8_t * dinp = NULL; + uint8_t * doutp = NULL; + uint8_t * free_buf_out = NULL; + uint8_t * wrkBuf = NULL; + struct opts_t opts; + struct opts_t * op; + char b[128]; + const int b_len = sizeof(b); + + op = &opts; + memset(op, 0, sizeof(opts)); + op->timeout = DEFAULT_TIMEOUT; + ret = parse_cmd_line(op, argc, argv); +#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) { + pr_version(); + goto done; + } + + if (ret != 0) { + pr2serr("\n"); /* blank line before outputting usage */ + usage(); + goto done; + } else if (op->do_help) { + usage(); + goto done; + } else if (op->do_enumerate) + goto done; + + sg_fd = scsi_pt_open_device(op->device_name, op->readonly, + op->verbose); + if (sg_fd < 0) { + pr2serr("%s: %s\n", op->device_name, safe_strerror(-sg_fd)); + ret = sg_convert_errno(-sg_fd); + goto done; + } + + ptvp = construct_scsi_pt_obj_with_fd(sg_fd, op->verbose); + if (ptvp == NULL) { + pr2serr("construct_scsi_pt_obj_with_fd() failed\n"); + ret = SG_LIB_CAT_OTHER; + goto done; + } + + if (op->scan_first < op->scan_last) + do_scan = true; + +and_again: + if (do_scan) { + op->cdb[0] = op->scan_first; + printf("Command bytes in hex:"); + for (k = 0; k < op->cdb_length; ++k) + printf(" %02x", op->cdb[k]); + printf("\n"); + } + + is_scsi_cdb = sg_is_scsi_cdb(op->cdb, op->cdb_length); + if ((1 == op->cmdset) && !is_scsi_cdb) + is_scsi_cdb = true; + else if ((2 == op->cmdset) && is_scsi_cdb) + is_scsi_cdb = false; + + if (op->do_dataout) { + uint32_t dout_len; + + doutp = fetch_dataout(op, &free_buf_out, &err); + if (doutp == NULL) { + ret = err; + goto done; + } + dout_len = op->dataout_len; + if (op->verbose > 2) + pr2serr("dxfer_buffer_out=%p, length=%d\n", + (void *)doutp, dout_len); + set_scsi_pt_data_out(ptvp, doutp, dout_len); + if (op->cmdfile_given) { + if (NVME_ADDR_DATA_OUT == + sg_get_unaligned_le64(op->cdb + SG_NVME_PT_ADDR)) + sg_put_unaligned_le64((uint64_t)(sg_uintptr_t)doutp, + op->cdb + SG_NVME_PT_ADDR); + if (NVME_DATA_LEN_DATA_OUT == + sg_get_unaligned_le32(op->cdb + SG_NVME_PT_DATA_LEN)) + sg_put_unaligned_le32(dout_len, + op->cdb + SG_NVME_PT_DATA_LEN); + } + } + if (op->do_datain) { + uint32_t din_len = op->datain_len; + + dinp = sg_memalign(din_len, 0 /* page_size */, &wrkBuf, false); + if (dinp == NULL) { + pr2serr("sg_memalign: failed to get %d bytes of memory\n", + din_len); + ret = sg_convert_errno(ENOMEM); + goto done; + } + if (op->verbose > 2) + pr2serr("dxfer_buffer_in=%p, length=%d\n", (void *)dinp, din_len); + set_scsi_pt_data_in(ptvp, dinp, din_len); + if (op->cmdfile_given) { + if (NVME_ADDR_DATA_IN == + sg_get_unaligned_le64(op->cdb + SG_NVME_PT_ADDR)) + sg_put_unaligned_le64((uint64_t)(sg_uintptr_t)dinp, + op->cdb + SG_NVME_PT_ADDR); + if (NVME_DATA_LEN_DATA_IN == + sg_get_unaligned_le32(op->cdb + SG_NVME_PT_DATA_LEN)) + sg_put_unaligned_le32(din_len, + op->cdb + SG_NVME_PT_DATA_LEN); + } + } + if (op->verbose) { + char d[128]; + + pr2serr(" %s to send: ", is_scsi_cdb ? "cdb" : "cmd"); + if (is_scsi_cdb) { + pr2serr("%s\n", sg_get_command_str(op->cdb, op->cdb_length, + op->verbose > 1, + sizeof(d), d)); + } else { /* If not SCSI cdb then treat as NVMe command */ + pr2serr("\n"); + hex2stderr(op->cdb, op->cdb_length, -1); + if (op->verbose > 1) + pr2serr(" Command name: %s\n", + sg_get_nvme_opcode_name(op->cdb[0], ! op->do_nvm, + b_len, b)); + } + } + set_scsi_pt_cdb(ptvp, op->cdb, op->cdb_length); + if (op->verbose > 2) + pr2serr("sense_buffer=%p, length=%d\n", (void *)sense_buffer, + (int)sizeof(sense_buffer)); + set_scsi_pt_sense(ptvp, sense_buffer, sizeof(sense_buffer)); + + if (op->do_nvm) + ret = do_nvm_pt(ptvp, 0, op->timeout, op->verbose); + else + ret = do_scsi_pt(ptvp, -1, op->timeout, op->verbose); + if (ret > 0) { + switch (ret) { + case SCSI_PT_DO_BAD_PARAMS: + pr2serr("do_scsi_pt: bad pass through setup\n"); + ret = SG_LIB_CAT_OTHER; + break; + case SCSI_PT_DO_TIMEOUT: + pr2serr("do_scsi_pt: timeout\n"); + ret = SG_LIB_CAT_TIMEOUT; + break; + case SCSI_PT_DO_NVME_STATUS: + sct_sc = (uint16_t)get_scsi_pt_status_response(ptvp); + pr2serr("NVMe Status: %s [0x%x]\n", + sg_get_nvme_cmd_status_str(sct_sc, b_len, b), sct_sc); + if (op->verbose) { + result = get_pt_result(ptvp); + pr2serr("NVMe Result=0x%x\n", result); + s_len = get_scsi_pt_sense_len(ptvp); + if ((op->verbose > 1) && (s_len > 0)) { + pr2serr("NVMe completion queue 4 DWords (as byte " + "string):\n"); + hex2stderr(sense_buffer, s_len, -1); + } + } + break; + case SCSI_PT_DO_NOT_SUPPORTED: + pr2serr("do_scsi_pt: not supported\n"); + ret = SG_LIB_CAT_TIMEOUT; + break; + default: + pr2serr("do_scsi_pt: unknown error: %d\n", ret); + ret = SG_LIB_CAT_OTHER; + break; + } + goto done; + } else if (ret < 0) { + k = -ret; + pr2serr("do_scsi_pt: %s\n", safe_strerror(k)); + err = get_scsi_pt_os_err(ptvp); + if ((err != 0) && (err != k)) + pr2serr(" ... or perhaps: %s\n", safe_strerror(err)); + ret = sg_convert_errno(err); + goto done; + } + + s_len = get_scsi_pt_sense_len(ptvp); + if (is_scsi_cdb) { + res_cat = get_scsi_pt_result_category(ptvp); + switch (res_cat) { + case SCSI_PT_RESULT_GOOD: + ret = 0; + break; + case SCSI_PT_RESULT_SENSE: + ret = sg_err_category_sense(sense_buffer, s_len); + break; + case SCSI_PT_RESULT_TRANSPORT_ERR: + get_scsi_pt_transport_err_str(ptvp, b_len, b); + pr2serr(">>> transport error: %s\n", b); + ret = SG_LIB_CAT_OTHER; + break; + case SCSI_PT_RESULT_OS_ERR: + get_scsi_pt_os_err_str(ptvp, b_len, b); + pr2serr(">>> os error: %s\n", b); + ret = SG_LIB_CAT_OTHER; + break; + default: + pr2serr(">>> unknown pass through result category (%d)\n", + res_cat); + ret = SG_LIB_CAT_OTHER; + break; + } + + status = get_scsi_pt_status_response(ptvp); + pr2serr("SCSI Status: "); + sg_print_scsi_status(status); + pr2serr("\n\n"); + if ((SAM_STAT_CHECK_CONDITION == status) && (! op->no_sense)) { + if (0 == s_len) + pr2serr(">>> Strange: status is CHECK CONDITION but no Sense " + "Information\n"); + else { + pr2serr("Sense Information:\n"); + sg_print_sense(NULL, sense_buffer, s_len, (op->verbose > 0)); + pr2serr("\n"); + } + } + if (SAM_STAT_RESERVATION_CONFLICT == status) + ret = SG_LIB_CAT_RES_CONFLICT; + } else { /* NVMe command */ + result = get_pt_result(ptvp); + pr2serr("NVMe Result=0x%x\n", result); + if (op->verbose && (s_len > 0)) { + pr2serr("NVMe completion queue 4 DWords (as byte string):\n"); + hex2stderr(sense_buffer, s_len, -1); + } + } + + if (op->do_datain) { + int data_len = op->datain_len - get_scsi_pt_resid(ptvp); + + if (ret && !(SG_LIB_CAT_RECOVERED == ret || + SG_LIB_CAT_NO_SENSE == ret)) + pr2serr("Error %d occurred, no data received\n", ret); + else if (data_len == 0) { + pr2serr("No data received\n"); + } else { + if (op->datain_file == NULL && !op->datain_binary) { + pr2serr("Received %d bytes of data:\n", data_len); + hex2stderr(dinp, data_len, 0); + } else { + const char * cp = "stdout"; + + if (op->datain_file && + ! ((1 == strlen(op->datain_file)) && + ('-' == op->datain_file[0]))) + cp = op->datain_file; + pr2serr("Writing %d bytes of data to %s\n", data_len, cp); + ret2 = write_dataout(op->datain_file, dinp, + data_len); + if (0 != ret2) { + if (0 == ret) + ret = ret2; + goto done; + } + } + } + } + +done: + if (do_scan) { + ++op->scan_first; + if (op->scan_first <= op->scan_last) { + clear_scsi_pt_obj(ptvp); + goto and_again; + } + } + + if (op->verbose && is_scsi_cdb) { + sg_get_category_sense_str(ret, b_len, b, op->verbose - 1); + pr2serr("%s\n", b); + } + if (wrkBuf) + free(wrkBuf); + if (free_buf_out) + free(free_buf_out); + if (ptvp) + destruct_scsi_pt_obj(ptvp); + if (sg_fd >= 0) + scsi_pt_close_device(sg_fd); + return ret >= 0 ? ret : SG_LIB_CAT_OTHER; +} |