/*
 * scsiprint.cpp
 *
 * Home page of code is: https://www.smartmontools.org
 *
 * Copyright (C) 2002-11 Bruce Allen
 * Copyright (C) 2000 Michael Cornwell <cornwell@acm.org>
 * Copyright (C) 2003-23 Douglas Gilbert <dgilbert@interlog.com>
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */


#include "config.h"
#define __STDC_FORMAT_MACROS 1 // enable PRI* for C++

#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>

#include "scsicmds.h"
#include "atacmds.h" // dont_print_serial_number
#include "dev_interface.h"
#include "scsiprint.h"
#include "smartctl.h"
#include "utility.h"
#include "sg_unaligned.h"

#include "farmcmds.h"
#include "farmprint.h"

#define GBUF_SIZE 65532

const char * scsiprint_c_cvsid = "$Id: scsiprint.cpp 5495 2023-07-10 13:17:30Z chrfranke $"
                                 SCSIPRINT_H_CVSID;

#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))

uint8_t gBuf[GBUF_SIZE];
#define LOG_RESP_LEN 252
#define LOG_RESP_LONG_LEN ((62 * 256) + 252)
#define LOG_RESP_TAPE_ALERT_LEN 0x144

/* Supported log pages + Supported log pages and subpages maximum count */
#define SCSI_SUPP_LOG_PAGES_MAX_COUNT (252 + (62 * 128) + 126)

/* Log pages supported */
static bool gSmartLPage = false;     /* Informational Exceptions log page */
static bool gTempLPage = false;
static bool gSelfTestLPage = false;
static bool gStartStopLPage = false;
static bool gReadECounterLPage = false;
static bool gWriteECounterLPage = false;
static bool gVerifyECounterLPage = false;
static bool gNonMediumELPage = false;
static bool gLastNErrorEvLPage = false;
static bool gBackgroundResultsLPage = false;
static bool gProtocolSpecificLPage = false;
static bool gTapeAlertsLPage = false;
static bool gSSMediaLPage = false;
static bool gFormatStatusLPage = false;
static bool gEnviroReportingLPage = false;
static bool gEnviroLimitsLPage = false;
static bool gUtilizationLPage = false;
static bool gPendDefectsLPage = false;
static bool gBackgroundOpLPage = false;
static bool gLPSMisalignLPage = false;
static bool gTapeDeviceStatsLPage = false;
static bool gZBDeviceStatsLPage = false;
static bool gGenStatsAndPerfLPage = false;

/* Vendor specific log pages */
static bool gSeagateCacheLPage = false;
static bool gSeagateFactoryLPage = false;
static bool gSeagateFarmLPage = false;

/* Mode pages supported */
static bool gIecMPage = true;    /* N.B. assume it until we know otherwise */

/* Remember last successful mode sense/select command */
static int modese_len = 0;

/* Remember this value from the most recent INQUIRY */
static int scsi_version;
#define SCSI_VERSION_SPC_4 0x6
#define SCSI_VERSION_SPC_5 0x7
#define SCSI_VERSION_SPC_6 0xd  /* T10/BSR INCITS 566, proposed in 23-015r0 */
#define SCSI_VERSION_HIGHEST SCSI_VERSION_SPC_6

/* T10 vendor identification. Should match entry in last Annex of SPC
 * drafts and standards (e.g. SPC-4). */
static char scsi_vendor[8+1];
#define T10_VENDOR_SEAGATE "SEAGATE"
#define T10_VENDOR_HITACHI_1 "HITACHI"
#define T10_VENDOR_HITACHI_2 "HL-DT-ST"
#define T10_VENDOR_HITACHI_3 "HGST"

static const char * logSenStr = "Log Sense";
static const char * logSenRspStr = "Log Sense response";
static const char * gsap_s = "General statistics and performance";
static const char * ssm_s = "Solid state media";
static const char * zbds_s = "Zoned block device statistics";
static const char * lp_s = "log page";


static bool
seagate_or_hitachi(void)
{
    return ((0 == memcmp(scsi_vendor, T10_VENDOR_SEAGATE,
                         strlen(T10_VENDOR_SEAGATE))) ||
            (0 == memcmp(scsi_vendor, T10_VENDOR_HITACHI_1,
                         strlen(T10_VENDOR_HITACHI_1))) ||
            (0 == memcmp(scsi_vendor, T10_VENDOR_HITACHI_2,
                         strlen(T10_VENDOR_HITACHI_2))) ||
            (0 == memcmp(scsi_vendor, T10_VENDOR_HITACHI_3,
                         strlen(T10_VENDOR_HITACHI_3))));
}

static bool
all_ffs(const uint8_t * bp, int b_len)
{
    if ((nullptr == bp) || (b_len <= 0))
        return false;
    for (--b_len; b_len >= 0; --b_len) {
        if (0xff != bp[b_len])
            return false;
    }
    return true;
}

// trim from right. By default trims whitespace.
static std::string rtrim(const std::string& s, const char* t = " \t\n\r\f\v")
{
    std::string r(s);

    r.erase(r.find_last_not_of(t) + 1);
    return r;
}

static void
scsiGetSupportedLogPages(scsi_device * device)
{
    bool got_subpages = false;
    int k, err, resp_len, num_unreported, num_unreported_spg;
    int supp_lpg_and_spg_count = 0;

    const uint8_t * up;
    uint8_t sup_lpgs[LOG_RESP_LEN];
    struct scsi_supp_log_pages supp_lpg_and_spg[SCSI_SUPP_LOG_PAGES_MAX_COUNT];

    memset(gBuf, 0, LOG_RESP_LEN);
    memset(supp_lpg_and_spg, 0, sizeof(supp_lpg_and_spg));

    if (SC_NO_SUPPORT == device->cmd_support_level(LOG_SENSE, false, 0)) {
        if (scsi_debugmode > 0)
            pout("%s: RSOC says %s not supported\n", __func__, logSenStr);
        return;
    }
    /* Get supported log pages */
    if ((err = scsiLogSense(device, SUPPORTED_LPAGES, 0, gBuf,
                            LOG_RESP_LEN, 0 /* do double fetch */))) {
        if (scsi_debugmode > 0)
            pout("%s for supported pages failed [%s]\n", logSenStr,
                 scsiErrString(err));
        /* try one more time with defined length, workaround for the bug #678
        found with ST8000NM0075/E001 */
        err = scsiLogSense(device, SUPPORTED_LPAGES, 0, gBuf,
                            LOG_RESP_LEN, 68); /* 64 max pages + 4b header */
        if (scsi_debugmode > 0)
            pout("%s for supported pages failed (second attempt) [%s]\n",
                 logSenStr, scsiErrString(err));
        if (err)
            return;
    }

    memcpy(sup_lpgs, gBuf, LOG_RESP_LEN);
    resp_len = gBuf[3];
    up = gBuf + LOGPAGEHDRSIZE;

    for (k = 0; k < resp_len; k += 1) {
        uint8_t page_code = 0x3f & up[k];
        supp_lpg_and_spg[supp_lpg_and_spg_count++] = {page_code, 0};
    }

    if (SC_NO_SUPPORT ==
        device->cmd_support_level(LOG_SENSE, false, 0,
                                  true /* does it support subpages ? */))
        goto skip_subpages;

    /* Get supported log pages and subpages. Most drives seems to include the
    supported log pages here as well, but some drives such as the Samsung
    PM1643a will only report the additional log pages with subpages here */
    if ((scsi_version >= SCSI_VERSION_SPC_4) &&
            (scsi_version <= SCSI_VERSION_HIGHEST)) {
        /* unclear what code T10 will choose for SPC-6 */
        if ((err = scsiLogSense(device, SUPPORTED_LPAGES, SUPP_SPAGE_L_SPAGE,
                                gBuf, LOG_RESP_LONG_LEN,
                                -1 /* just single not double fetch */))) {
            if (scsi_debugmode > 0)
                pout("%s for supported pages and subpages failed [%s]\n",
                     logSenStr, scsiErrString(err));
        } else {
            /* Ensure we didn't get the same answer than without the subpages */
            if (0 == memcmp(gBuf, sup_lpgs, LOG_RESP_LEN)) {
                if (scsi_debugmode > 0)
                    pout("%s: %s ignored subpage field, bad\n",
                         __func__, logSenRspStr);
            } else if (! ((0x40 & gBuf[0]) &&
                          (SUPP_SPAGE_L_SPAGE == gBuf[1]))) {
                if (scsi_debugmode > 0)
                    pout("%s supported subpages is bad SPF=%u SUBPG=%u\n",
                         logSenRspStr, !! (0x40 & gBuf[0]), gBuf[2]);
            } else {
                got_subpages = true;
            }
        }
    }

    if (got_subpages) {
        resp_len = sg_get_unaligned_be16(gBuf + 2);
        up = gBuf + LOGPAGEHDRSIZE;
        for (k = 0; k < resp_len; k += 2) {
            uint8_t page_code = 0x3f & up[k];
            uint8_t subpage_code = up[k+1];
            supp_lpg_and_spg[supp_lpg_and_spg_count++] = {page_code, subpage_code};
        }
    }

skip_subpages:
    num_unreported = 0;
    num_unreported_spg = 0;
    for (k = 0; k < supp_lpg_and_spg_count; k += 1) {
        struct scsi_supp_log_pages supp_lpg = supp_lpg_and_spg[k];

        switch (supp_lpg.page_code)
        {
            case SUPPORTED_LPAGES:
                if (! ((NO_SUBPAGE_L_SPAGE == supp_lpg.subpage_code) ||
                       (SUPP_SPAGE_L_SPAGE == supp_lpg.subpage_code))) {
                    if (scsi_debugmode > 1)
                        pout("%s: Strange Log page number: 0x0,0x%x\n",
                             __func__, supp_lpg.subpage_code);
                }
                break;
            case READ_ERROR_COUNTER_LPAGE:
                gReadECounterLPage = true;
                break;
            case WRITE_ERROR_COUNTER_LPAGE:
                gWriteECounterLPage = true;
                break;
            case VERIFY_ERROR_COUNTER_LPAGE:
                gVerifyECounterLPage = true;
                break;
            case LAST_N_ERROR_EVENTS_LPAGE:
                gLastNErrorEvLPage = true;
                break;
            case NON_MEDIUM_ERROR_LPAGE:
                gNonMediumELPage = true;
                break;
            case TEMPERATURE_LPAGE:
                if (NO_SUBPAGE_L_SPAGE == supp_lpg.subpage_code)
                    gTempLPage = true;
                else if (ENVIRO_REP_L_SPAGE == supp_lpg.subpage_code)
                    gEnviroReportingLPage = true;
                else if (ENVIRO_LIMITS_L_SPAGE == supp_lpg.subpage_code)
                    gEnviroLimitsLPage = true;
                else if (SUPP_SPAGE_L_SPAGE != supp_lpg.subpage_code) {
                    ++num_unreported;
                    ++num_unreported_spg;
                }
                /* WDC/HGST report <lpage>,0xff tuples for all supported
                   lpages; Seagate doesn't. T10 does not exclude the
                   reporting of <lpage>,0xff so it is not an error. */
                break;
            case STARTSTOP_CYCLE_COUNTER_LPAGE:
                if (NO_SUBPAGE_L_SPAGE == supp_lpg.subpage_code)
                    gStartStopLPage = true;
                else if (UTILIZATION_L_SPAGE == supp_lpg.subpage_code)
                    gUtilizationLPage = true;
                else if (SUPP_SPAGE_L_SPAGE != supp_lpg.subpage_code) {
                    ++num_unreported;
                    ++num_unreported_spg;
                }
                break;
            case SELFTEST_RESULTS_LPAGE:
                gSelfTestLPage = true;
                break;
            case IE_LPAGE:
                gSmartLPage = true;
                break;
            case DEVICE_STATS_LPAGE:
                if (NO_SUBPAGE_L_SPAGE == supp_lpg.subpage_code)
                    gTapeDeviceStatsLPage = true;
                else if (ZB_DEV_STATS_L_SPAGE == supp_lpg.subpage_code)
                    gZBDeviceStatsLPage = true;
                break;
            case BACKGROUND_RESULTS_LPAGE:
                if (NO_SUBPAGE_L_SPAGE == supp_lpg.subpage_code)
                    gBackgroundResultsLPage = true;
                else if (PEND_DEFECTS_L_SPAGE == supp_lpg.subpage_code)
                    gPendDefectsLPage = true;
                else if (BACKGROUND_OP_L_SPAGE == supp_lpg.subpage_code)
                    gBackgroundOpLPage = true;
                else if (LPS_MISALIGN_L_SPAGE == supp_lpg.subpage_code)
                    gLPSMisalignLPage = true;
                else if (SUPP_SPAGE_L_SPAGE != supp_lpg.subpage_code) {
                    ++num_unreported;
                    ++num_unreported_spg;
                }
                break;
            case PROTOCOL_SPECIFIC_LPAGE:
                gProtocolSpecificLPage = true;
                break;
            case GEN_STATS_PERF_LPAGE:
                gGenStatsAndPerfLPage = true;
                break;
            case TAPE_ALERTS_LPAGE:
                gTapeAlertsLPage = true;
                break;
            case SS_MEDIA_LPAGE:
                gSSMediaLPage = true;
                break;
            case FORMAT_STATUS_LPAGE:
                gFormatStatusLPage = true;
                break;
            case SEAGATE_CACHE_LPAGE:
                if (failuretest_permissive) {
                    gSeagateCacheLPage = true;
                    break;
                }
                if (seagate_or_hitachi())
                    gSeagateCacheLPage = true;
                break;
            case SEAGATE_FACTORY_LPAGE:
                if (failuretest_permissive) {
                    gSeagateFactoryLPage = true;
                    break;
                }
                if (seagate_or_hitachi())
                    gSeagateFactoryLPage = true;
                break;
            case SEAGATE_FARM_LPAGE:
                if (scsiIsSeagate(scsi_vendor)) {
                    if (SEAGATE_FARM_CURRENT_L_SPAGE == supp_lpg.subpage_code) {
                        gSeagateFarmLPage = true;
                    } else if (SUPP_SPAGE_L_SPAGE != supp_lpg.subpage_code) {
                        ++num_unreported;
                        ++num_unreported_spg;
                    }
                }
                break;
            default:
                if (supp_lpg.page_code < 0x30) {     /* don't count VS pages */
                    ++num_unreported;
                    if ((supp_lpg.subpage_code > 0) &&
                            (SUPP_SPAGE_L_SPAGE != supp_lpg.subpage_code))
                        ++num_unreported_spg;
                }
                break;
        }
    }
    if (scsi_debugmode > 1)
        pout("%s: number of unreported (standard) %ss: %d (sub-pages: %d)\n",
              __func__, lp_s, num_unreported, num_unreported_spg);
}

/* Returns 0 if ok, -1 if can't check IE, -2 if can check and bad
   (or at least something to report). */
static int
scsiGetSmartData(scsi_device * device, bool attribs)
{
    uint8_t asc;
    uint8_t ascq;
    uint8_t currenttemp = 255;
    uint8_t triptemp = 255;
    const char * cp;
    int err = 0;
    char b[128];

    print_on();
    if (scsiCheckIE(device, gSmartLPage, gTempLPage, &asc, &ascq,
                    &currenttemp, &triptemp)) {
        /* error message already announced */
        print_off();
        return -1;
    }
    print_off();
    cp = scsiGetIEString(asc, ascq, b, sizeof(b));
    if (cp) {
        err = -2;
        print_on();
        jout("SMART Health Status: %s [asc=%x, ascq=%x]\n", cp, asc, ascq);
        print_off();
        jglb["smart_status"]["passed"] = false;
        jglb["smart_status"]["scsi"]["asc"] = asc;
        jglb["smart_status"]["scsi"]["ascq"] = ascq;
        jglb["smart_status"]["scsi"]["ie_string"] = cp;
    }
    else if (gIecMPage) {
        jout("SMART Health Status: OK\n");
        jglb["smart_status"]["passed"] = true;
    }

    if (attribs && !gTempLPage) {
        if (255 == currenttemp)
            pout("Current Drive Temperature:     <not available>\n");
        else {
            jout("Current Drive Temperature:     %d C\n", currenttemp);
            jglb["temperature"]["current"] = currenttemp;
        }
        if (255 == triptemp)
            pout("Drive Trip Temperature:        <not available>\n");
        else {
            jout("Drive Trip Temperature:        %d C\n", triptemp);
            jglb["temperature"]["drive_trip"] = triptemp;
        }
    }
    pout("\n");
    return err;
}


// Returns number of logged errors or zero if none or -1 if fetching
// TapeAlerts fails
static const char * const severities = "CWI";

static int
scsiPrintActiveTapeAlerts(scsi_device * device, int peripheral_type,
                          bool from_health)
{
    unsigned short pagelength;
    unsigned short parametercode;
    int i, k, j, m, err;
    const char *s;
    const char *ts;
    int failures = 0;
    const char * pad = from_health ? "" : "  ";
    static const char * const tapealert_s = "scsi_tapealert";

    jout("\nTapeAlert %s:\n", lp_s);
    print_on();
    if ((err = scsiLogSense(device, TAPE_ALERTS_LPAGE, 0, gBuf,
                        LOG_RESP_TAPE_ALERT_LEN, LOG_RESP_TAPE_ALERT_LEN))) {
        pout("%s Failed [%s]\n", __func__, scsiErrString(err));
        print_off();
        return -1;
    }
    if (gBuf[0] != 0x2e) {
        pout("%sTapeAlerts %s Failed\n", pad, logSenStr);
        print_off();
        return -1;
    }
    pagelength = sg_get_unaligned_be16(gBuf + 2);

    json::ref jref = jglb[tapealert_s]["status"];
    for (s=severities, k = 0, j = 0; *s; s++, ++k) {
        for (i = 4, m = 0; i < pagelength; i += 5, ++k, ++m) {
            parametercode = sg_get_unaligned_be16(gBuf + i);

            if (gBuf[i + 4]) {
                ts = SCSI_PT_MEDIUM_CHANGER == peripheral_type ?
                    scsiTapeAlertsChangerDevice(parametercode) :
                    scsiTapeAlertsTapeDevice(parametercode);
                if (*ts == *s) {
                    if (!failures)
                        jout("%sTapeAlert Errors (C=Critical, W=Warning, "
                             "I=Informational):\n", pad);
                    jout("%s[0x%02x] %s\n", pad, parametercode, ts);
                    jref[j]["descriptor_idx"] = m + 1;
                    jref[j]["parameter_code"] = parametercode;
                    jref[j]["string"] = ts;
                    ++j;
                    failures += 1;
                }
            }
        }
    }
    print_off();

    if (! failures) {
        jout("%sTapeAlert: OK\n", pad);
        jglb[tapealert_s]["status"] = "Good";
    }

    return failures;
}

