diff options
Diffstat (limited to 'src/sg_compare_and_write.c')
-rw-r--r-- | src/sg_compare_and_write.c | 623 |
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; +} |