/*
 * smartd.c
 *
 * Copyright (C) 2000 Michael Cornwell <cornwell@acm.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * You should have received a copy of the GNU General Public License
 * (for example COPYING); if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <linux/hdreg.h>
#include <syslog.h>

#include "atacmds.h"
#include "scsicmds.h"
#include "smartd.h"


int daemon_init(void)
{
   pid_t pid;

   if ( (pid = fork()) < 0)
      return -1;
   else if (pid != 0)
      exit (0);
   setsid ();
   chdir("/");
   umask(0);

   return(0);
}


/*  void Usage (void) 
	prints help information for command syntax */

void Usage ( void)
{
   printf( "smartd version %i.%i - S.M.A.R.T. Daemon\n", 
           VERSION_MAJOR, VERSION_MINOR);
   printf( "useage: smartd -[opts] \n");
   printf( "Read Only Commands:\n");
   printf( "\t\t%c\t\tStart smartd in debug Mode\n",DEBUGMODE);
}
	
/*  void ParseOpts ( chars *opts) 
	Takes command options and sets features to be run */	
void ParseOpts ( char *opts)
{
   int i = 0;
   char options[255];
	
   strcpy ( (char *) &options, opts);

   if ( options[0] != '-' )
   {
      Usage();
      exit (-1);
   }

   for (i = 1; options[i] != '\0'; i++ )
   {
      switch (options[i])
      {
         case DEBUGMODE :
            debugmode  = TRUE;
            break;
         case EMAILNOTIFICATION:
            emailnotification = TRUE;
            break;
         default:
            Usage();
            exit (-1);	
      }
   }
}

void failurenotify ( char *message)
{

}


void atadevicescan ( atadevices_t *devices)
{

   int i;
   int fd;
   struct hd_driveid drive;
   char device[] = "/dev/hda";
	
   for(i = 0; i < MAXATADEVICES ; i++,device[7]++ )
   {
      fd = 0;

      if (debugmode)
         printf("Reading Device %s\n", device);
	
      fd = open ( device , O_RDWR );

      if ( fd >= 0)
      {
         if (ataReadHDIdentity ( fd, &drive) == 0 ) 
         {
             if (ataSmartSupport (drive) ) 
             {
                if (ataEnableSmart)
                {
                    devices[numatadevices].fd = fd;
                    strcpy((char *) &devices[numatadevices].devicename,
                           (char*) &device);
                    devices[numatadevices].drive = drive;
                    if ( ataReadSmartValues ( fd, 
                         &devices[numatadevices].smartval) != 0 )
                    {
                        syslog (LOG_INFO, 
                                "Device: %s, Read Smart Values Failed\n", 
                                device);
                    }

                    if ( ataReadSmartThresholds ( fd, 
                         &devices[numatadevices].smartthres) != 0 )
                    {
                        syslog (LOG_INFO, 
                                "Device: %s, Read Smart Thresholds Failed\n", 
                                device);
                    }

                    if (debugmode)
                        printf("%s Found and is SMART capable\n", 
                               devices[numatadevices].devicename);

                    syslog (LOG_INFO, 
                            "Device: %s, Found and is SMART capable\n", 
                            device);
                    devices[numatadevices].selftest = 
                        isSupportSelfTest(devices[numatadevices].smartval);
                    numatadevices++;
               }      
            }
         }
      }
   }
}