static void
scsiGetStartStopData(scsi_device * device)
{
    int err, len, k, extra;
    unsigned char * ucp;
    char b[32];
    const char * q;
    static const char * jname = "scsi_start_stop_cycle_counter";

    if ((err = scsiLogSense(device, STARTSTOP_CYCLE_COUNTER_LPAGE, 0, gBuf,
                            LOG_RESP_LEN, 0))) {
        print_on();
        pout("%s Failed [%s]\n", __func__, scsiErrString(err));
        print_off();
        return;
    }
    if ((gBuf[0] & 0x3f) != STARTSTOP_CYCLE_COUNTER_LPAGE) {
        print_on();
        pout("StartStop %s Failed, page mismatch\n", logSenStr);
        print_off();
        return;
    }
    len = sg_get_unaligned_be16(gBuf + 2);
    ucp = gBuf + 4;
    for (k = len; k > 0; k -= extra, ucp += extra) {
        if (k < 3) {
            print_on();
            pout("StartStop %s: short\n", logSenRspStr);
            print_off();
            return;
        }
        extra = ucp[3] + 4;
        int pc = sg_get_unaligned_be16(ucp + 0);
        uint32_t u = (extra > 7) ? sg_get_unaligned_be32(ucp + 4) : 0;
        bool is_all_ffs = (extra > 7) ? all_ffs(ucp + 4, 4) : false;
        switch (pc) {
        case 1:
            if (10 == extra) {
                jout("Manufactured in week %.2s of year %.4s\n", ucp + 8,
                     ucp + 4);
                snprintf(b, sizeof(b), "%.4s", ucp + 4);
                jglb[jname]["year_of_manufacture"] = b;
                snprintf(b, sizeof(b), "%.2s", ucp + 8);
                jglb[jname]["week_of_manufacture"] = b;
            }
            break;
        case 2:
            /* ignore Accounting date */
            break;
        case 3:
            if ((extra > 7) && (! is_all_ffs)) {
                q = "Specified cycle count over device lifetime";
                jout("%s:  %u\n", q, u);
                jglb[jname][json::str2key(q)] = u;
            }
            break;
        case 4:
            if ((extra > 7) && (! is_all_ffs)) {
                q = "Accumulated start-stop cycles";
                jout("%s:  %u\n", q, u);
                jglb[jname][json::str2key(q)] = u;
            }
            break;
        case 5:
            if ((extra > 7) && (! is_all_ffs)) {
                q = "Specified load-unload count over device lifetime";
                jout("%s:  %u\n", q, u);
                jglb[jname][json::str2key(q)] = u;
            }
            break;
        case 6:
            if ((extra > 7) && (! is_all_ffs)) {
                q = "Accumulated load-unload cycles";
                jout("%s:  %u\n", q, u);
                jglb[jname][json::str2key(q)] = u;
            }
            break;
        default:
            /* ignore */
            break;
        }
    }
}
/* PENDING_DEFECTS_SUBPG [0x15,0x1]  introduced: SBC-4 */
static void
scsiPrintPendingDefectsLPage(scsi_device * device)
{
    static const char * pDefStr = "Pending Defects";
    static const char * jname = "scsi_pending_defects";

    int err;
    if ((err = scsiLogSense(device, BACKGROUND_RESULTS_LPAGE,
                            PEND_DEFECTS_L_SPAGE, gBuf, LOG_RESP_LONG_LEN,
                            0))) {
        print_on();
        pout("%s Failed [%s]\n", __func__, scsiErrString(err));
        print_off();
        return;
    }
    if (((gBuf[0] & 0x3f) != BACKGROUND_RESULTS_LPAGE) &&
        (gBuf[1] != PEND_DEFECTS_L_SPAGE)) {
        print_on();
        pout("%s %s, page mismatch\n", pDefStr, logSenRspStr);
        print_off();
        return;
    }
    int num = sg_get_unaligned_be16(gBuf + 2);
    if (num > LOG_RESP_LONG_LEN) {
        print_on();
        pout("%s %s too long\n", pDefStr, logSenRspStr);
        print_off();
        return;
    }
    const uint8_t * bp = gBuf + 4;
    while (num > 3) {
        int pc = sg_get_unaligned_be16(bp + 0);
        int pl = bp[3] + 4;
        uint32_t count, poh;
        uint64_t lba;

        switch (pc) {
        case 0x0:
            jout("  Pending defect count:");
            if ((pl < 8) || (num < 8)) {
                print_on();
                pout("%s truncated descriptor\n", pDefStr);
                print_off();
                return;
            }
            count = sg_get_unaligned_be32(bp + 4);
            jglb[jname]["count"] = count;
            if (0 == count)
                jout("0 %s\n", pDefStr);
            else if (1 == count)
                jout("1 Pending Defect, LBA and accumulated_power_on_hours "
                     "follow\n");
            else
                jout("%u %s: index, LBA and accumulated_power_on_hours "
                     "follow\n", count, pDefStr);
            break;
        default:
            if ((pl < 16) || (num < 16)) {
                print_on();
                pout("%s truncated descriptor\n", pDefStr);
                print_off();
                return;
            }
            poh = sg_get_unaligned_be32(bp + 4);
            lba = sg_get_unaligned_be64(bp + 8);
            jout("  %4d:  0x%-16" PRIx64 ",  %5u\n", pc, lba, poh);
            {
                json::ref jref = jglb[jname]["table"][pc];

                jref["lba"] = lba;
                jref["accum_power_on_hours"] = poh;
            }
            break;
        }
        num -= pl;
        bp += pl;
    }
}

static void
scsiPrintGrownDefectListLen(scsi_device * device, bool prefer12)
{
    bool got_rd12;
    int err, dl_format;
    unsigned int dl_len, div;
    static const char * hname = "Read defect list";

    memset(gBuf, 0, 8);
    if (prefer12) {
        err = scsiReadDefect12(device, 0 /* req_plist */, 1 /* req_glist */,
                               4 /* format: bytes from index */,
                               0 /* addr desc index */, gBuf, 8);
        got_rd12 = (0 == err);
        if (err) {
            if (scsi_debugmode > 0) {
                print_on();
                pout("%s (12) Failed: %s\n", hname, scsiErrString(err));
                print_off();
            }
        }
    } else {    /* still try Read Defect(12) first, if not found try RD(10) */
        err = scsiReadDefect12(device, 0 /* req_plist */, 1 /* req_glist */,
                               4 /* format: bytes from index */,
                               0 /* addr desc index */, gBuf, 8);
        if (2 == err) { /* command not supported */
            err = scsiReadDefect10(device, 0 /* req_plist */,
                                   1 /* req_glist */,
                                   4 /* format: bytes from index */, gBuf, 4);
            if (2 == err) { /* command not supported */
                if (scsi_debugmode > 0) {
                    print_on();
                    pout("%s (10) Failed: %s\n", hname, scsiErrString(err));
                    print_off();
                }
                return;
            } else if (101 == err)    /* Defect list not found, leave quietly */
                return;
            else {
                if (scsi_debugmode > 0) {
                    print_on();
                    pout("%s (12) Failed: %s\n", hname, scsiErrString(err));
                    print_off();
                }
                return;
            }
        } else
            got_rd12 = true;
    }

    if (got_rd12) {
        int generation = sg_get_unaligned_be16(gBuf + 2);
        if ((generation > 1) && (scsi_debugmode > 0)) {
            print_on();
            pout("%s (12): generation=%d\n", hname, generation);
            print_off();
        }
        dl_len = sg_get_unaligned_be32(gBuf + 4);
    } else
        dl_len = sg_get_unaligned_be16(gBuf + 2);
    if (0x8 != (gBuf[1] & 0x18)) {
        print_on();
        pout("%s: asked for grown list but didn't get it\n", hname);
        print_off();
        return;
    }
    div = 0;
    dl_format = (gBuf[1] & 0x7);
    switch (dl_format) {
        case 0:     /* short block */
            div = 4;
            break;
        case 1:     /* extended bytes from index */
        case 2:     /* extended physical sector */
            /* extended = 1; # might use in future */
            div = 8;
            break;
        case 3:     /* long block */
        case 4:     /* bytes from index */
        case 5:     /* physical sector */
            div = 8;
            break;
        case 6:     /* vendor specific */
            break;
        default:
            print_on();
            pout("defect list format %d unknown\n", dl_format);
            print_off();
            break;
    }
    if (0 == dl_len) {
        jout("Elements in grown defect list: 0\n\n");
        jglb["scsi_grown_defect_list"] = 0;
    }
    else {
        if (0 == div)
            pout("Grown defect list length=%u bytes [unknown "
                 "number of elements]\n\n", dl_len);
        else {
            jout("Elements in grown defect list: %u\n\n", dl_len / div);
            jglb["scsi_grown_defect_list"] = dl_len / div;
        }
    }
}

static uint64_t
variableLengthIntegerParam(const unsigned char * ucp)
{
    static const size_t sz_u64 = (int)sizeof(uint64_t);
    unsigned int u = ucp[3];
    const unsigned char * xp = ucp + 4;

    if (u > sz_u64) {
        xp += (u - sz_u64);
        u = sz_u64;
    }
    return sg_get_unaligned_be(u, xp + 0);
}

static void
scsiPrintSeagateCacheLPage(scsi_device * device)
{
    int num, pl, pc, err, len;
    unsigned char * ucp;
    static const char * seaCacStr = "Seagate Cache";

    if ((err = scsiLogSense(device, SEAGATE_CACHE_LPAGE, 0, gBuf,
                            LOG_RESP_LEN, 0))) {
        if (scsi_debugmode > 0) {
            print_on();
            pout("%s %s Failed: %s\n", seaCacStr, logSenStr,
                 scsiErrString(err));
            print_off();
        }
        return;
    }
    if ((gBuf[0] & 0x3f) != SEAGATE_CACHE_LPAGE) {
        if (scsi_debugmode > 0) {
            print_on();
            pout("%s %s, page mismatch\n", seaCacStr, logSenRspStr);
            print_off();
        }
        return;
    }
    len = sg_get_unaligned_be16(gBuf + 2) + 4;
    num = len - 4;
    ucp = &gBuf[0] + 4;
    while (num > 3) {
        pc = sg_get_unaligned_be16(ucp + 0);
        pl = ucp[3] + 4;
        switch (pc) {
        case 0: case 1: case 2: case 3: case 4:
            break;
        default:
            if (scsi_debugmode > 0) {
                print_on();
                pout("Vendor (%s) lpage has unexpected parameter, skip\n",
                     seaCacStr);
                print_off();
            }
            return;
        }
        num -= pl;
        ucp += pl;
    }
    pout("Vendor (%s) information\n", seaCacStr);
    num = len - 4;
    ucp = &gBuf[0] + 4;
    while (num > 3) {
        pc = sg_get_unaligned_be16(ucp + 0);
        pl = ucp[3] + 4;
        switch (pc) {
        case 0: pout("  Blocks sent to initiator"); break;
        case 1: pout("  Blocks received from initiator"); break;
        case 2: pout("  Blocks read from cache and sent to initiator"); break;
        case 3: pout("  Number of read and write commands whose size "
                       "<= segment size"); break;
        case 4: pout("  Number of read and write commands whose size "
                       "> segment size"); break;
        default: pout("  Unknown Seagate parameter code [0x%x]", pc); break;
        }
        pout(" = %" PRIu64 "\n", variableLengthIntegerParam(ucp));
        num -= pl;
        ucp += pl;
    }
    pout("\n");
}

static void
scsiPrintSeagateFactoryLPage(scsi_device * device)
{
    int num, pl, pc, len, err, good, bad;
    unsigned char * ucp;
    uint64_t ull;

    if ((err = scsiLogSense(device, SEAGATE_FACTORY_LPAGE, 0, gBuf,
                            LOG_RESP_LEN, 0))) {
        if (scsi_debugmode > 0) {
            print_on();
            pout("%s Failed [%s]\n", __func__, scsiErrString(err));
            print_off();
        }
        return;
    }
    if ((gBuf[0] & 0x3f) != SEAGATE_FACTORY_LPAGE) {
        if (scsi_debugmode > 0) {
            print_on();
            pout("Seagate/Hitachi Factory %s, page mismatch\n", logSenRspStr);
            print_off();
        }
        return;
    }
    len = sg_get_unaligned_be16(gBuf + 2) + 4;
    num = len - 4;
    ucp = &gBuf[0] + 4;
    good = 0;
    bad = 0;
    while (num > 3) {
        pc = sg_get_unaligned_be16(ucp + 0);
        pl = ucp[3] + 4;
        switch (pc) {
        case 0: case 8:
            ++good;
            break;
        default:
            ++bad;
            break;
        }
        num -= pl;
        ucp += pl;
    }
    if ((good < 2) || (bad > 4)) {  /* heuristic */
        if (scsi_debugmode > 0) {
            print_on();
            pout("\nVendor (Seagate/Hitachi) factory lpage has too many "
                 "unexpected parameters, skip\n");
            print_off();
        }
        return;
    }
    pout("Vendor (Seagate/Hitachi) factory information\n");
    num = len - 4;
    ucp = &gBuf[0] + 4;
    while (num > 3) {
        pc = sg_get_unaligned_be16(ucp + 0);
        pl = ucp[3] + 4;
        good = 0;
        switch (pc) {
        case 0: jout("  number of hours powered up");
            good = 1;
            break;
        case 8: pout("  number of minutes until next internal SMART test");
            good = 1;
            break;
        default:
            if (scsi_debugmode > 0) {
                print_on();
                pout("Vendor (Seagate/Hitachi) factory lpage: "
                     "unknown parameter code [0x%x]\n", pc);
                print_off();
            }
            break;
        }
        if (good) {
            ull = variableLengthIntegerParam(ucp);
            if (0 == pc) {
                jout(" = %.2f\n", ull / 60.0 );
                jglb["power_on_time"]["hours"] = ull / 60;
                jglb["power_on_time"]["minutes"] = ull % 60;
            }
            else
                pout(" = %" PRIu64 "\n", ull);
        }
        num -= pl;
        ucp += pl;
    }
    pout("\n");
}

