/*

 * Copyright Daniel Industries.
 *
 * Created by: Vipin Malik (vipin.malik@daniel.com)
 *
 * This code is released under the GPL version 2. See the file COPYING
 * for more details.
 *
 * Software distributed under the Licence is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
 * See the Licence for the specific language governing rights and
 * limitations under the Licence.
  
  This program opens files in progression (file00001, file00002 etc),
  upto MAX_NUM_FILES and checks their CRC. If a file is not found or the
  CRC does not match it stops it's operation.
  
  Everything is logged in a logfile called './logfile'.
  
  If everything is ok this program sends a signal, via com1, to the remote
  power control box to power cycle this computer.
  
  This program then proceeds to create new files file0....file<MAX_NUM_FILES>
  in a endless loop and checksum each before closing them.
  
  STRUCTURE OF THE FILES:
  The fist int is the size of the file in bytes.
  The last 2 bytes are the CRC for the entire file.
  There is random data in between.
  
  The files are opened in the current dir.
  
  $Id: checkfs.c,v 1.7 2001/06/21 23:04:17 dwmw2 Exp $
  $Log: checkfs.c,v $
  Revision 1.7  2001/06/21 23:04:17  dwmw2
  Initial import to MTD CVS

  Revision 1.6  2001/06/08 22:26:05  vipin
  Split the modbus comm part of the program (that sends the ok to pwr me down
  message) into another file "comm.c"

  Revision 1.5  2001/06/08 21:29:56  vipin
  fixed small issue with write() checking for < 0 instead of < (bytes to be written).
  Now it does the latter (as it should).

  Revision 1.4  2001/05/11 22:29:40  vipin
  Added a test to check and err out if the first int in file (which tells us
  how many bytes there are in the file) is zero. This will prevent a corrupt
  file with zero's in it from passing the crc test.

  Revision 1.3  2001/05/11 21:33:54  vipin
  Changed to use write() rather than fwrite() when creating new file. Additionally,
  and more important, it now does a single write() for the entire data. This will
  enable us to use this program to test for power fail *data* reliability when
  writing over an existing file, specially on powr fail "safe" file systems as
  jffs/jffs2. Also added a new cmdline parameter "-e" that specifies the max # of
  errors that can be tolerated. This should be set to ZERO to test for the above,
  as old data should be reliabily maintained if the newer write never "took" before
  power failed. If the write did succeed, then the newer data will have its own
  CRC in place when it gets checked => hence no error. In theory at least!

  
  Revision 1.2  2001/05/11 19:27:33  vipin
  Added cmd line args to change serial port, and specify max size of
  random files created. Some cleanup. Added -Wall to Makefile.

  Revision 1.1  2001/05/11 16:06:28  vipin
  Importing checkfs (the power fail test program) into CVS. This was
  originally done for NEWS. NEWS had a lot of version, this is
  based off the last one done for NEWS. The "makefiles" program
  is run once initially to create the files in the current dir.
  "checkfs" is then run on every powerup to check consistancy
  of the files. See checkfs.c for more details.

  
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include "common.h"



extern int do_pwr_dn(int fd, int cycleCnt);

#define CMDLINE_PORT "-p"
#define CMDLINE_MAXFILEBYTES "-s"
#define CMDLINE_MAXERROR "-e"
#define CMDLINE_HELPSHORT "-?"
#define CMDLINE_HELPLONG "--help"


int CycleCount;

char SerialDevice[255] = "/dev/ttyS0"; /* default, can be changed
                                        through cmd line. */

#define MAX_INTS_ALLOW 100000 /* max # of int's in the file written.
                                 Statis limit to size struct. */
float FileSizeMax = 1024.0; /*= (file size in bytes), MUST be float*/

int MaxErrAllowed = 1; /* default, can ge changed thru cmd line*/


/* Needed for CRC generation/checking */
static const unsigned short crc_ccitt_table[] = {
    0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
    0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
    0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
    0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
    0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
    0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
    0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
    0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
    0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
    0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
    0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
    0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
    0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
    0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
    0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
    0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
    0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
    0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
    0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
    0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
    0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
    0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
    0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
    0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
    0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
    0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
    0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
    0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
    0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
    0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
    0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
    0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
};


