/* cache.c */

/*#define NOCACHE*/
/*#define NOWRITEBEHIND*/

#include "chunks.h"
#include "debug.h"
#include "module.h"
#include "swis.h"
#include <stdlib.h>
#include <string.h>

static int xFiles__timer;

#define xFiles_CACHECHUNKSIZE     1024
#define xFiles_OK2CACHE           4096

#define roundDown(x) ((x) & ~(xFiles_CACHECHUNKSIZE-1))
#define roundUp(x)   roundDown((x) + xFiles_CACHECHUNKSIZE - 1)

typedef struct
{
   int prev, next;    /* Index into the array of cache chunks or -1 for NULL */
   unsigned owner;    /* (unsigned) pInfo for this chunk or 0 if unused      */
   unsigned offset;   /* Offset in file where it started                     */
   char     *buffer;
   BOOL     dirty;

} xFiles_cacheItem;

static xFiles_cacheItem *cache;
static char             *buffer;
static int               cacheHead, cacheTail;
static int               cacheSize, cacheUsed; /* elements that is */

void *xFiles_windowBuffer;    /* shared by all and sundry */
int   xFiles_windowBufferSize;
static BOOL  xFiles__bufferClaimed;

#define _max(x, y) ((x) > (y) ? (x) : (y))
#define _min(x, y) ((x) < (y) ? (x) : (y))

static void xFiles__disposeCache(void)
{
   if (cache)
   {
      free(cache);
      cache = NULL;
   }

   if (buffer)
   {
      free(buffer);
      buffer = NULL;
   }
}

void xFiles_Tidy(void)
{
   if (xFiles_windowBuffer)
   {
      free(xFiles_windowBuffer);
      xFiles_windowBuffer = NULL;
   }

   xFiles__disposeCache();
}

_kernel_oserror *xFiles_releaseBuffer(void)
{
   xFiles__bufferClaimed = FALSE;

   return NULL;
}

_kernel_oserror *xFiles_claimBuffer(int notBiggerThan)
{
   (void) notBiggerThan;

   if (xFiles__bufferClaimed)
      return &xFiles_BufferInUse;

   xFiles__bufferClaimed = TRUE;

   if (xFiles_windowBuffer)
      return NULL;

   if (xFiles_windowBuffer = malloc(xFiles_WINDOWSIZE), !xFiles_windowBuffer)
      return &xFiles_NoMemory;

   xFiles_windowBufferSize = xFiles_WINDOWSIZE;  

   return NULL;
}

static void xFiles_RemoveCacheItem(int item)
{
   if (cache[item].prev != -1)
      cache[cache[item].prev].next = cache[item].next;
   else
      cacheHead = cache[item].next;

   if (cache[item].next != -1)
      cache[cache[item].next].prev = cache[item].prev;
   else
      cacheTail = cache[item].prev;

   cache[item].next =
   cache[item].prev = -1;
}

static void xFiles_CacheAddAtHead(int item)
{
   if (cacheHead != -1)
      cache[cacheHead].prev = item;
   else
      cacheTail = item;

   cache[item].prev = -1;
   cache[item].next = cacheHead;
   cacheHead = item;
}

static void xFiles_CacheAddAtTail(int item)
{
   if (cacheTail != -1)
      cache[cacheTail].next = item;
   else
      cacheHead = item;

   cache[item].next = -1;
   cache[item].prev = cacheTail;
   cacheTail = item;
}

_kernel_oserror *xFiles_getLength(xFiles_info *pInfo, unsigned *pLength)
{
   _kernel_swi_regs regs;
   _kernel_oserror *err;

   if (pInfo->fileSize == (unsigned) -1)
   {
      regs.r[0] = 2;
      regs.r[1] = pInfo->fileHandle;
   
      if (err = _kernel_swi(OS_Args, &regs, &regs), err)
         return err;
   
      pInfo->fileSize = (unsigned) regs.r[2];
   }

   if (pLength) *pLength = pInfo->fileSize;
   
   return NULL;
}