static void
scsiPrintErrorCounterLog(scsi_device * device)
{
    struct scsiErrorCounter errCounterArr[3];
    struct scsiErrorCounter * ecp;
    int found[3] = {0, 0, 0};

    if (gReadECounterLPage && (0 == scsiLogSense(device,
                READ_ERROR_COUNTER_LPAGE, 0, gBuf, LOG_RESP_LEN, 0))) {
        scsiDecodeErrCounterPage(gBuf, &errCounterArr[0], LOG_RESP_LEN);
        found[0] = 1;
    }
    if (gWriteECounterLPage && (0 == scsiLogSense(device,
                WRITE_ERROR_COUNTER_LPAGE, 0, gBuf, LOG_RESP_LEN, 0))) {
        scsiDecodeErrCounterPage(gBuf, &errCounterArr[1], LOG_RESP_LEN);
        found[1] = 1;
    }
    if (gVerifyECounterLPage && (0 == scsiLogSense(device,
                VERIFY_ERROR_COUNTER_LPAGE, 0, gBuf, LOG_RESP_LEN, 0))) {
        scsiDecodeErrCounterPage(gBuf, &errCounterArr[2], LOG_RESP_LEN);
        ecp = &errCounterArr[2];
        for (int k = 0; k < 7; ++k) {
            if (ecp->gotPC[k] && ecp->counter[k]) {
                found[2] = 1;
                break;
            }
        }
    }
    if (found[0] || found[1] || found[2]) {
        pout("Error counter log:\n");
        pout("           Errors Corrected by           Total   "
             "Correction     Gigabytes    Total\n");
        pout("               ECC          rereads/    errors   "
             "algorithm      processed    uncorrected\n");
        pout("           fast | delayed   rewrites  corrected  "
             "invocations   [10^9 bytes]  errors\n");

        json::ref jref = jglb["scsi_error_counter_log"];
        for (int k = 0; k < 3; ++k) {
            if (! found[k])
                continue;
            ecp = &errCounterArr[k];
            static const char * const pageNames[3] =
                                 {"read:   ", "write:  ", "verify: "};
            static const char * jpageNames[3] =
                                 {"read", "write", "verify"};
            jout("%s%8" PRIu64 " %8" PRIu64 "  %8" PRIu64 "  %8" PRIu64
                 "   %8" PRIu64, pageNames[k], ecp->counter[0],
                 ecp->counter[1], ecp->counter[2], ecp->counter[3],
                 ecp->counter[4]);
            double processed_gb = ecp->counter[5] / 1000000000.0;
            jout("   %12.3f    %8" PRIu64 "\n", processed_gb,
                 ecp->counter[6]);
            // Error counter log info
            jref[jpageNames[k]]["errors_corrected_by_eccfast"] = ecp->counter[0];
            jref[jpageNames[k]]["errors_corrected_by_eccdelayed"] = ecp->counter[1];
            jref[jpageNames[k]]["errors_corrected_by_rereads_rewrites"] = ecp->counter[2];
            jref[jpageNames[k]]["total_errors_corrected"] = ecp->counter[3];
            jref[jpageNames[k]]["correction_algorithm_invocations"] = ecp->counter[4];
            jref[jpageNames[k]]["gigabytes_processed"] = strprintf("%.3f", processed_gb);
            jref[jpageNames[k]]["total_uncorrected_errors"] = ecp->counter[6];
        }
    }
    else
        pout("Error Counter logging not supported\n");
    if (gNonMediumELPage && (0 == scsiLogSense(device,
                NON_MEDIUM_ERROR_LPAGE, 0, gBuf, LOG_RESP_LEN, 0))) {
        struct scsiNonMediumError nme;
        scsiDecodeNonMediumErrPage(gBuf, &nme, LOG_RESP_LEN);
        if (nme.gotPC0)
            pout("\nNon-medium error count: %8" PRIu64 "\n", nme.counterPC0);
        if (nme.gotTFE_H)
            pout("Track following error count [Hitachi]: %8" PRIu64 "\n",
                 nme.counterTFE_H);
        if (nme.gotPE_H)
            pout("Positioning error count [Hitachi]: %8" PRIu64 "\n",
                 nme.counterPE_H);
    }
    if (gLastNErrorEvLPage &&
        (0 == scsiLogSense(device, LAST_N_ERROR_EVENTS_LPAGE, 0, gBuf,
                           LOG_RESP_LONG_LEN, 0))) {
        int num = sg_get_unaligned_be16(gBuf + 2) + 4;
        int truncated = (num > LOG_RESP_LONG_LEN) ? num : 0;
        if (truncated)
            num = LOG_RESP_LONG_LEN;
        unsigned char * ucp = gBuf + 4;
        num -= 4;
        if (num < 4)
            pout("\nNo error events logged\n");
        else {
            pout("\nLast n error events %s\n", lp_s);
            for (int k = num, pl; k > 0; k -= pl, ucp += pl) {
                if (k < 3) {
                    pout("  <<short Last n error events %s>>\n", lp_s);
                    break;
                }
                pl = ucp[3] + 4;
                int pc = sg_get_unaligned_be16(ucp + 0);
                if (pl > 4) {
                    if ((ucp[2] & 0x1) && (ucp[2] & 0x2)) {
                        pout("  Error event %d:\n", pc);
                        pout("    [binary]:\n");
                        dStrHex((const uint8_t *)ucp + 4, pl - 4, 1);
                    } else if (ucp[2] & 0x1) {
                        pout("  Error event %d:\n", pc);
                        pout("    %.*s\n", pl - 4, (const char *)(ucp + 4));
                    } else {
                        if (scsi_debugmode > 0) {
                            pout("  Error event %d:\n", pc);
                            pout("    [data counter??]:\n");
                            dStrHex((const uint8_t *)ucp + 4, pl - 4, 1);
                        }
                    }
                }
            }
            if (truncated)
                pout(" >>>> log truncated, fetched %d of %d available "
                     "bytes\n", LOG_RESP_LONG_LEN, truncated);
        }
    }
    pout("\n");
}

static const char * self_test_code[] = {
        "Default         ",
        "Background short",
        "Background long ",
        "Reserved(3)     ",
        "Abort background",
        "Foreground short",
        "Foreground long ",
        "Reserved(7)     "
};

static const char * self_test_result[] = {
        "Completed                ",
        "Aborted (by user command)",
        "Aborted (device reset ?) ",
        "Unknown error, incomplete",
        "Completed, segment failed",
        "Failed in first segment  ",
        "Failed in second segment ",
        "Failed in segment",            /* special handling for result 7 */
        "Reserved(8)              ",
        "Reserved(9)              ",
        "Reserved(10)             ",
        "Reserved(11)             ",
        "Reserved(12)             ",
        "Reserved(13)             ",
        "Reserved(14)             ",
        "Self test in progress ..."
};

// See SCSI Primary Commands - 3 (SPC-3) rev 23 (draft) section 7.2.10 .
// Returns 0 if ok else FAIL* bitmask. Note that if any of the most recent
// 20 self tests fail (result code 3 to 7 inclusive) then FAILLOG and/or
// FAILSMART is returned.
static int
scsiPrintSelfTest(scsi_device * device)
{
    bool noheader = true;
    int num, k, err, durationSec;
    int retval = 0;
    uint8_t * ucp;
    struct scsi_sense_disect sense_info;
    static const char * hname = "Self-test";
    static const char * fixup_stres7 = " -->    ";  /* only for non-json */

    // check if test is running
    if (!scsiRequestSense(device, &sense_info) &&
                        (sense_info.asc == 0x04 && sense_info.ascq == 0x09 &&
                        sense_info.progress != -1)) {
        pout("%s execution status:\t\t%d%% of test remaining\n", hname,
             100 - ((sense_info.progress * 100) / 65535));
    }

    if ((err = scsiLogSense(device, SELFTEST_RESULTS_LPAGE, 0, gBuf,
                            LOG_RESP_SELF_TEST_LEN, 0))) {
        print_on();
        pout("%s: Failed [%s]\n", __func__, scsiErrString(err));
        print_off();
        return FAILSMART;
    }
    if ((gBuf[0] & 0x3f) != SELFTEST_RESULTS_LPAGE) {
        print_on();
        pout("%s %s, page mismatch\n", hname, logSenRspStr);
        print_off();
        return FAILSMART;
    }
    // compute page length
    num = sg_get_unaligned_be16(gBuf + 2);
    // Log sense page length 0x190 bytes
    if (num != 0x190) {
        print_on();
        pout("%s %s length is 0x%x not 0x190 bytes\n", hname, logSenStr, num);
        print_off();
        return FAILSMART;
    }
    // loop through the twenty possible entries
    for (k = 0, ucp = gBuf + 4; k < 20; ++k, ucp += 20 ) {
        // timestamp in power-on hours (or zero if test in progress)
        unsigned int poh = sg_get_unaligned_be16(ucp + 6);
        unsigned int u, tr;
        char st[32];

        snprintf(st, sizeof(st), "scsi_self_test_%d", k);
        // The spec says "all 20 bytes will be zero if no test" but
        // DG has found otherwise.  So this is a heuristic.
        if ((0 == poh) && (0 == ucp[4]))
            break;

        // only print header if needed
        if (noheader) {
            jout("SMART %s log\n", hname);
            jout("Num  Test              Status                 segment  "
                   "LifeTime  LBA_first_err [SK ASC ASQ]\n");
            jout("     Description                              number   "
                   "(hours)\n");
            noheader = false;
        }

        // print parameter code (test number) & self-test code text
        u = (ucp[4] >> 5) & 0x7;
        jout("#%2d  %s", sg_get_unaligned_be16(ucp + 0), self_test_code[u]);
        jglb[st]["code"]["value"] = u;
        jglb[st]["code"]["string"] = rtrim(self_test_code[u]);

        // check the self-test result nibble, using the self-test results
        // field table from T10/1416-D (SPC-3) Rev. 23, section 7.2.10:
        tr = ucp[4] & 0xf;
        switch (tr) {
        case 0x3:
            // an unknown error occurred while the device server
            // was processing the self-test and the device server
            // was unable to complete the self-test
            retval|=FAILSMART;
            break;
        case 0x4:
            // the self-test completed with a failure in a test
            // segment, and the test segment that failed is not
            // known
            retval|=FAILLOG;
            break;
        case 0x5:
            // the first segment of the self-test failed
            retval|=FAILLOG;
            break;
        case 0x6:
            // the second segment of the self-test failed
            retval|=FAILLOG;
            break;
        case 0x7:
            // another segment of the self-test failed and which
            // test is indicated by the contents of the SELF-TEST
            // NUMBER field
            retval|=FAILLOG;
            break;
        default:
            break;
        }
        jout("  %s%s", self_test_result[tr], (tr == 7 ? fixup_stres7 : ""));
        jglb[st]["result"]["value"] = tr;
        jglb[st]["result"]["string"] = rtrim(self_test_result[tr]);

        // self-test number identifies test that failed and consists
        // of either the number of the segment that failed during
        // the test, or the number of the test that failed and the
        // number of the segment in which the test was run, using a
        // vendor-specific method of putting both numbers into a
        // single byte.
        u = ucp[5];
        if (u > 0) {
            jout(" %3u",  u);
            jglb[st]["failed_segment"]["value"] = u;
            jglb[st]["failed_segment"]["aka"] = "self_test_number";
        } else
            jout("   -");

        // print time that the self-test was completed
        if (poh==0 && tr==0xf) {
        // self-test in progress
            jout("     NOW");
            jglb[st]["self_test_in_progress"] = true;
        } else {
            jout("   %5d", poh);
            jglb[st]["power_on_time"]["hours"] = poh;
            jglb[st]["power_on_time"]["aka"] = "accumulated_power_on_hours";
        }

        // construct 8-byte integer address of first failure
        uint64_t ull = sg_get_unaligned_be64(ucp + 8);
        bool is_all_ffs = all_ffs(ucp + 8, 8);
        // print Address of First Failure, if sensible
        if ((! is_all_ffs) && (tr > 0) && (tr < 0xf)) {
            char buff[32];

            // was hex but change to decimal to conform with ATA
            snprintf(buff, sizeof(buff), "%" PRIu64, ull);
            // snprintf(buff, sizeof(buff), "0x%" PRIx64, ull);
            jout("%18s", buff);
            jglb[st]["lba_first_failure"]["value"] = ull;
            jglb[st]["lba_first_failure"]["aka"] = "address_of_first_failure";
        } else
            jout("                 -");

        // if sense key nonzero, then print it, along with
        // additional sense code and additional sense code qualifier
        if (ucp[16] & 0xf) {
            char b[48];

            jout(" [0x%x 0x%x 0x%x]\n", ucp[16] & 0xf, ucp[17], ucp[18]);
            u = ucp[16] & 0xf;
            jglb[st]["sense_key"]["value"] = u;
            jglb[st]["sense_key"]["string"] =
                        scsi_get_sense_key_str(u, sizeof(b), b);
            jglb[st]["asc"] = ucp[17];
            jglb[st]["ascq"] = ucp[18];
            jglb[st]["vendor_specific"] = ucp[19];
        } else
            pout(" [-   -    -]\n");
    }

    // if header never printed, then there was no output
    if (noheader)
        jout("No %ss have been logged\n", hname);
    else if ((0 == scsiFetchExtendedSelfTestTime(device, &durationSec,
                        modese_len)) && (durationSec > 0)) {
        if (durationSec > 14400)
            jout("\nLong (extended) %s duration: %d seconds "
                 "[%.1f hours]\n", hname, durationSec, durationSec / 3600.0);
        else
            jout("\nLong (extended) %s duration: %d seconds "
                 "[%.1f minutes]\n", hname, durationSec, durationSec / 60.0);
        jglb["scsi_extended_self_test_seconds"] = durationSec;
    }
    jout("\n");
    return retval;
}

static const char * bms_status[] = {
    "no scans active",
    "scan is active",
    "pre-scan is active",
    "halted due to fatal error",
    "halted due to a vendor specific pattern of error",
    "halted due to medium formatted without P-List",
    "halted - vendor specific cause",
    "halted due to temperature out of range",
    "waiting until BMS interval timer expires", /* 8 */
};

static const char * reassign_status[] = {
    "Reserved [0x0]",
    "Require Write or Reassign Blocks command",
    "Successfully reassigned",
    "Reserved [0x3]",
    "Reassignment by disk failed",
    "Recovered via rewrite in-place",
    "Reassigned by app, has valid data",
    "Reassigned by app, has no valid data",
    "Unsuccessfully reassigned by app", /* 8 */
};

// See SCSI Block Commands - 3 (SBC-3) rev 6 (draft) section 6.2.2 .
// Returns 0 if ok else FAIL* bitmask. Note can have a status entry
// and up to 2048 events (although would hope to have less). May set
// FAILLOG if serious errors detected (in the future).
// When only_pow_time is true only print "Accumulated power on time"
// data, if available.
static int
scsiPrintBackgroundResults(scsi_device * device, bool only_pow_time)
{
    bool noheader = true;
    bool firstresult = true;
    int num, j, m, err, truncated;
    int retval = 0;
    unsigned int u;
    uint64_t lba;
    uint8_t * ucp;
    char b[48];
    char res_s[32];
    static const char * hname = "Background scan results";
    static const char * jname = "scsi_background_scan";

    if ((err = scsiLogSense(device, BACKGROUND_RESULTS_LPAGE, 0, gBuf,
                            LOG_RESP_LONG_LEN, 0))) {
        print_on();
        pout("%s Failed [%s]\n", __func__, scsiErrString(err));
        print_off();
        return FAILSMART;
    }
    if ((gBuf[0] & 0x3f) != BACKGROUND_RESULTS_LPAGE) {
        print_on();
        pout("%s %s, page mismatch\n", hname, logSenRspStr);
        print_off();
        return FAILSMART;
    }
    // compute page length
    num = sg_get_unaligned_be16(gBuf + 2) + 4;
    if (num < 20) {
        if (! only_pow_time) {
            print_on();
            pout("%s %s length is %d, no scan status\n", hname, logSenStr,
                 num);
            print_off();
        }
        return FAILSMART;
    }
    truncated = (num > LOG_RESP_LONG_LEN) ? num : 0;
    if (truncated)
        num = LOG_RESP_LONG_LEN;
    ucp = gBuf + 4;
    num -= 4;
    while (num > 3) {
        int pc = sg_get_unaligned_be16(ucp + 0);
        // pcb = ucp[2];
        int pl = ucp[3] + 4;
        switch (pc) {
        case 0:
            if (noheader) {
                noheader = false;
                if (! only_pow_time)
                    jout("%s log\n", hname);
            }
            if (! only_pow_time)
                jout("  Status: ");
            if ((pl < 16) || (num < 16)) {
                if (! only_pow_time)
                    jout("\n");
                break;
            }
            j = ucp[9];
            if (! only_pow_time) {
                if (j < (int)ARRAY_SIZE(bms_status)) {
                    jout("%s\n", bms_status[j]);
                    jglb[jname]["status"]["value"] = j;
                    jglb[jname]["status"]["string"] = bms_status[j];
                } else {
                    jout("unknown [0x%x] background scan status value\n", j);
                    jglb[jname]["status"]["value"] = j;
                }
            }
            j = sg_get_unaligned_be32(ucp + 4);
            jout("%sAccumulated power on time, hours:minutes %d:%02d",
                 (only_pow_time ? "" : "    "), (j / 60), (j % 60));
            if (only_pow_time)
                jout("\n");
            else
                jout(" [%d minutes]\n", j);
            jglb["power_on_time"]["hours"] = j / 60;
            jglb["power_on_time"]["minutes"] = j % 60;
            if (only_pow_time)
                break;
            u = sg_get_unaligned_be16(ucp + 10);
            jout("    Number of background scans performed: %u,  ", u);
            jglb[jname]["status"]["number_scans_performed"] = u;
            u = sg_get_unaligned_be16(ucp + 12);
            snprintf(b, sizeof(b), "%.2f%%", (double)u * 100.0 / 65536.0);
            jout("scan progress: %s\n", b);
            jglb[jname]["status"]["scan_progress"] = b;
            u = sg_get_unaligned_be16(ucp + 14);
            jout("    Number of background medium scans performed: %d\n", u);
            jglb[jname]["status"]["number_medium_scans_performed"] = u;
            break;
        default:
            if (noheader) {
                noheader = false;
                if (! only_pow_time)
                    jout("\n%s log\n", hname);
            }
            if (only_pow_time)
                break;
            if (firstresult) {
                firstresult = 0;
                jout("\n   #  when        lba(hex)    [sk,asc,ascq]    "
                     "reassign_status\n");
            }
            snprintf(res_s, sizeof(res_s), "result_%d", pc);
            jout(" %3d ", pc);
            jglb[jname][res_s]["parameter_code"] = pc;
            if ((pl < 24) || (num < 24)) {
                if (pl < 24)
                    jout("parameter length >= 24 expected, got %d\n", pl);
                break;
            }
            u = sg_get_unaligned_be32(ucp + 4);
            jout("%4u:%02u  ", (u / 60), (u % 60));
            jglb[jname][res_s]["accumulated_power_on"]["minutes"] = u;
            for (m = 0; m < 8; ++m)
                jout("%02x", ucp[16 + m]);
            lba = sg_get_unaligned_be64(ucp + 16);
            jglb[jname][res_s]["lba"] = lba;
            u = ucp[8] & 0xf;
            jout("  [%x,%x,%x]   ", u, ucp[9], ucp[10]);
            jglb[jname][res_s]["sense_key"]["value"] = u;
            jglb[jname][res_s]["sense_key"]["string"] =
                        scsi_get_sense_key_str(u, sizeof(b), b);
            jglb[jname][res_s]["asc"] = ucp[9];
            jglb[jname][res_s]["ascq"] = ucp[10];
            u = (ucp[8] >> 4) & 0xf;
            if (u < ARRAY_SIZE(reassign_status)) {
                jout("%s\n", reassign_status[u]);
                jglb[jname][res_s]["reassign_status"]["value"] = u;
                jglb[jname][res_s]["reassign_status"]["string"] =
                                                        reassign_status[u];
            } else {
                jout("Reassign status: reserved [0x%x]\n", u);
                jglb[jname][res_s]["reassign_status"]["value"] = u;
            }
            break;
        }
        num -= pl;
        ucp += pl;
    }
    if (truncated && (! only_pow_time))
        jout(" >>>> log truncated, fetched %d of %d available "
             "bytes\n", LOG_RESP_LONG_LEN, truncated);
#if 0
    if (! only_pow_time)
        jout("\n");
#endif
    return retval;
}