/*
  Set's up the Linux serial port. Must be passed string to device to
  open. Parameters are fixed to 9600,e,1

  [A possible enhancement to this program would be to pass these
  parameters via the command line.]
  
  Returns file descriptor to open port. Use this fd to write to port
  and close it later, when done.
*/
int setupSerial (const char *dev) {
    int i, fd;
    struct termios tios;
    
    fd = open(dev,O_RDWR | O_NDELAY );
    if (fd < 0) {
        fprintf(stderr, "%s: %s\n", dev, sys_errlist[errno]); 
        exit(1);
    }
    if (tcgetattr(fd, &tios) < 0) {
        fprintf(stderr,"Could not get terminal attributes: %s",sys_errlist[errno]);
        exit(1);
    }
    
    tios.c_cflag = 
        CS7 |
        CREAD |			// Enable Receiver
        HUPCL |			// Hangup after close
        CLOCAL |                // Ignore modem control lines
        PARENB;			// Enable parity (even by default)
    
    
    
    tios.c_iflag      = IGNBRK; // Ignore break 
    tios.c_oflag      = 0;
    tios.c_lflag      = 0;
    for(i = 0; i < NCCS; i++) {
        tios.c_cc[i] = '\0';           // no special characters
    }
    tios.c_cc[VMIN] = 1;
    tios.c_cc[VTIME] = 0;
    
    cfsetospeed (&tios, B9600);
    cfsetispeed (&tios, B9600);
    
    if (tcsetattr(fd, TCSAFLUSH, &tios) < 0) {
        fprintf(stderr,"Could not set attributes: ,%s",sys_errlist[errno]);
        exit(1);
    }
    return fd;
}





//A portion of this code was taken from the AX.25 HDLC packet driver
//in LINUX. Once I test and have a better understanding of what
//it is doing, it will be better commented.

//For now one can speculate that the CRC routine always expects the
//CRC to calculate out to 0xf0b8 (the hardcoded value at the end)
//and returns TRUE if it is and FALSE if it doesn't.
//Why don't people document better!!!!
int check_crc_ccitt(char *filename)
{
    FILE *fp;
    FILE *logfp;
    unsigned short crc = 0xffff;
    int len;
    char dataByte;
    int retry;
    char done;
    
    fp =   fopen(filename,"rb");
    if(!fp){
        logfp = fopen("logfile","a"); /*open for appending only.*/
        fprintf(logfp, "Verify checksum:Error! Cannot open filename passed for verify checksum: %s\n",filename);
        fclose(logfp);
        return FALSE;
    }
    
    
    /*the first int contains an int that is the length of the file in long.*/
    if(fread(&len, sizeof(int), 1, fp) != 1){
        logfp = fopen("logfile","a"); /*open for appending only.*/
        fprintf(logfp, "verify checksum:Error reading from file: %s\n", filename);
        fclose(fp);
        fclose(logfp);
        return FALSE;
    }

    /* printf("Checking %i bytes for CRC in \"%s\".\n", len, filename); */

    /* Make sure that we did not read 0 as the number of bytes in file. This
       check prevents a corrupt file with zero's in it from passing the
       CRC test. A good file will always have some data in it. */
    if(len == 0)
    {

        logfp = fopen("logfile","a"); /*open for appending only.*/
        fprintf(logfp, "verify checksum: first int claims there are 0 data in file. Error!: %s\n", filename);
        fclose(fp);
        fclose(logfp);
        return FALSE;
    }
    
    
    rewind(fp);
    len+=2; /*the file has two extra bytes at the end, it's checksum. Those
              two MUST also be included in the checksum calculation.
            */
    
    for (;len>0;len--){
        retry=5; /*retry 5 times*/
        done = FALSE;
        while(!done){
            if(fread(&dataByte, sizeof(char), 1, fp) != 1){
                retry--;
            }else{
                done = TRUE;
            }
            if(retry == 0){
                done = TRUE;
            }
        }
        if(!retry){
            logfp = fopen("logfile","a"); /*open for appending only.*/
            fprintf(logfp, "Unexpected end of file: %s\n", filename);
            fprintf(logfp, "...bytes left to be read %i.\n",len);
            fclose(logfp);
            fclose(fp);
            return FALSE;
        }
        crc = (crc >> 8) ^ crc_ccitt_table[(crc ^ dataByte) & 0xff];
    }
    fclose(fp);
    if( (crc & 0xffff) != 0xf0b8){
        /*printf("The CRC of read file:%x\n", crc); */
        return FALSE;
    }
    return TRUE;
}/*end check_crc_ccitt() */