_kernel_oserror *xFiles_setLength(xFiles_info *pInfo, unsigned Length)
{
   _kernel_swi_regs regs;
   _kernel_oserror *err;
   int i;
   unsigned owner = (unsigned) pInfo;
   unsigned oldLength;

   /*TRACE("xFiles_setLength(%p, %08x)\n", pInfo, Length);*/

   if (err = xFiles_getLength(pInfo, &oldLength), err)
      return err;

   /* If the file is shrinking we need to discard any items which are cached
    * for the bit of file which no longer exists, otherwise when the file
    * grows again these bits will be mistaken for valid bits of file.
    */   

   if (Length < oldLength)
   {
      for (i = 0; i < cacheUsed; i++)
      {
         if (cache[i].owner == owner && cache[i].offset >= Length)
         {
            cache[i].offset = 0xFFFFFFFF;
            cache[i].dirty  = FALSE;
            xFiles_RemoveCacheItem(i);
            xFiles_CacheAddAtTail(i);
         }

         if (cache[i].offset == 0xFFFFFFFF)
            cache[i].owner  = i > 0 ? cache[i-1].owner + 1 : 0;
      }
   }
   
   regs.r[0] = 3;
   regs.r[1] = pInfo->fileHandle;
   regs.r[2] = (int) Length;

   if (err = _kernel_swi(OS_Args, &regs, &regs), err)
      return err;

   pInfo->fileSize = Length;
   return NULL;
}

/* Read a glob of the file into the specified buffer.
 * This is about as primitive as it gets.
 */

static _kernel_oserror *xFiles_rawRead(xFiles_info *pInfo, void *pBuffer, unsigned pos, unsigned size)
{
   _kernel_swi_regs regs;
   _kernel_oserror *err;

   /*TRACE("  xFiles_rawRead(%p, %p, %08x, %08x)\n", pInfo, pBuffer, pos, size);*/

   regs.r[0] = 3;  /* read bytes given pointer */
   regs.r[1] = pInfo->fileHandle;
   regs.r[2] = (int) pBuffer;
   regs.r[3] = size;
   regs.r[4] = pos;

   if (err = _kernel_swi(OS_GBPB, &regs, &regs), err)
      return err;

   if (regs.r[3] > 0)
   {
      TRACE("*** %d bytes not read\n", regs.r[3]);
   }

   return NULL;
}

static _kernel_oserror *xFiles_rawWrite(xFiles_info *pInfo, void *pBuffer, unsigned pos, unsigned size)
{
   _kernel_swi_regs regs;

   /*TRACE("  xFiles_rawWrite(%p, %p, %08x, %08x)\n", pInfo, pBuffer, pos, size);*/

   regs.r[0] = 1;  /* write bytes given pointer */
   regs.r[1] = pInfo->fileHandle;
   regs.r[2] = (int) pBuffer;
   regs.r[3] = size;
   regs.r[4] = pos;

   return _kernel_swi(OS_GBPB, &regs, &regs);
}

static void xFiles_EmptyCache()
{
   int i;
   char *bp;

   cacheHead =
   cacheTail = -1;

   /* Offsets within the buffer are statically associated with particular
    * index entries, so this relationship is established here.
    */
    
   for (i = 0, bp = buffer; i < cacheSize; i++, bp += xFiles_CACHECHUNKSIZE)
   {
      cache[i].prev =
      cache[i].next = -1;
      cache[i].owner = 0;
      cache[i].offset = 0; 
      cache[i].buffer = bp;
      cache[i].dirty  = FALSE;
   }

   cacheUsed = 0;
}

static int xFiles_CacheCmp(unsigned owner, unsigned offset, int item)
{
   return (owner == cache[item].owner) ? (offset - cache[item].offset)
                                       : (owner  - cache[item].owner);
}

