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