/*
  Sends "OK to power me down" message to the remote
  power cycling box, via the serial port.
  Also updates the num power cycle count in a local
  file.
  This file "./cycleCnt" must be present. This is
  initially (and once) created by the separate "makefiles.c"
  program.
*/
void send_pwrdn_ok(void){
    
    int fd;
    FILE *cyclefp;
    int cycle_fd;
    
    cyclefp =   fopen("cycleCnt","rb");
    if(!cyclefp){
        printf("expecting file \"cycleCnt\". Cannot continue.\n");
        exit(1);
    }
    if(fread(&CycleCount, sizeof(CycleCount),1,cyclefp) != 1){
        fprintf(stderr, "Error! Unexpected end of file cycleCnt.\n");
        exit(1);
    }
    fclose(cyclefp);
    
    CycleCount++;
    
    /*now write this puppy back*/
    cyclefp  = fopen("cycleCnt","wb");
    cycle_fd = fileno(cyclefp);
    if(!cyclefp){
        fprintf(stderr, "Error! cannot open file for write:\"cycleCnt\". Cannot continue.\n");
        exit(1);
    }
    if(fwrite(&CycleCount, sizeof(CycleCount), 1,cyclefp) !=1){
        fprintf(stderr, "Error writing to file cycleCnt. Cannot continue.\n");
        exit(1);
    }
    if(fdatasync(cycle_fd)){
        fprintf(stderr, "Error! cannot sync file buffer with disk.\n");
        exit(1);
    }
    
    fclose(cyclefp);
    (void)sync();
    
    printf("\n\n Sending Power down command to the remote box.\n");
    fd = setupSerial(SerialDevice);

    if(do_pwr_dn(fd, CycleCount) < 0)
    {
        fprintf(stderr, "Error sending power down command.\n");
        exit(1);
    }
    
    close(fd);
}//end send_pwrnd_ok()




/*
  Appends 16bit CRC at the end of numBytes long buffer.
  Make sure buf, extends at least 2 bytes beyond.
 */
void appendChecksum(char *buf, int numBytes){
    
    unsigned short crc = 0xffff;
    int index = 0;

    /* printf("Added CRC (2 bytes) to %i bytes.\n", numBytes); */
        
    for (; numBytes > 0; numBytes--){

        crc = (crc >> 8) ^ crc_ccitt_table[(crc ^ buf[index++]) & 0xff];
    }
    crc ^= 0xffff;
    /*printf("The CRC: %x\n\n", crc);*/

    buf[index++] = crc;
    buf[index++] = crc >> 8;

    
    
}/*end checksum()*/