static int xFiles_CacheLookup(xFiles_info *pInfo, unsigned offset)
{
   unsigned owner = (unsigned) pInfo;
   int lo, hi, mid, cmp;

   for (lo = 0, hi = cacheUsed-1; lo <= hi; )
   {
      mid = (lo + hi) / 2;
      cmp = xFiles_CacheCmp(owner, offset, mid);
      
      if (cmp < 0)
         hi = mid - 1;
      else if (cmp > 0)
         lo = mid + 1;
      else
         return mid;
   }

   return -1;
}


_kernel_oserror *xFiles_InitCache(unsigned size)
{
#ifdef NOCACHE
   (void) size;
#else
   cacheSize = size / xFiles_CACHECHUNKSIZE;

   xFiles__disposeCache();

   xFiles__timer = 0;

   if (cacheSize == 0)
   {
      cache = NULL;
      return NULL;
   }

   if (cache = malloc(sizeof(xFiles_cacheItem) * cacheSize), !cache)
      goto fail;

   if (buffer = malloc(xFiles_CACHECHUNKSIZE * cacheSize), !buffer)
      goto fail;

   xFiles_EmptyCache();
   
   return NULL;

fail:
   xFiles__disposeCache();
   cacheSize = 0;
   TRACE("Not enough space for cache; running with cache disabled");
#endif
   return NULL;
}

_kernel_oserror *xFiles_FlushFileInfo(xFiles_info *pInfo)
{
   int i;
   unsigned owner = (unsigned) pInfo;
   _kernel_oserror *err;

   if (err = xFiles_Flush(), err)
      return err;

   for (i = 0; i < cacheUsed; i++)
   {
      if (cache[i].owner == owner)
      {
         cache[i].offset = 0xFFFFFFFF;
         cache[i].dirty  = FALSE;
         xFiles_RemoveCacheItem(i);
         xFiles_CacheAddAtTail(i);
      }

      if (cache[i].offset == 0xFFFFFFFF)
         cache[i].owner = i > 0 ? cache[i-1].owner + 1 : 0;
   }
   
   return NULL;
}

static void xFiles_AdjustCache(int item, int by)
{
   int i;

   ASSERT(item != -1);

   for (i = 0; i < cacheUsed; i++)
   {
      ASSERT(by > 0 || cache[i].prev != item);
      ASSERT(by > 0 || cache[i].next != item);

      if (cache[i].prev >= item)
         cache[i].prev += by;
      if (cache[i].next >= item)
         cache[i].next += by;
   }

   ASSERT(by > 0 || cacheHead != item);
   ASSERT(by > 0 || cacheTail != item);

   if (cacheHead >= item)
      cacheHead += by;
   if (cacheTail >= item)
      cacheTail += by;
}

static void xFiles_CacheCheck(void)
{
   int prev, i, watchDog;
   int broken = 0;
   xFiles_info *pInfo;

   if (!cache)
      return;

   if (cacheUsed > cacheSize)
      goto trash;

   prev = -1;
   i = cacheHead;
   watchDog = cacheUsed+1;

   while (watchDog > 0 && i != -1)
   {
      if (cache[i].prev != prev)
      {
         TRACE("Cache back link is %d (should be %d)\n", cache[i].prev, prev);
         broken++;
      }

      prev = i;
      i = cache[i].next;
      watchDog--;
   }

   if (watchDog == 0)
   {
      TRACE("Loop in cache links\n");
      broken++;
   }
   else if (cacheTail != prev)
   {
      TRACE("cacheTail should is %d (should be %d)\n", cacheTail, prev);
      broken++;
   }

   for (i = 0; i < cacheUsed-1; i++)
   {
      int cmp = xFiles_CacheCmp(cache[i].owner, cache[i].offset, i+1);

      if (cmp >= 0)
      {
         TRACE("Cache items are not ordered/unique\n");
         broken++;
         break;
      }

      pInfo = (xFiles_info *) cache[i].owner;
      if (cache[i].offset != 0xFFFFFFFF && cache[i].offset >= pInfo->fileSize)
      {
         TRACE("Cache item outside file's length (offset = %08x, fileSize = %08x)\n",
                      cache[i].offset, pInfo->fileSize);
         broken++;
         break;
      }
   }

   if (broken == 0)
      return;

trash:
   TRACE("Cache is broken: discarding it\n");

   TRACE("Head: %d, Tail: %d, Size: %d, Used: %d\n", cacheHead, cacheTail, cacheSize, cacheUsed);
   for (i = 0; i < _min(cacheUsed, cacheSize); i++)
   {
      TRACE("%3d: %08x, %08x, %3d, %3d\n",
                  i, cache[i].owner, cache[i].offset, cache[i].prev, cache[i].next);
   }

   cacheUsed = 0;
   cacheHead = cacheTail = -1;
}

