From 97a9c2f1827f3eb5c2841720fcda53db676e9607 Mon Sep 17 00:00:00 2001 From: ballen4705 <ballen4705@4ea69e1a-61f1-4043-bf83-b5c94c648137> Date: Mon, 28 Oct 2002 23:47:00 +0000 Subject: [PATCH] This is the first CVS checkin of code that extends the options available for smartd. The following options can be placed into the /etc/smartd.conf file, and control the behavior of smartd. Configuration file Directives (following device name): -A Device is an ATA device -S Device is a SCSI device -c Monitor SMART Health Status -l Monitor SMART Error Log for changes -L Monitor SMART Self-Test Log for new errors -f Monitor for failure of any 'Usage' Attributes -p Report changes in 'Prefailure' Attributes -u Report changes in 'Usage' Attributes -t Equivalent to -p and -u Directives -i ID Ignore Attribute ID for -f Directive -I ID Ignore Attribute ID for -p, -u or -t Directive # Comment: text after a hash sign is ignored \ Line continuation character Attribute ID is a decimal integer 1 <= ID <= 255 All but -S directive are only implemented for ATA devices Example: /dev/hda -a This code is still very experimental and needs some testing before I generate a release. git-svn-id: https://smartmontools.svn.sourceforge.net/svnroot/smartmontools/trunk@147 4ea69e1a-61f1-4043-bf83-b5c94c648137 --- sm5/Makefile | 18 +- sm5/atacmds.c | 46 ++- sm5/atacmds.cpp | 46 ++- sm5/atacmds.h | 15 +- sm5/ataprint.c | 115 +++--- sm5/ataprint.cpp | 115 +++--- sm5/ataprint.h | 28 +- sm5/extern.h | 54 +-- sm5/scsicmds.c | 2 +- sm5/scsicmds.cpp | 2 +- sm5/scsiprint.c | 29 +- sm5/scsiprint.cpp | 29 +- sm5/scsiprint.h | 6 +- sm5/smartctl.8 | 19 +- sm5/smartctl.c | 134 ++++--- sm5/smartctl.cpp | 134 ++++--- sm5/smartd.8 | 261 ++++++++++--- sm5/smartd.c | 908 +++++++++++++++++++++++++++++++++++----------- sm5/smartd.conf | 48 ++- sm5/smartd.cpp | 908 +++++++++++++++++++++++++++++++++++----------- sm5/smartd.h | 59 ++- 21 files changed, 2125 insertions(+), 851 deletions(-) diff --git a/sm5/Makefile b/sm5/Makefile index 3b7f6c5ff..7150c8663 100644 --- a/sm5/Makefile +++ b/sm5/Makefile @@ -2,7 +2,7 @@ # # Home page: http://smartmontools.sourceforge.net # -# $Id: Makefile,v 1.24 2002/10/26 20:53:08 ballen4705 Exp $ +# $Id: Makefile,v 1.25 2002/10/28 23:46:59 ballen4705 Exp $ # # Copyright (C) 2002 Bruce Allen <smartmontools-support@lists.sourceforge.net> # @@ -23,13 +23,13 @@ CC = gcc # Debugging -# CFLAGS = -fsigned-char -Wall -g +CFLAGS = -fsigned-char -Wall -g # Build against kernel header files. Change linux-2.4 to correct path for your system # CFLAGS = -fsigned-char -Wall -O2 -I./usr/src/linux-2.4/include # Normal build -CFLAGS = -fsigned-char -Wall -O2 +# CFLAGS = -fsigned-char -Wall -O2 releasefiles=atacmds.c atacmds.h ataprint.c ataprint.h CHANGELOG COPYING extern.h Makefile\ README scsicmds.c scsicmds.h scsiprint.c scsiprint.h smartctl.8 smartctl.c smartctl.h\ @@ -42,22 +42,22 @@ pkgname2=$(pkgname)-$(counter) all: smartd smartctl -smartctl: atacmds.o scsicmds.o smartctl.c smartctl.h ataprint.o scsiprint.o atacmds.h ataprint.h scsicmds.h scsiprint.h VERSION Makefile +smartctl: smartctl.c atacmds.o ataprint.o scsicmds.o scsiprint.o atacmds.h ataprint.h extern.h scsicmds.h scsiprint.h smartctl.h VERSION Makefile ${CC} -DSMARTMONTOOLS_VERSION=$(counter) -o smartctl ${CFLAGS} smartctl.c atacmds.o scsicmds.o ataprint.o scsiprint.o -smartd: atacmds.o scsicmds.o smartd.c smartd.h atacmds.h scsicmds.h VERSION Makefile - ${CC} -DSMARTMONTOOLS_VERSION=$(counter) -o smartd ${CFLAGS} smartd.c scsicmds.o atacmds.o +smartd: smartd.c atacmds.o ataprint.o scsicmds.o atacmds.h ataprint.h extern.h scsicmds.h smartd.h VERSION Makefile + ${CC} -DSMARTMONTOOLS_VERSION=$(counter) -o smartd ${CFLAGS} smartd.c scsicmds.o atacmds.o ataprint.o atacmds.o: atacmds.h atacmds.c Makefile ${CC} ${CFLAGS} -c atacmds.c -ataprint.o: atacmds.o ataprint.h ataprint.c smartctl.h extern.h Makefile +ataprint.o: ataprint.c atacmds.h ataprint.h smartctl.h extern.h Makefile ${CC} ${CFLAGS} -c ataprint.c -scsicmds.o: scsicmds.h scsicmds.c Makefile +scsicmds.o: scsicmds.c scsicmds.h Makefile ${CC} ${CFLAGS} -c scsicmds.c -scsiprint.o: scsiprint.h scsiprint.c scsicmds.o smartctl.h extern.h scsicmds.h Makefile +scsiprint.o: scsiprint.c extern.h scsicmds.h scsiprint.h smartctl.h Makefile ${CC} ${CFLAGS} -c scsiprint.c clean: diff --git a/sm5/atacmds.c b/sm5/atacmds.c index cebba28fc..b19ec3d8c 100644 --- a/sm5/atacmds.c +++ b/sm5/atacmds.c @@ -30,7 +30,7 @@ #include <stdlib.h> #include "atacmds.h" -const char *CVSid1="$Id: atacmds.c,v 1.30 2002/10/26 20:10:34 ballen4705 Exp $" CVSID1; +const char *CVSid1="$Id: atacmds.c,v 1.31 2002/10/28 23:46:59 ballen4705 Exp $" CVSID1; // These Drive Identity tables are taken from hdparm 5.2, and are also // given in the ATA/ATAPI specs for the IDENTIFY DEVICE command. Note @@ -632,11 +632,11 @@ int isSupportSelfTest (struct ata_smart_values data){ // condition where imminent loss of data is being predicted." -// onlyfailing=0 : are or were any age or prefailure attributes <= threshold -// onlyfailing=1: are any prefailure attributes <= threshold now -int ataCheckSmart (struct ata_smart_values data, - struct ata_smart_thresholds thresholds, - int onlyfailed){ +// onlyfailed=0 : are or were any age or prefailure attributes <= threshold +// onlyfailed=1: are any prefailure attributes <= threshold now +int ataCheckSmart(struct ata_smart_values data, + struct ata_smart_thresholds thresholds, + int onlyfailed){ int i; // loop over all attributes @@ -664,6 +664,40 @@ int ataCheckSmart (struct ata_smart_values data, } + +// This checks the n'th attribute in the attribute list, NOT the +// attribute with id==n. If the attribute does not exist, or the +// attribute is > threshold, then returns zero. If the attribute is +// <= threshold (failing) then we the attribute number if it is a +// prefail attribute. Else we return minus the attribute number if it +// is a usage attribute. +int ataCheckAttribute(struct ata_smart_values *data, + struct ata_smart_thresholds *thresholds, + int n){ + struct ata_smart_attribute *disk; + struct ata_smart_threshold_entry *thre; + + if (n<0 || n>=NUMBER_ATA_SMART_ATTRIBUTES || !data || !thresholds) + return 0; + + // pointers to disk's values and vendor's thresholds + disk=data->vendor_attributes+n; + thre=thresholds->thres_entries+n; + + if (!disk || !thre) + return 0; + + // consider only valid attributes, check for failure + if (!disk->id || !thre->id || (disk->id != thre->id) || disk->current> thre->threshold) + return 0; + + // We have found a failed attribute. Return positive or negative? + if (disk->status.flag.prefailure) + return disk->id; + else + return -1*(disk->id); +} + // Note some attribute names appear redundant because different // manufacturers use different attribute IDs for an attribute with the // same name. diff --git a/sm5/atacmds.cpp b/sm5/atacmds.cpp index 92f46e9d1..22e9afc28 100644 --- a/sm5/atacmds.cpp +++ b/sm5/atacmds.cpp @@ -30,7 +30,7 @@ #include <stdlib.h> #include "atacmds.h" -const char *CVSid1="$Id: atacmds.cpp,v 1.30 2002/10/26 20:10:34 ballen4705 Exp $" CVSID1; +const char *CVSid1="$Id: atacmds.cpp,v 1.31 2002/10/28 23:46:59 ballen4705 Exp $" CVSID1; // These Drive Identity tables are taken from hdparm 5.2, and are also // given in the ATA/ATAPI specs for the IDENTIFY DEVICE command. Note @@ -632,11 +632,11 @@ int isSupportSelfTest (struct ata_smart_values data){ // condition where imminent loss of data is being predicted." -// onlyfailing=0 : are or were any age or prefailure attributes <= threshold -// onlyfailing=1: are any prefailure attributes <= threshold now -int ataCheckSmart (struct ata_smart_values data, - struct ata_smart_thresholds thresholds, - int onlyfailed){ +// onlyfailed=0 : are or were any age or prefailure attributes <= threshold +// onlyfailed=1: are any prefailure attributes <= threshold now +int ataCheckSmart(struct ata_smart_values data, + struct ata_smart_thresholds thresholds, + int onlyfailed){ int i; // loop over all attributes @@ -664,6 +664,40 @@ int ataCheckSmart (struct ata_smart_values data, } + +// This checks the n'th attribute in the attribute list, NOT the +// attribute with id==n. If the attribute does not exist, or the +// attribute is > threshold, then returns zero. If the attribute is +// <= threshold (failing) then we the attribute number if it is a +// prefail attribute. Else we return minus the attribute number if it +// is a usage attribute. +int ataCheckAttribute(struct ata_smart_values *data, + struct ata_smart_thresholds *thresholds, + int n){ + struct ata_smart_attribute *disk; + struct ata_smart_threshold_entry *thre; + + if (n<0 || n>=NUMBER_ATA_SMART_ATTRIBUTES || !data || !thresholds) + return 0; + + // pointers to disk's values and vendor's thresholds + disk=data->vendor_attributes+n; + thre=thresholds->thres_entries+n; + + if (!disk || !thre) + return 0; + + // consider only valid attributes, check for failure + if (!disk->id || !thre->id || (disk->id != thre->id) || disk->current> thre->threshold) + return 0; + + // We have found a failed attribute. Return positive or negative? + if (disk->status.flag.prefailure) + return disk->id; + else + return -1*(disk->id); +} + // Note some attribute names appear redundant because different // manufacturers use different attribute IDs for an attribute with the // same name. diff --git a/sm5/atacmds.h b/sm5/atacmds.h index 439e45f68..6285b6d2b 100644 --- a/sm5/atacmds.h +++ b/sm5/atacmds.h @@ -26,7 +26,7 @@ #define _ATACMDS_H_ #ifndef CVSID1 -#define CVSID1 "$Id: atacmds.h,v 1.20 2002/10/26 19:59:01 ballen4705 Exp $\n" +#define CVSID1 "$Id: atacmds.h,v 1.21 2002/10/28 23:46:59 ballen4705 Exp $\n" #endif // These are the major and minor versions for smartd and smartctl @@ -384,8 +384,15 @@ void pout(char *fmt, ...); #define CVSMAXLEN 512 void printone(char *block, const char *cvsid); -// MACROS to control printing behavior -#define QUIETON {if (quietmode) veryquietmode=0;} -#define QUIETOFF {if (quietmode && !veryquietmode) veryquietmode=1;} + +// This checks the n'th attribute in the attribute list, NOT the +// attribute with id==n. If the attribute does not exist, or the +// attribute is > threshold, then returns zero. If the attribute is +// <= threshold (failing) then we the attribute number if it is a +// prefail attribute. Else we return minus the attribute number if it +// is a usage attribute. +int ataCheckAttribute(struct ata_smart_values *data, + struct ata_smart_thresholds *thresholds, + int n); #endif /* _ATACMDS_H_ */ diff --git a/sm5/ataprint.c b/sm5/ataprint.c index 5b99788f6..e2e471fce 100644 --- a/sm5/ataprint.c +++ b/sm5/ataprint.c @@ -24,12 +24,16 @@ #include <ctype.h> #include <stdio.h> +#include "atacmds.h" #include "ataprint.h" #include "smartctl.h" #include "extern.h" -const char *CVSid4="$Id: ataprint.c,v 1.34 2002/10/26 19:59:01 ballen4705 Exp $" -CVSID2 CVSID3 CVSID6; +const char *CVSid2="$Id: ataprint.c,v 1.35 2002/10/28 23:46:59 ballen4705 Exp $" +CVSID1 CVSID2 CVSID3 CVSID6; + +// for passing global control variables +extern atamainctrl *con; // Function for printing ASCII byte-swapped strings, skipping white // space. This is needed on little-endian architectures, eg Intel, @@ -420,7 +424,7 @@ void PrintSmartAttribWithThres (struct ata_smart_values data, switch (disk->id){ // Power on time case 9: - if (smart009minutes) + if (con->smart009minutes) // minutes pout ("%llu h + %2llu m\n",rawvalue/60,rawvalue%60); else @@ -494,7 +498,8 @@ int nonempty(unsigned char *testarea,int n){ return 1; return 0; } - + +// returns number of errors void ataPrintSmartErrorlog (struct ata_smart_errorlog data){ int i,j,k; @@ -505,7 +510,7 @@ void ataPrintSmartErrorlog (struct ata_smart_errorlog data){ pout ("No Errors Logged\n\n"); return; } - QUIETON; + QUIETON(con); // if log pointer out of range, return if ( data.error_log_pointer>5 ){ pout("Invalid Error Log index = %02x (T13/1321D rev 1c" @@ -520,7 +525,7 @@ void ataPrintSmartErrorlog (struct ata_smart_errorlog data){ else pout ( "ATA Error Count: %u (device log contains only the most recent five errors)\n", data.ata_error_count); - QUIETOFF; + QUIETOFF(con); pout("\tDCR = Device Control Register\n"); pout("\tFR = Features Register\n"); pout("\tSC = Sector Count Register\n"); @@ -552,10 +557,10 @@ void ataPrintSmartErrorlog (struct ata_smart_errorlog data){ default: msgstate="in a vendor specific or reserved state"; } // See table 42 of ATA5 spec - QUIETON; + QUIETON(con); pout("Error %i occurred at disk power-on lifetime: %u hours\n", 5-k,data.errorlog_struct[i].error_struct.timestamp); - QUIETOFF; + QUIETOFF(con); pout("When the command that caused the error occurred, the device was %s.\n",msgstate); pout("After command completion occurred, registers were:\n"); pout("ER:%02x SC:%02x SN:%02x CL:%02x CH:%02x D/H:%02x ST:%02x\n", @@ -588,15 +593,15 @@ void ataPrintSmartErrorlog (struct ata_smart_errorlog data){ pout("\n"); } } - QUIETON; - if (quietmode) + QUIETON(con); + if (con->quietmode) pout("\n"); - QUIETOFF; + QUIETOFF(con); return; } // return value is number of entries found where the self-test showed an error -int ataPrintSmartSelfTestlog (struct ata_smart_selftestlog data,int allentries){ +int ataPrintSmartSelfTestlog(struct ata_smart_selftestlog data,int allentries){ int i,j,noheaderprinted=1; int retval=0; @@ -701,7 +706,7 @@ struct ata_smart_selftestlog smartselftest; int ataPrintMain (int fd){ int timewait,code; int returnval=0; - + // Start by getting Drive ID information. We need this, to know if SMART is supported. if (ataReadHDIdentity(fd,&drive)){ pout("Smartctl: Hard Drive Read Identity Failed\n\n"); @@ -709,7 +714,7 @@ int ataPrintMain (int fd){ } // Print most drive identity information if requested - if (driveinfo){ + if (con->driveinfo){ pout("=== START OF INFORMATION SECTION ===\n"); ataPrintDriveInfo(drive); } @@ -724,11 +729,11 @@ int ataPrintMain (int fd){ } else pout(" SMART appears to work. Continuing.\n"); - if (!driveinfo) pout("\n"); + if (!con->driveinfo) pout("\n"); } // Now print remaining drive info: is SMART enabled? - if (driveinfo){ + if (con->driveinfo){ pout("SMART support is: Available - device has SMART capability.\n"); if (ataDoesSmartWork(fd)) pout("SMART support is: Enabled\n"); @@ -739,13 +744,13 @@ int ataPrintMain (int fd){ // START OF THE ENABLE/DISABLE SECTION OF THE CODE - if (smartenable || smartdisable || - smartautosaveenable || smartautosavedisable || - smartautoofflineenable || smartautoofflinedisable) + if (con->smartenable || con->smartdisable || + con->smartautosaveenable || con->smartautosavedisable || + con->smartautoofflineenable || con->smartautoofflinedisable) pout("=== START OF ENABLE/DISABLE COMMANDS SECTION ===\n"); // Enable/Disable SMART commands - if (smartenable){ + if (con->smartenable){ if (ataEnableSmart(fd)) { pout("Smartctl: SMART Enable Failed.\n\n"); returnval|=FAILSMART; @@ -761,7 +766,7 @@ int ataPrintMain (int fd){ } // Turn off SMART on device - if (smartdisable){ + if (con->smartdisable){ if (ataDisableSmart(fd)) { pout( "Smartctl: SMART Disable Failed.\n\n"); returnval|=FAILSMART; @@ -776,7 +781,7 @@ int ataPrintMain (int fd){ returnval|=FAILSMART; // Enable/Disable Auto-save attributes - if (smartautosaveenable){ + if (con->smartautosaveenable){ if (ataEnableAutoSave(fd)){ pout( "Smartctl: SMART Enable Attribute Autosave Failed.\n\n"); returnval|=FAILSMART; @@ -784,7 +789,7 @@ int ataPrintMain (int fd){ else pout("SMART Attribute Autosave Enabled.\n"); } - if (smartautosavedisable){ + if (con->smartautosavedisable){ if (ataDisableAutoSave(fd)){ pout( "Smartctl: SMART Disable Attribute Autosave Failed.\n\n"); returnval|=FAILSMART; @@ -804,7 +809,7 @@ int ataPrintMain (int fd){ } // Enable/Disable Off-line testing - if (smartautoofflineenable){ + if (con->smartautoofflineenable){ if (!isSupportAutomaticTimer(smartval)){ pout("Warning: device does not support SMART Automatic Timers.\n\n"); } @@ -815,7 +820,7 @@ int ataPrintMain (int fd){ else pout ("SMART Automatic Offline Testing Enabled every four hours.\n"); } - if (smartautoofflinedisable){ + if (con->smartautoofflinedisable){ if (!isSupportAutomaticTimer(smartval)){ pout("Warning: device does not support SMART Automatic Timers.\n\n"); } @@ -828,28 +833,28 @@ int ataPrintMain (int fd){ } // all this for a newline! - if (smartenable || smartdisable || - smartautosaveenable || smartautosavedisable || - smartautoofflineenable || smartautoofflinedisable) + if (con->smartenable || con->smartdisable || + con->smartautosaveenable || con->smartautosavedisable || + con->smartautoofflineenable || con->smartautoofflinedisable) pout("\n"); // START OF READ-ONLY OPTIONS APART FROM -V and -i - if (checksmart || generalsmartvalues || smartvendorattrib || smarterrorlog || smartselftestlog) + if (con->checksmart || con->generalsmartvalues || con->smartvendorattrib || con->smarterrorlog || con->smartselftestlog) pout("=== START OF READ SMART DATA SECTION ===\n"); // Check SMART status (use previously returned value) - if (checksmart){ + if (con->checksmart){ if (code) { - QUIETON; + QUIETON(con); pout("SMART overall-health self-assessment test result: FAILED!\n" "Drive failure expected in less than 24 hours. SAVE ALL DATA.\n"); - QUIETOFF; + QUIETOFF(con); if (ataCheckSmart(smartval, smartthres,1)){ returnval|=FAILATTR; - if (smartvendorattrib) + if (con->smartvendorattrib) pout("See vendor-specific Attribute list for failed Attributes.\n\n"); else { - QUIETON; + QUIETON(con); pout("Failed Attributes:\n"); PrintSmartAttribWithThres(smartval, smartthres,1); } @@ -857,15 +862,15 @@ int ataPrintMain (int fd){ else pout("No failed Attributes found.\n\n"); returnval|=FAILSTATUS; - QUIETOFF; + QUIETOFF(con); } else { pout("SMART overall-health self-assessment test result: PASSED\n"); if (ataCheckSmart(smartval, smartthres,0)){ - if (smartvendorattrib) + if (con->smartvendorattrib) pout("See vendor-specific Attribute list for marginal Attributes.\n\n"); else { - QUIETON; + QUIETON(con); pout("Please note the following marginal attributes:\n"); PrintSmartAttribWithThres(smartval, smartthres,2); } @@ -874,22 +879,22 @@ int ataPrintMain (int fd){ else pout("\n"); } - QUIETOFF; + QUIETOFF(con); } // Print general SMART values - if (generalsmartvalues) + if (con->generalsmartvalues) ataPrintGeneralSmartValues(smartval); // Print vendor-specific attributes - if (smartvendorattrib){ - QUIETON; - PrintSmartAttribWithThres(smartval, smartthres,quietmode?2:0); - QUIETOFF; + if (con->smartvendorattrib){ + QUIETON(con); + PrintSmartAttribWithThres(smartval, smartthres,con->quietmode?2:0); + QUIETOFF(con); } // Print SMART error log - if (smarterrorlog){ + if (con->smarterrorlog){ if (!isSmartErrorLogCapable(smartval)) pout("Warning: device does not support Error Logging\n"); if (ataReadErrorLog(fd, &smarterror)){ @@ -899,12 +904,12 @@ int ataPrintMain (int fd){ else { // turn on quiet mode inside this ataPrintSmartErrorlog(smarterror); - QUIETOFF; + QUIETOFF(con); } } // Print SMART self-test log - if (smartselftestlog){ + if (con->smartselftestlog){ if (!isSmartErrorLogCapable(smartval)) pout("Warning: device does not support Self Test Logging\n"); else { @@ -913,36 +918,36 @@ int ataPrintMain (int fd){ returnval|=FAILSMART; } else { - QUIETON; - if (ataPrintSmartSelfTestlog(smartselftest,!quietmode)) + QUIETON(con); + if (ataPrintSmartSelfTestlog(smartselftest,!con->quietmode)) returnval|=FAILLOG; - QUIETOFF; + QUIETOFF(con); pout("\n"); } } } // START OF THE TESTING SECTION OF THE CODE. IF NO TESTING, RETURN - if (testcase==-1) + if (con->testcase==-1) return returnval; pout("=== START OF OFFLINE IMMEDIATE AND SELF-TEST SECTION ===\n"); // if doing a self-test, be sure it's supported by the hardware - if (testcase==OFFLINE_FULL_SCAN && !isSupportExecuteOfflineImmediate(smartval)) + if (con->testcase==OFFLINE_FULL_SCAN && !isSupportExecuteOfflineImmediate(smartval)) pout("Warning: device does not support Execute Off-Line Immediate function.\n\n"); else if (!isSupportSelfTest(smartval)) pout ("Warning: device does not support Self-Test functions.\n\n"); // Now do the test - if (ataSmartTest(fd, testcase)) + if (ataSmartTest(fd, con->testcase)) return returnval|=FAILSMART; // Tell user how long test will take to complete - if ((timewait=TestTime(smartval,testcase))){ + if ((timewait=TestTime(smartval,con->testcase))){ pout ("Please wait %d %s for test to complete.\n", - timewait, testcase==OFFLINE_FULL_SCAN?"seconds":"minutes"); + timewait, con->testcase==OFFLINE_FULL_SCAN?"seconds":"minutes"); - if (testcase!=SHORT_CAPTIVE_SELF_TEST && testcase!=EXTEND_CAPTIVE_SELF_TEST) + if (con->testcase!=SHORT_CAPTIVE_SELF_TEST && con->testcase!=EXTEND_CAPTIVE_SELF_TEST) pout ("Use smartctl -%c to abort test.\n", SMARTSELFTESTABORT); } return returnval; diff --git a/sm5/ataprint.cpp b/sm5/ataprint.cpp index 2802fe6ab..2e332784c 100644 --- a/sm5/ataprint.cpp +++ b/sm5/ataprint.cpp @@ -24,12 +24,16 @@ #include <ctype.h> #include <stdio.h> +#include "atacmds.h" #include "ataprint.h" #include "smartctl.h" #include "extern.h" -const char *CVSid4="$Id: ataprint.cpp,v 1.34 2002/10/26 19:59:01 ballen4705 Exp $" -CVSID2 CVSID3 CVSID6; +const char *CVSid2="$Id: ataprint.cpp,v 1.35 2002/10/28 23:46:59 ballen4705 Exp $" +CVSID1 CVSID2 CVSID3 CVSID6; + +// for passing global control variables +extern atamainctrl *con; // Function for printing ASCII byte-swapped strings, skipping white // space. This is needed on little-endian architectures, eg Intel, @@ -420,7 +424,7 @@ void PrintSmartAttribWithThres (struct ata_smart_values data, switch (disk->id){ // Power on time case 9: - if (smart009minutes) + if (con->smart009minutes) // minutes pout ("%llu h + %2llu m\n",rawvalue/60,rawvalue%60); else @@ -494,7 +498,8 @@ int nonempty(unsigned char *testarea,int n){ return 1; return 0; } - + +// returns number of errors void ataPrintSmartErrorlog (struct ata_smart_errorlog data){ int i,j,k; @@ -505,7 +510,7 @@ void ataPrintSmartErrorlog (struct ata_smart_errorlog data){ pout ("No Errors Logged\n\n"); return; } - QUIETON; + QUIETON(con); // if log pointer out of range, return if ( data.error_log_pointer>5 ){ pout("Invalid Error Log index = %02x (T13/1321D rev 1c" @@ -520,7 +525,7 @@ void ataPrintSmartErrorlog (struct ata_smart_errorlog data){ else pout ( "ATA Error Count: %u (device log contains only the most recent five errors)\n", data.ata_error_count); - QUIETOFF; + QUIETOFF(con); pout("\tDCR = Device Control Register\n"); pout("\tFR = Features Register\n"); pout("\tSC = Sector Count Register\n"); @@ -552,10 +557,10 @@ void ataPrintSmartErrorlog (struct ata_smart_errorlog data){ default: msgstate="in a vendor specific or reserved state"; } // See table 42 of ATA5 spec - QUIETON; + QUIETON(con); pout("Error %i occurred at disk power-on lifetime: %u hours\n", 5-k,data.errorlog_struct[i].error_struct.timestamp); - QUIETOFF; + QUIETOFF(con); pout("When the command that caused the error occurred, the device was %s.\n",msgstate); pout("After command completion occurred, registers were:\n"); pout("ER:%02x SC:%02x SN:%02x CL:%02x CH:%02x D/H:%02x ST:%02x\n", @@ -588,15 +593,15 @@ void ataPrintSmartErrorlog (struct ata_smart_errorlog data){ pout("\n"); } } - QUIETON; - if (quietmode) + QUIETON(con); + if (con->quietmode) pout("\n"); - QUIETOFF; + QUIETOFF(con); return; } // return value is number of entries found where the self-test showed an error -int ataPrintSmartSelfTestlog (struct ata_smart_selftestlog data,int allentries){ +int ataPrintSmartSelfTestlog(struct ata_smart_selftestlog data,int allentries){ int i,j,noheaderprinted=1; int retval=0; @@ -701,7 +706,7 @@ struct ata_smart_selftestlog smartselftest; int ataPrintMain (int fd){ int timewait,code; int returnval=0; - + // Start by getting Drive ID information. We need this, to know if SMART is supported. if (ataReadHDIdentity(fd,&drive)){ pout("Smartctl: Hard Drive Read Identity Failed\n\n"); @@ -709,7 +714,7 @@ int ataPrintMain (int fd){ } // Print most drive identity information if requested - if (driveinfo){ + if (con->driveinfo){ pout("=== START OF INFORMATION SECTION ===\n"); ataPrintDriveInfo(drive); } @@ -724,11 +729,11 @@ int ataPrintMain (int fd){ } else pout(" SMART appears to work. Continuing.\n"); - if (!driveinfo) pout("\n"); + if (!con->driveinfo) pout("\n"); } // Now print remaining drive info: is SMART enabled? - if (driveinfo){ + if (con->driveinfo){ pout("SMART support is: Available - device has SMART capability.\n"); if (ataDoesSmartWork(fd)) pout("SMART support is: Enabled\n"); @@ -739,13 +744,13 @@ int ataPrintMain (int fd){ // START OF THE ENABLE/DISABLE SECTION OF THE CODE - if (smartenable || smartdisable || - smartautosaveenable || smartautosavedisable || - smartautoofflineenable || smartautoofflinedisable) + if (con->smartenable || con->smartdisable || + con->smartautosaveenable || con->smartautosavedisable || + con->smartautoofflineenable || con->smartautoofflinedisable) pout("=== START OF ENABLE/DISABLE COMMANDS SECTION ===\n"); // Enable/Disable SMART commands - if (smartenable){ + if (con->smartenable){ if (ataEnableSmart(fd)) { pout("Smartctl: SMART Enable Failed.\n\n"); returnval|=FAILSMART; @@ -761,7 +766,7 @@ int ataPrintMain (int fd){ } // Turn off SMART on device - if (smartdisable){ + if (con->smartdisable){ if (ataDisableSmart(fd)) { pout( "Smartctl: SMART Disable Failed.\n\n"); returnval|=FAILSMART; @@ -776,7 +781,7 @@ int ataPrintMain (int fd){ returnval|=FAILSMART; // Enable/Disable Auto-save attributes - if (smartautosaveenable){ + if (con->smartautosaveenable){ if (ataEnableAutoSave(fd)){ pout( "Smartctl: SMART Enable Attribute Autosave Failed.\n\n"); returnval|=FAILSMART; @@ -784,7 +789,7 @@ int ataPrintMain (int fd){ else pout("SMART Attribute Autosave Enabled.\n"); } - if (smartautosavedisable){ + if (con->smartautosavedisable){ if (ataDisableAutoSave(fd)){ pout( "Smartctl: SMART Disable Attribute Autosave Failed.\n\n"); returnval|=FAILSMART; @@ -804,7 +809,7 @@ int ataPrintMain (int fd){ } // Enable/Disable Off-line testing - if (smartautoofflineenable){ + if (con->smartautoofflineenable){ if (!isSupportAutomaticTimer(smartval)){ pout("Warning: device does not support SMART Automatic Timers.\n\n"); } @@ -815,7 +820,7 @@ int ataPrintMain (int fd){ else pout ("SMART Automatic Offline Testing Enabled every four hours.\n"); } - if (smartautoofflinedisable){ + if (con->smartautoofflinedisable){ if (!isSupportAutomaticTimer(smartval)){ pout("Warning: device does not support SMART Automatic Timers.\n\n"); } @@ -828,28 +833,28 @@ int ataPrintMain (int fd){ } // all this for a newline! - if (smartenable || smartdisable || - smartautosaveenable || smartautosavedisable || - smartautoofflineenable || smartautoofflinedisable) + if (con->smartenable || con->smartdisable || + con->smartautosaveenable || con->smartautosavedisable || + con->smartautoofflineenable || con->smartautoofflinedisable) pout("\n"); // START OF READ-ONLY OPTIONS APART FROM -V and -i - if (checksmart || generalsmartvalues || smartvendorattrib || smarterrorlog || smartselftestlog) + if (con->checksmart || con->generalsmartvalues || con->smartvendorattrib || con->smarterrorlog || con->smartselftestlog) pout("=== START OF READ SMART DATA SECTION ===\n"); // Check SMART status (use previously returned value) - if (checksmart){ + if (con->checksmart){ if (code) { - QUIETON; + QUIETON(con); pout("SMART overall-health self-assessment test result: FAILED!\n" "Drive failure expected in less than 24 hours. SAVE ALL DATA.\n"); - QUIETOFF; + QUIETOFF(con); if (ataCheckSmart(smartval, smartthres,1)){ returnval|=FAILATTR; - if (smartvendorattrib) + if (con->smartvendorattrib) pout("See vendor-specific Attribute list for failed Attributes.\n\n"); else { - QUIETON; + QUIETON(con); pout("Failed Attributes:\n"); PrintSmartAttribWithThres(smartval, smartthres,1); } @@ -857,15 +862,15 @@ int ataPrintMain (int fd){ else pout("No failed Attributes found.\n\n"); returnval|=FAILSTATUS; - QUIETOFF; + QUIETOFF(con); } else { pout("SMART overall-health self-assessment test result: PASSED\n"); if (ataCheckSmart(smartval, smartthres,0)){ - if (smartvendorattrib) + if (con->smartvendorattrib) pout("See vendor-specific Attribute list for marginal Attributes.\n\n"); else { - QUIETON; + QUIETON(con); pout("Please note the following marginal attributes:\n"); PrintSmartAttribWithThres(smartval, smartthres,2); } @@ -874,22 +879,22 @@ int ataPrintMain (int fd){ else pout("\n"); } - QUIETOFF; + QUIETOFF(con); } // Print general SMART values - if (generalsmartvalues) + if (con->generalsmartvalues) ataPrintGeneralSmartValues(smartval); // Print vendor-specific attributes - if (smartvendorattrib){ - QUIETON; - PrintSmartAttribWithThres(smartval, smartthres,quietmode?2:0); - QUIETOFF; + if (con->smartvendorattrib){ + QUIETON(con); + PrintSmartAttribWithThres(smartval, smartthres,con->quietmode?2:0); + QUIETOFF(con); } // Print SMART error log - if (smarterrorlog){ + if (con->smarterrorlog){ if (!isSmartErrorLogCapable(smartval)) pout("Warning: device does not support Error Logging\n"); if (ataReadErrorLog(fd, &smarterror)){ @@ -899,12 +904,12 @@ int ataPrintMain (int fd){ else { // turn on quiet mode inside this ataPrintSmartErrorlog(smarterror); - QUIETOFF; + QUIETOFF(con); } } // Print SMART self-test log - if (smartselftestlog){ + if (con->smartselftestlog){ if (!isSmartErrorLogCapable(smartval)) pout("Warning: device does not support Self Test Logging\n"); else { @@ -913,36 +918,36 @@ int ataPrintMain (int fd){ returnval|=FAILSMART; } else { - QUIETON; - if (ataPrintSmartSelfTestlog(smartselftest,!quietmode)) + QUIETON(con); + if (ataPrintSmartSelfTestlog(smartselftest,!con->quietmode)) returnval|=FAILLOG; - QUIETOFF; + QUIETOFF(con); pout("\n"); } } } // START OF THE TESTING SECTION OF THE CODE. IF NO TESTING, RETURN - if (testcase==-1) + if (con->testcase==-1) return returnval; pout("=== START OF OFFLINE IMMEDIATE AND SELF-TEST SECTION ===\n"); // if doing a self-test, be sure it's supported by the hardware - if (testcase==OFFLINE_FULL_SCAN && !isSupportExecuteOfflineImmediate(smartval)) + if (con->testcase==OFFLINE_FULL_SCAN && !isSupportExecuteOfflineImmediate(smartval)) pout("Warning: device does not support Execute Off-Line Immediate function.\n\n"); else if (!isSupportSelfTest(smartval)) pout ("Warning: device does not support Self-Test functions.\n\n"); // Now do the test - if (ataSmartTest(fd, testcase)) + if (ataSmartTest(fd, con->testcase)) return returnval|=FAILSMART; // Tell user how long test will take to complete - if ((timewait=TestTime(smartval,testcase))){ + if ((timewait=TestTime(smartval,con->testcase))){ pout ("Please wait %d %s for test to complete.\n", - timewait, testcase==OFFLINE_FULL_SCAN?"seconds":"minutes"); + timewait, con->testcase==OFFLINE_FULL_SCAN?"seconds":"minutes"); - if (testcase!=SHORT_CAPTIVE_SELF_TEST && testcase!=EXTEND_CAPTIVE_SELF_TEST) + if (con->testcase!=SHORT_CAPTIVE_SELF_TEST && con->testcase!=EXTEND_CAPTIVE_SELF_TEST) pout ("Use smartctl -%c to abort test.\n", SMARTSELFTESTABORT); } return returnval; diff --git a/sm5/ataprint.h b/sm5/ataprint.h index c58112908..8bff3e511 100644 --- a/sm5/ataprint.h +++ b/sm5/ataprint.h @@ -26,35 +26,41 @@ #define _SMART_PRINT_H_ #ifndef CVSID2 -#define CVSID2 "$Id: ataprint.h,v 1.12 2002/10/24 09:54:02 ballen4705 Exp $\n" +#define CVSID2 "$Id: ataprint.h,v 1.13 2002/10/28 23:46:59 ballen4705 Exp $\n" #endif #include <stdio.h> #include <stdlib.h> -#include "atacmds.h" + +// MACROS to control printing behavior +#define QUIETON(control) {if (control->quietmode) control->veryquietmode=0;} +#define QUIETOFF(control) {if (control->quietmode && !control->veryquietmode) control->veryquietmode=1;} + + /* Prints ATA Drive Information and S.M.A.R.T. Capability */ -void ataPrintDriveInfo (struct hd_driveid); +void ataPrintDriveInfo(struct hd_driveid); -void ataPrintGeneralSmartValues (struct ata_smart_values); +void ataPrintGeneralSmartValues(struct ata_smart_values); -void ataPrintSmartThresholds (struct ata_smart_thresholds); +void ataPrintSmartThresholds(struct ata_smart_thresholds); -void ataPrintSmartErrorlog (struct ata_smart_errorlog); +void ataPrintSmartErrorlog(struct ata_smart_errorlog); -void PrintSmartAttributes (struct ata_smart_values data); +void PrintSmartAttributes(struct ata_smart_values data); -void PrintSmartAttribWithThres (struct ata_smart_values data, +void PrintSmartAttribWithThres(struct ata_smart_values data, struct ata_smart_thresholds thresholds, int onlyfailed); // returns number of entries that had logged errors int ataPrintSmartSelfTestlog(struct ata_smart_selftestlog data, int allentries); -void ataPseudoCheckSmart (struct ata_smart_values , - struct ata_smart_thresholds ); +void ataPseudoCheckSmart(struct ata_smart_values, struct ata_smart_thresholds ); + + -int ataPrintMain ( int fd ); +int ataPrintMain(int fd); #endif diff --git a/sm5/extern.h b/sm5/extern.h index d9b0f9ee4..85ebca773 100644 --- a/sm5/extern.h +++ b/sm5/extern.h @@ -27,30 +27,36 @@ #ifndef CVSID3 -#define CVSID3 "$Id: extern.h,v 1.8 2002/10/23 12:24:24 ballen4705 Exp $\n" +#define CVSID3 "$Id: extern.h,v 1.9 2002/10/28 23:46:59 ballen4705 Exp $\n" #endif -extern unsigned char driveinfo; -extern unsigned char checksmart; -extern unsigned char smartvendorattrib; -extern unsigned char generalsmartvalues; -extern unsigned char smartselftestlog; -extern unsigned char smarterrorlog; -extern unsigned char smartdisable; -extern unsigned char smartenable; -extern unsigned char smartstatus; -extern unsigned char smartexeoffimmediate; -extern unsigned char smartshortselftest; -extern unsigned char smartextendselftest; -extern unsigned char smartshortcapselftest; -extern unsigned char smartextendcapselftest; -extern unsigned char smartselftestabort; -extern unsigned char smartautoofflineenable; -extern unsigned char smartautoofflinedisable; -extern unsigned char smartautosaveenable; -extern unsigned char smartautosavedisable; -extern unsigned char smart009minutes; -extern int testcase; -extern unsigned char quietmode; -extern unsigned char veryquietmode; +// Block used for global control/communications. If you need more +// global variables, this should be the only place that you need to +// add them. +typedef struct ataprintmain_s { + unsigned char driveinfo; + unsigned char checksmart; + unsigned char smartvendorattrib; + unsigned char generalsmartvalues; + unsigned char smartselftestlog; + unsigned char smarterrorlog; + unsigned char smartdisable; + unsigned char smartenable; + unsigned char smartstatus; + unsigned char smartexeoffimmediate; + unsigned char smartshortselftest; + unsigned char smartextendselftest; + unsigned char smartshortcapselftest; + unsigned char smartextendcapselftest; + unsigned char smartselftestabort; + unsigned char smartautoofflineenable; + unsigned char smartautoofflinedisable; + unsigned char smartautosaveenable; + unsigned char smartautosavedisable; + unsigned char smart009minutes; + int testcase; + unsigned char quietmode; + unsigned char veryquietmode; +} atamainctrl; + #endif diff --git a/sm5/scsicmds.c b/sm5/scsicmds.c index cad4705a2..12184f2ca 100644 --- a/sm5/scsicmds.c +++ b/sm5/scsicmds.c @@ -33,7 +33,7 @@ #include <scsi/scsi.h> #include "scsicmds.h" -const char *CVSid2="$Id: scsicmds.c,v 1.11 2002/10/23 20:36:59 ballen4705 Exp $" CVSID4; +const char *CVSid3="$Id: scsicmds.c,v 1.12 2002/10/28 23:46:59 ballen4705 Exp $" CVSID4; UINT8 logsense (int device, UINT8 pagenum, UINT8 *pBuf) diff --git a/sm5/scsicmds.cpp b/sm5/scsicmds.cpp index 9ef4b0f19..65ef61b19 100644 --- a/sm5/scsicmds.cpp +++ b/sm5/scsicmds.cpp @@ -33,7 +33,7 @@ #include <scsi/scsi.h> #include "scsicmds.h" -const char *CVSid2="$Id: scsicmds.cpp,v 1.11 2002/10/23 20:36:59 ballen4705 Exp $" CVSID4; +const char *CVSid3="$Id: scsicmds.cpp,v 1.12 2002/10/28 23:46:59 ballen4705 Exp $" CVSID4; UINT8 logsense (int device, UINT8 pagenum, UINT8 *pBuf) diff --git a/sm5/scsiprint.c b/sm5/scsiprint.c index d459b84db..47446e8c8 100644 --- a/sm5/scsiprint.c +++ b/sm5/scsiprint.c @@ -30,15 +30,18 @@ #include <errno.h> #include "smartctl.h" -#include "extern.h" #include "scsicmds.h" #include "scsiprint.h" +#include "extern.h" #define GBUF_SIZE 65535 -const char* CVSid5="$Id: scsiprint.c,v 1.9 2002/10/23 20:36:59 ballen4705 Exp $" +const char* CVSid4="$Id: scsiprint.c,v 1.10 2002/10/28 23:46:59 ballen4705 Exp $" CVSID3 CVSID4 CVSID5 CVSID6; +// control block which points to external global control variables +extern atamainctrl *con; + UINT8 gBuf[GBUF_SIZE]; UINT8 gSmartPage = 0; @@ -293,20 +296,20 @@ void scsiPrintStopStart ( int device ) printf ("Start Stop Count: %d\n", css); **/ } - + void scsiPrintMain (int fd) { - if (driveinfo) + if (con->driveinfo) scsiGetDriveInfo(fd); - if (smartenable) + if (con->smartenable) scsiSmartEnable(fd); - if (smartdisable) + if (con->smartdisable) scsiSmartDisable(fd); - if (checksmart) + if (con->checksmart) { scsiGetSupportPages (fd); if(gTapeAlertsPage) @@ -322,7 +325,7 @@ void scsiPrintMain (int fd) } - if ( smartexeoffimmediate ) + if ( con->smartexeoffimmediate ) { if ( scsiSmartOfflineTest (fd) != 0) { @@ -336,7 +339,7 @@ void scsiPrintMain (int fd) } - if ( smartshortcapselftest ) + if ( con->smartshortcapselftest ) { if ( scsiSmartShortCapSelfTest (fd) != 0) { @@ -347,7 +350,7 @@ void scsiPrintMain (int fd) printf ("Use smartctl -%c to abort test\n", SMARTSELFTESTABORT); } - if ( smartshortselftest ) + if ( con->smartshortselftest ) { if ( scsiSmartShortSelfTest (fd) != 0) @@ -359,7 +362,7 @@ void scsiPrintMain (int fd) printf ("Use smartctl -%c to abort test\n", SMARTSELFTESTABORT); } - if ( smartextendselftest ) + if ( con->smartextendselftest ) { if ( scsiSmartExtendSelfTest (fd) != 0) { @@ -371,7 +374,7 @@ void scsiPrintMain (int fd) printf ("Use smartctl -%c to abort test\n", SMARTSELFTESTABORT); } - if ( smartextendcapselftest ) + if ( con->smartextendcapselftest ) { if ( scsiSmartExtendCapSelfTest (fd) != 0) @@ -384,7 +387,7 @@ void scsiPrintMain (int fd) printf ("Use smartctl -%c to abort test\n", SMARTSELFTESTABORT); } - if ( smartselftestabort ) + if ( con->smartselftestabort ) { if ( scsiSmartSelfTestAbort (fd) != 0) diff --git a/sm5/scsiprint.cpp b/sm5/scsiprint.cpp index 288d67f99..77049a2bf 100644 --- a/sm5/scsiprint.cpp +++ b/sm5/scsiprint.cpp @@ -30,15 +30,18 @@ #include <errno.h> #include "smartctl.h" -#include "extern.h" #include "scsicmds.h" #include "scsiprint.h" +#include "extern.h" #define GBUF_SIZE 65535 -const char* CVSid5="$Id: scsiprint.cpp,v 1.9 2002/10/23 20:36:59 ballen4705 Exp $" +const char* CVSid4="$Id: scsiprint.cpp,v 1.10 2002/10/28 23:46:59 ballen4705 Exp $" CVSID3 CVSID4 CVSID5 CVSID6; +// control block which points to external global control variables +extern atamainctrl *con; + UINT8 gBuf[GBUF_SIZE]; UINT8 gSmartPage = 0; @@ -293,20 +296,20 @@ void scsiPrintStopStart ( int device ) printf ("Start Stop Count: %d\n", css); **/ } - + void scsiPrintMain (int fd) { - if (driveinfo) + if (con->driveinfo) scsiGetDriveInfo(fd); - if (smartenable) + if (con->smartenable) scsiSmartEnable(fd); - if (smartdisable) + if (con->smartdisable) scsiSmartDisable(fd); - if (checksmart) + if (con->checksmart) { scsiGetSupportPages (fd); if(gTapeAlertsPage) @@ -322,7 +325,7 @@ void scsiPrintMain (int fd) } - if ( smartexeoffimmediate ) + if ( con->smartexeoffimmediate ) { if ( scsiSmartOfflineTest (fd) != 0) { @@ -336,7 +339,7 @@ void scsiPrintMain (int fd) } - if ( smartshortcapselftest ) + if ( con->smartshortcapselftest ) { if ( scsiSmartShortCapSelfTest (fd) != 0) { @@ -347,7 +350,7 @@ void scsiPrintMain (int fd) printf ("Use smartctl -%c to abort test\n", SMARTSELFTESTABORT); } - if ( smartshortselftest ) + if ( con->smartshortselftest ) { if ( scsiSmartShortSelfTest (fd) != 0) @@ -359,7 +362,7 @@ void scsiPrintMain (int fd) printf ("Use smartctl -%c to abort test\n", SMARTSELFTESTABORT); } - if ( smartextendselftest ) + if ( con->smartextendselftest ) { if ( scsiSmartExtendSelfTest (fd) != 0) { @@ -371,7 +374,7 @@ void scsiPrintMain (int fd) printf ("Use smartctl -%c to abort test\n", SMARTSELFTESTABORT); } - if ( smartextendcapselftest ) + if ( con->smartextendcapselftest ) { if ( scsiSmartExtendCapSelfTest (fd) != 0) @@ -384,7 +387,7 @@ void scsiPrintMain (int fd) printf ("Use smartctl -%c to abort test\n", SMARTSELFTESTABORT); } - if ( smartselftestabort ) + if ( con->smartselftestabort ) { if ( scsiSmartSelfTestAbort (fd) != 0) diff --git a/sm5/scsiprint.h b/sm5/scsiprint.h index 07b02c0f7..6a1b21084 100644 --- a/sm5/scsiprint.h +++ b/sm5/scsiprint.h @@ -28,11 +28,9 @@ #define SCSI_PRINT_H_ #ifndef CVSID5 -#define CVSID5 "$Id: scsiprint.h,v 1.6 2002/10/22 09:44:55 ballen4705 Exp $\n" +#define CVSID5 "$Id: scsiprint.h,v 1.7 2002/10/28 23:46:59 ballen4705 Exp $\n" #endif -void scsiPrintMain (int fd); - - +void scsiPrintMain(int fd); #endif diff --git a/sm5/smartctl.8 b/sm5/smartctl.8 index 02a3ffb9c..f7b94878c 100644 --- a/sm5/smartctl.8 +++ b/sm5/smartctl.8 @@ -1,6 +1,6 @@ \# Copyright (C) 2002 Bruce Allen <smartmontools-support@lists.sourceforge.net> \# -\# $Id: smartctl.8,v 1.20 2002/10/24 09:54:02 ballen4705 Exp $ +\# $Id: smartctl.8,v 1.21 2002/10/28 23:46:59 ballen4705 Exp $ \# \# 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 @@ -15,7 +15,7 @@ \# 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/ -.TH SMARTCTL 8 "$Date: 2002/10/24 09:54:02 $" "smartmontools-5.0" +.TH SMARTCTL 8 "$Date: 2002/10/28 23:46:59 $" "smartmontools-5.0" .SH NAME smartctl \- S.M.A.R.T. control and monitor utility .SH SYNOPSIS @@ -384,6 +384,19 @@ The device error log contains records of errors. .TP .B Bit 7: The device self-test log contains records of errors. + +To test within the shell for whether or not the different bits are +turned on or off, you can use the following type of construction (this +is bash syntax): +.nf +.B smartstat=$(($? & 8)) +.fi +This looks at only at bit 3 of the exit status +.B $? +(since 8=2^3). The shell variable +$smartstat will be nonzero if SMART status check returned 'disk +failing' and zero otherwise. + .PP .SH AUTHOR Bruce Allen @@ -458,4 +471,4 @@ Please let us know if there is an on\-line source for this document. .SH CVS ID OF THIS PAGE: -$Id: smartctl.8,v 1.20 2002/10/24 09:54:02 ballen4705 Exp $ +$Id: smartctl.8,v 1.21 2002/10/28 23:46:59 ballen4705 Exp $ diff --git a/sm5/smartctl.c b/sm5/smartctl.c index bfec094cd..f030db67a 100644 --- a/sm5/smartctl.c +++ b/sm5/smartctl.c @@ -36,39 +36,19 @@ #include "ataprint.h" #include "scsicmds.h" #include "scsiprint.h" +#include "extern.h" -extern const char *CVSid1, *CVSid2, *CVSid4, *CVSid5; -const char* CVSid6="$Id: smartctl.c,v 1.21 2002/10/26 19:33:40 ballen4705 Exp $" -CVSID1 CVSID2 CVSID4 CVSID5 CVSID6; - -unsigned char driveinfo = FALSE; -unsigned char checksmart = FALSE; -unsigned char smartvendorattrib = FALSE; -unsigned char generalsmartvalues = FALSE; -unsigned char smartselftestlog = FALSE; -unsigned char smarterrorlog = FALSE; -unsigned char smartdisable = FALSE; -unsigned char smartenable = FALSE; -unsigned char smartstatus = FALSE; -unsigned char smartexeoffimmediate = FALSE; -unsigned char smartshortselftest = FALSE; -unsigned char smartextendselftest = FALSE; -unsigned char smartshortcapselftest = FALSE; -unsigned char smartextendcapselftest = FALSE; -unsigned char smartselftestabort = FALSE; -unsigned char smartautoofflineenable = FALSE; -unsigned char smartautoofflinedisable = FALSE; -unsigned char smartautosaveenable = FALSE; -unsigned char smartautosavedisable = FALSE; -unsigned char printcopyleft = FALSE; -unsigned char smart009minutes = FALSE; -unsigned char quietmode = FALSE; -unsigned char veryquietmode = FALSE; -int testcase = -1; +extern const char *CVSid1, *CVSid2, *CVSid3, *CVSid4; +const char* CVSid5="$Id: smartctl.c,v 1.22 2002/10/28 23:46:59 ballen4705 Exp $" +CVSID1 CVSID2 CVSID3 CVSID4 CVSID5 CVSID6; +// This is a block containing all the "control variables". We declare +// this globally in this file, and externally in other files. +atamainctrl *con=NULL; void printslogan(){ - pout("smartctl version %d.%d-%d Copyright (C) 2002 Bruce Allen\n",RELEASE_MAJOR,RELEASE_MINOR,SMARTMONTOOLS_VERSION); + pout("smartctl version %d.%d-%d Copyright (C) 2002 Bruce Allen\n", + RELEASE_MAJOR,RELEASE_MINOR,SMARTMONTOOLS_VERSION); pout("Home page is %s\n\n",PROJECTHOME); return; } @@ -81,12 +61,12 @@ void printcopy(){ pout("under the terms of the GNU General Public License Version 2.\n"); pout("See http://www.gnu.org for further details.\n\n"); pout("CVS version IDs of files used to build this code are:\n"); - printone(out,CVSid6); - pout("%s",out); printone(out,CVSid1); pout("%s",out); printone(out,CVSid2); pout("%s",out); + printone(out,CVSid3); + pout("%s",out); printone(out,CVSid4); pout("%s",out); printone(out,CVSid5); @@ -140,99 +120,103 @@ const char opts[] = { SMARTAUTOSAVEENABLE,SMARTAUTOSAVEDISABLE,PRINTCOPYLEFT,SMART009MINUTES,QUIETMODE,VERYQUIETMODE,'h','?','\0' }; +unsigned char printcopyleft=0; + /* Takes command options and sets features to be run */ void ParseOpts (int argc, char** argv){ int optchar; extern char *optarg; extern int optopt, optind, opterr; + memset(con,0,sizeof(*con)); + con->testcase=-1; opterr=optopt=0; while (-1 != (optchar = getopt(argc, argv, opts))) { switch (optchar){ case QUIETMODE: - quietmode=TRUE; + con->quietmode=TRUE; break; case VERYQUIETMODE: - veryquietmode=TRUE; + con->veryquietmode=TRUE; break; case SMART009MINUTES: - smart009minutes=TRUE; + con->smart009minutes=TRUE; break; case PRINTCOPYLEFT : printcopyleft=TRUE; break; case DRIVEINFO : - driveinfo = TRUE; + con->driveinfo = TRUE; break; case CHECKSMART : - checksmart = TRUE; + con->checksmart = TRUE; break; case SMARTVERBOSEALL : - driveinfo = TRUE; - checksmart = TRUE; - generalsmartvalues = TRUE; - smartvendorattrib = TRUE; - smarterrorlog = TRUE; - smartselftestlog = TRUE; + con->driveinfo = TRUE; + con->checksmart = TRUE; + con->generalsmartvalues = TRUE; + con->smartvendorattrib = TRUE; + con->smarterrorlog = TRUE; + con->smartselftestlog = TRUE; break; case SMARTVENDORATTRIB : - smartvendorattrib = TRUE; + con->smartvendorattrib = TRUE; break; case GENERALSMARTVALUES : - generalsmartvalues = TRUE; + con->generalsmartvalues = TRUE; break; case SMARTERRORLOG : - smarterrorlog = TRUE; + con->smarterrorlog = TRUE; break; case SMARTSELFTESTLOG : - smartselftestlog = TRUE; + con->smartselftestlog = TRUE; break; case SMARTDISABLE : - smartdisable = TRUE; + con->smartdisable = TRUE; break; case SMARTENABLE : - smartenable = TRUE; + con->smartenable = TRUE; break; case SMARTAUTOSAVEENABLE: - smartautosaveenable = TRUE; + con->smartautosaveenable = TRUE; break; case SMARTAUTOSAVEDISABLE: - smartautosavedisable = TRUE; + con->smartautosavedisable = TRUE; break; case SMARTAUTOOFFLINEENABLE: - smartautoofflineenable = TRUE; + con->smartautoofflineenable = TRUE; break; case SMARTAUTOOFFLINEDISABLE: - smartautoofflinedisable = TRUE; + con->smartautoofflinedisable = TRUE; break; case SMARTEXEOFFIMMEDIATE: - smartexeoffimmediate = TRUE; - testcase=OFFLINE_FULL_SCAN; + con->smartexeoffimmediate = TRUE; + con->testcase=OFFLINE_FULL_SCAN; break; case SMARTSHORTSELFTEST : - smartshortselftest = TRUE; - testcase=SHORT_SELF_TEST; + con->smartshortselftest = TRUE; + con->testcase=SHORT_SELF_TEST; break; case SMARTEXTENDSELFTEST : - smartextendselftest = TRUE; - testcase=EXTEND_SELF_TEST; + con->smartextendselftest = TRUE; + con->testcase=EXTEND_SELF_TEST; break; case SMARTSHORTCAPSELFTEST: - smartshortcapselftest = TRUE; - testcase=SHORT_CAPTIVE_SELF_TEST; + con->smartshortcapselftest = TRUE; + con->testcase=SHORT_CAPTIVE_SELF_TEST; break; case SMARTEXTENDCAPSELFTEST: - smartextendcapselftest = TRUE; - testcase=EXTEND_CAPTIVE_SELF_TEST; + con->smartextendcapselftest = TRUE; + con->testcase=EXTEND_CAPTIVE_SELF_TEST; break; case SMARTSELFTESTABORT: - smartselftestabort = TRUE; - testcase=ABORT_SELF_TEST; + con->smartselftestabort = TRUE; + con->testcase=ABORT_SELF_TEST; break; case 'h': case '?': default: - veryquietmode=FALSE; + con->veryquietmode=FALSE; printslogan(); if (optopt){ pout("=======> UNRECOGNIZED OPTION: %c <=======\n\n",optopt); @@ -244,13 +228,13 @@ void ParseOpts (int argc, char** argv){ } } // Do this here, so results are independent of argument order - if (quietmode) - veryquietmode=TRUE; + if (con->quietmode) + con->veryquietmode=TRUE; // error message if user has asked for more than one test - if (1<(smartexeoffimmediate+smartshortselftest+smartextendselftest+ - smartshortcapselftest+smartextendcapselftest+smartselftestabort)){ - veryquietmode=FALSE; + if (1<(con->smartexeoffimmediate+con->smartshortselftest+con->smartextendselftest+ + con->smartshortcapselftest+con->smartextendcapselftest+con->smartselftestabort)){ + con->veryquietmode=FALSE; printslogan(); Usage(); printf ("\nERROR: smartctl can only run a single test (or abort) at a time.\n\n"); @@ -269,13 +253,13 @@ void ParseOpts (int argc, char** argv){ } -// Printing function (controlled by global veryquietmode) +// Printing function (controlled by global con->veryquietmode) void pout(char *fmt, ...){ va_list ap; // initialize variable argument list va_start(ap,fmt); - if (veryquietmode){ + if (con->veryquietmode){ va_end(ap); return; } @@ -291,7 +275,11 @@ void pout(char *fmt, ...){ int main (int argc, char **argv){ int fd,retval=0; char *device; - + atamainctrl control; + + // define control block for external functions + con=&control; + // Part input arguments ParseOpts(argc,argv); diff --git a/sm5/smartctl.cpp b/sm5/smartctl.cpp index 904421e37..26e39dbef 100644 --- a/sm5/smartctl.cpp +++ b/sm5/smartctl.cpp @@ -36,39 +36,19 @@ #include "ataprint.h" #include "scsicmds.h" #include "scsiprint.h" +#include "extern.h" -extern const char *CVSid1, *CVSid2, *CVSid4, *CVSid5; -const char* CVSid6="$Id: smartctl.cpp,v 1.21 2002/10/26 19:33:40 ballen4705 Exp $" -CVSID1 CVSID2 CVSID4 CVSID5 CVSID6; - -unsigned char driveinfo = FALSE; -unsigned char checksmart = FALSE; -unsigned char smartvendorattrib = FALSE; -unsigned char generalsmartvalues = FALSE; -unsigned char smartselftestlog = FALSE; -unsigned char smarterrorlog = FALSE; -unsigned char smartdisable = FALSE; -unsigned char smartenable = FALSE; -unsigned char smartstatus = FALSE; -unsigned char smartexeoffimmediate = FALSE; -unsigned char smartshortselftest = FALSE; -unsigned char smartextendselftest = FALSE; -unsigned char smartshortcapselftest = FALSE; -unsigned char smartextendcapselftest = FALSE; -unsigned char smartselftestabort = FALSE; -unsigned char smartautoofflineenable = FALSE; -unsigned char smartautoofflinedisable = FALSE; -unsigned char smartautosaveenable = FALSE; -unsigned char smartautosavedisable = FALSE; -unsigned char printcopyleft = FALSE; -unsigned char smart009minutes = FALSE; -unsigned char quietmode = FALSE; -unsigned char veryquietmode = FALSE; -int testcase = -1; +extern const char *CVSid1, *CVSid2, *CVSid3, *CVSid4; +const char* CVSid5="$Id: smartctl.cpp,v 1.22 2002/10/28 23:46:59 ballen4705 Exp $" +CVSID1 CVSID2 CVSID3 CVSID4 CVSID5 CVSID6; +// This is a block containing all the "control variables". We declare +// this globally in this file, and externally in other files. +atamainctrl *con=NULL; void printslogan(){ - pout("smartctl version %d.%d-%d Copyright (C) 2002 Bruce Allen\n",RELEASE_MAJOR,RELEASE_MINOR,SMARTMONTOOLS_VERSION); + pout("smartctl version %d.%d-%d Copyright (C) 2002 Bruce Allen\n", + RELEASE_MAJOR,RELEASE_MINOR,SMARTMONTOOLS_VERSION); pout("Home page is %s\n\n",PROJECTHOME); return; } @@ -81,12 +61,12 @@ void printcopy(){ pout("under the terms of the GNU General Public License Version 2.\n"); pout("See http://www.gnu.org for further details.\n\n"); pout("CVS version IDs of files used to build this code are:\n"); - printone(out,CVSid6); - pout("%s",out); printone(out,CVSid1); pout("%s",out); printone(out,CVSid2); pout("%s",out); + printone(out,CVSid3); + pout("%s",out); printone(out,CVSid4); pout("%s",out); printone(out,CVSid5); @@ -140,99 +120,103 @@ const char opts[] = { SMARTAUTOSAVEENABLE,SMARTAUTOSAVEDISABLE,PRINTCOPYLEFT,SMART009MINUTES,QUIETMODE,VERYQUIETMODE,'h','?','\0' }; +unsigned char printcopyleft=0; + /* Takes command options and sets features to be run */ void ParseOpts (int argc, char** argv){ int optchar; extern char *optarg; extern int optopt, optind, opterr; + memset(con,0,sizeof(*con)); + con->testcase=-1; opterr=optopt=0; while (-1 != (optchar = getopt(argc, argv, opts))) { switch (optchar){ case QUIETMODE: - quietmode=TRUE; + con->quietmode=TRUE; break; case VERYQUIETMODE: - veryquietmode=TRUE; + con->veryquietmode=TRUE; break; case SMART009MINUTES: - smart009minutes=TRUE; + con->smart009minutes=TRUE; break; case PRINTCOPYLEFT : printcopyleft=TRUE; break; case DRIVEINFO : - driveinfo = TRUE; + con->driveinfo = TRUE; break; case CHECKSMART : - checksmart = TRUE; + con->checksmart = TRUE; break; case SMARTVERBOSEALL : - driveinfo = TRUE; - checksmart = TRUE; - generalsmartvalues = TRUE; - smartvendorattrib = TRUE; - smarterrorlog = TRUE; - smartselftestlog = TRUE; + con->driveinfo = TRUE; + con->checksmart = TRUE; + con->generalsmartvalues = TRUE; + con->smartvendorattrib = TRUE; + con->smarterrorlog = TRUE; + con->smartselftestlog = TRUE; break; case SMARTVENDORATTRIB : - smartvendorattrib = TRUE; + con->smartvendorattrib = TRUE; break; case GENERALSMARTVALUES : - generalsmartvalues = TRUE; + con->generalsmartvalues = TRUE; break; case SMARTERRORLOG : - smarterrorlog = TRUE; + con->smarterrorlog = TRUE; break; case SMARTSELFTESTLOG : - smartselftestlog = TRUE; + con->smartselftestlog = TRUE; break; case SMARTDISABLE : - smartdisable = TRUE; + con->smartdisable = TRUE; break; case SMARTENABLE : - smartenable = TRUE; + con->smartenable = TRUE; break; case SMARTAUTOSAVEENABLE: - smartautosaveenable = TRUE; + con->smartautosaveenable = TRUE; break; case SMARTAUTOSAVEDISABLE: - smartautosavedisable = TRUE; + con->smartautosavedisable = TRUE; break; case SMARTAUTOOFFLINEENABLE: - smartautoofflineenable = TRUE; + con->smartautoofflineenable = TRUE; break; case SMARTAUTOOFFLINEDISABLE: - smartautoofflinedisable = TRUE; + con->smartautoofflinedisable = TRUE; break; case SMARTEXEOFFIMMEDIATE: - smartexeoffimmediate = TRUE; - testcase=OFFLINE_FULL_SCAN; + con->smartexeoffimmediate = TRUE; + con->testcase=OFFLINE_FULL_SCAN; break; case SMARTSHORTSELFTEST : - smartshortselftest = TRUE; - testcase=SHORT_SELF_TEST; + con->smartshortselftest = TRUE; + con->testcase=SHORT_SELF_TEST; break; case SMARTEXTENDSELFTEST : - smartextendselftest = TRUE; - testcase=EXTEND_SELF_TEST; + con->smartextendselftest = TRUE; + con->testcase=EXTEND_SELF_TEST; break; case SMARTSHORTCAPSELFTEST: - smartshortcapselftest = TRUE; - testcase=SHORT_CAPTIVE_SELF_TEST; + con->smartshortcapselftest = TRUE; + con->testcase=SHORT_CAPTIVE_SELF_TEST; break; case SMARTEXTENDCAPSELFTEST: - smartextendcapselftest = TRUE; - testcase=EXTEND_CAPTIVE_SELF_TEST; + con->smartextendcapselftest = TRUE; + con->testcase=EXTEND_CAPTIVE_SELF_TEST; break; case SMARTSELFTESTABORT: - smartselftestabort = TRUE; - testcase=ABORT_SELF_TEST; + con->smartselftestabort = TRUE; + con->testcase=ABORT_SELF_TEST; break; case 'h': case '?': default: - veryquietmode=FALSE; + con->veryquietmode=FALSE; printslogan(); if (optopt){ pout("=======> UNRECOGNIZED OPTION: %c <=======\n\n",optopt); @@ -244,13 +228,13 @@ void ParseOpts (int argc, char** argv){ } } // Do this here, so results are independent of argument order - if (quietmode) - veryquietmode=TRUE; + if (con->quietmode) + con->veryquietmode=TRUE; // error message if user has asked for more than one test - if (1<(smartexeoffimmediate+smartshortselftest+smartextendselftest+ - smartshortcapselftest+smartextendcapselftest+smartselftestabort)){ - veryquietmode=FALSE; + if (1<(con->smartexeoffimmediate+con->smartshortselftest+con->smartextendselftest+ + con->smartshortcapselftest+con->smartextendcapselftest+con->smartselftestabort)){ + con->veryquietmode=FALSE; printslogan(); Usage(); printf ("\nERROR: smartctl can only run a single test (or abort) at a time.\n\n"); @@ -269,13 +253,13 @@ void ParseOpts (int argc, char** argv){ } -// Printing function (controlled by global veryquietmode) +// Printing function (controlled by global con->veryquietmode) void pout(char *fmt, ...){ va_list ap; // initialize variable argument list va_start(ap,fmt); - if (veryquietmode){ + if (con->veryquietmode){ va_end(ap); return; } @@ -291,7 +275,11 @@ void pout(char *fmt, ...){ int main (int argc, char **argv){ int fd,retval=0; char *device; - + atamainctrl control; + + // define control block for external functions + con=&control; + // Part input arguments ParseOpts(argc,argv); diff --git a/sm5/smartd.8 b/sm5/smartd.8 index 6c0f06385..a6729f06e 100644 --- a/sm5/smartd.8 +++ b/sm5/smartd.8 @@ -13,7 +13,7 @@ \# 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/ -.TH SMARTD 8 "$Date: 2002/10/26 20:53:08 $" "smartmontools-5.0" +.TH SMARTD 8 "$Date: 2002/10/28 23:47:00 $" "smartmontools-5.0" .SH NAME smartd \- S.M.A.R.T. Daemon .SH SYNOPSIS @@ -27,8 +27,10 @@ Technology (S.M.A.R.T.) system built into many ATA-3 and later ATA, IDE and SCSI-3 hard drives. The purpose of S.M.A.R.T. is to monitor the reliability of the hard drive and predict drive failures, and to carry out different types of drive self-tests. This version of -smartd is compatible with ATA/ATAPI-5 and earlier standards (see -REFERENCES below) +.B smartd +is compatible with ATA/ATAPI-5 and earlier standards (see +.B REFERENCES +below) .B smartd will notify users of S.M.A.R.T. errors and changes of @@ -38,6 +40,12 @@ and warnings normally appear in It can be configured at start-up using the file .B /etc/smartd.conf. +Note that +.B smartd +only reads the configuration file at start-up. Changes to the +configuration file only take effect after the +.B smartd +daemon is restarted. .PP .SH SYNTAX @@ -45,8 +53,7 @@ using the file .B smartd takes either no arguments, or a single argument. The optional -argument begins with a '\-' followed by a letter or letters. Multiple -options must begin with a single '\-'. +argument begins with a '\-' followed by a letter or letters. In the absense of the configuration file .B /etc/smartd.conf @@ -55,14 +62,17 @@ the smartd daemon scans for all devices that support S.M.A.R.T., using "/dev/hd*" for IDE/ATA devices, and "/dev/sd*" for SCSI devices. It -polls these devices every 30 minutes checking for S.M.A.R.T. errors. -[Note that on start-up, when -.B -smartd scans for devices, a warning message may appear in -.B /var/log/messages, -about missing block-major-xx devices. These -messages are usually harmless. Alternatively, the configuration file can be -used to list devices to search for at start up.] +polls these devices every 30 minutes checking for all possible S.M.A.R.T. errors +(corresponding to the '\-a' option in the configuration file; please see +.B CONFIGURATION FILE +below). When +.B smartd +scans for devices on startup, +.B a warning message may appear in /var/log/messages, +about missing block-major-xx devices. These messages are usually +harmless. Alternatively, the configuration file can be used to exclude +non-existent devices by giving a list of devices to monitor at start +up. .P .SH OPTIONS @@ -73,8 +83,9 @@ STDOUT and then exits. Please include this information if you are reporting bugs. .TP .B X -eXamine: Runs smartd in "debug" mode. In this mode, it does not fork and -displays status information to STDOUT. +eXamine: Runs smartd in "debug" mode. In this mode, it does not fork +and displays status information to STDOUT. It also prints more +verbose information about what it is doing. .SH EXAMPLES @@ -82,70 +93,219 @@ displays status information to STDOUT. smartd .fi Runs the daemon in forked mode. This is the normal way to run -.B -smartd. -.fi +.B smartd. Entries are logged to .B /var/log/messages. .fi -Note that smartmontools provides a start-up script in +Note that +.B smartmontools +provides a start-up script in .B /etc/rc.d/init.d/smartd which is responsible for starting and stopping the daemon via the normal init interface. Using this script, you can start .B smartd -by giving the command -.B '/etc/rc.d/init.d/smartd start' -and stop it by using -.B '/etc/rc.d/init.d/smartd stop'. +by giving the command: +.nf +.B /etc/rc.d/init.d/smartd start +.fi +and stop it by using the command: +.nf +.B /etc/rc.d/init.d/smartd stop +.fi If you want .B smartd to start running whenever your machine is booted, this can be enabled by using the command: .nf -.B '/sbin/chkconfig --add smartd' +.B /sbin/chkconfig --add smartd .fi and disabled using the command: .nf -.B '/sbin/chkconfig --del smartd' +.B /sbin/chkconfig --del smartd -.SH CONFIGURATION FILE +.SH CONFIGURATION FILE /etc/smartd.conf In the absence of a configuration file, .B smartd -will try to open the 12 ATA devices /dev/hd[a-l] and the 26 -SCSI devices /dev/sd[a-z]. This can be annoying if you have an ATA or -SCSI device that hangs or misbehaves when receiving SMART commands. -Even if this causes no problems, you may be annoyed by the string of -error log messages about block-major devices that can't be found, and -SCSI devices that can't be opened. - -A simple solution is to create a configuration file -.B /etc/smartd.conf -which should contain a list of devices to monitor, with one device per line. -For security, this file should not be writable by anyone but root. -Spaces and tabs in the file are ignored, as are lines starting with a hash sign (#). -Here is a sample configuration file: +will try to open the 12 ATA devices +.B /dev/hd[a-l] +and the 26 SCSI +devices +.B /dev/sd[a-z]. +This can be annoying if you have an ATA or SCSI device that hangs or +misbehaves when receiving SMART commands. Even if this causes no +problems, you may be annoyed by the string of error log messages about +block-major devices that can't be found, and SCSI devices that can't +be opened. + +One can avoid this problem, and gain more control over what types of +changes and events +.B smartd +is watching for, by using the configuration file +.B /etc/smartd.conf. +This file contains a list of devices to monitor, with one device per +line. An example file is included with the +.B smartmontools +distribution, but is not normally installed in /etc. If +.B smartmontools +was properly installed on your system, you will find this sample +configuration file in +.B /usr/share/doc/smartmontools-5.0/. +For security, the configuration file should not be writable by anyone +but root. The syntax of the file is as follows: + +There should be one device listed per line, although you may have +lines that are entirely comments or white space. + +Any text following a hash sign (#) and up to the end of the line is +taken to be a comment, and ignored. + +Lines may be continued by using a backslash (\(rs) as the last +non-whitespace or non-comment item on a line. + +Here is an example configuration file. It's for illustrative purposes +only; please don't copy it onto your system without reading to the end +of the +.B DIRECTIVES +Section below! .nf .B # This is an example smartd startup config file -.B # /etc/smartd.conf -.B # for monitoring two IDE disks and one SCSI disk +.B # /etc/smartd.conf for monitoring three IDE disks +.B # and two SCSI disks. -.B # first IDE disk on each of two interfaces -.B /dev/hda -.B /dev/hdc +.nf +.B # First IDE disk on each of two interfaces +.B /dev/hda -a +.B /dev/hdc -a -I 194 -I 5 -i 12 +.nf .B # SCSI disk .B /dev/sda + +.nf +.B # Strange device. It's SCSI. +.B /dev/weird -S + +.nf +.B # And here is a continued line +.B /dev/hdd\ -L\ -l\ -t\ \(rs\ \ # Attributes below not tracked +.B\ \ \ \ \ \ \ \ \ \ \ \ -I\ 194\ \(rs\ \ # temperature +.B\ \ \ \ \ \ \ \ \ \ \ \ -I\ 231\ \(rs\ \ # also temperature +.B\ \ \ \ \ \ \ \ \ \ \ \ -I 9\ \ \ \ \ \ # power-on hours .fi -If -.B smartmontools -was properly installed on your system, you should be able to find a sample configuration file in -.B /usr/share/doc/smartmontools-5.0 + +.PP +.SH CONFIGURATION FILE DIRECTIVES +.PP +.sp 2 +The following are the directives that may appear following the device +name on any line of the +.B /etc/smartd.conf +configuration file. Note that +.B these are NOT command-line options for smartd. +The command-line options for +.B smartd +are listed above. The directives below may appear in any order, +following the device name. For the moment, apart from the '\-S' +option, these directives only apply to ATA disks. For ATA disks, if +no directives appear, the disk will not be monitored. The '\-a' +directive will try to monitor everything possible. +.TP +.B A +ATA: The device is an ATA device. Don't try issuing SCSI commands to it. +.TP +.B S +SCSI: The device is a SCSI device. Don't try issuing IDE/ATA +commands to it. + +In the absence of either of these directives, +.B smartd +will attempt to guess the device type by looking at whether the fifth +character in the device name is an 's' or an 'h'. If it can't guess +from this fifth character, then it will simply try to access the +device using first ATA and then SCSI ioctl()s. +.TP +.B c +Check: Will check the SMART status of the disk. If any prefailure +attributes are less than or equal to their threshold values, then disk +failure is predicted in less than 24 hours, and a message at priority +.B 'CRITICAL' +will be logged to syslog. +.TP +.B l +Log: Report if that the number of ATA errors reported in the ATA +Error Log has increased since the last check. +.TP +.B L +Log: Report if that the number of errors reported in the SMART +Self-Test Log has increased since the last check. Note that such +errors will +.I only +be logged if you run self-tests on the disk (and it fails the tests!). +Self-tests can be run by using the '\-SXsxO' options of +.B smartctl. +Please see the +.B smartctl +man pages. +.TP +.B f +Fail: Check for 'failure' of any usage attributes. If these +attributes are less than or equal to the threshold, it does NOT +indicate imminent disk failure. It "indicates an advisory condition +where the usage or age of the device has exceeded its intended design +life period." +.TP +.B p +Prefail: Report anytime that a prefail attribute has changed +its value since the last check, 30 min ago. +.TP +.B u +Usage: Report anytime that a usage attribute has changed its value +since the last check, 30 min ago. +.TP +.B t +Track: Equivalent to turning on the two previous flags '\-t' and '\-u'. +Tracks changes in +.I all +device attributes. +.TP +.B i INT +Ignore: This directive modifies the behavior of the '\-f' attribute +and has no effect without it. +.I This directive takes a decimal integer argument INT in the range from 1 to 255. +It means to ignore device attribute number INT, when checking for +failure of usage attributes. This is useful, for example, if you have +a very old disk and don't want to keep getting messages about the +hours-on-lifetime attribute (usually attribute 9) failing. +.TP +.B I INT +Ignore: This directive modifies the +behavior of the '\-p', '\-u', and '\-t' attributes +and has no effect without one of them. +.I This directive takes a decimal integer argument INT in the range from 1 to 255. +It means to ignore device attribute INT, when tracking changes in the +attribute values. This is useful, for example, if one of the device +attributes is the disk temperature (usually attribute 194 or +231). It's annoying to get reports each time the temperature changes. +.TP +.B a +All: equivalent to turning on the following directives: +.B '\-c' +to check the SMART status, +.B '\-f' +to report failures of usage (rather than pre-fail) attributes, +.B '\-t' +to track changes in both prefailure and usage attributes, +.B '\-L' +to report increases in the number of self-test log errors, and +.B '\-l' +to report increases in the number of ATA errors. + .SH NOTES .B smartd @@ -204,7 +364,8 @@ REFERENCES FOR S.M.A.R.T. If you would like to understand better how S.M.A.R.T. works, and what it does, a good place to start is Section 8.41 of the 'AT Attachment with Packet Interface-5' (ATA/ATAPI-5) specification. This -documents the S.M.A.R.T. functionality which the smartmontools +documents the S.M.A.R.T. functionality which the +.B smartmontools utilities provide access to. You can find Revision 1 of this document at: .nf @@ -242,4 +403,4 @@ Please let us know if there is an on\-line source for this document. .SH CVS ID OF THIS PAGE: -$Id: smartd.8,v 1.13 2002/10/26 20:53:08 ballen4705 Exp $ +$Id: smartd.8,v 1.14 2002/10/28 23:47:00 ballen4705 Exp $ diff --git a/sm5/smartd.c b/sm5/smartd.c index 5bb4c54c4..f3a86e062 100644 --- a/sm5/smartd.c +++ b/sm5/smartd.c @@ -20,8 +20,7 @@ * */ -#include <errno.h> -#include <stdlib.h> +#define _GNU_SOURCE #include <stdio.h> #include <sys/ioctl.h> #include <sys/types.h> @@ -32,15 +31,23 @@ #include <linux/hdreg.h> #include <syslog.h> #include <stdarg.h> - +#include <signal.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> #include "atacmds.h" #include "scsicmds.h" #include "smartd.h" +#include "ataprint.h" +#include "extern.h" // CVS ID strings extern const char *CVSid1, *CVSid2; -const char *CVSid3="$Id: smartd.c,v 1.36 2002/10/26 19:33:40 ballen4705 Exp $" -CVSID1 CVSID4 CVSID7; +const char *CVSid6="$Id: smartd.c,v 1.37 2002/10/28 23:47:00 ballen4705 Exp $" +CVSID1 CVSID2 CVSID3 CVSID4 CVSID7; + +// global variable used for control of printing, passing arguments, etc. +atamainctrl *con=NULL; // This function prints either to stdout or to the syslog as needed void printout(int priority,char *fmt, ...){ @@ -56,20 +63,33 @@ void printout(int priority,char *fmt, ...){ return; } -// Printing function for debugging atacmds. For debugging set 0 to 1 +// Printing function for debugging atacmds. // in #if statement void pout(char *fmt, ...){ va_list ap; - // initialize variable argument list va_start(ap,fmt); -#if (0) - vprintf(fmt,ap); -#endif + // in debug mode we will print the output from the ataprint.o functions! + if (debugmode) + vprintf(fmt,ap); va_end(ap); return; } +void goobye(){ + printout(LOG_CRIT,"smartd is exiting\n"); + return; +} + +volatile sig_atomic_t fatal = 0; +// simple signal handler to print goodby message to syslog +void sighandler(int sig){ + printout(LOG_CRIT,"smartd received signal %d: %s\n", + sig,strsignal(sig)); + exit(1); +} + + // Forks new process, closes all file descriptors, redirects stdin, // stdout, stderr int daemon_init(void){ @@ -78,16 +98,29 @@ int daemon_init(void){ if ((pid=fork()) < 0) { // unable to fork! - printout(LOG_CRIT,"Unable to fork daemon process!\n"); + printout(LOG_CRIT,"smartd unable to fork daemon process!\n"); exit(1); } else if (pid) // we are the parent process -- exit cleanly exit(0); - // from here on, we are the child process + // from here on, we are the child process. setsid(); + + // Fork one more time to avoid any possibility of having terminals + if ((pid=fork()) < 0) { + // unable to fork! + printout(LOG_CRIT,"smartd unable to fork daemon process!\n"); + exit(1); + } + else if (pid) + // we are the parent process -- exit cleanly + exit(0); + + // Now we are the child's child... + // close any open file descriptors for (i=getdtablesize();i>=0;--i) close(i); @@ -111,13 +144,37 @@ void printhead(){ return; } + +// prints help info for configuration file directives +void Directives() { + printout(LOG_INFO,"Configuration file Directives (following device name):\n"); + printout(LOG_INFO," -A Device is an ATA device\n"); + printout(LOG_INFO," -S Device is a SCSI device\n"); + printout(LOG_INFO," -c Monitor SMART Health Status\n"); + printout(LOG_INFO," -l Monitor SMART Error Log for changes\n"); + printout(LOG_INFO," -L Monitor SMART Self-Test Log for new errors\n"); + printout(LOG_INFO," -f Monitor for failure of any 'Usage' Attributes\n"); + printout(LOG_INFO," -p Report changes in 'Prefailure' Attributes\n"); + printout(LOG_INFO," -u Report changes in 'Usage' Attributes\n"); + printout(LOG_INFO," -t Equivalent to -p and -u Directives\n"); + printout(LOG_INFO," -i ID Ignore Attribute ID for -f Directive\n"); + printout(LOG_INFO," -I ID Ignore Attribute ID for -p, -u or -t Directive\n"); + printout(LOG_INFO," # Comment: text after a hash sign is ignored\n"); + printout(LOG_INFO," \\ Line continuation character\n"); + printout(LOG_INFO,"Attribute ID is a decimal integer 1 <= ID <= 255\n"); + printout(LOG_INFO,"All but -S directive are only implemented for ATA devices\n"); + printout(LOG_INFO,"Example: /dev/hda -a\n"); +return; +} + /* prints help information for command syntax */ void Usage (void){ printout(LOG_INFO,"usage: smartd -[opts] \n\n"); - printout(LOG_INFO,"Read Only Options:\n"); - printout(LOG_INFO," %c Start smartd in debug Mode\n",DEBUGMODE); - printout(LOG_INFO," %c Print License, Copyright, and version information\n\n",PRINTCOPYLEFT); + printout(LOG_INFO,"Command Line Options:\n"); + printout(LOG_INFO," %c Start smartd in debug Mode\n",DEBUGMODE); + printout(LOG_INFO," %c Print License, Copyright, and version information\n\n",PRINTCOPYLEFT); printout(LOG_INFO,"Optional configuration file: %s\n",CONFIGFILE); + Directives(); } // returns negative if problem, else fd>=0 @@ -125,9 +182,9 @@ int opendevice(char *device){ int fd = open(device, O_RDONLY); if (fd<0) { if (errno<sys_nerr) - printout(LOG_INFO,"%s: Device: %s, Opening device failed\n",sys_errlist[errno],device); + printout(LOG_INFO,"Device: %s, %s, open() failed\n",device, sys_errlist[errno]); else - printout(LOG_INFO,"Device: %s, Opening device failed\n",device); + printout(LOG_INFO,"Device: %s, open() failed\n",device); return -1; } // device opened sucessfully @@ -135,80 +192,150 @@ int opendevice(char *device){ } // returns 1 if problem, else zero -int closedevice(int fd){ +int closedevice(int fd, char *name){ if (close(fd)){ if (errno<sys_nerr) - printout(LOG_INFO,"%s: Closing file descriptor %d failed\n",sys_errlist[errno],fd); + printout(LOG_INFO,"Device: %s, %s, close(%d) failed\n", name, sys_errlist[errno], fd); else - printout(LOG_INFO,"Closing file descriptor %d failed\n",fd); + printout(LOG_INFO,"Device: %s, close(%d) failed\n",name,fd); return 1; } - // device opened sucessfully + // device sucessfully closed return 0; } +// returns <0 on failure +int ataerrorcount(int fd, char *name){ + struct ata_smart_errorlog log; + + if (-1==ataReadErrorLog(fd,&log)){ + printout(LOG_INFO,"Device: %s, Read SMART Error Log Failed\n",name); + return -1; + } + + // return current number of ATA errors + return log.error_log_pointer?log.ata_error_count:0; +} + +// returns <0 if problem +char selftesterrorcount(int fd, char *name){ + struct ata_smart_selftestlog log; + + if (-1==ataReadSelfTestLog(fd,&log)){ + printout(LOG_INFO,"Device: %s, Read SMART Self Test Log Failed\n",name); + return -1; + } + + // return current number of self-test errors + return (char)ataPrintSmartSelfTestlog(log,0); +} + + + // scan to see what ata devices there are, and if they support SMART -int atadevicescan (atadevices_t *devices, char *device){ +int atadevicescan2(atadevices_t *devices, cfgfile *cfg){ int fd; struct hd_driveid drive; + char *device=cfg->name; - printout(LOG_INFO,"Opening device %s\n", device); + // should we try to register this as an ATA device? + if (!(cfg->tryata)) + return 1; + + // open the device if ((fd=opendevice(device))<0) // device open failed return 1; + printout(LOG_INFO,"Device: %s, opened\n", device); + // Get drive identity structure if (ataReadHDIdentity (fd,&drive) || !ataSmartSupport(drive) || ataEnableSmart(fd)){ // device exists, but not able to do SMART + printout(LOG_INFO,"Device: %s, not SMART capable, or couldn't enable SMART\n",device); close(fd); - printout(LOG_INFO,"Device: %s, Found but not SMART capable, or couldn't enable SMART\n",device); - return 2; + return 2; } - // Does device support read values and read thresholds? We should - // modify this next block for devices that do support SMART status - // but don't support read values and read thresholds. - if (ataReadSmartValues (fd,&devices[numatadevices].smartval)){ - close(fd); - printout(LOG_INFO,"Device: %s, Read SMART Values Failed\n",device); - return 3; + // capability check: SMART status + if (cfg->smartcheck && ataSmartStatus2(fd)==-1){ + printout(LOG_INFO,"Device: %s, not capable of SMART Health Status check\n",device); + cfg->smartcheck=0; } - else if (ataReadSmartThresholds (fd,&devices[numatadevices].smartthres)){ + + // capability check: Read smart values and thresholds + if (cfg->usagefailed || cfg->prefail || cfg->usage) { + devices->smartval=(struct ata_smart_values *)calloc(1,sizeof(struct ata_smart_values)); + devices->smartthres=(struct ata_smart_thresholds *)calloc(1,sizeof(struct ata_smart_thresholds)); + + if (!devices->smartval || !devices->smartthres){ + printout(LOG_CRIT,"Not enough memory to obtain SMART data\n"); + exit(1); + } + + if (ataReadSmartValues(fd,devices->smartval) || + ataReadSmartThresholds (fd,devices->smartthres)){ + printout(LOG_INFO,"Device: %s, Read SMART Values and/or Thresholds Failed\n",device); + free(devices->smartval); + free(devices->smartthres); + cfg->usagefailed=cfg->prefail=cfg->usage=0; + } + } + + // capability check: self-test-log + if (cfg->selftest){ + char val=selftesterrorcount(fd, device); + if (val>=0) + cfg->selflogcount=val; + else + cfg->selftest=0; + } + + // capability check: ATA error log + if (cfg->errorlog){ + int val=ataerrorcount(fd, device); + if (val>=0) + cfg->ataerrorcount=val; + else + cfg->errorlog=0; + } + + // If not tests available or selected, return + if (!(cfg->errorlog || cfg->selftest || cfg->smartcheck || + cfg->usagefailed || cfg->prefail || cfg->usage)) { close(fd); - printout(LOG_INFO,"Device: %s, Read SMART Thresholds Failed\n",device); - return 4; + return 3; } - // Device exists, and does SMART. Add to list + // Do we still have entries available? if (numatadevices>=MAXATADEVICES){ printout(LOG_CRIT,"smartd has found more than MAXATADEVICES=%d ATA devices.\n" "Recompile code from " PROJECTHOME " with larger MAXATADEVICES\n",numatadevices); exit(1); } - - printout(LOG_INFO,"%s Found and is SMART capable. Adding to \"monitor\" list.\n",device); - strcpy(devices[numatadevices].devicename, device); - devices[numatadevices].drive = drive; - // This makes NO sense. We may want to know if the drive supports - // Offline Surface Scan, for example. But checking if it supports - // self-tests seems useless. In any case, smartd NEVER uses this - // field anywhere... - devices[numatadevices].selftest = - isSupportSelfTest(devices[numatadevices].smartval); + printout(LOG_INFO,"Device: %s, is SMART capable. Adding to \"monitor\" list.\n",device); + // no need to try sending SCSI commands to this device! + cfg->tryscsi=0; + + // we were called from a routine that has global storage for the name. Keep pointer. + devices->devicename=device; + devices->cfg=cfg; + numatadevices++; - closedevice(fd); + closedevice(fd, device); return 0; } + // This function is hard to read and ought to be rewritten. Why in the // world is the four-byte integer cast to a pointer to an eight-byte -// object?? -int scsidevicescan (scsidevices_t *devices, char *device){ +// object?? Can anyone explain this obscurity? +int scsidevicescan(scsidevices_t *devices, char *device){ int i, fd, smartsupport; unsigned char tBuf[4096]; - printout(LOG_INFO,"Opening device %s\n", device); + printout(LOG_INFO,"Device: %s, opening\n", device); if ((fd=opendevice(device))<0) // device open failed return 1; @@ -243,8 +370,10 @@ int scsidevicescan (scsidevices_t *devices, char *device){ } // now we can proceed to register the device - printout(LOG_INFO, "Device: %s, Found and is SMART capable. Adding to \"monitor\" list.\n",device); - strcpy(devices[numscsidevices].devicename,device); + printout(LOG_INFO, "Device: %s, is SMART capable. Adding to \"monitor\" list.\n",device); + + // since device points to global memory, just keep that address + devices[numscsidevices].devicename=device; // register the supported functionality. The smartd code does not // seem to make any further use of this information. @@ -263,83 +392,195 @@ int scsidevicescan (scsidevices_t *devices, char *device){ } } numscsidevices++; - closedevice(fd); + closedevice(fd, device); return 0; } +// We compare old and new values of the n'th attribute. Note that n +// is NOT the attribute ID number.. If equal, return 0. The thre +// structure is used to verify that the attributes are valid ones. If +// the new value is lower than the old value, then we return both old +// and new values. new value=>lowest byte, old value=>next-to-lowest +// byte, id value=>next-to-next-to-lowest byte., and prefail flag x as +// bottom bit of highest byte. See below (lsb on right) -void ataCompareSmartValues (atadevices_t *device, struct ata_smart_values new ){ - int i; - int oldval,newval,idold,idnew; - - for ( i =0; i < NUMBER_ATA_SMART_ATTRIBUTES; i++){ - // which device is it? - idnew=new.vendor_attributes[i].id; - idold=device->smartval.vendor_attributes[i].id; - - if (idold && idnew){ - // if it's a valid attribute, compare values - newval=new.vendor_attributes[i].current; - oldval=device->smartval.vendor_attributes[i].current; - if (oldval!=newval){ - // values have changed; print them - char *loc,attributename[64]; - loc=attributename; - ataPrintSmartAttribName(attributename,idnew); - // skip blank space in name - while (*loc && *loc==' ') - loc++; - printout(LOG_INFO, "Device: %s, SMART Attribute: %s changed from %i to %i\n", - device->devicename,loc,oldval,newval); - } - } - } +// [00000000x][attribute ID][old value][new value] +int ataCompareSmartValues2(struct ata_smart_values *new, + struct ata_smart_values *old, + struct ata_smart_thresholds *thresholds, + int n){ + struct ata_smart_attribute *now,*was; + struct ata_smart_threshold_entry *thre; + unsigned char oldval,newval; + int returnvalue; + + // check that attribute number in range, and no null pointers + if (n<0 || n>=NUMBER_ATA_SMART_ATTRIBUTES || !new || !old || !thre) + return 0; + + // pointers to disk's values and vendor's thresholds + now=new->vendor_attributes+n; + was=old->vendor_attributes+n; + thre=thresholds->thres_entries+n; + + // consider only valid attributes, with same valid ID in all structures + if (!now->id || !was->id || !thre->id || (now->id != was->id) || (now->id != thre->id)) + return 0; + + // if values have not changed, return + newval=now->current; + oldval=was->current; + + // if any values out of the allowed range, or the values haven't changed, return + if (!newval || !oldval || newval>0xfe || oldval>0xfe || oldval==newval) + return 0; + + // values have changed. Construct output + returnvalue=0; + returnvalue |= newval; + returnvalue |= oldval<<8; + returnvalue |= now->id<<16; + returnvalue |= (now->status.flag.prefailure)<<24; + + return returnvalue; } +// This looks to see if the corresponding bit of the 32 bytes is set. +// This wastes a few bytes of storage but eliminates all searching and +// sorting functions! Entry is ZERO <==> the attribute ON. Calling +// with set=0 tells you if the attribute is being tracked or not. +// Calling with set=1 turns the attribute OFF. +int isattoff(unsigned char attr,unsigned char *data, int set){ + // locate correct attribute + int loc=attr>>3; + int bit=attr & 0x07; + unsigned char mask=0x01<<bit; -int ataCheckDevice( atadevices_t *drive){ - struct ata_smart_values tempsmartval; - struct ata_smart_thresholds tempsmartthres; - int failed,fd; - char *loc,attributename[64]; + // attribute zero is always OFF + if (!attr) + return 1; + if (!set) + return (data[loc] & mask); + + data[loc]|=mask; + // return value when setting makes no sense! + return 0; +} + + +int ataCheckDevice(atadevices_t *drive){ + int fd,i; + char *name=drive->devicename; + cfgfile *cfg=drive->cfg; + // if we can't open device, fail gracefully rather than hard -- // perhaps the next time around we'll be able to open it - if ((fd=opendevice(drive->devicename))<0) + if ((fd=opendevice(name))<0) return 1; - // Coming into this function, *drive contains the last values measured, - // and we read the NEW values into tempsmartval - if (ataReadSmartValues(fd,&tempsmartval)) - printout(LOG_INFO, "%s:Failed to read SMART values\n", drive->devicename); - - // and we read the new thresholds into tempsmartthres - if (ataReadSmartThresholds(fd, &tempsmartthres)) - printout(LOG_INFO, "%s:Failed to read SMART thresholds\n",drive->devicename); - - // See if any vendor attributes are below minimum, and print them - // out. WHEN IT WORKS, we should here add a call to - // ataSmartStatus2() either in addition to or instead of the - // ataCheckSmart command below. This is the "right" long-term - // solution. - if ((failed=ataCheckSmart(tempsmartval,tempsmartthres,1))){ - ataPrintSmartAttribName(attributename,failed); - // skip blank space in name - loc=attributename; - while (*loc && *loc==' ') - loc++; - printout(LOG_CRIT,"Device: %s, Failed SMART attribute: %s. Use smartctl -a %s.\n", - drive->devicename,loc,drive->devicename); - } - - // see if any values have changed. Second argument is new values - ataCompareSmartValues(drive, tempsmartval); - - // Save the new values into *drive for the next time around - drive->smartval = tempsmartval; - drive->smartthres = tempsmartthres; - - closedevice(fd); + // check smart status + if (cfg->smartcheck){ + int status=ataSmartStatus2(fd); + if (status==-1) + printout(LOG_INFO,"Device: %s, not capable of SMART self-check\n",name); + else if (status==1) + printout(LOG_INFO,"Device: %s, FAILED SMART self-check. BACK UP DATA NOW!\n",name); + } + + // Check everything that depends upon SMART Data (eg, Attribute values) + if (cfg->usagefailed || cfg->prefail || cfg->usage){ + struct ata_smart_values curval; + struct ata_smart_thresholds *thresh=drive->smartthres; + + // Read current attribute values. *drive contains old values adn thresholds + if (ataReadSmartValues(fd,&curval)) + printout(LOG_INFO, "Device: %s, failed to read SMART Attribute Data\n", name); + else { + // look for failed usage attributes, or track usage or prefail attributes + for (i=0; i<NUMBER_ATA_SMART_ATTRIBUTES; i++) { + int att; + + // This block looks for usage attributes that have failed. + // Prefail attributes that have failed are returned with a + // positive sign. No failure returns 0. Usage attributes<0. + if (cfg->usagefailed && ((att=ataCheckAttribute(&curval, thresh, i))<0)){ + + // are we tracking this attribute? + att *= -1; + if (!isattoff(att, cfg->failatt, 0)){ + char attname[64], *loc=attname; + + // get attribute name & skip white space + ataPrintSmartAttribName(loc, att); + while (*loc && *loc==' ') loc++; + + // warning message + printout(LOG_CRIT,"Device: %s, Failed SMART usage attribute: %s. Use smartctl -v %s.\n", name, loc, name); + } + } + + // This block tracks usage or prefailure attributes to see if they are changing + if ((cfg->usage || cfg->prefail) && ((att=ataCompareSmartValues2(&curval, drive->smartval, thresh, i)))){ + const int mask=0xff; + int prefail=(att>>24) & mask; + int id =(att>>16) & mask; + int oldval =(att>>8) & mask; + int newval =(att>>0) & mask; + char attname[64],*loc=attname; + + // are we tracking this attribute? + if (!isattoff(id, cfg->trackatt, 0)){ + + // get attribute name, skip spaces + ataPrintSmartAttribName(loc, id); + while (*loc && *loc==' ') loc++; + + // prefailure attribute + if (cfg->prefail && prefail) + printout(LOG_INFO, "Device: %s, SMART Prefailure Attribute: %s changed from %i to %i\n", + name, loc, oldval, newval); + + // usage attribute + if (cfg->usage && !prefail) + printout(LOG_INFO, "Device: %s, SMART Usage Attribute: %s changed from %i to %i\n", + name, loc, oldval, newval); + } + } // endof block tracking usage or prefailure + } // end of loop over attributes + + // Save the new values into *drive for the next time around + memcpy(drive->smartval,&curval,sizeof(curval)); + } + } + + // check if number of selftest errors has increased (note: may also DECREASE) + if (cfg->selftest){ + char old=cfg->selflogcount; + char new=selftesterrorcount(fd, name); + if (new>old){ + printout(LOG_CRIT,"Device: %s, Self-Test Log error count increased from %d to %d\n", + name,old,new); + } + if (new>=0) + // Needed suince self-test error count may DECREASE + cfg->selflogcount=new; + } + + + // check if number of ATA errors has increased + if (cfg->errorlog){ + int old=cfg->ataerrorcount; + int new=ataerrorcount(fd, name); + if (new>old){ + printout(LOG_CRIT,"Device: %s, ATA error count increased from %d to %d\n", + name,old,new); + } + // this last line is probably not needed, count always increases + if (new>=0) + cfg->ataerrorcount=new; + } + closedevice(fd, name); return 0; } @@ -359,7 +600,7 @@ int scsiCheckDevice( scsidevices_t *drive){ currenttemp = triptemp = 0; if (scsiCheckSmart(fd, drive->SmartPageSupported, &returnvalue, ¤ttemp, &triptemp)) - printout(LOG_INFO, "%s:Failed to read SMART values\n", drive->devicename); + printout(LOG_INFO, "Device: %s, failed to read SMART values\n", drive->devicename); if (returnvalue) printout(LOG_CRIT, "Device: %s, SMART Failure: (%02x) %s\n", drive->devicename, @@ -375,11 +616,11 @@ int scsiCheckDevice( scsidevices_t *drive){ drive->devicename, (int) (currenttemp - drive->Temperature), (unsigned int) currenttemp ); drive->Temperature = currenttemp; } - closedevice(fd); + closedevice(fd, drive->devicename); return 0; } -void CheckDevices ( atadevices_t *atadevices, scsidevices_t *scsidevices){ +void CheckDevices(atadevices_t *atadevices, scsidevices_t *scsidevices){ int i; // If there are no devices to monitor, then exit @@ -410,12 +651,197 @@ char copyleftstring[]= cfgfile config[MAXENTRIES]; -// returns number of entries in config file, or 0 if no config file exists +int parsetoken(char *token,cfgfile *cfg){ + char sym=token[1]; + char *name=cfg->name; + int lineno=cfg->lineno; + char *delim=" \n\t"; + + // is the rest of the line a comment + if (*token=='#') + return 1; + + // is the token not recognized? + if (*token!='-' || strlen(token)!=2) { + printout(LOG_CRIT,"Drive: %s, unknown Directive: %s at line %d of file %s\n", + name,token,lineno,CONFIGFILE); + Directives(); + exit(1); + } + + // let's parse the token and swallow its argument + switch (sym) { + char *arg; + char *endptr; + int val; + + case 'A': + // ATA device + cfg->tryata=1; + cfg->tryscsi=0; + break; + case 'S': + //SCSI device + cfg->tryscsi=1; + cfg->tryata=0; + break; + case 'c': + // check SMART status + cfg->smartcheck=1; + break; + case 'f': + // check for failure of usage attributes + cfg->usagefailed=1; + break; + case 't': + // track changes in all vendor attributes + cfg->prefail=1; + cfg->usage=1; + break; + case 'p': + // track changes in prefail vendor attributes + cfg->prefail=1; + break; + case 'u': + // track changes in usage vendor attributes + cfg->usage=1; + break; + case 'L': + // track changes in self-test log + cfg->selftest=1; + break; + case 'l': + // track changes ATA error log + cfg->errorlog=1; + break; + case 'a': + // monitor everything + cfg->smartcheck=1; + cfg->prefail=1; + cfg->usagefailed=1; + cfg->usage=1; + cfg->selftest=1; + cfg->errorlog=1; + break; + case 'i': + case 'I': + // ignore a particular vendor attribute for tracking (i) or + // failure (I) + arg=strtok(NULL,delim); + // make sure argument is there + if (!arg) { + printout(LOG_CRIT,"Drive %s Directive: %s at line %d of file %s needs integer argument.\n", + name,token,lineno,CONFIGFILE); + Directives(); + exit(1); + } + // get argument value, check that it's properly-formed, an + // integer, and in-range + val=strtol(arg,&endptr,10); + if (*endptr!='\0' || val<=0 || val>255) { + printout(LOG_CRIT,"Drive %s Directive: %s at line %d of file %s has argument: %s, needs 0 < n < 256\n", + name,token,lineno,CONFIGFILE,arg); + Directives(); + exit(1); + } + // put into correct list (bitmaps, access only with isattoff() + // function. Turns OFF corresponding attribute. + if (sym=='I') + isattoff(val,cfg->trackatt,1); + else + isattoff(val,cfg->failatt,1); + break; + default: + printout(LOG_CRIT,"Drive: %s, unknown option: %s at line %d of file %s\n", + name,token,lineno,CONFIGFILE); + Directives(); + exit(1); + } + return 1; +} + + +int parseconfigline(int entry, int lineno,char *line){ + char *token,*copy; + char *delim=" \n\t"; + char *name; + int len; + cfgfile *cfg; + + if (!(copy=strdup(line))){ + perror("no memory available to parse line"); + exit(1); + } + + // get first token -- device name + if (!(name=strtok(copy,delim)) || *name=='#'){ + free(copy); + return 0; + } + + // Is there space for another entry? + if (entry>=MAXENTRIES){ + printout(LOG_CRIT,"Error: configuration file %s can have no more than %d entries\n", + CONFIGFILE,MAXENTRIES); + exit(1); + } + + // We've got a legit entry, clear structure + cfg=config+entry; + memset(cfg,0,sizeof(*config)); + + // Save info to process memory for after forking 32 bytes contains 1 + // bit per possible attribute ID. See isattoff() + cfg->name=strdup(name); + cfg->failatt=(unsigned char *)calloc(32,1); + cfg->trackatt=(unsigned char *)calloc(32,1); + + if (!cfg->name || !cfg->failatt || !cfg->trackatt) { + perror("no memory available to save name"); + exit(1); + } + + cfg->lineno=lineno; + cfg->tryscsi=cfg->tryata=1; + + // Try and recognize if a IDE or SCSI device. These can be + // overwritten by configuration file directives. + len=strlen(name); + if (len>5 && !strncmp("/dev/h",name, 6)) + cfg->tryscsi=0; + + if (len>5 && !strncmp("/dev/s",name, 6)) + cfg->tryata=0; + + // parse tokens one at a time from the file + while ((token=strtok(NULL,delim)) && parsetoken(token,cfg)){ +#if 0 + printout(LOG_INFO,"Parsed token %s\n",token); +#endif + } + + // basic sanity check -- are any options turned on? + if (!(cfg->smartcheck || cfg->usagefailed || cfg->prefail || cfg->usage || cfg->selftest || cfg->errorlog || cfg->tryscsi)){ + printout(LOG_CRIT,"Drive: %s, no monitoring Directives on line %d of file %s\n", + cfg->name, cfg->lineno, CONFIGFILE); + Directives(); + exit(1); + } + + entry++; + free(copy); + return 1; +} + +// returns number of entries in config file, or 0 if no config file +// exists. A config file with zero entries will cause an error +// message and an exit. int parseconfigfile(){ FILE *fp; - int entry=0,lineno=0; + int entry=0,lineno=1,cont=0,contlineno=0; char line[MAXLINELEN+2]; - + char fullline[MAXCONTLINE+1]; + // Open config file, if it exists fp=fopen(CONFIGFILE,"r"); if (fp==NULL && errno!=ENOENT){ @@ -432,14 +858,34 @@ int parseconfigfile(){ if (fp==NULL) return 0; - // configuration file exists. Read it and search for devices + // configuration file exists printout(LOG_INFO,"Using configuration file %s\n",CONFIGFILE); - while (fgets(line,MAXLINELEN+2,fp)){ - int len; - char *dev; + + // parse config file line by line + while (1) { + int len=0; + char *lastslash; + char *comment; + char *code; + + // make debugging simpler + memset(line,0,sizeof(line)); + + // get a line + code=fgets(line,MAXLINELEN+2,fp); - // track linenumber for error messages - lineno++; + // are we at the end of the file? + if (!code){ + if (cont) { + // the final line is part of a continuation line + cont=0; + entry+=parseconfigline(entry,lineno,fullline); + } + break; + } + + // input file line number + contlineno++; // See if line is too long len=strlen(line); @@ -449,77 +895,44 @@ int parseconfigfile(){ warn="(including newline!) "; else warn=""; - printout(LOG_CRIT,"Error: line %d of file %s %sis more than than %d characters long.\n", - lineno,CONFIGFILE,warn,MAXLINELEN); + printout(LOG_CRIT,"Error: line %d of file %s %sis more than %d characters.\n", + contlineno,CONFIGFILE,warn,MAXLINELEN); exit(1); } - - // eliminate any terminating newline - if (line[len-1]=='\n'){ - len--; - line[len]='\0'; + + // Ignore anything after comment symbol + if ((comment=index(line,'#'))){ + *comment='\0'; + len=strlen(line); } - - // Skip white space - dev=line; - while (*dev && (*dev==' ' || *dev=='\t')) - dev++; - len=strlen(dev); - - // If line is blank, or a comment, skip it - if (!len || *dev=='#') - continue; - -#if 0 - // This is the start of some code to handle continuation - // characters in the /etc/smartd.conf file. Note that these must - // be the final character on a line, with just a newline after - // them. No other white space afterwards. - if (continuation+len>maxlinelen) - error; + // is the total line (made of all continuation lines) too long? + if (cont+len>MAXCONTLINE){ + printout(LOG_CRIT,"Error: continued line %d (actual line %d) of file %s is more than %d characters.\n", + lineno,contlineno,CONFIGFILE,MAXCONTLINE); + exit(1); + } - curr=config[entry].name+continuation; - strcpy(curr,dev); - + // copy string so far into fullline, and increment length + strcpy(fullline+cont,line); + cont+=len; - if (cur[len-1]=='\ '){ - cur[len-1]=' '; - continuation+=len; + // is this a continuation line. If so, replace \ by space and look at next line + if ( (lastslash=rindex(line,'\\')) && !strtok(lastslash+1," \n\t")){ + *(fullline+(cont-len)+(lastslash-line))=' '; continue; } - else { - continuation=0; - lineno++; - } -#endif - // We've got a legit entry - if (entry>=MAXENTRIES){ - printout(LOG_CRIT,"Error: configuration file %s can have no more than %d entries\n", - CONFIGFILE,MAXENTRIES); - exit(1); - } - - // Copy information into data structure for after forking - strcpy(config[entry].name,dev); - config[entry].lineno=lineno; - config[entry].tryscsi=config[entry].tryata=1; - - // Try and recognize if a IDE or SCSI device - if (len>5 && !strncmp("/dev/h",dev, 6)) - config[entry].tryscsi=0; - - if (len>5 && !strncmp("/dev/s",dev, 6)) - config[entry].tryata=0; - - entry++; + // Not a continuation line. Parse it + entry+=parseconfigline(entry,lineno,fullline); + lineno++; + cont=0; } fclose(fp); if (entry) return entry; - printout(LOG_CRIT,"Configuration file %s contained no devices (like /dev/hda)\n",CONFIGFILE); + printout(LOG_CRIT,"Configuration file %s contains no devices (like /dev/hda)\n",CONFIGFILE); exit(1); } @@ -571,12 +984,12 @@ void ParseOpts(int argc, char **argv){ printhead(); printout(LOG_INFO,copyleftstring); printout(LOG_INFO,"CVS version IDs of files used to build this code are:\n"); - printone(out,CVSid3); - printout(LOG_INFO,"%s",out); printone(out,CVSid1); printout(LOG_INFO,"%s",out); printone(out,CVSid2); printout(LOG_INFO,"%s",out); + printone(out,CVSid6); + printout(LOG_INFO,"%s",out); exit(0); } @@ -585,17 +998,85 @@ void ParseOpts(int argc, char **argv){ return; } +// Function we call if no configuration file was found. It makes +// entries for /dev/hd[a-l] and /dev/sd[a-z]. +int makeconfigentries(int num, char *name, int isata, int start){ + int i; + + if (MAXENTRIES<(start+num)){ + printout(LOG_CRIT,"Error: simulated config file can have no more than %d entries\n",MAXENTRIES); + exit(1); + } + + for(i=0; i<num; i++){ + cfgfile *cfg=config+start+i; + + // clear all fields of structure + memset(cfg,0,sizeof(*cfg)); + + // select if it's a SCSI or ATA device + cfg->tryata=isata; + cfg->tryscsi=!isata; + + // enable all possible tests + cfg->smartcheck=1; + cfg->prefail=1; + cfg->usagefailed=1; + cfg->usage=1; + cfg->selftest=1; + cfg->errorlog=1; + + // lineno==0 is our clue that the device was not found in a + // config file! + cfg->lineno=0; + + // put in the device name + cfg->name=strdup(name); + cfg->failatt=(unsigned char *)calloc(32,1); + cfg->trackatt=(unsigned char *)calloc(32,1); + if (!cfg->name || !cfg->failatt || !cfg->trackatt) { + perror("no memory available to save name"); + exit(1); + } + // increment final character of the name + cfg->name[strlen(name)-1]+=i; + } + return i; +} + + +void cantregister(char *name, char *type, int line){ + if (line) + printout(LOG_INFO,"Unable to register %s device %s at line %d of file %s\n", + type, name, line, CONFIGFILE); + else + printout(LOG_INFO,"Unable to register %s device %s\n", + type, name); + return; +} + + /* Main Program */ int main (int argc, char **argv){ atadevices_t atadevices[MAXATADEVICES], *atadevicesptr=atadevices; scsidevices_t scsidevices[MAXSCSIDEVICES], *scsidevicesptr=scsidevices; int i,entries; + atamainctrl control; + + // initialize global communications variables + con=&control; + memset(con,0,sizeof(control)); + // initialize global counters numatadevices=numscsidevices=0; // Parse input and print header and usage info if needed ParseOpts(argc,argv); - + + // Do we mute printing from ataprint commands? + con->quietmode=0; + con->veryquietmode=debugmode?0:1; + // look in configuration file CONFIGFILE (normally /etc/smartd.conf) entries=parseconfigfile(); @@ -603,30 +1084,37 @@ int main (int argc, char **argv){ if (!debugmode){ daemon_init(); } + + // setup signal handler for shutdown + if (signal(SIGINT, sighandler)==SIG_IGN) + signal(SIGINT, SIG_IGN); + if (signal(SIGTERM, sighandler)==SIG_IGN) + signal(SIGTERM, SIG_IGN); + if (signal(SIGQUIT, sighandler)==SIG_IGN) + signal(SIGQUIT, SIG_IGN); - // If we found a config file, register its entries - if (entries) - for (i=0;i<entries;i++){ - // register ATA devices - if (config[i].tryata && atadevicescan(atadevicesptr, config[i].name)) - printout(LOG_INFO,"Unable to register ATA device %s at line %d of file %s\n", - config[i].name, config[i].lineno, CONFIGFILE); - // then register SCSI devices - if (config[i].tryscsi && scsidevicescan(scsidevicesptr, config[i].name)) - printout(LOG_INFO,"Unable to register SCSI device %s at line %d of file %s\n", - config[i].name, config[i].lineno, CONFIGFILE); - } - else { - // since there was no config file found, search all ATA and SCSI disks - char deviceata[] = "/dev/hda"; - char devicescsi[]= "/dev/sda"; - printout(LOG_INFO,"No configuration file %s found. Searching for devices.\n",CONFIGFILE); - for(i=0;i<MAXATADEVICES;i++,deviceata[7]++) - atadevicescan(atadevicesptr, deviceata); - for(i=0;i<MAXSCSIDEVICES;i++,devicescsi[7]++) - scsidevicescan(scsidevicesptr, devicescsi); + // install goobye message + atexit(goobye); + + // if there was no config file, create needed entries + if (!entries){ + printout(LOG_INFO,"smartctl: file %s not found. Searching for devices.\n",CONFIGFILE); + entries+=makeconfigentries(MAXATADEVICES,"/dev/hda",1,entries); + entries+=makeconfigentries(MAXSCSIDEVICES,"/dev/sda",0,entries); + } + + // Register entries + for (i=0;i<entries;i++){ + // register ATA devices + if (config[i].tryata && atadevicescan2(atadevicesptr+numatadevices, config+i)) + cantregister(config[i].name, "ATA", config[i].lineno); + + // then register SCSI devices + if (config[i].tryscsi && scsidevicescan(scsidevicesptr, config[i].name)) + cantregister(config[i].name, "SCSI", config[i].lineno); } + // Now start an infinite loop that checks all devices CheckDevices(atadevicesptr, scsidevicesptr); return 0; diff --git a/sm5/smartd.conf b/sm5/smartd.conf index c4afbef0c..0114e5994 100644 --- a/sm5/smartd.conf +++ b/sm5/smartd.conf @@ -3,21 +3,41 @@ # Sample configuration file for smartd. See man 8 smartd. # Home page is: http://smartmontools.sourceforge.net -# The file gives a list of devices to monitor using smartd with one -# device per line. Since lines starting with a hash (#) are ignored, as are -# blanks and tabs, this file only contains a single entry, for /dev/hda +# The file gives a list of devices to monitor using smartd, with one +# device per line. Text after a hash (#) is ignored, and you may use +# spaces and tabs for white space. You may use '\' to continue lines. -# You can usually identify which hard disks are on your system by looking -# in /proc/ide and in /proc/scsi +# You can usually identify which hard disks are on your system by +# looking in /proc/ide and in /proc/scsi. -# First (primary) ATA/IDE hard disk -/dev/hda +# First (primary) ATA/IDE hard disk. Monitor all attributes +/dev/hda -a -# Other ATA/IDE hard disks -# /dev/hdb -# /dev/hdc -# /dev/hdd +# Monitor SMART status, ATA Error Log, Self-test log, and track +# changes in all attributes except for attribute 194 +/dev/hdb -c l L -t -I 194 /dev/hdc -# First two SCSI disks -# /dev/sda -# /dev/sdb +# A very silent check. Only report SMART health status if it fails +/dev/hdc -c + +# First two SCSI disks. Note that only the S directive applies +/dev/sda -S +/dev/sdb -S + + +# HERE IS A LIST OF DIRECTIVES FOR THIS CONFIGURATION FILE +# -A Device is an ATA device +# -S Device is a SCSI device +# -c Monitor SMART Health Status +# -l Monitor SMART Error Log for changes +# -L Monitor SMART Self-Test Log for new errors +# -f Monitor for failure of any 'Usage' Attributes +# -p Report changes in 'Prefailure' Attributes +# -u Report changes in 'Usage' Attributes +# -t Equivalent to -p and -u Directives +# -i ID Ignore Attribute ID for -f Directive +# -I ID Ignore Attribute ID for -p, -u or -t Directive +# # Comment: text after a hash sign is ignored +# \ Line continuation character +# Attribute ID is a decimal integer 1 <= ID <= 255 +# All but -S directive are only implemented for ATA devices diff --git a/sm5/smartd.cpp b/sm5/smartd.cpp index 7e3fe7e40..6cb7d5156 100644 --- a/sm5/smartd.cpp +++ b/sm5/smartd.cpp @@ -20,8 +20,7 @@ * */ -#include <errno.h> -#include <stdlib.h> +#define _GNU_SOURCE #include <stdio.h> #include <sys/ioctl.h> #include <sys/types.h> @@ -32,15 +31,23 @@ #include <linux/hdreg.h> #include <syslog.h> #include <stdarg.h> - +#include <signal.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> #include "atacmds.h" #include "scsicmds.h" #include "smartd.h" +#include "ataprint.h" +#include "extern.h" // CVS ID strings extern const char *CVSid1, *CVSid2; -const char *CVSid3="$Id: smartd.cpp,v 1.36 2002/10/26 19:33:40 ballen4705 Exp $" -CVSID1 CVSID4 CVSID7; +const char *CVSid6="$Id: smartd.cpp,v 1.37 2002/10/28 23:47:00 ballen4705 Exp $" +CVSID1 CVSID2 CVSID3 CVSID4 CVSID7; + +// global variable used for control of printing, passing arguments, etc. +atamainctrl *con=NULL; // This function prints either to stdout or to the syslog as needed void printout(int priority,char *fmt, ...){ @@ -56,20 +63,33 @@ void printout(int priority,char *fmt, ...){ return; } -// Printing function for debugging atacmds. For debugging set 0 to 1 +// Printing function for debugging atacmds. // in #if statement void pout(char *fmt, ...){ va_list ap; - // initialize variable argument list va_start(ap,fmt); -#if (0) - vprintf(fmt,ap); -#endif + // in debug mode we will print the output from the ataprint.o functions! + if (debugmode) + vprintf(fmt,ap); va_end(ap); return; } +void goobye(){ + printout(LOG_CRIT,"smartd is exiting\n"); + return; +} + +volatile sig_atomic_t fatal = 0; +// simple signal handler to print goodby message to syslog +void sighandler(int sig){ + printout(LOG_CRIT,"smartd received signal %d: %s\n", + sig,strsignal(sig)); + exit(1); +} + + // Forks new process, closes all file descriptors, redirects stdin, // stdout, stderr int daemon_init(void){ @@ -78,16 +98,29 @@ int daemon_init(void){ if ((pid=fork()) < 0) { // unable to fork! - printout(LOG_CRIT,"Unable to fork daemon process!\n"); + printout(LOG_CRIT,"smartd unable to fork daemon process!\n"); exit(1); } else if (pid) // we are the parent process -- exit cleanly exit(0); - // from here on, we are the child process + // from here on, we are the child process. setsid(); + + // Fork one more time to avoid any possibility of having terminals + if ((pid=fork()) < 0) { + // unable to fork! + printout(LOG_CRIT,"smartd unable to fork daemon process!\n"); + exit(1); + } + else if (pid) + // we are the parent process -- exit cleanly + exit(0); + + // Now we are the child's child... + // close any open file descriptors for (i=getdtablesize();i>=0;--i) close(i); @@ -111,13 +144,37 @@ void printhead(){ return; } + +// prints help info for configuration file directives +void Directives() { + printout(LOG_INFO,"Configuration file Directives (following device name):\n"); + printout(LOG_INFO," -A Device is an ATA device\n"); + printout(LOG_INFO," -S Device is a SCSI device\n"); + printout(LOG_INFO," -c Monitor SMART Health Status\n"); + printout(LOG_INFO," -l Monitor SMART Error Log for changes\n"); + printout(LOG_INFO," -L Monitor SMART Self-Test Log for new errors\n"); + printout(LOG_INFO," -f Monitor for failure of any 'Usage' Attributes\n"); + printout(LOG_INFO," -p Report changes in 'Prefailure' Attributes\n"); + printout(LOG_INFO," -u Report changes in 'Usage' Attributes\n"); + printout(LOG_INFO," -t Equivalent to -p and -u Directives\n"); + printout(LOG_INFO," -i ID Ignore Attribute ID for -f Directive\n"); + printout(LOG_INFO," -I ID Ignore Attribute ID for -p, -u or -t Directive\n"); + printout(LOG_INFO," # Comment: text after a hash sign is ignored\n"); + printout(LOG_INFO," \\ Line continuation character\n"); + printout(LOG_INFO,"Attribute ID is a decimal integer 1 <= ID <= 255\n"); + printout(LOG_INFO,"All but -S directive are only implemented for ATA devices\n"); + printout(LOG_INFO,"Example: /dev/hda -a\n"); +return; +} + /* prints help information for command syntax */ void Usage (void){ printout(LOG_INFO,"usage: smartd -[opts] \n\n"); - printout(LOG_INFO,"Read Only Options:\n"); - printout(LOG_INFO," %c Start smartd in debug Mode\n",DEBUGMODE); - printout(LOG_INFO," %c Print License, Copyright, and version information\n\n",PRINTCOPYLEFT); + printout(LOG_INFO,"Command Line Options:\n"); + printout(LOG_INFO," %c Start smartd in debug Mode\n",DEBUGMODE); + printout(LOG_INFO," %c Print License, Copyright, and version information\n\n",PRINTCOPYLEFT); printout(LOG_INFO,"Optional configuration file: %s\n",CONFIGFILE); + Directives(); } // returns negative if problem, else fd>=0 @@ -125,9 +182,9 @@ int opendevice(char *device){ int fd = open(device, O_RDONLY); if (fd<0) { if (errno<sys_nerr) - printout(LOG_INFO,"%s: Device: %s, Opening device failed\n",sys_errlist[errno],device); + printout(LOG_INFO,"Device: %s, %s, open() failed\n",device, sys_errlist[errno]); else - printout(LOG_INFO,"Device: %s, Opening device failed\n",device); + printout(LOG_INFO,"Device: %s, open() failed\n",device); return -1; } // device opened sucessfully @@ -135,80 +192,150 @@ int opendevice(char *device){ } // returns 1 if problem, else zero -int closedevice(int fd){ +int closedevice(int fd, char *name){ if (close(fd)){ if (errno<sys_nerr) - printout(LOG_INFO,"%s: Closing file descriptor %d failed\n",sys_errlist[errno],fd); + printout(LOG_INFO,"Device: %s, %s, close(%d) failed\n", name, sys_errlist[errno], fd); else - printout(LOG_INFO,"Closing file descriptor %d failed\n",fd); + printout(LOG_INFO,"Device: %s, close(%d) failed\n",name,fd); return 1; } - // device opened sucessfully + // device sucessfully closed return 0; } +// returns <0 on failure +int ataerrorcount(int fd, char *name){ + struct ata_smart_errorlog log; + + if (-1==ataReadErrorLog(fd,&log)){ + printout(LOG_INFO,"Device: %s, Read SMART Error Log Failed\n",name); + return -1; + } + + // return current number of ATA errors + return log.error_log_pointer?log.ata_error_count:0; +} + +// returns <0 if problem +char selftesterrorcount(int fd, char *name){ + struct ata_smart_selftestlog log; + + if (-1==ataReadSelfTestLog(fd,&log)){ + printout(LOG_INFO,"Device: %s, Read SMART Self Test Log Failed\n",name); + return -1; + } + + // return current number of self-test errors + return (char)ataPrintSmartSelfTestlog(log,0); +} + + + // scan to see what ata devices there are, and if they support SMART -int atadevicescan (atadevices_t *devices, char *device){ +int atadevicescan2(atadevices_t *devices, cfgfile *cfg){ int fd; struct hd_driveid drive; + char *device=cfg->name; - printout(LOG_INFO,"Opening device %s\n", device); + // should we try to register this as an ATA device? + if (!(cfg->tryata)) + return 1; + + // open the device if ((fd=opendevice(device))<0) // device open failed return 1; + printout(LOG_INFO,"Device: %s, opened\n", device); + // Get drive identity structure if (ataReadHDIdentity (fd,&drive) || !ataSmartSupport(drive) || ataEnableSmart(fd)){ // device exists, but not able to do SMART + printout(LOG_INFO,"Device: %s, not SMART capable, or couldn't enable SMART\n",device); close(fd); - printout(LOG_INFO,"Device: %s, Found but not SMART capable, or couldn't enable SMART\n",device); - return 2; + return 2; } - // Does device support read values and read thresholds? We should - // modify this next block for devices that do support SMART status - // but don't support read values and read thresholds. - if (ataReadSmartValues (fd,&devices[numatadevices].smartval)){ - close(fd); - printout(LOG_INFO,"Device: %s, Read SMART Values Failed\n",device); - return 3; + // capability check: SMART status + if (cfg->smartcheck && ataSmartStatus2(fd)==-1){ + printout(LOG_INFO,"Device: %s, not capable of SMART Health Status check\n",device); + cfg->smartcheck=0; } - else if (ataReadSmartThresholds (fd,&devices[numatadevices].smartthres)){ + + // capability check: Read smart values and thresholds + if (cfg->usagefailed || cfg->prefail || cfg->usage) { + devices->smartval=(struct ata_smart_values *)calloc(1,sizeof(struct ata_smart_values)); + devices->smartthres=(struct ata_smart_thresholds *)calloc(1,sizeof(struct ata_smart_thresholds)); + + if (!devices->smartval || !devices->smartthres){ + printout(LOG_CRIT,"Not enough memory to obtain SMART data\n"); + exit(1); + } + + if (ataReadSmartValues(fd,devices->smartval) || + ataReadSmartThresholds (fd,devices->smartthres)){ + printout(LOG_INFO,"Device: %s, Read SMART Values and/or Thresholds Failed\n",device); + free(devices->smartval); + free(devices->smartthres); + cfg->usagefailed=cfg->prefail=cfg->usage=0; + } + } + + // capability check: self-test-log + if (cfg->selftest){ + char val=selftesterrorcount(fd, device); + if (val>=0) + cfg->selflogcount=val; + else + cfg->selftest=0; + } + + // capability check: ATA error log + if (cfg->errorlog){ + int val=ataerrorcount(fd, device); + if (val>=0) + cfg->ataerrorcount=val; + else + cfg->errorlog=0; + } + + // If not tests available or selected, return + if (!(cfg->errorlog || cfg->selftest || cfg->smartcheck || + cfg->usagefailed || cfg->prefail || cfg->usage)) { close(fd); - printout(LOG_INFO,"Device: %s, Read SMART Thresholds Failed\n",device); - return 4; + return 3; } - // Device exists, and does SMART. Add to list + // Do we still have entries available? if (numatadevices>=MAXATADEVICES){ printout(LOG_CRIT,"smartd has found more than MAXATADEVICES=%d ATA devices.\n" "Recompile code from " PROJECTHOME " with larger MAXATADEVICES\n",numatadevices); exit(1); } - - printout(LOG_INFO,"%s Found and is SMART capable. Adding to \"monitor\" list.\n",device); - strcpy(devices[numatadevices].devicename, device); - devices[numatadevices].drive = drive; - // This makes NO sense. We may want to know if the drive supports - // Offline Surface Scan, for example. But checking if it supports - // self-tests seems useless. In any case, smartd NEVER uses this - // field anywhere... - devices[numatadevices].selftest = - isSupportSelfTest(devices[numatadevices].smartval); + printout(LOG_INFO,"Device: %s, is SMART capable. Adding to \"monitor\" list.\n",device); + // no need to try sending SCSI commands to this device! + cfg->tryscsi=0; + + // we were called from a routine that has global storage for the name. Keep pointer. + devices->devicename=device; + devices->cfg=cfg; + numatadevices++; - closedevice(fd); + closedevice(fd, device); return 0; } + // This function is hard to read and ought to be rewritten. Why in the // world is the four-byte integer cast to a pointer to an eight-byte -// object?? -int scsidevicescan (scsidevices_t *devices, char *device){ +// object?? Can anyone explain this obscurity? +int scsidevicescan(scsidevices_t *devices, char *device){ int i, fd, smartsupport; unsigned char tBuf[4096]; - printout(LOG_INFO,"Opening device %s\n", device); + printout(LOG_INFO,"Device: %s, opening\n", device); if ((fd=opendevice(device))<0) // device open failed return 1; @@ -243,8 +370,10 @@ int scsidevicescan (scsidevices_t *devices, char *device){ } // now we can proceed to register the device - printout(LOG_INFO, "Device: %s, Found and is SMART capable. Adding to \"monitor\" list.\n",device); - strcpy(devices[numscsidevices].devicename,device); + printout(LOG_INFO, "Device: %s, is SMART capable. Adding to \"monitor\" list.\n",device); + + // since device points to global memory, just keep that address + devices[numscsidevices].devicename=device; // register the supported functionality. The smartd code does not // seem to make any further use of this information. @@ -263,83 +392,195 @@ int scsidevicescan (scsidevices_t *devices, char *device){ } } numscsidevices++; - closedevice(fd); + closedevice(fd, device); return 0; } +// We compare old and new values of the n'th attribute. Note that n +// is NOT the attribute ID number.. If equal, return 0. The thre +// structure is used to verify that the attributes are valid ones. If +// the new value is lower than the old value, then we return both old +// and new values. new value=>lowest byte, old value=>next-to-lowest +// byte, id value=>next-to-next-to-lowest byte., and prefail flag x as +// bottom bit of highest byte. See below (lsb on right) -void ataCompareSmartValues (atadevices_t *device, struct ata_smart_values new ){ - int i; - int oldval,newval,idold,idnew; - - for ( i =0; i < NUMBER_ATA_SMART_ATTRIBUTES; i++){ - // which device is it? - idnew=new.vendor_attributes[i].id; - idold=device->smartval.vendor_attributes[i].id; - - if (idold && idnew){ - // if it's a valid attribute, compare values - newval=new.vendor_attributes[i].current; - oldval=device->smartval.vendor_attributes[i].current; - if (oldval!=newval){ - // values have changed; print them - char *loc,attributename[64]; - loc=attributename; - ataPrintSmartAttribName(attributename,idnew); - // skip blank space in name - while (*loc && *loc==' ') - loc++; - printout(LOG_INFO, "Device: %s, SMART Attribute: %s changed from %i to %i\n", - device->devicename,loc,oldval,newval); - } - } - } +// [00000000x][attribute ID][old value][new value] +int ataCompareSmartValues2(struct ata_smart_values *new, + struct ata_smart_values *old, + struct ata_smart_thresholds *thresholds, + int n){ + struct ata_smart_attribute *now,*was; + struct ata_smart_threshold_entry *thre; + unsigned char oldval,newval; + int returnvalue; + + // check that attribute number in range, and no null pointers + if (n<0 || n>=NUMBER_ATA_SMART_ATTRIBUTES || !new || !old || !thre) + return 0; + + // pointers to disk's values and vendor's thresholds + now=new->vendor_attributes+n; + was=old->vendor_attributes+n; + thre=thresholds->thres_entries+n; + + // consider only valid attributes, with same valid ID in all structures + if (!now->id || !was->id || !thre->id || (now->id != was->id) || (now->id != thre->id)) + return 0; + + // if values have not changed, return + newval=now->current; + oldval=was->current; + + // if any values out of the allowed range, or the values haven't changed, return + if (!newval || !oldval || newval>0xfe || oldval>0xfe || oldval==newval) + return 0; + + // values have changed. Construct output + returnvalue=0; + returnvalue |= newval; + returnvalue |= oldval<<8; + returnvalue |= now->id<<16; + returnvalue |= (now->status.flag.prefailure)<<24; + + return returnvalue; } +// This looks to see if the corresponding bit of the 32 bytes is set. +// This wastes a few bytes of storage but eliminates all searching and +// sorting functions! Entry is ZERO <==> the attribute ON. Calling +// with set=0 tells you if the attribute is being tracked or not. +// Calling with set=1 turns the attribute OFF. +int isattoff(unsigned char attr,unsigned char *data, int set){ + // locate correct attribute + int loc=attr>>3; + int bit=attr & 0x07; + unsigned char mask=0x01<<bit; -int ataCheckDevice( atadevices_t *drive){ - struct ata_smart_values tempsmartval; - struct ata_smart_thresholds tempsmartthres; - int failed,fd; - char *loc,attributename[64]; + // attribute zero is always OFF + if (!attr) + return 1; + if (!set) + return (data[loc] & mask); + + data[loc]|=mask; + // return value when setting makes no sense! + return 0; +} + + +int ataCheckDevice(atadevices_t *drive){ + int fd,i; + char *name=drive->devicename; + cfgfile *cfg=drive->cfg; + // if we can't open device, fail gracefully rather than hard -- // perhaps the next time around we'll be able to open it - if ((fd=opendevice(drive->devicename))<0) + if ((fd=opendevice(name))<0) return 1; - // Coming into this function, *drive contains the last values measured, - // and we read the NEW values into tempsmartval - if (ataReadSmartValues(fd,&tempsmartval)) - printout(LOG_INFO, "%s:Failed to read SMART values\n", drive->devicename); - - // and we read the new thresholds into tempsmartthres - if (ataReadSmartThresholds(fd, &tempsmartthres)) - printout(LOG_INFO, "%s:Failed to read SMART thresholds\n",drive->devicename); - - // See if any vendor attributes are below minimum, and print them - // out. WHEN IT WORKS, we should here add a call to - // ataSmartStatus2() either in addition to or instead of the - // ataCheckSmart command below. This is the "right" long-term - // solution. - if ((failed=ataCheckSmart(tempsmartval,tempsmartthres,1))){ - ataPrintSmartAttribName(attributename,failed); - // skip blank space in name - loc=attributename; - while (*loc && *loc==' ') - loc++; - printout(LOG_CRIT,"Device: %s, Failed SMART attribute: %s. Use smartctl -a %s.\n", - drive->devicename,loc,drive->devicename); - } - - // see if any values have changed. Second argument is new values - ataCompareSmartValues(drive, tempsmartval); - - // Save the new values into *drive for the next time around - drive->smartval = tempsmartval; - drive->smartthres = tempsmartthres; - - closedevice(fd); + // check smart status + if (cfg->smartcheck){ + int status=ataSmartStatus2(fd); + if (status==-1) + printout(LOG_INFO,"Device: %s, not capable of SMART self-check\n",name); + else if (status==1) + printout(LOG_INFO,"Device: %s, FAILED SMART self-check. BACK UP DATA NOW!\n",name); + } + + // Check everything that depends upon SMART Data (eg, Attribute values) + if (cfg->usagefailed || cfg->prefail || cfg->usage){ + struct ata_smart_values curval; + struct ata_smart_thresholds *thresh=drive->smartthres; + + // Read current attribute values. *drive contains old values adn thresholds + if (ataReadSmartValues(fd,&curval)) + printout(LOG_INFO, "Device: %s, failed to read SMART Attribute Data\n", name); + else { + // look for failed usage attributes, or track usage or prefail attributes + for (i=0; i<NUMBER_ATA_SMART_ATTRIBUTES; i++) { + int att; + + // This block looks for usage attributes that have failed. + // Prefail attributes that have failed are returned with a + // positive sign. No failure returns 0. Usage attributes<0. + if (cfg->usagefailed && ((att=ataCheckAttribute(&curval, thresh, i))<0)){ + + // are we tracking this attribute? + att *= -1; + if (!isattoff(att, cfg->failatt, 0)){ + char attname[64], *loc=attname; + + // get attribute name & skip white space + ataPrintSmartAttribName(loc, att); + while (*loc && *loc==' ') loc++; + + // warning message + printout(LOG_CRIT,"Device: %s, Failed SMART usage attribute: %s. Use smartctl -v %s.\n", name, loc, name); + } + } + + // This block tracks usage or prefailure attributes to see if they are changing + if ((cfg->usage || cfg->prefail) && ((att=ataCompareSmartValues2(&curval, drive->smartval, thresh, i)))){ + const int mask=0xff; + int prefail=(att>>24) & mask; + int id =(att>>16) & mask; + int oldval =(att>>8) & mask; + int newval =(att>>0) & mask; + char attname[64],*loc=attname; + + // are we tracking this attribute? + if (!isattoff(id, cfg->trackatt, 0)){ + + // get attribute name, skip spaces + ataPrintSmartAttribName(loc, id); + while (*loc && *loc==' ') loc++; + + // prefailure attribute + if (cfg->prefail && prefail) + printout(LOG_INFO, "Device: %s, SMART Prefailure Attribute: %s changed from %i to %i\n", + name, loc, oldval, newval); + + // usage attribute + if (cfg->usage && !prefail) + printout(LOG_INFO, "Device: %s, SMART Usage Attribute: %s changed from %i to %i\n", + name, loc, oldval, newval); + } + } // endof block tracking usage or prefailure + } // end of loop over attributes + + // Save the new values into *drive for the next time around + memcpy(drive->smartval,&curval,sizeof(curval)); + } + } + + // check if number of selftest errors has increased (note: may also DECREASE) + if (cfg->selftest){ + char old=cfg->selflogcount; + char new=selftesterrorcount(fd, name); + if (new>old){ + printout(LOG_CRIT,"Device: %s, Self-Test Log error count increased from %d to %d\n", + name,old,new); + } + if (new>=0) + // Needed suince self-test error count may DECREASE + cfg->selflogcount=new; + } + + + // check if number of ATA errors has increased + if (cfg->errorlog){ + int old=cfg->ataerrorcount; + int new=ataerrorcount(fd, name); + if (new>old){ + printout(LOG_CRIT,"Device: %s, ATA error count increased from %d to %d\n", + name,old,new); + } + // this last line is probably not needed, count always increases + if (new>=0) + cfg->ataerrorcount=new; + } + closedevice(fd, name); return 0; } @@ -359,7 +600,7 @@ int scsiCheckDevice( scsidevices_t *drive){ currenttemp = triptemp = 0; if (scsiCheckSmart(fd, drive->SmartPageSupported, &returnvalue, ¤ttemp, &triptemp)) - printout(LOG_INFO, "%s:Failed to read SMART values\n", drive->devicename); + printout(LOG_INFO, "Device: %s, failed to read SMART values\n", drive->devicename); if (returnvalue) printout(LOG_CRIT, "Device: %s, SMART Failure: (%02x) %s\n", drive->devicename, @@ -375,11 +616,11 @@ int scsiCheckDevice( scsidevices_t *drive){ drive->devicename, (int) (currenttemp - drive->Temperature), (unsigned int) currenttemp ); drive->Temperature = currenttemp; } - closedevice(fd); + closedevice(fd, drive->devicename); return 0; } -void CheckDevices ( atadevices_t *atadevices, scsidevices_t *scsidevices){ +void CheckDevices(atadevices_t *atadevices, scsidevices_t *scsidevices){ int i; // If there are no devices to monitor, then exit @@ -410,12 +651,197 @@ char copyleftstring[]= cfgfile config[MAXENTRIES]; -// returns number of entries in config file, or 0 if no config file exists +int parsetoken(char *token,cfgfile *cfg){ + char sym=token[1]; + char *name=cfg->name; + int lineno=cfg->lineno; + char *delim=" \n\t"; + + // is the rest of the line a comment + if (*token=='#') + return 1; + + // is the token not recognized? + if (*token!='-' || strlen(token)!=2) { + printout(LOG_CRIT,"Drive: %s, unknown Directive: %s at line %d of file %s\n", + name,token,lineno,CONFIGFILE); + Directives(); + exit(1); + } + + // let's parse the token and swallow its argument + switch (sym) { + char *arg; + char *endptr; + int val; + + case 'A': + // ATA device + cfg->tryata=1; + cfg->tryscsi=0; + break; + case 'S': + //SCSI device + cfg->tryscsi=1; + cfg->tryata=0; + break; + case 'c': + // check SMART status + cfg->smartcheck=1; + break; + case 'f': + // check for failure of usage attributes + cfg->usagefailed=1; + break; + case 't': + // track changes in all vendor attributes + cfg->prefail=1; + cfg->usage=1; + break; + case 'p': + // track changes in prefail vendor attributes + cfg->prefail=1; + break; + case 'u': + // track changes in usage vendor attributes + cfg->usage=1; + break; + case 'L': + // track changes in self-test log + cfg->selftest=1; + break; + case 'l': + // track changes ATA error log + cfg->errorlog=1; + break; + case 'a': + // monitor everything + cfg->smartcheck=1; + cfg->prefail=1; + cfg->usagefailed=1; + cfg->usage=1; + cfg->selftest=1; + cfg->errorlog=1; + break; + case 'i': + case 'I': + // ignore a particular vendor attribute for tracking (i) or + // failure (I) + arg=strtok(NULL,delim); + // make sure argument is there + if (!arg) { + printout(LOG_CRIT,"Drive %s Directive: %s at line %d of file %s needs integer argument.\n", + name,token,lineno,CONFIGFILE); + Directives(); + exit(1); + } + // get argument value, check that it's properly-formed, an + // integer, and in-range + val=strtol(arg,&endptr,10); + if (*endptr!='\0' || val<=0 || val>255) { + printout(LOG_CRIT,"Drive %s Directive: %s at line %d of file %s has argument: %s, needs 0 < n < 256\n", + name,token,lineno,CONFIGFILE,arg); + Directives(); + exit(1); + } + // put into correct list (bitmaps, access only with isattoff() + // function. Turns OFF corresponding attribute. + if (sym=='I') + isattoff(val,cfg->trackatt,1); + else + isattoff(val,cfg->failatt,1); + break; + default: + printout(LOG_CRIT,"Drive: %s, unknown option: %s at line %d of file %s\n", + name,token,lineno,CONFIGFILE); + Directives(); + exit(1); + } + return 1; +} + + +int parseconfigline(int entry, int lineno,char *line){ + char *token,*copy; + char *delim=" \n\t"; + char *name; + int len; + cfgfile *cfg; + + if (!(copy=strdup(line))){ + perror("no memory available to parse line"); + exit(1); + } + + // get first token -- device name + if (!(name=strtok(copy,delim)) || *name=='#'){ + free(copy); + return 0; + } + + // Is there space for another entry? + if (entry>=MAXENTRIES){ + printout(LOG_CRIT,"Error: configuration file %s can have no more than %d entries\n", + CONFIGFILE,MAXENTRIES); + exit(1); + } + + // We've got a legit entry, clear structure + cfg=config+entry; + memset(cfg,0,sizeof(*config)); + + // Save info to process memory for after forking 32 bytes contains 1 + // bit per possible attribute ID. See isattoff() + cfg->name=strdup(name); + cfg->failatt=(unsigned char *)calloc(32,1); + cfg->trackatt=(unsigned char *)calloc(32,1); + + if (!cfg->name || !cfg->failatt || !cfg->trackatt) { + perror("no memory available to save name"); + exit(1); + } + + cfg->lineno=lineno; + cfg->tryscsi=cfg->tryata=1; + + // Try and recognize if a IDE or SCSI device. These can be + // overwritten by configuration file directives. + len=strlen(name); + if (len>5 && !strncmp("/dev/h",name, 6)) + cfg->tryscsi=0; + + if (len>5 && !strncmp("/dev/s",name, 6)) + cfg->tryata=0; + + // parse tokens one at a time from the file + while ((token=strtok(NULL,delim)) && parsetoken(token,cfg)){ +#if 0 + printout(LOG_INFO,"Parsed token %s\n",token); +#endif + } + + // basic sanity check -- are any options turned on? + if (!(cfg->smartcheck || cfg->usagefailed || cfg->prefail || cfg->usage || cfg->selftest || cfg->errorlog || cfg->tryscsi)){ + printout(LOG_CRIT,"Drive: %s, no monitoring Directives on line %d of file %s\n", + cfg->name, cfg->lineno, CONFIGFILE); + Directives(); + exit(1); + } + + entry++; + free(copy); + return 1; +} + +// returns number of entries in config file, or 0 if no config file +// exists. A config file with zero entries will cause an error +// message and an exit. int parseconfigfile(){ FILE *fp; - int entry=0,lineno=0; + int entry=0,lineno=1,cont=0,contlineno=0; char line[MAXLINELEN+2]; - + char fullline[MAXCONTLINE+1]; + // Open config file, if it exists fp=fopen(CONFIGFILE,"r"); if (fp==NULL && errno!=ENOENT){ @@ -432,14 +858,34 @@ int parseconfigfile(){ if (fp==NULL) return 0; - // configuration file exists. Read it and search for devices + // configuration file exists printout(LOG_INFO,"Using configuration file %s\n",CONFIGFILE); - while (fgets(line,MAXLINELEN+2,fp)){ - int len; - char *dev; + + // parse config file line by line + while (1) { + int len=0; + char *lastslash; + char *comment; + char *code; + + // make debugging simpler + memset(line,0,sizeof(line)); + + // get a line + code=fgets(line,MAXLINELEN+2,fp); - // track linenumber for error messages - lineno++; + // are we at the end of the file? + if (!code){ + if (cont) { + // the final line is part of a continuation line + cont=0; + entry+=parseconfigline(entry,lineno,fullline); + } + break; + } + + // input file line number + contlineno++; // See if line is too long len=strlen(line); @@ -449,77 +895,44 @@ int parseconfigfile(){ warn="(including newline!) "; else warn=""; - printout(LOG_CRIT,"Error: line %d of file %s %sis more than than %d characters long.\n", - lineno,CONFIGFILE,warn,MAXLINELEN); + printout(LOG_CRIT,"Error: line %d of file %s %sis more than %d characters.\n", + contlineno,CONFIGFILE,warn,MAXLINELEN); exit(1); } - - // eliminate any terminating newline - if (line[len-1]=='\n'){ - len--; - line[len]='\0'; + + // Ignore anything after comment symbol + if ((comment=index(line,'#'))){ + *comment='\0'; + len=strlen(line); } - - // Skip white space - dev=line; - while (*dev && (*dev==' ' || *dev=='\t')) - dev++; - len=strlen(dev); - - // If line is blank, or a comment, skip it - if (!len || *dev=='#') - continue; - -#if 0 - // This is the start of some code to handle continuation - // characters in the /etc/smartd.conf file. Note that these must - // be the final character on a line, with just a newline after - // them. No other white space afterwards. - if (continuation+len>maxlinelen) - error; + // is the total line (made of all continuation lines) too long? + if (cont+len>MAXCONTLINE){ + printout(LOG_CRIT,"Error: continued line %d (actual line %d) of file %s is more than %d characters.\n", + lineno,contlineno,CONFIGFILE,MAXCONTLINE); + exit(1); + } - curr=config[entry].name+continuation; - strcpy(curr,dev); - + // copy string so far into fullline, and increment length + strcpy(fullline+cont,line); + cont+=len; - if (cur[len-1]=='\ '){ - cur[len-1]=' '; - continuation+=len; + // is this a continuation line. If so, replace \ by space and look at next line + if ( (lastslash=rindex(line,'\\')) && !strtok(lastslash+1," \n\t")){ + *(fullline+(cont-len)+(lastslash-line))=' '; continue; } - else { - continuation=0; - lineno++; - } -#endif - // We've got a legit entry - if (entry>=MAXENTRIES){ - printout(LOG_CRIT,"Error: configuration file %s can have no more than %d entries\n", - CONFIGFILE,MAXENTRIES); - exit(1); - } - - // Copy information into data structure for after forking - strcpy(config[entry].name,dev); - config[entry].lineno=lineno; - config[entry].tryscsi=config[entry].tryata=1; - - // Try and recognize if a IDE or SCSI device - if (len>5 && !strncmp("/dev/h",dev, 6)) - config[entry].tryscsi=0; - - if (len>5 && !strncmp("/dev/s",dev, 6)) - config[entry].tryata=0; - - entry++; + // Not a continuation line. Parse it + entry+=parseconfigline(entry,lineno,fullline); + lineno++; + cont=0; } fclose(fp); if (entry) return entry; - printout(LOG_CRIT,"Configuration file %s contained no devices (like /dev/hda)\n",CONFIGFILE); + printout(LOG_CRIT,"Configuration file %s contains no devices (like /dev/hda)\n",CONFIGFILE); exit(1); } @@ -571,12 +984,12 @@ void ParseOpts(int argc, char **argv){ printhead(); printout(LOG_INFO,copyleftstring); printout(LOG_INFO,"CVS version IDs of files used to build this code are:\n"); - printone(out,CVSid3); - printout(LOG_INFO,"%s",out); printone(out,CVSid1); printout(LOG_INFO,"%s",out); printone(out,CVSid2); printout(LOG_INFO,"%s",out); + printone(out,CVSid6); + printout(LOG_INFO,"%s",out); exit(0); } @@ -585,17 +998,85 @@ void ParseOpts(int argc, char **argv){ return; } +// Function we call if no configuration file was found. It makes +// entries for /dev/hd[a-l] and /dev/sd[a-z]. +int makeconfigentries(int num, char *name, int isata, int start){ + int i; + + if (MAXENTRIES<(start+num)){ + printout(LOG_CRIT,"Error: simulated config file can have no more than %d entries\n",MAXENTRIES); + exit(1); + } + + for(i=0; i<num; i++){ + cfgfile *cfg=config+start+i; + + // clear all fields of structure + memset(cfg,0,sizeof(*cfg)); + + // select if it's a SCSI or ATA device + cfg->tryata=isata; + cfg->tryscsi=!isata; + + // enable all possible tests + cfg->smartcheck=1; + cfg->prefail=1; + cfg->usagefailed=1; + cfg->usage=1; + cfg->selftest=1; + cfg->errorlog=1; + + // lineno==0 is our clue that the device was not found in a + // config file! + cfg->lineno=0; + + // put in the device name + cfg->name=strdup(name); + cfg->failatt=(unsigned char *)calloc(32,1); + cfg->trackatt=(unsigned char *)calloc(32,1); + if (!cfg->name || !cfg->failatt || !cfg->trackatt) { + perror("no memory available to save name"); + exit(1); + } + // increment final character of the name + cfg->name[strlen(name)-1]+=i; + } + return i; +} + + +void cantregister(char *name, char *type, int line){ + if (line) + printout(LOG_INFO,"Unable to register %s device %s at line %d of file %s\n", + type, name, line, CONFIGFILE); + else + printout(LOG_INFO,"Unable to register %s device %s\n", + type, name); + return; +} + + /* Main Program */ int main (int argc, char **argv){ atadevices_t atadevices[MAXATADEVICES], *atadevicesptr=atadevices; scsidevices_t scsidevices[MAXSCSIDEVICES], *scsidevicesptr=scsidevices; int i,entries; + atamainctrl control; + + // initialize global communications variables + con=&control; + memset(con,0,sizeof(control)); + // initialize global counters numatadevices=numscsidevices=0; // Parse input and print header and usage info if needed ParseOpts(argc,argv); - + + // Do we mute printing from ataprint commands? + con->quietmode=0; + con->veryquietmode=debugmode?0:1; + // look in configuration file CONFIGFILE (normally /etc/smartd.conf) entries=parseconfigfile(); @@ -603,30 +1084,37 @@ int main (int argc, char **argv){ if (!debugmode){ daemon_init(); } + + // setup signal handler for shutdown + if (signal(SIGINT, sighandler)==SIG_IGN) + signal(SIGINT, SIG_IGN); + if (signal(SIGTERM, sighandler)==SIG_IGN) + signal(SIGTERM, SIG_IGN); + if (signal(SIGQUIT, sighandler)==SIG_IGN) + signal(SIGQUIT, SIG_IGN); - // If we found a config file, register its entries - if (entries) - for (i=0;i<entries;i++){ - // register ATA devices - if (config[i].tryata && atadevicescan(atadevicesptr, config[i].name)) - printout(LOG_INFO,"Unable to register ATA device %s at line %d of file %s\n", - config[i].name, config[i].lineno, CONFIGFILE); - // then register SCSI devices - if (config[i].tryscsi && scsidevicescan(scsidevicesptr, config[i].name)) - printout(LOG_INFO,"Unable to register SCSI device %s at line %d of file %s\n", - config[i].name, config[i].lineno, CONFIGFILE); - } - else { - // since there was no config file found, search all ATA and SCSI disks - char deviceata[] = "/dev/hda"; - char devicescsi[]= "/dev/sda"; - printout(LOG_INFO,"No configuration file %s found. Searching for devices.\n",CONFIGFILE); - for(i=0;i<MAXATADEVICES;i++,deviceata[7]++) - atadevicescan(atadevicesptr, deviceata); - for(i=0;i<MAXSCSIDEVICES;i++,devicescsi[7]++) - scsidevicescan(scsidevicesptr, devicescsi); + // install goobye message + atexit(goobye); + + // if there was no config file, create needed entries + if (!entries){ + printout(LOG_INFO,"smartctl: file %s not found. Searching for devices.\n",CONFIGFILE); + entries+=makeconfigentries(MAXATADEVICES,"/dev/hda",1,entries); + entries+=makeconfigentries(MAXSCSIDEVICES,"/dev/sda",0,entries); + } + + // Register entries + for (i=0;i<entries;i++){ + // register ATA devices + if (config[i].tryata && atadevicescan2(atadevicesptr+numatadevices, config+i)) + cantregister(config[i].name, "ATA", config[i].lineno); + + // then register SCSI devices + if (config[i].tryscsi && scsidevicescan(scsidevicesptr, config[i].name)) + cantregister(config[i].name, "SCSI", config[i].lineno); } + // Now start an infinite loop that checks all devices CheckDevices(atadevicesptr, scsidevicesptr); return 0; diff --git a/sm5/smartd.h b/sm5/smartd.h index bf0fdd3f6..2b1febf47 100644 --- a/sm5/smartd.h +++ b/sm5/smartd.h @@ -23,13 +23,15 @@ */ #ifndef CVSID7 -#define CVSID7 "$Id: smartd.h,v 1.11 2002/10/26 10:19:16 ballen4705 Exp $\n" +#define CVSID7 "$Id: smartd.h,v 1.12 2002/10/28 23:47:00 ballen4705 Exp $\n" #endif // Configuration file #define CONFIGFILE "/etc/smartd.conf" -#define MAXLINELEN 114 +#define MAXLINELEN 128 #define MAXENTRIES 64 +#define MAXCONTLINE 511 +#define MAXDEVLEN 51 // BAD PROGRAMMING - GLOBAL VARIABLES SHOULD BE IN .c NOT .h FILE @@ -58,24 +60,49 @@ unsigned char debugmode = FALSE; unsigned char emailnotification = FALSE; unsigned char printcopyleft = FALSE; -typedef struct atadevices_s { - int selftest; - struct hd_driveid drive; - struct ata_smart_values smartval; - struct ata_smart_thresholds smartthres; - char devicename[MAXLINELEN+2]; -} atadevices_t; typedef struct scsidevices_s { - unsigned char SmartPageSupported; - unsigned char TempPageSupported; - unsigned char Temperature; - char devicename[MAXLINELEN+2]; + unsigned char SmartPageSupported; + unsigned char TempPageSupported; + unsigned char Temperature; + char *devicename; } scsidevices_t; + typedef struct configfile_s { + // which line was entry in file; what device type and name? int lineno; - int tryata; - int tryscsi; - char name[MAXLINELEN+2]; // really only needs to be +1 + char tryata; + char tryscsi; + char *name; + + // which tests have been enabled? + char smartcheck; + char usagefailed; + char prefail; + char usage; + char selftest; + char errorlog; + + // store counts of ata and self-test errors + char selflogcount; + int ataerrorcount; + // following two items point to 32 bytes, in the form of are + // 32x8=256 single bit flags + + // valid attribute numbers are from 1 <= x <= 255 + // valid attribute values are from 1 <= x <= 254 + unsigned char *failatt; + unsigned char *trackatt; } cfgfile; + + +typedef struct atadevices_s { + struct ata_smart_values *smartval; + struct ata_smart_thresholds *smartthres; + cfgfile *cfg; + char *devicename; +} atadevices_t; + + +int ataCheckDevice(atadevices_t *drive); -- GitLab