/*
 * Memory file system
 * I/O driver for Memory file system
 *
 * Copyright 2009 Mindaugas Kavaliauskas <dbtopas at dbtopas.lt>
 *
 * 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; see the file LICENSE.txt.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA (or visit https://www.gnu.org/licenses/).
 *
 * As a special exception, the Harbour Project gives permission for
 * additional uses of the text contained in its release of Harbour.
 *
 * The exception is that, if you link the Harbour libraries with other
 * files to produce an executable, this does not by itself cause the
 * resulting executable to be covered by the GNU General Public License.
 * Your use of that executable is in no way restricted on account of
 * linking the Harbour library code into it.
 *
 * This exception does not however invalidate any other reasons why
 * the executable file might be covered by the GNU General Public License.
 *
 * This exception applies only to the code released by the Harbour
 * Project under the name Harbour.  If you copy code from other
 * Harbour Project or Free Software Foundation releases into a copy of
 * Harbour, as the General Public License permits, the exception does
 * not apply to the code that you add in this way.  To avoid misleading
 * anyone as to the status of such modified files, you must delete
 * this exception notice from them.
 *
 * If you write modifications of your own for Harbour, it is your choice
 * whether to permit this exception to apply to your modifications.
 * If you do not wish that, delete this exception notice.
 *
 */

/* this has to be declared before hbapifs.h is included */
#define _HB_FILE_IMPLEMENTATION_

#include "hbapi.h"
#include "hbapifs.h"
#include "hbapiitm.h"
#include "hbapierr.h"
#include "hbthread.h"
#include "hbvm.h"
#include "hbinit.h"

#include "directry.ch"

/* -- Memory file system --- */

/* change this define for public hb_memfs*() API */
#ifdef HB_MEMFS_PUBLIC_API
#define HB_MEMFS_EXPORT
#else
#define HB_MEMFS_EXPORT    static
#endif

#define HB_MEMFS_INITSIZE  16

/* File access flags */
#define FOX_READ           1
#define FOX_WRITE          2
#define FOX_READWRITE      3

/* File sharing flags */
#define FOX_DENYNONE       0
#define FOX_DENYREAD       16
#define FOX_DENYWRITE      32
#define FOX_EXCLUSIVE      48
#define FOX_DENYFLAGS      48


typedef struct _HB_MEMFS_INODE
{
   HB_FOFFSET   llSize;
   HB_FOFFSET   llAlloc;
   char *       pData;
   char *       szName;
   unsigned int uiCount;
   unsigned int uiCountRead, uiCountWrite;
   HB_USHORT    uiDeny;
} HB_MEMFS_INODE, * PHB_MEMFS_INODE;


typedef struct _HB_MEMFS_FILE
{
   PHB_MEMFS_INODE pInode;
   HB_FOFFSET      llPos;
   HB_USHORT       uiFlags;
} HB_MEMFS_FILE, * PHB_MEMFS_FILE;


typedef struct _HB_MEMFS_FS
{
   HB_ULONG          ulInodeCount;
   HB_ULONG          ulInodeAlloc;
   PHB_MEMFS_INODE * pInodes;
   HB_ULONG          ulFileAlloc;
   HB_ULONG          ulFileLast;
   PHB_MEMFS_FILE *  pFiles;
} HB_MEMFS_FS, * PHB_MEMFS_FS;

typedef struct _HB_MEMFS_DIRENTRY
{
   char *     szName;
   HB_FOFFSET llSize;
} HB_MEMFS_DIRENTRY, * PHB_MEMFS_DIRENTRY;

static HB_MEMFS_FS s_fs;
static HB_ERRCODE  s_error;

static HB_CRITICAL_NEW( s_mtx );

#define HB_MEMFSMT_LOCK()    hb_threadEnterCriticalSection( &s_mtx )
#define HB_MEMFSMT_UNLOCK()  hb_threadLeaveCriticalSection( &s_mtx )

static void memfsInodeFree( PHB_MEMFS_INODE pInode );


static void memfsExit( void * cargo )
{
   HB_ULONG ul;

   HB_SYMBOL_UNUSED( cargo );

   for( ul = 0; ul < s_fs.ulFileAlloc; ul++ )
   {
      if( s_fs.pFiles[ ul ] )
         hb_xfree( s_fs.pFiles[ ul ] );
   }
   hb_xfree( s_fs.pFiles );
   for( ul = 0; ul < s_fs.ulInodeCount; ul++ )
   {
      memfsInodeFree( s_fs.pInodes[ ul ] );
   }
   hb_xfree( s_fs.pInodes );
}


static void memfsInit( void )
{
   #if 0
   HB_CRITICAL_INIT( s_mtx );
   #endif
   s_error = 0;
   s_fs.ulInodeCount = 0;
   s_fs.ulInodeAlloc = HB_MEMFS_INITSIZE;
   s_fs.pInodes = ( PHB_MEMFS_INODE * ) hb_xgrab( sizeof( PHB_MEMFS_INODE ) * s_fs.ulInodeAlloc );
   s_fs.ulFileAlloc = HB_MEMFS_INITSIZE;
   s_fs.pFiles = ( PHB_MEMFS_FILE * ) hb_xgrab( sizeof( PHB_MEMFS_FILE ) * s_fs.ulFileAlloc );
   memset( s_fs.pFiles, 0, sizeof( PHB_MEMFS_FILE ) * s_fs.ulFileAlloc );
   s_fs.ulFileLast = 0;
   hb_vmAtQuit( memfsExit, NULL );
}


