/*****************************************************************************
 *                                                                            *
 *  BTREE.C                                                                   *
 *                                                                            *
 *  Copyright (C) Microsoft Corporation 1989 - 1994.                          *
 *  All Rights reserved.                                                      *
 *                                                                            *
 ******************************************************************************
 *                                                                            *
 *  Module Intent                                                             *
 *                                                                            *
 *  Btree manager general functions: open, close, etc.                        *
 *                                                                            *
 ******************************************************************************
 *                                                                            *
 *  Current Owner:  BinhN                                                     *
 *                                                                            *
 *****************************************************************************/

/*****************************************************************************
 *
 *  Revision History:  Created 02/10/89 by JohnSc
 *
 *   2/10/89 johnsc   created: stub version
 *   3/10/89 johnsc   use FS files
 *   8/21/89 johnsc   autodocified
 *  11/08/90 JohnSc   added a parameter to RcGetBtreeInfo() to get block size
 *  11/29/90 RobertBu #ifdef'ed out a dead routine
 *  12/14/90 JohnSc   added VerifyHbt()
 *   3/05/97     erinfox Change errors to HRESULTS
 *
 *****************************************************************************/

static char s_aszModule[] = __FILE__;   /* For error report */

#include <mvopsys.h>
#include <string.h>
#include <orkin.h>
#include <misc.h>
#include <iterror.h>
#include <wrapstor.h>
#include <mvsearch.h>
#include <_mvutil.h>
#include "common.h"


/***************************************************************************
 *
- Function:     RcMakeCache(qbthr)
-
 * Purpose:      Allocate a btree cache with one block per level.
 *
 * ASSUMES
 *   args IN:    qbthr - no cache
 *
 * PROMISES
 *   returns:    S_OK, or errors
 *   args OUT:   qbthr->ghCache is allocated; qbthr->qCache is NULL
 *
 ***************************************************************************/
HRESULT PASCAL FAR RcMakeCache(QBTHR qbthr)
{
	SHORT i;

	/* Sanity check */
	if (qbthr == NULL)
		return (E_INVALIDARG); 
		
	/* Allocate the memory */
		
	if (qbthr->bth.cLevels > 0)
	{
	    // would it work to just alloc 0 bytes???
		qbthr->ghCache = _GLOBALALLOC(GMEM_ZEROINIT|GMEM_SHARE| GMEM_MOVEABLE,
		    (LONG)qbthr->bth.cLevels * CbCacheBlock(qbthr) );

	if (qbthr->ghCache == NULL)
		return (E_OUTOFMEMORY);

	qbthr->qCache = (QB) _GLOBALLOCK(qbthr->ghCache);

	/* Initialize the flags */

	for (i = 0; i < qbthr->bth.cLevels; i++)
		QCacheBlock(qbthr, i)->bFlags = (BYTE)0;

	_GLOBALUNLOCK(qbthr->ghCache);
    }
	else {
		qbthr->ghCache = NULL;
	}

	qbthr->qCache = NULL;

	return (S_OK);
}

/***************************************************************************
 *
 *                           Public Routines
 *
 ***************************************************************************/


/***************************************************************************
 *
- Function:     HbtCreateBtreeSz(sz, qbtp)
-
 * Purpose:      Create and open a btree.
 *
 * ASSUMES
 *   args IN:    sz    - name of the btree
 *               qbtp  - pointer to btree params: NO default because we
 *                       need an HFS.
 *                               The bFlags param contains HFILE_SYSTEM for the 
 *                                               system btree, but none of the btree code should
 *                                               care whether or not it is a system file.
 *
 * PROMISES
 *   returns:    handle to the new btree
 *
 * Note:         KT supported:  KT_SZ, KT_LONG, KT_SZI, KT_SZISCAND.
 * +++
 *
 * Method:       Btrees are files inside a FS.  The FS directory is a
 *               special file in the FS.
 *
 ***************************************************************************/
