blob: a0254cd935ac8b7e82207ec9279778b91fcfec23 [file] [log] [blame]
/*
* 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;
}