/* Note: returns 1 based index! */
static HB_ULONG memfsInodeFind( const char * szName, HB_ULONG * pulPos )
{
   HB_ULONG ulLeft, ulRight;

   ulLeft = 0;
   ulRight = s_fs.ulInodeCount;
   while( ulLeft < ulRight )
   {
      HB_ULONG ulMiddle;
      int i;

      ulMiddle = ( ulLeft + ulRight ) >> 1;
      i = strcmp( szName, s_fs.pInodes[ ulMiddle ]->szName );
      if( i == 0 )
         return ulMiddle + 1;
      else if( i < 0 )
         ulRight = ulMiddle;
      else
         ulLeft = ulMiddle + 1;
   }
   if( pulPos )
      *pulPos = ulLeft;
   return 0;
}


static PHB_MEMFS_INODE memfsInodeAlloc( const char * szName )
{
   PHB_MEMFS_INODE pInode = ( PHB_MEMFS_INODE ) hb_xgrab( sizeof( HB_MEMFS_INODE ) );
   HB_ULONG ulInode = 0;

   pInode->llSize = 0;
   pInode->llAlloc = HB_MEMFS_INITSIZE;
   pInode->pData = ( char * ) hb_xgrab( ( HB_ULONG ) pInode->llAlloc );
   memset( pInode->pData, 0, ( HB_SIZE ) pInode->llAlloc );
   pInode->szName = hb_strdup( szName );

   pInode->uiCount = 1;
   pInode->uiCountRead = pInode->uiCountWrite = 0;
   pInode->uiDeny = 0;

   /* Insert into inode array. Inode should not exist!!! */
   if( s_fs.ulInodeCount >= s_fs.ulInodeAlloc )
   {
      s_fs.ulInodeAlloc += s_fs.ulInodeAlloc >> 1;
      s_fs.pInodes = ( PHB_MEMFS_INODE * ) hb_xrealloc( s_fs.pInodes, s_fs.ulInodeAlloc * sizeof( PHB_MEMFS_INODE ) );
   }

   if( memfsInodeFind( szName, &ulInode ) )
   {
      hb_errInternal( 9999, "memfsInodeAlloc: Inode already exists", NULL, NULL );
      return NULL;
   }

   if( ulInode < s_fs.ulInodeCount )
   {
      memmove( s_fs.pInodes + ulInode + 1, s_fs.pInodes + ulInode, ( s_fs.ulInodeCount - ulInode ) * sizeof( PHB_MEMFS_INODE ) );
   }
   s_fs.pInodes[ ulInode ] = pInode;
   s_fs.ulInodeCount++;
   return pInode;
}


static void memfsInodeFree( PHB_MEMFS_INODE pInode )
{
   hb_xfree( pInode->pData );
   hb_xfree( pInode->szName );
   hb_xfree( pInode );
}


static PHB_MEMFS_FILE memfsFileAlloc( PHB_MEMFS_INODE pInode )
{
   PHB_MEMFS_FILE pFile = ( PHB_MEMFS_FILE ) hb_xgrab( sizeof( HB_MEMFS_FILE ) );

   pFile->pInode = pInode;
   pFile->llPos = 0;

   pInode->uiCount++;
   return pFile;
}


static PHB_MEMFS_FILE memfsHandleToFile( HB_FHANDLE hFile )
{
   if( hFile == FS_ERROR || ( HB_ULONG ) hFile == 0 || ( HB_ULONG ) hFile > s_fs.ulFileAlloc || s_fs.pFiles[ ( HB_ULONG ) hFile - 1 ] == NULL )
   {
#if 0
      hb_errInternal( 9999, "memfsHandleToFile: Invalid file handle", NULL, NULL );
#endif
      return NULL;
   }
   else
      return s_fs.pFiles[ ( HB_ULONG ) hFile - 1 ];
}


static HB_FHANDLE memfsHandleAlloc( PHB_MEMFS_FILE pFile )
{
   HB_ULONG ul;

   /* This allocation will help to avoid reallocation of just released file handle. */
   for( ul = s_fs.ulFileLast; ul < s_fs.ulFileAlloc; ul++ )
   {
      if( ! s_fs.pFiles[ ul ] )
      {
         s_fs.pFiles[ ul ] = pFile;
         s_fs.ulFileLast = ul + 1;
         return ( HB_FHANDLE ) ( ul + 1 );
      }
   }

   for( ul = 0; ul < s_fs.ulFileLast; ul++ )
   {
      if( ! s_fs.pFiles[ ul ] )
      {
         s_fs.pFiles[ ul ] = pFile;
         s_fs.ulFileLast = ul + 1;
         return ( HB_FHANDLE ) ( ul + 1 );
      }
   }

   s_fs.pFiles = ( PHB_MEMFS_FILE * ) hb_xrealloc( s_fs.pFiles, ( s_fs.ulFileAlloc << 1 ) * sizeof( PHB_MEMFS_FILE ) );
   memset( s_fs.pFiles + s_fs.ulFileAlloc, 0, s_fs.ulFileAlloc * sizeof( PHB_MEMFS_FILE ) );
   ul = s_fs.ulFileAlloc;
   s_fs.ulFileAlloc <<= 1;
   s_fs.pFiles[ ul ] = pFile;
   s_fs.ulFileLast = ul + 1;
   return ( HB_FHANDLE ) ( ul + 1 );
}