/*
  This guy make a new "random data file" with the filename
  passed to it. This file is checksummed with the checksum
  stored at the end. The first "int" in the file is the
  number of int's in it (this is needed to know how much
  data to read and checksum later).
*/
void make_new_file(char *filename){
    
    
    int dfd; /* data file descriptor */
    int rand_data;
    int data_size;
    int temp_size;
    int dataIndex = 0;
    int err;

    
    struct {
        int sizeInBytes; /* must be int */
        int dataInt[MAX_INTS_ALLOW+1]; /* how many int's can we write? */
    }__attribute((packed)) dataBuf;
    
    
    fprintf(stderr, "Creating File:%s. ", filename);

    if((dfd = open(filename, O_RDWR | O_CREAT | O_SYNC)) <= 0)
    {
        printf("Error! Cannot open file: %s\n",filename);
        perror("Error");
        exit(1);
    }

    /*now write a bunch of random binary data to the file*/
    /*first figure out how much data to write. That is random also.*/

    /*file should not be less than 5 ints long. (so that we have decent length files,
      that's all)*/
    while(
	((data_size = (int)(1+(int)((FileSizeMax/sizeof(int))*rand()/(RAND_MAX+1.0)))) < 5)
    );
    
    /* printf("Writing %i ints to the file.\n", data_size); */
    
    temp_size = data_size * sizeof(int);

    /* Make sure that all data is written in one go! This is important to
       check for reliability of file systems like JFFS/JFFS that purport to
       have "reliable" writes during powre fail.
     */
    
    dataBuf.sizeInBytes = temp_size;

    data_size--; /*one alrady written*/
    dataIndex = 0;
    
    while(data_size--){
        rand_data =  (int)(1 + (int)(10000.0*rand()/(RAND_MAX+1.0)));

        dataBuf.dataInt[dataIndex++] = rand_data;
        
    }
    
    /*now calculate the file checksum and append it to the end*/
    appendChecksum((char *)&dataBuf, dataBuf.sizeInBytes);

    /* Don't forget to increase the size of data written by the 2 chars of CRC at end.
     These 2 bytes are NOT included in the sizeInBytes field. */
    if((err = write(dfd, (void *)&dataBuf, dataBuf.sizeInBytes + sizeof(short))) <
       (dataBuf.sizeInBytes + sizeof(short))
    )
    {
        printf("Error writing data buffer to file. Written %i bytes rather than %i bytes.",
               err, dataBuf.sizeInBytes);
        perror("Error");
        exit(1);
    }

    /* Now that the data is (hopefully) safely written. I can truncate the file to the new
       length so that I can reclaim any unused space, if the older file was larger.
     */
    if(ftruncate(dfd, dataBuf.sizeInBytes + sizeof(short)) < 0)
    {
        perror("Error: Unable to truncate file.");
        exit(1);
    }
    
    
    close(dfd);
    
    
}//end make_new_file()



/*
  Show's help on stdout
 */
void printHelp(char **argv)
{
    printf("Usage:%s <options, defined below>\n", argv[0]);
    printf("%s </dev/ttyS0,1,2...>: Set com port to send ok to pwr dn msg on\n",
           CMDLINE_PORT);
    printf("%s <n>: Set Max size in bytes of each file to be created.\n",
           CMDLINE_MAXFILEBYTES);
    printf("%s <n>: Set Max errors allowed when checking all files for CRC on start.\n",
           CMDLINE_MAXERROR);
    printf("%s or %s: This Help screen.\n", CMDLINE_HELPSHORT,
           CMDLINE_HELPLONG);
    
}/* end printHelp()*/



void processCmdLine(int argc, char **argv)
{
    
    int cnt;

    /* skip past name of this program, process rest */
    for(cnt = 1; cnt < argc; cnt++)
    {
        if(strcmp(argv[cnt], CMDLINE_PORT) == 0)
        {
            strncpy(SerialDevice, argv[++cnt], sizeof(SerialDevice));
            continue;
        }else
            if(strcmp(argv[cnt], CMDLINE_MAXFILEBYTES) == 0)
            {
                FileSizeMax = (float)atoi(argv[++cnt]);
                if(FileSizeMax > (MAX_INTS_ALLOW*sizeof(int)))
                {
                    printf("Max file size allowd is %i.\n",
                           MAX_INTS_ALLOW*sizeof(int));
                    exit(0);
                }
                
                continue;
            }else
                if(strcmp(argv[cnt], CMDLINE_HELPSHORT) == 0)
                {
                    printHelp(argv);
                    exit(0);

                }else
                    if(strcmp(argv[cnt], CMDLINE_HELPLONG) == 0)
                    {
                        printHelp(argv);
                        exit(0);
                    }else
                        
                        if(strcmp(argv[cnt], CMDLINE_MAXERROR) == 0)
                        {
                            MaxErrAllowed = atoi(argv[++cnt]);
                        }           
                        else
                        {
                            printf("Unknown cmd line option:%s\n", argv[cnt]);
                            printHelp(argv);
                            exit(0);
                            
                        }
    }
    
    
}/* end processCmdLine() */





