xref: /aosp_15_r20/external/sg3_utils/src/sg_reassign.c (revision 44704f698541f6367e81f991ef8bb54ccbf3fc18)
1 /*
2  * Copyright (c) 2005-2019 Douglas Gilbert.
3  * All rights reserved.
4  * Use of this source code is governed by a BSD-style
5  * license that can be found in the BSD_LICENSE file.
6  *
7  * SPDX-License-Identifier: BSD-2-Clause
8  */
9 
10 #include <unistd.h>
11 #include <fcntl.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <stdarg.h>
15 #include <stdbool.h>
16 #include <string.h>
17 #include <ctype.h>
18 #include <getopt.h>
19 #include <limits.h>
20 #define __STDC_FORMAT_MACROS 1
21 #include <inttypes.h>
22 
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26 
27 #include "sg_lib.h"
28 #include "sg_cmds_basic.h"
29 #include "sg_cmds_extra.h"
30 #include "sg_unaligned.h"
31 #include "sg_pr2serr.h"
32 
33 /* A utility program originally written for the Linux OS SCSI subsystem.
34  *
35  * This utility invokes the REASSIGN BLOCKS SCSI command to reassign
36  * an existing (possibly damaged) lba on a direct access device (e.g.
37  * a disk) to a new physical location. The previous contents is
38  * recoverable then it is written to the remapped lba otherwise
39  * vendor specific data is written.
40  */
41 
42 static const char * version_str = "1.27 20191001";
43 
44 #define DEF_DEFECT_LIST_FORMAT 4        /* bytes from index */
45 
46 #define MAX_NUM_ADDR 1024
47 
48 #ifndef UINT32_MAX
49 #define UINT32_MAX ((uint32_t)-1)
50 #endif
51 
52 
53 static struct option long_options[] = {
54         {"address", required_argument, 0, 'a'},
55         {"dummy", no_argument, 0, 'd'},
56         {"eight", required_argument, 0, 'e'},
57         {"grown", no_argument, 0, 'g'},
58         {"help", no_argument, 0, 'h'},
59         {"hex", no_argument, 0, 'H'},
60         {"longlist", required_argument, 0, 'l'},
61         {"primary", no_argument, 0, 'p'},
62         {"verbose", no_argument, 0, 'v'},
63         {"version", no_argument, 0, 'V'},
64         {0, 0, 0, 0},
65 };
66 
67 static void
usage()68 usage()
69 {
70     pr2serr("Usage: sg_reassign [--address=A,A...] [--dummy] [--eight=0|1] "
71             "[--grown]\n"
72             "                   [--help] [--hex] [--longlist=0|1] "
73             "[--primary] [--verbose]\n"
74             "                   [--version] DEVICE\n"
75             "  where:\n"
76             "    --address=A,A...|-a A,A...    comma separated logical block "
77             "addresses\n"
78             "                                  one or more, assumed to be "
79             "decimal\n"
80             "    --address=-|-a -    read stdin for logical block "
81             "addresses\n"
82             "    --dummy|-d          prepare but do not execute REASSIGN "
83             "BLOCKS command\n"
84             "    --eight=0|1\n"
85             "      -e 0|1            force eight byte (64 bit) lbas "
86             "when 1,\n"
87             "                        four byte (32 bit) lbas when 0 "
88             "(def)\n"
89             "    --grown|-g          fetch grown defect list length, "
90             "don't reassign\n"
91             "    --help|-h           print out usage message\n"
92             "    --hex|-H            print response in hex (for '-g' or "
93             "'-p')\n"
94             "    --longlist=0|1\n"
95             "       -l 0|1           use 4 byte list length when 1, safe to "
96             "ignore\n"
97             "                        (def: 0 (2 byte list length))\n"
98             "    --primary|-p        fetch primary defect list length, "
99             "don't reassign\n"
100             "    --verbose|-v        increase verbosity\n"
101             "    --version|-V        print version string and exit\n\n"
102             "Perform a SCSI REASSIGN BLOCKS command (or READ DEFECT LIST)\n");
103 }
104 
105 /* Read numbers (up to 64 bits in size) from command line (comma (or
106  * (single) space) separated list) or from stdin (one per line, comma
107  * separated list or space separated list). Assumed decimal unless prefixed
108  * by '0x', '0X' or contains trailing 'h' or 'H' (which indicate hex).
109  * Returns 0 if ok, or error code. */
110 static int
build_lba_arr(const char * inp,uint64_t * lba_arr,int * lba_arr_len,int max_arr_len)111 build_lba_arr(const char * inp, uint64_t * lba_arr,
112               int * lba_arr_len, int max_arr_len)
113 {
114     int in_len, k, j, m;
115     const char * lcp;
116     int64_t ll;
117     char * cp;
118     char * c2p;
119 
120     if ((NULL == inp) || (NULL == lba_arr) ||
121         (NULL == lba_arr_len))
122         return SG_LIB_LOGIC_ERROR;
123     lcp = inp;
124     in_len = strlen(inp);
125     if (0 == in_len)
126         *lba_arr_len = 0;
127     if ('-' == inp[0]) {        /* read from stdin */
128         char line[1024];
129         int off = 0;
130 
131         for (j = 0; j < 512; ++j) {
132             if (NULL == fgets(line, sizeof(line), stdin))
133                 break;
134             // could improve with carry_over logic if sizeof(line) too small
135             in_len = strlen(line);
136             if (in_len > 0) {
137                 if ('\n' == line[in_len - 1]) {
138                     --in_len;
139                     line[in_len] = '\0';
140                 }
141             }
142             if (in_len < 1)
143                 continue;
144             lcp = line;
145             m = strspn(lcp, " \t");
146             if (m == in_len)
147                 continue;
148             lcp += m;
149             in_len -= m;
150             if ('#' == *lcp)
151                 continue;
152             k = strspn(lcp, "0123456789aAbBcCdDeEfFhHxX ,\t");
153             if ((k < in_len) && ('#' != lcp[k])) {
154                 pr2serr("%s: syntax error at line %d, pos %d\n", __func__,
155                         j + 1, m + k + 1);
156                 return SG_LIB_SYNTAX_ERROR;
157             }
158             for (k = 0; k < 1024; ++k) {
159                 ll = sg_get_llnum_nomult(lcp);
160                 if (-1 != ll) {
161                     if ((off + k) >= max_arr_len) {
162                         pr2serr("%s: array length exceeded\n", __func__);
163                         return SG_LIB_SYNTAX_ERROR;
164                     }
165                     lba_arr[off + k] = (uint64_t)ll;
166                     lcp = strpbrk(lcp, " ,\t");
167                     if (NULL == lcp)
168                         break;
169                     lcp += strspn(lcp, " ,\t");
170                     if ('\0' == *lcp)
171                         break;
172                 } else {
173                     if ('#' == *lcp) {
174                         --k;
175                         break;
176                     }
177                     pr2serr("%s: error in line %d, at pos %d\n", __func__,
178                             j + 1, (int)(lcp - line + 1));
179                     return SG_LIB_SYNTAX_ERROR;
180                 }
181             }
182             off += (k + 1);
183         }
184         *lba_arr_len = off;
185     } else {        /* list of numbers (default decimal) on command line */
186         k = strspn(inp, "0123456789aAbBcCdDeEfFhHxX, ");
187         if (in_len != k) {
188             pr2serr("%s: error at pos %d\n", __func__, k + 1);
189             return SG_LIB_SYNTAX_ERROR;
190         }
191         for (k = 0; k < max_arr_len; ++k) {
192             ll = sg_get_llnum_nomult(lcp);
193             if (-1 != ll) {
194                 lba_arr[k] = (uint64_t)ll;
195                 cp = (char *)strchr(lcp, ',');
196                 c2p = (char *)strchr(lcp, ' ');
197                 if (NULL == cp)
198                     cp = c2p;
199                 if (NULL == cp)
200                     break;
201                 if (c2p && (c2p < cp))
202                     cp = c2p;
203                 lcp = cp + 1;
204             } else {
205                 pr2serr("%s: error at pos %d\n", __func__,
206                         (int)(lcp - inp + 1));
207                 return SG_LIB_SYNTAX_ERROR;
208             }
209         }
210         *lba_arr_len = k + 1;
211         if (k == max_arr_len) {
212             pr2serr("%s: array length exceeded\n", __func__);
213             return SG_LIB_SYNTAX_ERROR;
214         }
215     }
216     return 0;
217 }
218 
219 
220 int
main(int argc,char * argv[])221 main(int argc, char * argv[])
222 {
223     bool dummy = false;
224     bool eight = false;
225     bool eight_given = false;
226     bool got_addr = false;
227     bool longlist = false;
228     bool primary = false;
229     bool grown = false;
230     bool verbose_given = false;
231     bool version_given = false;
232     int res, c, num, k, j;
233     int sg_fd = -1;
234     int addr_arr_len = 0;
235     int do_hex = 0;
236     int verbose = 0;
237     const char * device_name = NULL;
238     uint64_t addr_arr[MAX_NUM_ADDR];
239     uint8_t param_arr[4 + (MAX_NUM_ADDR * 8)];
240     char b[80];
241     int param_len = 4;
242     int ret = 0;
243 
244     while (1) {
245         int option_index = 0;
246 
247         c = getopt_long(argc, argv, "a:de:ghHl:pvV", long_options,
248                         &option_index);
249         if (c == -1)
250             break;
251 
252         switch (c) {
253         case 'a':
254             memset(addr_arr, 0, sizeof(addr_arr));
255             if ((res = build_lba_arr(optarg, addr_arr, &addr_arr_len,
256                                      MAX_NUM_ADDR))) {
257                 pr2serr("bad argument to '--address'\n");
258                 return res;
259             }
260             got_addr = true;
261             break;
262         case 'd':
263             dummy = true;
264             break;
265         case 'e':
266             num = sscanf(optarg, "%d", &res);
267             if ((1 == num) && ((0 == res) || (1 == res)))
268                 eight = !! res;
269             else {
270                 pr2serr("value for '--eight=' must be 0 or 1\n");
271                 return SG_LIB_SYNTAX_ERROR;
272             }
273             eight_given = true;
274             break;
275         case 'g':
276             grown = true;
277             break;
278         case 'h':
279         case '?':
280             usage();
281             return 0;
282         case 'H':
283             ++do_hex;
284             break;
285         case 'l':
286             num = sscanf(optarg, "%d", &res);
287             if ((1 == num) && ((0 == res) || (1 == res)))
288                 longlist = !!res;
289             else {
290                 pr2serr("value for '--longlist=' must be 0 or 1\n");
291                 return SG_LIB_SYNTAX_ERROR;
292             }
293             break;
294         case 'p':
295             primary = true;
296             break;
297         case 'v':
298             verbose_given = true;
299             ++verbose;
300             break;
301         case 'V':
302             version_given = true;
303             break;
304         default:
305             pr2serr("unrecognised option code 0x%x ??\n", c);
306             usage();
307             return SG_LIB_SYNTAX_ERROR;
308         }
309     }
310     if (optind < argc) {
311         if (NULL == device_name) {
312             device_name = argv[optind];
313             ++optind;
314         }
315         if (optind < argc) {
316             for (; optind < argc; ++optind)
317                 pr2serr("Unexpected extra argument: %s\n", argv[optind]);
318             usage();
319             return SG_LIB_SYNTAX_ERROR;
320         }
321     }
322 #ifdef DEBUG
323     pr2serr("In DEBUG mode, ");
324     if (verbose_given && version_given) {
325         pr2serr("but override: '-vV' given, zero verbose and continue\n");
326         verbose_given = false;
327         version_given = false;
328         verbose = 0;
329     } else if (! verbose_given) {
330         pr2serr("set '-vv'\n");
331         verbose = 2;
332     } else
333         pr2serr("keep verbose=%d\n", verbose);
334 #else
335     if (verbose_given && version_given)
336         pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
337 #endif
338     if (version_given) {
339         pr2serr("version: %s\n", version_str);
340         return 0;
341     }
342 
343     if (NULL == device_name) {
344         pr2serr("Missing device name!\n\n");
345         usage();
346         return SG_LIB_SYNTAX_ERROR;
347     }
348     if (grown || primary) {
349         if (got_addr) {
350             pr2serr("can't have '--address=' with '--grown' or '--primary'\n");
351             usage();
352             return SG_LIB_CONTRADICT;
353         }
354     } else if ((! got_addr) || (addr_arr_len < 1)) {
355         pr2serr("need at least one address (see '--address=')\n");
356         usage();
357         return SG_LIB_SYNTAX_ERROR;
358     }
359     if (got_addr) {
360         for (k = 0; k < addr_arr_len; ++k) {
361             if (addr_arr[k] >= UINT32_MAX) {
362                 if (! eight_given) {
363                     eight = true;
364                     break;
365                 } else if (! eight) {
366                     pr2serr("address number %d exceeds 32 bits so "
367                             "'--eight=0' invalid\n", k + 1);
368                     return SG_LIB_CONTRADICT;
369                 }
370             }
371         }
372         if (! eight_given)
373             eight = false;
374 
375         k = 4;
376         for (j = 0; j < addr_arr_len; ++j) {
377             if (eight) {
378                 sg_put_unaligned_be64(addr_arr[j], param_arr + k);
379                 k += 8;
380             } else {
381                 sg_put_unaligned_be32((uint32_t)addr_arr[j], param_arr + k);
382                 k += 4;
383             }
384         }
385         param_len = k;
386         k -= 4;
387         if (longlist)
388             sg_put_unaligned_be32((uint32_t)k, param_arr + 0);
389         else
390             sg_put_unaligned_be16((uint16_t)k, param_arr + 2);
391     }
392 
393     sg_fd = sg_cmds_open_device(device_name, false /* rw */, verbose);
394     if (sg_fd < 0) {
395         if (verbose)
396             pr2serr("open error: %s: %s\n", device_name,
397                     safe_strerror(-sg_fd));
398         ret = sg_convert_errno(-sg_fd);
399         goto err_out;
400     }
401 
402     if (got_addr) {
403         if (dummy) {
404             pr2serr(">>> dummy: REASSIGN BLOCKS not executed\n");
405             if (verbose) {
406                 pr2serr("  Would have reassigned these blocks:\n");
407                 for (j = 0; j < addr_arr_len; ++j)
408                     printf("    0x%" PRIx64 "\n", addr_arr[j]);
409             }
410             return 0;
411         }
412         res = sg_ll_reassign_blocks(sg_fd, eight, longlist, param_arr,
413                                     param_len, true, verbose);
414         ret = res;
415         if (res) {
416             sg_get_category_sense_str(res, sizeof(b), b, verbose);
417             pr2serr("REASSIGN BLOCKS: %s\n", b);
418             goto err_out;
419         }
420     } else /* if (grown || primary) */ {
421         int dl_format = DEF_DEFECT_LIST_FORMAT;
422         int div = 0;
423         int dl_len;
424         bool got_grown, got_primary;
425         const char * lstp;
426 
427         param_len = 4;
428         memset(param_arr, 0, param_len);
429         res = sg_ll_read_defect10(sg_fd, primary, grown, dl_format,
430                                   param_arr, param_len, false, verbose);
431         ret = res;
432         if (res) {
433             sg_get_category_sense_str(res, sizeof(b), b, verbose);
434             pr2serr("READ DEFECT DATA(10): %s\n", b);
435             goto err_out;
436         }
437         if (do_hex) {
438             hex2stdout(param_arr, param_len, 1);
439             goto err_out;       /* ret is zero */
440         }
441         got_grown = !!(param_arr[1] & 0x8);
442         got_primary = !!(param_arr[1] & 0x10);
443         if (got_grown && got_primary)
444             lstp = "grown and primary defect lists";
445         else if (got_grown)
446             lstp = "grown defect list";
447         else if (got_primary)
448             lstp = "primary defect list";
449         else {
450             pr2serr("didn't get grown or primary list in response\n");
451             goto err_out;
452         }
453         if (verbose)
454             pr2serr("asked for defect list format %d, got %d\n", dl_format,
455                     (param_arr[1] & 0x7));
456         dl_format = (param_arr[1] & 0x7);
457         switch (dl_format) {    /* Defect list formats: */
458             case 0:     /* short block */
459                 div = 4;
460                 break;
461             case 1:     /* extended bytes from index */
462                 div = 8;
463                 break;
464             case 2:     /* extended physical sector */
465                 div = 8;
466                 break;
467             case 3:     /* long block */
468             case 4:     /* bytes from index */
469             case 5:     /* physical sector */
470                 div = 8;
471                 break;
472             case 6:     /* vendor specific */
473                 if (verbose)
474                     pr2serr("defect list format: vendor specific\n");
475                 break;
476             default:
477                 pr2serr("defect list format %d unknown\n", dl_format);
478                 break;
479         }
480         dl_len = sg_get_unaligned_be16(param_arr + 2);
481         if (0 == dl_len)
482             printf(">> Elements in %s: 0\n", lstp);
483         else {
484             if (0 == div)
485                 printf(">> %s length=%d bytes [unknown number of elements]\n",
486                        lstp, dl_len);
487             else
488                 printf(">> Elements in %s: %d\n", lstp,
489                        dl_len / div);
490         }
491     }
492 
493 err_out:
494     if (sg_fd >= 0) {
495         res = sg_cmds_close_device(sg_fd);
496         if (res < 0) {
497             pr2serr("close error: %s\n", safe_strerror(-res));
498             if (0 == ret)
499                 ret = sg_convert_errno(-res);
500         }
501     }
502     if (0 == verbose) {
503         if (! sg_if_can2stderr("sg_reassign failed: ", ret))
504             pr2serr("Some error occurred, try again with '-v' "
505                     "or '-vv' for more information\n");
506     }
507     return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
508 }
509