/* f4write.c (c)Copyright Sequiter Software Inc., 1988-1996.  All rights reserved. */

#include "d4all.h"
#ifdef __TURBOC__
   #pragma hdrstop
#endif

#ifdef S4TEMP
   #include "t4test.h"
#endif

#ifdef S4WINTEL
   #ifndef S4IBMOS2
      #ifndef __TURBOC__
         #include <sys\locking.h>
         #define S4LOCKING
      #endif
      #ifdef _MSC_VER
         #include <sys\types.h>
         #include <sys\locking.h>
      #endif
   #endif
   #ifndef S4OFF_OPTIMIZE
      #ifdef E4ANALYZE_ALL
         #include <sys\stat.h>
         #include <share.h>
         #include <fcntl.h>
      #endif
   #endif

/*   #include <sys\stat.h>*/
/*   #include <share.h>*/
#endif

/*#include <fcntl.h>*/

/* returns the length written */
static unsigned file4writeLowDo( FILE4 *f4, const long pos, const void *ptr, const unsigned len )
{
   unsigned int urc ;
/* */
/* */
/* */

   #ifdef S4MULTI_THREAD
      EnterCriticalSection( &f4->critical4file ) ;
   #endif

   #ifdef S4WIN32
      if ( SetFilePointer( (HANDLE)f4->hand, pos, NULL, FILE_BEGIN ) == (DWORD)-1 )
   #else
/* */
/* */
/* */
         #ifdef S4WINDOWS
            if ( _llseek( f4->hand, pos, 0 ) != pos )
         #else
            #ifdef S4LSEEK
               if ( f4lseek( f4->hand, pos, 0, 1 ) != pos )
            #else
               if ( lseek( f4->hand, pos, 0 ) != pos )
            #endif
         #endif
/* */
   #endif
   {
      #ifdef S4MULTI_THREAD
         LeaveCriticalSection( &f4->critical4file ) ;
      #endif
      return error4describe( f4->codeBase, e4write, E90615, f4->name, 0, 0 ) ;
   }

   #ifdef S4WIN32
      WriteFile( (HANDLE)f4->hand, ptr, len, (unsigned long *)&urc, NULL ) ;
   #else
/* */
/* */
/* */
/* */
/* */
/* */
         #ifdef S4WINDOWS
            urc = (unsigned)_lwrite( f4->hand, (char *) ptr, len ) ;
         #else
            urc = (unsigned)write( f4->hand, ptr, len ) ;
         #endif
/* */
   #endif

   #ifdef S4MULTI_THREAD
      LeaveCriticalSection( &f4->critical4file ) ;
   #endif

   return urc ;
}

#ifdef P4ARGS_USED
   #pragma argsused
