diff options
Diffstat (limited to '')
-rw-r--r-- | scsiprint.cpp | 3971 |
1 files changed, 3971 insertions, 0 deletions
diff --git a/scsiprint.cpp b/scsiprint.cpp new file mode 100644 index 0000000..8b07d9a --- /dev/null +++ b/scsiprint.cpp @@ -0,0 +1,3971 @@ +/* + * 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, + ¤ttemp, &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; +} |