/* --- Public Memory FS functions --- */

HB_MEMFS_EXPORT HB_ERRCODE hb_memfsError( void )
{
   return s_error;
}


HB_MEMFS_EXPORT HB_BOOL hb_memfsFileExists( const char * szName )
{
   HB_BOOL bRet;

   HB_MEMFSMT_LOCK();
   bRet = memfsInodeFind( szName, NULL ) != 0;
   HB_MEMFSMT_UNLOCK();
   return bRet;
}


HB_MEMFS_EXPORT HB_BOOL hb_memfsDelete( const char * szName )
{
   PHB_MEMFS_INODE pInode;
   HB_ULONG ulFile;

   HB_MEMFSMT_LOCK();
   if( ( ulFile = memfsInodeFind( szName, NULL ) ) == 0 )
   {
      HB_MEMFSMT_UNLOCK();
      return HB_FALSE;
   }
   pInode = s_fs.pInodes[ ulFile - 1 ];

   if( ulFile < s_fs.ulInodeCount )
      memmove( s_fs.pInodes + ulFile - 1, s_fs.pInodes + ulFile, ( s_fs.ulInodeCount - ulFile ) * sizeof( PHB_MEMFS_INODE ) );

   s_fs.ulInodeCount--;

   if( --pInode->uiCount == 0 )
      memfsInodeFree( pInode );
   HB_MEMFSMT_UNLOCK();
   return HB_TRUE;
}


HB_MEMFS_EXPORT HB_BOOL hb_memfsRename( const char * szName, const char * szNewName )
{
   HB_ULONG ulInode;

   HB_MEMFSMT_LOCK();
   if( ( ulInode = memfsInodeFind( szName, NULL ) ) == 0 )
   {
      HB_MEMFSMT_UNLOCK();
      /* File not found */
      return HB_FALSE;
   }
   if( memfsInodeFind( szNewName, NULL ) )
   {
      HB_MEMFSMT_UNLOCK();
      /* File already exists */
      return HB_FALSE;
   }
   hb_xfree( s_fs.pInodes[ ulInode - 1 ]->szName );
   s_fs.pInodes[ ulInode - 1 ]->szName = hb_strdup( szNewName );
   HB_MEMFSMT_UNLOCK();
   return HB_TRUE;
}


HB_MEMFS_EXPORT PHB_ITEM hb_memfsDirectory( const char * pszDirSpec, const char * pszAttr )
{
   PHB_MEMFS_DIRENTRY pDirEn = NULL;
   char *   pszFree = NULL;
   PHB_ITEM pDirArray;
   HB_SIZE  nLen;
   HB_ULONG ulCount, ul;

   HB_SYMBOL_UNUSED( pszAttr );

   if( pszDirSpec && *pszDirSpec )
   {
      nLen = strlen( pszDirSpec ) - 1;
      if( pszDirSpec[ nLen ] == HB_OS_PATH_DELIM_CHR )
      {
         if( nLen == 0 )
            pszDirSpec = HB_OS_ALLFILE_MASK;
         else
            pszDirSpec = pszFree = hb_xstrcpy( NULL, pszDirSpec, HB_OS_ALLFILE_MASK, NULL );
      }
   }
   else
      pszDirSpec = HB_OS_ALLFILE_MASK;

   HB_MEMFSMT_LOCK();
   ulCount = s_fs.ulInodeCount;
   nLen = 0;
   if( ulCount )
   {
      pDirEn = ( PHB_MEMFS_DIRENTRY ) hb_xgrab( ulCount * sizeof( HB_MEMFS_DIRENTRY ) );
      for( ul = 0; ul < ulCount; ul++ )
      {
         if( hb_strMatchFile( s_fs.pInodes[ ul ]->szName, pszDirSpec ) )
         {
            pDirEn[ nLen ].szName = hb_strdup( s_fs.pInodes[ ul ]->szName );
            pDirEn[ nLen ].llSize = s_fs.pInodes[ ul ]->llSize;
            nLen++;
         }
      }
   }
   HB_MEMFSMT_UNLOCK();

   pDirArray = hb_itemArrayNew( nLen );
   for( ul = 0; ( HB_SIZE ) ul < nLen; ul++ )
   {
      PHB_ITEM pSubarray = hb_arrayGetItemPtr( pDirArray, ul + 1 );

      hb_arrayNew    ( pSubarray, F_LEN );
      hb_arraySetCPtr( pSubarray, F_NAME, pDirEn[ ul ].szName );
      hb_arraySetNInt( pSubarray, F_SIZE, pDirEn[ ul ].llSize );
      hb_arraySetDL  ( pSubarray, F_DATE, 0 );
      hb_arraySetC   ( pSubarray, F_TIME, "00:00:00" );
      hb_arraySetC   ( pSubarray, F_ATTR, "" );
   }

   if( pDirEn )
      hb_xfree( pDirEn );
   if( pszFree )
      hb_xfree( pszFree );

   return pDirArray;
}