static int64_t
scsiGetTimeUnitInNano(const uint8_t * ucp, int num, uint16_t ti_pc) 
{
    uint16_t loop_pc, pl;
    uint32_t a_exp, a_int, casc;
    int64_t res = -1;

    while (num > 3) {
        loop_pc = sg_get_unaligned_be16(ucp + 0);
        pl = ucp[3] + 4;

        if (loop_pc == ti_pc) {
            /* assume this pc corresponds to Time Interval param */
            if (pl < 12) {
                print_on();
                pout("%s Time interval log parameter too short (pl=%d)\n",
                     __func__, pl);
                print_off();
                return res;
            }
            a_exp = sg_get_unaligned_be32(ucp + 4);
            a_int = sg_get_unaligned_be32(ucp + 8);
            if (0 == a_int)
                return 0;
            else
                res = a_int;
            if (a_exp > 10)
                return -2;
            if (10 == a_exp) {
                if (a_int < 10)
                    return -2;
                return a_int / 10;
            }
            casc = 9 - a_exp;
            while (casc > 0) {
                res *= 10;
                --casc;
            }
            return res;
        }
        num -= pl;
        ucp += pl;
    }
    return res;
}

static void
scsiPrintTimeUnitInNano(int leadin_spaces, uint64_t intervals,
                        int64_t timeUnitInNS)
{
    if ((intervals > 0) && (timeUnitInNS > 0)) {
        intervals *= timeUnitInNS;
        intervals /= 1000000; /* now in milliseconds */
        jout("%*cin seconds: %" PRIu64 ".%03" PRIu64 "\n",
             leadin_spaces, ' ', intervals / 1000, intervals % 1000);
        if (intervals > 3600000) {
            intervals /= 3600; /* now in 3.6 second units */
            jout("%*cin hours: %" PRIu64 ".%03" PRIu64 "\n",
                 leadin_spaces, ' ', intervals / 1000, intervals % 1000);
        }
    }
}

// See SCSI Primary Commands - 6 (SPC-6) General Statistics and Performance
// log page [lp: 0x19,0x0]
static int
scsiPrintGStatsPerf(scsi_device * device)
{
    int num, err, truncated;
    int retval = 0;
    int64_t timeUnitInNS;
    uint64_t ull;
    const char * ccp;
    uint8_t * ucp;
    // const char * q;
    json::ref jref = jglb["scsi_general_statistics_and_performance_log"];
    json::ref jref1 = jref["general_access"];
    json::ref jref2 = jref["idle_time"];
    json::ref jref3 = jref["time_interval"];
    json::ref jref4 = jref["fua_stats"];
    static const char * p1name = "General access statistics and performance";
    static const char * p2name = "Idle time";
    static const char * p3name = "Time interval";
    static const char * p4name = "Force Unit Access statistics and "
                                 "performance";

    jout("\n%s %s:\n", gsap_s, lp_s);
    if ((err = scsiLogSense(device, GEN_STATS_PERF_LPAGE, 0, gBuf,
                            LOG_RESP_LONG_LEN, 0))) {
        print_on();
        pout("%s: Failed [%s]\n", __func__, scsiErrString(err));
        print_off();
        return FAILSMART;
    }
    if ((gBuf[0] & 0x3f) != GEN_STATS_PERF_LPAGE) {
        print_on();
        pout("%s %s, page mismatch\n", gsap_s, logSenStr);
        print_off();
        return FAILSMART;
    }
    // compute page length
    num = sg_get_unaligned_be16(gBuf + 2) + 4;
    if (num < 12) {
        print_on();
        pout("%s %s length is %d, too short\n", gsap_s, logSenStr, num);
        print_off();
        return FAILSMART;
    }
    truncated = (num > LOG_RESP_LONG_LEN) ? num : 0;
    if (truncated)
        num = LOG_RESP_LONG_LEN;
    ucp = gBuf + 4;
    num -= 4;
    timeUnitInNS = scsiGetTimeUnitInNano(ucp, num, 0x3 /* Time Interval */);
    if (timeUnitInNS < 0) {
        if (scsi_debugmode > 1) {
            print_on();
            pout("%s unable to decode time unit [%d]\n", gsap_s,
                 (int)timeUnitInNS);
            print_off();
        }
        timeUnitInNS = 0;
    }

    while (num > 3) {
        int pc = sg_get_unaligned_be16(ucp + 0);
        // pcb = ucp[2];
        int pl = ucp[3] + 4;

        switch (pc) {
        case 1: /* General access statistics and performance log parameter */
            if (pl < 0x40 + 4) {
                print_on();
                pout("%s %s log parameter too short (pl=%d)\n", gsap_s,
                     p1name, pl);
                print_off();
                return FAILSMART;
            }
            jout("  %s:\n", p1name);
            ccp =  "Number of read commands";
            ull = sg_get_unaligned_be64(ucp + 4);
            jout("    %s: %" PRIu64 "\n", ccp, ull);
            jref1[ccp] = ull;
            ccp =  "Number of write commands";
            ull = sg_get_unaligned_be64(ucp + 12);
            jout("    %s: %" PRIu64 "\n", ccp, ull);
            jref1[ccp] = ull;
            ccp =  "number of logical blocks received";
            ull = sg_get_unaligned_be64(ucp + 20);
            jout("    %s: %" PRIu64 "\n", ccp, ull);
            jref1[ccp] = ull;
            ccp =  "number of logical blocks transmitted";
            ull = sg_get_unaligned_be64(ucp + 28);
            jout("    %s: %" PRIu64 "\n", ccp, ull);
            jref1[ccp] = ull;
            ccp = "read command processing intervals";
            ull = sg_get_unaligned_be64(ucp + 36);
            jout("    %s: %" PRIu64 "\n", ccp, ull);
            scsiPrintTimeUnitInNano(6, ull, timeUnitInNS);
            jref1[ccp] = ull;
            ccp = "write command processing intervals";
            ull = sg_get_unaligned_be64(ucp + 44);
            jout("    %s: %" PRIu64 "\n", ccp, ull);
            scsiPrintTimeUnitInNano(6, ull, timeUnitInNS);
            jref1[ccp] = ull;
            ccp = "weighted number of read commands plus write commands";
            ull = sg_get_unaligned_be64(ucp + 52);
            jout("    %s: %" PRIu64 "\n", ccp, ull);
            jref1[ccp] = ull;
            ccp = "weighted read command processing plus write command "
                  "processing";
            ull = sg_get_unaligned_be64(ucp + 60);
            scsiPrintTimeUnitInNano(6, ull, timeUnitInNS);
            jout("    %s: %" PRIu64 "\n", ccp, ull);
            jref1[ccp] = ull;
            break;
        case 2:     /* Idle time log parameter */
            if (pl < 0x8 + 4) {
                print_on();
                pout("%s %s log parameter too short (pl=%d)\n", gsap_s,
                     p2name, pl);
                print_off();
                return FAILSMART;
            }
            jout("  %s:\n", p2name);
            ccp = "Idle time intervals";
            ull = sg_get_unaligned_be64(ucp + 4);
            jout("    %s: %" PRIu64 "\n", ccp, ull);
            scsiPrintTimeUnitInNano(6, ull, timeUnitInNS);
            jref2[ccp] = ull;
            break;
        case 3:    /* Time interval log parameter (shared with other lpages */
            /* only produce JSON for this parameter */
            if (pl < 0x8 + 4) {
                print_on();
                pout("%s %s log parameter too short (pl=%d)\n", gsap_s,
                     p3name, pl);
                print_off();
                return FAILSMART;
            }
            ccp = "Exponent";
            ull = sg_get_unaligned_be32(ucp + 4);
            jref3[ccp] = ull;
            ccp = "Integer";
            ull = sg_get_unaligned_be32(ucp + 8);
            jref3[ccp] = ull;
            break;
        case 4:     /* FUA statistics and performance log parameter */
            if (pl < 0x40 + 4) {
                print_on();
                pout("%s %s log parameter too short (pl=%d)\n", gsap_s,
                     p4name, pl);
                print_off();
                return FAILSMART;
            }
            jout("  %s:\n", p4name);
            ccp = "Number of read FUA commands";
            ull = sg_get_unaligned_be64(ucp + 4);
            jout("    %s: %" PRIu64 "\n", ccp, ull);
            jref4[ccp] = ull;
            ccp = "Number of write FUA commands";
            ull = sg_get_unaligned_be64(ucp + 12);
            jout("    %s: %" PRIu64 "\n", ccp, ull);
            jref4[ccp] = ull;
            ccp = "Number of read FUA_NV commands";
            ull = sg_get_unaligned_be64(ucp + 20);
            jout("    %s: %" PRIu64 "\n", ccp, ull);
            jref4[ccp] = ull;
            ccp = "Number of write FUA_NV commands";
            ull = sg_get_unaligned_be64(ucp + 28);
            jout("    %s: %" PRIu64 "\n", ccp, ull);
            jref4[ccp] = ull;
            ccp = "Number of read FUA intervals";
            ull = sg_get_unaligned_be64(ucp + 36);
            jout("    %s: %" PRIu64 "\n", ccp, ull);
            scsiPrintTimeUnitInNano(6, ull, timeUnitInNS);
            jref4[ccp] = ull;
            ccp = "Number of write FUA intervals";
            ull = sg_get_unaligned_be64(ucp + 44);
            jout("    %s: %" PRIu64 "\n", ccp, ull);
            jref4[ccp] = ull;
            ccp = "Number of read FUA_NV intervals";
            ull = sg_get_unaligned_be64(ucp + 52);
            jout("    %s: %" PRIu64 "\n", ccp, ull);
            scsiPrintTimeUnitInNano(6, ull, timeUnitInNS);
            jref4[ccp] = ull;
            ccp = "Number of write FUA_NV intervals";
            ull = sg_get_unaligned_be64(ucp + 60);
            jout("    %s: %" PRIu64 "\n", ccp, ull);
            scsiPrintTimeUnitInNano(6, ull, timeUnitInNS);
            jref4[ccp] = ull;
            break;
        default:        /* ignore other parameter codes */
            break;
        }
        num -= pl;
        ucp += pl;
    }           /* end of long for loop */
    return retval;
}

// Print Solid state media log page.
// See SCSI Block Commands - 3 (SBC-3) rev 27 (draft) section 6.3.6 .
// Returns 0 if ok else FAIL* bitmask. Note can have a status entry
// and up to 2048 events (although would hope to have less). May set
// FAILLOG if serious errors detected (in the future).
static int
scsiPrintSSMedia(scsi_device * device)
{
    int num, err, truncated;
    int retval = 0;
    uint8_t * ucp;
    const char * q;

    if ((err = scsiLogSense(device, SS_MEDIA_LPAGE, 0, gBuf,
                            LOG_RESP_LONG_LEN, 0))) {
        print_on();
        pout("%s: Failed [%s]\n", __func__, scsiErrString(err));
        print_off();
        return FAILSMART;
    }
    if ((gBuf[0] & 0x3f) != SS_MEDIA_LPAGE) {
        print_on();
        pout("%s %s, page mismatch\n", ssm_s, logSenStr);
        print_off();
        return FAILSMART;
    }
    // compute page length
    num = sg_get_unaligned_be16(gBuf + 2) + 4;
    if (num < 12) {
        print_on();
        pout("%s %s length is %d, too short\n", ssm_s, logSenStr, num);
        print_off();
        return FAILSMART;
    }
    truncated = (num > LOG_RESP_LONG_LEN) ? num : 0;
    if (truncated)
        num = LOG_RESP_LONG_LEN;
    ucp = gBuf + 4;
    num -= 4;
    while (num > 3) {
        int pc = sg_get_unaligned_be16(ucp + 0);
        // pcb = ucp[2];
        int pl = ucp[3] + 4;
        switch (pc) {
        case 1:
            if (pl < 8) {
                print_on();
                pout("%s Percentage used endurance indicator parameter "
                     "too short (pl=%d)\n", ssm_s, pl);
                print_off();
                return FAILSMART;
            }
            q = "Percentage used endurance indicator";
            jout("%s: %d%%\n", q, ucp[7]);
            jglb[std::string("scsi_") + json::str2key(q)] = ucp[7];
        default:        /* ignore other parameter codes */
            break;
        }
        num -= pl;
        ucp += pl;
    }
    return retval;
}

static int
scsiPrintZBDeviceStats(scsi_device * device)
{
    int num, err, truncated;
    int retval = 0;
    uint32_t u;
    uint8_t * ucp;
    const char * q;
    static const char * jname = "scsi_zoned_block_device_statistics";

    jout("\n%s %s:\n", zbds_s, lp_s);
    if ((err = scsiLogSense(device, DEVICE_STATS_LPAGE, ZB_DEV_STATS_L_SPAGE,
                            gBuf, LOG_RESP_LONG_LEN, 0))) {
        print_on();
        pout("%s: Failed [%s]\n", __func__, scsiErrString(err));
        print_off();
        return FAILSMART;
    }
    if (((gBuf[0] & 0x3f) != DEVICE_STATS_LPAGE) &&
        (gBuf[1] == ZB_DEV_STATS_L_SPAGE)) {
        print_on();
        pout("%s %s, page mismatch\n", zbds_s, logSenStr);
        print_off();
        return FAILSMART;
    }
    // compute page length
    num = sg_get_unaligned_be16(gBuf + 2) + 4;
    if (num < 12) {
        print_on();
        pout("%s %s length is %d, too short\n", zbds_s, logSenStr, num);
        print_off();
        return FAILSMART;
    }
    truncated = (num > LOG_RESP_LONG_LEN) ? num : 0;
    if (truncated)
        num = LOG_RESP_LONG_LEN;
    ucp = gBuf + 4;
    num -= 4;
    while (num > 3) {
        int pc = sg_get_unaligned_be16(ucp + 0);
        // pcb = ucp[2];
        int pl = ucp[3] + 4;

        if (pl < 12)
            goto skip;  /* DC HC650 has non-compliant 4 byte parameters */
        switch (pc) {
        case 0:
            q = "Maximum open zones";
            u = sg_get_unaligned_be32(ucp + 8);
            jout("    %s: %u\n", q, u);
            jglb[jname][json::str2key(q)] = u;
            break;
        case 1:
            q = "Maximum explicitly open zones";
            u = sg_get_unaligned_be32(ucp + 8);
            jout("    %s: %u\n", q, u);
            jglb[jname][json::str2key(q)] = u;
            break;
        case 2:
            q = "Maximum implicitly open zones";
            u = sg_get_unaligned_be32(ucp + 8);
            jout("    %s: %u\n", q, u);
            jglb[jname][json::str2key(q)] = u;
            break;
        case 3:
            q = "Minimum empty zones";
            u = sg_get_unaligned_be32(ucp + 8);
            jout("    %s: %u\n", q, u);
            jglb[jname][json::str2key(q)] = u;
            break;
        case 4:
            q = "Maximum nonseq zones";
            u = sg_get_unaligned_be32(ucp + 8);
            jout("    %s: %u\n", q, u);
            jglb[jname][json::str2key(q)] = u;
            break;
        case 5:
            q = "Zones emptied";
            u = sg_get_unaligned_be32(ucp + 8);
            jout("    %s: %u\n", q, u);
            jglb[jname][json::str2key(q)] = u;
            break;
        case 6:
            q = "Suboptimal write commands";
            u = sg_get_unaligned_be32(ucp + 8);
            jout("    %s: %u\n", q, u);
            jglb[jname][json::str2key(q)] = u;
            break;
        case 7:
            q = "Commands exceeding optinmal limit";
            u = sg_get_unaligned_be32(ucp + 8);
            jout("    %s: %u\n", q, u);
            jglb[jname][json::str2key(q)] = u;
            break;
        case 8:
            q = "Failed explicit opens";
            u = sg_get_unaligned_be32(ucp + 8);
            jout("    %s: %u\n", q, u);
            jglb[jname][json::str2key(q)] = u;
            break;
        case 9:
            q = "Read rule violations";
            u = sg_get_unaligned_be32(ucp + 8);
            jout("    %s: %u\n", q, u);
            jglb[jname][json::str2key(q)] = u;
            break;
        case 0xa:
            q = "Write rule violations";
            u = sg_get_unaligned_be32(ucp + 8);
            jout("    %s: %u\n", q, u);
            jglb[jname][json::str2key(q)] = u;
            break;
        case 0xb:
            q = "Maximum implicitly open sequential or before required zones";
            u = sg_get_unaligned_be32(ucp + 8);
            jout("    %s: %u\n", q, u);
            jglb[jname][json::str2key(q)] = u;
            break;
        default:        /* ignore other parameter codes */
            break;
        }
skip:
        num -= pl;
        ucp += pl;
    }
    return retval;
}

