aboutsummaryrefslogtreecommitdiff
path: root/sg_raw.c
diff options
context:
space:
mode:
Diffstat (limited to 'sg_raw.c')
-rw-r--r--sg_raw.c448
1 files changed, 448 insertions, 0 deletions
diff --git a/sg_raw.c b/sg_raw.c
new file mode 100644
index 00000000..b68a6773
--- /dev/null
+++ b/sg_raw.c
@@ -0,0 +1,448 @@
+/*
+ * A utility program originally written for the Linux OS SCSI subsystem.
+ *
+ * Copyright (C) 2000-2007 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.
+ *
+ * This program can be used to send raw SCSI commands (with an optional
+ * data phase) through a Generic SCSI interface.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <getopt.h>
+
+#include "sg_lib.h"
+#include "sg_pt.h"
+
+#define VERSION "0.3.4 (2007-04-03)"
+
+#define DEFAULT_TIMEOUT 20
+#define MIN_SCSI_CDBSZ 6
+#define MAX_SCSI_CDBSZ 16
+#define MAX_SCSI_DXLEN (64 * 1024)
+
+static struct option long_options[] = {
+ { "binary", no_argument, NULL, 'b' },
+ { "help", no_argument, NULL, 'h' },
+ { "infile", required_argument, NULL, 'i' },
+ { "skip", required_argument, NULL, 'k' },
+ { "nosense", no_argument, NULL, 'n' },
+ { "outfile", required_argument, NULL, 'o' },
+ { "request", required_argument, NULL, 'r' },
+ { "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 {
+ char *device_name;
+ unsigned char cdb[MAX_SCSI_CDBSZ];
+ int cdb_length;
+ int do_datain;
+ int datain_len;
+ const char *datain_file;
+ int datain_binary;
+ int do_dataout;
+ int dataout_len;
+ const char *dataout_file;
+ off_t dataout_offset;
+ int timeout;
+ int no_sense;
+ int do_help;
+ int do_verbose;
+ int do_version;
+};
+
+static void version()
+{
+ fprintf(stderr,
+ "sg_raw " VERSION "\n"
+ "Copyright (C) 2007 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 "
+ "<http://www.gnu.org/licenses/gpl.html>.\n"
+ "There is NO WARRANTY, to the extent permitted by law.\n");
+}
+
+static void usage()
+{
+ fprintf(stderr,
+ "Usage: sg_raw [OPTION] DEVICE CDB0 CDB1 ...\n"
+ "\n"
+ "Options:\n"
+ " -b, --binary Dump data in binary form, even when "
+ "writing to stdout\n"
+ " -h, --help Show this message and exit\n"
+ " -i, --infile=FILE Read data to send from FILE (default: "
+ "stdin)\n"
+ " -k, --skip=LEN Skip the first LEN bytes when reading "
+ "data to send\n"
+ " -n, --nosense Don't display sense information\n"
+ " -o, --outfile=FILE Write data to FILE (default: hexdump "
+ "to stdout)\n"
+ " -r, --request=LEN Request up to LEN bytes of data\n"
+ " -s, --send=LEN Send LEN bytes of data\n"
+ " -t, --timeout=SEC Timeout in seconds (default: 20)\n"
+ " -v, --verbose Increase verbosity\n"
+ " -V, --version Show version information and exit\n"
+ "\n"
+ "Between 6 and 16 command bytes (two hex digits each) can be\n"
+ "specified and will be sent to DEVICE.\n"
+ "\n"
+ "Example: Perform INQUIRY on /dev/sg0:\n"
+ " sg_raw -r 1k /dev/sg0 12 00 00 00 60 00\n");
+}
+
+static int process_cl(struct opts_t *optsp, int argc, char *argv[])
+{
+ while (1) {
+ int c, n;
+
+ c = getopt_long(argc, argv, "r:o:bs:i:k:t:nvhV", long_options, NULL);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'r':
+ optsp->do_datain = 1;
+ n = sg_get_num(optarg);
+ if (n < 0 || n > MAX_SCSI_DXLEN) {
+ fprintf(stderr, "Invalid argument to '--request'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ optsp->datain_len = n;
+ break;
+ case 'o':
+ if (optsp->datain_file) {
+ fprintf(stderr, "Too many '--outfile=' options\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ optsp->datain_file = optarg;
+ break;
+ case 'b':
+ optsp->datain_binary = 1;
+ break;
+ case 's':
+ optsp->do_dataout = 1;
+ n = sg_get_num(optarg);
+ if (n < 0 || n > MAX_SCSI_DXLEN) {
+ fprintf(stderr, "Invalid argument to '--send'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ optsp->dataout_len = n;
+ break;
+ case 'i':
+ if (optsp->dataout_file) {
+ fprintf(stderr, "Too many '--infile=' options\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ optsp->dataout_file = optarg;
+ break;
+ case 'k':
+ n = sg_get_num(optarg);
+ if (n < 0) {
+ fprintf(stderr, "Invalid argument to '--skip'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ optsp->dataout_offset = n;
+ break;
+ case 't':
+ n = sg_get_num(optarg);
+ if (n < 0) {
+ fprintf(stderr, "Invalid argument to '--timeout'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ optsp->timeout = n;
+ break;
+ case 'n':
+ optsp->no_sense = 1;
+ break;
+ case 'v':
+ ++optsp->do_verbose;
+ break;
+ case 'h':
+ case '?':
+ optsp->do_help = 1;
+ return 0;
+ case 'V':
+ optsp->do_version = 1;
+ return 0;
+ default:
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+ if (optsp->do_datain && optsp->do_dataout) {
+ fprintf(stderr, "Can't use '--request' and '--send' together\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ if (optind >= argc) {
+ fprintf(stderr, "No device specified\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ optsp->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) {
+ fprintf(stderr, "Invalid command byte '%s'\n", opt);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ if (optsp->cdb_length > MAX_SCSI_CDBSZ) {
+ fprintf(stderr, "CDB too long (max. %d bytes)\n", MAX_SCSI_CDBSZ);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ optsp->cdb[optsp->cdb_length] = cmd;
+ ++optsp->cdb_length;
+ }
+
+ if (optsp->cdb_length < MIN_SCSI_CDBSZ) {
+ fprintf(stderr, "CDB too short (min. %d bytes)\n", MIN_SCSI_CDBSZ);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ return 0;
+}
+
+static int skip(int fd, off_t offset)
+{
+ 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) {
+ perror("Error reading input data");
+ return SG_LIB_CAT_OTHER;
+ } else if (done == 0) {
+ fprintf(stderr, "EOF on input file/stream\n");
+ return SG_LIB_CAT_OTHER;
+ } else {
+ remain -= done;
+ }
+ }
+
+ return 0;
+}
+
+static unsigned char *fetch_dataout(struct opts_t *optsp)
+{
+ unsigned char *buf = NULL;
+ int fd, len;
+ int ok = 0;
+
+ if (optsp->dataout_file) {
+ fd = open(optsp->dataout_file, O_RDONLY);
+ if (fd < 0) {
+ perror(optsp->dataout_file);
+ goto bail;
+ }
+
+ } else {
+ fd = STDIN_FILENO;
+ }
+
+ if (optsp->dataout_offset > 0) {
+ if (skip(fd, optsp->dataout_offset) != 0) {
+ goto bail;
+ }
+ }
+
+ buf = (unsigned char *)malloc(optsp->dataout_len);
+ if (buf == NULL) {
+ perror("malloc");
+ goto bail;
+ }
+
+ len = read(fd, buf, optsp->dataout_len);
+ if (len < 0) {
+ perror("Failed to read input data");
+ goto bail;
+ } else if (len < optsp->dataout_len) {
+ fprintf(stderr, "EOF on input file/stream\n");
+ goto bail;
+ }
+
+ ok = 1;
+
+bail:
+ if (fd >= 0 && fd != STDIN_FILENO)
+ close(fd);
+ if (!ok) {
+ free(buf);
+ return NULL;
+ }
+ return buf;
+}
+
+static int write_dataout(const char *filename, unsigned char *buf, int len)
+{
+ int ret = SG_LIB_CAT_OTHER;
+ int fd;
+
+ if (filename != NULL) {
+ fd = creat(filename, 0666);
+ if (fd < 0) {
+ perror(filename);
+ goto bail;
+ }
+ } else {
+ fd = STDOUT_FILENO;
+ }
+
+ if (write(fd, buf, len) != len) {
+ perror(filename? filename : "stdout");
+ goto bail;
+ }
+
+ ret = 0;
+
+bail:
+ if (fd >= 0)
+ close(fd);
+ return ret;
+}
+
+int main(int argc, char *argv[])
+{
+ int ret = 0;
+ int res_cat;
+ int slen;
+ struct opts_t opts;
+ int sg_fd = -1;
+ struct sg_pt_base *ptvp = NULL;
+ unsigned char sense_buffer[32];
+ unsigned char *dxfer_buffer = NULL;
+
+ memset(&opts, 0, sizeof(opts));
+ opts.timeout = DEFAULT_TIMEOUT;
+ ret = process_cl(&opts, argc, argv);
+ if (ret != 0) {
+ usage();
+ goto done;
+ } else if (opts.do_help) {
+ usage();
+ goto done;
+ } else if (opts.do_version) {
+ version();
+ goto done;
+ }
+
+ sg_fd = scsi_pt_open_device(opts.device_name, 0 /* RDWR */,
+ opts.do_verbose);
+ if (sg_fd < 0) {
+ fprintf(stderr, "%s: %s\n", opts.device_name, safe_strerror(-sg_fd));
+ ret = SG_LIB_FILE_ERROR;
+ goto done;
+ }
+
+ ptvp = construct_scsi_pt_obj();
+ if (ptvp == NULL) {
+ fprintf(stderr, "out of memory\n");
+ goto done;
+ }
+ set_scsi_pt_cdb(ptvp, opts.cdb, opts.cdb_length);
+ set_scsi_pt_sense(ptvp, sense_buffer, sizeof(sense_buffer));
+
+ if (opts.do_dataout) {
+ dxfer_buffer = fetch_dataout(&opts);
+ if (dxfer_buffer == NULL) {
+ ret = SG_LIB_CAT_OTHER;
+ goto done;
+ }
+ set_scsi_pt_data_out(ptvp, dxfer_buffer, opts.dataout_len);
+ } else if (opts.do_datain) {
+ dxfer_buffer = (unsigned char *)malloc(opts.datain_len);
+ if (dxfer_buffer == NULL) {
+ perror("malloc");
+ ret = SG_LIB_CAT_OTHER;
+ goto done;
+ }
+ set_scsi_pt_data_in(ptvp, dxfer_buffer, opts.datain_len);
+ }
+
+ ret = do_scsi_pt(ptvp, sg_fd, opts.timeout, opts.do_verbose);
+ if (ret > 0) {
+ if (SCSI_PT_DO_BAD_PARAMS == ret) {
+ fprintf(stderr, "do_scsi_pt: bad pass through setup\n");
+ ret = SG_LIB_CAT_OTHER;
+ } else if (SCSI_PT_DO_TIMEOUT == ret) {
+ fprintf(stderr, "do_scsi_pt: timeout\n");
+ ret = SG_LIB_CAT_TIMEOUT;
+ } else
+ ret = SG_LIB_CAT_OTHER;
+ goto done;
+ } else if (ret < 0) {
+ fprintf(stderr, "do_scsi_pt: %s\n", safe_strerror(-sg_fd));
+ ret = SG_LIB_CAT_OTHER;
+ goto done;
+ }
+ slen = 0;
+ res_cat = get_scsi_pt_result_category(ptvp);
+ if (SCSI_PT_RESULT_GOOD == res_cat)
+ ret = 0;
+ else if (SCSI_PT_RESULT_SENSE == res_cat) {
+ slen = get_scsi_pt_sense_len(ptvp);
+ ret = sg_err_category_sense(sense_buffer, slen);
+ } else
+ ret = SG_LIB_CAT_OTHER;
+
+ fprintf(stderr, "SCSI Status: ");
+ sg_print_scsi_status(get_scsi_pt_status_response(ptvp));
+ fprintf(stderr, "\n\n");
+ if (!opts.no_sense) {
+ fprintf(stderr, "Sense Information:\n");
+ sg_print_sense(NULL, sense_buffer, slen, (opts.do_verbose > 0));
+ fprintf(stderr, "\n");
+ }
+
+ if (opts.do_datain) {
+ int data_len = opts.datain_len - get_scsi_pt_resid(ptvp);
+ if (data_len == 0) {
+ fprintf(stderr, "No data received\n");
+ } else {
+ if (opts.datain_file == NULL && !opts.datain_binary) {
+ fprintf(stderr, "Received %d bytes of data:\n", data_len);
+ dStrHex((const char *)dxfer_buffer, data_len, 0);
+ } else {
+ fprintf(stderr, "Writing %d bytes of data to %s\n", data_len,
+ opts.datain_file? opts.datain_file : "stdout");
+ ret = write_dataout(opts.datain_file, dxfer_buffer, data_len);
+ if (ret != 0)
+ goto done;
+ }
+ }
+ }
+
+done:
+ free(dxfer_buffer);
+ if (ptvp) destruct_scsi_pt_obj(ptvp);
+ if (sg_fd >= 0) scsi_pt_close_device(sg_fd);
+ return ret;
+}
+