blob: 310ea78b0c2ab5550edb6a4fd78c9b4f89621704 [file] [log] [blame]
/*
* Copyright (c) 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 <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdbool.h>
#include <string.h>
#include <ctype.h>
#include "sg_pr2serr.h"
#include "sg_json_builder.h"
/* Comment out next line to remove dependency on sg_lib.h */
#define SG_PRSE_SENSE_DECODE 1
#ifdef SG_PRSE_SENSE_DECODE
#include "sg_lib.h"
#include "sg_lib_data.h"
#include "sg_unaligned.h"
#endif
#define sgj_opts_ev "SG3_UTILS_JSON_OPTS"
/*
* #define json_serialize_mode_multiline 0
* #define json_serialize_mode_single_line 1
* #define json_serialize_mode_packed 2
*
* #define json_serialize_opt_CRLF (1 << 1)
* #define json_serialize_opt_pack_brackets (1 << 2)
* #define json_serialize_opt_no_space_after_comma (1 << 3)
* #define json_serialize_opt_no_space_after_colon (1 << 4)
* #define json_serialize_opt_use_tabs (1 << 5)
*/
static const json_serialize_opts def_out_settings = {
json_serialize_mode_multiline, /* one of serialize_mode_* */
0, /* serialize_opt_* OR-ed together */
4 /* indent size */
};
static int sgj_name_to_snake(const char * in, char * out, int maxlen_out);
/* Users of the sg_pr2serr.h header need this function definition */
int
pr2serr(const char * fmt, ...)
{
va_list args;
int n;
va_start(args, fmt);
n = vfprintf(stderr, fmt, args);
va_end(args);
return n;
}
static bool
sgj_parse_opts(sgj_state * jsp, const char * j_optarg)
{
bool bad_arg = false;
bool prev_negate = false;
bool negate;
int k, c;
for (k = 0; j_optarg[k]; ++k) { /* step over leading whitespace */
if (! isspace(j_optarg[k]))
break;
}
for ( ; j_optarg[k]; ++k) {
c = j_optarg[k];
negate = false;
switch (c) {
case '=':
if (0 == k)
break; /* allow and ignore leading '=' */
bad_arg = true;
if (0 == jsp->first_bad_char)
jsp->first_bad_char = c;
break;
case '!':
case '~':
case '-': /* '-' is probably most practical negation symbol */
negate = true;
break;
case '0':
case '2':
jsp->pr_indent_size = 2;
break;
case '3':
jsp->pr_indent_size = 3;
break;
case '4':
jsp->pr_indent_size = 4;
break;
case '8':
jsp->pr_indent_size = 8;
break;
case 'a': /* abbreviated name expansion */
jsp->pr_ane = ! prev_negate;
break;
case 'e':
jsp->pr_exit_status = ! prev_negate;
break;
case 'g':
jsp->pr_format = 'g';
break;
case 'h':
jsp->pr_hex = ! prev_negate;
break;
case 'k':
jsp->pr_packed = ! prev_negate;
break;
case 'l':
jsp->pr_leadin = ! prev_negate;
break;
case 'o':
jsp->pr_out_hr = ! prev_negate;
break;
case 'p':
jsp->pr_pretty = ! prev_negate;
break;
case 's':
jsp->pr_string = ! prev_negate;
break;
case 'v':
++jsp->verbose;
break;
case 'y':
jsp->pr_format = 'g';
break;
case '?':
bad_arg = true;
jsp->first_bad_char = '\0';
break;
default:
bad_arg = true;
if (0 == jsp->first_bad_char)
jsp->first_bad_char = c;
break;
}
prev_negate = negate ? ! prev_negate : false;
}
return ! bad_arg;
}
char *
sg_json_usage(int char_if_not_j, char * b, int blen)
{
int n = 0;
char short_opt = char_if_not_j ? char_if_not_j : 'j';
if ((NULL == b) || (blen < 1))
goto fini;
n += sg_scnpr(b + n, blen - n, "JSON option usage:\n");
n += sg_scnpr(b + n, blen - n,
" --json[-JO] | -%c[JO]\n\n", short_opt);
n += sg_scnpr(b + n, blen - n, " where JO is a string of one or more "
"of:\n");
n += sg_scnpr(b + n, blen - n,
" 0 | 2 tab pretty output to 2 spaces\n");
n += sg_scnpr(b + n, blen - n,
" 4 tab pretty output to 4 spaces\n");
n += sg_scnpr(b + n, blen - n,
" 8 tab pretty output to 8 spaces\n");
if (n >= (blen - 1))
goto fini;
n += sg_scnpr(b + n, blen - n,
" a show 'abbreviated_name_expansion' "
"fields\n");
n += sg_scnpr(b + n, blen - n,
" e show 'exit_status' field\n");
n += sg_scnpr(b + n, blen - n,
" h show 'hex' fields\n");
n += sg_scnpr(b + n, blen - n,
" k packed, only non-pretty printed output\n");
n += sg_scnpr(b + n, blen - n,
" l show lead-in fields (invocation "
"information)\n");
n += sg_scnpr(b + n, blen - n,
" o non-JSON output placed in 'output' array in "
"lead-in\n");
if (n >= (blen - 1))
goto fini;
n += sg_scnpr(b + n, blen - n,
" p pretty print the JSON output\n");
n += sg_scnpr(b + n, blen - n,
" s show string output (usually fields named "
"'meaning')\n");
n += sg_scnpr(b + n, blen - n,
" v make JSON output more verbose\n");
n += sg_scnpr(b + n, blen - n,
" = ignored if first character, else it's an "
"error\n");
n += sg_scnpr(b + n, blen - n,
" - | ~ | ! toggle next letter setting\n");
n += sg_scnpr(b + n, blen - n, "\nIn the absence of the optional JO "
"argument, the following are set\non: 'elps' while the "
"others are set off, and tabs are set to 4.\nBefore "
"command line JO options are applied, the environment\n"
"variable: %s is applied (if present). Note that\nno "
"space is permitted between the short option ('-%c') "
"and its\nargument ('JO').\n", sgj_opts_ev, short_opt);
fini:
return b;
}
char *
sg_json_settings(sgj_state * jsp, char * b, int blen)
{
snprintf(b, blen, "%d%sa%se%sh%sk%sl%so%sp%ss%sv", jsp->pr_indent_size,
jsp->pr_ane ? "" : "-", jsp->pr_exit_status ? "" : "-",
jsp->pr_hex ? "" : "-", jsp->pr_packed ? "" : "-",
jsp->pr_leadin ? "" : "-", jsp->pr_out_hr ? "" : "-",
jsp->pr_pretty ? "" : "-", jsp->pr_string ? "" : "-",
jsp->verbose ? "" : "-");
return b;
}
static void
sgj_def_opts(sgj_state * jsp)
{
jsp->pr_as_json = true;
jsp->pr_ane = false;
jsp->pr_exit_status = true;
jsp->pr_hex = false;
jsp->pr_leadin = true;
jsp->pr_out_hr = false;
jsp->pr_packed = false; /* 'k' control character, needs '-p' */
jsp->pr_pretty = true;
jsp->pr_string = true;
jsp->pr_format = 0;
jsp->first_bad_char = 0;
jsp->verbose = 0;
jsp->pr_indent_size = 4;
}
bool
sgj_init_state(sgj_state * jsp, const char * j_optarg)
{
const char * cp;
sgj_def_opts(jsp);
jsp->basep = NULL;
jsp->out_hrp = NULL;
jsp->userp = NULL;
cp = getenv(sgj_opts_ev);
if (cp) {
if (! sgj_parse_opts(jsp, cp)) {
pr2ws("error parsing %s environment variable, ignore\n",
sgj_opts_ev);
sgj_def_opts(jsp);
}
}
return j_optarg ? sgj_parse_opts(jsp, j_optarg) : true;
}
sgj_opaque_p
sgj_start(const char * util_name, const char * ver_str, int argc,
char *argv[], sgj_state * jsp)
{
int k;
json_value * jvp = json_object_new(0);
json_value * jv2p = NULL;
json_value * jap = NULL;
if (NULL == jvp)
return NULL;
if (NULL == jsp)
return jvp;
jsp->basep = jvp;
if (jsp->pr_leadin) {
jap = json_array_new(0);
if (NULL == jap) {
json_builder_free((json_value *)jvp);
return NULL;
}
/* assume rest of json_*_new() calls succeed */
json_array_push((json_value *)jap, json_integer_new(1));
json_array_push((json_value *)jap, json_integer_new(0));
json_object_push((json_value *)jvp, "json_format_version",
(json_value *)jap);
if (util_name) {
jap = json_array_new(0);
if (argv) {
for (k = 0; k < argc; ++k)
json_array_push((json_value *)jap,
json_string_new(argv[k]));
}
jv2p = json_object_push((json_value *)jvp, "utility_invoked",
json_object_new(0));
json_object_push((json_value *)jv2p, "name",
json_string_new(util_name));
if (ver_str)
json_object_push((json_value *)jv2p, "version_date",
json_string_new(ver_str));
else
json_object_push((json_value *)jv2p, "version_date",
json_string_new("0.0"));
json_object_push((json_value *)jv2p, "argv", jap);
}
if (jsp->verbose) {
const char * cp = getenv(sgj_opts_ev);
char b[32];
json_object_push((json_value *)jv2p, "environment_variable_name",
json_string_new(sgj_opts_ev));
json_object_push((json_value *)jv2p, "environment_variable_value",
json_string_new(cp ? cp : "no available"));
sg_json_settings(jsp, b, sizeof(b));
json_object_push((json_value *)jv2p, "json_options",
json_string_new(b));
}
} else {
if (jsp->pr_out_hr && util_name)
jv2p = json_object_push((json_value *)jvp, "utility_invoked",
json_object_new(0));
}
if (jsp->pr_out_hr && jv2p) {
jsp->out_hrp = json_object_push((json_value *)jv2p, "output",
json_array_new(0));
if (jsp->pr_leadin && (jsp->verbose > 3)) {
char * bp = (char *)calloc(4096, 1);
if (bp) {
sg_json_usage(0, bp, 4096);
sgj_pr_str_out_hr(jsp, bp, strlen(bp));
free(bp);
}
}
}
return jvp;
}
void
sgj_pr2file(sgj_state * jsp, sgj_opaque_p jop, int exit_status, FILE * fp)
{
size_t len;
char * b;
json_value * jvp = (json_value *)(jop ? jop : jsp->basep);
json_serialize_opts out_settings;
if (NULL == jvp) {
fprintf(fp, "%s: all NULL pointers ??\n", __func__);
return;
}
if ((NULL == jop) && jsp->pr_exit_status) {
char d[80];
if (sg_exit2str(exit_status, jsp->verbose, sizeof(d), d)) {
if (0 == strlen(d))
strncpy(d, "no errors", sizeof(d) - 1);
} else
strncpy(d, "not available", sizeof(d) - 1);
sgj_add_nv_istr(jsp, jop, "exit_status", exit_status, NULL, d);
}
memcpy(&out_settings, &def_out_settings, sizeof(out_settings));
if (jsp->pr_indent_size != def_out_settings.indent_size)
out_settings.indent_size = jsp->pr_indent_size;
if (! jsp->pr_pretty)
out_settings.mode = jsp->pr_packed ? json_serialize_mode_packed :
json_serialize_mode_single_line;
len = json_measure_ex(jvp, out_settings);
if (len < 1)
return;
if (jsp->verbose > 3)
fprintf(fp, "%s: serialization length: %zu bytes\n", __func__, len);
b = (char *)calloc(len, 1);
if (NULL == b) {
if (jsp->verbose > 3)
pr2serr("%s: unable to get %zu bytes on heap\n", __func__, len);
return;
}
json_serialize_ex(b, jvp, out_settings);
if (jsp->verbose > 3)
fprintf(fp, "json serialized:\n");
fprintf(fp, "%s\n", b);
}
void
sgj_finish(sgj_state * jsp)
{
if (jsp && jsp->basep) {
json_builder_free((json_value *)jsp->basep);
jsp->basep = NULL;
jsp->out_hrp = NULL;
jsp->userp = NULL;
}
}
void
sgj_free_unattached(sgj_opaque_p jop)
{
if (jop)
json_builder_free((json_value *)jop);
}
void
sgj_pr_hr(sgj_state * jsp, const char * fmt, ...)
{
va_list args;
if (jsp->pr_as_json && jsp->pr_out_hr) {
size_t len;
char b[256];
va_start(args, fmt);
len = vsnprintf(b, sizeof(b), fmt, args);
if ((len > 0) && (len < sizeof(b))) {
const char * cp = b;
/* remove up to two trailing linefeeds */
if (b[len - 1] == '\n') {
--len;
if (b[len - 1] == '\n')
--len;
b[len] = '\0';
}
/* remove leading linefeed, if present */
if ((len > 0) && ('\n' == b[0]))
++cp;
json_array_push((json_value *)jsp->out_hrp, json_string_new(cp));
}
va_end(args);
} else if (jsp->pr_as_json) {
va_start(args, fmt);
va_end(args);
} else {
va_start(args, fmt);
vfprintf(stdout, fmt, args);
va_end(args);
}
}
/* jop will 'own' returned value (if non-NULL) */
sgj_opaque_p
sgj_new_named_object(sgj_state * jsp, sgj_opaque_p jop, const char * name)
{
sgj_opaque_p resp = NULL;
if (jsp && jsp->pr_as_json && name)
resp = json_object_push((json_value *)(jop ? jop : jsp->basep), name,
json_object_new(0));
return resp;
}
sgj_opaque_p
sgj_new_snake_named_object(sgj_state * jsp, sgj_opaque_p jop,
const char * conv2sname)
{
if (jsp && jsp->pr_as_json && conv2sname) {
int olen = strlen(conv2sname);
char * sname = malloc(olen + 8);
int nlen = sgj_name_to_snake(conv2sname, sname, olen + 8);
if (nlen > 0)
return json_object_push((json_value *)(jop ? jop : jsp->basep),
sname, json_object_new(0));
}
return NULL;
}
/* jop will 'own' returned value (if non-NULL) */
sgj_opaque_p
sgj_new_named_array(sgj_state * jsp, sgj_opaque_p jop, const char * name)
{
sgj_opaque_p resp = NULL;
if (jsp && jsp->pr_as_json && name)
resp = json_object_push((json_value *)(jop ? jop : jsp->basep), name,
json_array_new(0));
return resp;
}
sgj_opaque_p
sgj_new_snake_named_array(sgj_state * jsp, sgj_opaque_p jop,
const char * conv2sname)
{
if (jsp && jsp->pr_as_json && conv2sname) {
int olen = strlen(conv2sname);
char * sname = malloc(olen + 8);
int nlen = sgj_name_to_snake(conv2sname, sname, olen + 8);
if (nlen > 0)
return json_object_push((json_value *)(jop ? jop : jsp->basep),
sname, json_array_new(0));
}
return NULL;
}
/* Newly created object is un-attached to jsp->basep tree */
sgj_opaque_p
sgj_new_unattached_object(sgj_state * jsp)
{
return (jsp && jsp->pr_as_json) ? json_object_new(0) : NULL;
}
/* Newly created array is un-attached to jsp->basep tree */
sgj_opaque_p
sgj_new_unattached_array(sgj_state * jsp)
{
return (jsp && jsp->pr_as_json) ? json_array_new(0) : NULL;
}
sgj_opaque_p
sgj_add_nv_s(sgj_state * jsp, sgj_opaque_p jop, const char * name,
const char * value)
{
if (jsp && jsp->pr_as_json && value) {
if (name)
return json_object_push((json_value *)(jop ? jop : jsp->basep),
name, json_string_new(value));
else
return json_array_push((json_value *)(jop ? jop : jsp->basep),
json_string_new(value));
} else
return NULL;
}
sgj_opaque_p
sgj_add_nv_s_len(sgj_state * jsp, sgj_opaque_p jop, const char * name,
const char * value, int slen)
{
int k;
if (jsp && jsp->pr_as_json && value && (slen >= 0)) {
for (k = 0; k < slen; ++k) { /* don't want '\0' in value string */
if (0 == value[k])
break;
}
if (name)
return json_object_push((json_value *)(jop ? jop : jsp->basep),
name, json_string_new_length(k, value));
else
return json_array_push((json_value *)(jop ? jop : jsp->basep),
json_string_new_length(k, value));
} else
return NULL;
}
sgj_opaque_p
sgj_add_nv_i(sgj_state * jsp, sgj_opaque_p jop, const char * name,
int64_t value)
{
if (jsp && jsp->pr_as_json) {
if (name)
return json_object_push((json_value *)(jop ? jop : jsp->basep),
name, json_integer_new(value));
else
return json_array_push((json_value *)(jop ? jop : jsp->basep),
json_integer_new(value));
}
else
return NULL;
}
sgj_opaque_p
sgj_add_nv_b(sgj_state * jsp, sgj_opaque_p jop, const char * name, bool value)
{
if (jsp && jsp->pr_as_json) {
if (name)
return json_object_push((json_value *)(jop ? jop : jsp->basep),
name, json_boolean_new(value));
else
return json_array_push((json_value *)(jop ? jop : jsp->basep),
json_boolean_new(value));
} else
return NULL;
}
/* jop will 'own' ua_jop (if returned value is non-NULL) */
sgj_opaque_p
sgj_add_nv_o(sgj_state * jsp, sgj_opaque_p jop, const char * name,
sgj_opaque_p ua_jop)
{
if (jsp && jsp->pr_as_json && ua_jop) {
if (name)
return json_object_push((json_value *)(jop ? jop : jsp->basep),
name, (json_value *)ua_jop);
else
return json_array_push((json_value *)(jop ? jop : jsp->basep),
(json_value *)ua_jop);
} else
return NULL;
}
void
sgj_add_nv_ihex(sgj_state * jsp, sgj_opaque_p jop, const char * name,
uint64_t value)
{
if ((NULL == jsp) || (NULL == name) || (! jsp->pr_as_json))
return;
else if (jsp->pr_hex) {
sgj_opaque_p jo2p = sgj_new_named_object(jsp, jop, name);
char b[64];
if (NULL == jo2p)
return;
sgj_add_nv_i(jsp, jo2p, "i", (int64_t)value);
snprintf(b, sizeof(b), "%" PRIx64, value);
sgj_add_nv_s(jsp, jo2p, "hex", b);
} else
sgj_add_nv_i(jsp, jop, name, (int64_t)value);
}
static const char * sc_mn_s = "meaning";
void
sgj_add_nv_istr(sgj_state * jsp, sgj_opaque_p jop, const char * name,
int64_t val_i, const char * str_name, const char * val_s)
{
if ((NULL == jsp) || (! jsp->pr_as_json))
return;
else if (jsp->pr_string) {
sgj_opaque_p jo2p = sgj_new_named_object(jsp, jop, name);
if (NULL == jo2p)
return;
sgj_add_nv_i(jsp, jo2p, "i", (int64_t)val_i);
if (val_s)
sgj_add_nv_s(jsp, jo2p, str_name ? str_name : sc_mn_s, val_s);
} else
sgj_add_nv_i(jsp, jop, name, val_i);
}
void
sgj_add_nv_ihexstr(sgj_state * jsp, sgj_opaque_p jop, const char * name,
int64_t val_i, const char * str_name, const char * val_s)
{
if ((NULL == jsp) || (! jsp->pr_as_json))
return;
if ((! jsp->pr_hex) && (! jsp->pr_string))
sgj_add_nv_i(jsp, jop, name, val_i);
else {
char b[64];
sgj_opaque_p jo2p = sgj_new_named_object(jsp, jop, name);
if (NULL == jo2p)
return;
if (jsp->pr_string) {
sgj_add_nv_i(jsp, jo2p, "i", (int64_t)val_i);
if (jsp->pr_hex) {
snprintf(b, sizeof(b), "%" PRIx64, val_i);
sgj_add_nv_s(jsp, jo2p, "hex", b);
}
if (val_s)
sgj_add_nv_s(jsp, jo2p, str_name ? str_name : sc_mn_s, val_s);
} else if (jsp->pr_hex) {
sgj_add_nv_i(jsp, jo2p, "i", (int64_t)val_i);
snprintf(b, sizeof(b), "%" PRIx64, val_i);
sgj_add_nv_s(jsp, jo2p, "hex", b);
}
}
}
static const char * sc_ane_s = "abbreviated_name_expansion";
void
sgj_add_nv_ihex_ane(sgj_state * jsp, sgj_opaque_p jop, const char * name,
int64_t val_i, bool want_hex, const char * ane_s)
{
bool as_hex = jsp->pr_hex && want_hex;
bool as_ane = jsp->pr_ane && ane_s;
if ((NULL == jsp) || (! jsp->pr_as_json))
return;
if (! (as_hex || as_ane))
sgj_add_nv_i(jsp, jop, name, val_i);
else {
char b[64];
sgj_opaque_p jo2p =
sgj_new_named_object(jsp, jop, name);
if (NULL == jo2p)
return;
sgj_add_nv_i(jsp, jo2p, "i", (int64_t)val_i);
if (as_ane) {
if (jsp->pr_hex && want_hex) {
snprintf(b, sizeof(b), "%" PRIx64, val_i);
sgj_add_nv_s(jsp, jo2p, "hex", b);
}
sgj_add_nv_s(jsp, jo2p, sc_ane_s, ane_s);
} else if (as_hex) {
snprintf(b, sizeof(b), "%" PRIx64, val_i);
sgj_add_nv_s(jsp, jo2p, "hex", b);
}
}
}
/* Add hex byte strings irrespective of jsp->pr_hex setting. */
void
sgj_add_nv_hex_bytes(sgj_state * jsp, sgj_opaque_p jop, const char * name,
const uint8_t * byte_arr, int num_bytes)
{
int blen = num_bytes * 4;
char * bp;
if ((NULL == jsp) || (! jsp->pr_as_json))
return;
bp = (char *)calloc(blen + 4, 1);
if (bp) {
hex2str(byte_arr, num_bytes, NULL, 2, blen, bp);
sgj_add_nv_s(jsp, jop, name, bp);
free(bp);
}
}
void
sgj_add_nv_ihexstr_ane(sgj_state * jsp, sgj_opaque_p jop, const char * name,
int64_t val_i, bool want_hex, const char * str_name,
const char * val_s, const char * ane_s)
{
bool as_hex = jsp->pr_hex && want_hex;
bool as_str = jsp->pr_string && val_s;
bool as_ane = jsp->pr_ane && ane_s;
const char * sname = str_name ? str_name : sc_mn_s;
if ((NULL == jsp) || (! jsp->pr_as_json))
return;
if (! (as_hex || as_ane || as_str))
sgj_add_nv_i(jsp, jop, name, val_i);
else {
char b[64];
sgj_opaque_p jo2p =
sgj_new_named_object(jsp, jop, name);
if (NULL == jo2p)
return;
sgj_add_nv_i(jsp, jo2p, "i", (int64_t)val_i);
if (as_ane) {
if (as_hex) {
snprintf(b, sizeof(b), "%" PRIx64, val_i);
sgj_add_nv_s(jsp, jo2p, "hex", b);
}
if (as_str) {
sgj_add_nv_s(jsp, jo2p, sname, val_s);
}
sgj_add_nv_s(jsp, jo2p, sc_ane_s, ane_s);
} else if (as_hex) {
snprintf(b, sizeof(b), "%" PRIx64, val_i);
sgj_add_nv_s(jsp, jo2p, "hex", b);
if (as_str)
sgj_add_nv_s(jsp, jo2p, sname, val_s);
} else if (as_str)
sgj_add_nv_s(jsp, jo2p, sname, val_s);
}
}
/* Treat '\n' in sp as line breaks. Consumes characters from sp until either
* a '\0' is found or slen is exhausted. Add each line to jsp->out_hrp JSON
* array (if conditions met). */
void
sgj_pr_str_out_hr(sgj_state * jsp, const char * sp, int slen)
{
char c;
int k, n;
const char * prev_sp = sp;
const char * cur_sp = sp;
if ((NULL == jsp) || (NULL == jsp->out_hrp) || (! jsp->pr_as_json) ||
(! jsp->pr_out_hr))
return;
for (k = 0; k < slen; ++k, ++cur_sp) {
c = *cur_sp;
if ('\0' == c)
break;
else if ('\n' == c) {
n = cur_sp - prev_sp;
/* when name is NULL, add to array (jsp->out_hrp) */
sgj_add_nv_s_len(jsp, jsp->out_hrp, NULL, prev_sp, n);
prev_sp = cur_sp + 1;
}
}
if (prev_sp < cur_sp) {
n = cur_sp - prev_sp;
sgj_add_nv_s_len(jsp, jsp->out_hrp, NULL, prev_sp, n);
}
}
/* This function tries to convert the 'in' C string to "snake_case"
* convention so the output 'out' only contains lower case ASCII letters,
* numerals and "_" as a separator. Any leading or trailing underscores
* are removed as are repeated underscores (e.g. "_Snake __ case" becomes
* "snake_case"). Returns number of characters placed in 'out' excluding
* the trailing NULL */
static int
sgj_name_to_snake(const char * in, char * out, int maxlen_out)
{
bool prev_underscore = false;
int c, k, j, inlen;
if (maxlen_out < 2) {
if (maxlen_out == 1)
out[0] = '\0';
return 0;
}
inlen = strlen(in);
for (k = 0, j = 0; (k < inlen) && (j < maxlen_out); ++k) {
c = in[k];
if (isalnum(c)) {
out[j++] = isupper(c) ? tolower(c) : c;
prev_underscore = false;
} else if ((j > 0) && (! prev_underscore)) {
out[j++] = '_';
prev_underscore = true;
}
/* else we are skipping character 'c' */
}
if (j == maxlen_out)
out[--j] = '\0';
/* trim of trailing underscores (might have been spaces) */
for (k = j - 1; k >= 0; --k) {
if (out[k] != '_')
break;
}
if (k < 0)
k = 0;
else
++k;
out[k] = '\0';
return k;
}
static void
sgj_pr_hr_js_xx(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
const char * name, enum sgj_separator_t sep, json_value * jvp)
{
bool eaten = false;
bool as_json = (jsp && jsp->pr_as_json);
int n;
json_type jtype = jvp ? jvp->type : json_none;
char b[256];
char jname[96];
static const int blen = sizeof(b);
if (leadin_sp > 128)
leadin_sp = 128;
for (n = 0; n < leadin_sp; ++n)
b[n] = ' ';
b[n] = '\0';
if (NULL == name) {
if ((! as_json) || (jsp && jsp->pr_out_hr)) {
switch (jtype) {
case json_string:
sg_scnpr(b + n, blen - n, "%s", jvp->u.string.ptr);
break;
case json_integer:
sg_scnpr(b + n, blen - n, "%" PRIi64, jvp->u.integer);
break;
case json_boolean:
sg_scnpr(b + n, blen - n, "%s",
jvp->u.boolean ? "true" : "false");
break;
case json_none:
default:
break;
}
printf("%s\n", b);
}
if (NULL == jop) {
if (as_json && jsp->pr_out_hr) {
eaten = true;
json_array_push((json_value *)jsp->out_hrp,
jvp ? jvp : json_null_new());
}
} else { /* assume jop points to named array */
if (as_json) {
eaten = true;
json_array_push((json_value *)jop,
jvp ? jvp : json_null_new());
}
}
if (jvp && (! eaten))
json_builder_free((json_value *)jvp);
return;
}
n += sg_scnpr(b + n, blen - n, "%s", name);
if (as_json) {
int k;
if (NULL == jop)
jop = jsp->basep;
k = sgj_name_to_snake(name, jname, sizeof(jname));
if (k > 0) {
eaten = true;
json_object_push((json_value *)jop, jname,
jvp ? jvp : json_null_new());
}
}
if (jvp && ((as_json && jsp->pr_out_hr) || (! as_json))) {
switch (sep) {
case SGJ_SEP_NONE:
break;
case SGJ_SEP_SPACE_1:
n += sg_scnpr(b + n, blen - n, " ");
break;
case SGJ_SEP_SPACE_2:
n += sg_scnpr(b + n, blen - n, " ");
break;
case SGJ_SEP_SPACE_3:
n += sg_scnpr(b + n, blen - n, " ");
break;
case SGJ_SEP_SPACE_4:
n += sg_scnpr(b + n, blen - n, " ");
break;
case SGJ_SEP_EQUAL_NO_SPACE:
n += sg_scnpr(b + n, blen - n, "=");
break;
case SGJ_SEP_EQUAL_1_SPACE:
n += sg_scnpr(b + n, blen - n, "= ");
break;
case SGJ_SEP_COLON_NO_SPACE:
n += sg_scnpr(b + n, blen - n, ":");
break;
case SGJ_SEP_COLON_1_SPACE:
n += sg_scnpr(b + n, blen - n, ": ");
break;
default:
break;
}
switch (jtype) {
case json_string:
sg_scnpr(b + n, blen - n, "%s", jvp->u.string.ptr);
break;
case json_integer:
sg_scnpr(b + n, blen - n, "%" PRIi64, jvp->u.integer);
break;
case json_boolean:
sg_scnpr(b + n, blen - n, "%s", jvp->u.boolean ? "true" : "false");
break;
case json_none:
default:
break;
}
}
if (as_json && jsp->pr_out_hr)
json_array_push((json_value *)jsp->out_hrp, json_string_new(b));
else if (! as_json)
printf("%s\n", b);
if (jvp && (! eaten))
json_builder_free((json_value *)jvp);
}
void
sgj_pr_hr_js_vs(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
const char * name, enum sgj_separator_t sep,
const char * value)
{
json_value * jvp;
/* make json_value even if jsp->pr_as_json is false */
jvp = value ? json_string_new(value) : NULL;
sgj_pr_hr_js_xx(jsp, jop, leadin_sp, name, sep, jvp);
}
void
sgj_pr_hr_js_vi(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
const char * name, enum sgj_separator_t sep, int64_t value)
{
json_value * jvp;
jvp = json_integer_new(value);
sgj_pr_hr_js_xx(jsp, jop, leadin_sp, name, sep, jvp);
}
void
sgj_pr_hr_js_vb(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
const char * name, enum sgj_separator_t sep, bool value)
{
json_value * jvp;
jvp = json_boolean_new(value);
sgj_pr_hr_js_xx(jsp, jop, leadin_sp, name, sep, jvp);
}
#ifdef SG_PRSE_SENSE_DECODE
static const char * dtsp = "descriptor too short";
static const char * sksvp = "sense-key specific valid";
static const char * ddep = "designation_descriptor_error";
static const char * naa_exp = "Network Address Authority";
static const char * aoi_exp = "IEEE-Administered Organizational Identifier";
bool
sgj_get_designation_descriptor(sgj_state * jsp, sgj_opaque_p jop,
const uint8_t * ddp, int dd_len)
{
int p_id, piv, c_set, assoc, desig_type, d_id, naa;
int n, aoi, vsi, dlen;
uint64_t ull;
const uint8_t * ip;
char e[80];
char b[256];
const char * cp;
const char * naa_sp;
sgj_opaque_p jo2p;
static const int blen = sizeof(b);
static const int elen = sizeof(e);
if (dd_len < 4) {
sgj_add_nv_s(jsp, jop, ddep, "too short");
return false;
}
dlen = ddp[3];
if (dlen > (dd_len - 4)) {
snprintf(e, elen, "too long: says it is %d bytes, but given %d "
"bytes\n", dlen, dd_len - 4);
sgj_add_nv_s(jsp, jop, ddep, e);
return false;
}
ip = ddp + 4;
p_id = ((ddp[0] >> 4) & 0xf);
c_set = (ddp[0] & 0xf);
piv = ((ddp[1] & 0x80) ? 1 : 0);
assoc = ((ddp[1] >> 4) & 0x3);
desig_type = (ddp[1] & 0xf);
cp = sg_get_desig_assoc_str(assoc);
if (assoc == 3)
cp = "Reserved [0x3]"; /* should not happen */
sgj_add_nv_ihexstr(jsp, jop, "association", assoc, NULL, cp);
cp = sg_get_desig_type_str(desig_type);
if (NULL == cp)
cp = "unknown";
sgj_add_nv_ihexstr(jsp, jop, "designator_type", desig_type,
NULL, cp);
cp = sg_get_desig_code_set_str(c_set);
if (NULL == cp)
cp = "unknown";
sgj_add_nv_ihexstr(jsp, jop, "code_set", desig_type,
NULL, cp);
sgj_add_nv_ihex_ane(jsp, jop, "piv", piv, false,
"Protocol Identifier Valid");
sg_get_trans_proto_str(p_id, elen, e);
sgj_add_nv_ihexstr(jsp, jop, "protocol_identifier", p_id, NULL, e);
switch (desig_type) {
case 0: /* vendor specific */
sgj_add_nv_hex_bytes(jsp, jop, "vendor_specific_hexbytes", ip, dlen);
break;
case 1: /* T10 vendor identification */
n = (dlen < 8) ? dlen : 8;
snprintf(b, blen, "%.*s", n, ip);
sgj_add_nv_s(jsp, jop, "t10_vendor_identification", b);
b[0] = '\0';
if (dlen > 8)
snprintf(b, blen, "%.*s", dlen - 8, ip + 8);
sgj_add_nv_s(jsp, jop, "vendor_specific_identifier", b);
break;
case 2: /* EUI-64 based */
sgj_add_nv_i(jsp, jop, "eui_64_based_designator_length", dlen);
ull = sg_get_unaligned_be64(ip);
switch (dlen) {
case 8:
sgj_add_nv_ihex(jsp, jop, "ieee_identifier", ull);
break;
case 12:
sgj_add_nv_ihex(jsp, jop, "ieee_identifier", ull);
sgj_add_nv_ihex(jsp, jop, "directory_id",
sg_get_unaligned_be32(ip + 8));
break;
case 16:
sgj_add_nv_ihex(jsp, jop, "identifier_extension", ull);
sgj_add_nv_ihex(jsp, jop, "ieee_identifier",
sg_get_unaligned_be64(ip + 8));
break;
default:
sgj_add_nv_s(jsp, jop, "eui_64", "decoding failed");
break;
}
break;
case 3: /* NAA <n> */
if (jsp->pr_hex)
sgj_add_nv_hex_bytes(jsp, jop, "full_naa_hexbytes", ip, dlen);
naa = (ip[0] >> 4) & 0xff;
switch (naa) {
case 2:
naa_sp = "IEEE Extended";
sgj_add_nv_ihexstr_ane(jsp, jop, "naa", naa, false, NULL, naa_sp,
naa_exp);
d_id = (((ip[0] & 0xf) << 8) | ip[1]);
sgj_add_nv_ihex(jsp, jop, "vendor_specific_identifier_a", d_id);
aoi = sg_get_unaligned_be24(ip + 2);
sgj_add_nv_ihex_ane(jsp, jop, "aoi", aoi, true, aoi_exp);
vsi = sg_get_unaligned_be24(ip + 5);
sgj_add_nv_ihex(jsp, jop, "vendor_specific_identifier_b", vsi);
break;
case 3:
naa_sp = "Locally Assigned";
sgj_add_nv_ihexstr_ane(jsp, jop, "naa", naa, false, NULL, naa_sp,
naa_exp);
ull = sg_get_unaligned_be64(ip + 0) & 0xfffffffffffffffULL;
sgj_add_nv_ihex(jsp, jop, "locally_administered_value", ull);
break;
case 5:
naa_sp = "IEEE Registered";
sgj_add_nv_ihexstr_ane(jsp, jop, "naa", naa, false, NULL, naa_sp,
naa_exp);
aoi = (sg_get_unaligned_be32(ip + 0) >> 4) & 0xffffff;
sgj_add_nv_ihex_ane(jsp, jop, "aoi", aoi, true, aoi_exp);
ull = sg_get_unaligned_be48(ip + 2) & 0xfffffffffULL;
sgj_add_nv_ihex(jsp, jop, "vendor_specific_identifier", ull);
break;
case 6:
naa_sp = "IEEE Registered Extended";
sgj_add_nv_ihexstr_ane(jsp, jop, "naa", naa, false, NULL, naa_sp,
naa_exp);
aoi = (sg_get_unaligned_be32(ip + 0) >> 4) & 0xffffff;
sgj_add_nv_ihex_ane(jsp, jop, "aoi", aoi, true, aoi_exp);
ull = sg_get_unaligned_be48(ip + 2) & 0xfffffffffULL;
sgj_add_nv_ihex(jsp, jop, "vendor_specific_identifier", ull);
ull = sg_get_unaligned_be64(ip + 8);
sgj_add_nv_ihex(jsp, jop, "vendor_specific_identifier_extension",
ull);
break;
default:
snprintf(b, blen, "unknown NAA value=0x%x", naa);
sgj_add_nv_ihexstr_ane(jsp, jop, "naa", naa, true, NULL, b,
naa_exp);
sgj_add_nv_hex_bytes(jsp, jop, "full_naa_hexbytes", ip, dlen);
break;
}
break;
case 4: /* Relative target port */
if (jsp->pr_hex)
sgj_add_nv_hex_bytes(jsp, jop, "relative_target_port_hexbytes",
ip, dlen);
sgj_add_nv_ihex(jsp, jop, "relative_target_port_identifier",
sg_get_unaligned_be16(ip + 2));
break;
case 5: /* (primary) Target port group */
if (jsp->pr_hex)
sgj_add_nv_hex_bytes(jsp, jop, "target_port_group_hexbytes",
ip, dlen);
sgj_add_nv_ihex(jsp, jop, "target_port_group",
sg_get_unaligned_be16(ip + 2));
break;
case 6: /* Logical unit group */
if (jsp->pr_hex)
sgj_add_nv_hex_bytes(jsp, jop, "logical_unit_group_hexbytes",
ip, dlen);
sgj_add_nv_ihex(jsp, jop, "logical_unit_group",
sg_get_unaligned_be16(ip + 2));
break;
case 7: /* MD5 logical unit identifier */
sgj_add_nv_hex_bytes(jsp, jop, "md5_logical_unit_hexbytes",
ip, dlen);
break;
case 8: /* SCSI name string */
if (jsp->pr_hex)
sgj_add_nv_hex_bytes(jsp, jop, "scsi_name_string_hexbytes",
ip, dlen);
snprintf(b, blen, "%s", ip);
sgj_add_nv_s(jsp, jop, "scsi_name_string", b);
break;
case 9: /* Protocol specific port identifier */
if (jsp->pr_hex)
sgj_add_nv_hex_bytes(jsp, jop,
"protocol_specific_port_identifier_hexbytes",
ip, dlen);
if (TPROTO_UAS == p_id) {
jo2p = sgj_new_named_object(jsp, jop,
"usb_target_port_identifier");
sgj_add_nv_ihex(jsp, jo2p, "device_address", 0x7f & ip[0]);
sgj_add_nv_ihex(jsp, jo2p, "interface_number", ip[2]);
} else if (TPROTO_SOP == p_id) {
jo2p = sgj_new_named_object(jsp, jop, "pci_express_routing_id");
sgj_add_nv_ihex(jsp, jo2p, "routing_id",
sg_get_unaligned_be16(ip + 0));
} else
sgj_add_nv_s(jsp, jop, "protocol_specific_port_identifier",
"decoding failure");
break;
case 0xa: /* UUID identifier */
if (jsp->pr_hex)
sgj_add_nv_hex_bytes(jsp, jop, "uuid_hexbytes",
ip, dlen);
sg_t10_uuid_desig2str(ip, dlen, c_set, false, true, NULL, blen, b);
n = strlen(b);
if ((n > 0) && ('\n' == b[n - 1]))
b[n - 1] = '\0';
sgj_add_nv_s(jsp, jop, "uuid", b);
break;
default: /* reserved */
sgj_add_nv_hex_bytes(jsp, jop, "reserved_designator_hexbytes",
ip, dlen);
break;
}
return true;
}
static void
sgj_progress_indication(sgj_state * jsp, sgj_opaque_p jop,
uint16_t prog_indic, bool is_another)
{
uint32_t progress, pr, rem;
sgj_opaque_p jo2p;
char b[64];
if (is_another)
jo2p = sgj_new_named_object(jsp, jop, "another_progress_indication");
else
jo2p = sgj_new_named_object(jsp, jop, "progress_indication");
if (NULL == jo2p)
return;
progress = prog_indic;
sgj_add_nv_i(jsp, jo2p, "i", progress);
snprintf(b, sizeof(b), "%x", progress);
sgj_add_nv_s(jsp, jo2p, "hex", b);
progress *= 100;
pr = progress / 65536;
rem = (progress % 65536) / 656;
snprintf(b, sizeof(b), "%d.02%d%%\n", pr, rem);
sgj_add_nv_s(jsp, jo2p, "percentage", b);
}
static bool
sgj_decode_sks(sgj_state * jsp, sgj_opaque_p jop, const uint8_t * dp, int dlen,
int sense_key)
{
switch (sense_key) {
case SPC_SK_ILLEGAL_REQUEST:
if (dlen < 3) {
sgj_add_nv_s(jsp, jop, "illegal_request_sks", dtsp);
return false;
}
sgj_add_nv_ihex_ane(jsp, jop, "sksv", !! (dp[0] & 0x80), false,
sksvp);
sgj_add_nv_ihex_ane(jsp, jop, "c_d", !! (dp[0] & 0x40), false,
"c: cdb; d: data-out");
sgj_add_nv_ihex_ane(jsp, jop, "bpv", !! (dp[0] & 0x8), false,
"bit pointer (index) valid");
sgj_add_nv_i(jsp, jop, "bit_pointer", dp[0] & 0x7);
sgj_add_nv_ihex(jsp, jop, "field_pointer",
sg_get_unaligned_be16(dp + 1));
break;
case SPC_SK_HARDWARE_ERROR:
case SPC_SK_MEDIUM_ERROR:
case SPC_SK_RECOVERED_ERROR:
if (dlen < 3) {
sgj_add_nv_s(jsp, jop, "actual_retry_count_sks", dtsp);
return false;
}
sgj_add_nv_ihex_ane(jsp, jop, "sksv", !! (dp[0] & 0x80), false,
sksvp);
sgj_add_nv_ihex(jsp, jop, "actual_retry_count",
sg_get_unaligned_be16(dp + 1));
break;
case SPC_SK_NO_SENSE:
case SPC_SK_NOT_READY:
if (dlen < 7) {
sgj_add_nv_s(jsp, jop, "progress_indication_sks", dtsp);
return false;
}
sgj_add_nv_ihex_ane(jsp, jop, "sksv", !! (dp[0] & 0x80), false,
sksvp);
sgj_progress_indication(jsp, jop, sg_get_unaligned_be16(dp + 1),
false);
break;
case SPC_SK_COPY_ABORTED:
if (dlen < 7) {
sgj_add_nv_s(jsp, jop, "segment_indication_sks", dtsp);
return false;
}
sgj_add_nv_ihex_ane(jsp, jop, "sksv", !! (dp[0] & 0x80), false,
sksvp);
sgj_add_nv_ihex_ane(jsp, jop, "sd", !! (dp[0] & 0x20), false,
"field pointer relative to: 1->segment "
"descriptor, 0->parameter list");
sgj_add_nv_ihex_ane(jsp, jop, "bpv", !! (dp[0] & 0x8), false,
"bit pointer (index) valid");
sgj_add_nv_i(jsp, jop, "bit_pointer", dp[0] & 0x7);
sgj_add_nv_ihex(jsp, jop, "field_pointer",
sg_get_unaligned_be16(dp + 1));
break;
case SPC_SK_UNIT_ATTENTION:
if (dlen < 7) {
sgj_add_nv_s(jsp, jop, "segment_indication_sks", dtsp);
return false;
}
sgj_add_nv_ihex_ane(jsp, jop, "sksv", !! (dp[0] & 0x80), false,
sksvp);
sgj_add_nv_i(jsp, jop, "overflow", !! (dp[0] & 0x80));
break;
default:
sgj_add_nv_ihex(jsp, jop, "unexpected_sense_key", sense_key);
return false;
}
return true;
}
#define TPGS_STATE_OPTIMIZED 0x0
#define TPGS_STATE_NONOPTIMIZED 0x1
#define TPGS_STATE_STANDBY 0x2
#define TPGS_STATE_UNAVAILABLE 0x3
#define TPGS_STATE_OFFLINE 0xe
#define TPGS_STATE_TRANSITIONING 0xf
static int
decode_tpgs_state(int st, char * b, int blen)
{
switch (st) {
case TPGS_STATE_OPTIMIZED:
return sg_scnpr(b, blen, "active/optimized");
case TPGS_STATE_NONOPTIMIZED:
return sg_scnpr(b, blen, "active/non optimized");
case TPGS_STATE_STANDBY:
return sg_scnpr(b, blen, "standby");
case TPGS_STATE_UNAVAILABLE:
return sg_scnpr(b, blen, "unavailable");
case TPGS_STATE_OFFLINE:
return sg_scnpr(b, blen, "offline");
case TPGS_STATE_TRANSITIONING:
return sg_scnpr(b, blen, "transitioning between states");
default:
return sg_scnpr(b, blen, "unknown: 0x%x", st);
}
}
static bool
sgj_uds_referral_descriptor(sgj_state * jsp, sgj_opaque_p jop,
const uint8_t * dp, int alen)
{
int dlen = alen - 2;
int k, j, g, f, aas;
uint64_t ull;
const uint8_t * tp;
sgj_opaque_p jap, jo2p, ja2p, jo3p;
char c[40];
sgj_add_nv_ihex_ane(jsp, jop, "not_all_r", (dp[2] & 0x1), false,
"Not all referrals");
dp += 4;
jap = sgj_new_named_array(jsp, jop,
"user_data_segment_referral_descriptor_list");
for (k = 0, f = 1; (k + 4) < dlen; k += g, dp += g, ++f) {
int ntpgd = dp[3];
jo2p = sgj_new_unattached_object(jsp);
g = (ntpgd * 4) + 20;
sgj_add_nv_ihex(jsp, jo2p, "number_of_target_port_group_descriptors",
ntpgd);
if ((k + g) > dlen) {
sgj_add_nv_i(jsp, jo2p, "truncated_descriptor_dlen", dlen);
sgj_add_nv_o(jsp, jap, NULL /* name */, jo2p);
return false;
}
ull = sg_get_unaligned_be64(dp + 4);
sgj_add_nv_ihex(jsp, jo2p, "first_user_date_sgment_lba", ull);
ull = sg_get_unaligned_be64(dp + 12);
sgj_add_nv_ihex(jsp, jo2p, "last_user_date_sgment_lba", ull);
ja2p = sgj_new_named_array(jsp, jo2p,
"target_port_group_descriptor_list");
for (j = 0; j < ntpgd; ++j) {
jo3p = sgj_new_unattached_object(jsp);
tp = dp + 20 + (j * 4);
aas = tp[0] & 0xf;
decode_tpgs_state(aas, c, sizeof(c));
sgj_add_nv_ihexstr(jsp, jo3p, "asymmetric_access_state", aas,
NULL, c);
sgj_add_nv_ihex(jsp, jo3p, "target_port_group",
sg_get_unaligned_be16(tp + 2));
sgj_add_nv_o(jsp, ja2p, NULL /* name */, jo3p);
}
sgj_add_nv_o(jsp, jap, NULL /* name */, jo2p);
}
return true;
}
/* Copy of static array in sg_lib.c */
static const char * dd_usage_reason_str_arr[] = {
"Unknown",
"resend this and further commands to:",
"resend this command to:",
"new subsidiary lu added to this administrative lu:",
"administrative lu associated with a preferred binding:",
};
static bool
sgj_get_sense_descriptors(sgj_state * jsp, sgj_opaque_p jop,
const struct sg_scsi_sense_hdr * sshp,
const uint8_t * sbp, int sb_len)
{
bool processed = true;
int add_sb_len, desc_len, k, dt, sense_key, n, sds;
uint16_t sct_sc;
uint64_t ull;
const uint8_t * descp;
const char * cp;
sgj_opaque_p jap, jo2p, jo3p;
char b[80];
static const int blen = sizeof(b);
static const char * parsing = "parsing_error";
#if 0
static const char * eccp = "Extended copy command";
static const char * ddp = "destination device";
#endif
add_sb_len = sshp->additional_length;
add_sb_len = (add_sb_len < sb_len) ? add_sb_len : sb_len;
sense_key = sshp->sense_key;
jap = sgj_new_named_array(jsp, jop, "sense_data_descriptor_list");
for (descp = sbp, k = 0; (k < add_sb_len);
k += desc_len, descp += desc_len) {
int add_d_len = (k < (add_sb_len - 1)) ? descp[1] : -1;
jo2p = sgj_new_unattached_object(jsp);
if ((k + add_d_len + 2) > add_sb_len)
add_d_len = add_sb_len - k - 2;
desc_len = add_d_len + 2;
processed = true;
dt = descp[0];
switch (dt) {
case 0:
sgj_add_nv_ihexstr(jsp, jo2p, "descriptor_type", dt,
NULL, "Information");
if (add_d_len >= 10) {
int valid = !! (0x80 & descp[2]);
sgj_add_nv_ihexstr(jsp, jo2p, "valid", valid, NULL,
valid ? "as per T10" : "Vendor specific");
sgj_add_nv_ihex(jsp, jo2p, "information",
sg_get_unaligned_be64(descp + 4));
} else {
sgj_add_nv_s(jsp, jo2p, parsing, dtsp);
processed = false;
}
break;
case 1:
sgj_add_nv_ihexstr(jsp, jo2p, "descriptor_type", dt,
NULL, "Command specific");
if (add_d_len >= 10) {
sgj_add_nv_ihex(jsp, jo2p, "command_specific_information",
sg_get_unaligned_be64(descp + 4));
} else {
sgj_add_nv_s(jsp, jo2p, parsing, dtsp);
processed = false;
}
break;
case 2: /* Sense Key Specific */
sgj_add_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
"Sense key specific");
processed = sgj_decode_sks(jsp, jo2p, descp + 4, desc_len - 4,
sense_key);
break;
case 3:
sgj_add_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
"Field replaceable unit code");
if (add_d_len >= 2)
sgj_add_nv_ihex(jsp, jo2p, "field_replaceable_unit_code",
descp[3]);
else {
sgj_add_nv_s(jsp, jo2p, parsing, dtsp);
processed = false;
}
break;
case 4:
sgj_add_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
"Stream commands");
if (add_d_len >= 2) {
sgj_add_nv_i(jsp, jo2p, "filemark", !! (descp[3] & 0x80));
sgj_add_nv_ihex_ane(jsp, jo2p, "eom", !! (descp[3] & 0x40),
false, "End Of Medium");
sgj_add_nv_ihex_ane(jsp, jo2p, "ili", !! (descp[3] & 0x20),
false, "Incorrect Length Indicator");
} else {
sgj_add_nv_s(jsp, jo2p, parsing, dtsp);
processed = false;
}
break;
case 5:
sgj_add_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
"Block commands");
if (add_d_len >= 2)
sgj_add_nv_ihex_ane(jsp, jo2p, "ili", !! (descp[3] & 0x20),
false, "Incorrect Length Indicator");
else {
sgj_add_nv_s(jsp, jo2p, parsing, dtsp);
processed = false;
}
break;
case 6:
sgj_add_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
"OSD object identification");
sgj_add_nv_s(jsp, jo2p, parsing, "Unsupported");
processed = false;
break;
case 7:
sgj_add_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
"OSD response integrity check value");
sgj_add_nv_s(jsp, jo2p, parsing, "Unsupported");
break;
case 8:
sgj_add_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
"OSD attribute identification");
sgj_add_nv_s(jsp, jo2p, parsing, "Unsupported");
processed = false;
break;
case 9: /* this is defined in SAT (SAT-2) */
sgj_add_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
"ATA status return");
if (add_d_len >= 12) {
sgj_add_nv_i(jsp, jo2p, "extend", !! (descp[2] & 1));
sgj_add_nv_ihex(jsp, jo2p, "error", descp[3]);
sgj_add_nv_ihex(jsp, jo2p, "count",
sg_get_unaligned_be16(descp + 4));
ull = ((uint64_t)descp[10] << 40) |
((uint64_t)descp[8] << 32) |
(descp[6] << 24) |
(descp[11] << 16) |
(descp[9] << 8) |
descp[7];
sgj_add_nv_ihex(jsp, jo2p, "lba", ull);
sgj_add_nv_ihex(jsp, jo2p, "device", descp[12]);
sgj_add_nv_ihex(jsp, jo2p, "status", descp[13]);
} else {
sgj_add_nv_s(jsp, jo2p, parsing, dtsp);
processed = false;
}
break;
case 0xa:
/* Added in SPC-4 rev 17, became 'Another ...' in rev 34 */
sgj_add_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
"Another progress indication");
if (add_d_len < 6) {
sgj_add_nv_s(jsp, jo2p, parsing, dtsp);
processed = false;
break;
}
sgj_add_nv_ihex(jsp, jo2p, "another_sense_key", descp[2]);
sgj_add_nv_ihex(jsp, jo2p, "another_additional_sense_code",
descp[3]);
sgj_add_nv_ihex(jsp, jo2p,
"another_additional_sense_code_qualifier",
descp[4]);
sgj_progress_indication(jsp, jo2p,
sg_get_unaligned_be16(descp + 6), true);
break;
case 0xb: /* Added in SPC-4 rev 23, defined in SBC-3 rev 22 */
sgj_add_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
"User data segment referral");
if (add_d_len < 2) {
sgj_add_nv_s(jsp, jo2p, parsing, dtsp);
processed = false;
break;
}
if (! sgj_uds_referral_descriptor(jsp, jo2p, descp, add_d_len)) {
sgj_add_nv_s(jsp, jo2p, parsing, dtsp);
processed = false;
}
break;
case 0xc: /* Added in SPC-4 rev 28 */
sgj_add_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
"Forwarded sense data");
if (add_d_len < 2) {
sgj_add_nv_s(jsp, jo2p, parsing, dtsp);
processed = false;
break;
}
sgj_add_nv_ihex_ane(jsp, jo2p, "fsdt", !! (0x80 & descp[2]),
false, "Forwarded Sense Data Truncated");
sds = (0x7 & descp[2]);
if (sds < 1)
snprintf(b, blen, "%s [%d]", "Unknown", sds);
else if (sds > 9)
snprintf(b, blen, "%s [%d]", "Reserved", sds);
else {
n = 0;
n += sg_scnpr(b + n, blen - n, "EXTENDED COPY command copy %s",
(sds == 1) ? "source" : "destination");
if (sds > 1)
n += sg_scnpr(b + n, blen - n, " %d", sds - 1);
}
sgj_add_nv_ihexstr(jsp, jo2p, "sense_data_source",
(0x7 & descp[2]), NULL, b);
jo3p = sgj_new_named_object(jsp, jo2p, "forwarded_sense_data");
sgj_get_sense(jsp, jo3p, descp + 4, desc_len - 4);
break;
case 0xd: /* Added in SBC-3 rev 36d */
/* this descriptor combines descriptors 0, 1, 2 and 3 */
sgj_add_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
"Direct-access block device");
if (add_d_len < 28) {
sgj_add_nv_s(jsp, jo2p, parsing, dtsp);
processed = false;
break;
}
sgj_add_nv_i(jsp, jo2p, "valid", (0x80 & descp[2]));
sgj_add_nv_ihex_ane(jsp, jo2p, "ili", !! (0x20 & descp[2]),
false, "Incorrect Length Indicator");
processed = sgj_decode_sks(jsp, jo2p, descp + 4, desc_len - 4,
sense_key);
sgj_add_nv_ihex(jsp, jo2p, "field_replaceable_unit_code",
descp[7]);
sgj_add_nv_ihex(jsp, jo2p, "information",
sg_get_unaligned_be64(descp + 8));
sgj_add_nv_ihex(jsp, jo2p, "command_specific_information",
sg_get_unaligned_be64(descp + 16));
break;
case 0xe: /* Added in SPC-5 rev 6 (for Bind/Unbind) */
sgj_add_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
"Device designation");
n = descp[3];
cp = (n < (int)SG_ARRAY_SIZE(dd_usage_reason_str_arr)) ?
dd_usage_reason_str_arr[n] : "Unknown (reserved)";
sgj_add_nv_ihexstr(jsp, jo2p, "descriptor_usage_reason",
n, NULL, cp);
jo3p = sgj_new_named_object(jsp, jo2p,
"device_designation_descriptor");
sgj_get_designation_descriptor(jsp, jo3p, descp + 4,
desc_len - 4);
break;
case 0xf: /* Added in SPC-5 rev 10 (for Write buffer) */
sgj_add_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
"Microcode activation");
if (add_d_len < 6) {
sgj_add_nv_s(jsp, jop, parsing, dtsp);
processed = false;
break;
}
sgj_add_nv_ihex(jsp, jo2p, "microcode_activation_time",
sg_get_unaligned_be16(descp + 6));
break;
case 0xde: /* NVME Status Field; vendor (sg3_utils) specific */
sgj_add_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
"NVME status (sg3_utils)");
if (add_d_len < 6) {
sgj_add_nv_s(jsp, jop, parsing, dtsp);
processed = false;
break;
}
sgj_add_nv_ihex_ane(jsp, jo2p, "dnr", !! (0x80 & descp[5]),
false, "Do not retry");
sgj_add_nv_ihex_ane(jsp, jo2p, "m", !! (0x40 & descp[5]),
false, "More");
sct_sc = sg_get_unaligned_be16(descp + 6);
sgj_add_nv_ihexstr_ane
(jsp, jo2p, "sct_sc", sct_sc, true, NULL,
sg_get_nvme_cmd_status_str(sct_sc, blen, b),
"Status Code Type (upper 8 bits) and Status Code");
break;
default:
if (dt >= 0x80)
sgj_add_nv_ihex(jsp, jo2p, "vendor_specific_descriptor_type",
dt);
else
sgj_add_nv_ihex(jsp, jo2p, "unknown_descriptor_type", dt);
sgj_add_nv_hex_bytes(jsp, jo2p, "descriptor_hexbytes",
descp, desc_len);
processed = false;
break;
}
sgj_add_nv_o(jsp, jap, NULL /* name */, jo2p);
}
return processed;
}
#define ASCQ_ATA_PT_INFO_AVAILABLE 0x1d /* corresponding ASC is 0 */
/* Fetch sense information */
bool
sgj_get_sense(sgj_state * jsp, sgj_opaque_p jop, const uint8_t * sbp,
int sb_len)
{
bool descriptor_format = false;
bool sdat_ovfl = false;
bool ret = true;
bool valid_info_fld;
int len, n;
uint32_t info;
uint8_t resp_code;
const char * ebp = NULL;
char ebuff[64];
char b[256];
struct sg_scsi_sense_hdr ssh;
static int blen = sizeof(b);
static int elen = sizeof(ebuff);
if ((NULL == sbp) || (sb_len < 1)) {
snprintf(ebuff, elen, "sense buffer empty\n");
ebp = ebuff;
ret = false;
goto fini;
}
resp_code = 0x7f & sbp[0];
valid_info_fld = !!(sbp[0] & 0x80);
len = sb_len;
if (! sg_scsi_normalize_sense(sbp, sb_len, &ssh)) {
ebp = "unable to normalize sense buffer";
ret = false;
goto fini;
}
/* We have been able to normalize the sense buffer */
switch (resp_code) {
case 0x70: /* fixed, current */
ebp = "Fixed format, current";
len = (sb_len > 7) ? (sbp[7] + 8) : sb_len;
len = (len > sb_len) ? sb_len : len;
sdat_ovfl = (len > 2) ? !!(sbp[2] & 0x10) : false;
break;
case 0x71: /* fixed, deferred */
/* error related to a previous command */
ebp = "Fixed format, <<<deferred>>>";
len = (sb_len > 7) ? (sbp[7] + 8) : sb_len;
len = (len > sb_len) ? sb_len : len;
sdat_ovfl = (len > 2) ? !!(sbp[2] & 0x10) : false;
break;
case 0x72: /* descriptor, current */
descriptor_format = true;
ebp = "Descriptor format, current";
sdat_ovfl = (sb_len > 4) ? !!(sbp[4] & 0x80) : false;
break;
case 0x73: /* descriptor, deferred */
descriptor_format = true;
ebp = "Descriptor format, <<<deferred>>>";
sdat_ovfl = (sb_len > 4) ? !!(sbp[4] & 0x80) : false;
break;
default:
sg_scnpr(ebuff, elen, "Unknown code: 0x%x", resp_code);
ebp = ebuff;
break;
}
sgj_add_nv_ihexstr(jsp, jop, "response_code", resp_code, NULL, ebp);
sgj_add_nv_b(jsp, jop, "descriptor_format", descriptor_format);
sgj_add_nv_ihex_ane(jsp, jop, "sdat_ovfl", sdat_ovfl, false,
"Sense data overflow");
sgj_add_nv_ihexstr(jsp, jop, "sense_key", ssh.sense_key, NULL,
sg_lib_sense_key_desc[ssh.sense_key]);
sgj_add_nv_ihex(jsp, jop, "additional_sense_code", ssh.asc);
sgj_add_nv_ihex(jsp, jop, "additional_sense_code_qualifier", ssh.ascq);
sgj_add_nv_s(jsp, jop, "additional_sense_str",
sg_get_additional_sense_str(ssh.asc, ssh.ascq, false,
blen, b));
if (descriptor_format) {
if (len > 8) {
ret = sgj_get_sense_descriptors(jsp, jop, &ssh, sbp + 8, len - 8);
if (ret == false) {
ebp = "unable to decode sense descriptor";
goto fini;
}
}
} else if ((len > 12) && (0 == ssh.asc) &&
(ASCQ_ATA_PT_INFO_AVAILABLE == ssh.ascq)) {
/* SAT ATA PASS-THROUGH fixed format */
sgj_add_nv_ihex(jsp, jop, "error", sbp[3]);
sgj_add_nv_ihex(jsp, jop, "status", sbp[4]);
sgj_add_nv_ihex(jsp, jop, "device", sbp[5]);
sgj_add_nv_i(jsp, jop, "extend", !! (0x80 & sbp[8]));
sgj_add_nv_i(jsp, jop, "count_upper_nonzero", !! (0x40 & sbp[8]));
sgj_add_nv_i(jsp, jop, "lba_upper_nonzero", !! (0x20 & sbp[8]));
sgj_add_nv_i(jsp, jop, "log_index", (0x7 & sbp[8]));
sgj_add_nv_i(jsp, jop, "lba", sg_get_unaligned_le24(sbp + 9));
} else if (len > 2) { /* fixed format */
sgj_add_nv_i(jsp, jop, "valid", valid_info_fld);
sgj_add_nv_i(jsp, jop, "filemark", !! (sbp[2] & 0x80));
sgj_add_nv_ihex_ane(jsp, jop, "eom", !! (sbp[2] & 0x40),
false, "End Of Medium");
sgj_add_nv_ihex_ane(jsp, jop, "ili", !! (sbp[2] & 0x20),
false, "Incorrect Length Indicator");
info = sg_get_unaligned_be32(sbp + 3);
sgj_add_nv_ihex(jsp, jop, "information", info);
sgj_add_nv_ihex(jsp, jop, "additional_sense_length", sbp[7]);
if (sb_len > 11) {
info = sg_get_unaligned_be32(sbp + 8);
sgj_add_nv_ihex(jsp, jop, "command_specific_information", info);
}
if (sb_len > 14)
sgj_add_nv_ihex(jsp, jop, "field_replaceable_unit_code", sbp[14]);
if (sb_len > 17)
sgj_decode_sks(jsp, jop, sbp + 15, sb_len - 15, ssh.sense_key);
n = sbp[7];
n = (sb_len > n) ? n : sb_len;
sgj_add_nv_ihex(jsp, jop, "number_of_bytes_beyond_18",
(n > 18) ? n - 18 : 0);
} else {
snprintf(ebuff, sizeof(ebuff), "sb_len=%d too short", sb_len);
ebp = ebuff;
ret = false;
}
fini:
if ((! ret) && ebp)
sgj_add_nv_s(jsp, jop, "sense_decode_error", ebp);
return ret;
}
#endif