void scsidevicescan ( scsidevices_t *devices)
{
   int i, fd, smartsupport;
   unsigned char  tBuf[4096];
   char device[] = "/dev/sda";
	

   for(i = 0; i < MAXSCSIDEVICES ; i++,device[7]++ )
   {
      fd = 0;

      if (debugmode)
         printf("Reading Device %s\n", device);

      fd = open ( device , O_RDWR );

      if(debugmode)
         printf ("Device: %s, File Descriptor: %d \n", device, fd);

      if ( fd >= 0)
      {
         if (  testunitready (fd) == 0 ) 
         {
            if (modesense ( fd, 0x1c, (UINT8 *) &tBuf) != 0)
            {
               if(debugmode)
                   printf ("Device: %s, Failed read of ModePage 1C \n", device);
            }
            else
            {
            if ( scsiSmartSupport( fd, (UINT8 *) &smartsupport) == 0)
            {			
               if (!(smartsupport & DEXCPT_ENABLE))
               {
                  devices[numscsidevices].fd = fd;
                  strcpy((char *) &devices[numscsidevices].devicename,
                         (char*) &device);
							
                  if (debugmode)
                    printf("%s Found and is SMART capable\n", 
                            devices[numscsidevices].devicename);
                 
                  syslog (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++;
             }
         }
      }
   }
   }
}
}



void ataCompareSmartValues ( atadevices_t *device, struct ata_smart_values b )
{
    int i;

    for ( i =0; i < NUMBER_ATA_SMART_ATTRIBUTES; i++)
    {
       if( device->smartval.vendor_attributes[i].id & b.vendor_attributes[i].id 
           & (device->smartval.vendor_attributes[i].current 
           != b.vendor_attributes[i].current))
       { 
          syslog (LOG_INFO, "Device: %s, S.M.A.R.T. Attribute: %i Changed %i\n",
                  device->devicename, 
                  device->smartval.vendor_attributes[i].id, 
                  (device->smartval.vendor_attributes[i].current 
                   - b.vendor_attributes[i].current));

       } 
	}  
}



int ataCheckDevice( atadevices_t *drive)
{
	struct ata_smart_values tempsmartval;
	struct ata_smart_thresholds tempsmartthres;

	if ( ataReadSmartValues ( drive->fd, &tempsmartval) != 0 )
	{
       syslog (LOG_INFO, "%s:Failed to read smart values\n", drive->devicename);
	}

	if ( ataReadSmartThresholds ( drive->fd, &tempsmartthres) != 0 )
	{
       syslog (LOG_INFO, "%s:Failed to read smart thresholds\n", 
					    drive->devicename);
	}
			
    if (ataCheckSmart( tempsmartval, tempsmartthres))
	{
		if(debugmode)
			printf ("Device: %s, Failed attribute: %i\n", drive->devicename, 
                     ataCheckSmart( tempsmartval, tempsmartthres));
    	else
			syslog (LOG_CRIT, "Device: %s, Failed attribute: %i\n", drive->devicename, 
						ataCheckSmart( tempsmartval, tempsmartthres));
	}
	
			
	if(debugmode)
		printf ("Device: %s, S.M.A.R.T. Failure attribute: %i\n", drive->devicename, 
				ataCheckSmart( tempsmartval, tempsmartthres));
				ataCompareSmartValues (drive , tempsmartval);
			
	/* Copy the values back to the structure */
	drive->smartval = tempsmartval;
	drive->smartthres = tempsmartthres;
			
	return 0;
}


int scsiCheckDevice( scsidevices_t *drive)
{
	UINT8 returnvalue;
	UINT8 currenttemp;
	UINT8 triptemp;

	currenttemp = triptemp = 0;

	if ( scsiCheckSmart( drive->fd, drive->SmartPageSupported, &returnvalue, &currenttemp, &triptemp ) != 0)
	{
       syslog (LOG_INFO, "%s:Failed to read smart values\n", drive->devicename);
	}
	
	if ( returnvalue )
	{
		if(debugmode)
			printf ("Device: %s, S.M.A.R.T. Failure: (%02x) %s\n", drive->devicename, 
					returnvalue, scsiSmartGetSenseCode( returnvalue) );
		else
			syslog (LOG_CRIT, "Device: %s, S.M.A.R.T. Failure: (%02x) %s\n", drive->devicename, 
					returnvalue, scsiSmartGetSenseCode( returnvalue) );
	}				
	else
	{
		if(debugmode)
			printf ("Device: %s, Okay attribute: %d\n", drive->devicename, returnvalue);  
 	}
	if ( currenttemp )
	{
		if ( (currenttemp != drive->Temperature) && ( drive->Temperature) )
			syslog (LOG_INFO, "Device: %s, Temperature changed %d degrees to %d degress since last reading\n", 
					drive->devicename, (int) (currenttemp - drive->Temperature), (unsigned int) currenttemp );

		drive->Temperature = currenttemp;
	}

	return 0;
}



void CheckDevices (  atadevices_t *atadevices, scsidevices_t *scsidevices)
{
	int i;
	
	while (1)
	{
		for (i = 0; i < numatadevices;i++)
		{
			if ( ataCheckDevice (  &atadevices[i]) != 0 )
			{
				exit (-1); /* never reached */
			}
		
		}
		
		for (i = 0; i < numscsidevices;i++)
		{
			if ( scsiCheckDevice (  &scsidevices[i]) != 0 )
			{
				exit (-1); /* never reached */
			}
		
		}
		
		
		sleep ( checktime );
	}
}



/* Main Program */

int main (int argv, char **argc)
{
	

	atadevices_t atadevices[MAXATADEVICES], *atadevicesptr;
	scsidevices_t scsidevices[MAXSCSIDEVICES], *scsidevicesptr;
	
	numatadevices=0;
	numscsidevices=0;
	
	scsidevicesptr = scsidevices;
	atadevicesptr = atadevices;

	if ( *(argc+1) != NULL)
		ParseOpts ( *(argc+1));
	syslog (LOG_INFO, "smartd started\n");
	if (!debugmode)
	{
		daemon_init();
	}

	/*	fork into indepentant process */
	atadevicescan (atadevicesptr); 
	scsidevicescan (scsidevicesptr);
	
	CheckDevices ( atadevicesptr, scsidevicesptr); 
	return 0;
}

