/*
*      hptsqfix - rebuilding squish index and some info in sqd
*      made by Serguei Revtov 2:5021/19.1
*      from hptUtil by Fedor Lizunkov 2:5020/960@Fidonet
*
* This file is part of HPT.
*
* HPT 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.
*
* HPT is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with HPT; see the file COPYING.  If not, write to the Free
* Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*****************************************************************************
*/
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>

#if !defined(UNIX) && !defined(SASC)
#include <io.h>
#endif

#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#if !defined(UNIX) && !defined(SASC)
#include <share.h>
#endif
#if !(defined(_MSC_VER) && (_MSC_VER >= 1200))
#include <unistd.h>
#endif

#include <smapi/prog.h>
#include <smapi/msgapi.h>
#include <squish.h>

#define fop_wpb (O_CREAT | O_TRUNC | O_RDWR | O_BINARY)
#define fop_rpb (O_RDWR | O_BINARY)

#define VERSION	"v.1.2.4-release"


int Open_File(char *name, word mode)
{
    int handle;
    
#ifdef UNIX
    handle = sopen(name, mode, SH_DENYNONE, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
#else
    handle = sopen(name, mode, SH_DENYNONE, S_IREAD | S_IWRITE);
#endif
    
    if (handle<0) fprintf(stderr, "Can't open file '%s'\n", name);
    
    return handle;
}

void usage()
{
    fprintf(stderr, "hptsqfix - squish base repairing utility, %s\n", VERSION);
    fprintf(stderr, "Usage: hptsqfix [-f] [-e] [-u] [-s] areafilename ...\n");
    fprintf(stderr, "                 -f  - try to find next header after broken msg\n");
    fprintf(stderr, "                 -e  - 'areafilename' has extension, strip it\n");
    fprintf(stderr, "                 -u  - undelete (restore deleted messages)\n");
    fprintf(stderr, "                 -s  - sort messages by date written\n");
    exit(-1);
}

int findhdr(int SqdHandle)
{
    off_t pos;
    dword  i=0, rc=1, stop=0;
    
    unsigned char chr;
    unsigned char sqhdrid[]={0x53, 0x44, 0xae, 0xaf};
    
    fprintf(stderr, "Searching valid header ID... ");
    
    pos = tell(SqdHandle);
    
    
    do {
        if (read (SqdHandle, &chr, 1) == 1) {
            pos++;
            if (chr == sqhdrid[i]) {
                i++;
                if (i==4) {
                    fprintf(stderr, " Wow! Found at pos %lu\n", (unsigned long) pos-4);
                    lseek(SqdHandle, pos-4, SEEK_SET);
                    rc = 0;
                    stop = 1;
                }
            } else {
                if (chr == sqhdrid[0])
                    i = 1;
                else
                    i = 0;
            }
        } else {
            fprintf(stderr, " not found\n");
            stop = 1;
            rc = 1;
        }
        
    } while (!stop);
    
    return rc;
}

int tryfind=0;
int unDelete=0;
int mSort=0;

typedef struct {
    time_t msgTime;
    dword frame_pos;
}   sortedbase;

sortedbase *SortedBase = NULL;

static int cmp_msgEntry(const void *a, const void *b)
{
    const sortedbase* r1 = (sortedbase*)a;
    const sortedbase* r2 = (sortedbase*)b;
    
    if( r1->msgTime > r2->msgTime)
        return 1;
    else if( r1->msgTime < r2->msgTime)
        return -1;
    return 0;
}

int repair(char *areaName)
{
    
    int SqdHandle;
    int NewSqdHandle, NewSqiHandle;
    dword i;
    long saved=0;
    int stop;
    int firstmsg=1;
    int sayFree=0;
    dword frame_length = 0;
    dword num_msg =  0;
    
    char *sqd, *newsqd, *newsqi, *text;
    
    SQBASE     sqbase;
    SQHDR      sqhdr;
    XMSG       xmsg;
    SQIDX      sqidx;
    size_t     maxMsgLen;
    struct tm  tmdate;
    
    sqd = (char*)malloc(strlen(areaName)+5);
    
    newsqd = (char*)malloc(strlen(areaName)+5);
    newsqi = (char*)malloc(strlen(areaName)+5);
    
    sprintf(sqd, "%s%s", areaName, EXT_SQDFILE);
    
    SqdHandle = Open_File(sqd, fop_rpb);
    if (SqdHandle == -1) {
        free(sqd);
        free(newsqd);
        free(newsqi);
        return 0;
    } /* endif */
    
    sprintf(newsqd, "%s.tmd", areaName);
    sprintf(newsqi, "%s.tmi", areaName);
    
    NewSqdHandle = Open_File(newsqd, fop_wpb);
    if (NewSqdHandle == -1) {
        free(sqd);
        free(newsqd);
        free(newsqi);
        close(SqdHandle);
        return 0;
    } /* endif */
    NewSqiHandle = Open_File(newsqi, fop_wpb);
    if (NewSqiHandle == -1) {
        free(sqd);
        free(newsqd);
        free(newsqi);
        close(NewSqdHandle);
        close(SqdHandle);
        return 0;
    } /* endif */
    
    lseek(SqdHandle, 0L, SEEK_END);
    maxMsgLen = tell(SqdHandle) - SQBASE_SIZE - SQHDR_SIZE;
    lseek(SqdHandle, 0L, SEEK_SET);
    
    
    if (lock(SqdHandle, 0, 1) == 0) {
        
        read_sqbase(SqdHandle, &sqbase);
        if(mSort)
        {
            num_msg = sqbase.num_msg;
            SortedBase = calloc(sizeof(sortedbase),num_msg);
            if(!SortedBase)
            {
                fprintf(stderr, "\nNot enough memory to sort base\n");
                mSort = 0;
            }
        }
        
        sqbase.num_msg = 0;
        sqbase.high_msg = 0;
        sqbase.skip_msg = 0;
        sqbase.begin_frame = SQBASE_SIZE;
        sqbase.last_frame = sqbase.begin_frame;
        sqbase.free_frame = sqbase.last_free_frame = 0;
        sqbase.end_frame = sqbase.begin_frame;
        write_sqbase(NewSqdHandle, &sqbase);
        
        
        for (stop = 0, i = 1; !stop; i++) {
            
            fprintf(stderr, "Msg %ld", i);
            
            if (read_sqhdr(SqdHandle, &sqhdr) == 0) {
                fprintf(stderr, "... end");
                stop++;
            }
            
            if(sqhdr.frame_length != sqhdr.msg_length)
                fprintf(stderr, "\nFrame Length != Msg Length %ld:%ld\n",sqhdr.frame_length ,sqhdr.msg_length); 
            
            if (stop==0 && sqhdr.id != SQHDRID) {
                
                fprintf(stderr, "\nLooks like Message header is damaged\n");
                
                if ( tryfind ) {
                    stop = findhdr(SqdHandle);
                    continue;
                }else{
                    fprintf(stderr, "\nYou may want to use -f parameter\n");
                    stop++;
                }
            }
            
            if (stop==0 && sqhdr.msg_length <= XMSG_SIZE) {
                
                fprintf(stderr, "\nMessage body is too short: %ld\n",sqhdr.msg_length);
                
                if ( tryfind ) {
                    stop = findhdr(SqdHandle);
                    continue;
                }else{
                    fprintf(stderr, "\nYou may want to use -f parameter\n");
                    stop++;
                }
            }
            
            if (stop==0 && sqhdr.frame_length > maxMsgLen) {
                
                fprintf(stderr, "\nFrame is larger than rest of file\nLooks like Message header is damaged\n");
                
                if ( tryfind ) {
                    stop = findhdr(SqdHandle);
                    continue;
                }else{
                    fprintf(stderr, "\nYou may want to use -f parameter\n");
                    stop++;
                }
            }
            
            if (!stop) {
                if (sqhdr.frame_type != FRAME_normal) {
                    if (unDelete != 0 && sqhdr.frame_type == FRAME_free) {
                        sqhdr.frame_type = FRAME_normal;
                        fprintf(stderr, "... free, restoring\n");
                    } else {
                        sayFree = 1;
                        fprintf(stderr, "... free\r");
                        lseek(SqdHandle, sqhdr.frame_length, SEEK_CUR);
                        continue;
                    }
                }
                
                read_xmsg(SqdHandle, &xmsg);
                
                text = (char*)calloc(sqhdr.frame_length, sizeof(char*));
                farread(SqdHandle, text, (sqhdr.frame_length - XMSG_SIZE));
                memcpy(text,text,sqhdr.msg_length);
                frame_length       = sqhdr.frame_length;
                sqhdr.frame_length = sqhdr.msg_length;
                if (firstmsg) {
                    sqhdr.prev_frame = 0;
                    firstmsg=0;
                } else {
                    sqhdr.prev_frame = sqbase.last_frame;
                } /* endif */
                
                sqhdr.next_frame = tell(NewSqdHandle) + SQHDR_SIZE + sqhdr.msg_length;
                
                sqbase.last_frame = tell(NewSqdHandle);
                sqidx.ofs = sqbase.last_frame;
                
                if(mSort)
                {
                    if(num_msg == sqbase.num_msg)
                    {
                        num_msg = sqbase.num_msg+1;
                        SortedBase = realloc(SortedBase,num_msg*sizeof(sortedbase));
                        if(!SortedBase)
                        {
                            fprintf(stderr, "\nNot enough memory to sort base\n");
                            mSort = 0;
                        }
                    }
                    DosDate_to_TmDate((union stamp_combo *)&xmsg.date_written, &tmdate);
                    SortedBase[sqbase.num_msg].frame_pos = sqbase.last_frame;
                    SortedBase[sqbase.num_msg].msgTime   = mktime(&tmdate);
                }
                
                write_sqhdr(NewSqdHandle, &sqhdr);
                write_xmsg(NewSqdHandle, &xmsg);
                farwrite(NewSqdHandle, text, (sqhdr.msg_length - XMSG_SIZE));
                
                sqbase.end_frame = tell(NewSqdHandle);
                
                sqidx.hash = SquishHash(xmsg.to);
                if (xmsg.attr & MSGREAD) {
                    sqidx.hash |= (dword) 0x80000000L;
                }
                sqidx.umsgid = sqbase.num_msg+1;
                
                write_sqidx(NewSqiHandle, &sqidx, 1);
                free(text);
                
                sqbase.num_msg++;
                sqbase.high_msg = sqbase.num_msg;
                saved++;
                if (sayFree) {
                    fprintf(stderr, "        ");
                    sayFree=0;
                }
                fprintf(stderr, "\r");
                
                maxMsgLen -= frame_length;
            }
       } /* endfor */
       
       fprintf(stderr, "\n%ld messages read\n", i-2);
       
       lseek(NewSqiHandle, SQIDX_SIZE, SEEK_END);
       read_sqidx(NewSqiHandle, &sqidx, 1);
       lseek(NewSqdHandle, sqidx.ofs, SEEK_SET);
       read_sqhdr(NewSqdHandle, &sqhdr);
       sqhdr.next_frame = 0;
       lseek(NewSqdHandle, sqidx.ofs, SEEK_SET);
       write_sqhdr(NewSqdHandle, &sqhdr);
       lseek(NewSqdHandle, 0L, SEEK_SET);
       write_sqbase(NewSqdHandle, &sqbase);
       
       unlock(SqdHandle, 0, 1);
       
       close(NewSqdHandle);
       close(NewSqiHandle);
       close(SqdHandle);
       
       if(mSort)
       {
           num_msg = sqbase.num_msg;           
           
           fprintf(stderr, "\nSorting msgbase...\n");
           
           qsort( (void*)SortedBase, num_msg, sizeof(sortedbase), cmp_msgEntry ); 
           
           sprintf(sqd, "%s.tmd", areaName );
           SqdHandle = Open_File(sqd, fop_rpb);
           if (SqdHandle == -1) {
               free(sqd);
               free(newsqd);
               free(newsqi);
               return 0;
           } /* endif */
           
           sprintf(newsqd, "%s.tsd", areaName);
           sprintf(newsqi, "%s.tsi", areaName);
           
           NewSqdHandle = Open_File(newsqd, fop_wpb);
           if (NewSqdHandle == -1) {
               free(sqd);
               free(newsqd);
               free(newsqi);
               close(SqdHandle);
               return 0;
           } /* endif */
           NewSqiHandle = Open_File(newsqi, fop_wpb);
           if (NewSqiHandle == -1) {
               free(sqd);
               free(newsqd);
               free(newsqi);
               close(NewSqdHandle);
               close(SqdHandle);
               return 0;
           } /* endif */
           
           lseek(SqdHandle, 0L, SEEK_END);
           maxMsgLen = tell(SqdHandle) - SQBASE_SIZE - SQHDR_SIZE;
           lseek(SqdHandle, 0L, SEEK_SET);
           
           
           if (lock(SqdHandle, 0, 1) == 0) {
               firstmsg=0;
               read_sqbase(SqdHandle, &sqbase);
               sqbase.num_msg = 0;
               sqbase.high_msg = 0;
               sqbase.skip_msg = 0;
               sqbase.begin_frame = SQBASE_SIZE;
               sqbase.last_frame = sqbase.begin_frame;
               sqbase.free_frame = sqbase.last_free_frame = 0;
               sqbase.end_frame = sqbase.begin_frame;
               write_sqbase(NewSqdHandle, &sqbase);
               
               for (i = 0; i < num_msg; i++) {
                   
                   lseek(SqdHandle, SortedBase[i].frame_pos , SEEK_SET);

                   if (read_sqhdr(SqdHandle, &sqhdr) == 0) {
                       fprintf(stderr, "... end");
                       break;
                   }
                   read_xmsg(SqdHandle, &xmsg);
                   text = (char*)calloc(sqhdr.frame_length, sizeof(char*));
                   farread(SqdHandle, text, (sqhdr.frame_length - XMSG_SIZE));
                   memcpy(text,text,sqhdr.msg_length);
                   if (firstmsg) {
                       sqhdr.prev_frame = 0;
                       firstmsg=0;
                   } else {
                       sqhdr.prev_frame = sqbase.last_frame;
                   } /* endif */
                   
                   sqhdr.next_frame = tell(NewSqdHandle) + SQHDR_SIZE + sqhdr.msg_length;
                   sqbase.last_frame = tell(NewSqdHandle);
                   sqidx.ofs = sqbase.last_frame;
                   write_sqhdr(NewSqdHandle, &sqhdr);
                   write_xmsg(NewSqdHandle, &xmsg);
                   farwrite(NewSqdHandle, text, (sqhdr.msg_length - XMSG_SIZE));
                   sqbase.end_frame = tell(NewSqdHandle);
                   sqidx.hash = SquishHash(xmsg.to);
                   if (xmsg.attr & MSGREAD) {
                       sqidx.hash |= (dword) 0x80000000L;
                   }
                   sqidx.umsgid = sqbase.num_msg+1;
                   write_sqidx(NewSqiHandle, &sqidx, 1);
                   free(text);
                   sqbase.num_msg++;
                   sqbase.high_msg = sqbase.num_msg;
                   maxMsgLen -= sqhdr.frame_length;
               }
               lseek(NewSqiHandle, SQIDX_SIZE, SEEK_END);
               read_sqidx(NewSqiHandle, &sqidx, 1);
               lseek(NewSqdHandle, sqidx.ofs, SEEK_SET);
               read_sqhdr(NewSqdHandle, &sqhdr);
               sqhdr.next_frame = 0;
               lseek(NewSqdHandle, sqidx.ofs, SEEK_SET);
               write_sqhdr(NewSqdHandle, &sqhdr);
               lseek(NewSqdHandle, 0L, SEEK_SET);
               write_sqbase(NewSqdHandle, &sqbase);
               
               unlock(SqdHandle, 0, 1);
               
               close(NewSqdHandle);
               close(NewSqiHandle);
               close(SqdHandle);
               
           } 
       }
   } else {
       close(NewSqdHandle);
       close(NewSqiHandle);
       close(SqdHandle);
       
   } /* endif */
   
   free(sqd);
   free(newsqd);
   free(newsqi);
   
   fprintf(stderr, "%ld messages saved\n", saved);
   
   return 0;
}


int main(int argc, char *argv[])
{
    int i, j, extIndx;
    int stripExt=0;
    
    if (argc < 2) usage();
    
    for (i=1; i<argc; i++) {
        if (argv[i][0] == '-') {
            if (stricmp(argv[i], "-f")==0) {
                tryfind = 1;
            } else if (stricmp(argv[i], "-e")==0) {
                stripExt = 1;
            } else if (stricmp(argv[i], "-u")==0) {
                unDelete = 1;
            } else if (stricmp(argv[i], "-s")==0) {
                mSort = 1;
            } else {
                usage();
            }
        }
    }
    
    for (i=1; i<argc; i++) {
        if (argv[i][0] != '-') {
            
            if (stripExt) {
                extIndx=0;
                for (j=0; argv[i][j]; j++) {
                    if (argv[i][j]=='.') extIndx = j;
                }
                if (extIndx > 0) {
                    argv[i][extIndx] = '\0';
                } else {
                    fprintf(stderr, "Warning: loose extension in '%s'\n", argv[i]);
                }
            }
            
            fprintf(stderr, "Repairing area '%s'\n", argv[i]);
            
            repair(argv[i]);
            
            fprintf(stderr, "Done\n\n");
        }
    }
    
    return 0;
}