#endif
int file4writeLow( FILE4 *f4, const long pos, const void *ptr, const unsigned len, const int checkDelayList, const int checkAdvanceList )
{
   unsigned urc ;
   CODE4 *c4 ;
   #ifdef S4WRITE_DELAY
      FILE4WRITE_DELAY *writeDelay ;
      LINK4 *delayLink ;
   #endif

   c4 = f4->codeBase ;

   if ( f4->isReadOnly )
      return error4( c4, e4write, E80606 ) ;

   #ifdef S4ADVANCE_READ
      /* take care of over-writing advance-read buffers where appropriate */
      if ( c4->advanceReadsEnabled )
         file4advanceReadWriteOver( f4, pos, len, ptr, 1 ) ;
   #endif

   #ifdef S4MULTI_THREAD
      EnterCriticalSection( &f4->critical4file ) ;
   #endif

   #ifdef S4WRITE_DELAY
      if ( checkDelayList )  /* check to see if write request covers delayed areas */
         if ( l4numNodes( &f4->delayWriteFileList ) != 0 )
         {
            for ( delayLink = (LINK4 *)l4first( &f4->delayWriteFileList ) ;; )
            {
               if ( delayLink == 0 )
                  break ;
               writeDelay = (FILE4WRITE_DELAY *)(delayLink - 1 ) ;
               delayLink = (LINK4 *)l4next( &f4->delayWriteFileList, delayLink ) ;

               if ( pos >= ( writeDelay->pos + (long)writeDelay->len ) )  /* outside of block */
                  continue ;
               if ( ( pos + (long)len ) <= writeDelay->pos )  /* outside of block */
                  continue ;

               /* now, if the delay piece belongs in the buffer, then read all
                  the info before the delay piece, copy the delay piece over,
                  and read all the info after the delay piece */
               while ( writeDelay->usageFlag == r4inUse )  /* is being written to disk, just wait until it is done... */
                  Sleep( 0 ) ;

               if ( writeDelay->usageFlag == r4finished ) /* is written to disk, so can just ignore */
                  continue ;

               /* if the entire block is within the range, then can just delete
                  it, otherwise must write it now (note that, due to constant
                  contents of the delay-write buffer, cannot copy into it
                  directly */

               if ( ( writeDelay->pos >= pos ) && ( pos + len >= writeDelay->pos + writeDelay->len ) ) /* remove */
                  writeDelay->status = 0 ;
               else /* otherwise must write the block now, and then continue */
                  writeDelay->status = file4writeLow( writeDelay->file, writeDelay->pos, writeDelay->data, writeDelay->len, 0, 1 ) ;

               writeDelay->usageFlag = r4finished ;  /* outside of critical section, to allow a wait for completion while keeping the critical section */
               l4remove( &c4->delayWriteList, writeDelay ) ;
               l4remove( &f4->delayWriteFileList, &writeDelay->fileLink ) ;
               writeDelay->completionRoutine( writeDelay ) ;
               mem4free( c4->delayWriteMemory, writeDelay ) ;
            }
         }
   #endif

   urc = file4writeLowDo( f4, pos, ptr, len ) ;

   #ifdef S4MULTI_THREAD
      LeaveCriticalSection( &f4->critical4file ) ;
   #endif

   if ( urc != len )
      return error4describe( c4, e4write, E90615, f4->name, 0, 0 ) ;

   #ifndef S4OFF_MULTI
      if ( f4->codeBase->fileFlush != 0 )
         file4flush( f4 ) ;
   #endif

   return 0 ;
}

#ifndef S4OFF_OPTIMIZE
#ifdef P4ARGS_USED
   #pragma argsused