static int
scsiPrintTapeDeviceStats(scsi_device * device)
{
    int num, err, truncated;
    int retval = 0;
    uint32_t k, n, u;
    uint64_t ull;
    uint8_t * ucp;
    const char * q;
    static const char * hname = "Device statistics (SSC, tape)";
    static const char * jname = "scsi_device_statistics";

    jout("\n%s %s:\n", hname, lp_s);
    if ((err = scsiLogSense(device, DEVICE_STATS_LPAGE, 0,
                            gBuf, LOG_RESP_LONG_LEN, 0))) {
        print_on();
        pout("%s: Failed [%s]\n", __func__, scsiErrString(err));
        print_off();
        return FAILSMART;
    }
    if (((gBuf[0] & 0x3f) != DEVICE_STATS_LPAGE) &&
        (gBuf[1] != 0)) {
        print_on();
        pout("%s %s, page mismatch\n", hname, logSenStr);
        print_off();
        return FAILSMART;
    }
    // compute page length
    num = sg_get_unaligned_be16(gBuf + 2) + 4;
    if (num < 12) {
        print_on();
        pout("%s %s length is %d, too short\n", hname, logSenStr, num);
        print_off();
        return FAILSMART;
    }
    truncated = (num > LOG_RESP_LONG_LEN) ? num : 0;
    if (truncated)
        num = LOG_RESP_LONG_LEN;
    ucp = gBuf + 4;
    num -= 4;
    while (num > 3) {
        int pc = sg_get_unaligned_be16(ucp + 0);
        // pcb = ucp[2];
        int pl = ucp[3] + 4;
        std::string s;

        switch (pc) {
        case 0:
            q = "Lifetime volume loads";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 1:
            q = "Lifetime cleaning operations";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 2:
            q = "Lifetime power on hours";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 3:
            q = "Lifetime medium motion hours";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 4:
            q = "Lifetime meters of tape processed";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 5:
            q = "Lifetime medium motion hours at last incompatible volume "
                "load";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 6:
            q = "Lifetime power on hours at last temperature condition "
                "occurrence";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 7:
            q = "Lifetime power on hours at last power consumption condition "
                "occurrence";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 8:
            q = "Medium motion hours since last successful cleaning "
                "operation";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 9:
            q = "Medium motion hours since second to last successful "
                "cleaning operation";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 0xa:
            q = "Medium motion hours since third to last successful "
                "cleaning operation";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 0xb:
            q = "Lifetime power on hours at last operator initiated forced "
                "reset and/or emergency eject occurrence";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 0xc:
            q = "Lifetime power cycles";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 0xd:
            q = "Volume loads since last parameter reset";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 0xe:
            q = "Hard write errors";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 0xf:
            q = "Hard read errors";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 0x10:
            q = "Duty cycle sample time";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 0x11:
            q = "Read duty cycle";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 0x12:
            q = "Write duty cycle";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 0x13:
            q = "Activity duty cycle";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 0x14:
            q = "Volume not present duty cycle";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 0x15:
            q = "Ready duty cycle";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 0x16:
            q = "Megabytes transferred from application client in duty cycle"
                "sample time";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 0x17:
            q = "Megabytes transferred to application client in duty cycle"
                "sample time";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 0x40:
            {
                std::string v((const char *)(ucp + 4), ucp[3]);
                q = "Drive manufacturer's serial number";
                jout("    %s: %s\n", q, v.c_str());
                jglb[jname][json::str2key(q)] = v;
            }
            break;
        case 0x41:
            {
                std::string v((const char *)(ucp + 4), ucp[3]);
                q = "Drive serial number";
                jout("    %s: %s\n", q, v.c_str());
                jglb[jname][json::str2key(q)] = v;
            }
            break;
        case 0x42:
            {
                std::string v((const char *)(ucp + 4), ucp[3]);
                q = "Manufacturing date year,month,day";
                jout("    %s: %s\n", q, v.c_str());
                jglb[jname][json::str2key(q)] = v;
            }
            break;
        case 0x43:
            {
                std::string v((const char *)(ucp + 4), ucp[3]);
                q = "Manufacturing date year,week";
                jout("    %s: %s\n", q, v.c_str());
                jglb[jname][json::str2key(q)] = v;
            }
            break;
        case 0x44:
            {
                std::string v((const char *)(ucp + 4), ucp[3]);
                q = "Manufacturing date year,week";
                jout("    %s: %s\n", q, v.c_str());
                jglb[jname][json::str2key(q)] = v;
            }
            break;
        case 0x80:
            q = "Medium removal prevented";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 0x81:
            q = "Maximum recommended mechanism temperature exceeded";
            ull = variableLengthIntegerParam(ucp);
            jout("    %s: %" PRIu64 "\n", q, ull);
            jglb[jname][json::str2key(q)] = ull;
            break;
        case 0x1000:
            q = "Medium motion hours for each medium type";
            s = json::str2key(q);
            n = ucp[3] / 8;
            jout("    %s, number of element: %u\n", q, n);
            for (k = 0; k < n; ++k, ucp += 8) {
                u = sg_get_unaligned_be32(ucp + 8);
                jout("      [%d] density code: %u, density code: %u, hours: "
                     "%u\n", k + 1, ucp[6], ucp[7], u);
                jglb[jname][s][k]["density_code"] = ucp[6];
                jglb[jname][s][k]["medium_type"] = ucp[7];
                jglb[jname][s][k]["medium_motion_hours"] = u;
            }
            break;
        default:        /* ignore other parameter codes */
            break;
        }
        num -= pl;
        ucp += pl;
    }
    return retval;
}

static int
scsiPrintFormatStatus(scsi_device * device)
{
    int num, err, truncated;
    int retval = 0;
    uint64_t ull;
    uint8_t * ucp;
    static const char * hname = "Format Status";
    static const char * jname = "scsi_format_status";

    if ((err = scsiLogSense(device, FORMAT_STATUS_LPAGE, 0, gBuf,
                            LOG_RESP_LONG_LEN, 0))) {
        print_on();
        jout("%s: Failed [%s]\n", __func__, scsiErrString(err));
        print_off();
        return FAILSMART;
    }
    if ((gBuf[0] & 0x3f) != FORMAT_STATUS_LPAGE) {
        print_on();
        jout("%s %s, page mismatch\n", hname, logSenRspStr);
        print_off();
        return FAILSMART;
    }
    // compute page length
    num = sg_get_unaligned_be16(gBuf + 2) + 4;
    if (num < 12) {
        print_on();
        jout("%s %s length is %d, too short\n", hname, logSenStr, num);
        print_off();
        return FAILSMART;
    }
    truncated = (num > LOG_RESP_LONG_LEN) ? num : 0;
    if (truncated)
        num = LOG_RESP_LONG_LEN;
    ucp = gBuf + 4;
    num -= 4;
    while (num > 3) {
        int pc = sg_get_unaligned_be16(ucp + 0);
        // pcb = ucp[2];
        int pl = ucp[3] + 4;

        bool is_count = true;
        const char * jout_str = "";
        const char * jglb_str = "x";
        switch (pc) {
        case 0:
            if (scsi_debugmode > 1) {
                if (pl < 5)
                    jout("Format data out: <empty>\n");
                else {
                    if (all_ffs(ucp + 4, pl - 4))
                        jout("Format data out: <not available>\n");
                    else {
                        jout("Format data out:\n");
                        dStrHex((const uint8_t *)ucp + 4, pl - 4, 0);
                    }
                }
            }
            is_count = false;
            break;
        case 1:
            jout_str = "Grown defects during certification";
            jglb_str = "grown_defects_during_cert";
            break;
        case 2:
            jout_str = "Total blocks reassigned during format";
            jglb_str = "blocks_reassigned_during_format";
            break;
        case 3:
            jout_str = "Total new blocks reassigned";
            jglb_str = "total_new_block_since_format";
            break;
        case 4:
            jout_str = "Power on minutes since format";
            jglb_str = "power_on_minutes_since_format";
            break;
        default:
            if (scsi_debugmode > 3) {
                pout("  Unknown Format parameter code = 0x%x\n", pc);
                dStrHex((const uint8_t *)ucp, pl, 0);
            }
            is_count = false;
            break;
        }
        if (is_count) {
            if (all_ffs(ucp + 4, ucp[3])) {
                pout("%s <not available>\n", jout_str);
            } else {
                ull = variableLengthIntegerParam(ucp);
                jout("%s = %" PRIu64 "\n", jout_str, ull);
                jglb[jname][jglb_str] = ull;
            }
        } else
        num -= pl;
        ucp += pl;
    }
    return retval;

}

static void
show_sas_phy_event_info(const json::ref & jref, int peis, unsigned int val,
                        unsigned thresh_val)
{
    unsigned int u;
    const char * q;
    static const char * pvd_th = "Peak value detector threshold";
    static const char * pvd_th_j = "pvd_threshold";

    switch (peis) {
    case 0:
        jout("     No event\n");
        break;
    case 0x1:   /* 0x1 to 0x4 will be duplicates so append "_2" to name */
        q = "Invalid dword count";
        jout("     %s: %u\n", q, val);
        jref[std::string(q) + "_2"] = val;
        break;
    case 0x2:
        q = "Running disparity error count";
        jout("     %s: %u\n", q, val);
        jref[std::string(q) + "_2"] = val;
        break;
    case 0x3:
        q = "Loss of dword synchronization count";
        jout("     %s: %u\n", q, val);
        jref[std::string(q) + "_2"] = val;
        break;
    case 0x4:
        q = "Phy reset problem count";
        jout("     %s: %u\n", q, val);
        jref[std::string(q) + "_2"] = val;
        break;
    case 0x5:
        q = "Elasticity buffer overflow count";
        jout("     %s: %u\n", q, val);
        jref[q] = val;
        break;
    case 0x6:
        q = "Received ERROR count";
        jout("     %s: %u\n", q, val);
        jref[q] = val;
        break;
    case 0x20:
        q = "Received address frame error count";
        jout("     %s: %u\n", q, val);
        jref[q] = val;
        break;
    case 0x21:
        q = "Transmitted abandon-class OPEN_REJECT count";
        jout("     %s: %u\n", q, val);
        jref[q] = val;
        break;
    case 0x22:
        q = "Received abandon-class OPEN_REJECT count";
        jout("     %s: %u\n", q, val);
        jref[q] = val;
        break;
    case 0x23:
        q = "Transmitted retry-class OPEN_REJECT count";
        jout("     %s: %u\n", q, val);
        jref[q] = val;
        break;
    case 0x24:
        q = "Received retry-class OPEN_REJECT count";
        jout("     %s: %u\n", q, val);
        jref[q] = val;
        break;
    case 0x25:
        q = "Received AIP (WAITING ON PARTIAL) count";
        jout("     %s: %u\n", q, val);
        jref[q] = val;
        break;
    case 0x26:
        q = "Received AIP (WAITING ON CONNECTION) count";
        jout("     %s: %u\n", q, val);
        jref[q] = val;
        break;
    case 0x27:
        q = "Transmitted BREAK count";
        jout("     %s: %u\n", q, val);
        jref[q] = val;
        break;
    case 0x28:
        q = "Received BREAK count";
        jout("     %s: %u\n", q, val);
        jref[q] = val;
        break;
    case 0x29:
        q = "Break timeout count";
        jout("     %s: %u\n", q, val);
        jref[q] = val;
        break;
    case 0x2a:
        q = "Connection count";
        jout("     %s: %u\n", q, val);
        jref[q] = val;
        break;
    case 0x2b:
        q = "Peak transmitted pathway blocked";
        jout("     %s count: %u\n", q, val & 0xff);
        jout("         %s: %u\n", pvd_th, thresh_val & 0xff);
        jref[q]["count"] = val & 0xff;
        jref[q][pvd_th_j] = thresh_val & 0xff;
        break;
    case 0x2c:
        q = "Peak transmitted arbitration wait time";
        u = val & 0xffff;
        if (u < 0x8000) {
            jout("     %s (us): %u\n", q, u);
            jref[std::string(q) + "_us"]["event"] = u;
        } else {
            jout("     %s (ms): %u\n", q, 33 + (u - 0x8000));
            jref[std::string(q) + "_ms"]["event"] = 33 + (u - 0x8000);
        }
        u = thresh_val & 0xffff;
        if (u < 0x8000) {
            jout("         %s (us): %u\n", pvd_th, u);
            jref[std::string(q) + "_us"][pvd_th_j] = u;
        } else {
            jout("         %s (ms): %u\n", pvd_th, 33 + (u - 0x8000));
            jref[std::string(q) + "_ms"][pvd_th_j] = 33 + (u - 0x8000);
        }
        break;
    case 0x2d:
        q = "Peak arbitration time";
        jout("     %s (us): %u\n", q, val);
        jref[std::string(q) + "_us"]["event"] = val;
        jout("         %s: %u\n", pvd_th, thresh_val);
        jref[std::string(q) + "_us"][pvd_th_j] = thresh_val;
        break;
    case 0x2e:
        q = "Peak connection time";
        jout("     %s (us): %u\n", q, val);
        jref[std::string(q) + "_us"]["event"] = val;
        jout("         %s: %u\n", pvd_th, thresh_val);
        jref[std::string(q) + "_us"][pvd_th_j] = thresh_val;
        break;
    case 0x40:
        q = "Transmitted SSP frame count";
        jout("     %s: %u\n", q, val);
        jref[q] = val;
        break;
    case 0x41:
        q = "Received SSP frame count";
        jout("     %s: %u\n", q, val);
        jref[q] = val;
        break;
    case 0x42:
        q = "Transmitted SSP frame error count";
        jout("     %s: %u\n", q, val);
        jref[q] = val;
        break;
    case 0x43:
        q = "Received SSP frame error count";
        jout("     %s: %u\n", q, val);
        jref[q] = val;
        break;
    case 0x44:
        q = "Transmitted CREDIT_BLOCKED count";
        jout("     %s: %u\n", q, val);
        jref[q] = val;
        break;
    case 0x45:
        q = "Received CREDIT_BLOCKED count";
        jout("     %s: %u\n", q, val);
        jref[q] = val;
        break;
    case 0x50:
        q = "Transmitted SATA frame count";
        jout("     %s: %u\n", q, val);
        jref[q] = val;
        break;
    case 0x51:
        q = "Received SATA frame count";
        jout("     %s: %u\n", q, val);
        jref[q] = val;
        break;
    case 0x52:
        q = "SATA flow control buffer overflow count";
        jout("     %s: %u\n", q, val);
        jref[q] = val;
        break;
    case 0x60:
        q = "Transmitted SMP frame count";
        jout("     %s: %u\n", q, val);
        jref[q] = val;
        break;
    case 0x61:
        q = "Received SMP frame count";
        jout("     %s: %u\n", q, val);
        jref[q] = val;
        break;
    case 0x63:
        q = "Received SMP frame error count";
        jout("     %s: %u\n", q, val);
        jref[q] = val;
        break;
    default:
        break;
    }
}

static void
show_sas_port_param(int port_num, unsigned char * ucp, int param_len)
{
    int k, j, m, nphys, t, sz, spld_len;
    char pn[32];
    unsigned char * vcp;
    char s[64];
    const char * q;

    snprintf(pn, sizeof(pn), "scsi_sas_port_%d", port_num);
    sz = sizeof(s);
    // pcb = ucp[2];
    t = sg_get_unaligned_be16(ucp + 0);
    jout("relative target port id = %d\n", t);
    jglb[pn]["relative_target_port_id"] = t;
    jout("  generation code = %d\n", ucp[6]);
    jglb[pn]["generation_code"] = ucp[6];
    nphys = ucp[7];
    jout("  number of phys = %d\n", nphys);
    jglb[pn]["number_of_phys"] = nphys;

    for (j = 0, k = 0, vcp = ucp + 8; j < (param_len - 8);
         vcp += spld_len, j += spld_len, ++k) {
        char yn[32];

        snprintf(yn, sizeof(yn), "phy_%d", k);
        json::ref jref = jglb[pn][yn];
        jout("  phy identifier = %d\n", vcp[1]);
        jref["identifier"] = vcp[1];
        spld_len = vcp[3];
        if (spld_len < 44)
            spld_len = 48;      /* in SAS-1 and SAS-1.1 vcp[3]==0 */
        else
            spld_len += 4;
        t = ((0x70 & vcp[4]) >> 4);
        switch (t) {
        case 0: snprintf(s, sz, "no device attached"); break;
        case 1: snprintf(s, sz, "SAS or SATA device"); break;
        case 2: snprintf(s, sz, "expander device"); break;
        case 3: snprintf(s, sz, "expander device (fanout)"); break;
        default: snprintf(s, sz, "reserved [%d]", t); break;
        }
        q = "attached device type";
        jout("    %s: %s\n", q, s);
        jref[q] = s;
        t = 0xf & vcp[4];
        switch (t) {
        case 0: snprintf(s, sz, "unknown"); break;
        case 1: snprintf(s, sz, "power on"); break;
        case 2: snprintf(s, sz, "hard reset"); break;
        case 3: snprintf(s, sz, "SMP phy control function"); break;
        case 4: snprintf(s, sz, "loss of dword synchronization"); break;
        case 5: snprintf(s, sz, "mux mix up"); break;
        case 6: snprintf(s, sz, "I_T nexus loss timeout for STP/SATA");
            break;
        case 7: snprintf(s, sz, "break timeout timer expired"); break;
        case 8: snprintf(s, sz, "phy test function stopped"); break;
        case 9: snprintf(s, sz, "expander device reduced functionality");
             break;
        default: snprintf(s, sz, "reserved [0x%x]", t); break;
        }
        q = "attached reason";
        jout("    %s: %s\n", q, s);
        jref[q] = s;
        t = (vcp[5] & 0xf0) >> 4;
        switch (t) {
        case 0: snprintf(s, sz, "unknown"); break;
        case 1: snprintf(s, sz, "power on"); break;
        case 2: snprintf(s, sz, "hard reset"); break;
        case 3: snprintf(s, sz, "SMP phy control function"); break;
        case 4: snprintf(s, sz, "loss of dword synchronization"); break;
        case 5: snprintf(s, sz, "mux mix up"); break;
        case 6: snprintf(s, sz, "I_T nexus loss timeout for STP/SATA");
            break;
        case 7: snprintf(s, sz, "break timeout timer expired"); break;
        case 8: snprintf(s, sz, "phy test function stopped"); break;
        case 9: snprintf(s, sz, "expander device reduced functionality");
             break;
        default: snprintf(s, sz, "reserved [0x%x]", t); break;
        }
        q = "reason";
        jout("    %s: %s\n", q, s);
        jref[q] = s;
        t = (0xf & vcp[5]);
        switch (t) {
        case 0: snprintf(s, sz, "phy enabled; unknown");
                     break;
        case 1: snprintf(s, sz, "phy disabled"); break;
        case 2: snprintf(s, sz, "phy enabled; speed negotiation failed");
                     break;
        case 3: snprintf(s, sz, "phy enabled; SATA spinup hold state");
                     break;
        case 4: snprintf(s, sz, "phy enabled; port selector");
                     break;
        case 5: snprintf(s, sz, "phy enabled; reset in progress");
                     break;
        case 6: snprintf(s, sz, "phy enabled; unsupported phy attached");
                     break;
        case 8: snprintf(s, sz, "phy enabled; 1.5 Gbps"); break;
        case 9: snprintf(s, sz, "phy enabled; 3 Gbps"); break;
        case 0xa: snprintf(s, sz, "phy enabled; 6 Gbps"); break;
        case 0xb: snprintf(s, sz, "phy enabled; 12 Gbps"); break;
        default: snprintf(s, sz, "reserved [%d]", t); break;
        }
        q = "negotiated logical link rate";
        jout("    %s: %s\n", q, s);
        jref[q] = s;
        q = "attached initiator port";
        jout("    %s: ssp=%d stp=%d smp=%d\n", q,
               !! (vcp[6] & 8), !! (vcp[6] & 4), !! (vcp[6] & 2));
        snprintf(s, sz, "%03d", ((vcp[6] & 8) ? 100 : 0) +
                 ((vcp[6] & 4) ? 10 : 0) + ((vcp[6] & 2) ? 1 : 0));
        jref[q]["ssp_stp_smp"] = s;
        q = "attached target port";
        jout("    %s: ssp=%d stp=%d smp=%d\n", q,
               !! (vcp[7] & 8), !! (vcp[7] & 4), !! (vcp[7] & 2));
        snprintf(s, sz, "%03d", ((vcp[7] & 8) ? 100 : 0) +
                 ((vcp[7] & 4) ? 10 : 0) + ((vcp[7] & 2) ? 1 : 0));
        jref[q]["ssp_stp_smp"] = s;
        if (!dont_print_serial_number) {
            uint64_t ull = sg_get_unaligned_be64(vcp + 8);
            char b[32];

            snprintf(b, sizeof(b), "0x%" PRIx64, ull);
            q = "SAS address";
            jout("    %s = %s\n", q, b);
            jref[q] = b;
            ull = sg_get_unaligned_be64(vcp + 16);
            snprintf(b, sizeof(b), "0x%" PRIx64, ull);
            q = "attached SAS address";
            jout("    %s = %s\n", q, b);
            jref[q] = b;
        }
        q = "attached phy identifier";
        jout("    %s = %d\n", q, vcp[24]);
        jref[q] = vcp[24];
        unsigned int ui = sg_get_unaligned_be32(vcp + 32);

        q = "Invalid DWORD count";
        jout("    %s = %u\n", q, ui);
        jref[q] = ui;
        ui = sg_get_unaligned_be32(vcp + 36);
        q = "Running disparity error count";
        jout("    %s = %u\n", q, ui);
        jref[q] = ui;
        ui = sg_get_unaligned_be32(vcp + 40);
        q = "Loss of DWORD synchronization count";
        jout("    %s = %u\n", q, ui);
        jref[q] = ui;
        ui = sg_get_unaligned_be32(vcp + 44);
        q = "Phy reset problem count";
        jout("    %s = %u\n", q, ui);
        jref[q] = ui;
        if (spld_len > 51) {
            bool header_given = false;
            bool allow_dupl = (scsi_debugmode > 0);
            int num_ped;
            unsigned char * xcp;

            num_ped = vcp[51];
            xcp = vcp + 52;
            for (m = 0; m < (num_ped * 12); m += 12, xcp += 12) {
                int peis;
                unsigned int pvdt;

                peis = xcp[3];
                ui = sg_get_unaligned_be32(xcp + 4);
                pvdt = sg_get_unaligned_be32(xcp + 8);
                if (allow_dupl || (peis > 0x4)) {
                    if (! header_given) {
                        header_given = true;
                        jout("    Phy event descriptors:\n");
                    }
                    show_sas_phy_event_info(jref, peis, ui, pvdt);
                }
            }
        }
    }
}