HBT FAR PASCAL HbtCreateBtreeSz(LPSTR sz,BTREE_PARAMS FAR *qbtp, PHRESULT phr)
{
	HF    hf;
	HBT   hbt;
	QBTHR qbthr;


	/* see if we support key type */

	if (qbtp == NULL ||
        (
#ifdef FULL_BTREE // {
            qbtp->rgchFormat[0] != KT_SZ
			    &&
            qbtp->rgchFormat[0] != KT_VSTI
			    &&
			qbtp->rgchFormat[0] != KT_SZI
			    &&
			qbtp->rgchFormat[0] != KT_SZMAP
			    &&
			qbtp->rgchFormat[0] != KT_SZISCAND
				&&
#endif // }
			qbtp->rgchFormat[0] != KT_LONG
			    &&
			qbtp->rgchFormat[0] != KT_EXTSORT) )
	{
		SetErrCode(phr, E_INVALIDARG);
		return NULL;
	}


	/* allocate btree handle struct */

	if (( hbt =  _GLOBALALLOC(GMEM_ZEROINIT|GMEM_SHARE| GMEM_MOVEABLE,
	    (LONG)sizeof( BTH_RAM) ) ) == NULL ) {
		SetErrCode(phr, E_OUTOFMEMORY);
		return NULL;
	}                                                                                                                                                                           

	qbthr = (QBTHR) _GLOBALLOCK(hbt);

	/* initialize bthr struct */

	qbtp->rgchFormat[ wMaxFormat ] = '\0';
	lstrcpy(qbthr->bth.rgchFormat, qbtp->rgchFormat[0] == '\0'
			      ? rgchBtreeFormatDefault : qbtp->rgchFormat);

	switch (qbtp->rgchFormat[ 0 ])
	{
#ifdef FULL_BTREE // {
		case KT_SZ:
			qbthr->BkScanInternal = BkScanSzInternal;
			qbthr->RcScanLeaf     = RcScanSzLeaf;
			break;

		case KT_VSTI:
			qbthr->BkScanInternal = BkScanVstiInternal;
			qbthr->RcScanLeaf     = RcScanVstiLeaf;
			break;

		case KT_SZI:
			qbthr->BkScanInternal = BkScanSziInternal;
			qbthr->RcScanLeaf     = RcScanSziLeaf;
			break;

		case KT_SZISCAND:
			qbthr->BkScanInternal = BkScanSziScandInternal;
			qbthr->RcScanLeaf     = RcScanSziScandLeaf;
			break;

		case KT_SZMAP:
			qbthr->BkScanInternal = BkScanCMapInternal ;
			qbthr->RcScanLeaf     = RcScanCMapLeaf; 
			break;
#endif // }
		case KT_LONG:
			qbthr->BkScanInternal = BkScanLInternal;
			qbthr->RcScanLeaf     = RcScanLLeaf;
			break;

		case KT_EXTSORT:
			qbthr->BkScanInternal = BkScanExtSortInternal ;
			qbthr->RcScanLeaf     = RcScanExtSortLeaf; 
			break;

		default:
			/* unsupported KT */
			SetErrCode(phr, E_INVALIDARG);
			goto error_return;
			break;
	}

	/* create the btree file */

	if (!FHfValid(hf = HfCreateFileHfs(qbtp->hfs, sz, qbtp->bFlags, phr)))
	{
		goto error_return;
	}

	
	qbthr->bth.wMagic     = wBtreeMagic;
	qbthr->bth.bVersion   = bBtreeVersion;

	qbthr->bth.bFlags     = qbtp->bFlags | fFSDirty;
	qbthr->bth.cbBlock    = qbtp->cbBlock ? qbtp->cbBlock : cbBtreeBlockDefault;

	qbthr->bth.bkFirst    =
	qbthr->bth.bkLast     =
	qbthr->bth.bkRoot     =
	qbthr->bth.bkFree     = bkNil;
	qbthr->bth.bkEOF      = (BK)0;

	qbthr->bth.cLevels    = 0;
	qbthr->bth.lcEntries  = (LONG)0;

	qbthr->bth.dwCodePageID			= qbtp->dwCodePageID;
	qbthr->bth.lcid					= qbtp->lcid;
	qbthr->bth.dwExtSortInstID		= qbtp->dwExtSortInstID;
	qbthr->bth.dwExtSortKeyType		= qbtp->dwExtSortKeyType;

	qbthr->hf             = hf;
	qbthr->cbRecordSize   = 0;
	qbthr->ghCache        = NULL;
	qbthr->qCache         = NULL;
	qbthr->lrglpCharTab   = NULL;
	qbthr->pITSortKey	  = NULL;

	LcbWriteHf(qbthr->hf, &(qbthr->bth), (LONG)sizeof(BTH), phr ); /* why??? */

	_GLOBALUNLOCK(hbt);
	return hbt;

error_return:
	_GLOBALUNLOCK(hbt);
	_GLOBALFREE(hbt);
	return NULL;
}