HB_MEMFS_EXPORT HB_FHANDLE hb_memfsOpen( const char * szName, HB_USHORT uiFlags )
{
   PHB_MEMFS_FILE pFile = NULL;
   HB_FHANDLE     hFile;
   HB_ULONG       ulInode;
   HB_ERRCODE     uiError = 0;

   /*
      Recalculate flags. Bit should indicate feature: 1=read, 2=write, 16=denyread, 32=denywrite.
      So, 3=readwrite, 48=exclusive.
      Compatibility mode == DenyNone.
    */
   uiFlags = ( uiFlags & ( FO_CREAT | FO_TRUNC | FO_EXCL ) ) |
             ( uiFlags & FO_READWRITE ? FOX_READWRITE : ( uiFlags & FO_WRITE ? FOX_WRITE : FOX_READ ) ) |
             ( ( uiFlags & 0xf0 ) == FO_EXCLUSIVE ? FOX_EXCLUSIVE :
               ( ( uiFlags & 0xf0 ) == FO_DENYWRITE ? FOX_DENYWRITE :
                 ( ( uiFlags & 0xf0 ) == FO_DENYREAD ? FOX_DENYREAD : FOX_DENYNONE ) ) );

   HB_MEMFSMT_LOCK();

   ulInode = memfsInodeFind( szName, NULL );

   if( uiFlags & FO_CREAT )
   {
      if( uiFlags & FO_EXCL )
      {
         /* create new */
         if( ulInode )
            uiError = 80;
      }
      else if( uiFlags & FO_TRUNC )
      {
         /* create always */
         if( ulInode && s_fs.pInodes[ ulInode - 1 ]->uiDeny & FOX_DENYWRITE )
            uiError = 32;
      }
      else
      {
         /* open always */
         /* Do nothing */
      }
   }
   else if( uiFlags & FO_TRUNC )
   {
      /* truncate existing */
      if( ulInode )
      {
         if( s_fs.pInodes[ ulInode - 1 ]->uiDeny & FOX_DENYWRITE )
            uiError = 32;
      }
      else
         uiError = 2;
   }
   else
   {
      /* open existing */
      if( ! ulInode )
         uiError = 2;
   }

   if( ! uiError )
   {
      if( ulInode )
      {
         if( ( uiFlags & FOX_READ && s_fs.pInodes[ ulInode - 1 ]->uiDeny & FOX_DENYREAD ) ||
             ( uiFlags & FOX_WRITE && s_fs.pInodes[ ulInode - 1 ]->uiDeny & FOX_DENYWRITE ) ||
             s_fs.pInodes[ ulInode - 1 ]->uiDeny & uiFlags )
            uiError = 32;
         else
            pFile = memfsFileAlloc( s_fs.pInodes[ ulInode - 1 ] );
      }
      else
         pFile = memfsFileAlloc( memfsInodeAlloc( szName ) );
   }

   s_error = uiError;

   if( pFile )
   {
      if( uiFlags & FO_TRUNC )
      {
         pFile->pInode->llSize = 0;
         if( pFile->pInode->llAlloc != HB_MEMFS_INITSIZE )
         {
            pFile->pInode->llAlloc = HB_MEMFS_INITSIZE;
            hb_xfree( pFile->pInode->pData );
            pFile->pInode->pData = ( char * ) hb_xgrab( ( HB_ULONG ) pFile->pInode->llAlloc );
         }
         memset( pFile->pInode->pData, 0, ( HB_SIZE ) pFile->pInode->llAlloc );
      }

      pFile->pInode->uiDeny |= uiFlags & FOX_DENYFLAGS;
      if( uiFlags & FOX_READ )
         pFile->pInode->uiCountRead++;
      if( uiFlags & FOX_WRITE )
         pFile->pInode->uiCountWrite++;

      pFile->uiFlags = uiFlags;
      hFile = memfsHandleAlloc( pFile );
   }
   else
      hFile = FS_ERROR;

   HB_MEMFSMT_UNLOCK();

   return hFile;
}


HB_MEMFS_EXPORT void hb_memfsClose( HB_FHANDLE hFile )
{
   PHB_MEMFS_FILE  pFile;
   PHB_MEMFS_INODE pInode;

   HB_MEMFSMT_LOCK();
   if( ( pFile = memfsHandleToFile( hFile ) ) == NULL )
   {
      HB_MEMFSMT_UNLOCK();
      return; /* invalid handle */
   }
   s_fs.pFiles[ hFile - 1 ] = NULL;
   pInode = pFile->pInode;

   if( --pInode->uiCount == 0 )
   {
      memfsInodeFree( pInode );
   }
   else
   {
      if( pFile->uiFlags & FOX_READ )
         pInode->uiCountRead--;
      if( pFile->uiFlags & FOX_WRITE )
         pInode->uiCountWrite--;
      pInode->uiDeny ^= pFile->uiFlags & FOX_DENYFLAGS;
   }
   HB_MEMFSMT_UNLOCK();
   hb_xfree( pFile );
}


HB_MEMFS_EXPORT HB_SIZE hb_memfsReadAt( HB_FHANDLE hFile, void * pBuff, HB_SIZE nCount, HB_FOFFSET llOffset )
{
   PHB_MEMFS_FILE  pFile;
   PHB_MEMFS_INODE pInode;
   HB_SIZE         nRead;

   if( ( pFile = memfsHandleToFile( hFile ) ) == NULL )
      return 0;  /* invalid handle */
   pInode = pFile->pInode;

   if( ( pFile->uiFlags & FOX_READ ) == 0 )
      return 0;  /* access denied */

   if( llOffset < 0 || pInode->llSize <= llOffset )
      return 0;

   HB_MEMFSMT_LOCK();
   if( pInode->llSize >= llOffset + ( HB_FOFFSET ) nCount )
      nRead = nCount;
   else
      nRead = ( HB_SIZE ) ( pInode->llSize - llOffset );

   memcpy( pBuff, pInode->pData + ( HB_SIZE ) llOffset, nRead );
   HB_MEMFSMT_UNLOCK();
   pFile->llPos = llOffset + ( HB_FOFFSET ) nCount;
   return nRead;
}


