| /* |
| * A utility program originally written for the Linux OS SCSI subsystem. |
| * |
| * Copyright (C) 2000-2022 Ingo van Lil <[email protected]> |
| * |
| * 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 <[email protected]>\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; |
| } |