// Returns 1 if okay, 0 if non SAS descriptors
static int
show_protocol_specific_port_page(unsigned char * resp, int len)
{
    int k, j, num;
    unsigned char * ucp;

    num = len - 4;
    for (k = 0, j = 0, ucp = resp + 4; k < num; ++j) {
        int param_len = ucp[3] + 4;
        if (SCSI_TPROTO_SAS != (0xf & ucp[4]))
            return 0;   /* only decode SAS log page */
        if (0 == k)
            jout("\nProtocol Specific port %s for SAS SSP\n", lp_s);
        show_sas_port_param(j, ucp, param_len);
        k += param_len;
        ucp += param_len;
    }
    pout("\n");
    return 1;
}


// See Serial Attached SCSI (SPL-3) (e.g. revision 6g) the Protocol Specific
// log page [0x18]. Returns 0 if ok else FAIL* bitmask.
static int
scsiPrintSasPhy(scsi_device * device, int reset)
{
    int num, err;
    static const char * hname = "Protocol specific port";

    if ((err = scsiLogSense(device, PROTOCOL_SPECIFIC_LPAGE, 0, gBuf,
                            LOG_RESP_LONG_LEN, 0))) {
        print_on();
        pout("%s %s Failed [%s]\n\n", __func__, logSenStr,
             scsiErrString(err));
        print_off();
        return FAILSMART;
    }
    if ((gBuf[0] & 0x3f) != PROTOCOL_SPECIFIC_LPAGE) {
        print_on();
        pout("%s %s, page mismatch\n\n", hname, logSenRspStr);
        print_off();
        return FAILSMART;
    }
    // compute page length
    num = sg_get_unaligned_be16(gBuf + 2);
    if (1 != show_protocol_specific_port_page(gBuf, num + 4)) {
        print_on();
        pout("Only support %s %s on SAS devices\n\n", hname, lp_s);
        print_off();
        return FAILSMART;
    }
    if (reset) {
        if ((err = scsiLogSelect(device, 1 /* pcr */, 0 /* sp */, 0 /* pc */,
                                 PROTOCOL_SPECIFIC_LPAGE, 0, nullptr, 0))) {
            print_on();
            pout("%s Log Select (reset) Failed [%s]\n\n", __func__,
                 scsiErrString(err));
            print_off();
            return FAILSMART;
        }
    }
    return 0;
}


static const char * peripheral_dt_arr[32] = {
        "disk",
        "tape",
        "printer",
        "processor",
        "optical disk(4)",
        "CD/DVD",
        "scanner",
        "optical disk(7)",
        "medium changer",
        "communications",
        "graphics(10)",
        "graphics(11)",
        "storage array",
        "enclosure",
        "simplified disk",
        "optical card reader",
        "reserved [0x10]",
        "object based storage",
        "automation/driver interface",
        "security manager device",
        "host managed zoned block device",
        "reserved [0x15]",
        "reserved [0x16]",
        "reserved [0x17]",
        "reserved [0x18]",
        "reserved [0x19]",
        "reserved [0x1a]",
        "reserved [0x1b]",
        "reserved [0x1c]",
        "reserved [0x1d]",
        "well known logical unit",
        "unknown or no device type",
};

/* Symbolic indexes to this array SCSI_TPROTO_* in scscmds.h */
static const char * transport_proto_arr[] = {
        "Fibre channel (FCP-4)",
        "Parallel SCSI (SPI-4)",        /* obsolete */
        "SSA",
        "IEEE 1394 (SBP-3)",
        "RDMA (SRP)",
        "iSCSI",
        "SAS (SPL-4)",
        "ADT",
        "ATA (ACS-2)",
        "UAS",
        "SOP",
        "PCIe",
        "0xc",
        "0xd",
        "0xe",
        "None given [0xf]"
};

/* Returns 0 on success, 1 on general error and 2 for early, clean exit */
static int
scsiGetDriveInfo(scsi_device * device, uint8_t * peripheral_type,
                 bool & have_zbc, bool all)
{
    bool ok;
    bool is_tape = false;
    int err, iec_err, len, req_len, avail_len;
    int peri_dt = 0;
    int transport = -1;
    int form_factor = 0;
    int haw_zbc = 0;
    int protect = 0;
    const char * q;
    struct scsi_iec_mode_page iec;

    memset(gBuf, 0, 96);
    have_zbc = false;
    req_len = 36;
    if ((err = scsiStdInquiry(device, gBuf, req_len))) {
        print_on();
        pout("Standard Inquiry (36 bytes) failed [%s]\n", scsiErrString(err));
        pout("Retrying with a 64 byte Standard Inquiry\n");
        print_off();
        /* Marvell controllers fail with 36 byte StdInquiry, but 64 is ok */
        req_len = 64;
        if ((err = scsiStdInquiry(device, gBuf, req_len))) {
            print_on();
            pout("Standard Inquiry (64 bytes) failed [%s]\n",
                 scsiErrString(err));
            print_off();
            return 1;
        }
    }
    avail_len = gBuf[4] + 5;
    len = (avail_len < req_len) ? avail_len : req_len;
    peri_dt = gBuf[0] & 0x1f;
    *peripheral_type = peri_dt;
    if (SCSI_PT_HOST_MANAGED == peri_dt)
        have_zbc = true;
    if ((SCSI_PT_SEQUENTIAL_ACCESS == peri_dt) ||
        (SCSI_PT_MEDIUM_CHANGER == peri_dt))
        is_tape = true;

    if (len < 36) {
        print_on();
        pout("Short INQUIRY response, skip product id\n");
        print_off();
        return 1;
    }
    // Upper bits of version bytes were used in older standards
    // Only interested in SPC-4 (0x6) and SPC-5 (assumed to be 0x7)
    scsi_version = gBuf[2] & 0x7;

    if (all && (0 != strncmp((char *)&gBuf[8], "ATA", 3))) {
        char product[16+1], revision[4+1];
        scsi_format_id_string(scsi_vendor, &gBuf[8], 8);
        scsi_format_id_string(product, &gBuf[16], 16);
        scsi_format_id_string(revision, &gBuf[32], 4);

        pout("=== START OF INFORMATION SECTION ===\n");
        jout("Vendor:               %.8s\n", scsi_vendor);
        jglb["scsi_vendor"] = scsi_vendor;
        jout("Product:              %.16s\n", product);
        jglb["scsi_product"] = product;
        jglb["scsi_model_name"] = strprintf("%s%s%s",
          scsi_vendor, (*scsi_vendor && *product ? " " : ""), product);
        if (gBuf[32] >= ' ') {
            jout("Revision:             %.4s\n", revision);
            // jglb["firmware_version"] = revision;
            jglb["scsi_revision"] = revision;
        }
        if ((scsi_version > 0x3) && (scsi_version < 0x8)) {
            char sv_arr[8];

            snprintf(sv_arr, sizeof(sv_arr), "SPC-%d", scsi_version - 2);
            jout("Compliance:           %s\n", sv_arr);
            jglb["scsi_version"] = sv_arr;
        }
    }

    if (!*device->get_req_type()/*no type requested*/ &&
               (0 == strncmp((char *)&gBuf[8], "ATA", 3))) {
        pout("\nProbable ATA device behind a SAT layer\n"
             "Try an additional '-d ata' or '-d sat' argument.\n");
        return 2;
    }
    if (! all)
        return 0;

    protect = gBuf[5] & 0x1;    /* from and including SPC-3 */

    if (! is_tape) {    /* assume disk if not tape drive (or tape changer) */
        struct scsi_readcap_resp srr;
        int lbpme = -1;
        int lbprz = -1;
        unsigned char lb_prov_resp[8];
        uint64_t capacity = scsiGetSize(device, false /*avoid_rcap16 */,
                                        &srr);
        static const char * lb_prov_j = "scsi_lb_provisioning";

        if (capacity) {
            char cap_str[64], si_str[64];
            format_with_thousands_sep(cap_str, sizeof(cap_str), capacity);
            format_capacity(si_str, sizeof(si_str), capacity);
            jout("User Capacity:        %s bytes [%s]\n", cap_str, si_str);
            if (srr.lb_size)
              jglb["user_capacity"]["blocks"].set_unsafe_uint64(capacity /
                                                                srr.lb_size);
            jglb["user_capacity"]["bytes"].set_unsafe_uint64(capacity);
            jout("Logical block size:   %u bytes\n", srr.lb_size);
            jglb["logical_block_size"] = srr.lb_size;
            if (protect || srr.lb_p_pb_exp) {
                if (srr.lb_p_pb_exp > 0) {
                    unsigned pb_size = srr.lb_size * (1 << srr.lb_p_pb_exp);
                    jout("Physical block size:  %u bytes\n", pb_size);
                    jglb["physical_block_size"] = pb_size;
                    if (srr.l_a_lba > 0)  // not common so cut the clutter
                        pout("Lowest aligned LBA:   %u\n", srr.l_a_lba);
                }
                if (srr.prot_type > 0) {
                    switch (srr.prot_type) {
                    case 1 :
                        pout("Formatted with type 1 protection\n");
                        break;
                    case 2 :
                        pout("Formatted with type 2 protection\n");
                        break;
                    case 3 :
                        pout("Formatted with type 3 protection\n");
                        break;
                    default:
                        pout("Formatted with unknown protection type [%d]\n",
                             srr.prot_type);
                        break;
                    }
                    jglb["scsi_protection_type"] = srr.prot_type;
                    unsigned p_i_per_lb = (1 << srr.p_i_exp);
                    const unsigned pi_sz = 8;   /* ref-tag(4 bytes),
                                                   app-tag(2), tag-mask(2) */

                    if (p_i_per_lb > 1) {
                        jout("%d protection information intervals per "
                             "logical block\n", p_i_per_lb);
                        jglb["scsi_protection_intervals_per_lb"] = srr.prot_type;
                    }
                    jout("%d bytes of protection information per logical "
                         "block\n", pi_sz * p_i_per_lb);
                    jglb["scsi_protection_interval_bytes_per_lb"] =
                                                        pi_sz * p_i_per_lb;
                }
                /* Pick up some LB provisioning info since its available */
                lbpme = (int)srr.lbpme;
                lbprz = (int)srr.lbprz;
            }
        }
        /* Thin Provisioning VPD page renamed Logical Block Provisioning VPD
         * page in sbc3r25; some fields changed their meaning so that the
         * new page covered both thin and resource provisioned LUs. */
        if (0 == scsiInquiryVpd(device, SCSI_VPD_LOGICAL_BLOCK_PROVISIONING,
                                lb_prov_resp, sizeof(lb_prov_resp))) {
            int prov_type = lb_prov_resp[6] & 0x7;      /* added sbc3r27 */
            int vpd_lbprz = ((lb_prov_resp[5]  >> 2) & 0x7);  /* sbc4r07 */

            if (-1 == lbprz)
                lbprz = vpd_lbprz;
            else if ((0 == vpd_lbprz) && (1 == lbprz))
                ;  /* vpd_lbprz introduced in sbc3r27, expanded in sbc4r07 */
            else
                lbprz = vpd_lbprz;
            switch (prov_type) {
            case 0:
                if (lbpme <= 0) {
                    jout("LU is fully provisioned");
                    jglb[lb_prov_j]["name"] = "fully provisioned";
                    if (lbprz)
                        jout(" [LBPRZ=%d]\n", lbprz);
                    else
                        jout("\n");
                } else {
                    jout("LB provisioning type: not reported [LBPME=1, "
                         "LBPRZ=%d]\n", lbprz);
                    jglb[lb_prov_j]["name"] = "not reported";
                }
                break;
            case 1:
                jout("LU is resource provisioned, LBPRZ=%d\n", lbprz);
                jglb[lb_prov_j]["name"] = "resource provisioned";
                break;
            case 2:
                jout("LU is thin provisioned, LBPRZ=%d\n", lbprz);
                jglb[lb_prov_j]["name"] = "thin provisioned";
                break;
            default:
                jout("LU provisioning type reserved [%d], LBPRZ=%d\n",
                     prov_type, lbprz);
                jglb[lb_prov_j]["name"] = "reserved";
                break;
            }
            jglb[lb_prov_j]["value"] = prov_type;
            jglb[lb_prov_j]["management_enabled"]["name"] = "LBPME";
            jglb[lb_prov_j]["management_enabled"]["value"] = lbpme;
            jglb[lb_prov_j]["read_zeros"]["name"] = "LBPRZ";
            jglb[lb_prov_j]["read_zeros"]["value"] = lbprz;
        } else if (1 == lbpme) {
            if (scsi_debugmode > 0)
                jout("rcap_16 sets LBPME but no LB provisioning VPD page\n");
            jout("Logical block provisioning enabled, LBPRZ=%d\n", lbprz);
        }

        int rpm = scsiGetRPM(device, modese_len, &form_factor, &haw_zbc);
        if (rpm >= 0) {
            if (0 == rpm)
                ;       // Not reported
            else if (1 == rpm)
                jout("Rotation Rate:        Solid State Device\n");
            else if ((rpm <= 0x400) || (0xffff == rpm))
                ;       // Reserved
            else
                jout("Rotation Rate:        %d rpm\n", rpm);
            jglb["rotation_rate"] = (rpm == 1 ? 0 : rpm);
        }
        if (form_factor > 0) {
            const char * cp = nullptr;

            switch (form_factor) {
            case 1:
                cp = "5.25";
                break;
            case 2:
                cp = "3.5";
                break;
            case 3:
                cp = "2.5";
                break;
            case 4:
                cp = "1.8";
                break;
            case 5:
                cp = "< 1.8";
                break;
            }
            jglb["form_factor"]["scsi_value"] = form_factor;
            if (cp) {
                jout("Form Factor:          %s inches\n", cp);
                jglb["form_factor"]["name"] = strprintf("%s inches", cp);
            }
        }
        if (haw_zbc == 1) {
            have_zbc = true;
            q = "Host aware zoned block capable";
            jout("%s\n", q);
            jglb[std::string("scsi_") + json::str2key(q)] = true;
        } else if (haw_zbc == 2) {
            have_zbc = true;
            q = "Device managed zoned block capable";
            jout("%s\n", q);
            jglb[std::string("scsi_") + json::str2key(q)] = true;
        } else {
            supported_vpd_pages * s_vpd_pp = supported_vpd_pages_p;

            if (s_vpd_pp &&
                s_vpd_pp->is_supported(SCSI_VPD_ZONED_BLOCK_DEV_CHAR)) {
                // TODO: need to read that VPD page and look at the
                // 'Zoned block device extension' field

            }
        }
    }

    /* Do this here to try and detect badly conforming devices (some USB
       keys) that will lock up on a InquiryVpd or log sense or ... */
    if ((iec_err = scsiFetchIECmpage(device, &iec, modese_len))) {
        if (SIMPLE_ERR_BAD_RESP == iec_err) {
            pout(">> Terminate command early due to bad response to IEC "
                 "mode page\n");
            print_off();
            gIecMPage = 0;
            return 1;
        }
    } else
        modese_len = iec.modese_len;

    if (! dont_print_serial_number) {
        if (0 == (err = scsiInquiryVpd(device, SCSI_VPD_DEVICE_IDENTIFICATION,
                                       gBuf, 252))) {
            char s[256];

            len = gBuf[3];
            scsi_decode_lu_dev_id(gBuf + 4, len, s, sizeof(s), &transport);
            if (strlen(s) > 0) {
                jout("Logical Unit id:      %s\n", s);
                jglb["logical_unit_id"] = s;
            }
        } else if (scsi_debugmode > 0) {
            print_on();
            if (SIMPLE_ERR_BAD_RESP == err)
                pout("Vital Product Data (VPD) bit ignored in INQUIRY\n");
            else
                pout("Vital Product Data (VPD) INQUIRY failed [%d]\n", err);
            print_off();
        }
        if (0 == (err = scsiInquiryVpd(device, SCSI_VPD_UNIT_SERIAL_NUMBER,
                                       gBuf, 252))) {
            char serial[256];
            len = gBuf[3];

            gBuf[4 + len] = '\0';
            scsi_format_id_string(serial, &gBuf[4], len);
            jout("Serial number:        %s\n", serial);
            jglb["serial_number"] = serial;
        } else if (scsi_debugmode > 0) {
            print_on();
            if (SIMPLE_ERR_BAD_RESP == err)
                pout("Vital Product Data (VPD) bit ignored in INQUIRY\n");
            else
                pout("Vital Product Data (VPD) INQUIRY failed [%d]\n", err);
            print_off();
        }
    }

    // print SCSI peripheral device type
    jglb["device_type"]["scsi_terminology"] = "Peripheral Device Type [PDT]";
    jglb["device_type"]["scsi_value"] = peri_dt;
    if (peri_dt < (int)(ARRAY_SIZE(peripheral_dt_arr))) {
        jout("Device type:          %s\n", peripheral_dt_arr[peri_dt]);
        jglb["device_type"]["name"] = peripheral_dt_arr[peri_dt];
    }
    else
        jout("Device type:          <%d>\n", peri_dt);

    // See if transport protocol is known
    if (transport < 0)
        transport = scsiFetchTransportProtocol(device, modese_len);
    if ((transport >= 0) && (transport <= 0xf)) {
        jout("Transport protocol:   %s\n", transport_proto_arr[transport]);
        jglb["scsi_transport_protocol"]["name"] = transport_proto_arr[transport];
        jglb["scsi_transport_protocol"]["value"] = transport;
    }

    jout_startup_datetime("Local Time is:        ");

    // See if unit accepts SCSI commands from us
    if ((err = scsiTestUnitReady(device))) {
        if (SIMPLE_ERR_NOT_READY == err) {
            print_on();
            if (!is_tape)
                pout("device is NOT READY (e.g. spun down, busy)\n");
            else
                pout("device is NOT READY (e.g. no tape)\n");
            print_off();
        } else if (SIMPLE_ERR_NO_MEDIUM == err) {
            print_on();
            if (is_tape)
                pout("NO tape present in drive\n");
            else
                pout("NO MEDIUM present in device\n");
            print_off();
        } else if (SIMPLE_ERR_BECOMING_READY == err) {
            print_on();
            pout("device becoming ready (wait)\n");
            print_off();
        } else {
            print_on();
            pout("device Test Unit Ready  [%s]\n", scsiErrString(err));
            print_off();
        }
        if (! is_tape) {
            // TODO: exit with FAILID if failuretest returns
            failuretest(MANDATORY_CMD, FAILID);
        }
    }

    if (iec_err) {
        if (!is_tape) {
            print_on();
            jout("SMART support is:     Unavailable - device lacks SMART "
                 "capability.\n");
            jglb["smart_support"]["available"] = false;
            if (scsi_debugmode > 0)
                pout(" [%s]\n", scsiErrString(iec_err));
            print_off();
        }
        gIecMPage = 0;
        return 0;
    }

    if (!is_tape) {
        ok = scsi_IsExceptionControlEnabled(&iec);
        jout("SMART support is:     Available - device has SMART capability.\n"
             "SMART support is:     %s\n", ok ? "Enabled" : "Disabled");
        jglb["smart_support"]["available"] = true;
        jglb["smart_support"]["enabled"] = ok;
    }
    ok = scsi_IsWarningEnabled(&iec);
    jout("Temperature Warning:  %s\n",
         ok ? "Enabled" : "Disabled or Not Supported");
    jglb["temperature_warning"]["enabled"] = ok;
    return 0;
}