HB_MEMFS_EXPORT HB_SIZE hb_memfsWriteAt( HB_FHANDLE hFile, const void * pBuff, HB_SIZE nCount, HB_FOFFSET llOffset )
{
   PHB_MEMFS_FILE  pFile;
   PHB_MEMFS_INODE pInode;

   if( ( pFile = memfsHandleToFile( hFile ) ) == NULL )
      return 0;  /* invalid handle */
   pInode = pFile->pInode;

   if( ( pFile->uiFlags & FOX_WRITE ) == 0 )
      return 0;  /* access denied */

   if( llOffset < 0 )
      return 0;

   HB_MEMFSMT_LOCK();

   /* Reallocate if neccesary */
   if( pInode->llAlloc < llOffset + ( HB_FOFFSET ) nCount )
   {
      HB_FOFFSET llNewAlloc = pInode->llAlloc + ( pInode->llAlloc >> 1 );

      if( llNewAlloc < llOffset + ( HB_FOFFSET ) nCount )
         llNewAlloc = llOffset + ( HB_FOFFSET ) nCount;

      pInode->pData = ( char * ) hb_xrealloc( pInode->pData, ( HB_SIZE ) llNewAlloc );
      memset( pInode->pData + ( HB_SIZE ) pInode->llAlloc, 0, ( HB_SIZE ) ( llNewAlloc - pInode->llAlloc ) );
      pInode->llAlloc = llNewAlloc;
   }
   memcpy( pInode->pData + ( HB_SIZE ) llOffset, pBuff, nCount );

   if( pInode->llSize < llOffset + ( HB_FOFFSET ) nCount )
      pInode->llSize = llOffset + ( HB_FOFFSET ) nCount;

   HB_MEMFSMT_UNLOCK();
   pFile->llPos = llOffset + ( HB_FOFFSET ) nCount;
   return nCount;
}


HB_MEMFS_EXPORT HB_SIZE hb_memfsRead( HB_FHANDLE hFile, void * pBuff, HB_SIZE nCount )
{
   PHB_MEMFS_FILE  pFile;

   if( ( pFile = memfsHandleToFile( hFile ) ) == NULL )
      return 0;  /* invalid handle */

   return hb_memfsReadAt( hFile, pBuff, nCount, pFile->llPos );
}


HB_MEMFS_EXPORT HB_SIZE hb_memfsWrite( HB_FHANDLE hFile, const void * pBuff, HB_SIZE nCount )
{
   PHB_MEMFS_FILE  pFile;

   if( ( pFile = memfsHandleToFile( hFile ) ) == NULL )
      return 0;  /* invalid handle */

   return hb_memfsWriteAt( hFile, pBuff, nCount, pFile->llPos );
}


HB_MEMFS_EXPORT HB_BOOL hb_memfsTruncAt( HB_FHANDLE hFile, HB_FOFFSET llOffset )
{
   PHB_MEMFS_FILE  pFile;
   PHB_MEMFS_INODE pInode;

   if( ( pFile = memfsHandleToFile( hFile ) ) == NULL )
      return HB_FALSE;  /* invalid handle */
   pInode = pFile->pInode;

   if( ( pFile->uiFlags & FOX_WRITE ) == 0 )
      return HB_FALSE;  /* access denied */

   if( llOffset < 0 )
      return HB_FALSE;

   HB_MEMFSMT_LOCK();

   /* Reallocate if neccesary */
   if( pInode->llAlloc < llOffset )
   {
      HB_FOFFSET llNewAlloc = pInode->llAlloc + ( pInode->llAlloc >> 1 );

      if( llNewAlloc < llOffset )
         llNewAlloc = llOffset;

      pInode->pData = ( char * ) hb_xrealloc( pInode->pData, ( HB_SIZE ) llNewAlloc );
      memset( pInode->pData + ( HB_SIZE ) pInode->llAlloc, 0, ( HB_SIZE ) ( llNewAlloc - pInode->llAlloc ) );
      pInode->llAlloc = llNewAlloc;
   }
   else if( ( pInode->llAlloc >> 2 ) > ( llOffset > HB_MEMFS_INITSIZE ? llOffset : HB_MEMFS_INITSIZE ) )
   {
      pInode->llAlloc = ( llOffset > HB_MEMFS_INITSIZE ? llOffset : HB_MEMFS_INITSIZE );
      pInode->pData = ( char * ) hb_xrealloc( pInode->pData, ( HB_SIZE ) pInode->llAlloc );
   }

   memset( pInode->pData + ( HB_SIZE ) llOffset, 0, ( HB_SIZE ) ( pInode->llAlloc - llOffset ) );

   pInode->llSize = llOffset;
   HB_MEMFSMT_UNLOCK();
   return HB_TRUE;
}