/***************************************************************************
 *
- Function:     RcDestroyBtreeSz(sz, hfs)
-
 * Purpose:      destroy an existing btree
 *
 * Method:       look for file and unlink it
 *
 * ASSUMES
 *   args IN:    sz - name of btree file
 *               hfs - file system btree lives in
 *   state IN:   btree is closed (if not data will be lost)
 *
 * PROMISES
 *   returns:    S_OK or errors
 *
 * Notes:        FS directory btree never gets destroyed: you just get rid
 *               of the whole fs.
 *
 ***************************************************************************/
HRESULT FAR PASCAL RcDestroyBtreeSz(LPSTR sz, HFS hfs)
{
	return (RcUnlinkFileHfs(hfs, sz));
}

/***************************************************************************
 *
 * Function:     HbtOpenBtreeSz(sz, hfs, bFlags)
 *
 * Purpose:      open an existing btree
 *
 * ASSUMES
 *   args IN:    sz        - name of the btree (ignored if isdir is set)
 *               hfs       - hfs btree lives in
 *               bFlags    - open mode, isdir flag
 *	 args OUT:
 *				 phr       - error code
 *
 * PROMISES
 *   returns:    handle to the open btree or NULL on failure
 *               isdir flag set in qbthr->bth.bFlags if indicated
 *
 ***************************************************************************/
