/*
_____       _    _    Corso   Italia,  178
(_|__   .  (_   |_|_  56125           Pisa
(_|_) |)|(()_)()| |   tel.  +39  050 46380
  |   |               picosoft@picosoft.it

 Copyright (C) Picosoft s.r.l. 1999-2002

 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.

 This program 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 this program; if not, write to the Free Software
 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/
# include <fcntl.h>
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <errno.h>
# if defined(MSDOS) || defined(WIN32)
# include <windows.h>
# include <process.h>
# include <io.h>
# include <errno.h>
# include <sys/locking.h>
typedef int pid_t;
typedef int uid_t;
typedef long off_t;
# define getuid() 0
# else
# include <unistd.h>
#endif
# include <time.h>
# include "isam.h"
# include "picoisam.h"
# include "picolog.h"

# define ISTRUE (1==1)
# define ISFALSE (1==0)

static int logFd = -1;
static int beginWork = 0;
static TransactionCallback rollCallback = 0;
static TransactionCallback commCallback = 0;

typedef union u_allTypes {
   char           chr[sizeof(long)];
   short          sht;
   unsigned short ush;
   long           lng;
   unsigned long  uln;
} AllTypes;

typedef struct t_LogHeader
{
   unsigned short recLen;
   unsigned short tnxType;
   pid_t tnxId;
   uid_t uid;
   time_t tnxTime;
   off_t prevRec; 
   short isfd;
} LogHeader;

typedef struct t_LogTran
{
   unsigned long recNum;
   unsigned short biLen;
   unsigned short aiLen;
   char data[1];        /* trick !! */
} LogTran;

static off_t currentOffset = 0xFFFFFFFFUL;

static LogHeader header = { 0, 0, 0, 0, 0, 0 };

static void
prepareHeader (char *out, unsigned short recLen,
               unsigned short tnxType, int isfd)
{
   if (header.recLen == 0) { /* first time only */
      header.tnxId = getpid();
      header.uid = getuid();
   }
   header.recLen = recLen;
   header.tnxType = tnxType;
   header.tnxTime = time(0);
   header.prevRec = currentOffset;
   header.isfd = isfd;
   memcpy (out, (void *) &header, sizeof(header));
}

# ifndef MAX_PATH
# define MAX_PATH 144
# endif
char logFileName[MAX_PATH] = { 0 };

static char*
logTmpName ()
{
# ifdef MSDOS
# ifdef WIN32
   GetTempPath (sizeof(logFileName), logFileName);
   GetTempFileName (logFileName, "log", 0, logFileName);
# else
   GetTempFileName (GetTempDrive(0), "log", 0, logFileName);
# endif // WIN32
# else
   strncpy (logFileName, tempnam (0, "log"), sizeof(logFileName) - 1);
   logFileName[sizeof(logFileName) - 1] = 0;
# endif // MSDOS
   return logFileName;
}

