blob: d8d8dfd2d782460b86844ddf087e755664baa3e1 [file] [log] [blame]
/*
* Copyright (c) 2010-2015 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.
*/
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.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"
static const char * version_str = "1.08 20151219";
#define MAX_SENSE_LEN 1024 /* max descriptor format actually: 256+8 */
static struct option long_options[] = {
{"binary", required_argument, 0, 'b'},
{"file", required_argument, 0, 'f'},
{"help", no_argument, 0, 'h'},
{"hex", no_argument, 0, 'H'},
{"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 {
int do_binary;
const char * fname;
int do_file;
int do_help;
int do_hex;
int no_space;
int do_status;
int sstatus;
int do_verbose;
int do_version;
const char * wfname;
unsigned char sense[MAX_SENSE_LEN + 4];
const char * no_space_str;
int sense_len;
};
static char concat_buff[1024];
static void
usage()
{
pr2serr("Usage: sg_decode_sense [--binary=FN] [--file=FN] [--help] [--hex] "
"[--nospace]\n"
" [--status=SS] [--verbose] [--version] "
"[--write=WFN]\n"
" [H1 H2 H3 ...]\n"
" where:\n"
" --binary=FN|-b FN FN is a file name to read sense "
"data in\n"
" binary from. If FN is '-' then read "
"from stdin\n"
" --file=FN|-f FN FN 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"
" --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.\n"
);
}
static int
process_cl(struct opts_t *optsp, int argc, char *argv[])
{
int c;
unsigned int ui;
char * opt;
char *endptr;
long val;
while (1) {
c = getopt_long(argc, argv, "b:f:hHns:vVw:", long_options, NULL);
if (c == -1)
break;
switch (c) {
case 'b':
if (optsp->fname) {
pr2serr("expect only one '--binary=FN' or '--file=FN' "
"option\n");
return SG_LIB_SYNTAX_ERROR;
}
++optsp->do_binary;
optsp->fname = optarg;
break;
case 'f':
if (optsp->fname) {
pr2serr("expect only one '--binary=FN' or '--file=FN' "
"option\n");
return SG_LIB_SYNTAX_ERROR;
}
++optsp->do_file;
optsp->fname = optarg;
break;
case 'h':
case '?':
optsp->do_help = 1;
return 0;
case 'H':
++optsp->do_hex;
break;
case 'n':
++optsp->no_space;
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;
}
++optsp->do_status;
optsp->sstatus = ui;
break;
case 'v':
++optsp->do_verbose;
break;
case 'V':
optsp->do_version = 1;
return 0;
case 'w':
optsp->wfname = optarg;
break;
default:
return SG_LIB_SYNTAX_ERROR;
}
}
while (optind < argc) {
opt = argv[optind++];
if (optsp->no_space) {
if (optsp->no_space_str) {
if ('\0' == concat_buff[0]) {
if (strlen(optsp->no_space_str) > sizeof(concat_buff)) {
pr2serr("'--nospace' concat_buff overflow\n");
return SG_LIB_SYNTAX_ERROR;
}
strcpy(concat_buff, optsp->no_space_str);
}
if ((strlen(concat_buff) + strlen(opt)) >=
sizeof(concat_buff)) {
pr2serr("'--nospace' concat_buff overflow\n");
return SG_LIB_SYNTAX_ERROR;
}
if (optsp->do_version)
pr2serr("'--nospace' and found whitespace so "
"concatenate\n");
strcat(concat_buff, opt);
optsp->no_space_str = concat_buff;
} else
optsp->no_space_str = opt;
continue;
}
val = strtol(opt, &endptr, 16);
if (*opt == '\0' || *endptr != '\0' || val < 0x00 || val > 0xff) {
pr2serr("Invalid byte '%s'\n", opt);
return SG_LIB_SYNTAX_ERROR;
}
if (optsp->sense_len > MAX_SENSE_LEN) {
pr2serr("sense data too long (max. %d bytes)\n", MAX_SENSE_LEN);
return SG_LIB_SYNTAX_ERROR;
}
optsp->sense[optsp->sense_len++] = (unsigned char)val;
}
return 0;
}
/* Read ASCII hex bytes from fname (a file named '-' taken as stdin).
* There should be either one entry per line or a comma, space or tab
* separated list of bytes. If no_space is set then a string of ACSII hex
* digits is expected, 2 per byte. Everything from and including a '#'
* on a line is ignored. Returns 0 if ok, or 1 if error. */
static int
f2hex_arr(const char * fname, int no_space, unsigned char * mp_arr,
int * mp_arr_len, int max_arr_len)
{
int fn_len, in_len, k, j, m, split_line;
unsigned int h;
const char * lcp;
FILE * fp;
char line[512];
char carry_over[4];
int off = 0;
if ((NULL == fname) || (NULL == mp_arr) || (NULL == mp_arr_len))
return 1;
fn_len = strlen(fname);
if (0 == fn_len)
return 1;
if ((1 == fn_len) && ('-' == fname[0])) /* read from stdin */
fp = stdin;
else {
fp = fopen(fname, "r");
if (NULL == fp) {
pr2serr("Unable to open %s for reading\n", fname);
return 1;
}
}
carry_over[0] = 0;
for (j = 0; j < 512; ++j) {
if (NULL == fgets(line, sizeof(line), fp))
break;
in_len = strlen(line);
if (in_len > 0) {
if ('\n' == line[in_len - 1]) {
--in_len;
line[in_len] = '\0';
split_line = 0;
} else
split_line = 1;
}
if (in_len < 1) {
carry_over[0] = 0;
continue;
}
if (carry_over[0]) {
if (isxdigit(line[0])) {
carry_over[1] = line[0];
carry_over[2] = '\0';
if (1 == sscanf(carry_over, "%x", &h))
mp_arr[off - 1] = h; /* back up and overwrite */
else {
pr2serr("f2hex_arr: carry_over error ['%s'] around line "
"%d\n", carry_over, j + 1);
goto bad;
}
lcp = line + 1;
--in_len;
} else
lcp = line;
carry_over[0] = 0;
} else
lcp = line;
m = strspn(lcp, " \t");
if (m == in_len)
continue;
lcp += m;
in_len -= m;
if ('#' == *lcp)
continue;
k = strspn(lcp, "0123456789aAbBcCdDeEfF ,\t");
if ((k < in_len) && ('#' != lcp[k]) && ('\r' != lcp[k])) {
pr2serr("f2hex_arr: syntax error at line %d, pos %d\n", j + 1,
m + k + 1);
goto bad;
}
if (no_space) {
for (k = 0; isxdigit(*lcp) && isxdigit(*(lcp + 1));
++k, lcp += 2) {
if (1 != sscanf(lcp, "%2x", &h)) {
pr2serr("f2hex_arr: bad hex number in line %d, pos %d\n",
j + 1, (int)(lcp - line + 1));
goto bad;
}
if ((off + k) >= max_arr_len) {
pr2serr("f2hex_arr: array length exceeded\n");
goto bad;
}
mp_arr[off + k] = h;
}
if (isxdigit(*lcp) && (! isxdigit(*(lcp + 1))))
carry_over[0] = *lcp;
off += k;
} else {
for (k = 0; k < 1024; ++k) {
if (1 == sscanf(lcp, "%x", &h)) {
if (h > 0xff) {
pr2serr("f2hex_arr: hex number larger than 0xff in "
"line %d, pos %d\n", j + 1,
(int)(lcp - line + 1));
goto bad;
}
if (split_line && (1 == strlen(lcp))) {
/* single trailing hex digit might be a split pair */
carry_over[0] = *lcp;
}
if ((off + k) >= max_arr_len) {
pr2serr("f2hex_arr: array length exceeded\n");
goto bad;
}
mp_arr[off + k] = h;
lcp = strpbrk(lcp, " ,\t");
if (NULL == lcp)
break;
lcp += strspn(lcp, " ,\t");
if ('\0' == *lcp)
break;
} else {
if (('#' == *lcp) || ('\r' == *lcp)) {
--k;
break;
}
pr2serr("f2hex_arr: error in line %d, at pos %d\n", j + 1,
(int)(lcp - line + 1));
goto bad;
}
}
off += (k + 1);
}
}
*mp_arr_len = off;
if (stdin != fp)
fclose(fp);
return 0;
bad:
if (stdin != fp)
fclose(fp);
return 1;
}
static void
write2wfn(FILE * fp, struct opts_t * optsp)
{
int k, n;
size_t s;
char b[128];
if (optsp->do_hex) {
for (k = 0, n = 0; k < optsp->sense_len; ++k) {
n += sprintf(b + n, "0x%02x,", optsp->sense[k]);
if (15 == (k % 16)) {
b[n] = '\n';
s = fwrite(b, 1, n + 1, fp);
n = 0;
}
}
if (n > 0) {
b[n] = '\n';
s = fwrite(b, 1, n + 1, fp);
}
} else {
s = fwrite(optsp->sense, 1, optsp->sense_len, fp);
if ((int)s != optsp->sense_len)
pr2serr("only able to write %d of %d bytes to %s\n", (int)s,
optsp->sense_len, optsp->wfname);
}
}
int
main(int argc, char *argv[])
{
int k;
int ret = 0;
unsigned int ui;
size_t s;
struct opts_t opts;
char b[2048];
FILE * fp = NULL;
const char * cp;
memset(&opts, 0, sizeof(opts));
memset(b, 0, sizeof(b));
ret = process_cl(&opts, argc, argv);
if (ret != 0) {
usage();
return ret;
} else if (opts.do_help) {
usage();
return 0;
} else if (opts.do_version) {
pr2serr("version: %s\n", version_str);
return 0;
}
if (opts.do_status) {
sg_get_scsi_status_str(opts.sstatus, sizeof(b) - 1, b);
printf("SCSI status: %s\n", b);
}
if ((0 == opts.sense_len) && opts.no_space_str) {
if (opts.do_verbose > 2)
pr2serr("no_space str: %s\n", opts.no_space_str);
cp = opts.no_space_str;
for (k = 0; isxdigit(cp[k]) && isxdigit(cp[k + 1]); k += 2) {
if (1 != sscanf(cp + k, "%2x", &ui)) {
pr2serr("bad no_space hex string: %s\n", cp);
return SG_LIB_SYNTAX_ERROR;
}
opts.sense[opts.sense_len++] = (unsigned char)ui;
}
}
if ((0 == opts.sense_len) && (! opts.do_binary) && (! opts.do_file)) {
if (opts.do_status)
return 0;
pr2serr(">> Need sense data on the command line or in a file\n\n");
usage();
return SG_LIB_SYNTAX_ERROR;
}
if (opts.sense_len && (opts.do_binary || opts.do_file)) {
pr2serr(">> Need sense data on command line or in a file, not "
"both\n\n");
return SG_LIB_SYNTAX_ERROR;
}
if (opts.do_binary && opts.do_file) {
pr2serr(">> Either a binary file or a ASCII hexadecimal, file not "
"both\n\n");
return SG_LIB_SYNTAX_ERROR;
}
if (opts.do_binary) {
fp = fopen(opts.fname, "r");
if (NULL == fp) {
pr2serr("unable to open file: %s\n", opts.fname);
return SG_LIB_SYNTAX_ERROR;
}
s = fread(opts.sense, 1, MAX_SENSE_LEN, fp);
fclose(fp);
if (0 == s) {
pr2serr("read nothing from file: %s\n", opts.fname);
return SG_LIB_SYNTAX_ERROR;
}
opts.sense_len = s;
} else if (opts.do_file) {
ret = f2hex_arr(opts.fname, opts.no_space, opts.sense,
&opts.sense_len, MAX_SENSE_LEN);
if (ret) {
pr2serr("unable to decode ASCII hex from file: %s\n", opts.fname);
return SG_LIB_SYNTAX_ERROR;
}
}
if (opts.sense_len) {
if (opts.wfname) {
if ((fp = fopen(opts.wfname, "w"))) {
write2wfn(fp, &opts);
fclose(fp);
} else {
perror("open");
pr2serr("trying to write to %s\n", opts.wfname);
}
}
sg_get_sense_str(NULL, opts.sense, opts.sense_len, opts.do_verbose,
sizeof(b) - 1, b);
printf("%s\n", b);
}
return 0;
}