/* * scsiprint.c * * Home page of code is: http://smartmontools.sourceforge.net * * Copyright (C) 2002-3 Bruce Allen <smartmontools-support@lists.sourceforge.net> * Copyright (C) 2000 Michael Cornwell <cornwell@acm.org> * * Additional SCSI work: * Copyright (C) 2003 Douglas Gilbert <dougg@torque.net> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * You should have received a copy of the GNU General Public License * (for example COPYING); if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * This code was originally developed as a Senior Thesis by Michael Cornwell * at the Concurrent Systems Laboratory (now part of the Storage Systems * Research Center), Jack Baskin School of Engineering, University of * California, Santa Cruz. http://ssrc.soe.ucsc.edu/ * */ #include <stdio.h> #include <unistd.h> #include <string.h> #include <fcntl.h> #include <errno.h> #include "extern.h" #include "scsicmds.h" #include "scsiprint.h" #include "smartctl.h" #include "utility.h" #define GBUF_SIZE 65535 const char* scsiprint_c_cvsid="$Id: scsiprint.cpp,v 1.49 2003/06/17 06:10:08 dpgilbert Exp $" EXTERN_H_CVSID SCSICMDS_H_CVSID SCSIPRINT_H_CVSID SMARTCTL_H_CVSID UTILITY_H_CVSID; // control block which points to external global control variables extern smartmonctrl *con; UINT8 gBuf[GBUF_SIZE]; #define LOG_RESP_LEN 252 #define LOG_RESP_TAPE_ALERT_LEN 0x144 #define LOG_RESP_SELF_TEST_LEN 0x194 /* Log pages supported */ static int gSmartLPage = 0; /* Informational Exceptions log page */ static int gTempLPage = 0; static int gSelfTestLPage = 0; static int gStartStopLPage = 0; static int gTapeAlertsLPage = 0; /* Mode pages supported */ static int gIecMPage = 1; /* N.B. assume it until we know otherwise */ static void scsiGetSupportedLogPages(int device) { int i, err; if ((err = scsiLogSense(device, SUPPORTED_LOG_PAGES, gBuf, LOG_RESP_LEN, 0))) { if (con->reportscsiioctl > 0) pout("Log Sense for supported pages failed [%s]\n", scsiErrString(err)); return; } for (i = 4; i < gBuf[3] + LOGPAGEHDRSIZE; i++) { switch (gBuf[i]) { case TEMPERATURE_PAGE: gTempLPage = 1; break; case STARTSTOP_CYCLE_COUNTER_PAGE: gStartStopLPage = 1; break; case SELFTEST_RESULTS_PAGE: gSelfTestLPage = 1; break; case IE_LOG_PAGE: gSmartLPage = 1; break; case TAPE_ALERTS_PAGE: gTapeAlertsLPage = 1; break; default: break; } } } void scsiGetSmartData(int device) { UINT8 asc; UINT8 ascq; UINT8 currenttemp = 0; const char * cp; int err; QUIETON(con); if ((err = scsiCheckIE(device, gSmartLPage, gTempLPage, &asc, &ascq, ¤ttemp))) { /* error message already announced */ QUIETOFF(con); return; } QUIETOFF(con); cp = scsiGetIEString(asc, ascq); if (cp) { QUIETON(con); pout("SMART Health Status: %s [asc=%x,ascq=%x]\n", cp, asc, ascq); QUIETOFF(con); } else if (gIecMPage) pout("SMART Health Status: OK\n"); if (currenttemp && !gTempLPage) { if (255 != currenttemp) pout("Current Drive Temperature: %d C\n", currenttemp); else pout("Current Drive Temperature: <not available>\n"); } } // Returns number of logged errors or zero if none or -1 if fetching // TapeAlerts fails static char *severities = "CWI"; static int scsiGetTapeAlertsData(int device, int peripheral_type) { unsigned short pagelength; unsigned short parametercode; int i, err; char *s; const char *ts; int failures = 0; QUIETON(con); if ((err = scsiLogSense(device, TAPE_ALERTS_PAGE, gBuf, LOG_RESP_TAPE_ALERT_LEN, LOG_RESP_TAPE_ALERT_LEN))) { pout("scsiGetTapesAlertData Failed [%s]\n", scsiErrString(err)); QUIETOFF(con); return -1; } if (gBuf[0] != 0x2e) { pout("TapeAlerts Log Sense Failed\n"); QUIETOFF(con); return -1; } pagelength = (unsigned short) gBuf[2] << 8 | gBuf[3]; for (s=severities; *s; s++) { for (i = 4; i < pagelength; i += 5) { parametercode = (unsigned short) gBuf[i] << 8 | gBuf[i+1]; if (gBuf[i + 4]) { ts = SCSI_PT_MEDIUM_CHANGER == peripheral_type ? scsiTapeAlertsChangerDevice(parametercode) : scsiTapeAlertsTapeDevice(parametercode); if (*ts == *s) { if (!failures) pout("TapeAlert Errors (C=Critical, W=Warning, I=Informational):\n"); pout("[0x%02x] %s\n", parametercode, ts); failures += 1; } } } } QUIETOFF(con); if (! failures) pout("TapeAlert: OK\n"); return failures; } void scsiGetStartStopData(int device) { UINT32 currentStartStop; UINT32 recommendedStartStop; int err, len, k; char str[6]; if ((err = scsiLogSense(device, STARTSTOP_CYCLE_COUNTER_PAGE, gBuf, LOG_RESP_LEN, 0))) { QUIETON(con); pout("scsiGetStartStopData Failed [%s]\n", scsiErrString(err)); QUIETOFF(con); return; } if (gBuf[0] != STARTSTOP_CYCLE_COUNTER_PAGE) { QUIETON(con); pout("StartStop Log Sense Failed, page mismatch\n"); QUIETOFF(con); return; } len = ((gBuf[2] << 8) | gBuf[3]) + 4; if (len > 13) { for (k = 0; k < 2; ++k) str[k] = gBuf[12 + k]; str[k] = '\0'; pout("Manufactured in week %s of year ", str); for (k = 0; k < 4; ++k) str[k] = gBuf[8 + k]; str[k] = '\0'; pout("%s\n", str); } if (len > 39) { recommendedStartStop = (gBuf[28] << 24) | (gBuf[29] << 16) | (gBuf[30] << 8) | gBuf[31]; currentStartStop = (gBuf[36] << 24) | (gBuf[37] << 16) | (gBuf[38] << 8) | gBuf[39]; pout("Current start stop count: %u times\n", currentStartStop); pout("Recommended start stop count: %u times\n", recommendedStartStop); } } static void scsiPrintErrorCounterLog(int device) { struct scsiErrorCounter errCounterArr[3]; struct scsiErrorCounter * ecp; struct scsiNonMediumError nme; int found[3] = {0, 0, 0}; const char * pageNames[3] = {"read: ", "write: ", "verify: "}; int k; double processed_gb; if (0 == scsiLogSense(device, READ_ERROR_COUNTER_PAGE, gBuf, LOG_RESP_LEN, 0)) { scsiDecodeErrCounterPage(gBuf, &errCounterArr[0]); found[0] = 1; } if (0 == scsiLogSense(device, WRITE_ERROR_COUNTER_PAGE, gBuf, LOG_RESP_LEN, 0)) { scsiDecodeErrCounterPage(gBuf, &errCounterArr[1]); found[1] = 1; } if (0 == scsiLogSense(device, VERIFY_ERROR_COUNTER_PAGE, gBuf, LOG_RESP_LEN, 0)) { scsiDecodeErrCounterPage(gBuf, &errCounterArr[2]); ecp = &errCounterArr[2]; for (k = 0; k < 7; ++k) { if (ecp->gotPC[k] && ecp->counter[k]) { found[2] = 1; break; } } } if (found[0] || found[1] || found[2]) { pout("\nError counter log:\n"); pout(" Errors Corrected Total Total " "Correction Gigabytes Total\n"); pout(" delay: [rereads/ errors " "algorithm processed uncorrected\n"); pout(" minor | major rewrites] corrected " "invocations [10^9 bytes] errors\n"); for (k = 0; k < 3; ++k) { if (! found[k]) continue; ecp = &errCounterArr[k]; pout("%s%8llu %8llu %8llu %8llu %8llu", pageNames[k], ecp->counter[0], ecp->counter[1], ecp->counter[2], ecp->counter[3], ecp->counter[4]); processed_gb = ecp->counter[5] / 1000000000.0; pout(" %12.3f %8llu\n", processed_gb, ecp->counter[6]); } } else pout("\nDevice does not support Error Counter logging\n"); if (0 == scsiLogSense(device, NON_MEDIUM_ERROR_PAGE, gBuf, LOG_RESP_LEN, 0)) { scsiDecodeNonMediumErrPage(gBuf, &nme); if (nme.gotPC0) pout("\nNon-medium error count: %8llu\n", nme.counterPC0); } } const char * self_test_code[] = { "Default ", "Background short", "Background long ", "Reserved(3) ", "Abort background", "Foreground short", "Foreground long ", "Reserved(7) " }; const char * self_test_result[] = { "Completed ", "Interrupted ('-X' switch)", "Interrupted (bus reset ?)", "Unknown error, incomplete", "Completed, segment failed", "Failed in first segment ", "Failed in second segment ", "Failed in segment --> ", "Reserved(8) ", "Reserved(9) ", "Reserved(10) ", "Reserved(11) ", "Reserved(12) ", "Reserved(13) ", "Reserved(14) ", "Self test in progress ..." }; // See Working Draft SCSI Primary Commands - 3 (SPC-3) pages 231-233 // T10/1416-D Rev 10 static int scsiPrintSelfTest(int device) { int num, k, n, res, err, durationSec; int noheader = 1; UINT8 * ucp; unsigned long long ull=0; if ((err = scsiLogSense(device, SELFTEST_RESULTS_PAGE, gBuf, LOG_RESP_SELF_TEST_LEN, 0))) { QUIETON(con); pout("scsiPrintSelfTest Failed [%s]\n", scsiErrString(err)); QUIETOFF(con); return 1; } if (gBuf[0] != SELFTEST_RESULTS_PAGE) { QUIETON(con); pout("Self-test Log Sense Failed, page mismatch\n"); QUIETOFF(con); return 1; } // compute page length num = (gBuf[2] << 8) + gBuf[3]; // Log sense page length 0x190 bytes if (num != 0x190) { QUIETON(con); pout("Self-test Log Sense length is 0x%x not 0x190 bytes\n",num); QUIETOFF(con); return 1; } // loop through the twenty possible entries for (k = 0, ucp = gBuf + 4; k < 20; ++k, ucp += 20 ) { int i; // timestamp in power-on hours (or zero if test in progress) n = (ucp[6] << 8) | ucp[7]; // The spec says "all 20 bytes will be zero if no test" but // DG has found otherwise. So this is a heuristic. if ((0 == n) && (0 == ucp[4])) break; // only print header if needed if (noheader) { pout("\nSMART Self-test log\n"); pout("Num Test Status segment " "LifeTime LBA_first_err [SK ASC ASQ]\n"); pout(" Description number " "(hours)\n"); noheader=0; } // print parameter code (test number) & self-test code text pout("#%2d %s", (ucp[0] << 8) | ucp[1], self_test_code[(ucp[4] >> 5) & 0x7]); // self-test result res = ucp[4] & 0xf; pout(" %s", self_test_result[res]); // 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. if (ucp[5]) pout(" %3d", (int)ucp[5]); else pout(" -"); // print time that the self-test was completed if (n==0 && res==0xf) // self-test in progress pout(" NOW"); else pout(" %5d", n); // construct 8-byte integer address of first failure for (i = 0; i < 8; i++) { ull <<= 8; ull |= ucp[i+8]; } // print Address of First Failure, if sensible if ((0xffffffffffffffffULL != ull) && (res > 0) && ( res < 0xf)) pout(" 0x%16llx", ull); else pout(" -"); // if sense key nonzero, then print it, along with // additional sense code and additional sense code qualifier if (ucp[16] & 0xf) pout(" [0x%x 0x%x 0x%x]\n", ucp[16] & 0xf, ucp[17], ucp[18]); else pout(" [- - -]\n"); } // if header never printed, then there was no output if (noheader) pout("No self-tests have been logged\n"); else pout("\n"); if ((0 == scsiFetchExtendedSelfTestTime(device, &durationSec)) && (durationSec > 0)) pout("Long (extended) Self Test duration: %d seconds " "[%.1f minutes]\n", durationSec, durationSec / 60.0); return 0; } static const char * peripheral_dt_arr[] = { "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" }; /* Returns 0 on success */ static int scsiGetDriveInfo(int device, UINT8 * peripheral_type, int all) { char manufacturer[9]; char product[17]; char revision[5]; char timedatetz[64]; struct scsi_iec_mode_page iec; int err, len; int is_tape = 0; int peri_dt = 0; memset(gBuf, 0, 36); if ((err = scsiStdInquiry(device, gBuf, 36))) { QUIETON(con); pout("Standard Inquiry failed [%s]\n", scsiErrString(err)); QUIETOFF(con); return 1; } len = gBuf[4] + 5; peri_dt = gBuf[0] & 0x1f; if (peripheral_type) *peripheral_type = peri_dt; if (! all) return 0; if (len >= 36) { memset(manufacturer, 0, sizeof(manufacturer)); strncpy(manufacturer, &gBuf[8], 8); memset(product, 0, sizeof(product)); strncpy(product, &gBuf[16], 16); memset(revision, 0, sizeof(revision)); strncpy(revision, &gBuf[32], 4); pout("Device: %s %s Version: %s\n", manufacturer, product, revision); if (0 == (err = scsiInquiryVpd(device, 0x80, gBuf, 64))) { /* should use VPD page 0x83 and fall back to this page (0x80) * if 0x83 not supported. NAA requires a lot of decoding code */ len = gBuf[3]; gBuf[4 + len] = '\0'; pout("Serial number: %s\n", &gBuf[4]); } else if (con->reportscsiioctl > 0) { QUIETON(con); 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); QUIETOFF(con); } } else { QUIETON(con); pout("Short INQUIRY response, skip product id\n"); QUIETOFF(con); } // print SCSI peripheral device type if (peri_dt < (sizeof(peripheral_dt_arr) / sizeof(peripheral_dt_arr[0]))) pout("Device type: %s\n", peripheral_dt_arr[peri_dt]); else pout("Device type: <%d>\n", peri_dt); // print current time and date and timezone dateandtimezone(timedatetz); pout("Local Time is: %s\n", timedatetz); if ((SCSI_PT_SEQUENTIAL_ACCESS == *peripheral_type) || (SCSI_PT_MEDIUM_CHANGER == *peripheral_type)) is_tape = 1; // See if unit accepts SCSI commmands from us if ((err = scsiTestUnitReady(device))) { if (SIMPLE_ERR_NOT_READY == err) { QUIETON(con); pout("device is NOT READY (media absent, spun down, etc)\n"); QUIETOFF(con); } else { QUIETON(con); pout("device Test Unit Ready [%s]\n", scsiErrString(err)); QUIETOFF(con); } return 0; } if ((err = scsiFetchIECmpage(device, &iec))) { if (!is_tape) { QUIETON(con); pout("Device does not support SMART"); if (con->reportscsiioctl > 0) pout(" [%s]\n", scsiErrString(err)); else pout("\n"); if (SIMPLE_ERR_BAD_RESP == err) { pout(">> Terminate command early due to bad response to IEC " "mode page\n"); QUIETOFF(con); gIecMPage = 0; return 1; } QUIETOFF(con); } gIecMPage = 0; return 0; } if (!is_tape) pout("Device supports SMART and is %s\n", (scsi_IsExceptionControlEnabled(&iec)) ? "Enabled" : "Disabled"); pout("%s\n", (scsi_IsWarningEnabled(&iec)) ? "Temperature Warning Enabled" : "Temperature Warning Disabled or Not Supported"); return 0; } static int scsiSmartEnable(int device) { struct scsi_iec_mode_page iec; int err; if ((err = scsiFetchIECmpage(device, &iec))) { QUIETON(con); pout("unable to fetch IEC (SMART) mode page [%s]\n", scsiErrString(err)); QUIETOFF(con); return 1; } if ((err = scsiSetExceptionControlAndWarning(device, 1, &iec))) { QUIETON(con); pout("unable to enable Exception control and warning [%s]\n", scsiErrString(err)); QUIETOFF(con); return 1; } /* Need to refetch 'iec' since could be modified by previous call */ if ((err = scsiFetchIECmpage(device, &iec))) { pout("unable to fetch IEC (SMART) mode page [%s]\n", scsiErrString(err)); return 1; } 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(int device) { struct scsi_iec_mode_page iec; int err; if ((err = scsiFetchIECmpage(device, &iec))) { QUIETON(con); pout("unable to fetch IEC (SMART) mode page [%s]\n", scsiErrString(err)); QUIETOFF(con); return 1; } if ((err = scsiSetExceptionControlAndWarning(device, 0, &iec))) { QUIETON(con); pout("unable to disable Exception control and warning [%s]\n", scsiErrString(err)); QUIETOFF(con); return 1; } /* Need to refetch 'iec' since could be modified by previous call */ if ((err = scsiFetchIECmpage(device, &iec))) { pout("unable to fetch IEC (SMART) mode page [%s]\n", scsiErrString(err)); return 1; } pout("Informational Exceptions (SMART) %s\n", scsi_IsExceptionControlEnabled(&iec) ? "enabled" : "disabled"); pout("Temperature warning %s\n", scsi_IsWarningEnabled(&iec) ? "enabled" : "disabled"); return 0; } void scsiPrintTemp(int device) { UINT8 temp = 0; UINT8 trip = 0; if (scsiGetTemp(device, &temp, &trip)) return; if (temp) { if (255 != temp) pout("Current Drive Temperature: %d C\n", temp); else pout("Current Drive Temperature: <not available>\n"); } if (trip) pout("Drive Trip Temperature: %d C\n", trip); } // Compares failure type to policy in effect, and either exits or // simply returns to the calling routine. static void failuretest(int type, int returnvalue) { // If this is an error in an "optional" SMART command if (type == OPTIONAL_CMD) { if (con->conservative) { pout("An optional SMART command has failed: exiting.\n" "To continue, set the tolerance level to something other " "than 'conservative'\n"); exit(returnvalue); } return; } // If this is an error in a "mandatory" SMART command if (type==MANDATORY_CMD) { if (con->permissive) return; pout("A mandatory SMART command has failed: exiting. To continue, " "use the -T option to set the tolerance level to 'permissive'\n"); exit(returnvalue); } pout("Smartctl internal error in failuretest(type=%d). Please contact " "%s\n",type,PROJECTHOME); exit(returnvalue|FAILCMD); } /* Main entry point used by smartctl command. Return 0 for success */ int scsiPrintMain(const char *dev_name, int fd) { int checkedSupportedLogPages = 0; UINT8 peripheral_type = 0; int returnval=0; int res; if (scsiGetDriveInfo(fd, &peripheral_type, con->driveinfo)) { failuretest(MANDATORY_CMD, returnval |= FAILID); } if (con->smartenable) { if (scsiSmartEnable(fd)) failuretest(MANDATORY_CMD, returnval |= FAILSMART); } if (con->smartdisable) { if (scsiSmartDisable(fd)) failuretest(MANDATORY_CMD,returnval |= FAILSMART); } if (con->checksmart) { scsiGetSupportedLogPages(fd); checkedSupportedLogPages = 1; if ((SCSI_PT_SEQUENTIAL_ACCESS == peripheral_type) || (SCSI_PT_MEDIUM_CHANGER == peripheral_type)) { /* tape device */ if (gTapeAlertsLPage) { if (con->driveinfo) pout("TapeAlert Supported\n"); if (-1 == scsiGetTapeAlertsData(fd, peripheral_type)) failuretest(OPTIONAL_CMD, returnval |= FAILSMART); } else pout("TapeAlert Not Supported\n"); if (gTempLPage) scsiPrintTemp(fd); if (gStartStopLPage) scsiGetStartStopData(fd); } else { /* disk, cd/dvd, enclosure, etc */ scsiGetSmartData(fd); if (gTempLPage) scsiPrintTemp(fd); if (gStartStopLPage) scsiGetStartStopData(fd); } } if (con->smarterrorlog) scsiPrintErrorCounterLog(fd); if (con->smartselftestlog) { if (! checkedSupportedLogPages) scsiGetSupportedLogPages(fd); res = 0; if (gSelfTestLPage) res = scsiPrintSelfTest(fd); else { pout("Device does not support Self Test logging\n"); failuretest(OPTIONAL_CMD, returnval|=FAILSMART); } if (0 != res) failuretest(OPTIONAL_CMD, returnval|=FAILSMART); } if (con->smartexeoffimmediate) { if (scsiSmartDefaultSelfTest(fd)) { pout( "Default Self Test Failed\n"); return returnval; } pout("Default Self Test Successful\n"); } if (con->smartshortcapselftest) { if (scsiSmartShortCapSelfTest(fd)) { pout("Short Foreground Self Test Failed\n"); return returnval; } pout("Short Foreground Self Test Successful\n"); } if (con->smartshortselftest ) { if ( scsiSmartShortSelfTest(fd)) { pout("Short Background Self Test Failed\n"); return returnval; } pout("Short Background Self Test has begun\n"); pout("Use smartctl -X to abort test\n"); } if (con->smartextendselftest) { if (scsiSmartExtendSelfTest(fd)) { pout("Extended Background Self Test Failed\n"); return returnval; } pout("Extended Background Self Test has begun\n"); pout("Use smartctl -X to abort test\n"); } if (con->smartextendcapselftest) { if (scsiSmartExtendCapSelfTest(fd)) { pout("Extended Foreground Self Test Failed\n"); return returnval; } pout("Extended Foreground Self Test Successful\n"); } if (con->smartselftestabort) { if (scsiSmartSelfTestAbort(fd)) { pout("Self Test Abort Failed\n"); return returnval; } pout("Self Test returned without error\n"); } return returnval; }