static int
scsiSmartEnable(scsi_device * device)
{
    struct scsi_iec_mode_page iec;
    int err;

    if ((err = scsiFetchIECmpage(device, &iec, modese_len))) {
        print_on();
        pout("unable to fetch IEC (SMART) mode page [%s]\n",
             scsiErrString(err));
        print_off();
        return 1;
    } else
        modese_len = iec.modese_len;

    if ((err = scsiSetExceptionControlAndWarning(device, 1, &iec))) {
        print_on();
        pout("unable to enable Exception control and warning [%s]\n",
             scsiErrString(err));
        print_off();
        return 1;
    }
    /* Need to refetch 'iec' since could be modified by previous call */
    if ((err = scsiFetchIECmpage(device, &iec, modese_len))) {
        pout("unable to fetch IEC (SMART) mode page [%s]\n",
             scsiErrString(err));
        return 1;
    } else
        modese_len = iec.modese_len;

    pout("Informational Exceptions (SMART) %s\n",
         scsi_IsExceptionControlEnabled(&iec) ? "enabled" : "disabled");
    pout("Temperature warning %s\n",
         scsi_IsWarningEnabled(&iec) ? "enabled" : "disabled");
    return 0;
}

static int
scsiSmartDisable(scsi_device * device)
{
    struct scsi_iec_mode_page iec;
    int err;

    if ((err = scsiFetchIECmpage(device, &iec, modese_len))) {
        print_on();
        pout("unable to fetch IEC (SMART) mode page [%s]\n",
             scsiErrString(err));
        print_off();
        return 1;
    } else
        modese_len = iec.modese_len;

    if ((err = scsiSetExceptionControlAndWarning(device, 0, &iec))) {
        print_on();
        pout("unable to disable Exception control and warning [%s]\n",
             scsiErrString(err));
        print_off();
        return 1;
    }
    /* Need to refetch 'iec' since could be modified by previous call */
    if ((err = scsiFetchIECmpage(device, &iec, modese_len))) {
        pout("unable to fetch IEC (SMART) mode page [%s]\n",
             scsiErrString(err));
        return 1;
    } else
        modese_len = iec.modese_len;

    pout("Informational Exceptions (SMART) %s\n",
         scsi_IsExceptionControlEnabled(&iec) ? "enabled" : "disabled");
    pout("Temperature warning %s\n",
         scsi_IsWarningEnabled(&iec) ? "enabled" : "disabled");
    return 0;
}

static void
scsiPrintTemp(scsi_device * device)
{
    uint8_t temp = 255;
    uint8_t trip = 255;

    if (scsiGetTemp(device, &temp, &trip))
        return;

    if (255 == temp)
        pout("Current Drive Temperature:     <not available>\n");
    else {
        jout("Current Drive Temperature:     %d C\n", temp);
        jglb["temperature"]["current"] = temp;
    }
    if (255 == trip)
        pout("Drive Trip Temperature:        <not available>\n");
    else {
        jout("Drive Trip Temperature:        %d C\n", trip);
        jglb["temperature"]["drive_trip"] = trip;
    }
    pout("\n");
}

static void
scsiPrintEnviroReporting(scsi_device * device)
{
    int len, num, err;
    int temp_num = 0;
    int humid_num = 0;
    unsigned char * ucp;
    const char * q;
    static const char * hname = "Environmental Reports";
    static const char * jname = "scsi_environmental_reports";
    static const char * rh_n = "relative humidity";
    static const char * temp_n = "temperature";
    static const char * sop_n = "since power on";
    static const char * unkn_n = "unknown";

   if ((err = scsiLogSense(device, TEMPERATURE_LPAGE, ENVIRO_REP_L_SPAGE,
                           gBuf, LOG_RESP_LEN, -1 /* single fetch */))) {
        print_on();
        pout("%s Failed [%s]\n", __func__, scsiErrString(err));
        print_off();
        return;
    }
    if (((gBuf[0] & 0x3f) != TEMPERATURE_LPAGE) ||
        (gBuf[1] != ENVIRO_REP_L_SPAGE)) {
        print_on();
        pout("%s %s Failed, page mismatch\n", hname, logSenStr);
        print_off();
        return;
    }
    if (! (gBuf[0] & 0x40)) {
        if (scsi_debugmode > 0) {
            print_on();
            pout("Another flaky device that doesn't set the SPF bit\n");
            print_off();
        }
    }
    len = sg_get_unaligned_be16(gBuf + 2);
    num = len - 4;
    ucp = &gBuf[0] + 4;

    while (num > 3) {
        int pc = sg_get_unaligned_be16(ucp + 0);
        int pl = ucp[3] + 4;
        char pc_s[32];
        std::string s;

        if ((pc < 0x100) && (pl == 12)) {
            snprintf(pc_s, sizeof(pc_s), "temperature_%d", ++temp_num);
            /* temperature is two's complement 8 bit in centigrade */
            int temp = (int)(int8_t)ucp[5];

            jglb[jname][pc_s]["parameter_code"] = pc;
            q = "Current";
            s = json::str2key(q);
            if (ucp[5] == 0x80) {
                jout("%s %s = %s\n", q, temp_n, unkn_n);
                jglb[jname][pc_s][s] = unkn_n;
            } else {
                jout("%s %s = %d\n", q, temp_n, temp);
                jglb[jname][pc_s][s] = temp;
            }
            temp = (int)(int8_t)ucp[6];
            q = "Lifetime maximum";
            s = json::str2key(q);
            if (ucp[6] == 0x80) {
                jout("%s %s = %s\n", q, temp_n, unkn_n);
                jglb[jname][pc_s][s] = unkn_n;
            } else {
                jout("%s %s = %d\n", q, temp_n, temp);
                jglb[jname][pc_s][s] = temp;
            }
            temp = (int)(int8_t)ucp[7];
            q = "Lifetime minimum";
            s = json::str2key(q);
            if (ucp[7] == 0x80) {
                jout("%s %s = %s\n", q, temp_n, unkn_n);
                jglb[jname][pc_s][s] = unkn_n;
            } else {
                jout("%s %s = %d\n", q, temp_n, temp);
                jglb[jname][pc_s][s] = temp;
            }
            temp = (int)(int8_t)ucp[8];
            q = "Maximum since power on";
            s = json::str2key(q);
            if (ucp[8] == 0x80) {
                jout("Maximum %s %s = %s\n", temp_n, sop_n, unkn_n);
                jglb[jname][pc_s][s] = unkn_n;
            } else {
                jout("Maximum %s %s = %d\n", temp_n, sop_n, temp);
                jglb[jname][pc_s][s] = temp;
            }
            temp = (int)(int8_t)ucp[9];
            q = "Minimum since power on";
            s = json::str2key(q);
            if (ucp[9] == 0x80) {
                jout("Minimum %s %s = %s\n", temp_n, sop_n, unkn_n);
                jglb[jname][pc_s][s] = unkn_n;
            } else {
                jout("Minimum %s %s = %d\n", temp_n, sop_n, temp);
                jglb[jname][pc_s][s] = temp;
            }
            if ((ucp[4] & 0x3) == 1) {  /* OTV field set to 1 */
                temp = (int)(int8_t)ucp[10];
                q = "Maximum other";
                s = json::str2key(q);
                if (ucp[10] == 0x80) {
                    jout("%s %s = %s\n", q, temp_n, unkn_n);
                    jglb[jname][pc_s][s] = unkn_n;
                } else {
                    jout("%s %s = %d\n", q, temp_n, temp);
                    jglb[jname][pc_s][s] = temp;
                }
                temp = (int)(int8_t)ucp[11];
                q = "Minimum other";
                s = json::str2key(q);
                if (ucp[11] == 0x80) {
                    jout("%s %s = %s\n", q, temp_n, unkn_n);
                    jglb[jname][pc_s][s] = unkn_n;
                } else {
                    jout("%s %s = %d\n", q, temp_n, temp);
                    jglb[jname][pc_s][s] = temp;
                }
            }
        } else if ((pc < 0x200) && (pl == 12)) {
            snprintf(pc_s, sizeof(pc_s), "relative_humidity_%d", ++humid_num);
            jglb[jname][pc_s]["parameter_code"] = pc;
            jout("Relative humidity = %u\n", ucp[5]);
            jglb[jname][pc_s]["current"] = ucp[5];
            q = "Lifetime maximum";
            s = json::str2key(q);
            jout("%s %s = %d\n", q, rh_n, ucp[6]);
            jglb[jname][pc_s][s] = ucp[6];
            q = "Lifetime minimum";
            s = json::str2key(q);
            jout("%s %s = %d\n", q, rh_n, ucp[7]);
            jglb[jname][pc_s][s] = ucp[7];
            jout("Maximum %s %s = %d\n", rh_n, sop_n, ucp[8]);
            jglb[jname][pc_s]["maximum_since_power_on"] = ucp[8];
            jout("Minimum %s %s = %d\n", rh_n, sop_n, ucp[9]);
            jglb[jname][pc_s]["minimum_since_power_on"] = ucp[9];
            if ((ucp[4] & 0x3) == 1) {  /* ORHV field set to 1 */
                q = "Maximum other";
                s = json::str2key(q);
                jout("%s %s = %d\n", q, rh_n, ucp[10]);
                jglb[jname][pc_s][s] = ucp[10];
                q = "Minimum other";
                s = json::str2key(q);
                jout("%s %s = %d\n", q, rh_n, ucp[11]);
                jglb[jname][pc_s][s] = ucp[11];
            }
        } else {
            if (scsi_debugmode > 0) {
                print_on();
                if ((pc < 0x200) && (pl != 12))
                    pout("%s sub-lpage unexpected parameter length [%d], skip\n",
                         hname, pl);
                else
                    pout("%s sub-lpage has unexpected parameter [0x%x], skip\n",
                         hname, pc);
                print_off();
            }
            return;
        }
        num -= pl;
        ucp += pl;
    }
}


