xref: /aosp_15_r20/external/sg3_utils/src/sg_decode_sense.c (revision 44704f698541f6367e81f991ef8bb54ccbf3fc18)
1 /*
2  * Copyright (c) 2010-2022 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 <stdbool.h>
15 #include <string.h>
16 #include <errno.h>
17 #include <limits.h>
18 #include <ctype.h>
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <getopt.h>
22 #define __STDC_FORMAT_MACROS 1
23 #include <inttypes.h>
24 
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28 #include "sg_lib.h"
29 #include "sg_pr2serr.h"
30 #include "sg_unaligned.h"
31 
32 
33 static const char * version_str = "1.32 20220730";
34 
35 #define MY_NAME "sg_decode_sense"
36 
37 #define MAX_SENSE_LEN 8192 /* max descriptor format actually: 255+8 */
38 
39 static struct option long_options[] = {
40     {"binary", required_argument, 0, 'b'},
41     {"cdb", no_argument, 0, 'c'},
42     {"err", required_argument, 0, 'e'},
43     {"exit-status", required_argument, 0, 'e'},
44     {"exit_status", required_argument, 0, 'e'},
45     {"file", required_argument, 0, 'f'},
46     {"help", no_argument, 0, 'h'},
47     {"hex", no_argument, 0, 'H'},
48     {"in", required_argument, 0, 'i'},          /* don't advertise */
49     {"inhex", required_argument, 0, 'i'},       /* same as --file */
50     {"ignore-first", no_argument, 0, 'I'},
51     {"ignore_first", no_argument, 0, 'I'},
52     {"json", optional_argument, 0, 'j'},
53     {"nodecode", no_argument, 0, 'N'},
54     {"nospace", no_argument, 0, 'n'},
55     {"status", required_argument, 0, 's'},
56     {"verbose", no_argument, 0, 'v'},
57     {"version", no_argument, 0, 'V'},
58     {"write", required_argument, 0, 'w'},
59     {0, 0, 0, 0},
60 };
61 
62 struct opts_t {
63     bool do_binary;
64     bool do_cdb;
65     bool do_help;
66     bool no_decode;
67     bool no_space;
68     bool do_status;
69     bool verbose_given;
70     bool version_given;
71     bool err_given;
72     bool file_given;
73     bool ignore_first;
74     const char * fname;
75     int es_val;
76     int hex_count;
77     int sense_len;
78     int sstatus;
79     int verbose;
80     const char * wfname;
81     const char * no_space_str;
82     sgj_state json_st;
83     uint8_t sense[MAX_SENSE_LEN + 4];
84 };
85 
86 static char concat_buff[1024];
87 
88 
89 static void
usage()90 usage()
91 {
92   pr2serr("Usage: sg_decode_sense [--binary=BFN] [--cdb] [--err=ES] "
93           "[--file=HFN]\n"
94           "                       [--help] [--hex] [--inhex=HFN] "
95           "[--ignore-first]\n"
96           "                       [--json[=JO]] [--nodecode] [--nospace] "
97           "[--status=SS]\n"
98           "                       [--verbose] [--version] [--write=WFN] "
99           "H1 H2 H3 ...\n"
100           "  where:\n"
101           "    --binary=BFN|-b BFN    BFN is a file name to read sense "
102           "data in\n"
103           "                          binary from. If BFN is '-' then read "
104           "from stdin\n"
105           "    --cdb|-c              decode given hex as cdb rather than "
106           "sense data\n"
107           "    --err=ES|-e ES        ES is Exit Status from utility in this "
108           "package\n"
109           "    --file=HFN|-f HFN     HFN is a file name from which to read "
110           "sense data\n"
111           "                          in ASCII hexadecimal. Interpret '-' "
112           "as stdin\n"
113           "    --help|-h             print out usage message\n"
114           "    --hex|-H              used together with --write=WFN, to "
115           "write out\n"
116           "                          C language style ASCII hex (instead "
117           "of binary).\n"
118           "                          Otherwise don't decode, output incoming "
119           "data in\n"
120           "                          hex (used '-HH' or '-HHH' for different "
121           "formats)\n"
122           "    --inhex=HFN|-i HFN    same as action as --file=HFN\n"
123           "    --ignore-first|-I     when reading hex (e.g. with --file=HFN) "
124           "skip\n"
125           "                          the first hexadecimal value on each "
126           "line\n"
127           "    --json[=JO]|-j[JO]    output in JSON instead of human "
128           "readable text.\n"
129           "                          Use --json=? for JSON help\n"
130           "    --nodecode|-N         do not decode, may be neither sense "
131           "nor cdb\n"
132           "    --nospace|-n          no spaces or other separators between "
133           "pairs of\n"
134           "                          hex digits (e.g. '3132330A')\n"
135           "    --status=SS |-s SS    SCSI status value in hex\n"
136           "    --verbose|-v          increase verbosity\n"
137           "    --version|-V          print version string then exit\n"
138           "    --write=WFN |-w WFN    write sense data in binary to WFN, "
139           "create if\n"
140           "                           required else truncate prior to "
141           "writing\n\n"
142           "Decodes SCSI sense data given on the command line as a sequence "
143           "of\nhexadecimal bytes (H1 H2 H3 ...) . Alternatively the sense "
144           "data can\nbe in a binary file or in a file containing ASCII "
145           "hexadecimal. If\n'--cdb' is given then interpret hex as SCSI CDB "
146           "rather than sense data.\n"
147           );
148 }
149 
150 static int
parse_cmd_line(struct opts_t * op,int argc,char * argv[])151 parse_cmd_line(struct opts_t *op, int argc, char *argv[])
152 {
153     int c, n;
154     unsigned int ui;
155     long val;
156     char * avp;
157     char *endptr;
158 
159     while (1) {
160         c = getopt_long(argc, argv, "b:ce:f:hHi:Ij::nNs:vVw:", long_options,
161                         NULL);
162         if (c == -1)
163             break;
164 
165         switch (c) {
166         case 'b':
167             if (op->fname) {
168                 pr2serr("expect only one '--binary=BFN', '--file=HFN' or "
169                         "'--inhex=HFN' option\n");
170                 return SG_LIB_CONTRADICT;
171             }
172             op->do_binary = true;
173             op->fname = optarg;
174             break;
175         case 'c':
176             op->do_cdb = true;
177             break;
178         case 'e':
179             n = sg_get_num(optarg);
180             if ((n < 0) || (n > 255)) {
181                 pr2serr("--err= expected number from 0 to 255 inclusive\n");
182                 return SG_LIB_SYNTAX_ERROR;
183             }
184             op->err_given = true;
185             op->es_val = n;
186             break;
187         case 'f':
188             if (op->fname) {
189                 pr2serr("expect only one '--binary=BFN', '--file=HFN' or "
190                         "'--inhex=HFN' option\n");
191                 return SG_LIB_CONTRADICT;
192             }
193             op->file_given = true;
194             op->fname = optarg;
195             break;
196         case 'h':
197         case '?':
198             op->do_help = true;
199             return 0;
200         case 'H':
201             op->hex_count++;
202             break;
203         case 'i':
204             if (op->fname) {
205                 pr2serr("expect only one '--binary=BFN', '--file=HFN' or "
206                         "'--inhex=HFN' option\n");
207                 return SG_LIB_CONTRADICT;
208             }
209             op->file_given = true;
210             op->fname = optarg;
211             break;
212         case 'I':
213             op->ignore_first = true;
214             break;
215        case 'j':
216             if (! sgj_init_state(&op->json_st, optarg)) {
217                 int bad_char = op->json_st.first_bad_char;
218                 char e[1500];
219 
220                 if (bad_char) {
221                     pr2serr("bad argument to --json= option, unrecognized "
222                             "character '%c'\n\n", bad_char);
223                 }
224                 sg_json_usage(0, e, sizeof(e));
225                 pr2serr("%s", e);
226                 return SG_LIB_SYNTAX_ERROR;
227             }
228             break;
229         case 'n':
230             op->no_space = true;
231             break;
232         case 'N':
233             op->no_decode = true;
234             break;
235         case 's':
236             if (1 != sscanf(optarg, "%x", &ui)) {
237                 pr2serr("'--status=SS' expects a byte value\n");
238                 return SG_LIB_SYNTAX_ERROR;
239             }
240             if (ui > 0xff) {
241                 pr2serr("'--status=SS' byte value exceeds FF\n");
242                 return SG_LIB_SYNTAX_ERROR;
243             }
244             op->do_status = true;
245             op->sstatus = ui;
246             break;
247         case 'v':
248             op->verbose_given = true;
249             ++op->verbose;
250             break;
251         case 'V':
252             op->version_given = true;
253             break;
254         case 'w':
255             op->wfname = optarg;
256             break;
257         default:
258             return SG_LIB_SYNTAX_ERROR;
259         }
260     }
261     if (op->err_given)
262         goto the_end;
263 
264     while (optind < argc) {
265         avp = argv[optind++];
266         if (op->no_space) {
267             if (op->no_space_str) {
268                 if ('\0' == concat_buff[0]) {
269                     if (strlen(op->no_space_str) > sizeof(concat_buff)) {
270                         pr2serr("'--nospace' concat_buff overflow\n");
271                         return SG_LIB_SYNTAX_ERROR;
272                     }
273                     strcpy(concat_buff, op->no_space_str);
274                 }
275                 if ((strlen(concat_buff) + strlen(avp)) >=
276                     sizeof(concat_buff)) {
277                     pr2serr("'--nospace' concat_buff overflow\n");
278                     return SG_LIB_SYNTAX_ERROR;
279                 }
280                 if (op->version_given)
281                     pr2serr("'--nospace' and found whitespace so "
282                             "concatenate\n");
283                 strcat(concat_buff, avp);
284                 op->no_space_str = concat_buff;
285             } else
286                 op->no_space_str = avp;
287             continue;
288         }
289         val = strtol(avp, &endptr, 16);
290         if (*avp == '\0' || *endptr != '\0' || val < 0x00 || val > 0xff) {
291             pr2serr("Invalid byte '%s'\n", avp);
292             return SG_LIB_SYNTAX_ERROR;
293         }
294 
295         if (op->sense_len > MAX_SENSE_LEN) {
296             pr2serr("sense data too long (max. %d bytes)\n", MAX_SENSE_LEN);
297             return SG_LIB_SYNTAX_ERROR;
298         }
299         op->sense[op->sense_len++] = (uint8_t)val;
300     }
301 the_end:
302     return 0;
303 }
304 
305 /* Keep this format (e.g. 0xff,0x12,...) for backward compatibility */
306 static void
write2wfn(FILE * fp,struct opts_t * op)307 write2wfn(FILE * fp, struct opts_t * op)
308 {
309     int k, n;
310     size_t s;
311     char b[128];
312 
313     for (k = 0, n = 0; k < op->sense_len; ++k) {
314         n += sprintf(b + n, "0x%02x,", op->sense[k]);
315         if (15 == (k % 16)) {
316             b[n] = '\n';
317             s = fwrite(b, 1, n + 1, fp);
318             if ((int)s != (n + 1))
319                 pr2serr("only able to write %d of %d bytes to %s\n",
320                         (int)s, n + 1, op->wfname);
321             n = 0;
322         }
323     }
324     if (n > 0) {
325         b[n] = '\n';
326         s = fwrite(b, 1, n + 1, fp);
327         if ((int)s != (n + 1))
328             pr2serr("only able to write %d of %d bytes to %s\n", (int)s,
329                     n + 1, op->wfname);
330     }
331 }
332 
333 
334 int
main(int argc,char * argv[])335 main(int argc, char *argv[])
336 {
337     bool as_json;
338     int k, err, blen;
339     int ret = 0;
340     unsigned int ui;
341     size_t s;
342     struct opts_t * op;
343     FILE * fp = NULL;
344     const char * cp;
345     sgj_state * jsp;
346     sgj_opaque_p jop = NULL;
347     uint8_t * free_op_buff = NULL;
348     char b[2048];
349 
350     op = (struct opts_t *)sg_memalign(sizeof(*op), 0 /* page align */,
351 				      &free_op_buff, false);
352     if (NULL == op) {
353         pr2serr("Unable to allocate heap for options structure\n");
354         ret = sg_convert_errno(ENOMEM);
355         goto clean_op;
356     }
357     blen = sizeof(b);
358     memset(b, 0, blen);
359     ret = parse_cmd_line(op, argc, argv);
360 
361 #ifdef DEBUG
362     pr2serr("In DEBUG mode, ");
363     if (op->verbose_given && op->version_given) {
364         pr2serr("but override: '-vV' given, zero verbose and continue\n");
365         op->verbose_given = false;
366         op->version_given = false;
367         op->verbose = 0;
368     } else if (! op->verbose_given) {
369         pr2serr("set '-vv'\n");
370         op->verbose = 2;
371     } else
372         pr2serr("keep verbose=%d\n", op->verbose);
373 #else
374     if (op->verbose_given && op->version_given)
375         pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
376 #endif
377     if (op->version_given) {
378         pr2serr("version: %s\n", version_str);
379         goto clean_op;
380     }
381     if (ret != 0) {
382         usage();
383         goto clean_op;
384     } else if (op->do_help) {
385         usage();
386         goto clean_op;
387     }
388     as_json = op->json_st.pr_as_json;
389     jsp = &op->json_st;
390     if (as_json)
391         jop = sgj_start_r(MY_NAME, version_str, argc, argv, jsp);
392 
393     if (op->err_given) {
394         char d[128];
395         const int dlen = sizeof(d);
396 
397         if (! sg_exit2str(op->es_val, op->verbose > 1, dlen, d))
398             snprintf(d, dlen, "Unable to decode exit status %d", op->es_val);
399         if (1 & op->verbose) /* odd values of verbose print to stderr */
400             pr2serr("%s\n", d);
401         else    /* even values of verbose (including not given) to stdout */
402             printf("%s\n", d);
403         goto fini;
404     }
405 
406     if (op->do_status) {
407         sg_get_scsi_status_str(op->sstatus, blen, b);
408         printf("SCSI status: %s\n", b);
409     }
410 
411     if ((0 == op->sense_len) && op->no_space_str) {
412         if (op->verbose > 2)
413             pr2serr("no_space str: %s\n", op->no_space_str);
414         cp = op->no_space_str;
415         for (k = 0; isxdigit((uint8_t)cp[k]) &&
416                     isxdigit((uint8_t)cp[k + 1]); k += 2) {
417             if (1 != sscanf(cp + k, "%2x", &ui)) {
418                 pr2serr("bad no_space hex string: %s\n", cp);
419                 ret = SG_LIB_SYNTAX_ERROR;
420                 goto fini;
421             }
422             op->sense[op->sense_len++] = (uint8_t)ui;
423         }
424     }
425 
426     if ((0 == op->sense_len) && (! op->do_binary) && (! op->file_given)) {
427         if (op->do_status) {
428             ret = 0;
429             goto fini;
430         }
431         pr2serr(">> Need sense/cdb/arbitrary data on the command line or "
432                 "in a file\n\n");
433         usage();
434         ret = SG_LIB_SYNTAX_ERROR;
435         goto fini;
436     }
437     if (op->sense_len && (op->do_binary || op->file_given)) {
438         pr2serr(">> Need sense data on command line or in a file, not "
439                 "both\n\n");
440         ret = SG_LIB_CONTRADICT;
441         goto fini;
442     }
443     if (op->do_binary && op->file_given) {
444         pr2serr(">> Either a binary file or a ASCII hexadecimal, file not "
445                 "both\n\n");
446         ret = SG_LIB_CONTRADICT;
447         goto fini;
448     }
449 
450     if (op->do_binary) {
451         fp = fopen(op->fname, "r");
452         if (NULL == fp) {
453             err = errno;
454             pr2serr("unable to open file: %s: %s\n", op->fname,
455                     safe_strerror(err));
456             ret = sg_convert_errno(err);
457             goto fini;
458         }
459         s = fread(op->sense, 1, MAX_SENSE_LEN, fp);
460         fclose(fp);
461         if (0 == s) {
462             pr2serr("read nothing from file: %s\n", op->fname);
463             ret = SG_LIB_SYNTAX_ERROR;
464             goto fini;
465         }
466         op->sense_len = s;
467     } else if (op->file_given) {
468         ret = sg_f2hex_arr(op->fname, false, op->no_space, op->sense,
469                            &op->sense_len,
470                            (op->ignore_first ? -MAX_SENSE_LEN :
471                                                MAX_SENSE_LEN));
472         if (ret) {
473             pr2serr("unable to decode ASCII hex from file: %s\n", op->fname);
474             goto fini;
475         }
476     }
477 
478     if (op->sense_len > 0) {
479         if (op->wfname || op->hex_count) {
480             if (op->wfname) {
481                 if (NULL == ((fp = fopen(op->wfname, "w")))) {
482                     err =errno;
483                     perror("open");
484                     pr2serr("trying to write to %s\n", op->wfname);
485                     ret = sg_convert_errno(err);
486                     goto fini;
487                 }
488             } else
489                 fp = stdout;
490 
491             if (op->wfname && (1 == op->hex_count))
492                 write2wfn(fp, op);
493             else if (op->hex_count && (2 != op->hex_count))
494                 dStrHexFp((const char *)op->sense, op->sense_len,
495                            ((1 == op->hex_count) ? 1 : -1), fp);
496             else if (op->hex_count)
497                 dStrHexFp((const char *)op->sense, op->sense_len, 0, fp);
498             else {
499                 s = fwrite(op->sense, 1, op->sense_len, fp);
500                 if ((int)s != op->sense_len)
501                     pr2serr("only able to write %d of %d bytes to %s\n",
502                             (int)s, op->sense_len, op->wfname);
503             }
504             if (op->wfname)
505                 fclose(fp);
506         } else if (op->no_decode) {
507             if (op->verbose > 1)
508                 pr2serr("Not decoding as %s because --nodecode given\n",
509                         (op->do_cdb ? "cdb" : "sense"));
510         } else if (op->do_cdb) {
511             int sa, opcode;
512 
513             opcode = op->sense[0];
514             if ((0x75 == opcode) || (0x7e == opcode) || (op->sense_len > 16))
515                 sa = sg_get_unaligned_be16(op->sense + 8);
516             else if (op->sense_len > 1)
517                 sa = op->sense[1] & 0x1f;
518             else
519                 sa = 0;
520             sg_get_opcode_sa_name(opcode, sa, 0, blen, b);
521             printf("%s\n", b);
522         } else {
523             if (as_json) {
524                 sgj_js_sense(jsp, jop, op->sense, op->sense_len);
525                 if (jsp->pr_out_hr) {
526                     sg_get_sense_str(NULL, op->sense, op->sense_len,
527                                      op->verbose, blen, b);
528                      sgj_js_str_out(jsp, b, strlen(b));
529                 }
530             } else {
531                 sg_get_sense_str(NULL, op->sense, op->sense_len,
532                                  op->verbose, blen, b);
533                 printf("%s\n", b);
534             }
535         }
536     }
537 fini:
538    if (as_json) {
539         if (0 == op->hex_count)
540             sgj_js2file(&op->json_st, NULL, ret, stdout);
541         sgj_finish(jsp);
542     }
543 clean_op:
544     if (free_op_buff)
545         free(free_op_buff);
546     return ret;
547 }
548