xref: /aosp_15_r20/external/sg3_utils/src/sg_compare_and_write.c (revision 44704f698541f6367e81f991ef8bb54ccbf3fc18)
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