HB_MEMFS_EXPORT HB_FOFFSET hb_memfsSeek( HB_FHANDLE hFile, HB_FOFFSET llOffset, HB_USHORT uiFlags )
{
   PHB_MEMFS_FILE  pFile;
   PHB_MEMFS_INODE pInode;
   HB_FOFFSET      llPos;

   if( ( pFile = memfsHandleToFile( hFile ) ) == NULL )
      return 0;  /* invalid handle */
   pInode = pFile->pInode;

   HB_MEMFSMT_LOCK();
   if( uiFlags & FS_END )
      llPos = pInode->llSize + llOffset;
   else if( uiFlags & FS_RELATIVE )
      llPos = pFile->llPos + llOffset;
   else
      llPos = llOffset;

   if( llPos < 0 )
      llPos = 0;

   if( llPos > pInode->llSize )
      llPos = pInode->llSize;

   HB_MEMFSMT_UNLOCK();
   pFile->llPos = llPos;
   return llPos;
}


HB_MEMFS_EXPORT HB_BOOL hb_memfsEof( HB_FHANDLE hFile )
{
   PHB_MEMFS_FILE  pFile;
   PHB_MEMFS_INODE pInode;
   HB_BOOL         fEof;

   if( ( pFile = memfsHandleToFile( hFile ) ) == NULL )
      return HB_FALSE;  /* invalid handle */
   pInode = pFile->pInode;

   HB_MEMFSMT_LOCK();
   fEof = pFile->llPos >= pInode->llSize;
   HB_MEMFSMT_UNLOCK();
   return fEof;
}

HB_MEMFS_EXPORT void hb_memfsFlush( HB_FHANDLE hFile, HB_BOOL fDirty )
{
   HB_SYMBOL_UNUSED( hFile );
   HB_SYMBOL_UNUSED( fDirty );
}


HB_MEMFS_EXPORT void hb_memfsCommit( HB_FHANDLE hFile )
{
   HB_SYMBOL_UNUSED( hFile );
}


HB_MEMFS_EXPORT HB_BOOL hb_memfsLock( HB_FHANDLE hFile, HB_FOFFSET ulStart, HB_FOFFSET nLength, int iMode )
{
   HB_SYMBOL_UNUSED( hFile );
   HB_SYMBOL_UNUSED( ulStart );
   HB_SYMBOL_UNUSED( nLength );
   HB_SYMBOL_UNUSED( iMode );
   return HB_TRUE;
}

HB_MEMFS_EXPORT int hb_memfsLockTest( HB_FHANDLE hFile, HB_FOFFSET ulStart, HB_FOFFSET nLength, int iMode )
{
   HB_SYMBOL_UNUSED( hFile );
   HB_SYMBOL_UNUSED( ulStart );
   HB_SYMBOL_UNUSED( nLength );
   HB_SYMBOL_UNUSED( iMode );
   return 0;
}

/* --- I/O Driver for Memory file system --- */

#define FILE_PREFIX      "MEM:"
#define FILE_PREFIX_LEN  strlen( FILE_PREFIX )

typedef struct _HB_FILE
{
   const HB_FILE_FUNCS * pFuncs;
   HB_FHANDLE            hFile;
}
HB_FILE;


static PHB_FILE s_fileNew( HB_FHANDLE hFile );


static HB_BOOL s_fileAccept( PHB_FILE_FUNCS pFuncs, const char * pszFileName )
{
   HB_SYMBOL_UNUSED( pFuncs );

   return hb_strnicmp( pszFileName, FILE_PREFIX, FILE_PREFIX_LEN ) == 0;
}


static HB_BOOL s_fileExists( PHB_FILE_FUNCS pFuncs, const char * pszFileName, char * pRetPath )
{
   HB_SYMBOL_UNUSED( pFuncs );

   if( hb_memfsFileExists( pszFileName + FILE_PREFIX_LEN ) )
   {
      /* Warning: return buffer could be the same memory place as filename parameter! */
      if( pRetPath && pRetPath != pszFileName )
         hb_strncpy( pRetPath, pszFileName, HB_PATH_MAX - 1 );
      return HB_TRUE;
   }
   return HB_FALSE;
}


static HB_BOOL s_fileDelete( PHB_FILE_FUNCS pFuncs, const char * pszFileName )
{
   HB_SYMBOL_UNUSED( pFuncs );
   return hb_memfsDelete( pszFileName + FILE_PREFIX_LEN );
}


static HB_BOOL s_fileRename( PHB_FILE_FUNCS pFuncs, const char * szName, const char * szNewName )
{
   szName += FILE_PREFIX_LEN;
   if( s_fileAccept( pFuncs, szNewName ) )
   {
      szNewName += FILE_PREFIX_LEN;
      return hb_memfsRename( szName, szNewName );
   }
   return HB_FALSE;
}


static HB_BOOL s_fileCopy( PHB_FILE_FUNCS pFuncs, const char * pszSrcFile, const char * pszDstFile )
{
   HB_SYMBOL_UNUSED( pFuncs );
   /* TODO: optimize it when both points to MEMIO files */
   return hb_fsCopy( pszSrcFile, pszDstFile );
}


static HB_BOOL s_fileDirExists( PHB_FILE_FUNCS pFuncs, const char * pszDirName )
{
   HB_SYMBOL_UNUSED( pFuncs );
   HB_SYMBOL_UNUSED( pszDirName );
   return HB_FALSE;
}


static HB_BOOL s_fileDirMake( PHB_FILE_FUNCS pFuncs, const char * pszDirName )
{
   HB_SYMBOL_UNUSED( pFuncs );
   HB_SYMBOL_UNUSED( pszDirName );
   return HB_FALSE;
}


