aboutsummaryrefslogtreecommitdiff
path: root/src/sg_write_buffer.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sg_write_buffer.c')
-rw-r--r--src/sg_write_buffer.c597
1 files changed, 597 insertions, 0 deletions
diff --git a/src/sg_write_buffer.c b/src/sg_write_buffer.c
new file mode 100644
index 00000000..2b84323a
--- /dev/null
+++ b/src/sg_write_buffer.c
@@ -0,0 +1,597 @@
+/*
+ * Copyright (c) 2006-2021 Luben Tuikov and Douglas Gilbert.
+ * All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the BSD_LICENSE file.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_cmds_extra.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+#ifdef SG_LIB_WIN32
+#ifdef SG_LIB_WIN32_DIRECT
+#include "sg_pt.h" /* needed for scsi_pt_win32_direct() */
+#endif
+#endif
+
+/*
+ * This utility issues the SCSI WRITE BUFFER command to the given device.
+ */
+
+static const char * version_str = "1.30 20210610"; /* spc6r05 */
+
+#define ME "sg_write_buffer: "
+#define DEF_XFER_LEN (8 * 1024 * 1024)
+#define EBUFF_SZ 256
+
+#define WRITE_BUFFER_CMD 0x3b
+#define WRITE_BUFFER_CMDLEN 10
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define DEF_PT_TIMEOUT 300 /* 300 seconds, 5 minutes */
+
+static struct option long_options[] = {
+ {"bpw", required_argument, 0, 'b'},
+ {"dry-run", no_argument, 0, 'd'},
+ {"dry_run", no_argument, 0, 'd'},
+ {"help", no_argument, 0, 'h'},
+ {"id", required_argument, 0, 'i'},
+ {"in", required_argument, 0, 'I'},
+ {"length", required_argument, 0, 'l'},
+ {"mode", required_argument, 0, 'm'},
+ {"offset", required_argument, 0, 'o'},
+ {"read-stdin", no_argument, 0, 'r'},
+ {"read_stdin", no_argument, 0, 'r'},
+ {"raw", no_argument, 0, 'r'},
+ {"skip", required_argument, 0, 's'},
+ {"specific", required_argument, 0, 'S'},
+ {"timeout", required_argument, 0, 't' },
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: "
+ "sg_write_buffer [--bpw=CS] [--dry-run] [--help] [--id=ID] "
+ "[--in=FILE]\n"
+ " [--length=LEN] [--mode=MO] "
+ "[--offset=OFF]\n"
+ " [--read-stdin] [--skip=SKIP] "
+ "[--specific=MS]\n"
+ " [--timeout=TO] [--verbose] [--version] "
+ "DEVICE\n"
+ " where:\n"
+ " --bpw=CS|-b CS CS is chunk size: bytes per write "
+ "buffer\n"
+ " command (def: 0 -> as many as "
+ "possible)\n"
+ " --dry-run|-d skip WRITE BUFFER commands, do "
+ "everything else\n"
+ " --help|-h print out usage message then exit\n"
+ " --id=ID|-i ID buffer identifier (0 (default) to "
+ "255)\n"
+ " --in=FILE|-I FILE read from FILE ('-I -' read "
+ "from stdin)\n"
+ " --length=LEN|-l LEN length in bytes to write; may be "
+ "deduced from\n"
+ " FILE\n"
+ " --mode=MO|-m MO write buffer mode, MO is number or "
+ "acronym\n"
+ " (def: 0 -> 'combined header and "
+ "data' (obs))\n"
+ " --offset=OFF|-o OFF buffer offset (unit: bytes, def: 0)\n"
+ " --read-stdin|-r read from stdin (same as '-I -')\n"
+ " --skip=SKIP|-s SKIP bytes in file FILE to skip before "
+ "reading\n"
+ " --specific=MS|-S MS mode specific value; 3 bit field "
+ "(0 to 7)\n"
+ " --timeout=TO|-t TO command timeout in seconds (def: "
+ "300)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Performs one or more SCSI WRITE BUFFER commands. Use '-m xxx' "
+ "to list\navailable modes. A chunk size of 4 KB ('--bpw=4k') "
+ "seems to work well.\nExample: sg_write_buffer -b 4k -I xxx.lod "
+ "-m 7 /dev/sg3\n"
+ );
+
+}
+
+#define MODE_HEADER_DATA 0
+#define MODE_VENDOR 1
+#define MODE_DATA 2
+#define MODE_DNLD_MC 4
+#define MODE_DNLD_MC_SAVE 5
+#define MODE_DNLD_MC_OFFS 6
+#define MODE_DNLD_MC_OFFS_SAVE 7
+#define MODE_ECHO_BUFFER 0x0A
+#define MODE_DNLD_MC_EV_OFFS_DEFER 0x0D
+#define MODE_DNLD_MC_OFFS_DEFER 0x0E
+#define MODE_ACTIVATE_MC 0x0F
+#define MODE_EN_EX_ECHO 0x1A
+#define MODE_DIS_EX 0x1B
+#define MODE_DNLD_ERR_HISTORY 0x1C
+
+
+struct mode_s {
+ const char *mode_string;
+ int mode;
+ const char *comment;
+};
+
+static struct mode_s mode_arr[] = {
+ {"hd", MODE_HEADER_DATA, "combined header and data "
+ "(obsolete)"},
+ {"vendor", MODE_VENDOR, "vendor specific"},
+ {"data", MODE_DATA, "data"},
+ {"dmc", MODE_DNLD_MC, "download microcode and activate"},
+ {"dmc_save", MODE_DNLD_MC_SAVE, "download microcode, save and "
+ "activate"},
+ {"dmc_offs", MODE_DNLD_MC_OFFS, "download microcode with offsets "
+ "and activate"},
+ {"dmc_offs_save", MODE_DNLD_MC_OFFS_SAVE, "download microcode with "
+ "offsets, save and\n\t\t\t\tactivate"},
+ {"echo", MODE_ECHO_BUFFER, "write data to echo buffer"},
+ {"dmc_offs_ev_defer", MODE_DNLD_MC_EV_OFFS_DEFER, "download "
+ "microcode with offsets, select\n\t\t\t\tactivation event, "
+ "save and defer activation"},
+ {"dmc_offs_defer", MODE_DNLD_MC_OFFS_DEFER, "download microcode "
+ "with offsets, save and\n\t\t\t\tdefer activation"},
+ {"activate_mc", MODE_ACTIVATE_MC, "activate deferred microcode"},
+ {"en_ex", MODE_EN_EX_ECHO, "enable expander communications "
+ "protocol and\n\t\t\t\techo buffer (obsolete)"},
+ {"dis_ex", MODE_DIS_EX, "disable expander communications "
+ "protocol\n\t\t\t\t(obsolete)"},
+ {"deh", MODE_DNLD_ERR_HISTORY, "download application client "
+ "error history "},
+ {NULL, 0, NULL},
+};
+
+static void
+print_modes(void)
+{
+ const struct mode_s * mp;
+
+ pr2serr("The modes parameter argument can be numeric (hex or decimal)\n"
+ "or symbolic:\n");
+ for (mp = mode_arr; mp->mode_string; ++mp) {
+ pr2serr(" %2d (0x%02x) %-18s%s\n", mp->mode, mp->mode,
+ mp->mode_string, mp->comment);
+ }
+ pr2serr("\nAdditionally '--bpw=<val>,act' does a activate deferred "
+ "microcode after\nsuccessful dmc_offs_defer and "
+ "dmc_offs_ev_defer mode downloads.\n");
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool bpw_then_activate = false;
+ bool dry_run = false;
+ bool got_stdin = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ bool wb_len_given = false;
+ int infd, res, c, len, k, n;
+ int sg_fd = -1;
+ int bpw = 0;
+ int do_help = 0;
+ int ret = 0;
+ int verbose = 0;
+ int wb_id = 0;
+ int wb_len = 0;
+ int wb_mode = 0;
+ int wb_offset = 0;
+ int wb_skip = 0;
+ int wb_timeout = DEF_PT_TIMEOUT;
+ int wb_mspec = 0;
+ const char * device_name = NULL;
+ const char * file_name = NULL;
+ uint8_t * dop = NULL;
+ uint8_t * read_buf = NULL;
+ uint8_t * free_dop = NULL;
+ char * cp;
+ const struct mode_s * mp;
+ char ebuff[EBUFF_SZ];
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "b:dhi:I:l:m:o:rs:S:t:vV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'b':
+ bpw = sg_get_num(optarg);
+ if (bpw < 0) {
+ pr2serr("argument to '--bpw' should be in a positive "
+ "number\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((cp = strchr(optarg, ','))) {
+ if (0 == strncmp("act", cp + 1, 3))
+ bpw_then_activate = true;
+ }
+ break;
+ case 'd':
+ dry_run = true;
+ break;
+ case 'h':
+ case '?':
+ ++do_help;
+ break;
+ case 'i':
+ wb_id = sg_get_num(optarg);
+ if ((wb_id < 0) || (wb_id > 255)) {
+ pr2serr("argument to '--id' should be in the range 0 to "
+ "255\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'I':
+ file_name = optarg;
+ break;
+ case 'l':
+ wb_len = sg_get_num(optarg);
+ if (wb_len < 0) {
+ pr2serr("bad argument to '--length'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ wb_len_given = true;
+ break;
+ case 'm':
+ if (isdigit((uint8_t)*optarg)) {
+ wb_mode = sg_get_num(optarg);
+ if ((wb_mode < 0) || (wb_mode > 31)) {
+ pr2serr("argument to '--mode' should be in the range 0 "
+ "to 31\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else {
+ len = strlen(optarg);
+ for (mp = mode_arr; mp->mode_string; ++mp) {
+ if (0 == strncmp(mp->mode_string, optarg, len)) {
+ wb_mode = mp->mode;
+ break;
+ }
+ }
+ if (! mp->mode_string) {
+ print_modes();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ break;
+ case 'o':
+ wb_offset = sg_get_num(optarg);
+ if (wb_offset < 0) {
+ pr2serr("bad argument to '--offset'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'r': /* --read-stdin and --raw (previous name) */
+ file_name = "-";
+ break;
+ case 's':
+ wb_skip = sg_get_num(optarg);
+ if (wb_skip < 0) {
+ pr2serr("bad argument to '--skip'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'S':
+ wb_mspec = sg_get_num(optarg);
+ if ((wb_mspec < 0) || (wb_mspec > 7)) {
+ pr2serr("expected argument to '--specific' to be 0 to 7\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 't':
+ wb_timeout = sg_get_num(optarg);
+ if (wb_timeout < 0) {
+ pr2serr("Invalid argument to '--timeout'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("unrecognised option code 0x%x ??\n", c);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (do_help) {
+ if (do_help > 1) {
+ usage();
+ pr2serr("\n");
+ print_modes();
+ } else
+ usage();
+ return 0;
+ }
+ if (optind < argc) {
+ if (NULL == device_name) {
+ 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;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("Missing device name!\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ if ((wb_len > 0) && (bpw > wb_len)) {
+ pr2serr("trim chunk size (CS) to be the same as LEN\n");
+ bpw = wb_len;
+ }
+
+#ifdef SG_LIB_WIN32
+#ifdef SG_LIB_WIN32_DIRECT
+ if (verbose > 4)
+ pr2serr("Initial win32 SPT interface state: %s\n",
+ scsi_pt_win32_spt_state() ? "direct" : "indirect");
+ scsi_pt_win32_direct(SG_LIB_WIN32_DIRECT /* SPT pt interface */);
+#endif
+#endif
+
+ sg_fd = sg_cmds_open_device(device_name, false /* rw */, verbose);
+ if (sg_fd < 0) {
+ if (verbose)
+ pr2serr(ME "open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto err_out;
+ }
+ if (file_name || (wb_len > 0)) {
+ if (0 == wb_len)
+ wb_len = DEF_XFER_LEN;
+ dop = sg_memalign(wb_len, 0, &free_dop, false);
+ if (NULL == dop) {
+ pr2serr(ME "out of memory\n");
+ ret = sg_convert_errno(ENOMEM);
+ goto err_out;
+ }
+ memset(dop, 0xff, wb_len);
+ if (file_name) {
+ got_stdin = (0 == strcmp(file_name, "-"));
+ if (got_stdin) {
+ if (wb_skip > 0) {
+ pr2serr("Can't skip on stdin\n");
+ ret = SG_LIB_FILE_ERROR;
+ goto err_out;
+ }
+ infd = STDIN_FILENO;
+ } else {
+ if ((infd = open(file_name, O_RDONLY)) < 0) {
+ ret = sg_convert_errno(errno);
+ snprintf(ebuff, EBUFF_SZ,
+ ME "could not open %s for reading", file_name);
+ perror(ebuff);
+ goto err_out;
+ } else if (sg_set_binary_mode(infd) < 0)
+ perror("sg_set_binary_mode");
+ if (wb_skip > 0) {
+ if (lseek(infd, wb_skip, SEEK_SET) < 0) {
+ ret = sg_convert_errno(errno);
+ snprintf(ebuff, EBUFF_SZ, ME "couldn't skip to "
+ "required position on %s", file_name);
+ perror(ebuff);
+ close(infd);
+ goto err_out;
+ }
+ }
+ }
+ if (infd == STDIN_FILENO) {
+ if (NULL == (read_buf = (uint8_t *)malloc(DEF_XFER_LEN))) {
+ pr2serr(ME "out of memory\n");
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ res = read(infd, read_buf, DEF_XFER_LEN);
+ if (res < 0) {
+ snprintf(ebuff, EBUFF_SZ, ME "couldn't read from STDIN");
+ perror(ebuff);
+ ret = SG_LIB_FILE_ERROR;
+ goto err_out;
+ }
+ char * pch;
+ int val = 0;
+ res = 0;
+ pch = strtok((char*)read_buf, ",. \n\t");
+ while (pch != NULL) {
+ val = sg_get_num_nomult(pch);
+ if (val >= 0 && val < 255) {
+ dop[res] = val;
+ res++;
+ } else {
+ pr2serr("Data read from STDIO is wrong.\nPlease "
+ "input the data a byte at a time, the bytes "
+ "should be separated\nby either space, or "
+ "',' ( or by '.'), and the value per byte "
+ "should\nbe between 0~255. Hexadecimal "
+ "numbers should be preceded by either '0x' "
+ "or\n'OX' (or have a trailing 'h' or "
+ "'H').\n");
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ pch = strtok(NULL, ",. \n\t");
+ }
+ } else {
+ res = read(infd, dop, wb_len);
+ if (res < 0) {
+ ret = sg_convert_errno(errno);
+ snprintf(ebuff, EBUFF_SZ, ME "couldn't read from %s",
+ file_name);
+ perror(ebuff);
+ if (! got_stdin)
+ close(infd);
+ goto err_out;
+ }
+ }
+ if (res < wb_len) {
+ if (wb_len_given) {
+ pr2serr("tried to read %d bytes from %s, got %d bytes\n",
+ wb_len, file_name, res);
+ pr2serr("pad with 0xff bytes and continue\n");
+ } else {
+ if (verbose) {
+ pr2serr("tried to read %d bytes from %s, got %d "
+ "bytes\n", wb_len, file_name, res);
+ pr2serr("will write %d bytes", res);
+ if ((bpw > 0) && (bpw < wb_len))
+ pr2serr(", %d bytes per WRITE BUFFER command\n",
+ bpw);
+ else
+ pr2serr("\n");
+ }
+ wb_len = res;
+ }
+ }
+ if (! got_stdin)
+ close(infd);
+ }
+ }
+
+ res = 0;
+ if (bpw > 0) {
+ for (k = 0; k < wb_len; k += n) {
+ n = wb_len - k;
+ if (n > bpw)
+ n = bpw;
+ if (verbose)
+ pr2serr("sending write buffer, mode=0x%x, mspec=%d, id=%d, "
+ " offset=%d, len=%d\n", wb_mode, wb_mspec, wb_id,
+ wb_offset + k, n);
+ if (dry_run) {
+ if (verbose)
+ pr2serr("skipping WRITE BUFFER command due to "
+ "--dry-run\n");
+ res = 0;
+ } else
+ res = sg_ll_write_buffer_v2(sg_fd, wb_mode, wb_mspec, wb_id,
+ wb_offset + k, dop + k, n,
+ wb_timeout, true, verbose);
+ if (res)
+ break;
+ }
+ if (bpw_then_activate) {
+ if (verbose)
+ pr2serr("sending Activate deferred microcode [0xf]\n");
+ if (dry_run) {
+ if (verbose)
+ pr2serr("skipping WRITE BUFFER(ACTIVATE) command due to "
+ "--dry-run\n");
+ res = 0;
+ } else
+ res = sg_ll_write_buffer_v2(sg_fd, MODE_ACTIVATE_MC,
+ 0 /* buffer_id */,
+ 0 /* buffer_offset */, 0,
+ NULL, 0, wb_timeout, true,
+ verbose);
+ }
+ } else {
+ if (verbose)
+ pr2serr("sending single write buffer, mode=0x%x, mpsec=%d, "
+ "id=%d, offset=%d, len=%d\n", wb_mode, wb_mspec, wb_id,
+ wb_offset, wb_len);
+ if (dry_run) {
+ if (verbose)
+ pr2serr("skipping WRITE BUFFER(all in one) command due to "
+ "--dry-run\n");
+ res = 0;
+ } else
+ res = sg_ll_write_buffer_v2(sg_fd, wb_mode, wb_mspec, wb_id,
+ wb_offset, dop, wb_len, wb_timeout,
+ true, verbose);
+ }
+ if (0 != res) {
+ char b[80];
+
+ ret = res;
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("Write buffer failed: %s\n", b);
+ }
+
+err_out:
+ if (free_dop)
+ free(free_dop);
+ if (read_buf)
+ free(read_buf);
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_write_buffer failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}