/* Main entry point used by smartctl command. Return 0 for success */
int
scsiPrintMain(scsi_device * device, const scsi_print_options & options)
{
    bool envRepDone = false;
    uint8_t peripheral_type = 0;
    int returnval = 0;
    int res, durationSec;
    struct scsi_sense_disect sense_info;
    bool is_disk;
    bool is_zbc;
    bool is_tape;
    bool any_output = options.drive_info;

// Enable -n option for SCSI Drives
    const char * powername = nullptr;
    bool powerchg = false;

    if (options.powermode) {
        scsiRequestSense(device, &sense_info) ;
        if (sense_info.asc == 0x5E) {
            unsigned char powerlimit = 0xff;
            int     powermode = sense_info.ascq ;

            // 5Eh/00h  DZTPRO A  K    LOW POWER CONDITION ON
            // 5Eh/01h  DZTPRO A  K    IDLE CONDITION ACTIVATED BY TIMER
            // 5Eh/02h  DZTPRO A  K    STANDBY CONDITION ACTIVATED BY TIMER
            // 5Eh/03h  DZTPRO A  K    IDLE CONDITION ACTIVATED BY COMMAND
            // 5Eh/04h  DZTPRO A  K    STANDBY CONDITION ACTIVATED BY COMMAND
            // 5Eh/05h  DZTPRO A  K    IDLE_B CONDITION ACTIVATED BY TIMER
            // 5Eh/06h  DZTPRO A  K    IDLE_B CONDITION ACTIVATED BY COMMAND
            // 5Eh/07h  DZTPRO A  K    IDLE_C CONDITION ACTIVATED BY TIMER
            // 5Eh/08h  DZTPRO A  K    IDLE_C CONDITION ACTIVATED BY COMMAND
            // 5Eh/09h  DZTPRO A  K    STANDBY_Y CONDITION ACTIVATED BY TIMER
            // 5Eh/0Ah  DZTPRO A  K    STANDBY_Y CONDITION ACTIVATED BY COMMAND
            // 5Eh/41h           B     POWER STATE CHANGE TO ACTIVE
            // 5Eh/42h           B     POWER STATE CHANGE TO IDLE
            // 5Eh/43h           B     POWER STATE CHANGE TO STANDBY
            // 5Eh/45h           B     POWER STATE CHANGE TO SLEEP
            // 5Eh/47h           BK    POWER STATE CHANGE TO DEVICE CONTROL

            switch (powermode) {
            case -1:
               if (device->is_syscall_unsup()) {
                   pout("CHECK POWER MODE not implemented, ignoring -n option\n"); break;
                }
                powername = "SLEEP";   powerlimit = 2;
                break;

            case 0x00: // LOW POWER CONDITION ON
                powername = "LOW POWER"; powerlimit = 2; break;
            case 0x01: // IDLE CONDITION ACTIVATED BY TIMER
                powername = "IDLE BY TIMER"; powerlimit = 4; break;
            case 0x02: // STANDBY CONDITION ACTIVATED BY TIMER
                powername = "STANDBY BY TIMER";    powerlimit = 2; break;
            case 0x03: // IDLE CONDITION ACTIVATED BY COMMAND
                powername = "IDLE BY COMMAND";  powerlimit = 4; break;
            case 0x04: // STANDBY CONDITION ACTIVATED BY COMMAND
                powername = "STANDBY BY COMMAND";  powerlimit = 2; break;
            case 0x05: // IDLE_B CONDITION ACTIVATED BY TIMER
                powername = "IDLE BY TIMER";  powerlimit = 4; break;
            case 0x06: // IDLE_B CONDITION ACTIVATED BY COMMAND
                powername = "IDLE_ BY COMMAND";  powerlimit = 4; break;
            case 0x07: // IDLE_C CONDITION ACTIVATED BY TIMER
                powername = "IDLE_C BY TIMER";  powerlimit = 4; break;
            case 0x08: // IDLE_C CONDITION ACTIVATED BY COMMAND
                powername = "IDLE_C BY COMMAND";  powerlimit = 4; break;
            case 0x09: // STANDBY_Y CONDITION ACTIVATED BY TIMER
                powername = "STANDBY_Y BY TIMER";    powerlimit = 2; break;
            case 0x0A: // STANDBY_Y CONDITION ACTIVATED BY COMMAND
                powername = "STANDBY_Y BY COMMAND";  powerlimit = 2; break;

            default:
                pout("CHECK POWER MODE returned unknown value 0x%02x, "
                     "ignoring -n option\n", powermode);
                break;
            }
            if (powername) {
                if (options.powermode >= powerlimit) {
                    jinf("Device is in %s mode, exit(%d)\n", powername, options.powerexit);
                    return options.powerexit;
                }
                powerchg = (powermode != 0xff);
            }
        } else
            powername = "ACTIVE";
    }

    delete supported_vpd_pages_p;
    supported_vpd_pages_p = new supported_vpd_pages(device);

    res = scsiGetDriveInfo(device, &peripheral_type, is_zbc,
                           options.drive_info || options.farm_log);
    if (res) {
        if (2 == res)
            return 0;
        else
            failuretest(MANDATORY_CMD, returnval |= FAILID);
        any_output = true;
    }
    is_disk = ((SCSI_PT_DIRECT_ACCESS == peripheral_type) ||
               (SCSI_PT_HOST_MANAGED == peripheral_type));
    is_tape = ((SCSI_PT_SEQUENTIAL_ACCESS == peripheral_type) ||
               (SCSI_PT_MEDIUM_CHANGER == peripheral_type));
    bool ge_spc4 = device->is_spc4_or_higher();
    if (ge_spc4 && (! device->checked_cmd_support())) {
        if (! device->query_cmd_support()) {
            if (scsi_debugmode)
                pout("%s: query_cmd_support() failed\n", __func__);
        }
    }

    short int wce = -1, rcd = -1;
    // Print read look-ahead status for disks
    if (options.get_rcd || options.get_wce) {
        if (is_disk) {
            res = scsiGetSetCache(device, modese_len, &wce, &rcd);
            if (options.get_rcd)
                pout("Read Cache is:        %s\n",
                     res ? "Unavailable" : // error
                     rcd ? "Disabled" : "Enabled");
            if (options.get_wce)
                pout("Writeback Cache is:   %s\n",
                     res ? "Unavailable" : // error
                     !wce ? "Disabled" : "Enabled");
        }
        any_output = true;
    }

    if (options.drive_info) {
        if (powername)   // Print power condition if requested -n (nocheck)
            pout("Power mode %s       %s\n", (powerchg?"was:":"is: "), powername);
        pout("\n");
    }

    // START OF THE ENABLE/DISABLE SECTION OF THE CODE
    if (options.smart_disable           || options.smart_enable ||
        options.smart_auto_save_disable || options.smart_auto_save_enable)
        pout("=== START OF ENABLE/DISABLE COMMANDS SECTION ===\n");

    if (options.smart_enable) {
        if (scsiSmartEnable(device))
            failuretest(MANDATORY_CMD, returnval |= FAILSMART);
        any_output = true;
    }

    if (options.smart_disable) {
        if (scsiSmartDisable(device))
            failuretest(MANDATORY_CMD,returnval |= FAILSMART);
        any_output = true;
    }

    if (options.smart_auto_save_enable) {
        if (scsiSetControlGLTSD(device, 0, modese_len)) {
            pout("Enable autosave (clear GLTSD bit) failed\n");
            failuretest(OPTIONAL_CMD,returnval |= FAILSMART);
        } else
            pout("Autosave enabled (GLTSD bit cleared).\n");
        any_output = true;
    }

    // Enable/Disable write cache
    if (options.set_wce && is_disk) {
        short int enable = wce = (options.set_wce > 0);

        rcd = -1;
        if (scsiGetSetCache(device, modese_len, &wce, &rcd)) {
            pout("Write cache %sable failed: %s\n", (enable ? "en" : "dis"),
                 device->get_errmsg());
            failuretest(OPTIONAL_CMD,returnval |= FAILSMART);
        } else
            pout("Write cache %sabled\n", (enable ? "en" : "dis"));
        any_output = true;
    }

    // Enable/Disable read cache
    if (options.set_rcd && is_disk) {
        short int enable =  (options.set_rcd > 0);

        rcd = !enable;
        wce = -1;
        if (scsiGetSetCache(device, modese_len, &wce, &rcd)) {
            pout("Read cache %sable failed: %s\n", (enable ? "en" : "dis"),
                device->get_errmsg());
            failuretest(OPTIONAL_CMD,returnval |= FAILSMART);
        } else
            pout("Read cache %sabled\n", (enable ? "en" : "dis"));
        any_output = true;
    }

    if (options.smart_auto_save_disable) {
        if (scsiSetControlGLTSD(device, 1, modese_len)) {
            pout("Disable autosave (set GLTSD bit) failed\n");
            failuretest(OPTIONAL_CMD,returnval |= FAILSMART);
        } else
            pout("Autosave disabled (GLTSD bit set).\n");
        any_output = true;
    }
    if (options.smart_disable           || options.smart_enable ||
        options.smart_auto_save_disable || options.smart_auto_save_enable)
        pout("\n"); // END OF THE ENABLE/DISABLE SECTION OF THE CODE

    // START OF READ-ONLY OPTIONS APART FROM -V and -i
    if (options.smart_check_status  || options.smart_ss_media_log ||
        options.smart_vendor_attrib || options.smart_error_log ||
        options.smart_selftest_log  || options.smart_background_log ||
        options.sasphy)
        pout("=== START OF READ SMART DATA SECTION ===\n");

    // Most of the following need log page data. Check for the supported log
    // pages unless we have been told by RSOC that LOG SENSE is not supported
    if (SC_NO_SUPPORT != device->cmd_support_level(LOG_SENSE, false, 0))
        scsiGetSupportedLogPages(device);

    if (options.smart_check_status) {
        if (is_tape) {
            if (gTapeAlertsLPage) {
                if (options.drive_info) {
                    jout("TapeAlert Supported\n");
                    jglb["tapealert"]["supported"] = true;
                }
                if (options.health_opt_count > 1) {
                    if (-1 == scsiPrintActiveTapeAlerts(device, peripheral_type, true))
                        failuretest(OPTIONAL_CMD, returnval |= FAILSMART);
                }
            } else {
                jout("TapeAlert Not Supported\n");
                jglb["tapealert"]["supported"] = false;
            }
        } else { /* disk, cd/dvd, enclosure, etc */
            if ((res = scsiGetSmartData(device,
                                        options.smart_vendor_attrib))) {
                if (-2 == res)
                    returnval |= FAILSTATUS;
                else
                    returnval |= FAILSMART;
            }
        }
        any_output = true;
    }

    if (is_disk && options.smart_ss_media_log) {
        res = 0;
        if (gSSMediaLPage)
            res = scsiPrintSSMedia(device);
        if (0 != res)
            failuretest(OPTIONAL_CMD, returnval|=res);
        if (gFormatStatusLPage)
            res = scsiPrintFormatStatus(device);
        if (0 != res)
            failuretest(OPTIONAL_CMD, returnval|=res);
        any_output = true;
    }
    if (options.smart_vendor_attrib) {
        if (gEnviroReportingLPage && options.smart_env_rep) {
            scsiPrintEnviroReporting(device);
            envRepDone = true;
        } else if (gTempLPage)
            scsiPrintTemp(device);
        // in the 'smartctl -A' case only want: "Accumulated power on time"
        if ((! options.smart_background_log) && is_disk) {
            res = 0;
            if (gBackgroundResultsLPage)
                res = scsiPrintBackgroundResults(device, true);
            (void)res; // not yet used below, suppress warning
        }
        if (gStartStopLPage)
            scsiGetStartStopData(device);
        if (is_disk) {
            enum scsi_cmd_support rdefect10 =
                        device->cmd_support_level(READ_DEFECT_10, false, 0);
            enum scsi_cmd_support rdefect12 =
                        device->cmd_support_level(READ_DEFECT_12, false, 0);

            if ((SC_NO_SUPPORT == rdefect10) && (SC_NO_SUPPORT == rdefect12))
                ;
            else if (SC_SUPPORT == rdefect12)
                scsiPrintGrownDefectListLen(device, true);
            else
                scsiPrintGrownDefectListLen(device, false);

            if (gSeagateCacheLPage)
                scsiPrintSeagateCacheLPage(device);
            if (gSeagateFactoryLPage)
                scsiPrintSeagateFactoryLPage(device);
        }
        any_output = true;
    }
    // Print SCSI FARM log for Seagate SCSI drive
    if (options.farm_log || options.farm_log_suggest) {
        bool farm_supported = true;
        // Check if drive is a Seagate drive that supports FARM
        if (gSeagateFarmLPage) {
            // If -x/-xall or -a/-all is run without explicit -l farm, suggests FARM log
            if (options.farm_log_suggest && !options.farm_log) {
                jout("Seagate FARM log supported [try: -l farm]\n\n");
            // Otherwise, actually pull the FARM log
            } else {
                scsiFarmLog farmLog;
                if (!scsiReadFarmLog(device, farmLog)) {
                    pout("\nRead FARM log (SCSI Log page 0x3d, sub-page 0x3) failed\n\n");
                    farm_supported = false;
                } else {
                    scsiPrintFarmLog(farmLog);
                }
            }
        } else {
            if (options.farm_log) {
                jout("\nFARM log (SCSI Log page 0x3d, sub-page 0x3) not supported\n\n");
            }
            farm_supported = false;
        }
        jglb["seagate_farm_log"]["supported"] = farm_supported;
    }
    if (options.smart_error_log || options.scsi_pending_defects) {
        if (options.smart_error_log) {
            scsiPrintErrorCounterLog(device);
            any_output = true;
        }
        if (gPendDefectsLPage) {
            scsiPrintPendingDefectsLPage(device);
            any_output = true;
        }
        if (options.smart_error_log) {
            if (1 == scsiFetchControlGLTSD(device, modese_len, 1)) {
                pout("\n[GLTSD (Global Logging Target Save Disable) set. "
                     "Enable Save with '-S on']\n");
                any_output = true;
            }
        }
    }
    if (options.smart_selftest_log) {
        res = 0;
        if (gSelfTestLPage)
            res = scsiPrintSelfTest(device);
        else {
            pout("Device does not support Self Test logging\n");
            if (! is_tape)
                failuretest(OPTIONAL_CMD, returnval|=FAILSMART);
        }
        if (0 != res)
            failuretest(OPTIONAL_CMD, returnval|=res);
        any_output = true;
    }
    if (options.smart_background_log && is_disk) {
        res = 0;
        if (gBackgroundResultsLPage)
            res = scsiPrintBackgroundResults(device, false);
        else {
            pout("Device does not support Background scan results logging\n");
            failuretest(OPTIONAL_CMD, returnval|=FAILSMART);
        }
        if (0 != res)
            failuretest(OPTIONAL_CMD, returnval|=res);
        any_output = true;
    }
    if (options.zoned_device_stats && is_zbc) {
        res = 0;
        if (gZBDeviceStatsLPage)
            res = scsiPrintZBDeviceStats(device);
        else {
            pout("Device does not support %s logging\n", zbds_s);
            failuretest(OPTIONAL_CMD, returnval|=FAILSMART);
        }
        if (0 != res)
            failuretest(OPTIONAL_CMD, returnval|=res);
        any_output = true;
    }
    if (options.general_stats_and_perf) {
        res = 0;
        if (gGenStatsAndPerfLPage)
            res = scsiPrintGStatsPerf(device);
        else {
            pout("Device does not support %s logging\n", gsap_s);
            failuretest(OPTIONAL_CMD, returnval|=FAILSMART);
        }
        if (0 != res)
            failuretest(OPTIONAL_CMD, returnval|=res);
        any_output = true;

    }
    if (is_tape) {
        if (options.tape_device_stats) {
            res = 0;
            if (gTapeDeviceStatsLPage) {
                res = scsiPrintTapeDeviceStats(device);
            } else {
                pout("Device does not support (tape) device characteristics "
                     "(SSC) logging\n");
                failuretest(OPTIONAL_CMD, returnval|=FAILSMART);
            }
            if (0 != res)
                failuretest(OPTIONAL_CMD, returnval|=res);
            any_output = true;
        }
        if (options.tape_alert) {
            res = 0;
            if (gTapeAlertsLPage) {
                res = scsiPrintActiveTapeAlerts(device, peripheral_type, false);
            } else {
                pout("Device does not support TapeAlert logging\n");
                failuretest(OPTIONAL_CMD, returnval|=FAILSMART);
            }
            if (res < 0)
                failuretest(OPTIONAL_CMD, returnval|=res);
            if ((scsi_debugmode > 0) && (res == 0))
                pout("TapeAlerts only printed if active, so none printed is good\n");
            any_output = true;
        }
    }
    if (options.smart_default_selftest) {
        if (scsiSmartDefaultSelfTest(device))
            return returnval | FAILSMART;
        pout("Default Self Test Successful\n");
        any_output = true;
    }
    if (options.smart_short_cap_selftest) {
        if (scsiSmartShortCapSelfTest(device))
            return returnval | FAILSMART;
        pout("Short Foreground Self Test Successful\n");
        any_output = true;
    }
    // check if another test is running
    if (options.smart_short_selftest || options.smart_extend_selftest) {
        if (!scsiRequestSense(device, &sense_info) &&
            (sense_info.asc == 0x04 && sense_info.ascq == 0x09)) {
            if (!options.smart_selftest_force) {
                pout("Can't start self-test without aborting current test");
                if (sense_info.progress != -1)
                    pout(" (%d%% remaining)",
                         100 - sense_info.progress * 100 / 65535);
                pout(",\nadd '-t force' option to override, or run "
                     "'smartctl -X' to abort test.\n");
                return -1;
            } else
                scsiSmartSelfTestAbort(device);
        }
    }
    if (options.smart_short_selftest) {
        if (scsiSmartShortSelfTest(device))
            return returnval | FAILSMART;
        pout("Short Background Self Test has begun\n");
        pout("Use smartctl -X to abort test\n");
        any_output = true;
    }
    if (options.smart_extend_selftest) {
        if (scsiSmartExtendSelfTest(device))
            return returnval | FAILSMART;
        pout("Extended Background Self Test has begun\n");
        if ((0 == scsiFetchExtendedSelfTestTime(device, &durationSec,
                        modese_len)) && (durationSec > 0)) {
            time_t t = time(nullptr);

            t += durationSec;
            pout("Please wait %d minutes for test to complete.\n",
                 durationSec / 60);
            char comptime[DATEANDEPOCHLEN];
            dateandtimezoneepoch(comptime, t);
            pout("Estimated completion time: %s\n", comptime);
        }
        pout("Use smartctl -X to abort test\n");
        any_output = true;
    }
    if (options.smart_extend_cap_selftest) {
        if (scsiSmartExtendCapSelfTest(device))
            return returnval | FAILSMART;
        pout("Extended Foreground Self Test Successful\n");
    }
    if (options.smart_selftest_abort) {
        if (scsiSmartSelfTestAbort(device))
            return returnval | FAILSMART;
        pout("Self Test returned without error\n");
        any_output = true;
    }
    if (options.sasphy) {
        if (gProtocolSpecificLPage) {
            if (scsiPrintSasPhy(device, options.sasphy_reset))
                return returnval | FAILSMART;
            any_output = true;
        }
    }
    if (options.smart_env_rep && ! envRepDone) {
        if (gEnviroReportingLPage) {
            scsiPrintEnviroReporting(device);
            any_output = true;
        }
    }

    if (options.set_standby == 1) {
        if (scsiSetPowerCondition(device, SCSI_POW_COND_ACTIVE)) {
            pout("SCSI SSU(ACTIVE) command failed: %s\n",
                 device->get_errmsg());
            returnval |= FAILSMART;
        } else
            pout("Device placed in ACTIVE mode\n");
    } else if (options.set_standby > 1) {
        pout("SCSI SSU(STANDBY) with timeout not supported yet\n");
        returnval |= FAILSMART;
    } else if (options.set_standby_now) {
        if (scsiSetPowerCondition(device, SCSI_POW_COND_STANDBY)) {
            pout("SCSI STANDBY command failed: %s\n", device->get_errmsg());
            returnval |= FAILSMART;
        } else
            pout("Device placed in STANDBY mode\n");
    }

    if (!any_output && powername) // Output power mode if -n (nocheck)
        pout("Device is in %s mode\n", powername);

    if (!any_output)
        pout("SCSI device successfully opened\n\nUse 'smartctl -a' (or '-x') "
             "to print SMART (and more) information\n\n");

    return returnval;
}