/* Ensure the chunk with the specified offset is in the cache, returning it's index.
 * If the chunk isn't in the cache it will be loaded from the file replacing the
 * least recently used item in the cache.
 */

static _kernel_oserror *xFiles_CacheEnsure(xFiles_info *pInfo, unsigned offset, int *pItem, BOOL noRead)
{
   int item;
   unsigned owner = (unsigned) pInfo;
   _kernel_oserror *err;
   xFiles_cacheItem tmp;

   ASSERT(offset == roundDown(offset));
   ASSERT(cacheUsed <= cacheSize);

   if (item = xFiles_CacheLookup(pInfo, offset), item != -1)
   {
      /*TRACE("%p, %08x -- cache hit\n", pInfo, offset);*/
      xFiles_RemoveCacheItem(item);
      xFiles_CacheAddAtHead(item);
      *pItem = item;
      return NULL;
   }

   /*TRACE("%p, %08x -- cache miss\n", pInfo, offset);*/

   /* If the cache is full get rid of a chunk. What actually happens is that the chunk to
    * be discarded is moved up to the end of the cache. We can't actually throw it away
    * because each chunk refers to its own bit of buffer and that only gets allocated once.
    */

   if (cacheUsed == cacheSize)
   {
      item = cacheTail;

      ASSERT(cacheTail != -1);

      if (cache[item].dirty)
      {
         if (cache[item].offset != 0xFFFFFFFF)
         {
            /*TRACE("Flush(1) %08x, %08x, %08x\n",
                         cache[item].owner, cache[item].buffer, cache[item].offset);*/
   
            if (err = xFiles_rawWrite((xFiles_info *) cache[item].owner,
                                      cache[item].buffer, cache[item].offset,
                                      xFiles_CACHECHUNKSIZE), err)
               return err;
         }
         
         cache[item].dirty = FALSE;
      }

      tmp = cache[item];  /* save it */

      /*TRACE("  discarding item %d (%08x, %08x)\n",
               item, cache[item].owner, cache[item].offset);*/

      xFiles_RemoveCacheItem(item);  /* remove from the linked list */
      cacheUsed--;
      memmove(&cache[item], &cache[item+1], sizeof(xFiles_cacheItem) * (cacheUsed - item));
      xFiles_AdjustCache(item, -1);
      cache[cacheUsed] = tmp;
   }

   for (item = 0; item < cacheUsed && xFiles_CacheCmp(owner, offset, item) > 0; item++)
      ;

   /*TRACE("  inserting as item %d\n", item);*/

   tmp = cache[cacheUsed];   /* Get the unused one at the end */
   if (item < cacheUsed)
      memmove(&cache[item+1], &cache[item], sizeof(xFiles_cacheItem) * (cacheUsed - item));
   cacheUsed++;
   xFiles_AdjustCache(item, 1);
   cache[item] = tmp;
   xFiles_CacheAddAtHead(item);
   cache[item].owner = owner;
   cache[item].offset = offset;
   cache[item].dirty = FALSE;

   if (!(item <= 0 || xFiles_CacheCmp(cache[item-1].owner, cache[item-1].offset, item) < 0) ||
       !(item >= cacheUsed-1 || xFiles_CacheCmp(cache[item+1].owner, cache[item+1].offset, item) > 0))
   {
      unsigned i;

      for (i = 0; i < cacheUsed; i++)
      {
         TRACE("%3d: %08x, %08x, %3d, %3d%s\n", i, cache[item].owner, cache[item].offset,
                                                   cache[item].prev, cache[item].next,
                                                   i == item ? " *" : "");
      }

      TRACE("xFiles_CacheEnsure(%p, %08x, %p, %d)\n", pInfo, offset, pItem, noRead);
      TRACE("Destroying cache\n");

      cacheUsed = 1;    /* just destroy it */
      cacheHead = cacheTail = 0;
      cache[0].prev = cache[0].next = -1;
      item = 0;
   }

   *pItem = item;

   if (noRead)
      return NULL;

   return xFiles_rawRead(pInfo, cache[item].buffer, offset, xFiles_CACHECHUNKSIZE);
}

