Commit 69ec55db authored by ballen4705's avatar ballen4705
Browse files

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.


git-svn-id: https://smartmontools.svn.sourceforge.net/svnroot/smartmontools/trunk@120 4ea69e1a-61f1-4043-bf83-b5c94c648137
parent aca78d3b
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
......
......@@ -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 $
......@@ -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;
}
......@@ -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