| /* |
| * Copyright (c) 2004-2020 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 <stdbool.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_pr2serr.h" |
| |
| /* A utility program for the Linux OS SCSI subsystem. |
| * |
| * This program issues the SCSI VERIFY(10) or VERIFY(16) command to the given |
| * SCSI block device. |
| * |
| * N.B. This utility should, but doesn't, check the logical block size with |
| * the SCSI READ CAPACITY command. It is up to the user to make sure that |
| * the count of blocks requested and the number of bytes transferred (when |
| * BYTCHK>0) are "in sync". That caclculation is somewhat complicated by |
| * the possibility of protection data (DIF). |
| */ |
| |
| static const char * version_str = "1.27 20201029"; /* sbc4r17 */ |
| |
| #define ME "sg_verify: " |
| |
| #define EBUFF_SZ 256 |
| |
| |
| static struct option long_options[] = { |
| {"0", no_argument, 0, '0'}, |
| {"16", no_argument, 0, 'S'}, |
| {"bpc", required_argument, 0, 'b'}, |
| {"bytchk", required_argument, 0, 'B'}, /* 4 backward compatibility */ |
| {"count", required_argument, 0, 'c'}, |
| {"dpo", no_argument, 0, 'd'}, |
| {"ebytchk", required_argument, 0, 'E'}, /* extended bytchk (2 bits) */ |
| {"group", required_argument, 0, 'g'}, |
| {"help", no_argument, 0, 'h'}, |
| {"in", required_argument, 0, 'i'}, |
| {"lba", required_argument, 0, 'l'}, |
| {"nbo", required_argument, 0, 'n'}, /* misspelling, legacy */ |
| {"ndo", required_argument, 0, 'n'}, |
| {"quiet", no_argument, 0, 'q'}, |
| {"readonly", no_argument, 0, 'r'}, |
| {"verbose", no_argument, 0, 'v'}, |
| {"version", no_argument, 0, 'V'}, |
| {"vrprotect", required_argument, 0, 'P'}, |
| {0, 0, 0, 0}, |
| }; |
| |
| static void |
| usage() |
| { |
| pr2serr("Usage: sg_verify [--0] [--16] [--bpc=BPC] [--count=COUNT] " |
| "[--dpo]\n" |
| " [--ebytchk=BCH] [--ff] [--group=GN] [--help] " |
| "[--in=IF]\n" |
| " [--lba=LBA] [--ndo=NDO] [--quiet] " |
| "[--readonly]\n" |
| " [--verbose] [--version] [--vrprotect=VRP] " |
| "DEVICE\n" |
| " where:\n" |
| " --0|-0 fill buffer with zeros (don't read " |
| "stdin)\n" |
| " --16|-S use VERIFY(16) (def: use " |
| "VERIFY(10) )\n" |
| " --bpc=BPC|-b BPC max blocks per verify command " |
| "(def: 128)\n" |
| " --count=COUNT|-c COUNT count of blocks to verify " |
| "(def: 1).\n" |
| " --dpo|-d disable page out (cache retention " |
| "priority)\n" |
| " --ebytchk=BCH|-E BCH sets BYTCHK value, either 1, 2 " |
| "or 3 (def: 0).\n" |
| " BCH overrides BYTCHK=1 set by " |
| "'--ndo='. If\n" |
| " BCH is 3 then NDO must be the LBA " |
| "size\n" |
| " (plus protection size if DIF " |
| "active)\n" |
| " --ff|-f fill buffer with 0xff bytes (don't read " |
| "stdin)\n" |
| " --group=GN|-g GN set group number field to GN (def: 0)\n" |
| " --help|-h print out usage message\n" |
| " --in=IF|-i IF input from file called IF (def: " |
| "stdin)\n" |
| " only active if --ebytchk=BCH given\n" |
| " --lba=LBA|-l LBA logical block address to start " |
| "verify (def: 0)\n" |
| " --ndo=NDO|-n NDO NDO is number of bytes placed in " |
| "data-out buffer.\n" |
| " These are fetched from IF (or " |
| "stdin) and used\n" |
| " to verify the device data against. " |
| "Forces\n" |
| " --bpc=COUNT. Sets BYTCHK (byte check) " |
| "to 1\n" |
| " --quiet|-q suppress miscompare report to stderr, " |
| "still\n" |
| " causes an exit status of 14\n" |
| " --readonly|-r open DEVICE read-only (def: open it " |
| "read-write)\n" |
| " --verbose|-v increase verbosity\n" |
| " --version|-V print version string and exit\n" |
| " --vrprotect=VRP|-P VRP set vrprotect field to VRP " |
| "(def: 0)\n\n" |
| "Performs one or more SCSI VERIFY(10) or SCSI VERIFY(16) " |
| "commands. sbc3r34\nmade the BYTCHK field two bits wide " |
| "(it was a single bit).\n"); |
| } |
| |
| int |
| main(int argc, char * argv[]) |
| { |
| bool bpc_given = false; |
| bool dpo = false; |
| bool ff_given = false; |
| bool got_stdin = false; |
| bool quiet = false; |
| bool readonly = false; |
| bool verbose_given = false; |
| bool verify16 = false; |
| bool version_given = false; |
| bool zero_given = false; |
| int res, c, num, nread, infd; |
| int sg_fd = -1; |
| int bpc = 128; |
| int group = 0; |
| int bytchk = 0; |
| int ndo = 0; /* number of bytes in data-out buffer */ |
| int verbose = 0; |
| int ret = 0; |
| int vrprotect = 0; |
| unsigned int info = 0; |
| int64_t count = 1; |
| int64_t ll; |
| int64_t orig_count; |
| uint64_t info64 = 0; |
| uint64_t lba = 0; |
| uint64_t orig_lba; |
| uint8_t * ref_data = NULL; |
| uint8_t * free_ref_data = NULL; |
| const char * device_name = NULL; |
| const char * file_name = NULL; |
| const char * vc; |
| char ebuff[EBUFF_SZ]; |
| |
| while (1) { |
| int option_index = 0; |
| |
| c = getopt_long(argc, argv, "0b:B:c:dE:fg:hi:l:n:P:qrSvV", |
| long_options, &option_index); |
| if (c == -1) |
| break; |
| |
| switch (c) { |
| case '0': |
| zero_given = true; |
| break; |
| case 'b': |
| bpc = sg_get_num(optarg); |
| if (bpc < 1) { |
| pr2serr("bad argument to '--bpc'\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| bpc_given = true; |
| break; |
| case 'c': |
| count = sg_get_llnum(optarg); |
| if (count < 0) { |
| pr2serr("bad argument to '--count'\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| break; |
| case 'd': |
| dpo = true; |
| break; |
| case 'E': |
| bytchk = sg_get_num(optarg); |
| if ((bytchk < 0) || (bytchk > 3)) { |
| pr2serr("bad argument to '--ebytchk'\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| break; |
| case 'f': |
| ff_given = true; |
| break; |
| case 'g': |
| group = sg_get_num(optarg); |
| if ((group < 0) || (group > 63)) { |
| pr2serr("bad argument to '--group'\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| break; |
| case 'h': |
| case '?': |
| usage(); |
| return 0; |
| case 'i': |
| file_name = optarg; |
| break; |
| case 'l': |
| ll = sg_get_llnum(optarg); |
| if (-1 == ll) { |
| pr2serr("bad argument to '--lba'\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| lba = (uint64_t)ll; |
| break; |
| case 'n': /* number of bytes in data-out buffer */ |
| case 'B': /* undocumented, old --bytchk=NDO option */ |
| ndo = sg_get_num(optarg); |
| if (ndo < 1) { |
| pr2serr("bad argument to '--ndo'\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| break; |
| case 'P': |
| vrprotect = sg_get_num(optarg); |
| if (-1 == vrprotect) { |
| pr2serr("bad argument to '--vrprotect'\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| if ((vrprotect < 0) || (vrprotect > 7)) { |
| pr2serr("'--vrprotect' requires a value from 0 to 7 " |
| "(inclusive)\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| break; |
| case 'q': |
| quiet = true; |
| break; |
| case 'r': |
| readonly = true; |
| break; |
| case 'S': |
| verify16 = true; |
| 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 (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(ME "version: %s\n", version_str); |
| return 0; |
| } |
| |
| if (ndo > 0) { |
| if (0 == bytchk) |
| bytchk = 1; |
| if (bpc_given && (bpc != count)) |
| pr2serr("'bpc' argument ignored, using --bpc=%" PRIu64 "\n", |
| count); |
| if (count > 0x7fffffffLL) { |
| pr2serr("count exceed 31 bits, way too large\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| #if 0 |
| if ((3 == bytchk) && (1 != count)) { |
| pr2serr("count must be 1 when bytchk=3\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| // bpc = (int)count; |
| #endif |
| } else if (bytchk > 0) { |
| pr2serr("when the 'ebytchk=BCH' option is given, then '--ndo=NDO' " |
| "must also be given\n"); |
| return SG_LIB_CONTRADICT; |
| } |
| if ((zero_given || ff_given) && file_name) { |
| pr2serr("giving --0 or --ff is not compatible with --if=%s\n", |
| file_name); |
| return SG_LIB_CONTRADICT; |
| } |
| |
| if ((bpc > 0xffff) && (! verify16)) { |
| pr2serr("'%s' exceeds 65535, so use VERIFY(16)\n", |
| (ndo > 0) ? "count" : "bpc"); |
| verify16 = true; |
| } |
| if (((lba + count - 1) > 0xffffffffLLU) && (! verify16)) { |
| pr2serr("'lba' exceed 32 bits, so use VERIFY(16)\n"); |
| verify16 = true; |
| } |
| if ((group > 0) && (! verify16)) |
| pr2serr("group number ignored with VERIFY(10) command, use the --16 " |
| "option\n"); |
| |
| orig_count = count; |
| orig_lba = lba; |
| |
| if (ndo > 0) { |
| ref_data = (uint8_t *)sg_memalign(ndo, 0, &free_ref_data, verbose > 4); |
| if (NULL == ref_data) { |
| pr2serr("failed to allocate %d byte buffer\n", ndo); |
| ret = sg_convert_errno(ENOMEM); |
| goto err_out; |
| } |
| if (ff_given) |
| memset(ref_data, 0xff, ndo); |
| if (zero_given || ff_given) |
| goto skip; |
| if ((NULL == file_name) || (0 == strcmp(file_name, "-"))) { |
| got_stdin = true; |
| infd = STDIN_FILENO; |
| if (sg_set_binary_mode(STDIN_FILENO) < 0) |
| perror("sg_set_binary_mode"); |
| } 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 (verbose && got_stdin) |
| pr2serr("about to wait on STDIN\n"); |
| for (nread = 0; nread < ndo; nread += res) { |
| res = read(infd, ref_data + nread, ndo - nread); |
| if (res <= 0) { |
| ret = sg_convert_errno(errno); |
| pr2serr("reading from %s failed at file offset=%d\n", |
| (got_stdin ? "stdin" : file_name), nread); |
| goto err_out; |
| } |
| } |
| if (! got_stdin) |
| close(infd); |
| } |
| skip: |
| if (NULL == device_name) { |
| pr2serr("missing device name!\n\n"); |
| usage(); |
| ret = SG_LIB_SYNTAX_ERROR; |
| goto err_out; |
| } |
| sg_fd = sg_cmds_open_device(device_name, readonly, 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; |
| } |
| |
| vc = verify16 ? "VERIFY(16)" : "VERIFY(10)"; |
| for (; count > 0; count -= bpc, lba += bpc) { |
| num = (count > bpc) ? bpc : count; |
| if (verify16) |
| res = sg_ll_verify16(sg_fd, vrprotect, dpo, bytchk, |
| lba, num, group, ref_data, |
| ndo, &info64, !quiet , verbose); |
| else |
| res = sg_ll_verify10(sg_fd, vrprotect, dpo, bytchk, |
| (unsigned int)lba, num, ref_data, |
| ndo, &info, !quiet, verbose); |
| if (0 != res) { |
| char b[80]; |
| |
| ret = res; |
| switch (res) { |
| case SG_LIB_CAT_ILLEGAL_REQ: |
| pr2serr("bad field in %s cdb, near lba=0x%" PRIx64 "\n", vc, |
| lba); |
| break; |
| case SG_LIB_CAT_MEDIUM_HARD: |
| pr2serr("%s medium or hardware error near lba=0x%" PRIx64 "\n", |
| vc, lba); |
| break; |
| case SG_LIB_CAT_MEDIUM_HARD_WITH_INFO: |
| if (verify16) |
| pr2serr("%s medium or hardware error, reported lba=0x%" |
| PRIx64 "\n", vc, info64); |
| else |
| pr2serr("%s medium or hardware error, reported lba=0x%x\n", |
| vc, info); |
| break; |
| case SG_LIB_CAT_MISCOMPARE: |
| if ((0 == quiet) || verbose) |
| pr2serr("%s MISCOMPARE: started at LBA 0x%" PRIx64 "\n", |
| vc, lba); |
| break; |
| default: |
| sg_get_category_sense_str(res, sizeof(b), b, verbose); |
| pr2serr("%s: %s\n", vc, b); |
| pr2serr(" failed near lba=%" PRIu64 " [0x%" PRIx64 "]\n", |
| lba, lba); |
| break; |
| } |
| break; |
| } |
| } |
| |
| if (verbose && (0 == ret) && (orig_count > 1)) |
| pr2serr("Verified %" PRId64 " [0x%" PRIx64 "] blocks from lba %" PRIu64 |
| " [0x%" PRIx64 "]\n without error\n", orig_count, |
| (uint64_t)orig_count, orig_lba, orig_lba); |
| |
| err_out: |
| 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 (free_ref_data) |
| free(free_ref_data); |
| if (0 == verbose) { |
| if (! sg_if_can2stderr("sg_verify failed: ", ret)) |
| pr2serr("Some error occurred, try again with '-v' " |
| "or '-vv' for more information\n"); |
| } |
| return (ret >= 0) ? ret : SG_LIB_CAT_OTHER; |
| } |