_kernel_oserror *xFiles_read(xFiles_info *pInfo, void *pBuffer, unsigned pos, unsigned size)
{

   _kernel_oserror *err;
   int item;
   unsigned start, end;
   unsigned bufPos;
   char *outPtr;
   unsigned fragSize, totalSize;
   unsigned pendingStart, pendingSize;

#ifndef NOCACHE
   if (!cache)
#endif
      return xFiles_rawRead(pInfo, pBuffer, pos, size);

   xFiles_CacheCheck();

   /*TRACE("xFiles_read(%p, %p, %08x, %08x)\n", pInfo, pBuffer, pos, size);*/

   if (size == 0)
      return NULL;

   /* Set about reading it through the cache */

   start  = roundDown(pos);
   end    = roundUp(pos + size);
   bufPos = pos - start;
   outPtr = (char *) pBuffer;
   totalSize = size;

   ASSERT(start < end);
   ASSERT(bufPos < xFiles_CACHECHUNKSIZE);

   if (size > xFiles_OK2CACHE)
   {
      /* Large transfer: check each component block for a cache hit */

      pendingStart = pos;
      pendingSize  = 0;

      while (start < end)
      {
         item = xFiles_CacheLookup(pInfo, start);
   
         fragSize = _min(xFiles_CACHECHUNKSIZE - bufPos, totalSize);
   
         if (item != -1)
         {
            if (pendingSize != 0)
            {
               if (err = xFiles_rawRead(pInfo, outPtr, pendingStart, pendingSize), err)
                  return err;

               pendingStart += pendingSize;
               outPtr += pendingSize;
               pendingSize = 0;
            }

            ASSERT(cache[item].buffer >= buffer);
            ASSERT(cache[item].buffer < buffer + xFiles_CACHECHUNKSIZE * cacheSize);
            memcpy(outPtr, cache[item].buffer + bufPos, fragSize);
            outPtr += fragSize;
            pendingStart += fragSize;
         }
         else
            pendingSize += fragSize;
   
         totalSize -= fragSize;
         start     += xFiles_CACHECHUNKSIZE;
         bufPos = 0;
      }

      if (pendingSize != 0)
      {
         if (err = xFiles_rawRead(pInfo, outPtr, pendingStart, pendingSize), err)
            return err;
      }
   }
   else
   {
      while (start < end)
      {
         /*TRACE("   reading chunk at %08x through cache\n", start);*/
   
         if (err = xFiles_CacheEnsure(pInfo, start, &item, FALSE), err)
            return err;
   
         fragSize = _min(xFiles_CACHECHUNKSIZE - bufPos, totalSize);
   
         ASSERT(outPtr >= (char *) pBuffer);
         ASSERT(outPtr + fragSize <= (char *) pBuffer + size);
   
         memcpy(outPtr, cache[item].buffer + bufPos, fragSize);
   
         outPtr += fragSize;
         totalSize -= fragSize;
         start  += xFiles_CACHECHUNKSIZE;
         bufPos = 0;
     }
   }

   return NULL;
}