#endif
#ifdef S4WRITE_DELAY
int file4writeOpt( FILE4 *f4, const long pos, const void *ptr, const unsigned len, int doDelay, S4DELAY_FUNCTION *completionRoutine, void *completionData )
#else
int file4writeOpt( FILE4 *f4, const long pos, const void *ptr, const unsigned len, int doDelay, void *completionRoutine, void *completionData )
#endif
{
   int rc ;
   CODE4 *c4 ;
   #ifndef S4OFF_OPTIMIZE
      unsigned urc ;
      #ifdef E4ANALYZE_ALL
         char buf[512] ;
         long bufWritePos, bufWriteLen ;
      #endif
      #ifdef S4OPTIMIZE_STATS
         DATA4 *stat ;
      #endif
   #endif

   c4 = f4->codeBase ;
   rc = 0 ;

   if ( f4->doBuffer )
   {
      #ifdef S4OPTIMIZE_STATS
         stat = c4->statusDbf ;
         if ( stat != 0 )  /* track stats */
         {
            if ( f4 != &stat->dataFile->file )  /* don't do for the stat file! */
            {
               if ( d4appendStart( stat, 0 ) == 0 )
               {
                  f4assignChar( c4->typeFld, 'W' ) ;  /* high-level */
                  f4assign( c4->fileNameFld, f4->name ) ;
                  f4assignLong( c4->offsetFld, pos ) ;
                  f4assignLong( c4->lengthFld, len ) ;
                  d4append( stat ) ;
               }
            }
         }
      #endif

      urc = (unsigned int)opt4fileWrite( f4, pos, len, ptr, f4->bufferWrites ) ;
      if ( urc != len )
         return error4describe( c4, e4write, E90619, f4->name, 0, 0 ) ;
   }

   if ( f4->doBuffer == 0 || f4->writeBuffer == 0 || f4->bufferWrites == 0 )
   {
      if ( f4->fileCreated == 0 )
      {
         c4->opt.forceCurrent = 1 ;
         #ifdef S4CB51
            file4temp( f4, c4, (char *)f4->name, 1 );
         #else
            file4tempLow( f4, c4, 1 ) ;
         #endif
         c4->opt.forceCurrent = 0 ;
      }

      #ifdef E4ANALYZE_ALL
         if ( f4->hasDup == 1 )
         {
            if ( file4len( f4 ) < pos )
            {
               /* need to null out any extra file contents in order for file verification for optimization
                  to work properly */
               memset( buf, 0, sizeof( buf ) ) ;
               while ( file4len( f4 ) < pos )
               {
                  bufWritePos = file4len( f4 ) ;
                  bufWriteLen = (pos - bufWritePos) ;
                  if ( bufWriteLen > (long)sizeof( buf ) )
                     bufWriteLen = (long)sizeof( buf ) ;
                  if ( file4write( f4, bufWritePos, buf, (int)bufWriteLen ) != 0 )
                     break ;
               }
            }
         }
      #endif  /* E4ANALYZE_ALL */

      #ifdef S4OPTIMIZE_STATS
         stat = c4->statusDbf ;
         if ( stat != 0 )  /* track stats */
         {
            if ( f4 != &stat->dataFile->file )  /* don't do for the stat file! */
            {
               if ( d4appendStart( stat, 0 ) == 0 )
               {
                  f4assignChar( c4->typeFld, 'X' ) ;  /* low-level */
                  f4assign( c4->fileNameFld, f4->name ) ;
                  f4assignLong( c4->offsetFld, pos ) ;
                  f4assignLong( c4->lengthFld, len ) ;
                  d4append( stat ) ;
               }
            }
         }
      #endif
      #ifdef S4WRITE_DELAY
         if ( doDelay == 1 )
            rc = file4writeDelay( f4, pos, ptr, len, completionRoutine, completionData ) ;
         else
      #endif
         rc = file4writeLow( f4, pos, ptr, len, 1, 1 ) ;
   }

   #ifdef E4ANALYZE_ALL
      if ( f4->hasDup == 1 )
         if ( f4->doBuffer == 1 || f4->link.n == 0 )
            if ( file4writePart( ptr, f4, pos, urc ) != 0 )
               return error4( c4, e4opt, E80602 ) ;
   #endif

   return rc ;
}
#endif /* S4OFF_OPTIMIZE */

int S4FUNCTION file4write( FILE4 *f4, const long pos, const void *ptr, const unsigned len )
{
   CODE4 *c4 ;

   #ifdef E4PARM_HIGH
      if ( f4 == 0 )
         return error4( 0, e4parm_null, E90619 ) ;
      if ( pos < 0 || ( ptr == 0 && len ) )
         return error4( f4->codeBase, e4parm, E90619 ) ;
      if ( f4->hand < 0 )
         return error4( f4->codeBase, e4parm, E90619 ) ;
   #endif

   c4 = f4->codeBase ;

   if ( error4code( c4 ) < 0 )
      return e4codeBase ;

   if ( f4->isReadOnly )
      return error4( c4, e4write, E80606 ) ;

   #ifndef S4OFF_OPTIMIZE
      #ifdef S4ADVANCE_READ
         /* take care of over-writing advance-read buffers where appropriate */
         if ( c4->advanceReadsEnabled )
            file4advanceReadWriteOver( f4, pos, len, ptr, 1 ) ;
      #endif
      return file4writeOpt( f4, pos, ptr, len, 0, 0, 0 ) ;
   #else
      /* file4writeLow() itself takes care of the advance-read condition... */
      return file4writeLow( f4, pos, ptr, len, 1, 0 ) ;
   #endif
}

#ifdef S4WRITE_DELAY

/* f4writed.c (c)Copyright Sequiter Software Inc., 1988-1996.  All rights reserved. */

#define MEM4DELAY_START 10
#define MEM4DELAY_EXPAND 10