int main(int argc, char **argv){
    
    FILE *logfp;
    int log_fd;
    char filename[30];
    short filenameCounter = 0;
    unsigned short counter;
    unsigned short numberFiles;
    char error = FALSE;
    short errorCnt = 0;
    time_t timep;
    char * time_string;
    unsigned int seed;
    
    
    numberFiles = MAX_NUM_FILES;

    if(argc >= 1)
    {
        processCmdLine(argc, argv);
    }
    
    
    /*
      First open MAX_NUM_FILES and make sure that the checksum is ok.
      Also make an intry into the logfile.
    */
    /* timestamp! */
    time(&timep);
    time_string = (char *)ctime((time_t *)&timep);
    
    /*start a new check, make a log entry and continue*/
    logfp = fopen("logfile","a"); /*open for appending only.*/
    log_fd = fileno(logfp);
    fprintf(logfp,"%s", time_string);
    fprintf(logfp,"Starting new check.\n");
    if(fdatasync(log_fd) == -1){
        fprintf(stderr,"Error! Cannot sync file data with disk.\n");
        exit(1);
    }  
    
    fclose(logfp);
    (void)sync();
    
    /*
      Now check all random data files in this dir.
    */
    for(counter=0;counter<MAX_NUM_FILES;counter++){
        
        fprintf(stderr, "%i.", counter);
        
        /*create the filename in sequence. The number of files
          to check and the algorithm to create the filename is
          fixed and known in advance.*/
        sprintf(filename,"file%i",filenameCounter++);
        
        if(!check_crc_ccitt(filename)){
            /*oops, checksum does not match. Make an entry into the log file
              and decide if we can continue or not.*/
            fprintf(stderr, "crcError:%s ", filename);
            logfp = fopen("logfile","a"); /*open for appending only.*/
            log_fd = fileno(logfp);
            fprintf(logfp,"CRC error in file: %s\n", filename);
            if(fdatasync(log_fd) == -1){
                fprintf(stderr,"Error! Cannot sync file data with disk.\n");
                exit(1);
            }
            fclose(logfp);
            (void)sync();
            
            error = TRUE;
            errorCnt++;
            
            if(errorCnt > MaxErrAllowed){
                logfp = fopen("logfile","a"); /*open for appending only.*/
                log_fd = fileno(logfp);
                fprintf(logfp,"\nMax Error count exceed. Stopping!\n");
                if(fdatasync(log_fd) == -1){
                    fprintf(stderr,"Error! Cannot sync file data with disk.\n");
                    exit(1);
                }
                fclose(logfp);
                (void)sync();
                
                fprintf(stderr, "Too many errors. See \"logfile\".\n");
                exit(1);
            }/* if too many errors */
            
            /*we have decided to continue, however first repair this file
              so that we do not cumulate errors across power cycles.*/
            make_new_file(filename);
        }
    }//for
    
    /*all files checked, make a log entry and continue*/
    logfp = fopen("logfile","a"); /*open for appending only.*/
    log_fd = fileno(logfp);
    fprintf(logfp,"All files checked. Total errors found: %i\n\n", errorCnt);
    if(fdatasync(log_fd)){
        fprintf(stderr, "Error! cannot sync file buffer with disk.\n");
        exit(1);
    }  
    
    fclose(logfp);
    (void)sync();
    
    /*now send a message to the remote power box and have it start a random
      pwer down timer after which power will be killed to this unit.
    */
    send_pwrdn_ok();
    
    /*now go into a forever loop of writing to files and CRC'ing them on 
      a continious basis.*/
    
    /*start from a random file #*/
    /*seed rand based on the current time*/
    seed = (unsigned int)time(NULL);
    srand(seed);
    
    filenameCounter=(int)(1+(int)((float)(MAX_NUM_FILES-1)*rand()/(RAND_MAX+1.0)));
    
    while(1){
        
        for(;filenameCounter<MAX_NUM_FILES;filenameCounter++){
            
            /*create the filename in sequence*/
            sprintf(filename,"file%i",filenameCounter);
            make_new_file(filename);
        }
        filenameCounter = 0;
    }
    
    exit(0); /* though we will never reach here, but keeps the compiler happy*/
}/*end main()*/
