| /* |
| * Copyright (c) 2010-2022 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 <stdbool.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <limits.h> |
| #include <ctype.h> |
| #include <sys/types.h> |
| #include <sys/stat.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_pr2serr.h" |
| #include "sg_unaligned.h" |
| |
| |
| static const char * version_str = "1.28 20220626"; |
| |
| #define MY_NAME "sg_decode_sense" |
| |
| #define MAX_SENSE_LEN 4096 /* max descriptor format actually: 255+8 */ |
| |
| static struct option long_options[] = { |
| {"binary", required_argument, 0, 'b'}, |
| {"cdb", no_argument, 0, 'c'}, |
| {"err", required_argument, 0, 'e'}, |
| {"exit-status", required_argument, 0, 'e'}, |
| {"exit_status", required_argument, 0, 'e'}, |
| {"file", required_argument, 0, 'f'}, |
| {"help", no_argument, 0, 'h'}, |
| {"hex", no_argument, 0, 'H'}, |
| {"in", required_argument, 0, 'i'}, /* don't advertise */ |
| {"json", optional_argument, 0, 'j'}, |
| {"inhex", required_argument, 0, 'i'}, /* same as --file */ |
| {"nodecode", no_argument, 0, 'N'}, |
| {"nospace", no_argument, 0, 'n'}, |
| {"status", required_argument, 0, 's'}, |
| {"verbose", no_argument, 0, 'v'}, |
| {"version", no_argument, 0, 'V'}, |
| {"write", required_argument, 0, 'w'}, |
| {0, 0, 0, 0}, |
| }; |
| |
| struct opts_t { |
| bool do_binary; |
| bool do_cdb; |
| bool do_help; |
| bool no_decode; |
| bool no_space; |
| bool do_status; |
| bool verbose_given; |
| bool version_given; |
| bool err_given; |
| bool file_given; |
| const char * fname; |
| int es_val; |
| int hex_count; |
| int sense_len; |
| int sstatus; |
| int verbose; |
| const char * wfname; |
| const char * no_space_str; |
| sgj_state json_st; |
| uint8_t sense[MAX_SENSE_LEN + 4]; |
| }; |
| |
| static char concat_buff[1024]; |
| |
| |
| static void |
| usage() |
| { |
| pr2serr("Usage: sg_decode_sense [--binary=BFN] [--cdb] [--err=ES] " |
| "[--file=HFN]\n" |
| " [--help] [--hex] [--inhex=HFN] " |
| "[--json[=JO]]\n" |
| " [--nodecode] [--nospace] [--status=SS] " |
| "[--verbose]\n" |
| " [--version] [--write=WFN] H1 H2 H3 ...\n" |
| " where:\n" |
| " --binary=BFN|-b BFN BFN is a file name to read sense " |
| "data in\n" |
| " binary from. If BFN is '-' then read " |
| "from stdin\n" |
| " --cdb|-c decode given hex as cdb rather than " |
| "sense data\n" |
| " --err=ES|-e ES ES is Exit Status from utility in this " |
| "package\n" |
| " --file=HFN|-f HFN HFN is a file name from which to read " |
| "sense data\n" |
| " in ASCII hexadecimal. Interpret '-' " |
| "as stdin\n" |
| " --help|-h print out usage message\n" |
| " --hex|-H used together with --write=WFN, to " |
| "write out\n" |
| " C language style ASCII hex (instead " |
| "of binary).\n" |
| " Otherwise don't decode, output incoming " |
| "data in\n" |
| " hex (used '-HH' or '-HHH' for different " |
| "formats)\n" |
| " --inhex=HFN|-i HFN same as action as --file=HFN\n" |
| " --json[=JO]|-j[JO] output in JSON instead of human " |
| "readable text.\n" |
| " Use --json=? for JSON help\n" |
| " --nodecode|-N do not decode, may be neither sense " |
| "nor cdb\n" |
| " --nospace|-n no spaces or other separators between " |
| "pairs of\n" |
| " hex digits (e.g. '3132330A')\n" |
| " --status=SS |-s SS SCSI status value in hex\n" |
| " --verbose|-v increase verbosity\n" |
| " --version|-V print version string then exit\n" |
| " --write=WFN |-w WFN write sense data in binary to WFN, " |
| "create if\n" |
| " required else truncate prior to " |
| "writing\n\n" |
| "Decodes SCSI sense data given on the command line as a sequence " |
| "of\nhexadecimal bytes (H1 H2 H3 ...) . Alternatively the sense " |
| "data can\nbe in a binary file or in a file containing ASCII " |
| "hexadecimal. If\n'--cdb' is given then interpret hex as SCSI CDB " |
| "rather than sense data.\n" |
| ); |
| } |
| |
| static int |
| parse_cmd_line(struct opts_t *op, int argc, char *argv[]) |
| { |
| int c, n; |
| unsigned int ui; |
| long val; |
| char * avp; |
| char *endptr; |
| |
| while (1) { |
| c = getopt_long(argc, argv, "b:ce:f:hHi:j::nNs:vVw:", long_options, |
| NULL); |
| if (c == -1) |
| break; |
| |
| switch (c) { |
| case 'b': |
| if (op->fname) { |
| pr2serr("expect only one '--binary=BFN', '--file=HFN' or " |
| "'--inhex=HFN' option\n"); |
| return SG_LIB_CONTRADICT; |
| } |
| op->do_binary = true; |
| op->fname = optarg; |
| break; |
| case 'c': |
| op->do_cdb = true; |
| break; |
| case 'e': |
| n = sg_get_num(optarg); |
| if ((n < 0) || (n > 255)) { |
| pr2serr("--err= expected number from 0 to 255 inclusive\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->err_given = true; |
| op->es_val = n; |
| break; |
| case 'f': |
| if (op->fname) { |
| pr2serr("expect only one '--binary=BFN', '--file=HFN' or " |
| "'--inhex=HFN' option\n"); |
| return SG_LIB_CONTRADICT; |
| } |
| op->file_given = true; |
| op->fname = optarg; |
| break; |
| case 'h': |
| case '?': |
| op->do_help = true; |
| return 0; |
| case 'H': |
| op->hex_count++; |
| break; |
| case 'i': |
| if (op->fname) { |
| pr2serr("expect only one '--binary=BFN', '--file=HFN' or " |
| "'--inhex=HFN' option\n"); |
| return SG_LIB_CONTRADICT; |
| } |
| op->file_given = true; |
| op->fname = optarg; |
| break; |
| case 'j': |
| if (! sgj_init_state(&op->json_st, optarg)) { |
| int bad_char = op->json_st.first_bad_char; |
| char e[1500]; |
| |
| if (bad_char) { |
| pr2serr("bad argument to --json= option, unrecognized " |
| "character '%c'\n\n", bad_char); |
| } |
| sg_json_usage(0, e, sizeof(e)); |
| pr2serr("%s", e); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| break; |
| case 'n': |
| op->no_space = true; |
| break; |
| case 'N': |
| op->no_decode = true; |
| break; |
| case 's': |
| if (1 != sscanf(optarg, "%x", &ui)) { |
| pr2serr("'--status=SS' expects a byte value\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| if (ui > 0xff) { |
| pr2serr("'--status=SS' byte value exceeds FF\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->do_status = true; |
| op->sstatus = ui; |
| break; |
| case 'v': |
| op->verbose_given = true; |
| ++op->verbose; |
| break; |
| case 'V': |
| op->version_given = true; |
| break; |
| case 'w': |
| op->wfname = optarg; |
| break; |
| default: |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| } |
| if (op->err_given) |
| goto the_end; |
| |
| while (optind < argc) { |
| avp = argv[optind++]; |
| if (op->no_space) { |
| if (op->no_space_str) { |
| if ('\0' == concat_buff[0]) { |
| if (strlen(op->no_space_str) > sizeof(concat_buff)) { |
| pr2serr("'--nospace' concat_buff overflow\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| strcpy(concat_buff, op->no_space_str); |
| } |
| if ((strlen(concat_buff) + strlen(avp)) >= |
| sizeof(concat_buff)) { |
| pr2serr("'--nospace' concat_buff overflow\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| if (op->version_given) |
| pr2serr("'--nospace' and found whitespace so " |
| "concatenate\n"); |
| strcat(concat_buff, avp); |
| op->no_space_str = concat_buff; |
| } else |
| op->no_space_str = avp; |
| continue; |
| } |
| val = strtol(avp, &endptr, 16); |
| if (*avp == '\0' || *endptr != '\0' || val < 0x00 || val > 0xff) { |
| pr2serr("Invalid byte '%s'\n", avp); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| |
| if (op->sense_len > MAX_SENSE_LEN) { |
| pr2serr("sense data too long (max. %d bytes)\n", MAX_SENSE_LEN); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->sense[op->sense_len++] = (uint8_t)val; |
| } |
| the_end: |
| return 0; |
| } |
| |
| /* Keep this format (e.g. 0xff,0x12,...) for backward compatibility */ |
| static void |
| write2wfn(FILE * fp, struct opts_t * op) |
| { |
| int k, n; |
| size_t s; |
| char b[128]; |
| |
| for (k = 0, n = 0; k < op->sense_len; ++k) { |
| n += sprintf(b + n, "0x%02x,", op->sense[k]); |
| if (15 == (k % 16)) { |
| b[n] = '\n'; |
| s = fwrite(b, 1, n + 1, fp); |
| if ((int)s != (n + 1)) |
| pr2serr("only able to write %d of %d bytes to %s\n", |
| (int)s, n + 1, op->wfname); |
| n = 0; |
| } |
| } |
| if (n > 0) { |
| b[n] = '\n'; |
| s = fwrite(b, 1, n + 1, fp); |
| if ((int)s != (n + 1)) |
| pr2serr("only able to write %d of %d bytes to %s\n", (int)s, |
| n + 1, op->wfname); |
| } |
| } |
| |
| |
| int |
| main(int argc, char *argv[]) |
| { |
| bool as_json; |
| int k, err, blen; |
| int ret = 0; |
| unsigned int ui; |
| size_t s; |
| struct opts_t * op; |
| FILE * fp = NULL; |
| const char * cp; |
| sgj_state * jsp; |
| sgj_opaque_p jop = NULL; |
| char b[2048]; |
| struct opts_t opts; |
| |
| op = &opts; |
| blen = sizeof(b); |
| memset(op, 0, sizeof(opts)); |
| memset(b, 0, blen); |
| ret = parse_cmd_line(op, argc, argv); |
| |
| #ifdef DEBUG |
| pr2serr("In DEBUG mode, "); |
| if (op->verbose_given && op->version_given) { |
| pr2serr("but override: '-vV' given, zero verbose and continue\n"); |
| op->verbose_given = false; |
| op->version_given = false; |
| op->verbose = 0; |
| } else if (! op->verbose_given) { |
| pr2serr("set '-vv'\n"); |
| op->verbose = 2; |
| } else |
| pr2serr("keep verbose=%d\n", op->verbose); |
| #else |
| if (op->verbose_given && op->version_given) |
| pr2serr("Not in DEBUG mode, so '-vV' has no special action\n"); |
| #endif |
| if (op->version_given) { |
| pr2serr("version: %s\n", version_str); |
| return 0; |
| } |
| if (ret != 0) { |
| usage(); |
| return ret; |
| } else if (op->do_help) { |
| usage(); |
| return 0; |
| } |
| as_json = op->json_st.pr_as_json; |
| jsp = &op->json_st; |
| if (as_json) |
| jop = sgj_start(MY_NAME, version_str, argc, argv, jsp); |
| |
| if (op->err_given) { |
| char d[128]; |
| const int dlen = sizeof(d); |
| |
| if (! sg_exit2str(op->es_val, op->verbose > 1, dlen, d)) |
| snprintf(d, dlen, "Unable to decode exit status %d", op->es_val); |
| if (1 & op->verbose) /* odd values of verbose print to stderr */ |
| pr2serr("%s\n", d); |
| else /* even values of verbose (including not given) to stdout */ |
| printf("%s\n", d); |
| goto fini; |
| } |
| |
| if (op->do_status) { |
| sg_get_scsi_status_str(op->sstatus, blen, b); |
| printf("SCSI status: %s\n", b); |
| } |
| |
| if ((0 == op->sense_len) && op->no_space_str) { |
| if (op->verbose > 2) |
| pr2serr("no_space str: %s\n", op->no_space_str); |
| cp = op->no_space_str; |
| for (k = 0; isxdigit((uint8_t)cp[k]) && |
| isxdigit((uint8_t)cp[k + 1]); k += 2) { |
| if (1 != sscanf(cp + k, "%2x", &ui)) { |
| pr2serr("bad no_space hex string: %s\n", cp); |
| ret = SG_LIB_SYNTAX_ERROR; |
| goto fini; |
| } |
| op->sense[op->sense_len++] = (uint8_t)ui; |
| } |
| } |
| |
| if ((0 == op->sense_len) && (! op->do_binary) && (! op->file_given)) { |
| if (op->do_status) { |
| ret = 0; |
| goto fini; |
| } |
| pr2serr(">> Need sense/cdb/arbitrary data on the command line or " |
| "in a file\n\n"); |
| usage(); |
| ret = SG_LIB_SYNTAX_ERROR; |
| goto fini; |
| } |
| if (op->sense_len && (op->do_binary || op->file_given)) { |
| pr2serr(">> Need sense data on command line or in a file, not " |
| "both\n\n"); |
| ret = SG_LIB_CONTRADICT; |
| goto fini; |
| } |
| if (op->do_binary && op->file_given) { |
| pr2serr(">> Either a binary file or a ASCII hexadecimal, file not " |
| "both\n\n"); |
| ret = SG_LIB_CONTRADICT; |
| goto fini; |
| } |
| |
| if (op->do_binary) { |
| fp = fopen(op->fname, "r"); |
| if (NULL == fp) { |
| err = errno; |
| pr2serr("unable to open file: %s: %s\n", op->fname, |
| safe_strerror(err)); |
| ret = sg_convert_errno(err); |
| goto fini; |
| } |
| s = fread(op->sense, 1, MAX_SENSE_LEN, fp); |
| fclose(fp); |
| if (0 == s) { |
| pr2serr("read nothing from file: %s\n", op->fname); |
| ret = SG_LIB_SYNTAX_ERROR; |
| goto fini; |
| } |
| op->sense_len = s; |
| } else if (op->file_given) { |
| ret = sg_f2hex_arr(op->fname, false, op->no_space, op->sense, |
| &op->sense_len, MAX_SENSE_LEN); |
| if (ret) { |
| pr2serr("unable to decode ASCII hex from file: %s\n", op->fname); |
| goto fini; |
| } |
| } |
| |
| if (op->sense_len > 0) { |
| if (op->wfname || op->hex_count) { |
| if (op->wfname) { |
| if (NULL == ((fp = fopen(op->wfname, "w")))) { |
| err =errno; |
| perror("open"); |
| pr2serr("trying to write to %s\n", op->wfname); |
| ret = sg_convert_errno(err); |
| goto fini; |
| } |
| } else |
| fp = stdout; |
| |
| if (op->wfname && (1 == op->hex_count)) |
| write2wfn(fp, op); |
| else if (op->hex_count && (2 != op->hex_count)) |
| dStrHexFp((const char *)op->sense, op->sense_len, |
| ((1 == op->hex_count) ? 1 : -1), fp); |
| else if (op->hex_count) |
| dStrHexFp((const char *)op->sense, op->sense_len, 0, fp); |
| else { |
| size_t s = fwrite(op->sense, 1, op->sense_len, fp); |
| |
| if ((int)s != op->sense_len) |
| pr2serr("only able to write %d of %d bytes to %s\n", |
| (int)s, op->sense_len, op->wfname); |
| } |
| if (op->wfname) |
| fclose(fp); |
| } else if (op->no_decode) { |
| if (op->verbose > 1) |
| pr2serr("Not decoding as %s because --nodecode given\n", |
| (op->do_cdb ? "cdb" : "sense")); |
| } else if (op->do_cdb) { |
| int sa, opcode; |
| |
| opcode = op->sense[0]; |
| if ((0x75 == opcode) || (0x7e == opcode) || (op->sense_len > 16)) |
| sa = sg_get_unaligned_be16(op->sense + 8); |
| else if (op->sense_len > 1) |
| sa = op->sense[1] & 0x1f; |
| else |
| sa = 0; |
| sg_get_opcode_sa_name(opcode, sa, 0, blen, b); |
| printf("%s\n", b); |
| } else { |
| if (as_json) { |
| sgj_get_sense(jsp, jop, op->sense, op->sense_len); |
| if (jsp->pr_out_hr) { |
| sg_get_sense_str(NULL, op->sense, op->sense_len, |
| op->verbose, blen, b); |
| sgj_pr_str_out_hr(jsp, b, strlen(b)); |
| } |
| } else { |
| sg_get_sense_str(NULL, op->sense, op->sense_len, |
| op->verbose, blen, b); |
| printf("%s\n", b); |
| } |
| } |
| } |
| fini: |
| if (as_json) { |
| |
| #if 0 |
| // <<<< testing |
| { |
| sgj_opaque_p jo2p; |
| |
| // uint8_t dd[] = {0x1, 0x0, 0x0, 0x6, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; |
| |
| // uint8_t dd[] = {0x01, 0x00, 0x00, 0x16, 0x11, 0x22, 0x33, 0x44 , 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, |
| // 0xdd, 0xee, 0xff, 0xed, 0xcb, 0xa9, 0x87, 0x65 , 0x43, 0x21}; |
| |
| // uint8_t dd[] = {0x2, 0x1, 0x0, 0x14, |
| // 0x41, 0x42, 0x43, 0x20, 0x20, 0x20, 0x20, 0x20, |
| // 0x58, 0x59, 0x5a, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39}; |
| |
| // uint8_t dd[] = {0x01, 0x03, 0x00, 0x08, 0x51, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; |
| // uint8_t dd[] = {0x01, 0x03, 0x00, 0x10, 0x61, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0xee, 0xdd}; |
| |
| uint8_t dd[] = {0x01, 0x14, 0x00, 0x04, 0x0, 0x0, 0x0, 0x2, }; |
| |
| jo2p = sgj_new_named_object(jsp, jop, "designation_descriptor"); |
| sgj_get_designation_descriptor(jsp, jo2p, dd, sizeof(dd)); |
| } |
| // <<<< end of testing |
| #endif |
| |
| if (0 == op->hex_count) |
| sgj_pr2file(&op->json_st, NULL, ret, stdout); |
| sgj_finish(jsp); |
| } |
| return ret; |
| } |