int file4writeDelay( FILE4 *f4, const long pos, const void *data, const unsigned int len, S4DELAY_FUNCTION *completionRoutine, void *completionData )
{
   FILE4WRITE_DELAY *writeDelay ;
   LINK4 *delayLink ;
   CODE4 *c4 ;
   int rc ;

   c4 = f4->codeBase ;

   /* first check to see if the current delay-write over-rides an existing one */
   if ( l4numNodes( &f4->delayWriteFileList ) != 0 )
   {
      EnterCriticalSection( &c4->critical4delayWriteList ) ;

      for ( delayLink = (LINK4 *)l4first( &f4->delayWriteFileList ) ;; )
      {
         if ( delayLink == 0 )
            break ;
         writeDelay = (FILE4WRITE_DELAY *)(delayLink - 1 ) ;
         delayLink = (LINK4 *)l4next( &f4->delayWriteFileList, delayLink ) ;

         if ( pos >= ( writeDelay->pos + (long)writeDelay->len ) )  /* outside of block */
            continue ;
         if ( ( pos + (long)len ) <= writeDelay->pos )  /* outside of block */
            continue ;

         while ( writeDelay->usageFlag == r4inUse )  /* is being written to disk, just wait until it is done... */
            Sleep( 0 ) ;

         if ( writeDelay->usageFlag == r4finished ) /* is written to disk, so can just ignore */
            continue ;

         /* we have a delay-write which overlaps the current request */

         /* if the entire block is within the range, then can just delete
            it, otherwise must write it now (note that, due to constant
            contents of the delay-write buffer, cannot copy into it
            directly */

         if ( ( writeDelay->pos >= pos ) && ( pos + len >= writeDelay->pos + writeDelay->len ) ) /* remove */
            writeDelay->status = 0 ;
         else /* otherwise must write the block now, and then continue */
            writeDelay->status = file4writeLow( writeDelay->file, writeDelay->pos, writeDelay->data, writeDelay->len, 0, 1 ) ;

         writeDelay->usageFlag = r4finished ;  /* outside of critical section, to allow a wait for completion while keeping the critical section */
         l4remove( &f4->codeBase->delayWriteList, writeDelay ) ;
         l4remove( &f4->delayWriteFileList, &writeDelay->fileLink ) ;
         writeDelay->completionRoutine( writeDelay ) ;
         mem4free( c4->delayWriteMemory, writeDelay ) ;
      }
      LeaveCriticalSection( &c4->critical4delayWriteList ) ;
   }

   if ( c4->delayWriteMemory == 0 )
      writeDelay = (FILE4WRITE_DELAY *)mem4createAlloc( c4, &c4->delayWriteMemory, MEM4DELAY_START, sizeof( FILE4WRITE_DELAY ), MEM4DELAY_EXPAND, 0 ) ;
   else
      writeDelay = (FILE4WRITE_DELAY *)mem4alloc( c4->delayWriteMemory ) ;

   if ( writeDelay == 0 )
      return error4( c4, e4memory, E90624 ) ;

   writeDelay->file = f4 ;
   writeDelay->data = (const char *)data ;
   writeDelay->len = len ;
   writeDelay->pos = pos ;
   writeDelay->usageFlag = r4queued ;
   writeDelay->completionRoutine = completionRoutine ;
   writeDelay->completionData = completionData ;

   if ( c4->delayWritesEnabled == 0 )   /* delay-writes not enabled, so just write */
   {
      rc = file4writeLow( f4, pos, data, len, 1, 1 ) ;
      completionRoutine( writeDelay ) ;
      return rc ;
   }

   EnterCriticalSection( &c4->critical4delayWriteList ) ;

   l4add( &c4->delayWriteList, writeDelay ) ;
   l4add( &f4->delayWriteFileList, &writeDelay->fileLink ) ;

   LeaveCriticalSection( &c4->critical4delayWriteList ) ;

   SetEvent( c4->pendingWriteEvent ) ;  /* notify the write thread */
   Sleep( 0 ) ;

   return 0 ;
}

