/* 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;
		}
	}

#ifndef NOWRITEBEHIND
	xFiles__timer = 200;
#endif

	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;
		}
	}
	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;
		}

	}

#ifndef NOWRITEBEHIND
	xFiles__timer = 200;
#endif

	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;
#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);

#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