HBT FAR PASCAL HbtOpenBtreeSz(LPWSTR sz, HFS hfs, BYTE bFlags, PHRESULT phr)
{
	HF              hf;
	QBTHR   qbthr;
	HBT         hbt;
	LONG    lcb;
    HRESULT      rc;


	/* allocate struct */

	if ((hbt =  _GLOBALALLOC(GMEM_ZEROINIT|GMEM_SHARE| GMEM_MOVEABLE,
	    (LONG)sizeof(BTH_RAM))) == NULL)
	{
		SetErrCode(phr, E_OUTOFMEMORY);
		return NULL;
	}
	qbthr = (QBTHR) _GLOBALLOCK(hbt);

	/* open btree file */

	if (!FHfValid(hf = HfOpenHfs(hfs, sz, bFlags, phr)))
	{
exit0:
		_GLOBALUNLOCK (hbt);
		_GLOBALFREE(hbt);

		return 0;
	}

	/* read header from file */

	lcb = LcbReadHf(hf, &(qbthr->bth), (LONG)sizeof( BTH), phr);

	/* MAC swapping stuffs */

    if (qbthr->bth.wMagic == SWAPWORD(wBtreeMagic))
    {
	    qbthr->bth.wMagic = SWAPWORD(qbthr->bth.wMagic);
	    qbthr->bth.cbBlock = SWAPWORD(qbthr->bth.cbBlock);
	    qbthr->bth.bkFirst = SWAPLONG(qbthr->bth.bkFirst);
	    qbthr->bth.bkLast = SWAPLONG(qbthr->bth.bkLast);
	    qbthr->bth.bkRoot = SWAPLONG(qbthr->bth.bkRoot);
	    qbthr->bth.bkFree = SWAPLONG(qbthr->bth.bkFree);
	    qbthr->bth.bkEOF = SWAPLONG(qbthr->bth.bkEOF);
	    qbthr->bth.cLevels = SWAPWORD(qbthr->bth.cLevels);
	    qbthr->bth.lcEntries = SWAPLONG(qbthr->bth.lcEntries);
		qbthr->bth.dwCodePageID = SWAPLONG(qbthr->bth.dwCodePageID);
		qbthr->bth.lcid = SWAPLONG(qbthr->bth.lcid);
		qbthr->bth.dwExtSortInstID = SWAPLONG(qbthr->bth.dwExtSortInstID);
		qbthr->bth.dwExtSortKeyType = SWAPLONG(qbthr->bth.dwExtSortKeyType);
     }

	if (lcb != (LONG)sizeof(BTH))
	{
exit1:
		RcCloseHf(hf);
		goto exit0;
	}

	if (qbthr->bth.wMagic != wBtreeMagic)
	{
	     // check magic number
		SetErrCode(phr, E_FILEINVALID);
		goto exit1;
	}

	if (qbthr->bth.bVersion != bBtreeVersion)
	{
	    // support >1 vers someday
		SetErrCode(phr, E_BADVERSION);
		goto exit1;
	}

	/* initialize stuff */

	if ((rc = RcMakeCache(qbthr)) != S_OK)
	{
	    SetErrCode (phr, rc);
		goto exit1;
	}

	qbthr->hf = hf;
	qbthr->cbRecordSize = 0;

	switch (qbthr->bth.rgchFormat[0])
	{
#ifdef FULL_BTREE // {
		case KT_SZ:
			qbthr->BkScanInternal = BkScanSzInternal;
			qbthr->RcScanLeaf                = RcScanSzLeaf;
			break;

		case KT_SZI:
			qbthr->BkScanInternal = BkScanSziInternal;
			qbthr->RcScanLeaf                = RcScanSziLeaf;
			break;
		
		case KT_VSTI:
			qbthr->BkScanInternal = BkScanVstiInternal;
			qbthr->RcScanLeaf     = RcScanVstiLeaf;
			break;

		case KT_SZISCAND:
			qbthr->BkScanInternal = BkScanSziScandInternal;
			qbthr->RcScanLeaf                = RcScanSziScandLeaf;
			break;

		case KT_SZMAP:
			qbthr->BkScanInternal = BkScanCMapInternal ;
			qbthr->RcScanLeaf     = RcScanCMapLeaf; 
			break;
#endif // }
		case KT_LONG:
			qbthr->BkScanInternal = BkScanLInternal;
			qbthr->RcScanLeaf     = RcScanLLeaf;
			break;

		case KT_EXTSORT:
			qbthr->BkScanInternal = BkScanExtSortInternal ;
			qbthr->RcScanLeaf     = RcScanExtSortLeaf; 
			break;

		default: // unsupported KT
			SetErrCode(phr, E_INVALIDARG);
			goto exit1;
			break;
	}

	assert(! ( qbthr->bth.bFlags & ( fFSDirty) ) );

	if ((bFlags | qbthr->bth.bFlags) & ( fFSReadOnly | fFSOpenReadOnly ))
	{
		qbthr->bth.bFlags |= fFSOpenReadOnly;
	}

	_GLOBALUNLOCK(hbt);
	return hbt;
}


/***************************************************************************
 *
 * Function:     GetBtreeParams(hbt, qbtp)
 *
 * Purpose:      open an existing btree
 *
 * ASSUMES
 *   args IN:    hbt       - handle to btree info
 *	 args OUT:
 *               qbtp	   - Btree params (key type info, etc.).  All members
 *							 of the structure are set EXCEPT for the hfs.
 *
 * PROMISES
 *   returns:    nothing
 *
 ***************************************************************************/
VOID FAR PASCAL GetBtreeParams(HBT hbt, BTREE_PARAMS FAR *qbtp)
{
	if (hbt != NULL && qbtp != NULL)
	{
	    QBTHR qbthr;

		qbthr = (QBTHR) _GLOBALLOCK(hbt);

		// Copy btree info to BTREE_PARMS.
		qbtp->cbBlock = qbthr->bth.cbBlock;
		qbtp->dwCodePageID = qbthr->bth.dwCodePageID;
		qbtp->lcid = qbthr->bth.lcid;
		qbtp->dwExtSortInstID = qbthr->bth.dwExtSortInstID;
		qbtp->dwExtSortKeyType = qbthr->bth.dwExtSortKeyType;
		qbtp->bFlags = qbthr->bth.bFlags;
		lstrcpy(qbtp->rgchFormat, qbthr->bth.rgchFormat);

		_GLOBALUNLOCK(hbt);
	}
	else
	{
		ITASSERT(FALSE);
	}
}


