| /* |
| * Copyright (c) 2005-2019 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 <string.h> |
| #include <ctype.h> |
| #include <getopt.h> |
| #include <limits.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_unaligned.h" |
| #include "sg_pr2serr.h" |
| |
| /* A utility program originally written for the Linux OS SCSI subsystem. |
| * |
| * This utility invokes the REASSIGN BLOCKS SCSI command to reassign |
| * an existing (possibly damaged) lba on a direct access device (e.g. |
| * a disk) to a new physical location. The previous contents is |
| * recoverable then it is written to the remapped lba otherwise |
| * vendor specific data is written. |
| */ |
| |
| static const char * version_str = "1.27 20191001"; |
| |
| #define DEF_DEFECT_LIST_FORMAT 4 /* bytes from index */ |
| |
| #define MAX_NUM_ADDR 1024 |
| |
| #ifndef UINT32_MAX |
| #define UINT32_MAX ((uint32_t)-1) |
| #endif |
| |
| |
| static struct option long_options[] = { |
| {"address", required_argument, 0, 'a'}, |
| {"dummy", no_argument, 0, 'd'}, |
| {"eight", required_argument, 0, 'e'}, |
| {"grown", no_argument, 0, 'g'}, |
| {"help", no_argument, 0, 'h'}, |
| {"hex", no_argument, 0, 'H'}, |
| {"longlist", required_argument, 0, 'l'}, |
| {"primary", no_argument, 0, 'p'}, |
| {"verbose", no_argument, 0, 'v'}, |
| {"version", no_argument, 0, 'V'}, |
| {0, 0, 0, 0}, |
| }; |
| |
| static void |
| usage() |
| { |
| pr2serr("Usage: sg_reassign [--address=A,A...] [--dummy] [--eight=0|1] " |
| "[--grown]\n" |
| " [--help] [--hex] [--longlist=0|1] " |
| "[--primary] [--verbose]\n" |
| " [--version] DEVICE\n" |
| " where:\n" |
| " --address=A,A...|-a A,A... comma separated logical block " |
| "addresses\n" |
| " one or more, assumed to be " |
| "decimal\n" |
| " --address=-|-a - read stdin for logical block " |
| "addresses\n" |
| " --dummy|-d prepare but do not execute REASSIGN " |
| "BLOCKS command\n" |
| " --eight=0|1\n" |
| " -e 0|1 force eight byte (64 bit) lbas " |
| "when 1,\n" |
| " four byte (32 bit) lbas when 0 " |
| "(def)\n" |
| " --grown|-g fetch grown defect list length, " |
| "don't reassign\n" |
| " --help|-h print out usage message\n" |
| " --hex|-H print response in hex (for '-g' or " |
| "'-p')\n" |
| " --longlist=0|1\n" |
| " -l 0|1 use 4 byte list length when 1, safe to " |
| "ignore\n" |
| " (def: 0 (2 byte list length))\n" |
| " --primary|-p fetch primary defect list length, " |
| "don't reassign\n" |
| " --verbose|-v increase verbosity\n" |
| " --version|-V print version string and exit\n\n" |
| "Perform a SCSI REASSIGN BLOCKS command (or READ DEFECT LIST)\n"); |
| } |
| |
| /* Read numbers (up to 64 bits in size) from command line (comma (or |
| * (single) space) separated list) or from stdin (one per line, comma |
| * separated list or space separated list). Assumed decimal unless prefixed |
| * by '0x', '0X' or contains trailing 'h' or 'H' (which indicate hex). |
| * Returns 0 if ok, or error code. */ |
| static int |
| build_lba_arr(const char * inp, uint64_t * lba_arr, |
| int * lba_arr_len, int max_arr_len) |
| { |
| int in_len, k, j, m; |
| const char * lcp; |
| int64_t ll; |
| char * cp; |
| char * c2p; |
| |
| if ((NULL == inp) || (NULL == lba_arr) || |
| (NULL == lba_arr_len)) |
| return SG_LIB_LOGIC_ERROR; |
| lcp = inp; |
| in_len = strlen(inp); |
| if (0 == in_len) |
| *lba_arr_len = 0; |
| if ('-' == inp[0]) { /* read from stdin */ |
| char line[1024]; |
| int off = 0; |
| |
| for (j = 0; j < 512; ++j) { |
| if (NULL == fgets(line, sizeof(line), stdin)) |
| break; |
| // could improve with carry_over logic if sizeof(line) too small |
| in_len = strlen(line); |
| if (in_len > 0) { |
| if ('\n' == line[in_len - 1]) { |
| --in_len; |
| line[in_len] = '\0'; |
| } |
| } |
| if (in_len < 1) |
| continue; |
| lcp = line; |
| m = strspn(lcp, " \t"); |
| if (m == in_len) |
| continue; |
| lcp += m; |
| in_len -= m; |
| if ('#' == *lcp) |
| continue; |
| k = strspn(lcp, "0123456789aAbBcCdDeEfFhHxX ,\t"); |
| if ((k < in_len) && ('#' != lcp[k])) { |
| pr2serr("%s: syntax error at line %d, pos %d\n", __func__, |
| j + 1, m + k + 1); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| for (k = 0; k < 1024; ++k) { |
| ll = sg_get_llnum_nomult(lcp); |
| if (-1 != ll) { |
| if ((off + k) >= max_arr_len) { |
| pr2serr("%s: array length exceeded\n", __func__); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| lba_arr[off + k] = (uint64_t)ll; |
| lcp = strpbrk(lcp, " ,\t"); |
| if (NULL == lcp) |
| break; |
| lcp += strspn(lcp, " ,\t"); |
| if ('\0' == *lcp) |
| break; |
| } else { |
| if ('#' == *lcp) { |
| --k; |
| break; |
| } |
| pr2serr("%s: error in line %d, at pos %d\n", __func__, |
| j + 1, (int)(lcp - line + 1)); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| } |
| off += (k + 1); |
| } |
| *lba_arr_len = off; |
| } else { /* list of numbers (default decimal) on command line */ |
| k = strspn(inp, "0123456789aAbBcCdDeEfFhHxX, "); |
| if (in_len != k) { |
| pr2serr("%s: error at pos %d\n", __func__, k + 1); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| for (k = 0; k < max_arr_len; ++k) { |
| ll = sg_get_llnum_nomult(lcp); |
| if (-1 != ll) { |
| lba_arr[k] = (uint64_t)ll; |
| cp = (char *)strchr(lcp, ','); |
| c2p = (char *)strchr(lcp, ' '); |
| if (NULL == cp) |
| cp = c2p; |
| if (NULL == cp) |
| break; |
| if (c2p && (c2p < cp)) |
| cp = c2p; |
| lcp = cp + 1; |
| } else { |
| pr2serr("%s: error at pos %d\n", __func__, |
| (int)(lcp - inp + 1)); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| } |
| *lba_arr_len = k + 1; |
| if (k == max_arr_len) { |
| pr2serr("%s: array length exceeded\n", __func__); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| } |
| return 0; |
| } |
| |
| |
| int |
| main(int argc, char * argv[]) |
| { |
| bool dummy = false; |
| bool eight = false; |
| bool eight_given = false; |
| bool got_addr = false; |
| bool longlist = false; |
| bool primary = false; |
| bool grown = false; |
| bool verbose_given = false; |
| bool version_given = false; |
| int res, c, num, k, j; |
| int sg_fd = -1; |
| int addr_arr_len = 0; |
| int do_hex = 0; |
| int verbose = 0; |
| const char * device_name = NULL; |
| uint64_t addr_arr[MAX_NUM_ADDR]; |
| uint8_t param_arr[4 + (MAX_NUM_ADDR * 8)]; |
| char b[80]; |
| int param_len = 4; |
| int ret = 0; |
| |
| while (1) { |
| int option_index = 0; |
| |
| c = getopt_long(argc, argv, "a:de:ghHl:pvV", long_options, |
| &option_index); |
| if (c == -1) |
| break; |
| |
| switch (c) { |
| case 'a': |
| memset(addr_arr, 0, sizeof(addr_arr)); |
| if ((res = build_lba_arr(optarg, addr_arr, &addr_arr_len, |
| MAX_NUM_ADDR))) { |
| pr2serr("bad argument to '--address'\n"); |
| return res; |
| } |
| got_addr = true; |
| break; |
| case 'd': |
| dummy = true; |
| break; |
| case 'e': |
| num = sscanf(optarg, "%d", &res); |
| if ((1 == num) && ((0 == res) || (1 == res))) |
| eight = !! res; |
| else { |
| pr2serr("value for '--eight=' must be 0 or 1\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| eight_given = true; |
| break; |
| case 'g': |
| grown = true; |
| break; |
| case 'h': |
| case '?': |
| usage(); |
| return 0; |
| case 'H': |
| ++do_hex; |
| break; |
| case 'l': |
| num = sscanf(optarg, "%d", &res); |
| if ((1 == num) && ((0 == res) || (1 == res))) |
| longlist = !!res; |
| else { |
| pr2serr("value for '--longlist=' must be 0 or 1\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| break; |
| case 'p': |
| primary = 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("version: %s\n", version_str); |
| return 0; |
| } |
| |
| if (NULL == device_name) { |
| pr2serr("Missing device name!\n\n"); |
| usage(); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| if (grown || primary) { |
| if (got_addr) { |
| pr2serr("can't have '--address=' with '--grown' or '--primary'\n"); |
| usage(); |
| return SG_LIB_CONTRADICT; |
| } |
| } else if ((! got_addr) || (addr_arr_len < 1)) { |
| pr2serr("need at least one address (see '--address=')\n"); |
| usage(); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| if (got_addr) { |
| for (k = 0; k < addr_arr_len; ++k) { |
| if (addr_arr[k] >= UINT32_MAX) { |
| if (! eight_given) { |
| eight = true; |
| break; |
| } else if (! eight) { |
| pr2serr("address number %d exceeds 32 bits so " |
| "'--eight=0' invalid\n", k + 1); |
| return SG_LIB_CONTRADICT; |
| } |
| } |
| } |
| if (! eight_given) |
| eight = false; |
| |
| k = 4; |
| for (j = 0; j < addr_arr_len; ++j) { |
| if (eight) { |
| sg_put_unaligned_be64(addr_arr[j], param_arr + k); |
| k += 8; |
| } else { |
| sg_put_unaligned_be32((uint32_t)addr_arr[j], param_arr + k); |
| k += 4; |
| } |
| } |
| param_len = k; |
| k -= 4; |
| if (longlist) |
| sg_put_unaligned_be32((uint32_t)k, param_arr + 0); |
| else |
| sg_put_unaligned_be16((uint16_t)k, param_arr + 2); |
| } |
| |
| sg_fd = sg_cmds_open_device(device_name, false /* rw */, verbose); |
| if (sg_fd < 0) { |
| if (verbose) |
| pr2serr("open error: %s: %s\n", device_name, |
| safe_strerror(-sg_fd)); |
| ret = sg_convert_errno(-sg_fd); |
| goto err_out; |
| } |
| |
| if (got_addr) { |
| if (dummy) { |
| pr2serr(">>> dummy: REASSIGN BLOCKS not executed\n"); |
| if (verbose) { |
| pr2serr(" Would have reassigned these blocks:\n"); |
| for (j = 0; j < addr_arr_len; ++j) |
| printf(" 0x%" PRIx64 "\n", addr_arr[j]); |
| } |
| return 0; |
| } |
| res = sg_ll_reassign_blocks(sg_fd, eight, longlist, param_arr, |
| param_len, true, verbose); |
| ret = res; |
| if (res) { |
| sg_get_category_sense_str(res, sizeof(b), b, verbose); |
| pr2serr("REASSIGN BLOCKS: %s\n", b); |
| goto err_out; |
| } |
| } else /* if (grown || primary) */ { |
| int dl_format = DEF_DEFECT_LIST_FORMAT; |
| int div = 0; |
| int dl_len; |
| bool got_grown, got_primary; |
| const char * lstp; |
| |
| param_len = 4; |
| memset(param_arr, 0, param_len); |
| res = sg_ll_read_defect10(sg_fd, primary, grown, dl_format, |
| param_arr, param_len, false, verbose); |
| ret = res; |
| if (res) { |
| sg_get_category_sense_str(res, sizeof(b), b, verbose); |
| pr2serr("READ DEFECT DATA(10): %s\n", b); |
| goto err_out; |
| } |
| if (do_hex) { |
| hex2stdout(param_arr, param_len, 1); |
| goto err_out; /* ret is zero */ |
| } |
| got_grown = !!(param_arr[1] & 0x8); |
| got_primary = !!(param_arr[1] & 0x10); |
| if (got_grown && got_primary) |
| lstp = "grown and primary defect lists"; |
| else if (got_grown) |
| lstp = "grown defect list"; |
| else if (got_primary) |
| lstp = "primary defect list"; |
| else { |
| pr2serr("didn't get grown or primary list in response\n"); |
| goto err_out; |
| } |
| if (verbose) |
| pr2serr("asked for defect list format %d, got %d\n", dl_format, |
| (param_arr[1] & 0x7)); |
| dl_format = (param_arr[1] & 0x7); |
| switch (dl_format) { /* Defect list formats: */ |
| case 0: /* short block */ |
| div = 4; |
| break; |
| case 1: /* extended bytes from index */ |
| div = 8; |
| break; |
| case 2: /* extended physical sector */ |
| div = 8; |
| break; |
| case 3: /* long block */ |
| case 4: /* bytes from index */ |
| case 5: /* physical sector */ |
| div = 8; |
| break; |
| case 6: /* vendor specific */ |
| if (verbose) |
| pr2serr("defect list format: vendor specific\n"); |
| break; |
| default: |
| pr2serr("defect list format %d unknown\n", dl_format); |
| break; |
| } |
| dl_len = sg_get_unaligned_be16(param_arr + 2); |
| if (0 == dl_len) |
| printf(">> Elements in %s: 0\n", lstp); |
| else { |
| if (0 == div) |
| printf(">> %s length=%d bytes [unknown number of elements]\n", |
| lstp, dl_len); |
| else |
| printf(">> Elements in %s: %d\n", lstp, |
| dl_len / div); |
| } |
| } |
| |
| 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 (0 == verbose) { |
| if (! sg_if_can2stderr("sg_reassign failed: ", ret)) |
| pr2serr("Some error occurred, try again with '-v' " |
| "or '-vv' for more information\n"); |
| } |
| return (ret >= 0) ? ret : SG_LIB_CAT_OTHER; |
| } |