_kernel_oserror *xFiles_write(xFiles_info *pInfo, void *pBuffer, unsigned pos, unsigned size)
{
   int item;
   unsigned start, end;
   unsigned bufPos;
   char *outPtr;
   unsigned fragSize;
   unsigned totalSize;
   unsigned fileSize;
   BOOL noRead;
   _kernel_oserror *err;
   unsigned pendingStart, pendingSize;

   xFiles_CacheCheck();

   /*TRACE("xFiles_write(%p, %p, %08x, %08x)\n", pInfo, pBuffer, pos, size);*/

   if (size == 0)
      return NULL;

#ifndef NOCACHE
   if (!cache)
#endif   
      return xFiles_rawWrite(pInfo, pBuffer, pos, size);

   start  = roundDown(pos);
   end    = roundUp(pos + size);
   bufPos = pos - start;
   outPtr = (char *) pBuffer;
   totalSize = size;
   ASSERT(start < end);
   ASSERT(bufPos < xFiles_CACHECHUNKSIZE);

   if (size > xFiles_OK2CACHE)
   {
      /* Large transfer: check each component block for a cache hit and update
       * the cache too if necessary (bus snooping).
       */

      pendingStart = pos;
      pendingSize  = 0;

      while (start < end)
      {
         item = xFiles_CacheLookup(pInfo, start);
   
         fragSize = _min(xFiles_CACHECHUNKSIZE - bufPos, totalSize);
   
         if (item != -1)
         {
            if (pendingSize != 0)
            {
               if (err = xFiles_rawWrite(pInfo, outPtr, pendingStart, pendingSize), err)
                  return err;

               pendingStart += pendingSize;
               outPtr += pendingSize;
               pendingSize = 0;
            }

            ASSERT(cache[item].buffer >= buffer);
            ASSERT(cache[item].buffer < buffer + xFiles_CACHECHUNKSIZE * cacheSize);
            memcpy(cache[item].buffer + bufPos, outPtr, fragSize);
            outPtr += fragSize;
            pendingStart += fragSize;
            cache[item].dirty = TRUE;
         }
         else
            pendingSize += fragSize;
   
         totalSize -= fragSize;
         start     += xFiles_CACHECHUNKSIZE;
         bufPos = 0;
      }

      if (pendingSize != 0)
      {
         if (err = xFiles_rawWrite(pInfo, outPtr, pendingStart, pendingSize), err)
            return err;
      }

      return NULL;
   }
   else
   {
      if (err = xFiles_getLength(pInfo, &fileSize), err)
         return err;

      if (pos + size > fileSize)
      {
         fileSize = xFiles_roundUp(pInfo, pos + size);
         if (err = xFiles_setLength(pInfo, fileSize), err)
            return err;
      }

      /* Small transfer: ensure each component block is cached and write to both
       * the cached block and to disc.
       */

      while (start < end)
      {
         /* Work out whether we need to read before write */
   
         fragSize = _min(xFiles_CACHECHUNKSIZE - bufPos, totalSize);

         noRead = (bufPos == 0 && fragSize == xFiles_CACHECHUNKSIZE);

         /*TRACE("   writing chunk at %08x through cache (fragSize = %08x, noRead = %s)\n",
                   start, fragSize, noRead ? "TRUE" : "FALSE");*/

         if (err = xFiles_CacheEnsure(pInfo, start, &item, noRead), err)
            return err;
   
         ASSERT(outPtr >= (char *) pBuffer);
         ASSERT(outPtr + fragSize <= (char *) pBuffer + size);
   
         memcpy(cache[item].buffer + bufPos, outPtr, fragSize);

#if 1
         cache[item].dirty = TRUE;
#else
         if (err = xFiles_rawWrite(pInfo, cache[item].buffer, cache[item].offset, xFiles_CACHECHUNKSIZE), err)
            return err;
#endif            

         outPtr += fragSize;
         totalSize -= fragSize;
         start  += xFiles_CACHECHUNKSIZE;
         bufPos = 0;
      }
      
      return NULL;
   }
}