/* flush out any delayed-writes for the file in question */
/* if doWrite is false, then the blocks are just dumped (eg. temp. files) */
int file4writeDelayFlush( FILE4 *file, const int doWrite )
{
   FILE4WRITE_DELAY *writeDelay ;
   LINK4 *writeDelayLink, *saved ;
   CODE4 *c4 ;

   c4 = file->codeBase ;

   /* by obtaining the critical4delayWriteList critical section, we can
      guarantee that the other thread will be suspended.  Therefore
      the flushes for this file will get high-priority treatment, which
      is what is desired
   */

   EnterCriticalSection( &c4->critical4delayWriteList ) ;

   /* go through the list and manually flush each one belonging to our file */

   for ( writeDelayLink = (LINK4 *)l4first( &file->delayWriteFileList ) ;; )
   {
      if ( writeDelayLink == 0 )
         break ;
      writeDelay = (FILE4WRITE_DELAY *)(writeDelayLink - 1) ;
      saved = (LINK4 *)l4next( &file->delayWriteFileList, writeDelayLink ) ;
      if ( writeDelay->usageFlag == r4queued )  /* do ourselves */
      {
         l4remove( &file->delayWriteFileList, writeDelayLink ) ;
         l4remove( &c4->delayWriteList, writeDelay ) ;
         if ( doWrite == 1 )
            writeDelay->status = file4writeLow( writeDelay->file, writeDelay->pos, writeDelay->data, writeDelay->len, 0, 1 ) ;
         else
            writeDelay->status = 0 ;
         writeDelay->usageFlag = r4finished ;
         writeDelay->completionRoutine( writeDelay ) ;
         mem4free( c4->delayWriteMemory, writeDelay ) ;
      }
      writeDelayLink = saved ;
   }

   LeaveCriticalSection( &c4->critical4delayWriteList ) ;

   for ( ;; )
   {
      /* now verify that the checkInUse write gets completed */
      if ( l4numNodes( &file->delayWriteFileList ) == 0 )
         break ;
      #ifdef E4ANALYZE
         if ( l4numNodes( &file->delayWriteFileList ) > 1 )   /* in theory impossible, it means delay-write has 2 files writing at same time */
            return error4( c4, e4struct, E90624 ) ;
      #endif
      SetEvent( c4->pendingWriteEvent ) ;  /* notify the write thread */
      Sleep( 0 ) ;   /* give up our time slice to get the delay-write going */
   }

   return 0 ;
}

/* main function for delay-write thread (i.e. paramater to CreateThread()) */
#ifdef S4USE_INT_DELAY
   int file4writeDelayMain( void *data )
#else
   void file4writeDelayMain( void *data )
#endif
{
   CODE4 *c4 ;
   FILE4WRITE_DELAY *writeDelay ;

   c4 = (CODE4 *)data ;
   c4->delayWritesEnabled = 1 ;

   for ( ;; )
   {
      if ( l4numNodes( &c4->delayWriteList ) == 0 )
      {
         if ( c4->uninitializeDelayWrite == 1 )   /* shutdown */
         {
            SetEvent( c4->initUndoDelayWrite ) ;
            #ifdef S4USE_INT_DELAY
               return 0 ;
            #else
               return ;
            #endif
         }
         else
         {
            WaitForSingleObject( c4->pendingWriteEvent, INFINITE ) ;
            ResetEvent( c4->pendingWriteEvent ) ;
         }
      }
      else  /* perform a write on the first available block */
      {
         EnterCriticalSection( &c4->critical4delayWriteList ) ;
         writeDelay = (FILE4WRITE_DELAY *)l4first( &c4->delayWriteList ) ;
         if ( writeDelay == 0 )   /* maybe got removed by main thread, so none to write... */
         {
            LeaveCriticalSection( &c4->critical4delayWriteList ) ;
            Sleep( 0 ) ;
            continue ;
         }
         writeDelay->usageFlag = r4inUse ;
         LeaveCriticalSection( &c4->critical4delayWriteList ) ;

         writeDelay->status = file4writeLow( writeDelay->file, writeDelay->pos, writeDelay->data, writeDelay->len, 0, 1 ) ;
         writeDelay->usageFlag = r4finished ;  /* outside of critical section, to allow a wait for completion while keeping the critical section */
         EnterCriticalSection( &c4->critical4delayWriteList ) ;
         l4remove( &c4->delayWriteList, writeDelay ) ;
         l4remove( &writeDelay->file->delayWriteFileList, &writeDelay->fileLink ) ;
         LeaveCriticalSection( &c4->critical4delayWriteList ) ;
         writeDelay->completionRoutine( writeDelay ) ;
         mem4free( c4->delayWriteMemory, writeDelay ) ;
      }
   }
}

#endif /* S4WRITE_DELAY */