/***************************************************************************
 *
- Function:        RcCloseOrFlushHbt(hbt, fClose)
-
 * Purpose:      Close or flush the btree.  Flush only works for directory
 *               btree. (Is this true?  If so, why?)
 *
 * ASSUMES
 *   args IN:    hbt
 *               fClose - TRUE to close the btree, FALSE to flush it
 *
 * PROMISES
 *   returns:    rc
 *   args OUT:   hbt - the btree is still open and cache still exists
 *
 * NOTE:         This function gets called by RcCloseOrFlushHfs() even if
 *               there was an error (just to clean up memory.)
 *
 ***************************************************************************/
HRESULT FAR PASCAL RcCloseOrFlushHbt(HBT hbt, BOOL fClose)
{
	QBTHR qbthr;
	HF    hf;
	int fRet;
	HANDLE hnd;
    HRESULT   errb;


	if (hbt == 0)
		return (E_INVALIDARG);

	qbthr = (QBTHR) _GLOBALLOCK(hbt);
	fRet = S_OK;
	hf = qbthr->hf;

	if (qbthr->ghCache != NULL)
	{
		qbthr->qCache = (QB) _GLOBALLOCK(qbthr->ghCache);
	}

	fRet = S_OK;

	if (qbthr->bth.bFlags & fFSDirty)
	{
		assert(!( qbthr->bth.bFlags & ( fFSReadOnly | fFSOpenReadOnly) ) );

		if (qbthr->ghCache != NULL &&
		    (fRet = RcFlushCache(qbthr)) != S_OK)
		{
exit0:
			/* Close/flush the B-tree */
			
			// Previously
			//if ((fRet = RcCloseOrFlushHf(hf, fClose,
			//      qbthr->bth.bFlags & fFSOptCdRom ? sizeof(BTH):0))!= S_OK) 

			if (fClose)
				fRet=RcCloseHf(hf);
			else
				fRet=RcFlushHf(hf);

			if ((fRet!=S_OK) || (fClose))
			/* Release the memory */
			{       if ((hnd = qbthr->ghCache) != NULL)
				{
					while ((GlobalFlags(hnd) & GMEM_LOCKCOUNT) > 0)
						_GLOBALUNLOCK(hnd);
					if (fClose)
					{
						_GLOBALFREE(hnd);
						qbthr->ghCache = 0;
					}
				}
			}
			else
			{
				if (qbthr->ghCache)
				{       _GLOBALUNLOCK(qbthr->ghCache);
					qbthr->qCache=NULL;
				}
			}

			// Before we free the BTHR, we need to release any sort object we're
			// holding onto.
			if (fClose && qbthr->pITSortKey != NULL)
			{
				qbthr->pITSortKey->Release();
				qbthr->pITSortKey = NULL;
			}

			_GLOBALUNLOCK(hbt);
			if (fClose)
				_GLOBALFREE(hbt);

			return fRet;
		}

		qbthr->bth.bFlags &= ~(fFSDirty);

		if (!FoEquals(FoSeekHf(hf, foNil, wFSSeekSet, &errb),foNil))
			goto exit0;

		LcbWriteHf(hf, &(qbthr->bth), (LONG)sizeof(BTH), &errb);
	}
	goto exit0;
}

/***************************************************************************
 *
- Function:              RcCloseBtreeHbt(hbt)
-
 * Purpose:      Close an open btree.  If it's been modified, save changes.
 *
 * ASSUMES
 *   args IN:    hbt
 *
 * PROMISES
 *   returns:    S_OK or error
 *
 ***************************************************************************/
HRESULT FAR PASCAL RcCloseBtreeHbt(HBT hbt)
{
	return RcCloseOrFlushHbt(hbt, TRUE);
}

#ifdef DEADROUTINE
/***************************************************************************
 *
- Function:     RcFlushHbt(hbt)
-
 * Purpose:      Write any btree changes to disk.
 *               Btree stays open, cache remains.
 *
 * ASSUMES
 *   args IN:    hbt
 *
 * PROMISES
 *   returns:    rc
 *
 ***************************************************************************/
