aboutsummaryrefslogtreecommitdiff
path: root/src/sg_compare_and_write.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sg_compare_and_write.c')
-rw-r--r--src/sg_compare_and_write.c623
1 files changed, 623 insertions, 0 deletions
diff --git a/src/sg_compare_and_write.c b/src/sg_compare_and_write.c
new file mode 100644
index 00000000..b8ed82df
--- /dev/null
+++ b/src/sg_compare_and_write.c
@@ -0,0 +1,623 @@
+/*
+* Copyright (c) 2012-2022, Kaminario Technologies LTD
+* All rights reserved.
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are met:
+* * Redistributions of source code must retain the above copyright
+* notice, this list of conditions and the following disclaimer.
+* * Redistributions in binary form must reproduce the above copyright
+* notice, this list of conditions and the following disclaimer in the
+* documentation and/or other materials provided with the distribution.
+* * Neither the name of the <organization> nor the
+* names of its contributors may be used to endorse or promote products
+* derived from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+* ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * This command performs a SCSI COMPARE AND WRITE. See SBC-3 at
+ * https://www.t10.org
+ *
+ */
+
+#ifndef __sun
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+#endif
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <getopt.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_pt.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+static const char * version_str = "1.32 20220127";
+
+#define DEF_BLOCK_SIZE 512
+#define DEF_NUM_BLOCKS (1)
+#define DEF_BLOCKS_PER_TRANSFER 8
+#define DEF_TIMEOUT_SECS 60
+
+#define COMPARE_AND_WRITE_OPCODE (0x89)
+#define COMPARE_AND_WRITE_CDB_SIZE (16)
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+
+#define ME "sg_compare_and_write: "
+
+static struct option long_options[] = {
+ {"dpo", no_argument, 0, 'd'},
+ {"fua", no_argument, 0, 'f'},
+ {"fua_nv", no_argument, 0, 'F'},
+ {"fua-nv", no_argument, 0, 'F'},
+ {"group", required_argument, 0, 'g'},
+ {"grpnum", required_argument, 0, 'g'},
+ {"help", no_argument, 0, 'h'},
+ {"in", required_argument, 0, 'i'},
+ {"inc", required_argument, 0, 'C'},
+ {"inw", required_argument, 0, 'D'},
+ {"lba", required_argument, 0, 'l'},
+ {"num", required_argument, 0, 'n'},
+ {"quiet", no_argument, 0, 'q'},
+ {"timeout", required_argument, 0, 't'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {"wrprotect", required_argument, 0, 'w'},
+ {"xferlen", required_argument, 0, 'x'},
+ {0, 0, 0, 0},
+};
+
+struct caw_flags {
+ bool dpo;
+ bool fua;
+ bool fua_nv;
+ int group;
+ int wrprotect;
+};
+
+struct opts_t {
+ bool quiet;
+ bool verbose_given;
+ bool version_given;
+ bool wfn_given;
+ int numblocks;
+ int verbose;
+ int timeout;
+ int xfer_len;
+ uint64_t lba;
+ const char * ifn;
+ const char * wfn;
+ const char * device_name;
+ struct caw_flags flags;
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_compare_and_write [--dpo] [--fua] [--fua_nv] "
+ "[--grpnum=GN] [--help]\n"
+ " --in=IF|--inc=IF [--inw=WF] "
+ "--lba=LBA "
+ "[--num=NUM]\n"
+ " [--quiet] [--timeout=TO] "
+ "[--verbose] [--version]\n"
+ " [--wrprotect=WP] [--xferlen=LEN] "
+ "DEVICE\n"
+ " where:\n"
+ " --dpo|-d set the dpo bit in cdb (def: "
+ "clear)\n"
+ " --fua|-f set the fua bit in cdb (def: "
+ "clear)\n"
+ " --fua_nv|-F set the fua_nv bit in cdb (def: "
+ "clear)\n"
+ " --grpnum=GN|-g GN GN is GROUP NUMBER to set in "
+ "cdb (def: 0)\n"
+ " --help|-h print out usage message\n"
+ " --in=IF|-i IF IF is a file containing a compare "
+ "buffer and\n"
+ " optionally a write buffer (when "
+ "--inw=WF is\n"
+ " not given)\n"
+ " --inc=IF|-C IF The same as the --in option\n"
+ " --inw=WF|-D WF WF is a file containing a write "
+ "buffer\n"
+ " --lba=LBA|-l LBA LBA of the first block to compare "
+ "and write\n"
+ " --num=NUM|-n NUM number of blocks to "
+ "compare/write (def: 1)\n"
+ " --quiet|-q suppress MISCOMPARE report to "
+ "stderr,\n"
+ " still sets exit status of 14\n"
+ " --timeout=TO|-t TO timeout for the command "
+ "(def: 60 secs)\n"
+ " --verbose|-v increase verbosity (use '-vv' for "
+ "more)\n"
+ " --version|-V print version string then exit\n"
+ " --wrprotect=WP|-w WP write protect information "
+ "(def: 0)\n"
+ " --xferlen=LEN|-x LEN number of bytes to transfer. "
+ "Default is\n"
+ " (2 * NUM * 512) or 1024 when "
+ "NUM is 1\n"
+ "\n"
+ "Performs a SCSI COMPARE AND WRITE operation. Sends a double "
+ "size\nbuffer, the first half is used to compare what is at "
+ "LBA for NUM\nblocks. If and only if the comparison is "
+ "equal, then the second\nhalf of the buffer is written to "
+ "LBA for NUM blocks.\n");
+}
+
+static int
+parse_args(int argc, char* argv[], struct opts_t * op)
+{
+ bool lba_given = false;
+ bool if_given = false;
+ int c;
+ int64_t ll;
+
+ op->numblocks = DEF_NUM_BLOCKS;
+ /* COMPARE AND WRITE defines 2*buffers compare + write */
+ op->xfer_len = 0;
+ op->timeout = DEF_TIMEOUT_SECS;
+ op->device_name = NULL;
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "C:dD:fFg:hi:l:n:qt:vVw:x:",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'C':
+ case 'i':
+ op->ifn = optarg;
+ if_given = true;
+ break;
+ case 'd':
+ op->flags.dpo = true;
+ break;
+ case 'D':
+ op->wfn = optarg;
+ op->wfn_given = true;
+ break;
+ case 'F':
+ op->flags.fua_nv = true;
+ break;
+ case 'f':
+ op->flags.fua = true;
+ break;
+ case 'g':
+ op->flags.group = sg_get_num(optarg);
+ if ((op->flags.group < 0) ||
+ (op->flags.group > 63)) {
+ pr2serr("argument to '--grpnum=' expected to "
+ "be 0 to 63\n");
+ goto out_err_no_usage;
+ }
+ break;
+ case 'h':
+ case '?':
+ usage();
+ exit(0);
+ case 'l':
+ ll = sg_get_llnum(optarg);
+ if (-1 == ll) {
+ pr2serr("bad argument to '--lba'\n");
+ goto out_err_no_usage;
+ }
+ op->lba = (uint64_t)ll;
+ lba_given = true;
+ break;
+ case 'n':
+ op->numblocks = sg_get_num(optarg);
+ if ((op->numblocks < 0) || (op->numblocks > 255)) {
+ pr2serr("bad argument to '--num', expect 0 "
+ "to 255\n");
+ goto out_err_no_usage;
+ }
+ break;
+ case 'q':
+ op->quiet = true;
+ break;
+ case 't':
+ op->timeout = sg_get_num(optarg);
+ if (op->timeout < 0) {
+ pr2serr("bad argument to '--timeout'\n");
+ goto out_err_no_usage;
+ }
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case 'w':
+ op->flags.wrprotect = sg_get_num(optarg);
+ if (op->flags.wrprotect >> 3) {
+ pr2serr("bad argument to '--wrprotect' not "
+ "in range 0-7\n");
+ goto out_err_no_usage;
+ }
+ break;
+ case 'x':
+ op->xfer_len = sg_get_num(optarg);
+ if (op->xfer_len < 0) {
+ pr2serr("bad argument to '--xferlen'\n");
+ goto out_err_no_usage;
+ }
+ break;
+ default:
+ pr2serr("unrecognised option code 0x%x ??\n", c);
+ goto out_err;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == op->device_name) {
+ op->device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n",
+ argv[optind]);
+ goto out_err;
+ }
+ }
+ if (op->version_given && (! op->verbose_given))
+ return 0;
+ if (NULL == op->device_name) {
+ pr2serr("missing device name!\n");
+ goto out_err;
+ }
+ if (! if_given) {
+ pr2serr("missing input file\n");
+ goto out_err;
+ }
+ if (! lba_given) {
+ pr2serr("missing lba\n");
+ goto out_err;
+ }
+ if (0 == op->xfer_len)
+ op->xfer_len = 2 * op->numblocks * DEF_BLOCK_SIZE;
+ return 0;
+
+out_err:
+ usage();
+
+out_err_no_usage:
+ exit(1);
+}
+
+#define FLAG_FUA (0x8)
+#define FLAG_FUA_NV (0x2)
+#define FLAG_DPO (0x10)
+#define WRPROTECT_MASK (0x7)
+#define WRPROTECT_SHIFT (5)
+
+static int
+sg_build_scsi_cdb(uint8_t * cdbp, unsigned int blocks,
+ int64_t start_block, struct caw_flags flags)
+{
+ memset(cdbp, 0, COMPARE_AND_WRITE_CDB_SIZE);
+ cdbp[0] = COMPARE_AND_WRITE_OPCODE;
+ cdbp[1] = (flags.wrprotect & WRPROTECT_MASK) << WRPROTECT_SHIFT;
+ if (flags.dpo)
+ cdbp[1] |= FLAG_DPO;
+ if (flags.fua)
+ cdbp[1] |= FLAG_FUA;
+ if (flags.fua_nv)
+ cdbp[1] |= FLAG_FUA_NV;
+ sg_put_unaligned_be64((uint64_t)start_block, cdbp + 2);
+ /* cdbp[10-12] are reserved */
+ cdbp[13] = (uint8_t)(blocks & 0xff);
+ cdbp[14] = (uint8_t)(flags.group & GRPNUM_MASK);
+ return 0;
+}
+
+/* Returns 0 for success, SG_LIB_CAT_MISCOMPARE if compare fails,
+ * various other SG_LIB_CAT_*, otherwise -1 . */
+static int
+sg_ll_compare_and_write(int sg_fd, uint8_t * buff, int blocks,
+ int64_t lba, int xfer_len, struct caw_flags flags,
+ bool noisy, int verbose)
+{
+ bool valid;
+ int sense_cat, slen, res, ret;
+ uint64_t ull = 0;
+ struct sg_pt_base * ptvp;
+ uint8_t cawCmd[COMPARE_AND_WRITE_CDB_SIZE];
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+
+ if (sg_build_scsi_cdb(cawCmd, blocks, lba, flags)) {
+ pr2serr(ME "bad cdb build, lba=0x%" PRIx64 ", blocks=%d\n",
+ lba, blocks);
+ return -1;
+ }
+ ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp) {
+ pr2serr("Could not construct scsit_pt_obj, out of memory\n");
+ return -1;
+ }
+
+ set_scsi_pt_cdb(ptvp, cawCmd, COMPARE_AND_WRITE_CDB_SIZE);
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_out(ptvp, buff, xfer_len);
+ if (verbose > 1) {
+ char b[128];
+
+ pr2serr(" Compare and write cdb: %s\n",
+ sg_get_command_str(cawCmd, COMPARE_AND_WRITE_CDB_SIZE, false,
+ sizeof(b), b));
+ }
+ if ((verbose > 2) && (xfer_len > 0)) {
+ pr2serr(" Data-out buffer contents:\n");
+ hex2stderr(buff, xfer_len, 1);
+ }
+ res = do_scsi_pt(ptvp, sg_fd, DEF_TIMEOUT_SECS, verbose);
+ ret = sg_cmds_process_resp(ptvp, "COMPARE AND WRITE", res,
+ noisy, verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ case SG_LIB_CAT_MEDIUM_HARD:
+ slen = get_scsi_pt_sense_len(ptvp);
+ valid = sg_get_sense_info_fld(sense_b, slen,
+ &ull);
+ if (valid)
+ pr2serr("Medium or hardware error starting "
+ "at lba=%" PRIu64 " [0x%" PRIx64
+ "]\n", ull, ull);
+ else
+ pr2serr("Medium or hardware error\n");
+ ret = sense_cat;
+ break;
+ case SG_LIB_CAT_MISCOMPARE:
+ ret = sense_cat;
+ if (! (noisy || verbose))
+ break;
+ slen = get_scsi_pt_sense_len(ptvp);
+ valid = sg_get_sense_info_fld(sense_b, slen, &ull);
+ if (valid)
+ pr2serr("Miscompare at byte offset: %" PRIu64
+ " [0x%" PRIx64 "]\n", ull, ull);
+ else
+ pr2serr("Miscompare reported\n");
+ break;
+ case SG_LIB_CAT_ILLEGAL_REQ:
+ if (verbose)
+ sg_print_command_len(cawCmd,
+ COMPARE_AND_WRITE_CDB_SIZE);
+ /* FALL THROUGH */
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+static int
+open_if(const char * fn, bool got_stdin)
+{
+ int fd;
+
+ if (got_stdin)
+ fd = STDIN_FILENO;
+ else {
+ fd = open(fn, O_RDONLY);
+ if (fd < 0) {
+ pr2serr(ME "open error: %s: %s\n", fn,
+ safe_strerror(errno));
+ return -SG_LIB_FILE_ERROR;
+ }
+ }
+ if (sg_set_binary_mode(fd) < 0) {
+ perror("sg_set_binary_mode");
+ return -SG_LIB_FILE_ERROR;
+ }
+ return fd;
+}
+
+static int
+open_dev(const char * outf, int verbose)
+{
+ int sg_fd = sg_cmds_open_device(outf, false /* rw */, verbose);
+
+ if ((sg_fd < 0) && verbose)
+ pr2serr(ME "open error: %s: %s\n", outf,
+ safe_strerror(-sg_fd));
+ return sg_fd;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool ifn_stdin;
+ int res, half_xlen, vb;
+ int infd = -1;
+ int wfd = -1;
+ int devfd = -1;
+ uint8_t * wrkBuff = NULL;
+ uint8_t * free_wrkBuff = NULL;
+ struct opts_t * op;
+ struct opts_t opts;
+
+ op = &opts;
+ memset(op, 0, sizeof(opts));
+ res = parse_args(argc, argv, op);
+ if (res != 0) {
+ pr2serr("Failed parsing args\n");
+ goto out;
+ }
+
+#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) {
+ pr2serr(ME "version: %s\n", version_str);
+ return 0;
+ }
+ vb = op->verbose;
+
+ if (vb) {
+ pr2serr("Running COMPARE AND WRITE command with the "
+ "following options:\n in=%s ", op->ifn);
+ if (op->wfn_given)
+ pr2serr("inw=%s ", op->wfn);
+ pr2serr("device=%s\n lba=0x%" PRIx64 " num_blocks=%d "
+ "xfer_len=%d timeout=%d\n", op->device_name,
+ op->lba, op->numblocks, op->xfer_len, op->timeout);
+ }
+ ifn_stdin = ((1 == strlen(op->ifn)) && ('-' == op->ifn[0]));
+ infd = open_if(op->ifn, ifn_stdin);
+ if (infd < 0) {
+ res = -infd;
+ goto out;
+ }
+ if (op->wfn_given) {
+ if ((1 == strlen(op->wfn)) && ('-' == op->wfn[0])) {
+ pr2serr(ME "don't allow stdin for write file\n");
+ res = SG_LIB_FILE_ERROR;
+ goto out;
+ }
+ wfd = open_if(op->wfn, false);
+ if (wfd < 0) {
+ res = -wfd;
+ goto out;
+ }
+ }
+
+ devfd = open_dev(op->device_name, vb);
+ if (devfd < 0) {
+ res = sg_convert_errno(-devfd);
+ goto out;
+ }
+
+ wrkBuff = (uint8_t *)sg_memalign(op->xfer_len, 0, &free_wrkBuff,
+ vb > 3);
+ if (NULL == wrkBuff) {
+ pr2serr("Not enough user memory\n");
+ res = sg_convert_errno(ENOMEM);
+ goto out;
+ }
+
+ if (op->wfn_given) {
+ half_xlen = op->xfer_len / 2;
+ res = read(infd, wrkBuff, half_xlen);
+ if (res < 0) {
+ pr2serr("Could not read from %s", op->ifn);
+ goto out;
+ } else if (res < half_xlen) {
+ pr2serr("Read only %d bytes (expected %d) from %s\n",
+ res, half_xlen, op->ifn);
+ goto out;
+ }
+ res = read(wfd, wrkBuff + half_xlen, half_xlen);
+ if (res < 0) {
+ pr2serr("Could not read from %s", op->wfn);
+ goto out;
+ } else if (res < half_xlen) {
+ pr2serr("Read only %d bytes (expected %d) from %s\n",
+ res, half_xlen, op->wfn);
+ goto out;
+ }
+ } else {
+ res = read(infd, wrkBuff, op->xfer_len);
+ if (res < 0) {
+ pr2serr("Could not read from %s", op->ifn);
+ goto out;
+ } else if (res < op->xfer_len) {
+ pr2serr("Read only %d bytes (expected %d) from %s\n",
+ res, op->xfer_len, op->ifn);
+ goto out;
+ }
+ }
+ res = sg_ll_compare_and_write(devfd, wrkBuff, op->numblocks, op->lba,
+ op->xfer_len, op->flags, ! op->quiet,
+ vb);
+ if (0 != res) {
+ char b[80];
+
+ switch (res) {
+ case SG_LIB_CAT_MEDIUM_HARD:
+ case SG_LIB_CAT_MISCOMPARE:
+ case SG_LIB_FILE_ERROR:
+ break; /* already reported */
+ default:
+ sg_get_category_sense_str(res, sizeof(b), b, vb);
+ pr2serr(ME "SCSI COMPARE AND WRITE: %s\n", b);
+ break;
+ }
+ }
+out:
+ if (free_wrkBuff)
+ free(free_wrkBuff);
+ if ((infd >= 0) && (! ifn_stdin))
+ close(infd);
+ if (wfd >= 0)
+ close(wfd);
+ if (devfd >= 0)
+ close(devfd);
+ if (0 == op->verbose) {
+ if (! sg_if_can2stderr("sg_compare_and_write failed: ", res))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return res;
+}