1 /*
2 * Copyright (c) 2012-2022, Kaminario Technologies LTD
3 * All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are met:
6 * * Redistributions of source code must retain the above copyright
7 * notice, this list of conditions and the following disclaimer.
8 * * Redistributions in binary form must reproduce the above copyright
9 * notice, this list of conditions and the following disclaimer in the
10 * documentation and/or other materials provided with the distribution.
11 * * Neither the name of the <organization> nor the
12 * names of its contributors may be used to endorse or promote products
13 * derived from this software without specific prior written permission.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
19 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 *
26 * SPDX-License-Identifier: BSD-3-Clause
27 *
28 * This command performs a SCSI COMPARE AND WRITE. See SBC-3 at
29 * https://www.t10.org
30 *
31 */
32
33 #ifndef __sun
34 #ifndef _GNU_SOURCE
35 #define _GNU_SOURCE 1
36 #endif
37 #endif
38
39 #include <unistd.h>
40 #include <fcntl.h>
41 #include <stdlib.h>
42 #include <stdio.h>
43 #include <stdbool.h>
44 #include <string.h>
45 #include <errno.h>
46 #define __STDC_FORMAT_MACROS 1
47 #include <inttypes.h>
48 #include <getopt.h>
49
50 #ifdef HAVE_CONFIG_H
51 #include "config.h"
52 #endif
53 #include "sg_lib.h"
54 #include "sg_cmds_basic.h"
55 #include "sg_pt.h"
56 #include "sg_unaligned.h"
57 #include "sg_pr2serr.h"
58
59 static const char * version_str = "1.32 20220127";
60
61 #define DEF_BLOCK_SIZE 512
62 #define DEF_NUM_BLOCKS (1)
63 #define DEF_BLOCKS_PER_TRANSFER 8
64 #define DEF_TIMEOUT_SECS 60
65
66 #define COMPARE_AND_WRITE_OPCODE (0x89)
67 #define COMPARE_AND_WRITE_CDB_SIZE (16)
68
69 #define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
70
71 #define ME "sg_compare_and_write: "
72
73 static struct option long_options[] = {
74 {"dpo", no_argument, 0, 'd'},
75 {"fua", no_argument, 0, 'f'},
76 {"fua_nv", no_argument, 0, 'F'},
77 {"fua-nv", no_argument, 0, 'F'},
78 {"group", required_argument, 0, 'g'},
79 {"grpnum", required_argument, 0, 'g'},
80 {"help", no_argument, 0, 'h'},
81 {"in", required_argument, 0, 'i'},
82 {"inc", required_argument, 0, 'C'},
83 {"inw", required_argument, 0, 'D'},
84 {"lba", required_argument, 0, 'l'},
85 {"num", required_argument, 0, 'n'},
86 {"quiet", no_argument, 0, 'q'},
87 {"timeout", required_argument, 0, 't'},
88 {"verbose", no_argument, 0, 'v'},
89 {"version", no_argument, 0, 'V'},
90 {"wrprotect", required_argument, 0, 'w'},
91 {"xferlen", required_argument, 0, 'x'},
92 {0, 0, 0, 0},
93 };
94
95 struct caw_flags {
96 bool dpo;
97 bool fua;
98 bool fua_nv;
99 int group;
100 int wrprotect;
101 };
102
103 struct opts_t {
104 bool quiet;
105 bool verbose_given;
106 bool version_given;
107 bool wfn_given;
108 int numblocks;
109 int verbose;
110 int timeout;
111 int xfer_len;
112 uint64_t lba;
113 const char * ifn;
114 const char * wfn;
115 const char * device_name;
116 struct caw_flags flags;
117 };
118
119
120 static void
usage()121 usage()
122 {
123 pr2serr("Usage: sg_compare_and_write [--dpo] [--fua] [--fua_nv] "
124 "[--grpnum=GN] [--help]\n"
125 " --in=IF|--inc=IF [--inw=WF] "
126 "--lba=LBA "
127 "[--num=NUM]\n"
128 " [--quiet] [--timeout=TO] "
129 "[--verbose] [--version]\n"
130 " [--wrprotect=WP] [--xferlen=LEN] "
131 "DEVICE\n"
132 " where:\n"
133 " --dpo|-d set the dpo bit in cdb (def: "
134 "clear)\n"
135 " --fua|-f set the fua bit in cdb (def: "
136 "clear)\n"
137 " --fua_nv|-F set the fua_nv bit in cdb (def: "
138 "clear)\n"
139 " --grpnum=GN|-g GN GN is GROUP NUMBER to set in "
140 "cdb (def: 0)\n"
141 " --help|-h print out usage message\n"
142 " --in=IF|-i IF IF is a file containing a compare "
143 "buffer and\n"
144 " optionally a write buffer (when "
145 "--inw=WF is\n"
146 " not given)\n"
147 " --inc=IF|-C IF The same as the --in option\n"
148 " --inw=WF|-D WF WF is a file containing a write "
149 "buffer\n"
150 " --lba=LBA|-l LBA LBA of the first block to compare "
151 "and write\n"
152 " --num=NUM|-n NUM number of blocks to "
153 "compare/write (def: 1)\n"
154 " --quiet|-q suppress MISCOMPARE report to "
155 "stderr,\n"
156 " still sets exit status of 14\n"
157 " --timeout=TO|-t TO timeout for the command "
158 "(def: 60 secs)\n"
159 " --verbose|-v increase verbosity (use '-vv' for "
160 "more)\n"
161 " --version|-V print version string then exit\n"
162 " --wrprotect=WP|-w WP write protect information "
163 "(def: 0)\n"
164 " --xferlen=LEN|-x LEN number of bytes to transfer. "
165 "Default is\n"
166 " (2 * NUM * 512) or 1024 when "
167 "NUM is 1\n"
168 "\n"
169 "Performs a SCSI COMPARE AND WRITE operation. Sends a double "
170 "size\nbuffer, the first half is used to compare what is at "
171 "LBA for NUM\nblocks. If and only if the comparison is "
172 "equal, then the second\nhalf of the buffer is written to "
173 "LBA for NUM blocks.\n");
174 }
175
176 static int
parse_args(int argc,char * argv[],struct opts_t * op)177 parse_args(int argc, char* argv[], struct opts_t * op)
178 {
179 bool lba_given = false;
180 bool if_given = false;
181 int c;
182 int64_t ll;
183
184 op->numblocks = DEF_NUM_BLOCKS;
185 /* COMPARE AND WRITE defines 2*buffers compare + write */
186 op->xfer_len = 0;
187 op->timeout = DEF_TIMEOUT_SECS;
188 op->device_name = NULL;
189 while (1) {
190 int option_index = 0;
191
192 c = getopt_long(argc, argv, "C:dD:fFg:hi:l:n:qt:vVw:x:",
193 long_options, &option_index);
194 if (c == -1)
195 break;
196
197 switch (c) {
198 case 'C':
199 case 'i':
200 op->ifn = optarg;
201 if_given = true;
202 break;
203 case 'd':
204 op->flags.dpo = true;
205 break;
206 case 'D':
207 op->wfn = optarg;
208 op->wfn_given = true;
209 break;
210 case 'F':
211 op->flags.fua_nv = true;
212 break;
213 case 'f':
214 op->flags.fua = true;
215 break;
216 case 'g':
217 op->flags.group = sg_get_num(optarg);
218 if ((op->flags.group < 0) ||
219 (op->flags.group > 63)) {
220 pr2serr("argument to '--grpnum=' expected to "
221 "be 0 to 63\n");
222 goto out_err_no_usage;
223 }
224 break;
225 case 'h':
226 case '?':
227 usage();
228 exit(0);
229 case 'l':
230 ll = sg_get_llnum(optarg);
231 if (-1 == ll) {
232 pr2serr("bad argument to '--lba'\n");
233 goto out_err_no_usage;
234 }
235 op->lba = (uint64_t)ll;
236 lba_given = true;
237 break;
238 case 'n':
239 op->numblocks = sg_get_num(optarg);
240 if ((op->numblocks < 0) || (op->numblocks > 255)) {
241 pr2serr("bad argument to '--num', expect 0 "
242 "to 255\n");
243 goto out_err_no_usage;
244 }
245 break;
246 case 'q':
247 op->quiet = true;
248 break;
249 case 't':
250 op->timeout = sg_get_num(optarg);
251 if (op->timeout < 0) {
252 pr2serr("bad argument to '--timeout'\n");
253 goto out_err_no_usage;
254 }
255 break;
256 case 'v':
257 op->verbose_given = true;
258 ++op->verbose;
259 break;
260 case 'V':
261 op->version_given = true;
262 break;
263 case 'w':
264 op->flags.wrprotect = sg_get_num(optarg);
265 if (op->flags.wrprotect >> 3) {
266 pr2serr("bad argument to '--wrprotect' not "
267 "in range 0-7\n");
268 goto out_err_no_usage;
269 }
270 break;
271 case 'x':
272 op->xfer_len = sg_get_num(optarg);
273 if (op->xfer_len < 0) {
274 pr2serr("bad argument to '--xferlen'\n");
275 goto out_err_no_usage;
276 }
277 break;
278 default:
279 pr2serr("unrecognised option code 0x%x ??\n", c);
280 goto out_err;
281 }
282 }
283 if (optind < argc) {
284 if (NULL == op->device_name) {
285 op->device_name = argv[optind];
286 ++optind;
287 }
288 if (optind < argc) {
289 for (; optind < argc; ++optind)
290 pr2serr("Unexpected extra argument: %s\n",
291 argv[optind]);
292 goto out_err;
293 }
294 }
295 if (op->version_given && (! op->verbose_given))
296 return 0;
297 if (NULL == op->device_name) {
298 pr2serr("missing device name!\n");
299 goto out_err;
300 }
301 if (! if_given) {
302 pr2serr("missing input file\n");
303 goto out_err;
304 }
305 if (! lba_given) {
306 pr2serr("missing lba\n");
307 goto out_err;
308 }
309 if (0 == op->xfer_len)
310 op->xfer_len = 2 * op->numblocks * DEF_BLOCK_SIZE;
311 return 0;
312
313 out_err:
314 usage();
315
316 out_err_no_usage:
317 exit(1);
318 }
319
320 #define FLAG_FUA (0x8)
321 #define FLAG_FUA_NV (0x2)
322 #define FLAG_DPO (0x10)
323 #define WRPROTECT_MASK (0x7)
324 #define WRPROTECT_SHIFT (5)
325
326 static int
sg_build_scsi_cdb(uint8_t * cdbp,unsigned int blocks,int64_t start_block,struct caw_flags flags)327 sg_build_scsi_cdb(uint8_t * cdbp, unsigned int blocks,
328 int64_t start_block, struct caw_flags flags)
329 {
330 memset(cdbp, 0, COMPARE_AND_WRITE_CDB_SIZE);
331 cdbp[0] = COMPARE_AND_WRITE_OPCODE;
332 cdbp[1] = (flags.wrprotect & WRPROTECT_MASK) << WRPROTECT_SHIFT;
333 if (flags.dpo)
334 cdbp[1] |= FLAG_DPO;
335 if (flags.fua)
336 cdbp[1] |= FLAG_FUA;
337 if (flags.fua_nv)
338 cdbp[1] |= FLAG_FUA_NV;
339 sg_put_unaligned_be64((uint64_t)start_block, cdbp + 2);
340 /* cdbp[10-12] are reserved */
341 cdbp[13] = (uint8_t)(blocks & 0xff);
342 cdbp[14] = (uint8_t)(flags.group & GRPNUM_MASK);
343 return 0;
344 }
345
346 /* Returns 0 for success, SG_LIB_CAT_MISCOMPARE if compare fails,
347 * various other SG_LIB_CAT_*, otherwise -1 . */
348 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)349 sg_ll_compare_and_write(int sg_fd, uint8_t * buff, int blocks,
350 int64_t lba, int xfer_len, struct caw_flags flags,
351 bool noisy, int verbose)
352 {
353 bool valid;
354 int sense_cat, slen, res, ret;
355 uint64_t ull = 0;
356 struct sg_pt_base * ptvp;
357 uint8_t cawCmd[COMPARE_AND_WRITE_CDB_SIZE];
358 uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
359
360 if (sg_build_scsi_cdb(cawCmd, blocks, lba, flags)) {
361 pr2serr(ME "bad cdb build, lba=0x%" PRIx64 ", blocks=%d\n",
362 lba, blocks);
363 return -1;
364 }
365 ptvp = construct_scsi_pt_obj();
366 if (NULL == ptvp) {
367 pr2serr("Could not construct scsit_pt_obj, out of memory\n");
368 return -1;
369 }
370
371 set_scsi_pt_cdb(ptvp, cawCmd, COMPARE_AND_WRITE_CDB_SIZE);
372 set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
373 set_scsi_pt_data_out(ptvp, buff, xfer_len);
374 if (verbose > 1) {
375 char b[128];
376
377 pr2serr(" Compare and write cdb: %s\n",
378 sg_get_command_str(cawCmd, COMPARE_AND_WRITE_CDB_SIZE, false,
379 sizeof(b), b));
380 }
381 if ((verbose > 2) && (xfer_len > 0)) {
382 pr2serr(" Data-out buffer contents:\n");
383 hex2stderr(buff, xfer_len, 1);
384 }
385 res = do_scsi_pt(ptvp, sg_fd, DEF_TIMEOUT_SECS, verbose);
386 ret = sg_cmds_process_resp(ptvp, "COMPARE AND WRITE", res,
387 noisy, verbose, &sense_cat);
388 if (-1 == ret) {
389 if (get_scsi_pt_transport_err(ptvp))
390 ret = SG_LIB_TRANSPORT_ERROR;
391 else
392 ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
393 } else if (-2 == ret) {
394 switch (sense_cat) {
395 case SG_LIB_CAT_RECOVERED:
396 case SG_LIB_CAT_NO_SENSE:
397 ret = 0;
398 break;
399 case SG_LIB_CAT_MEDIUM_HARD:
400 slen = get_scsi_pt_sense_len(ptvp);
401 valid = sg_get_sense_info_fld(sense_b, slen,
402 &ull);
403 if (valid)
404 pr2serr("Medium or hardware error starting "
405 "at lba=%" PRIu64 " [0x%" PRIx64
406 "]\n", ull, ull);
407 else
408 pr2serr("Medium or hardware error\n");
409 ret = sense_cat;
410 break;
411 case SG_LIB_CAT_MISCOMPARE:
412 ret = sense_cat;
413 if (! (noisy || verbose))
414 break;
415 slen = get_scsi_pt_sense_len(ptvp);
416 valid = sg_get_sense_info_fld(sense_b, slen, &ull);
417 if (valid)
418 pr2serr("Miscompare at byte offset: %" PRIu64
419 " [0x%" PRIx64 "]\n", ull, ull);
420 else
421 pr2serr("Miscompare reported\n");
422 break;
423 case SG_LIB_CAT_ILLEGAL_REQ:
424 if (verbose)
425 sg_print_command_len(cawCmd,
426 COMPARE_AND_WRITE_CDB_SIZE);
427 /* FALL THROUGH */
428 default:
429 ret = sense_cat;
430 break;
431 }
432 } else
433 ret = 0;
434
435 destruct_scsi_pt_obj(ptvp);
436 return ret;
437 }
438
439 static int
open_if(const char * fn,bool got_stdin)440 open_if(const char * fn, bool got_stdin)
441 {
442 int fd;
443
444 if (got_stdin)
445 fd = STDIN_FILENO;
446 else {
447 fd = open(fn, O_RDONLY);
448 if (fd < 0) {
449 pr2serr(ME "open error: %s: %s\n", fn,
450 safe_strerror(errno));
451 return -SG_LIB_FILE_ERROR;
452 }
453 }
454 if (sg_set_binary_mode(fd) < 0) {
455 perror("sg_set_binary_mode");
456 return -SG_LIB_FILE_ERROR;
457 }
458 return fd;
459 }
460
461 static int
open_dev(const char * outf,int verbose)462 open_dev(const char * outf, int verbose)
463 {
464 int sg_fd = sg_cmds_open_device(outf, false /* rw */, verbose);
465
466 if ((sg_fd < 0) && verbose)
467 pr2serr(ME "open error: %s: %s\n", outf,
468 safe_strerror(-sg_fd));
469 return sg_fd;
470 }
471
472
473 int
main(int argc,char * argv[])474 main(int argc, char * argv[])
475 {
476 bool ifn_stdin;
477 int res, half_xlen, vb;
478 int infd = -1;
479 int wfd = -1;
480 int devfd = -1;
481 uint8_t * wrkBuff = NULL;
482 uint8_t * free_wrkBuff = NULL;
483 struct opts_t * op;
484 struct opts_t opts;
485
486 op = &opts;
487 memset(op, 0, sizeof(opts));
488 res = parse_args(argc, argv, op);
489 if (res != 0) {
490 pr2serr("Failed parsing args\n");
491 goto out;
492 }
493
494 #ifdef DEBUG
495 pr2serr("In DEBUG mode, ");
496 if (op->verbose_given && op->version_given) {
497 pr2serr("but override: '-vV' given, zero verbose and "
498 "continue\n");
499 op->verbose_given = false;
500 op->version_given = false;
501 op->verbose = 0;
502 } else if (! op->verbose_given) {
503 pr2serr("set '-vv'\n");
504 op->verbose = 2;
505 } else
506 pr2serr("keep verbose=%d\n", op->verbose);
507 #else
508 if (op->verbose_given && op->version_given)
509 pr2serr("Not in DEBUG mode, so '-vV' has no special "
510 "action\n");
511 #endif
512 if (op->version_given) {
513 pr2serr(ME "version: %s\n", version_str);
514 return 0;
515 }
516 vb = op->verbose;
517
518 if (vb) {
519 pr2serr("Running COMPARE AND WRITE command with the "
520 "following options:\n in=%s ", op->ifn);
521 if (op->wfn_given)
522 pr2serr("inw=%s ", op->wfn);
523 pr2serr("device=%s\n lba=0x%" PRIx64 " num_blocks=%d "
524 "xfer_len=%d timeout=%d\n", op->device_name,
525 op->lba, op->numblocks, op->xfer_len, op->timeout);
526 }
527 ifn_stdin = ((1 == strlen(op->ifn)) && ('-' == op->ifn[0]));
528 infd = open_if(op->ifn, ifn_stdin);
529 if (infd < 0) {
530 res = -infd;
531 goto out;
532 }
533 if (op->wfn_given) {
534 if ((1 == strlen(op->wfn)) && ('-' == op->wfn[0])) {
535 pr2serr(ME "don't allow stdin for write file\n");
536 res = SG_LIB_FILE_ERROR;
537 goto out;
538 }
539 wfd = open_if(op->wfn, false);
540 if (wfd < 0) {
541 res = -wfd;
542 goto out;
543 }
544 }
545
546 devfd = open_dev(op->device_name, vb);
547 if (devfd < 0) {
548 res = sg_convert_errno(-devfd);
549 goto out;
550 }
551
552 wrkBuff = (uint8_t *)sg_memalign(op->xfer_len, 0, &free_wrkBuff,
553 vb > 3);
554 if (NULL == wrkBuff) {
555 pr2serr("Not enough user memory\n");
556 res = sg_convert_errno(ENOMEM);
557 goto out;
558 }
559
560 if (op->wfn_given) {
561 half_xlen = op->xfer_len / 2;
562 res = read(infd, wrkBuff, half_xlen);
563 if (res < 0) {
564 pr2serr("Could not read from %s", op->ifn);
565 goto out;
566 } else if (res < half_xlen) {
567 pr2serr("Read only %d bytes (expected %d) from %s\n",
568 res, half_xlen, op->ifn);
569 goto out;
570 }
571 res = read(wfd, wrkBuff + half_xlen, half_xlen);
572 if (res < 0) {
573 pr2serr("Could not read from %s", op->wfn);
574 goto out;
575 } else if (res < half_xlen) {
576 pr2serr("Read only %d bytes (expected %d) from %s\n",
577 res, half_xlen, op->wfn);
578 goto out;
579 }
580 } else {
581 res = read(infd, wrkBuff, op->xfer_len);
582 if (res < 0) {
583 pr2serr("Could not read from %s", op->ifn);
584 goto out;
585 } else if (res < op->xfer_len) {
586 pr2serr("Read only %d bytes (expected %d) from %s\n",
587 res, op->xfer_len, op->ifn);
588 goto out;
589 }
590 }
591 res = sg_ll_compare_and_write(devfd, wrkBuff, op->numblocks, op->lba,
592 op->xfer_len, op->flags, ! op->quiet,
593 vb);
594 if (0 != res) {
595 char b[80];
596
597 switch (res) {
598 case SG_LIB_CAT_MEDIUM_HARD:
599 case SG_LIB_CAT_MISCOMPARE:
600 case SG_LIB_FILE_ERROR:
601 break; /* already reported */
602 default:
603 sg_get_category_sense_str(res, sizeof(b), b, vb);
604 pr2serr(ME "SCSI COMPARE AND WRITE: %s\n", b);
605 break;
606 }
607 }
608 out:
609 if (free_wrkBuff)
610 free(free_wrkBuff);
611 if ((infd >= 0) && (! ifn_stdin))
612 close(infd);
613 if (wfd >= 0)
614 close(wfd);
615 if (devfd >= 0)
616 close(devfd);
617 if (0 == op->verbose) {
618 if (! sg_if_can2stderr("sg_compare_and_write failed: ", res))
619 pr2serr("Some error occurred, try again with '-v' "
620 "or '-vv' for more information\n");
621 }
622 return res;
623 }
624