HRESULT FAR PASCAL RcFlushHbt(HBT hbt)
{
	return RcCloseOrFlushHbt(hbt, FALSE);
}
#endif
/***************************************************************************
 *
- Function:     HRESULT RcFreeCacheHbt(hbt)
-
 * Purpose:      Free the btree cache.
 *
 * ASSUMES
 *   args IN:    hbt - ghCache is NULL or allocated; qCache not locked
 *
 * PROMISES
 *   returns:    S_OK or errors 
 *   args OUT:   hbt - ghCache is NULL; qCache is NULL
 *
 ***************************************************************************/
HRESULT FAR PASCAL RcFreeCacheHbt(HBT hbt)
{
	QBTHR qbthr;
	HRESULT    rc = S_OK;

	
	if (hbt == NULL)
	{
		return E_INVALIDARG;
	}
	qbthr = (QBTHR) _GLOBALLOCK(hbt);

	if (qbthr->ghCache != NULL) {
		qbthr->qCache = (QB) _GLOBALLOCK(qbthr->ghCache);
		rc = RcFlushCache(qbthr);
		_GLOBALUNLOCK(qbthr->ghCache);
		_GLOBALFREE(qbthr->ghCache);
		qbthr->ghCache = NULL;
		qbthr->qCache = NULL;
	}

	_GLOBALUNLOCK(hbt);
	return rc;
}
/***************************************************************************
 *
- Function:     RcGetBtreeInfo(hbt, qchFormat, qlcKeys)
-
 * Purpose:      Return btree info: format string and/or number of keys
 *
 * ASSUMES
 *   args IN:    hbt
 *               qchFormat - pointer to buffer for fmt string or NULL
 *               qlcKeys   - pointer to long for key count or NULL
 *               qcbBlock  - pointer to int for block size in bytes or NULL
 *
 * PROMISES
 *   returns:    rc
 *   args OUT:   qchFormat - btree format string copied here
 *               qlcKeys   - gets number of keys in btree
 *               qcbBlock  - gets number of bytes in a block
 *
 ***************************************************************************/
HRESULT FAR PASCAL RcGetBtreeInfo(HBT hbt, LPBYTE qchFormat,
    QL qlcKeys, QW qcbBlock)
{
	QBTHR qbthr;


	if ((qbthr = (QBTHR) _GLOBALLOCK(hbt)) == NULL)
	return(E_INVALIDARG);
	
	if (qchFormat != NULL)
		STRCPY ((char *) qchFormat, qbthr->bth.rgchFormat);

	if (qlcKeys != NULL)
		*(LPUL)qlcKeys = qbthr->bth.lcEntries;

	if (qcbBlock != NULL)
		*qcbBlock = qbthr->bth.cbBlock;

	_GLOBALUNLOCK(hbt);
	return S_OK;
}
/***************************************************************************
 *
- Function:     RcAbandonHbt(hbt)
-
 * Purpose:      Abandon an open btree.  All changes since btree was opened
 *               will be lost.  If btree was opened with a create, it is
 *               as if the create never happened.
 *
 * ASSUMES
 *   args IN:    hbt
 *
 * PROMISES
 *   returns:    rc
 * +++
 *
 * Method:       Just abandon the file and free memory.
 *
 ***************************************************************************/
PUBLIC HRESULT PASCAL FAR EXPORT_API RcAbandonHbt(HBT hbt)
{
	QBTHR qbthr;
	int fRet;

	/* Sanity check */
	if ((qbthr = (QBTHR) _GLOBALLOCK(hbt)) == NULL)
       return(E_INVALIDARG);
       

	if (qbthr->ghCache != NULL)
		_GLOBALFREE(qbthr->ghCache);

	fRet = RcAbandonHf(qbthr->hf);

	_GLOBALUNLOCK(hbt);
	_GLOBALFREE(hbt);

	return fRet;
}

#if 0
/*************************************************************************
 *      @doc    API
 *      
 *      @func   PASCAL FAR | DiskBtreeCreate |
 *              Given a pointer to the B-tree ofthe file system, the fucntion
 *              will copy it to a disk file.
 *
 *      @parm   QBTHR | qbt |
 *              Pointer to the B-tree structure
 *
 *      @parm   LPSTR | szFilename |
 *              Disk filename
 *
 *      @rdesc  S_OK if succeeded, else various Rc errors
 *
 *      @comm   The current assumption is that the whole B-tree is less
 *                      64K. It is assume that the whole structure will reside
 *                      in memory at runtime.
 *************************************************************************/