static HB_BOOL s_fileDirRemove( PHB_FILE_FUNCS pFuncs, const char * pszDirName )
{
   HB_SYMBOL_UNUSED( pFuncs );
   HB_SYMBOL_UNUSED( pszDirName );
   return HB_FALSE;
}


static double s_fileDirSpace( PHB_FILE_FUNCS pFuncs, const char * pszDirName, HB_USHORT uiType )
{
   HB_SYMBOL_UNUSED( pFuncs );
   HB_SYMBOL_UNUSED( pszDirName );
   HB_SYMBOL_UNUSED( uiType );
   /* TODO: return allocated memory and free memory */
   return 0.0;
}


static PHB_ITEM s_fileDirectory( PHB_FILE_FUNCS pFuncs, const char * pszDirSpec, const char * pszAttr )
{
   HB_SYMBOL_UNUSED( pFuncs );
   return hb_memfsDirectory( pszDirSpec + FILE_PREFIX_LEN, pszAttr );
}


static HB_BOOL s_fileTimeGet( PHB_FILE_FUNCS pFuncs, const char * pszFileName, long * plJulian, long * plMillisec )
{
   HB_SYMBOL_UNUSED( pFuncs );
   HB_SYMBOL_UNUSED( pszFileName );
   HB_SYMBOL_UNUSED( plJulian );
   HB_SYMBOL_UNUSED( plMillisec );
   return HB_FALSE;
}


static HB_BOOL s_fileTimeSet( PHB_FILE_FUNCS pFuncs, const char * pszFileName, long lJulian, long lMillisec )
{
   HB_SYMBOL_UNUSED( pFuncs );
   HB_SYMBOL_UNUSED( pszFileName );
   HB_SYMBOL_UNUSED( lJulian );
   HB_SYMBOL_UNUSED( lMillisec );
   return HB_FALSE;
}


static HB_BOOL s_fileAttrGet( PHB_FILE_FUNCS pFuncs, const char * pszFileName, HB_FATTR * pulAttr )
{
   HB_SYMBOL_UNUSED( pFuncs );
   HB_SYMBOL_UNUSED( pszFileName );
   HB_SYMBOL_UNUSED( pulAttr );
   return HB_FALSE;
}


static HB_BOOL s_fileAttrSet( PHB_FILE_FUNCS pFuncs, const char * pszFileName, HB_FATTR ulAttr )
{
   HB_SYMBOL_UNUSED( pFuncs );
   HB_SYMBOL_UNUSED( pszFileName );
   HB_SYMBOL_UNUSED( ulAttr );
   return HB_FALSE;
}


static HB_BOOL s_fileLink( PHB_FILE_FUNCS pFuncs, const char * pszExisting, const char * pszNewName )
{
   HB_SYMBOL_UNUSED( pFuncs );
   HB_SYMBOL_UNUSED( pszExisting );
   HB_SYMBOL_UNUSED( pszNewName );
   return HB_FALSE;
}


static HB_BOOL s_fileLinkSym( PHB_FILE_FUNCS pFuncs, const char * pszTarget, const char * pszNewName )
{
   HB_SYMBOL_UNUSED( pFuncs );
   HB_SYMBOL_UNUSED( pszTarget );
   HB_SYMBOL_UNUSED( pszNewName );
   return HB_FALSE;
}


static char * s_fileLinkRead( PHB_FILE_FUNCS pFuncs, const char * pszFileName )
{
   HB_SYMBOL_UNUSED( pFuncs );
   HB_SYMBOL_UNUSED( pszFileName );
   return NULL;
}


static PHB_FILE s_fileOpen( PHB_FILE_FUNCS pFuncs, const char * szName,
                            const char * szDefExt, HB_FATTR nExFlags,
                            const char * pPaths, PHB_ITEM pError )
{
   HB_FHANDLE hFile;
   char       szNameNew[ HB_PATH_MAX ];
   HB_USHORT  uiFlags;

   HB_SYMBOL_UNUSED( pFuncs );
   HB_SYMBOL_UNUSED( pPaths );
   HB_SYMBOL_UNUSED( pError );

   hb_strncpy( szNameNew, szName + FILE_PREFIX_LEN, HB_PATH_MAX - 1 );

   if( szDefExt )
   {
      HB_SIZE nLen = strlen( szNameNew );
      do
      {
         if( nLen == 0 || strchr( HB_OS_PATH_DELIM_CHR_LIST, szNameNew[ nLen - 1 ] ) )
         {
            hb_strncat( szNameNew, szDefExt, HB_PATH_MAX - 1 );
            break;
         }
      }
      while( szNameNew[ --nLen ] != '.' );
   }

   uiFlags = nExFlags & 0xff;
   if( nExFlags & ( FXO_TRUNCATE | FXO_APPEND | FXO_UNIQUE ) )
   {
      uiFlags |= FO_CREAT;
      if( nExFlags & FXO_UNIQUE )
         uiFlags |= FO_EXCL;
      else if( nExFlags & FXO_TRUNCATE )
         uiFlags |= FO_TRUNC;
   }

   hFile = hb_memfsOpen( szNameNew, uiFlags );

   if( hFile != FS_ERROR )
      return s_fileNew( hFile );
   else
   {
      if( pError )
      {
         hb_errPutFileName( pError, szName );
         hb_errPutOsCode( pError, hb_memfsError() );
         hb_errPutGenCode( pError, ( HB_ERRCODE ) ( ( nExFlags & FXO_TRUNCATE ) ? EG_CREATE : EG_OPEN ) );
      }
      return NULL;
   }
}


