diff options
Diffstat (limited to 'lib/sg_pr2serr.c')
-rw-r--r-- | lib/sg_pr2serr.c | 2026 |
1 files changed, 2026 insertions, 0 deletions
diff --git a/lib/sg_pr2serr.c b/lib/sg_pr2serr.c new file mode 100644 index 00000000..ef533967 --- /dev/null +++ b/lib/sg_pr2serr.c @@ -0,0 +1,2026 @@ +/* + * 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" + +/* + * Some users of sg_pr2serr may not need fixed and descriptor sense decoded + * for JSON output. If the following define is commented out the effective + * compile size of this file is reduced by 800 lines plus dependencies on + * other large components of the sg3_utils library. + * Comment out the next line to remove dependency on sg_lib.h and its code. + */ +#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; +} + +#ifndef SG_PRSE_SENSE_DECODE + +/* Want safe, 'n += snprintf(b + n, blen - n, ...)' style sequence of + * functions. Returns number of chars placed in cp excluding the + * trailing null char. So for cp_max_len > 0 the return value is always + * < cp_max_len; for cp_max_len <= 1 the return value is 0 and no chars are + * written to cp. Note this means that when cp_max_len = 1, this function + * assumes that cp[0] is the null character and does nothing (and returns + * 0). Linux kernel has a similar function called scnprintf(). Public + * declaration in sg_pr2serr.h header */ +int +sg_scnpr(char * cp, int cp_max_len, const char * fmt, ...) +{ + va_list args; + int n; + + if (cp_max_len < 2) + return 0; + va_start(args, fmt); + n = vsnprintf(cp, cp_max_len, fmt, args); + va_end(args); + return (n < cp_max_len) ? n : (cp_max_len - 1); +} + +int +pr2ws(const char * fmt, ...) +{ + va_list args; + int n; + + va_start(args, fmt); + n = vfprintf(stderr, fmt, args); + va_end(args); + return n; +} + +#endif + +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 '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 'n': + jsp->pr_name_ex = ! 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, + " 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, + " n show 'name_extra' information fields\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%se%sh%sk%sl%sn%so%sp%ss%sv", jsp->pr_indent_size, + jsp->pr_exit_status ? "" : "-", jsp->pr_hex ? "" : "-", + jsp->pr_packed ? "" : "-", jsp->pr_leadin ? "" : "-", + jsp->pr_name_ex ? "" : "-", 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_exit_status = true; + jsp->pr_hex = false; + jsp->pr_leadin = true; + jsp->pr_out_hr = false; + jsp->pr_name_ex = 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_r(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_js_str_out(jsp, bp, strlen(bp)); + free(bp); + } + } + } + return jvp; +} + +void +sgj_js2file(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]; + +#ifdef SG_PRSE_SENSE_DECODE + 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); +#else + if (0 == exit_status) + strncpy(d, "no errors", sizeof(d) - 1); + else + snprintf(d, sizeof(d), "exit_status=%d", exit_status); +#endif + sgj_js_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_named_subobject_r(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_snake_named_subobject_r(sgj_state * jsp, sgj_opaque_p jop, + const char * conv2sname) +{ + if (jsp && jsp->pr_as_json && conv2sname) { + int olen = strlen(conv2sname); + char * sname = (char *)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_named_subarray_r(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_snake_named_subarray_r(sgj_state * jsp, sgj_opaque_p jop, + const char * conv2sname) +{ + if (jsp && jsp->pr_as_json && conv2sname) { + int olen = strlen(conv2sname); + char * sname = (char *)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_r(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_r(sgj_state * jsp) +{ + return (jsp && jsp->pr_as_json) ? json_array_new(0) : NULL; +} + +sgj_opaque_p +sgj_js_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_js_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_js_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_js_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_js_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_js_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_named_subobject_r(jsp, jop, name); + char b[64]; + + if (NULL == jo2p) + return; + sgj_js_nv_i(jsp, jo2p, "i", (int64_t)value); + snprintf(b, sizeof(b), "%" PRIx64, value); + sgj_js_nv_s(jsp, jo2p, "hex", b); + } else + sgj_js_nv_i(jsp, jop, name, (int64_t)value); +} + +static const char * sc_mn_s = "meaning"; + +void +sgj_js_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 (val_s && jsp->pr_string) { + sgj_opaque_p jo2p = sgj_named_subobject_r(jsp, jop, name); + + if (NULL == jo2p) + return; + sgj_js_nv_i(jsp, jo2p, "i", (int64_t)val_i); + sgj_js_nv_s(jsp, jo2p, str_name ? str_name : sc_mn_s, val_s); + } else + sgj_js_nv_i(jsp, jop, name, val_i); +} + +void +sgj_js_nv_ihexstr(sgj_state * jsp, sgj_opaque_p jop, const char * name, + int64_t val_i, const char * str_name, const char * val_s) +{ + bool as_str; + + if ((NULL == jsp) || (! jsp->pr_as_json)) + return; + as_str = jsp->pr_string && val_s; + if ((! jsp->pr_hex) && (! as_str)) + sgj_js_nv_i(jsp, jop, name, val_i); + else { + char b[64]; + sgj_opaque_p jo2p = sgj_named_subobject_r(jsp, jop, name); + + if (NULL == jo2p) + return; + sgj_js_nv_i(jsp, jo2p, "i", (int64_t)val_i); + if (jsp->pr_hex) { + snprintf(b, sizeof(b), "%" PRIx64, val_i); + sgj_js_nv_s(jsp, jo2p, "hex", b); + } + if (as_str) + sgj_js_nv_s(jsp, jo2p, str_name ? str_name : sc_mn_s, val_s); + } +} + +static const char * sc_nex_s = "name_extra"; + +void +sgj_js_nv_ihex_nex(sgj_state * jsp, sgj_opaque_p jop, const char * name, + int64_t val_i, bool hex_as_well, const char * nex_s) +{ + bool as_hex, as_nex; + + if ((NULL == jsp) || (! jsp->pr_as_json)) + return; + as_hex = jsp->pr_hex && hex_as_well; + as_nex = jsp->pr_name_ex && nex_s; + if (! (as_hex || as_nex)) + sgj_js_nv_i(jsp, jop, name, val_i); + else { + char b[64]; + sgj_opaque_p jo2p = + sgj_named_subobject_r(jsp, jop, name); + + if (NULL == jo2p) + return; + sgj_js_nv_i(jsp, jo2p, "i", (int64_t)val_i); + if (as_hex) { + snprintf(b, sizeof(b), "%" PRIx64, val_i); + sgj_js_nv_s(jsp, jo2p, "hex", b); + } + if (as_nex) + sgj_js_nv_s(jsp, jo2p, sc_nex_s, nex_s); + } +} + +#ifndef SG_PRSE_SENSE_DECODE +static void +h2str(const uint8_t * byte_arr, int num_bytes, char * bp, int blen) +{ + int j, k, n; + + for (k = 0, n = 0; (k < num_bytes) && (n < blen); ) { + j = sg_scnpr(bp + n, blen - n, "%02x ", byte_arr[k]); + if (j < 2) + break; + n += j; + ++k; + if ((0 == (k % 8)) && (k < num_bytes) && (n < blen)) { + bp[n++] = ' '; + } + } + j = strlen(bp); + if ((j > 0) && (' ' == bp[j - 1])) + bp[j - 1] = '\0'; /* chop off trailing space */ +} +#endif + +/* Add hex byte strings irrespective of jsp->pr_hex setting. */ +void +sgj_js_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) { +#ifdef SG_PRSE_SENSE_DECODE + hex2str(byte_arr, num_bytes, NULL, 2, blen, bp); +#else + h2str(byte_arr, num_bytes, bp, blen); +#endif + sgj_js_nv_s(jsp, jop, name, bp); + free(bp); + } +} + +void +sgj_js_nv_ihexstr_nex(sgj_state * jsp, sgj_opaque_p jop, const char * name, + int64_t val_i, bool hex_as_well, const char * str_name, + const char * val_s, const char * nex_s) +{ + bool as_hex = jsp->pr_hex && hex_as_well; + bool as_str = jsp->pr_string && val_s; + bool as_nex = jsp->pr_name_ex && nex_s; + const char * sname = str_name ? str_name : sc_mn_s; + + if ((NULL == jsp) || (! jsp->pr_as_json)) + return; + if (! (as_hex || as_nex || as_str)) + sgj_js_nv_i(jsp, jop, name, val_i); + else { + char b[64]; + sgj_opaque_p jo2p = + sgj_named_subobject_r(jsp, jop, name); + + if (NULL == jo2p) + return; + sgj_js_nv_i(jsp, jo2p, "i", (int64_t)val_i); + if (as_nex) { + if (as_hex) { + snprintf(b, sizeof(b), "%" PRIx64, val_i); + sgj_js_nv_s(jsp, jo2p, "hex", b); + } + if (as_str) { + sgj_js_nv_s(jsp, jo2p, sname, val_s); + } + sgj_js_nv_s(jsp, jo2p, sc_nex_s, nex_s); + } else if (as_hex) { + snprintf(b, sizeof(b), "%" PRIx64, val_i); + sgj_js_nv_s(jsp, jo2p, "hex", b); + if (as_str) + sgj_js_nv_s(jsp, jo2p, sname, val_s); + } else if (as_str) + sgj_js_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_js_str_out(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_js_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_js_nv_s_len(jsp, jsp->out_hrp, NULL, prev_sp, n); + } +} + +char * +sgj_convert_to_snake_name(const char * in_name, char * sname, + int max_sname_len) +{ + sgj_name_to_snake(in_name, sname, max_sname_len); + return sname; +} + +bool +sgj_is_snake_name(const char * in_name) +{ + size_t k; + size_t ln = strlen(in_name); + char c; + + for (k = 0; k < ln; ++k) { + c = in_name[k]; + if (((c >= '0') && (c <= '9')) || + ((c >= 'a') && (c <= 'z')) || + (c == '_')) + continue; + else + return false; + } + return true; +} + +/* 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"). Parentheses and the characters between them are removed. + * 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; + bool within_paren = 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 (within_paren) { + if (')' == c) + within_paren = false; + continue; + } + if (isalnum(c)) { + out[j++] = isupper(c) ? tolower(c) : c; + prev_underscore = false; + } else if ('(' == c) + within_paren = true; + 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 int +sgj_jtype_to_s(char * b, int blen_max, json_value * jvp) +{ + json_type jtype = jvp ? jvp->type : json_none; + + switch (jtype) { + case json_string: + return sg_scnpr(b, blen_max, "%s", jvp->u.string.ptr); + case json_integer: + return sg_scnpr(b, blen_max, "%" PRIi64, jvp->u.integer); + case json_boolean: + return sg_scnpr(b, blen_max, "%s", jvp->u.boolean ? "true" : "false"); + case json_none: + default: + if ((blen_max > 0) && ('\0' != b[0])) + b[0] = '\0'; + break; + } + return 0; +} + +static int +sgj_haj_helper(char * b, int blen_max, const char * name, + enum sgj_separator_t sep, bool use_jvp, + json_value * jvp, int64_t val_instead) +{ + int n = 0; + + if (name) { + n += sg_scnpr(b + n, blen_max - n, "%s", name); + switch (sep) { + case SGJ_SEP_NONE: + break; + case SGJ_SEP_SPACE_1: + n += sg_scnpr(b + n, blen_max - n, " "); + break; + case SGJ_SEP_SPACE_2: + n += sg_scnpr(b + n, blen_max - n, " "); + break; + case SGJ_SEP_SPACE_3: + n += sg_scnpr(b + n, blen_max - n, " "); + break; + case SGJ_SEP_SPACE_4: + n += sg_scnpr(b + n, blen_max - n, " "); + break; + case SGJ_SEP_EQUAL_NO_SPACE: + n += sg_scnpr(b + n, blen_max - n, "="); + break; + case SGJ_SEP_EQUAL_1_SPACE: + n += sg_scnpr(b + n, blen_max - n, "= "); + break; + case SGJ_SEP_COLON_NO_SPACE: + n += sg_scnpr(b + n, blen_max - n, ":"); + break; + case SGJ_SEP_COLON_1_SPACE: + n += sg_scnpr(b + n, blen_max - n, ": "); + break; + default: + break; + } + } + if (use_jvp) + n += sgj_jtype_to_s(b + n, blen_max - n, jvp); + else + n += sg_scnpr(b + n, blen_max - n, "%" PRIi64, val_instead); + return n; +} + +static void +sgj_haj_xx(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp, + const char * name, enum sgj_separator_t sep, json_value * jvp, + bool hex_as_well, const char * val_s, const char * nex_s) +{ + bool eaten = false; + bool as_json = (jsp && jsp->pr_as_json); + bool done; + 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)) { + n += sgj_jtype_to_s(b + n, blen - n, jvp); + 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()); + } + } + goto fini; + } + if (as_json) { + int k; + + if (NULL == jop) + jop = jsp->basep; + k = sgj_name_to_snake(name, jname, sizeof(jname)); + if (k > 0) { + done = false; + if (nex_s && (strlen(nex_s) > 0)) { + switch (jtype) { + case json_string: + break; + case json_integer: + sgj_js_nv_ihexstr_nex(jsp, jop, jname, jvp->u.integer, + hex_as_well, sc_mn_s, val_s, nex_s); + done = true; + break; + case json_boolean: + sgj_js_nv_ihexstr_nex(jsp, jop, jname, jvp->u.boolean, + false, sc_mn_s, val_s, nex_s); + done = true; + break; + case json_none: + default: + break; + } + } else { + switch (jtype) { + case json_string: + break; + case json_integer: + if (hex_as_well) { + sgj_js_nv_ihexstr(jsp, jop, jname, jvp->u.integer, + sc_mn_s, val_s); + done = true; + } + break; + case json_none: + default: + break; + } + } + if (! done) { + eaten = true; + json_object_push((json_value *)jop, jname, + jvp ? jvp : json_null_new()); + } + } + } + if (jvp && ((as_json && jsp->pr_out_hr) || (! as_json))) + n += sgj_haj_helper(b + n, blen - n, name, sep, true, jvp, 0); + + if (as_json && jsp->pr_out_hr) + json_array_push((json_value *)jsp->out_hrp, json_string_new(b)); + if (! as_json) + printf("%s\n", b); +fini: + if (jvp && (! eaten)) + json_builder_free((json_value *)jvp); +} + +void +sgj_haj_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_haj_xx(jsp, jop, leadin_sp, name, sep, jvp, false, NULL, NULL); +} + +void +sgj_haj_vi(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp, + const char * name, enum sgj_separator_t sep, int64_t value, + bool hex_as_well) +{ + json_value * jvp; + + jvp = json_integer_new(value); + sgj_haj_xx(jsp, jop, leadin_sp, name, sep, jvp, hex_as_well, NULL, NULL); +} + +void +sgj_haj_vistr(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp, + const char * name, enum sgj_separator_t sep, int64_t value, + bool hex_as_well, const char * val_s) +{ + json_value * jvp; + + jvp = json_integer_new(value); + sgj_haj_xx(jsp, jop, leadin_sp, name, sep, jvp, hex_as_well, val_s, + NULL); +} + +void +sgj_haj_vi_nex(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp, + const char * name, enum sgj_separator_t sep, + int64_t value, bool hex_as_well, const char * nex_s) +{ + json_value * jvp; + + jvp = json_integer_new(value); + sgj_haj_xx(jsp, jop, leadin_sp, name, sep, jvp, hex_as_well, NULL, nex_s); +} + +void +sgj_haj_vistr_nex(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp, + const char * name, enum sgj_separator_t sep, + int64_t value, bool hex_as_well, + const char * val_s, const char * nex_s) +{ + json_value * jvp; + + jvp = json_integer_new(value); + sgj_haj_xx(jsp, jop, leadin_sp, name, sep, jvp, hex_as_well, val_s, + nex_s); +} + +void +sgj_haj_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_haj_xx(jsp, jop, leadin_sp, name, sep, jvp, false, NULL, NULL); +} + +sgj_opaque_p +sgj_haj_subo_r(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp, + const char * name, enum sgj_separator_t sep, int64_t value, + bool hex_as_well) +{ + bool as_json = (jsp && jsp->pr_as_json); + int n = 0; + sgj_opaque_p jo2p; + char b[256]; + static const int blen = sizeof(b); + + if (NULL == name) + return NULL; + for (n = 0; n < leadin_sp; ++n) + b[n] = ' '; + b[n] = '\0'; + if ((! as_json) || (jsp && jsp->pr_out_hr)) + n += sgj_haj_helper(b + n, blen - n, name, sep, false, NULL, value); + + if (as_json && jsp->pr_out_hr) + json_array_push((json_value *)jsp->out_hrp, json_string_new(b)); + if (! as_json) + printf("%s\n", b); + + if (as_json) { + sgj_name_to_snake(name, b, blen); + jo2p = sgj_named_subobject_r(jsp, jop, b); + if (jo2p) { + sgj_js_nv_i(jsp, jo2p, "i", value); + if (hex_as_well && jsp->pr_hex) { + snprintf(b, blen, "%" PRIx64, value); + sgj_js_nv_s(jsp, jo2p, "hex", b); + } + } + return jo2p; + } + return NULL; +} + +#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_js_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_js_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_js_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_js_nv_ihexstr(jsp, jop, "association", assoc, NULL, cp); + cp = sg_get_desig_type_str(desig_type); + if (NULL == cp) + cp = "unknown"; + sgj_js_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_js_nv_ihexstr(jsp, jop, "code_set", desig_type, + NULL, cp); + sgj_js_nv_ihex_nex(jsp, jop, "piv", piv, false, + "Protocol Identifier Valid"); + sg_get_trans_proto_str(p_id, elen, e); + sgj_js_nv_ihexstr(jsp, jop, "protocol_identifier", p_id, NULL, e); + switch (desig_type) { + case 0: /* vendor specific */ + sgj_js_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_js_nv_s(jsp, jop, "t10_vendor_identification", b); + b[0] = '\0'; + if (dlen > 8) + snprintf(b, blen, "%.*s", dlen - 8, ip + 8); + sgj_js_nv_s(jsp, jop, "vendor_specific_identifier", b); + break; + case 2: /* EUI-64 based */ + sgj_js_nv_i(jsp, jop, "eui_64_based_designator_length", dlen); + ull = sg_get_unaligned_be64(ip); + switch (dlen) { + case 8: + sgj_js_nv_ihex(jsp, jop, "ieee_identifier", ull); + break; + case 12: + sgj_js_nv_ihex(jsp, jop, "ieee_identifier", ull); + sgj_js_nv_ihex(jsp, jop, "directory_id", + sg_get_unaligned_be32(ip + 8)); + break; + case 16: + sgj_js_nv_ihex(jsp, jop, "identifier_extension", ull); + sgj_js_nv_ihex(jsp, jop, "ieee_identifier", + sg_get_unaligned_be64(ip + 8)); + break; + default: + sgj_js_nv_s(jsp, jop, "eui_64", "decoding failed"); + break; + } + break; + case 3: /* NAA <n> */ + if (jsp->pr_hex) + sgj_js_nv_hex_bytes(jsp, jop, "full_naa_hexbytes", ip, dlen); + naa = (ip[0] >> 4) & 0xff; + switch (naa) { + case 2: + naa_sp = "IEEE Extended"; + sgj_js_nv_ihexstr_nex(jsp, jop, "naa", naa, false, NULL, naa_sp, + naa_exp); + d_id = (((ip[0] & 0xf) << 8) | ip[1]); + sgj_js_nv_ihex(jsp, jop, "vendor_specific_identifier_a", d_id); + aoi = sg_get_unaligned_be24(ip + 2); + sgj_js_nv_ihex_nex(jsp, jop, "aoi", aoi, true, aoi_exp); + vsi = sg_get_unaligned_be24(ip + 5); + sgj_js_nv_ihex(jsp, jop, "vendor_specific_identifier_b", vsi); + break; + case 3: + naa_sp = "Locally Assigned"; + sgj_js_nv_ihexstr_nex(jsp, jop, "naa", naa, false, NULL, naa_sp, + naa_exp); + ull = sg_get_unaligned_be64(ip + 0) & 0xfffffffffffffffULL; + sgj_js_nv_ihex(jsp, jop, "locally_administered_value", ull); + break; + case 5: + naa_sp = "IEEE Registered"; + sgj_js_nv_ihexstr_nex(jsp, jop, "naa", naa, false, NULL, naa_sp, + naa_exp); + aoi = (sg_get_unaligned_be32(ip + 0) >> 4) & 0xffffff; + sgj_js_nv_ihex_nex(jsp, jop, "aoi", aoi, true, aoi_exp); + ull = sg_get_unaligned_be48(ip + 2) & 0xfffffffffULL; + sgj_js_nv_ihex(jsp, jop, "vendor_specific_identifier", ull); + break; + case 6: + naa_sp = "IEEE Registered Extended"; + sgj_js_nv_ihexstr_nex(jsp, jop, "naa", naa, false, NULL, naa_sp, + naa_exp); + aoi = (sg_get_unaligned_be32(ip + 0) >> 4) & 0xffffff; + sgj_js_nv_ihex_nex(jsp, jop, "aoi", aoi, true, aoi_exp); + ull = sg_get_unaligned_be48(ip + 2) & 0xfffffffffULL; + sgj_js_nv_ihex(jsp, jop, "vendor_specific_identifier", ull); + ull = sg_get_unaligned_be64(ip + 8); + sgj_js_nv_ihex(jsp, jop, "vendor_specific_identifier_extension", + ull); + break; + default: + snprintf(b, blen, "unknown NAA value=0x%x", naa); + sgj_js_nv_ihexstr_nex(jsp, jop, "naa", naa, true, NULL, b, + naa_exp); + sgj_js_nv_hex_bytes(jsp, jop, "full_naa_hexbytes", ip, dlen); + break; + } + break; + case 4: /* Relative target port */ + if (jsp->pr_hex) + sgj_js_nv_hex_bytes(jsp, jop, "relative_target_port_hexbytes", + ip, dlen); + sgj_js_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_js_nv_hex_bytes(jsp, jop, "target_port_group_hexbytes", + ip, dlen); + sgj_js_nv_ihex(jsp, jop, "target_port_group", + sg_get_unaligned_be16(ip + 2)); + break; + case 6: /* Logical unit group */ + if (jsp->pr_hex) + sgj_js_nv_hex_bytes(jsp, jop, "logical_unit_group_hexbytes", + ip, dlen); + sgj_js_nv_ihex(jsp, jop, "logical_unit_group", + sg_get_unaligned_be16(ip + 2)); + break; + case 7: /* MD5 logical unit identifier */ + sgj_js_nv_hex_bytes(jsp, jop, "md5_logical_unit_hexbytes", + ip, dlen); + break; + case 8: /* SCSI name string */ + if (jsp->pr_hex) + sgj_js_nv_hex_bytes(jsp, jop, "scsi_name_string_hexbytes", + ip, dlen); + snprintf(b, blen, "%.*s", dlen, ip); + sgj_js_nv_s(jsp, jop, "scsi_name_string", b); + break; + case 9: /* Protocol specific port identifier */ + if (jsp->pr_hex) + sgj_js_nv_hex_bytes(jsp, jop, + "protocol_specific_port_identifier_hexbytes", + ip, dlen); + if (TPROTO_UAS == p_id) { + jo2p = sgj_named_subobject_r(jsp, jop, + "usb_target_port_identifier"); + sgj_js_nv_ihex(jsp, jo2p, "device_address", 0x7f & ip[0]); + sgj_js_nv_ihex(jsp, jo2p, "interface_number", ip[2]); + } else if (TPROTO_SOP == p_id) { + jo2p = sgj_named_subobject_r(jsp, jop, "pci_express_routing_id"); + sgj_js_nv_ihex(jsp, jo2p, "routing_id", + sg_get_unaligned_be16(ip + 0)); + } else + sgj_js_nv_s(jsp, jop, "protocol_specific_port_identifier", + "decoding failure"); + + break; + case 0xa: /* UUID identifier */ + if (jsp->pr_hex) + sgj_js_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_js_nv_s(jsp, jop, "uuid", b); + break; + default: /* reserved */ + sgj_js_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_named_subobject_r(jsp, jop, "another_progress_indication"); + else + jo2p = sgj_named_subobject_r(jsp, jop, "progress_indication"); + if (NULL == jo2p) + return; + progress = prog_indic; + sgj_js_nv_i(jsp, jo2p, "i", progress); + snprintf(b, sizeof(b), "%x", progress); + sgj_js_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_js_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_js_nv_s(jsp, jop, "illegal_request_sks", dtsp); + return false; + } + sgj_js_nv_ihex_nex(jsp, jop, "sksv", !! (dp[0] & 0x80), false, + sksvp); + sgj_js_nv_ihex_nex(jsp, jop, "c_d", !! (dp[0] & 0x40), false, + "c: cdb; d: data-out"); + sgj_js_nv_ihex_nex(jsp, jop, "bpv", !! (dp[0] & 0x8), false, + "bit pointer (index) valid"); + sgj_js_nv_i(jsp, jop, "bit_pointer", dp[0] & 0x7); + sgj_js_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_js_nv_s(jsp, jop, "actual_retry_count_sks", dtsp); + return false; + } + sgj_js_nv_ihex_nex(jsp, jop, "sksv", !! (dp[0] & 0x80), false, + sksvp); + sgj_js_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_js_nv_s(jsp, jop, "progress_indication_sks", dtsp); + return false; + } + sgj_js_nv_ihex_nex(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_js_nv_s(jsp, jop, "segment_indication_sks", dtsp); + return false; + } + sgj_js_nv_ihex_nex(jsp, jop, "sksv", !! (dp[0] & 0x80), false, + sksvp); + sgj_js_nv_ihex_nex(jsp, jop, "sd", !! (dp[0] & 0x20), false, + "field pointer relative to: 1->segment " + "descriptor, 0->parameter list"); + sgj_js_nv_ihex_nex(jsp, jop, "bpv", !! (dp[0] & 0x8), false, + "bit pointer (index) valid"); + sgj_js_nv_i(jsp, jop, "bit_pointer", dp[0] & 0x7); + sgj_js_nv_ihex(jsp, jop, "field_pointer", + sg_get_unaligned_be16(dp + 1)); + break; + case SPC_SK_UNIT_ATTENTION: + if (dlen < 7) { + sgj_js_nv_s(jsp, jop, "segment_indication_sks", dtsp); + return false; + } + sgj_js_nv_ihex_nex(jsp, jop, "sksv", !! (dp[0] & 0x80), false, + sksvp); + sgj_js_nv_i(jsp, jop, "overflow", !! (dp[0] & 0x80)); + break; + default: + sgj_js_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_js_nv_ihex_nex(jsp, jop, "not_all_r", (dp[2] & 0x1), false, + "Not all referrals"); + dp += 4; + jap = sgj_named_subarray_r(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_r(jsp); + g = (ntpgd * 4) + 20; + sgj_js_nv_ihex(jsp, jo2p, "number_of_target_port_group_descriptors", + ntpgd); + if ((k + g) > dlen) { + sgj_js_nv_i(jsp, jo2p, "truncated_descriptor_dlen", dlen); + sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p); + return false; + } + ull = sg_get_unaligned_be64(dp + 4); + sgj_js_nv_ihex(jsp, jo2p, "first_user_date_sgment_lba", ull); + ull = sg_get_unaligned_be64(dp + 12); + sgj_js_nv_ihex(jsp, jo2p, "last_user_date_sgment_lba", ull); + ja2p = sgj_named_subarray_r(jsp, jo2p, + "target_port_group_descriptor_list"); + for (j = 0; j < ntpgd; ++j) { + jo3p = sgj_new_unattached_object_r(jsp); + tp = dp + 20 + (j * 4); + aas = tp[0] & 0xf; + decode_tpgs_state(aas, c, sizeof(c)); + sgj_js_nv_ihexstr(jsp, jo3p, "asymmetric_access_state", aas, + NULL, c); + sgj_js_nv_ihex(jsp, jo3p, "target_port_group", + sg_get_unaligned_be16(tp + 2)); + sgj_js_nv_o(jsp, ja2p, NULL /* name */, jo3p); + } + sgj_js_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_js_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_named_subarray_r(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_r(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_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, + NULL, "Information"); + if (add_d_len >= 10) { + int valid = !! (0x80 & descp[2]); + sgj_js_nv_ihexstr(jsp, jo2p, "valid", valid, NULL, + valid ? "as per T10" : "Vendor specific"); + sgj_js_nv_ihex(jsp, jo2p, "information", + sg_get_unaligned_be64(descp + 4)); + } else { + sgj_js_nv_s(jsp, jo2p, parsing, dtsp); + processed = false; + } + break; + case 1: + sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, + NULL, "Command specific"); + if (add_d_len >= 10) { + sgj_js_nv_ihex(jsp, jo2p, "command_specific_information", + sg_get_unaligned_be64(descp + 4)); + } else { + sgj_js_nv_s(jsp, jo2p, parsing, dtsp); + processed = false; + } + break; + case 2: /* Sense Key Specific */ + sgj_js_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_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL, + "Field replaceable unit code"); + if (add_d_len >= 2) + sgj_js_nv_ihex(jsp, jo2p, "field_replaceable_unit_code", + descp[3]); + else { + sgj_js_nv_s(jsp, jo2p, parsing, dtsp); + processed = false; + } + break; + case 4: + sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL, + "Stream commands"); + if (add_d_len >= 2) { + sgj_js_nv_i(jsp, jo2p, "filemark", !! (descp[3] & 0x80)); + sgj_js_nv_ihex_nex(jsp, jo2p, "eom", !! (descp[3] & 0x40), + false, "End Of Medium"); + sgj_js_nv_ihex_nex(jsp, jo2p, "ili", !! (descp[3] & 0x20), + false, "Incorrect Length Indicator"); + } else { + sgj_js_nv_s(jsp, jo2p, parsing, dtsp); + processed = false; + } + break; + case 5: + sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL, + "Block commands"); + if (add_d_len >= 2) + sgj_js_nv_ihex_nex(jsp, jo2p, "ili", !! (descp[3] & 0x20), + false, "Incorrect Length Indicator"); + else { + sgj_js_nv_s(jsp, jo2p, parsing, dtsp); + processed = false; + } + break; + case 6: + sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL, + "OSD object identification"); + sgj_js_nv_s(jsp, jo2p, parsing, "Unsupported"); + processed = false; + break; + case 7: + sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL, + "OSD response integrity check value"); + sgj_js_nv_s(jsp, jo2p, parsing, "Unsupported"); + break; + case 8: + sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL, + "OSD attribute identification"); + sgj_js_nv_s(jsp, jo2p, parsing, "Unsupported"); + processed = false; + break; + case 9: /* this is defined in SAT (SAT-2) */ + sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL, + "ATA status return"); + if (add_d_len >= 12) { + sgj_js_nv_i(jsp, jo2p, "extend", !! (descp[2] & 1)); + sgj_js_nv_ihex(jsp, jo2p, "error", descp[3]); + sgj_js_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_js_nv_ihex(jsp, jo2p, "lba", ull); + sgj_js_nv_ihex(jsp, jo2p, "device", descp[12]); + sgj_js_nv_ihex(jsp, jo2p, "status", descp[13]); + } else { + sgj_js_nv_s(jsp, jo2p, parsing, dtsp); + processed = false; + } + break; + case 0xa: + /* Added in SPC-4 rev 17, became 'Another ...' in rev 34 */ + sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL, + "Another progress indication"); + if (add_d_len < 6) { + sgj_js_nv_s(jsp, jo2p, parsing, dtsp); + processed = false; + break; + } + sgj_js_nv_ihex(jsp, jo2p, "another_sense_key", descp[2]); + sgj_js_nv_ihex(jsp, jo2p, "another_additional_sense_code", + descp[3]); + sgj_js_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_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL, + "User data segment referral"); + if (add_d_len < 2) { + sgj_js_nv_s(jsp, jo2p, parsing, dtsp); + processed = false; + break; + } + if (! sgj_uds_referral_descriptor(jsp, jo2p, descp, add_d_len)) { + sgj_js_nv_s(jsp, jo2p, parsing, dtsp); + processed = false; + } + break; + case 0xc: /* Added in SPC-4 rev 28 */ + sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL, + "Forwarded sense data"); + if (add_d_len < 2) { + sgj_js_nv_s(jsp, jo2p, parsing, dtsp); + processed = false; + break; + } + sgj_js_nv_ihex_nex(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_js_nv_ihexstr(jsp, jo2p, "sense_data_source", + (0x7 & descp[2]), NULL, b); + jo3p = sgj_named_subobject_r(jsp, jo2p, "forwarded_sense_data"); + sgj_js_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_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL, + "Direct-access block device"); + if (add_d_len < 28) { + sgj_js_nv_s(jsp, jo2p, parsing, dtsp); + processed = false; + break; + } + sgj_js_nv_i(jsp, jo2p, "valid", (0x80 & descp[2])); + sgj_js_nv_ihex_nex(jsp, jo2p, "ili", !! (0x20 & descp[2]), + false, "Incorrect Length Indicator"); + processed = sgj_decode_sks(jsp, jo2p, descp + 4, desc_len - 4, + sense_key); + sgj_js_nv_ihex(jsp, jo2p, "field_replaceable_unit_code", + descp[7]); + sgj_js_nv_ihex(jsp, jo2p, "information", + sg_get_unaligned_be64(descp + 8)); + sgj_js_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_js_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_js_nv_ihexstr(jsp, jo2p, "descriptor_usage_reason", + n, NULL, cp); + jo3p = sgj_named_subobject_r(jsp, jo2p, + "device_designation_descriptor"); + sgj_js_designation_descriptor(jsp, jo3p, descp + 4, desc_len - 4); + break; + case 0xf: /* Added in SPC-5 rev 10 (for Write buffer) */ + sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL, + "Microcode activation"); + if (add_d_len < 6) { + sgj_js_nv_s(jsp, jop, parsing, dtsp); + processed = false; + break; + } + sgj_js_nv_ihex(jsp, jo2p, "microcode_activation_time", + sg_get_unaligned_be16(descp + 6)); + break; + case 0xde: /* NVME Status Field; vendor (sg3_utils) specific */ + sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL, + "NVME status (sg3_utils)"); + if (add_d_len < 6) { + sgj_js_nv_s(jsp, jop, parsing, dtsp); + processed = false; + break; + } + sgj_js_nv_ihex_nex(jsp, jo2p, "dnr", !! (0x80 & descp[5]), + false, "Do not retry"); + sgj_js_nv_ihex_nex(jsp, jo2p, "m", !! (0x40 & descp[5]), + false, "More"); + sct_sc = sg_get_unaligned_be16(descp + 6); + sgj_js_nv_ihexstr_nex + (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_js_nv_ihex(jsp, jo2p, "vendor_specific_descriptor_type", + dt); + else + sgj_js_nv_ihex(jsp, jo2p, "unknown_descriptor_type", dt); + sgj_js_nv_hex_bytes(jsp, jo2p, "descriptor_hexbytes", + descp, desc_len); + processed = false; + break; + } + sgj_js_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_js_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_js_nv_ihexstr(jsp, jop, "response_code", resp_code, NULL, ebp); + sgj_js_nv_b(jsp, jop, "descriptor_format", descriptor_format); + sgj_js_nv_ihex_nex(jsp, jop, "sdat_ovfl", sdat_ovfl, false, + "Sense data overflow"); + sgj_js_nv_ihexstr(jsp, jop, "sense_key", ssh.sense_key, NULL, + sg_lib_sense_key_desc[ssh.sense_key]); + sgj_js_nv_ihex(jsp, jop, "additional_sense_code", ssh.asc); + sgj_js_nv_ihex(jsp, jop, "additional_sense_code_qualifier", ssh.ascq); + sgj_js_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_js_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_js_nv_ihex(jsp, jop, "error", sbp[3]); + sgj_js_nv_ihex(jsp, jop, "status", sbp[4]); + sgj_js_nv_ihex(jsp, jop, "device", sbp[5]); + sgj_js_nv_i(jsp, jop, "extend", !! (0x80 & sbp[8])); + sgj_js_nv_i(jsp, jop, "count_upper_nonzero", !! (0x40 & sbp[8])); + sgj_js_nv_i(jsp, jop, "lba_upper_nonzero", !! (0x20 & sbp[8])); + sgj_js_nv_i(jsp, jop, "log_index", (0xf & sbp[8])); + sgj_js_nv_i(jsp, jop, "lba", sg_get_unaligned_le24(sbp + 9)); + } else if (len > 2) { /* fixed format */ + sgj_js_nv_i(jsp, jop, "valid", valid_info_fld); + sgj_js_nv_i(jsp, jop, "filemark", !! (sbp[2] & 0x80)); + sgj_js_nv_ihex_nex(jsp, jop, "eom", !! (sbp[2] & 0x40), + false, "End Of Medium"); + sgj_js_nv_ihex_nex(jsp, jop, "ili", !! (sbp[2] & 0x20), + false, "Incorrect Length Indicator"); + info = sg_get_unaligned_be32(sbp + 3); + sgj_js_nv_ihex(jsp, jop, "information", info); + sgj_js_nv_ihex(jsp, jop, "additional_sense_length", sbp[7]); + if (sb_len > 11) { + info = sg_get_unaligned_be32(sbp + 8); + sgj_js_nv_ihex(jsp, jop, "command_specific_information", info); + } + if (sb_len > 14) + sgj_js_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_js_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_js_nv_s(jsp, jop, "sense_decode_error", ebp); + return ret; +} + +#endif |