static int
writeLog (char *buffer, int bufLen)
{
   int Return = -1;
   if (logFd >= 0) {
# if defined(WIN32) || defined(MSDOS)
      errno = 0;
      while (_locking(logFd,_LK_LOCK ,1L) && errno == EDEADLOCK)
         errno = 0;
      if (errno == 0) {
# else
      struct flock lck;
      lck.l_whence = 0;
      lck.l_start = 0;
      lck.l_len = 0;
      lck.l_type = F_WRLCK;
      if (fcntl(logFd, F_SETLKW, &lck) == 0) {
# endif
         currentOffset = lseek (logFd, 0, SEEK_END);
         if (write (logFd, buffer, bufLen) == bufLen) {
            Return = 0;
# if defined(HAS_FDATASYNC)
            fdatasync (logFd);
# elif defined(HAS_FSYNC)
            fsync (logFd);
# endif
         }
# if defined(WIN32) || defined(MSDOS)
      lseek (logFd, 0L, 0);
      _locking(logFd, _LK_UNLCK, 1L);
# else
         lck.l_type = F_UNLCK;
         fcntl(logFd, F_SETLKW, &lck);
# endif
      }
   } else {
      errno = EBADF;
   }
   return Return;
}

static int
logOpen (char *fileName, int trunc)
{
   int Return = -1;
# if defined(MSDOS) || defined(WIN32)
   int flags = O_RDWR|O_CREAT|O_BINARY|(trunc ? O_TRUNC : 0);
# elif !defined(HAS_FSYNC) && !defined(HAS_FDATASYNC)
   int flags = O_RDWR|O_CREAT|O_SYNC|(trunc ? O_TRUNC : 0);
# else
   int flags = O_RDWR|O_CREAT|(trunc ? O_TRUNC : 0);
# endif

   if (logFd >= 0)
      close (logFd);
   if ((logFd = open (fileName, flags, 0666)) >= 0) {
      Return = 0;
   } else {
      pIserrno = ELOGOPEN;
      pIserrio = errno;
   }
   return Return;
}

int
_pI__logClose ()
{
   int Return = -1;

   if (logFd >= 0)
      Return = close (logFd);
   logFd = -1;
   return Return;
}

static int
removeLog (char *name)
{
   if (logFd >= 0) {
      close (logFd);
      logFd = -1;
   }
   return unlink (name);
}


static int
headerOnlyRec (unsigned short tnx)
{
   int Return = -1;
   char buffer[sizeof(header)];
   prepareHeader (buffer, sizeof(header), tnx, -1);
   if ((Return = writeLog (buffer, sizeof(header)) != 0)) {
      pIserrno = ELOGWRIT;
      pIserrio = errno;
   }
   return Return;
}

static int
fileRec (unsigned short tnx, int isfd, char *nam)
{
   int Return = -1;
   char * name = nam ? nam : "";
   unsigned short recLen = sizeof(header) + strlen(name);
   char *buffer = malloc (recLen + 1);
   prepareHeader (buffer, recLen, tnx, isfd);
   strcpy (&buffer[sizeof(header)], name);
   if ((Return = writeLog (buffer, recLen) != 0)) {
      pIserrno = ELOGWRIT;
      pIserrio = errno;
   }
   free (buffer);
   return Return;
}

static int
imageRec(unsigned short txn, int isfd, unsigned long recnum,
         char *beforeImage, unsigned short biLen,
         char *afterImage, unsigned short aiLen)
{
   int Return = -1;
   unsigned short recLen;
   char *buffer;
   LogTran *lh;
   char *pnt;

   if (beforeImage == 0)
      biLen = 0;
   if (afterImage == 0)
      aiLen = 0;
   recLen = sizeof(header) + sizeof(LogTran) + biLen + aiLen - 1;
   buffer = malloc (recLen);
   lh = (LogTran *) (buffer + sizeof(header));
   prepareHeader (buffer, recLen, txn, isfd);

   lh->recNum = recnum;
   lh->biLen = biLen;
   lh->aiLen = aiLen;
   pnt = lh->data;

   if (biLen > 0) {
      memcpy (pnt, beforeImage, biLen);
      pnt += biLen;
   }
   if (aiLen > 0) {
      memcpy (pnt, afterImage, aiLen);
      pnt += aiLen;
   }

   if ((Return = writeLog (buffer, recLen) != 0)) {
      pIserrno = ELOGWRIT;
      pIserrio = errno;
   }
   free (buffer);
   return Return;
}

static int
callback (TransactionCallback cb)
{
   int Return = -1;
   LogHeader hd;
   LogTran *lh;
   off_t addr = currentOffset;
   char *pnt;
   char *bi;
   char *ai;
   unsigned int dataLen;

   for ( ; ; ) {
      if (lseek (logFd, addr, SEEK_SET) >= 0 && 
          read (logFd, &hd, sizeof(hd)) == sizeof(hd)) {
         pnt = (char *) &hd.tnxType;
         switch (hd.tnxType) {
         case TNX_BEGIN_WORK:
         case TNX_COMMIT_WORK:
         case TNX_ROLLBACK_WORK:
            break;
         case TNX_FILE_OPEN:
         case TNX_FILE_CLOSE:
            dataLen = hd.recLen - sizeof(LogHeader);
            pnt = malloc (dataLen + 1);
            pnt[dataLen] = 0;
            if (read (logFd, pnt, dataLen) != dataLen) {
               free (pnt);
               pIserrno = EBADLOG;
               return Return;
            }
            Return = cb (hd.tnxType,hd.isfd,0,pnt,strlen(pnt),0,0);
            free (pnt);
            if (Return < 0)
                return Return;
            break;
         case TNX_INSERT:
         case TNX_DELETE:
         case TNX_UPDATE:
            dataLen = hd.recLen - sizeof(LogHeader);
            pnt = malloc (dataLen + 1);
            pnt[dataLen] = 0;
            if (read (logFd, pnt, dataLen) != dataLen) {
               free (pnt);
               pIserrno = EBADLOG;
               return Return;
            }
            lh = (LogTran *) pnt;
            if (lh->biLen > 0) 
               bi = lh->data;
            else
               bi = 0;
            if (lh->aiLen > 0) 
               ai = lh->data + lh->biLen;
            else
               ai = 0;
            
            Return = cb (hd.tnxType, hd.isfd, lh->recNum,
                         bi, lh->biLen, ai, lh->aiLen);
            free (pnt);
            if (Return < 0)
               return Return;
            break;
         default:
            pIserrno = EBADARG;
         }
         if (hd.tnxType != TNX_BEGIN_WORK) {
            addr = hd.prevRec;
         } else {
            Return = 0;
            break;
         }
      } else {
         pIserrno = EBADLOG;
         break;
      }
   }
   return Return;
}

int
_pI__logOpen (char *fileName, TransactionCallback commit,
                              TransactionCallback rollback)
{
   commCallback = commit;
   rollCallback = rollback;
   if (fileName)
      return logOpen (fileName, 0);
   else
      return  0;
}

int
_pI__transaction (unsigned short tnxType, short isfd, unsigned long recNum,
                  char *beforeImage, unsigned short biLen,
                  char *afterImage, unsigned short aiLen)
{
   int Return = -1;
   if (!beginWork && tnxType != TNX_BEGIN_WORK) {
      pIserrno = ENOBEGIN;
      return Return;
   } else if (beginWork && tnxType == TNX_BEGIN_WORK) {
      pIserrno = ENOTSUPP;
      return Return;
   }
   switch (tnxType) {
   case TNX_BEGIN_WORK:
      if (logFd < 0 && logOpen (logTmpName(), 1) < 0) {
         return Return;
      }
      if ((Return = headerOnlyRec (TNX_BEGIN_WORK)) >= 0) {
         beginWork = 1;
      }
      break;
   case TNX_COMMIT_WORK:
      if (logFileName[0] == 0)
         Return = headerOnlyRec (TNX_COMMIT_WORK);
      else
         Return = 0;
      beginWork = 0;
      if (Return >= 0)
         Return = callback(commCallback);
      if (logFileName[0] != 0)
         removeLog (logFileName);
      break;
   case TNX_ROLLBACK_WORK:
      if (logFileName[0] == 0)
         Return = headerOnlyRec (TNX_ROLLBACK_WORK);
      else
         Return = 0;
      beginWork = 0;
      if (Return >= 0)
         Return = callback(rollCallback);
      if (logFileName[0] != 0)
         removeLog (logFileName);
      break;
   case TNX_FILE_OPEN:
   case TNX_FILE_CLOSE:
      Return = fileRec (tnxType, isfd, beforeImage);
      break;
   case TNX_INSERT:
      Return = imageRec(tnxType,isfd,recNum, 0, 0, afterImage, aiLen);
      break;
   case TNX_DELETE:
      Return = imageRec(tnxType,isfd,recNum,beforeImage,biLen, 0, 0);
      break;
   case TNX_UPDATE:
      Return = imageRec(tnxType,isfd,recNum,beforeImage,biLen,afterImage,aiLen);
      break;
   default:
      pIserrno = EBADARG;
   }
   return Return;
}

typedef struct t_Session {
   pid_t tnxId;
   int nDimFiles;
   int nOpenFiles;
   int committed;
   off_t lastRec;
   struct t_File {
      int isfd;
      char *name;
   } *openFiles;
} Session;
static Session *sessionArray = 0;
static int sessionSize = 0;
static int sessionDim = 0;
# define ARRAY_INCR 10

static void
freeSessions ()
{
   int i, j;

   for (i = 0; i < sessionSize; i++) {
      for (j = 0; j < sessionArray[i].nOpenFiles; j++)
         if (sessionArray[i].openFiles[j].name != 0)
            free (sessionArray[i].openFiles[j].name);
      free (sessionArray[i].openFiles);
   }
   free (sessionArray);
   sessionArray = 0;
   sessionSize = 0;
   sessionDim = 0;
}

static Session *
sessionAdd (pid_t tnx)
{
   if (sessionSize == sessionDim) {
      sessionDim += ARRAY_INCR;
      sessionArray = realloc (sessionArray,sizeof(Session)*sessionDim);
   }
   sessionArray[sessionSize].tnxId = tnx;
   sessionArray[sessionSize].nDimFiles = 0;
   sessionArray[sessionSize].nOpenFiles = 0;
   sessionArray[sessionSize].committed = ISTRUE;
   sessionArray[sessionSize].lastRec = 0;
   sessionArray[sessionSize].openFiles = 0;
   sessionSize++;
   return &sessionArray[sessionSize - 1];
}

static Session *
sessionGet (pid_t tnx)
{
   int i;

   for (i = 0; i < sessionSize; i++)
      if (sessionArray[i].tnxId == tnx)
         return &sessionArray[i];
   return 0;
}

static void
sessionOpenFile (Session *ss, int isfd, char *name)
{
   int i;

   for (i = 0; i < ss->nOpenFiles; i++) {
      if (ss->openFiles[i].name == 0) {
         ss->openFiles[i].isfd = isfd;
         ss->openFiles[i].name = strdup(name);
         return;
      }
   }
   if (ss->nOpenFiles == ss->nDimFiles) {
      ss->nDimFiles += ARRAY_INCR;
      ss->openFiles=realloc(ss->openFiles,sizeof(struct t_File)*ss->nDimFiles);
   }
   ss->openFiles[i].isfd = isfd;
   ss->openFiles[i].name = strdup(name);
   ss->nOpenFiles++;
}

static void
sessionCloseFile (Session *ss, int isfd)
{
   int i;

   for (i = 0; i < ss->nOpenFiles; i++) {
      if (ss->openFiles[i].isfd == isfd) {
         ss->openFiles[i].isfd = -1;
         free(ss->openFiles[i].name);
         ss->openFiles[i].name = 0;
         return;
      }
   }
}

static char *
sessionGetFileName (Session *ss, int isfd)
{
   int i;

   for (i = 0; i < ss->nOpenFiles; i++) {
      if (ss->openFiles[i].isfd == isfd) {
         return ss->openFiles[i].name;
      }
   }
   return 0;
}

static int
recoverRollback (Session *ss, RecoverCallback fDo)
{
   int Return = -1;
   LogHeader hd;
   LogTran *lh;
   off_t addr = ss->lastRec;
   char *pnt;
   char *bi;
   char *ai;
   unsigned int dataLen;

   for ( ; ; ) {
      if (lseek (logFd, addr, SEEK_SET) >= 0 && 
          read (logFd, &hd, sizeof(hd)) == sizeof(hd)) {
         pnt = (char *) &hd.tnxType;
         switch (hd.tnxType) {
         case TNX_BEGIN_WORK:
         case TNX_COMMIT_WORK:
         case TNX_ROLLBACK_WORK:
         case TNX_FILE_OPEN:
         case TNX_FILE_CLOSE:
            break;
         case TNX_INSERT:
         case TNX_DELETE:
         case TNX_UPDATE:
            dataLen = hd.recLen - sizeof(LogHeader);
            pnt = malloc (dataLen + 1);
            pnt[dataLen] = 0;
            if (read (logFd, pnt, dataLen) != dataLen) {
               free (pnt);
               pIserrno = EBADLOG;
               return Return;
            }
            lh = (LogTran *) pnt;
            if (lh->biLen > 0) 
               bi = lh->data;
            else
               bi = 0;
            if (lh->aiLen > 0) 
               ai = lh->data + lh->biLen;
            else
               ai = 0;
            
            switch (hd.tnxType) {
            case TNX_INSERT:
               Return = fDo (TNX_DELETE, sessionGetFileName(ss, hd.isfd),
                             lh->recNum, ai, lh->aiLen, bi, lh->biLen);
               break;
            case TNX_DELETE:
               Return = fDo (TNX_INSERT, sessionGetFileName(ss, hd.isfd),
                             lh->recNum, ai, lh->aiLen, bi, lh->biLen);
               break;
            case TNX_UPDATE:
               Return = fDo (TNX_UPDATE, sessionGetFileName(ss, hd.isfd),
                             lh->recNum, ai, lh->aiLen, bi, lh->biLen);
               break;
            }
            free (pnt);
            if (Return < 0)
               return Return;
            break;
         default:
            pIserrno = EBADARG;
         }
         if (hd.tnxType != TNX_BEGIN_WORK) {
            addr = hd.prevRec;
         } else {
            Return = 0;
            break;
         }
      } else {
         pIserrno = EBADLOG;
         break;
      }
   }
   return Return;
}

int
_pI__recover (RecoverCallback fDo)
{
   int Return = 0;
   LogHeader hd;
   LogTran *lh;
   Session *ss;
   char *bi;
   char *ai;
   off_t addr = 0;
   int dataLen;
   char * pnt;
   int i;

   for ( ; Return == 0 ; ) {
      if (lseek (logFd, addr, SEEK_SET) >= 0 && 
          read (logFd, &hd, sizeof(hd)) == sizeof(hd)) {
         ss = sessionGet(hd.tnxId);
         if (ss == 0 && hd.tnxType == TNX_BEGIN_WORK)
            ss = sessionAdd (hd.tnxId);
         ss->lastRec = addr;
         switch (hd.tnxType) {
         case TNX_BEGIN_WORK:
            ss->committed = ISFALSE;
            break;
         case TNX_FILE_OPEN:
            dataLen = hd.recLen - sizeof(LogHeader);
            pnt = malloc (dataLen + 1);
            pnt[dataLen] = 0;
            if (read (logFd, pnt, dataLen) != dataLen) {
               free (pnt);
               pIserrno = EBADLOG;
               return -1;
            }
            sessionOpenFile (ss, hd.isfd, pnt);
            break;
         case TNX_FILE_CLOSE:
            sessionCloseFile (ss, hd.isfd);
            break;
         case TNX_INSERT:
         case TNX_DELETE:
         case TNX_UPDATE:
            dataLen = hd.recLen - sizeof(LogHeader);
            pnt = malloc (dataLen + 1);
            pnt[dataLen] = 0;
            if (read (logFd, pnt, dataLen) != dataLen) {
               free (pnt);
               pIserrno = EBADLOG;
               return -1;
            }
            lh = (LogTran *) pnt;
            if (lh->biLen > 0) 
               bi = lh->data;
            else
               bi = 0;
            if (lh->aiLen > 0) 
               ai = lh->data + lh->biLen;
            else
               ai = 0;
            Return = fDo (hd.tnxType, sessionGetFileName(ss, hd.isfd),
                          lh->recNum, bi, lh->biLen, ai, lh->aiLen);
            free (pnt);
            break;
         case TNX_COMMIT_WORK:
            ss->committed = ISTRUE;
            break;
         case TNX_ROLLBACK_WORK:
            Return = recoverRollback (ss, fDo);
            ss->committed = ISTRUE;
            break;
         default:
            Return = -1;
            pIserrno = EBADLOG;
            break;
         }
         addr += hd.recLen;
      } else {
         break;
      }
   }
   for (i = 0; Return == 0 && i < sessionSize; i++)
      if (sessionArray[i].committed == ISFALSE)
         Return = recoverRollback (&sessionArray[i], fDo);
   freeSessions();
   return Return;
}
