diff --git a/sm5/CHANGELOG b/sm5/CHANGELOG index d8085b55d626f22bf3a65a2a1aeb0dfd1055557b..dcf7bf2e6c48d86de9d7d6b1968a24a07e6258f6 100644 --- a/sm5/CHANGELOG +++ b/sm5/CHANGELOG @@ -1,6 +1,6 @@ CHANGELOG for smartmontools -$Id: CHANGELOG,v 1.19 2002/10/24 11:38:11 ballen4705 Exp $ +$Id: CHANGELOG,v 1.20 2002/10/25 14:15:05 ballen4705 Exp $ Copyright (C) 2002 Bruce Allen <smartmontools-support@lists.sourceforge.net> @@ -28,8 +28,15 @@ NOTES FOR NEXT RELEASE: smartmontools-5.0-12 + smartd on startup now looks in the configuration file /etc/smartd.conf for + a list of devices which to include in its monitoring list. See man page + (man smartd) for syntax. + smartd: close file descriptors of SCSI device if not SMART capable + Closes ALL file descriptors after forking to daemon. + added new temperature attribute (231, temperature) + smartd: now open ATA disks using O_RDONLY smartmontools-5.0-11 diff --git a/sm5/smartd.8 b/sm5/smartd.8 index c8747ab21f8c1434e2439371e8bcf6615a24729f..8bdea6065e5f57659a0faf333ac2c1ab8743543b 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/24 10:56:10 $" "smartmontools-5.0" +.TH SMARTD 8 "$Date: 2002/10/25 14:15:05 $" "smartmontools-5.0" .SH NAME smartd \- S.M.A.R.T. Daemon .SH SYNOPSIS @@ -33,7 +33,11 @@ REFERENCES below) .B smartd will notify users of S.M.A.R.T. errors and changes of S.M.A.R.T. attributes via the SYSLOG interface. These notifications -and warnings normally appear in /var/log/messages. +and warnings normally appear in +.B /var/log/messages. +It can be configured at start-up +using the file +.B /etc/smartd.conf. .PP .SH SYNTAX @@ -44,7 +48,9 @@ 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 '\-'. -The +In the absense of the configuration file +.B /etc/smartd.conf +the .B smartd daemon scans for all devices that support S.M.A.R.T., using @@ -52,10 +58,11 @@ daemon scans for all devices that support S.M.A.R.T., using 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 -/var/log/messages, about missing block-major-xx devices. These -messages are harmless.] +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.] .P .SH OPTIONS @@ -103,6 +110,36 @@ and disabled using the command: .nf .B '/sbin/chkconfig --del smartd' +.SH CONFIGURATION FILE +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: + +.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 # first IDE disk on each of two interfaces +.B /dev/hda +.B /dev/hdc + +.B # SCSI disk +.B /dev/sda +.fi + .SH NOTES .B smartd will make log entries if SMART attribute values have changed, @@ -198,4 +235,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.10 2002/10/24 10:56:10 ballen4705 Exp $ +$Id: smartd.8,v 1.11 2002/10/25 14:15:05 ballen4705 Exp $ diff --git a/sm5/smartd.c b/sm5/smartd.c index f49c781823458c24025ae44a6026e336734677ca..5763c86c33c0331602ec4593390097d1f6d88913 100644 --- a/sm5/smartd.c +++ b/sm5/smartd.c @@ -20,6 +20,8 @@ * */ +#include <errno.h> +#include <stdlib.h> #include <stdio.h> #include <sys/ioctl.h> #include <sys/types.h> @@ -37,7 +39,7 @@ #include "ataprint.h" extern const char *CVSid1, *CVSid2; -const char *CVSid3="$Id: smartd.c,v 1.29 2002/10/24 17:15:25 ballen4705 Exp $" +const char *CVSid3="$Id: smartd.c,v 1.30 2002/10/25 14:15:05 ballen4705 Exp $" CVSID1 CVSID4 CVSID7; int daemon_init(void){ @@ -75,8 +77,8 @@ int daemon_init(void){ void printout(int priority,char *fmt, ...){ va_list ap; // initialize variable argument list - va_start(ap,fmt); - if (debugmode) + va_start(ap,fmt); + if (debugmode) vprintf(fmt,ap); else vsyslog(priority,fmt,ap); @@ -109,119 +111,130 @@ void printhead(){ /* prints help information for command syntax */ void Usage (void){ - printhead(); 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",PRINTCOPYLEFT); + printout(LOG_INFO," %c Print License, Copyright, and version information\n\n",PRINTCOPYLEFT); + printout(LOG_INFO,"Configuration file: /etc/smartd.conf\n"); } // scan to see what ata devices there are, and if they support SMART -void atadevicescan ( atadevices_t *devices){ - int i; +int atadevicescan (atadevices_t *devices, char *device){ int fd; struct hd_driveid drive; - char device[] = "/dev/hda"; - for(i=0;i<MAXATADEVICES;i++,device[7]++ ){ - - printout(LOG_INFO,"Reading Device %s\n", device); - - fd = open(device, O_RDONLY); - if (fd < 0) - // no such device - continue; - - if (ataReadHDIdentity (fd,&drive) || !ataSmartSupport(drive) || ataEnableSmart(fd)){ - // device exists, but not able to do SMART - close(fd); - printout(LOG_INFO,"Device: %s, Found but not SMART capable, or couldn't enable SMART\n",device); - continue; - } - - // 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); - continue; - } - else if (ataReadSmartThresholds (fd,&devices[numatadevices].smartthres)){ - close(fd); - printout(LOG_INFO,"Device: %s, Read SMART Thresholds Failed\n",device); - continue; - } - - // device exists, and does SMART. Add to list - printout(LOG_INFO,"%s Found and is SMART capable\n",device); - devices[numatadevices].fd = fd; - 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); - - numatadevices++; + printout(LOG_INFO,"Opening device %s\n", device); + 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); + else + printout(LOG_INFO,"Device: %s, Opening device failed\n",device); + return 1; + } + + if (ataReadHDIdentity (fd,&drive) || !ataSmartSupport(drive) || ataEnableSmart(fd)){ + // device exists, but not able to do SMART + close(fd); + printout(LOG_INFO,"Device: %s, Found but not SMART capable, or couldn't enable SMART\n",device); + 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; } + else if (ataReadSmartThresholds (fd,&devices[numatadevices].smartthres)){ + close(fd); + printout(LOG_INFO,"Device: %s, Read SMART Thresholds Failed\n",device); + return 4; + } + + // device exists, and does SMART. Add to list + printout(LOG_INFO,"%s Found and is SMART capable. Adding to \"monitor\" list.\n",device); + devices[numatadevices].fd = fd; + 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); + + numatadevices++; + 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?? -void scsidevicescan ( scsidevices_t *devices){ +int scsidevicescan (scsidevices_t *devices, char *device){ int i, fd, smartsupport; unsigned char tBuf[4096]; - char device[] = "/dev/sda"; - for(i = 0; i < MAXSCSIDEVICES ; i++,device[7]++ ){ - - printout(LOG_INFO,"Reading Device %s\n", device); - - fd=open (device, O_RDWR); - - if (fd<0) - continue; + + // open device + printout(LOG_INFO,"Opening device %s\n", device); + fd=open(device, O_RDWR); + if (fd<0) { + if (errno<sys_nerr) + printout(LOG_INFO,"%s: Device: %s, Opening device failed\n",sys_errlist[errno],device); + else + printout(LOG_INFO,"Device: %s, Opening device failed\n", device); + return 1; + } + + // check that it's ready for commands + if (!testunitready(fd)){ + printout(LOG_INFO,"Device: %s, Failed Test Unit Ready\n", device); + close(fd); + return 2; + } + + // make sure that we can read mode page + if (modesense(fd, 0x1c, (UINT8 *) &tBuf)){ + printout(LOG_INFO,"Device: %s, Failed read of ModePage 0x1c\n", device); + close(fd); + return 3; + } + + // see if SMART is supported and enabled + if (scsiSmartSupport(fd, (UINT8 *) &smartsupport) || + (smartsupport & DEXCPT_ENABLE)){ + printout(LOG_INFO,"Device: %s, SMART not supported or not enabled\n", device); + close(fd); + return 4; + } - if (!testunitready (fd)) { - if (modesense(fd, 0x1c, (UINT8 *) &tBuf)){ - printout(LOG_INFO,"Device: %s, Failed read of ModePage 1C \n", device); - close(fd); + // now we can proceed to register the device + printout(LOG_INFO, "Device: %s, Found and is SMART capable. Adding to \"monitor\" list.\n",device); + devices[numscsidevices].fd = fd; + strcpy(devices[numscsidevices].devicename,device); + + // register the supported functionality. The smartd code does not + // seem to make any further use of this information. + if (logsense(fd, SUPPORT_LOG_PAGES, (UINT8 *) &tBuf) == 0){ + for ( i = 4; i < tBuf[3] + LOGPAGEHDRSIZE ; i++){ + switch ( tBuf[i]){ + case TEMPERATURE_PAGE: + devices[numscsidevices].TempPageSupported = 1; + break; + case SMART_PAGE: + devices[numscsidevices].SmartPageSupported = 1; + break; + default: + break; } - else - if (!scsiSmartSupport( fd, (UINT8 *) &smartsupport) && - !(smartsupport & DEXCPT_ENABLE)){ - devices[numscsidevices].fd = fd; - strcpy(devices[numscsidevices].devicename,device); - - printout(LOG_INFO, "Device: %s, Found and is SMART capable\n",device); - - if (logsense ( fd , SUPPORT_LOG_PAGES, (UINT8 *) &tBuf) == 0){ - for ( i = 4; i < tBuf[3] + LOGPAGEHDRSIZE ; i++){ - switch ( tBuf[i]){ - case TEMPERATURE_PAGE: - devices[numscsidevices].TempPageSupported = 1; - break; - case SMART_PAGE: - devices[numscsidevices].SmartPageSupported = 1; - break; - default: - break; - } - } - } - numscsidevices++; - } - else - close(fd); - } + } } + numscsidevices++; + return 0; } @@ -295,8 +308,8 @@ int ataCheckDevice( atadevices_t *drive){ } -int scsiCheckDevice( scsidevices_t *drive) -{ + +int scsiCheckDevice( scsidevices_t *drive){ UINT8 returnvalue; UINT8 currenttemp; UINT8 triptemp; @@ -312,6 +325,8 @@ int scsiCheckDevice( scsidevices_t *drive) else printout(LOG_INFO,"Device: %s, Acceptable attribute: %d\n", drive->devicename, returnvalue); + // Seems to completely ignore what capabilities were found on the + // device when scanned if (currenttemp){ if ( (currenttemp != drive->Temperature) && ( drive->Temperature) ) printout(LOG_INFO, "Device: %s, Temperature changed %d degrees to %d degrees since last reading\n", @@ -414,23 +429,111 @@ char copyleftstring[]= "under the terms of the GNU General Public License Version 2.\n" "See http://www.gnu.org for further details.\n\n"; -const char opts[] = { DEBUGMODE, EMAILNOTIFICATION, PRINTCOPYLEFT,'\0' }; +cfgfile config[MAXENTRIES]; + + +// returns number of entries in config file, or 0 if no config file exists +int parseconfigfile(){ + FILE *fp; + int entry=0,lineno=0; + char line[MAXLINELEN+2]; + + // Open config file, if it exists + fp=fopen(CONFIGFILE,"r"); + if (fp==NULL && errno!=ENOENT){ + // file exists but we can't read it + if (errno<sys_nerr) + printout(LOG_INFO,"%s: Unable to open configuration file %s\n", + sys_errlist[errno],CONFIGFILE); + else + printout(LOG_INFO,"Unable to open configuration file %s\n",CONFIGFILE); + exit(1); + } + + // No config file + if (fp==NULL) + return 0; + + // configuration file exists. Read it and search for devices + printout(LOG_INFO,"Using configuration file %s\n",CONFIGFILE); + while (fgets(line,MAXLINELEN+2,fp)){ + int len; + char *dev; + + // track linenumber for error messages + lineno++; + + // See if line is too long + len=strlen(line); + if (len>MAXLINELEN){ + printout(LOG_INFO,"Error: line %d of file %s is more than than %d characters long.\n", + lineno,CONFIGFILE,MAXLINELEN); + exit(1); + } + + // eliminate any terminating newline + if (line[len-1]=='\n'){ + len--; + line[len]='\0'; + } + + // 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; + + // We've got a legit entry + if (entry>=MAXENTRIES){ + printout(LOG_INFO,"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++; + } + fclose(fp); + if (entry) + return entry; + + printout(LOG_INFO,"Configuration file %s contained no devices (like /dev/hda)\n",CONFIGFILE); + exit(1); +} + +const char opts[] = { DEBUGMODE, EMAILNOTIFICATION, PRINTCOPYLEFT,'h','?','\0' }; /* Main Program */ int main (int argc, char **argv){ - atadevices_t atadevices[MAXATADEVICES], *atadevicesptr; scsidevices_t scsidevices[MAXSCSIDEVICES], *scsidevicesptr; - int optchar; + int optchar,i; extern char *optarg; extern int optopt, optind, opterr; + int entries; numatadevices=0; numscsidevices=0; scsidevicesptr = scsidevices; atadevicesptr = atadevices; - opterr=1; + opterr=optopt=0; + // Parse input options: while (-1 != (optchar = getopt(argc, argv, opts))){ switch(optchar) { case PRINTCOPYLEFT: @@ -443,14 +546,22 @@ int main (int argc, char **argv){ emailnotification = TRUE; break; case '?': + case 'h': default: debugmode=1; - printout(LOG_INFO,"\n"); + if (optopt) { + printhead(); + printout(LOG_INFO,"=======> UNRECOGNIZED OPTION: %c <======= \n\n",optopt); + Usage(); + exit(-1); + } + printhead(); Usage(); - exit(-1); + exit(0); } } + // If needed print copyright, license and version information if (printcopyleft){ debugmode=1; printhead(); @@ -462,20 +573,39 @@ int main (int argc, char **argv){ exit(0); } + // print header printhead(); + // look in configuration file CONFIGFILE (normally /etc/smartd.conf) + entries=parseconfigfile(); - // If we are running in background as a daemon, call - // a routines that forks then closes file descriptors. + // If in background as a daemon, fork and close file descriptors if (!debugmode){ daemon_init(); } - - // routines that look for devices on ata and scsi bus - atadevicescan (atadevicesptr); - scsidevicescan (scsidevicesptr); - CheckDevices ( atadevicesptr, scsidevicesptr); + // If we found a config file, look at its entries + if (entries) + for (i=0;i<entries;i++){ + 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); + + 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 { + 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); + } + + CheckDevices(atadevicesptr, scsidevicesptr); return 0; } diff --git a/sm5/smartd.cpp b/sm5/smartd.cpp index 6689dfd42f6d3ca4d9ddd013eb32ee56fe601413..b0e1c3114cc08e128b390563af9e7511f64cc74a 100644 --- a/sm5/smartd.cpp +++ b/sm5/smartd.cpp @@ -20,6 +20,8 @@ * */ +#include <errno.h> +#include <stdlib.h> #include <stdio.h> #include <sys/ioctl.h> #include <sys/types.h> @@ -37,7 +39,7 @@ #include "ataprint.h" extern const char *CVSid1, *CVSid2; -const char *CVSid3="$Id: smartd.cpp,v 1.29 2002/10/24 17:15:25 ballen4705 Exp $" +const char *CVSid3="$Id: smartd.cpp,v 1.30 2002/10/25 14:15:05 ballen4705 Exp $" CVSID1 CVSID4 CVSID7; int daemon_init(void){ @@ -75,8 +77,8 @@ int daemon_init(void){ void printout(int priority,char *fmt, ...){ va_list ap; // initialize variable argument list - va_start(ap,fmt); - if (debugmode) + va_start(ap,fmt); + if (debugmode) vprintf(fmt,ap); else vsyslog(priority,fmt,ap); @@ -109,119 +111,130 @@ void printhead(){ /* prints help information for command syntax */ void Usage (void){ - printhead(); 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",PRINTCOPYLEFT); + printout(LOG_INFO," %c Print License, Copyright, and version information\n\n",PRINTCOPYLEFT); + printout(LOG_INFO,"Configuration file: /etc/smartd.conf\n"); } // scan to see what ata devices there are, and if they support SMART -void atadevicescan ( atadevices_t *devices){ - int i; +int atadevicescan (atadevices_t *devices, char *device){ int fd; struct hd_driveid drive; - char device[] = "/dev/hda"; - for(i=0;i<MAXATADEVICES;i++,device[7]++ ){ - - printout(LOG_INFO,"Reading Device %s\n", device); - - fd = open(device, O_RDONLY); - if (fd < 0) - // no such device - continue; - - if (ataReadHDIdentity (fd,&drive) || !ataSmartSupport(drive) || ataEnableSmart(fd)){ - // device exists, but not able to do SMART - close(fd); - printout(LOG_INFO,"Device: %s, Found but not SMART capable, or couldn't enable SMART\n",device); - continue; - } - - // 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); - continue; - } - else if (ataReadSmartThresholds (fd,&devices[numatadevices].smartthres)){ - close(fd); - printout(LOG_INFO,"Device: %s, Read SMART Thresholds Failed\n",device); - continue; - } - - // device exists, and does SMART. Add to list - printout(LOG_INFO,"%s Found and is SMART capable\n",device); - devices[numatadevices].fd = fd; - 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); - - numatadevices++; + printout(LOG_INFO,"Opening device %s\n", device); + 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); + else + printout(LOG_INFO,"Device: %s, Opening device failed\n",device); + return 1; + } + + if (ataReadHDIdentity (fd,&drive) || !ataSmartSupport(drive) || ataEnableSmart(fd)){ + // device exists, but not able to do SMART + close(fd); + printout(LOG_INFO,"Device: %s, Found but not SMART capable, or couldn't enable SMART\n",device); + 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; } + else if (ataReadSmartThresholds (fd,&devices[numatadevices].smartthres)){ + close(fd); + printout(LOG_INFO,"Device: %s, Read SMART Thresholds Failed\n",device); + return 4; + } + + // device exists, and does SMART. Add to list + printout(LOG_INFO,"%s Found and is SMART capable. Adding to \"monitor\" list.\n",device); + devices[numatadevices].fd = fd; + 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); + + numatadevices++; + 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?? -void scsidevicescan ( scsidevices_t *devices){ +int scsidevicescan (scsidevices_t *devices, char *device){ int i, fd, smartsupport; unsigned char tBuf[4096]; - char device[] = "/dev/sda"; - for(i = 0; i < MAXSCSIDEVICES ; i++,device[7]++ ){ - - printout(LOG_INFO,"Reading Device %s\n", device); - - fd=open (device, O_RDWR); - - if (fd<0) - continue; + + // open device + printout(LOG_INFO,"Opening device %s\n", device); + fd=open(device, O_RDWR); + if (fd<0) { + if (errno<sys_nerr) + printout(LOG_INFO,"%s: Device: %s, Opening device failed\n",sys_errlist[errno],device); + else + printout(LOG_INFO,"Device: %s, Opening device failed\n", device); + return 1; + } + + // check that it's ready for commands + if (!testunitready(fd)){ + printout(LOG_INFO,"Device: %s, Failed Test Unit Ready\n", device); + close(fd); + return 2; + } + + // make sure that we can read mode page + if (modesense(fd, 0x1c, (UINT8 *) &tBuf)){ + printout(LOG_INFO,"Device: %s, Failed read of ModePage 0x1c\n", device); + close(fd); + return 3; + } + + // see if SMART is supported and enabled + if (scsiSmartSupport(fd, (UINT8 *) &smartsupport) || + (smartsupport & DEXCPT_ENABLE)){ + printout(LOG_INFO,"Device: %s, SMART not supported or not enabled\n", device); + close(fd); + return 4; + } - if (!testunitready (fd)) { - if (modesense(fd, 0x1c, (UINT8 *) &tBuf)){ - printout(LOG_INFO,"Device: %s, Failed read of ModePage 1C \n", device); - close(fd); + // now we can proceed to register the device + printout(LOG_INFO, "Device: %s, Found and is SMART capable. Adding to \"monitor\" list.\n",device); + devices[numscsidevices].fd = fd; + strcpy(devices[numscsidevices].devicename,device); + + // register the supported functionality. The smartd code does not + // seem to make any further use of this information. + if (logsense(fd, SUPPORT_LOG_PAGES, (UINT8 *) &tBuf) == 0){ + for ( i = 4; i < tBuf[3] + LOGPAGEHDRSIZE ; i++){ + switch ( tBuf[i]){ + case TEMPERATURE_PAGE: + devices[numscsidevices].TempPageSupported = 1; + break; + case SMART_PAGE: + devices[numscsidevices].SmartPageSupported = 1; + break; + default: + break; } - else - if (!scsiSmartSupport( fd, (UINT8 *) &smartsupport) && - !(smartsupport & DEXCPT_ENABLE)){ - devices[numscsidevices].fd = fd; - strcpy(devices[numscsidevices].devicename,device); - - printout(LOG_INFO, "Device: %s, Found and is SMART capable\n",device); - - if (logsense ( fd , SUPPORT_LOG_PAGES, (UINT8 *) &tBuf) == 0){ - for ( i = 4; i < tBuf[3] + LOGPAGEHDRSIZE ; i++){ - switch ( tBuf[i]){ - case TEMPERATURE_PAGE: - devices[numscsidevices].TempPageSupported = 1; - break; - case SMART_PAGE: - devices[numscsidevices].SmartPageSupported = 1; - break; - default: - break; - } - } - } - numscsidevices++; - } - else - close(fd); - } + } } + numscsidevices++; + return 0; } @@ -295,8 +308,8 @@ int ataCheckDevice( atadevices_t *drive){ } -int scsiCheckDevice( scsidevices_t *drive) -{ + +int scsiCheckDevice( scsidevices_t *drive){ UINT8 returnvalue; UINT8 currenttemp; UINT8 triptemp; @@ -312,6 +325,8 @@ int scsiCheckDevice( scsidevices_t *drive) else printout(LOG_INFO,"Device: %s, Acceptable attribute: %d\n", drive->devicename, returnvalue); + // Seems to completely ignore what capabilities were found on the + // device when scanned if (currenttemp){ if ( (currenttemp != drive->Temperature) && ( drive->Temperature) ) printout(LOG_INFO, "Device: %s, Temperature changed %d degrees to %d degrees since last reading\n", @@ -414,23 +429,111 @@ char copyleftstring[]= "under the terms of the GNU General Public License Version 2.\n" "See http://www.gnu.org for further details.\n\n"; -const char opts[] = { DEBUGMODE, EMAILNOTIFICATION, PRINTCOPYLEFT,'\0' }; +cfgfile config[MAXENTRIES]; + + +// returns number of entries in config file, or 0 if no config file exists +int parseconfigfile(){ + FILE *fp; + int entry=0,lineno=0; + char line[MAXLINELEN+2]; + + // Open config file, if it exists + fp=fopen(CONFIGFILE,"r"); + if (fp==NULL && errno!=ENOENT){ + // file exists but we can't read it + if (errno<sys_nerr) + printout(LOG_INFO,"%s: Unable to open configuration file %s\n", + sys_errlist[errno],CONFIGFILE); + else + printout(LOG_INFO,"Unable to open configuration file %s\n",CONFIGFILE); + exit(1); + } + + // No config file + if (fp==NULL) + return 0; + + // configuration file exists. Read it and search for devices + printout(LOG_INFO,"Using configuration file %s\n",CONFIGFILE); + while (fgets(line,MAXLINELEN+2,fp)){ + int len; + char *dev; + + // track linenumber for error messages + lineno++; + + // See if line is too long + len=strlen(line); + if (len>MAXLINELEN){ + printout(LOG_INFO,"Error: line %d of file %s is more than than %d characters long.\n", + lineno,CONFIGFILE,MAXLINELEN); + exit(1); + } + + // eliminate any terminating newline + if (line[len-1]=='\n'){ + len--; + line[len]='\0'; + } + + // 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; + + // We've got a legit entry + if (entry>=MAXENTRIES){ + printout(LOG_INFO,"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++; + } + fclose(fp); + if (entry) + return entry; + + printout(LOG_INFO,"Configuration file %s contained no devices (like /dev/hda)\n",CONFIGFILE); + exit(1); +} + +const char opts[] = { DEBUGMODE, EMAILNOTIFICATION, PRINTCOPYLEFT,'h','?','\0' }; /* Main Program */ int main (int argc, char **argv){ - atadevices_t atadevices[MAXATADEVICES], *atadevicesptr; scsidevices_t scsidevices[MAXSCSIDEVICES], *scsidevicesptr; - int optchar; + int optchar,i; extern char *optarg; extern int optopt, optind, opterr; + int entries; numatadevices=0; numscsidevices=0; scsidevicesptr = scsidevices; atadevicesptr = atadevices; - opterr=1; + opterr=optopt=0; + // Parse input options: while (-1 != (optchar = getopt(argc, argv, opts))){ switch(optchar) { case PRINTCOPYLEFT: @@ -443,14 +546,22 @@ int main (int argc, char **argv){ emailnotification = TRUE; break; case '?': + case 'h': default: debugmode=1; - printout(LOG_INFO,"\n"); + if (optopt) { + printhead(); + printout(LOG_INFO,"=======> UNRECOGNIZED OPTION: %c <======= \n\n",optopt); + Usage(); + exit(-1); + } + printhead(); Usage(); - exit(-1); + exit(0); } } + // If needed print copyright, license and version information if (printcopyleft){ debugmode=1; printhead(); @@ -462,20 +573,39 @@ int main (int argc, char **argv){ exit(0); } + // print header printhead(); + // look in configuration file CONFIGFILE (normally /etc/smartd.conf) + entries=parseconfigfile(); - // If we are running in background as a daemon, call - // a routines that forks then closes file descriptors. + // If in background as a daemon, fork and close file descriptors if (!debugmode){ daemon_init(); } - - // routines that look for devices on ata and scsi bus - atadevicescan (atadevicesptr); - scsidevicescan (scsidevicesptr); - CheckDevices ( atadevicesptr, scsidevicesptr); + // If we found a config file, look at its entries + if (entries) + for (i=0;i<entries;i++){ + 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); + + 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 { + 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); + } + + CheckDevices(atadevicesptr, scsidevicesptr); return 0; } diff --git a/sm5/smartd.h b/sm5/smartd.h index 0e6c13577e6b31a7631506986d42ac4b0b7a5ebc..43d9a1b0e95b758720b9ee042f8c1fda2a8d6793 100644 --- a/sm5/smartd.h +++ b/sm5/smartd.h @@ -23,9 +23,14 @@ */ #ifndef CVSID7 -#define CVSID7 "$Id: smartd.h,v 1.7 2002/10/24 07:50:45 ballen4705 Exp $\n" +#define CVSID7 "$Id: smartd.h,v 1.8 2002/10/25 14:15:05 ballen4705 Exp $\n" #endif +// Configuration file +#define CONFIGFILE "/etc/smartd.conf" +#define MAXLINELEN 126 +#define MAXENTRIES 64 + /* Defines for command line options */ #define DEBUGMODE 'X' #define EMAILNOTIFICATION 'e' @@ -67,3 +72,10 @@ typedef struct scsidevices_s { unsigned char TempPageSupported; unsigned char Temperature; } scsidevices_t; + +typedef struct configfile_s { + char name[MAXLINELEN+2]; + int tryata; + int tryscsi; + int lineno; +} cfgfile;