_kernel_oserror *xFiles_Commit(xFiles_info *pInfo)
{
   _kernel_oserror *err;

   if (err = xFiles_squashFile(pInfo, FALSE), err)
      return err;

#ifdef NOWRITEBEHIND
   if (err = xFiles_Flush(), err)
      return err;
#else
   xFiles__timer = 200;
#endif   

   return NULL;
}

/* Force the image file to truncate by
 * issuing the appropriate service call
 */

_kernel_oserror *xFiles_Truncate(xFiles_info *pInfo)
{
   _kernel_oserror *err;
   _kernel_swi_regs regs;
   char name[257];

   if (pInfo->openList.head != NULL)
   {
      /*TRACE("Don't want to truncate image which has open files\n");*/
      return NULL;
   }

   regs.r[0] = 7;
   regs.r[1] = pInfo->fileHandle;
   regs.r[2] = (int) name;
   regs.r[5] = 256;

   if (err = _kernel_swi(OS_Args, &regs, &regs), err)
      return err;

   /* Now force the named image to close. This will cause X-Files to reenter, so
    * be ready!
    */

   /*TRACE("Closing %s\n", name);*/

   regs.r[1] = 0x68;
   regs.r[2] = (int) name;
   regs.r[3] = 0;

   if (err = _kernel_swi(OS_ServiceCall, &regs, &regs), err)
      return err;

   return NULL;
}

_kernel_oserror *xFiles_Flush(void)
{
   int i;
   _kernel_oserror *err;
   _kernel_swi_regs regs;
   xFiles_info *pInfo;

   for (i = 0; i < cacheUsed; i++)
   {
      if (cache[i].offset != 0xFFFFFFFF && cache[i].dirty)
      {
         /*TRACE("Flush(2) %08x, %08x, %08x\n",
                      cache[i].owner, cache[i].buffer, cache[i].offset);*/

         if (err = xFiles_rawWrite((xFiles_info *) cache[i].owner,
                                   cache[i].buffer, cache[i].offset,
                                   xFiles_CACHECHUNKSIZE), err)
            return err;

         cache[i].dirty = FALSE;
      }
   }

   for (pInfo = (xFiles_info *) xFiles_openFiles.head; pInfo; pInfo = (xFiles_info *) pInfo->li.next)
   {
      regs.r[0] = 255;
      regs.r[1] = pInfo->fileHandle;
   
      if (err = _kernel_swi(OS_Args, &regs, &regs), err)
         return err;
   }


   return NULL;
}

_kernel_oserror *xFiles_WriteBehind(_kernel_swi_regs *regs)
{
   xFiles_info *pInfo, *pNext;

   (void) regs;

#ifndef NOWRITEBEHIND

   (void) xFiles_Flush();

   for (pInfo = (xFiles_info *) xFiles_openFiles.head; pInfo; pInfo = pNext)
   {
      pNext = (xFiles_info *) pInfo->li.next;

      if (pInfo->flags & xFiles_fNeedTruncate)
      {
         pInfo->flags &= ~xFiles_fNeedTruncate;

         (void) xFiles_Truncate(pInfo);

         /* Beware: pInfo might not be valid now */
      }
   }

#endif
   
   return NULL;
}

extern void entry_WriteBehind(void);

/* Ooh look. How evil. He's sitting on the ticker vector with some C! */

#pragma -s1
_kernel_oserror *xFiles_Ticker(_kernel_swi_regs *regs)
{
   (void) regs;

   if (xFiles__timer > 0 && --xFiles__timer == 0)
   {
      _kernel_swi_regs regs;

      regs.r[0] = (int) entry_WriteBehind;
      regs.r[1] = (int) wsp;

      (void) _kernel_swi(OS_AddCallBack, &regs, &regs);
   }

   return NULL;
}
#pragma -s0