HRESULT EXPORT_API PASCAL FAR DiskBtreeCreate (QBTHR qbt, LPSTR szFilename)
{
#ifdef MOSMAP // {
	// Disable function
	return ERR_FAILED;
#else // } {
	DWORD  dwbtSize;                /* Total size of B-tree */
	WORD   wBlockSize;      /* Size of a B-tree block */
	int    fRet;                    /* Return error value */ 
	HANDLE hbtBuf;                  /* Handle to B-tree memory buffer */
	QB     qbtBuf;       /* pointer to B-tree memory buffer */ 
	int    hFile;                   /* temp file handle */
	
	/* Calculate the size of the B-tree buffer */
	wBlockSize = qbt->bth.cbBlock;
	dwbtSize = qbt->bth.bkEOF *  wBlockSize;
		
		if (dwbtSize >= 0xffff)
			return ERR_FAILED;
			
	/* Allocate a buffer to hold the data */
	if ((hbtBuf =  _GLOBALALLOC(GMEM_ZEROINIT|GMEM_SHARE | GMEM_MOVEABLE, dwbtSize)) == NULL) {
		return E_OUTOFMEMORY;
	}
		qbtBuf = (QB)_GLOBALLOCK (hbtBuf);
		
		/* We can read everything at once into memory. First seek to the
		 * correct position
		 */
	if (DwSeekHf(qbt->hf, (LONG)sizeof(BTH), wFSSeekSet) != sizeof(BTH)) {
		fRet = ERR_SEEK_FAILED;
exit0:
		_GLOBALUNLOCK (hbtBuf);
		_GLOBALFREE (hbtBuf);
		return fRet;
	}
		
	/* Read in the whole B-tree */
	if (LcbReadHf(qbt->hf, qbtBuf, dwbtSize) != (LONG)dwbtSize) {
			fRet = ERR_FAILED;
		goto exit0;
	}
	
	/* Open the disk file */
		if ((hFile = _lcreat (szFilename, 0)) == HFILE_ERROR) {
		fRet = ERR_FILECREAT_FAILED;
		goto exit0;
	}
		
		/* Write the B-tree file header. Set bFlags = !fCompressed */
		qbt->bth.bFlags = 0;
		if ((_lwrite (hFile, (QB)&qbt->bth, sizeof(BTH))) != sizeof(BTH)) {
			fRet = ERR_CANTWRITE;
exit1:
		_lclose (hFile);
		goto exit0;
		}
		
		/* Write the whole B-tree */
		if ((_lwrite (hFile, qbtBuf, (WORD)dwbtSize)) != (WORD)dwbtSize) {
			fRet = ERR_CANTWRITE;
			goto exit1;
		}       
	
	fRet = S_OK;
	goto exit0;
#endif //}
}
 
/*************************************************************************
 *      @doc    API
 *
 *      @func   HBT PASCAL FAR | DiskBtreeLoad |
 *              :Load a B-tree structure saved on disk into memroy
 *
 *      @parm   LPSTR | szFilename | 
 *              Disk filename
 *
 *      @rdesc  The function returns a handle to the B-tree in memory
 *                      if succeeded, 0 otherwise
 * 
 *      @comm   The current assumption is that the whole B-tree is less
 *                      64K. It is assume that the whole structure will reside
 *                      in memory at runtime.
 *************************************************************************/