static void s_fileClose( PHB_FILE pFile )
{
   hb_memfsClose( pFile->hFile );
   hb_xfree( pFile );
}


static HB_BOOL s_fileLock( PHB_FILE pFile, HB_FOFFSET ulStart,
                           HB_FOFFSET nLen, int iType )
{
   return hb_memfsLock( pFile->hFile, ulStart, nLen, iType );
}


static int s_fileLockTest( PHB_FILE pFile, HB_FOFFSET ulStart,
                           HB_FOFFSET nLen, int iType )
{
   return hb_memfsLockTest( pFile->hFile, ulStart, nLen, iType );
}


static HB_SIZE s_fileRead( PHB_FILE pFile, void * buffer,
                           HB_SIZE nSize, HB_MAXINT nTimeout )
{
   HB_SYMBOL_UNUSED( nTimeout );
   return hb_memfsRead( pFile->hFile, buffer, nSize );
}


static HB_SIZE s_fileWrite( PHB_FILE pFile, const void * buffer,
                            HB_SIZE nSize, HB_MAXINT nTimeout )
{
   HB_SYMBOL_UNUSED( nTimeout );
   return hb_memfsWrite( pFile->hFile, buffer, nSize );
}


static HB_SIZE s_fileReadAt( PHB_FILE pFile, void * buffer,
                             HB_SIZE nSize, HB_FOFFSET llOffset )
{
   return hb_memfsReadAt( pFile->hFile, buffer, nSize, llOffset );
}


static HB_SIZE s_fileWriteAt( PHB_FILE pFile, const void * buffer,
                              HB_SIZE nSize, HB_FOFFSET llOffset )
{
   return hb_memfsWriteAt( pFile->hFile, buffer, nSize, llOffset );
}


static HB_BOOL s_fileTruncAt( PHB_FILE pFile, HB_FOFFSET llOffset )
{
   return hb_memfsTruncAt( pFile->hFile, llOffset );
}


static HB_FOFFSET s_fileSeek( PHB_FILE pFile, HB_FOFFSET nOffset,
                              HB_USHORT uiFlags )
{
   return hb_memfsSeek( pFile->hFile, nOffset, uiFlags );
}


static HB_FOFFSET s_fileSize( PHB_FILE pFile )
{
   return hb_memfsSeek( pFile->hFile, 0, FS_END );
}


static HB_BOOL s_fileEof( PHB_FILE pFile )
{
   return hb_memfsEof( pFile->hFile );
}


static void s_fileFlush( PHB_FILE pFile, HB_BOOL fDirty )
{
   hb_memfsFlush( pFile->hFile, fDirty );
}


static void s_fileCommit( PHB_FILE pFile )
{
   hb_memfsCommit( pFile->hFile );
}


static HB_BOOL s_fileConfigure( PHB_FILE pFile, int iIndex, PHB_ITEM pValue )
{
   HB_SYMBOL_UNUSED( pFile );

   switch( iIndex )
   {
      case HB_VF_IONAME:
         hb_itemPutC( pValue, FILE_PREFIX );
         return HB_TRUE;
   }

   return HB_FALSE;
}


static HB_FHANDLE s_fileHandle( PHB_FILE pFile )
{
   return pFile ? pFile->hFile : FS_ERROR;
}


static const HB_FILE_FUNCS s_fileFuncs =
{
   s_fileAccept,

   s_fileExists,
   s_fileDelete,
   s_fileRename,
   s_fileCopy,

   s_fileDirExists,
   s_fileDirMake,
   s_fileDirRemove,
   s_fileDirSpace,
   s_fileDirectory,

   s_fileTimeGet,
   s_fileTimeSet,
   s_fileAttrGet,
   s_fileAttrSet,

   s_fileLink,
   s_fileLinkSym,
   s_fileLinkRead,

   s_fileOpen,
   s_fileClose,
   s_fileLock,
   s_fileLockTest,
   s_fileRead,
   s_fileWrite,
   s_fileReadAt,
   s_fileWriteAt,
   s_fileTruncAt,
   s_fileSeek,
   s_fileSize,
   s_fileEof,
   s_fileFlush,
   s_fileCommit,
   s_fileConfigure,
   s_fileHandle
};


static PHB_FILE s_fileNew( HB_FHANDLE hFile )
{
   PHB_FILE pFile = ( PHB_FILE ) hb_xgrab( sizeof( HB_FILE ) );

   pFile->pFuncs = &s_fileFuncs;
   pFile->hFile = hFile;
   return pFile;
}


HB_FUNC( HB_MEMIO ) { ; }


HB_CALL_ON_STARTUP_BEGIN( _hb_file_memio_init_ )
   memfsInit();
   hb_fileRegisterFull( &s_fileFuncs );
HB_CALL_ON_STARTUP_END( _hb_file_memio_init_ )

#if defined( HB_PRAGMA_STARTUP )
   #pragma startup _hb_file_memio_init_
#elif defined( HB_DATASEG_STARTUP )
   #define HB_DATASEG_BODY  HB_DATASEG_FUNC( _hb_file_memio_init_ )
   #include "hbiniseg.h"
#endif