HBT EXPORT_API PASCAL FAR DiskBtreeLoad (LPSTR szFilename)
{
	int    hFile;                   /* File handle */
	BTH    btHeader;                /* B-tree header */ 
	HANDLE hbt = 0;         /* Handle to B-tree structure */
	DWORD  dwbtSize;                /* Size of B-tree */
	QBTHR  qbt;                             /* Pointer to B-tree structure */
	HRESULT     fRet;                    /* Error return code */
	
	/* Open the file */
	if ((hFile = _lopen ((QB)szFilename, OF_READ)) == HFILE_ERROR) {
		SetErrCode (ERR_NOTEXIST);
		return NULL;
	}
	
	/* Read in the header */
	if ((_lread (hFile, (QB)&btHeader, sizeof (BTH))) != sizeof (BTH)) {
		fRet = ERR_CANTREAD;
exit0:
		SetErrCode (fRet);
		_lclose (hFile);
		return hbt;
	}

	/* MAC swapping stuffs */

	btHeader.wMagic = SWAPWORD(btHeader.wMagic);
	btHeader.cbBlock = SWAPWORD(btHeader.cbBlock);
	btHeader.bkFirst = SWAPLONG(btHeader.bkFirst);
	btHeader.bkLast = SWAPLONG(btHeader.bkLast);
	btHeader.bkRoot = SWAPLONG(btHeader.bkRoot);
	btHeader.bkFree = SWAPLONG(btHeader.bkFree);
	btHeader.bkEOF = SWAPLONG(btHeader.bkEOF);
	btHeader.lcEntries = SWAPLONG(btHeader.lcEntries);
	btHeader.dwCodePageID = SWAPLONG(btHeader.dwCodePageID);
	btHeader.lcid = SWAPLONG(btHeader.lcid);
	btHeader.dwExtSortInstID = SWAPLONG(btHeader.dwExtSortInstID);
	btHeader.dwExtSortKeyType =	SWAPLONG(btHeader.dwExtSortKeyType);

	/* Check for validity */
	if (btHeader.wMagic != wBtreeMagic) {     // check magic number
		fRet = ERR_INVALID;
		goto exit0;
		}

	if (btHeader.bVersion != bBtreeVersion)  {// Check version
		fRet = E_BADVERSION;
		goto exit0;
	}
	
	dwbtSize = btHeader.bkEOF *  btHeader.cbBlock;
	
	/* Currently we do not support a B-tree larger than 64K */
	
	if (dwbtSize >= 0xffff) {
		fRet = ERR_NOTSUPPORTED;
		goto exit0;
	}
	
	/* Allocate the block of memory for the B-tree */
	if ((hbt =  _GLOBALALLOC (GMEM_ZEROINIT | GMEM_SHARE | GMEM_MOVEABLE, sizeof (BTH_RAM) + dwbtSize)) == NULL) {
		fRet = E_OUTOFMEMORY;
		goto exit0;
	}
	
	qbt = (QBTHR)_GLOBALLOCK (hbt);
	
	/* Initialize the structure */
	qbt->bth = btHeader;
	qbt->ghCache = hbt;
	qbt->qCache = (QB)qbt + sizeof (BTH_RAM); 
	
	/* Read in the B-tree */
	if ((_lread (hFile, qbt->qCache, (WORD)dwbtSize)) != dwbtSize) {
		fRet = ERR_CANTREAD;
		_GLOBALUNLOCK (hbt);
		_GLOBALFREE (hbt);
		hbt = 0;
		goto exit0;
	}
	_GLOBALUNLOCK (hbt);
	fRet = S_OK;
	goto exit0; 
}

/*************************************************************************
 *      @doc    API
 *
 *      @func   VOID PASCAL FAR | DiskBtreeFree |
 *              Free the in-memroy B-tree
 *
 *      @parm   HBT | hbt | 
 *              Handle to the B-tree structure
 *
 *************************************************************************/
VOID EXPORT_API PASCAL FAR DiskBtreeFree (HBT hbt)
{
	if (hbt) {
		_GLOBALUNLOCK (hbt);
		_GLOBALFREE (hbt);
	}
}
#endif // if 0

#ifdef _DEBUG
/***************************************************************************
 *
- Function:     VerifyHbt(hbt)
-
 * Purpose:      Verify the consistency of an HBT.  The main criterion
 *               is whether an RcAbandonHbt() would succeed.
 *
 * ASSUMES
 *   args IN:    hbt
 *
 * PROMISES
 *   state OUT:  Asserts on failure.
 *
 * Note:         hbt == NULL is considered OK.
 * +++
 *
 * Method:       Check the qfshr and cache memory.  Check the HF.
 *
 ***************************************************************************/
PUBLIC VOID PASCAL FAR EXPORT_API VerifyHbt(HBT hbt)
{
	QBTHR qbthr;


	if (hbt == NULL) return;

	qbthr = (QBTHR) _GLOBALLOCK(hbt);
	assert(qbthr != NULL);

	VerifyHf(qbthr->hf);
	_GLOBALUNLOCK(hbt);
}
#endif // _DEBUG

/* EOF */