//+-------------------------------------------------------------------------
//
//  Microsoft Windows
//
//  Copyright (C) Microsoft Corporation, 1996 - 1999
//
//  File:       cBFile.cpp
//
//  Contents:   Microsoft Internet Security
//
//  History:    24-Oct-1997 pberkman   created
//
//--------------------------------------------------------------------------

#include    "global.hxx"
#include    "stack.hxx"
#include    "cbfile.hxx"

cBFile_::cBFile_(CRITICAL_SECTION *pCriticalSection, WCHAR *pwszBFilePath, WCHAR *pwszBFileBaseName,
                 DWORD cbKey, DWORD cbData, SHORT sVersion, BOOL *pfCreatedOK)
{
    SECURITY_ATTRIBUTES  sa;
    SECURITY_ATTRIBUTES* psa = NULL;
    SECURITY_DESCRIPTOR  sd;

    SID_IDENTIFIER_AUTHORITY    siaWorldSidAuthority    = SECURITY_WORLD_SID_AUTHORITY;
    PSID                        psidEveryone            = NULL;
    DWORD                       dwAclSize;
    PACL                        pDacl                   = NULL;
    DWORD dwErr = 0;

    pCritical       = pCriticalSection;
    fDirty          = FALSE;
    fReadOnly       = FALSE;
    hKFile          = INVALID_HANDLE_VALUE;
    hDFile          = INVALID_HANDLE_VALUE;
    pbKMap          = NULL;
    pbDMap          = NULL;
    cbDMap          = 0;
    hDMutex         = NULL;

    fUseRecNumAsKey = FALSE;

    *pfCreatedOK    = TRUE;  // will be set to FALSE in case of ANY failure

    pwszPath = NULL;
    pwszBaseName = NULL;

    __try {

    pwszPath        = (LPWSTR) new WCHAR[wcslen(pwszBFilePath) + 1];
    if (pwszPath == NULL)
    {
        goto MemoryError;
    }
    wcscpy(pwszPath, pwszBFilePath);

    pwszBaseName    = (LPWSTR) new WCHAR[wcslen(pwszBFileBaseName) + 1];
    if (pwszBaseName == NULL)
    {
        goto MemoryError;
    }
    wcscpy(pwszBaseName, pwszBFileBaseName);

    memset(&sHeader, 0x00, sizeof(BFILE_HEADER));
    memset(&sRecord, 0x00, sizeof(BFILE_RECORD));

    sHeader.sIntVersion = BFILE_VERSION_1;
    sHeader.sVersion    = (DWORD)sVersion;
    sHeader.cbKey       = cbKey;
    sHeader.cbData      = cbData;

    if ( FIsWinNT() == TRUE )
    {
        if (!AllocateAndInitializeSid(
                &siaWorldSidAuthority,
                1,
                SECURITY_WORLD_RID,
                0, 0, 0, 0, 0, 0, 0,
                &psidEveryone))
        {
            goto ErrorReturn;
        }

        //
        // compute size of ACL
        //
        dwAclSize = 
            sizeof(ACL) +
            ( sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) ) +
            GetLengthSid(psidEveryone);

        //
        // allocate storage for Acl
        //
        if (NULL == (pDacl = (PACL) new BYTE[dwAclSize]))
        {
            goto MemoryError;
        }

        if (!InitializeAcl(pDacl, dwAclSize, ACL_REVISION))
        {
            goto ErrorReturn;
        }

        if (!AddAccessAllowedAce(
                pDacl,
                ACL_REVISION,
                SYNCHRONIZE,// | MUTEX_MODIFY_STATE,
                psidEveryone))
        {
            goto ErrorReturn;
        }

        if ( InitializeSecurityDescriptor( &sd, SECURITY_DESCRIPTOR_REVISION ) == FALSE )
        {
            goto ErrorReturn;
        }

        if ( SetSecurityDescriptorDacl( &sd, TRUE, pDacl, FALSE ) == FALSE )
        {
            goto ErrorReturn;
        }

        sa.nLength = sizeof( SECURITY_ATTRIBUTES );
        sa.lpSecurityDescriptor = &sd;
        sa.bInheritHandle = FALSE;

        psa = &sa;
    }

    hDMutex = CreateMutexU( psa, FALSE, pwszBFileBaseName );
    if ( hDMutex == NULL )
    {
        hDMutex = OpenMutexU( SYNCHRONIZE, FALSE, pwszBFileBaseName );
        if ( hDMutex == NULL )
        {
            dwErr = GetLastError();
            goto ErrorReturn;
        }
    }

    fInitialized    = TRUE;     // set it now becuase OpenFiles uses "Lock".

    if (!(this->OpenFiles()))
    {
        goto FileError;
    }

    } // __try
    __except(EXCEPTION_EXECUTE_HANDLER) 
    {
        if (pbKMap != NULL)
        {
            UnmapViewOfFile(pbKMap);
            pbKMap = NULL;
        }

        if (pbDMap != NULL)
        {
            UnmapViewOfFile(pbDMap);
            pbDMap = NULL;
        }

        if (hDFile != INVALID_HANDLE_VALUE)
        {
            CloseHandle(hDFile);
            hDFile = INVALID_HANDLE_VALUE;
        }

        if (hKFile != INVALID_HANDLE_VALUE)
        {
            CloseHandle(hKFile);
            hKFile = INVALID_HANDLE_VALUE;
        }

        if (hDMutex != NULL)
        {
            CloseHandle(hDMutex);
            hDMutex = NULL;
        }

        if (pwszPath != NULL)
        {
            delete[](pwszPath);
            pwszPath = NULL;
        }

        if (pwszBaseName != NULL)
        {
            delete[](pwszBaseName);
            pwszBaseName = NULL;
        }
        goto ErrorReturn;
    }

    sRecord.cbKey       = sHeader.cbKey;
    sRecord.cbData      = sHeader.cbData;

    if (!(sRecord.pvKey = (void *)new BYTE[cbKey + sizeof(DWORD)]) ||
        !(sRecord.pvData = (void *)new BYTE[cbData + sizeof(DWORD)]))
    {
        goto MemoryError;
    }

    sRecord.pvData = (BYTE *)sRecord.pvData + sizeof(DWORD);    // we use the first dword for rec # (see addrec)

    memset(sRecord.pvKey, 0x00, cbKey);
    memset(sRecord.pvData, 0x00, cbData);

CommonReturn:

    if (pDacl != NULL)
    {
        delete[] pDacl;
    }

    if (psidEveryone)
    {
        FreeSid(psidEveryone);
    }
    return;

ErrorReturn:
        fInitialized    = FALSE;
        *pfCreatedOK    = FALSE;
        goto CommonReturn;

    SET_ERROR_VAR_EX(DBG_SS, MemoryError,       ERROR_NOT_ENOUGH_MEMORY);
    SET_ERROR_VAR_EX(DBG_SS, FileError,         ERROR_FILE_NOT_FOUND);
}

cBFile_::~cBFile_(void)
{
    if (fDirty)
    {
        this->Sort();
    }

    if (pbKMap != NULL)
    {
        UnmapViewOfFile(pbKMap);
    }

    if (pbDMap != NULL)
    {
        UnmapViewOfFile(pbDMap);
    }

    if (hDFile != INVALID_HANDLE_VALUE)
    {
        CloseHandle(hDFile);
    }

    if (hKFile != INVALID_HANDLE_VALUE)
    {
        CloseHandle(hKFile);
    }

    if (hDMutex != NULL)
    {
        CloseHandle(hDMutex);
    }

    if (pwszPath != NULL)
    {
        delete[](pwszPath);
    }

    if (pwszBaseName != NULL)
    {
        delete[](pwszBaseName);
    }

    DELETE_OBJECT(sRecord.pvKey);

    if (sRecord.pvData)
    {
        sRecord.pvData = (BYTE *)sRecord.pvData - sizeof(DWORD);    // we bumped the pointer in the constructer, put it back..

        delete sRecord.pvData;
    }
}

BOOL cBFile_::Initialize(void)
{
    return(fInitialized);
}

DWORD cBFile_::GetNumKeys(void)
{
    DWORD   dwRet;

    dwRet = GetFileSize(this->hKFile, NULL);

    if (dwRet > 0)
    {
        dwRet /= BFILE_KEYSIZE;

        return(dwRet);
    }

    return(0);
}

void cBFile_::setKey(void *pvInKey)
{
    memcpy(sRecord.pvKey, pvInKey, sRecord.cbKey);
}

void cBFile_::setData(void *pvInData)
{
    memcpy(sRecord.pvData, pvInData, sRecord.cbData);
}

BOOL cBFile_::Find(void)
{
    DWORD   cbDOff;
    DWORD   dwLastGood;
    void    *pvKey;
    BOOL    fRet;

    pvKey = new BYTE[sRecord.cbKey];

    if (!(pvKey))
    {
        goto MemoryError;
    }

    memcpy(pvKey, sRecord.pvKey, sRecord.cbKey);

    if (this->BinaryFind(&cbDOff))
    {
        dwLastGood = this->dwFirstNextRecNum;

        while (this->GetPrev(dwLastGood))
        {
            if (memcmp(pvKey, sRecord.pvKey, sRecord.cbKey) != 0)
            {
                break;
            }

            dwLastGood = this->dwFirstNextRecNum;
        }

        if (dwLastGood > 0)
        {
            dwLastGood--;

            delete pvKey;

            return(this->GetNext(dwLastGood));
        }

        delete pvKey;

        __try {

        return(this->GetDataRecord(cbDOff));

        }
        __except(EXCEPTION_EXECUTE_HANDLER) {
            this->UnmapAll();
            SetLastError(GetExceptionCode());
        }

        return(FALSE);
    }

    fRet = FALSE;

    CommonReturn:
        if (pvKey)
        {
            delete pvKey;
        }

        return(fRet);

    ErrorReturn:
        fRet = FALSE;
        goto CommonReturn;

    SET_ERROR_VAR_EX(DBG_SS, MemoryError,       ERROR_NOT_ENOUGH_MEMORY);
}

BOOL cBFile_::GetFirst(void)
{
    BOOL    fRet;

    this->dwFirstNextRecNum = 0;

    if (!(this->pbKMap))
    {
        return(FALSE);
    }

    __try {

    if (!this->ReadHeader())
    {
        goto ErrorReturn;
    }

    DWORD       cbOff;

    memcpy(sRecord.pvKey, &this->pbKMap[0], sRecord.cbKey);
    memcpy(&cbOff, &this->pbKMap[sRecord.cbKey], sizeof(DWORD));

    if (!(this->GetDataRecord(cbOff)))
    {
        goto cBFileCorrupt;
    }

    fRet = TRUE;

    }
    __except(EXCEPTION_EXECUTE_HANDLER) {
        this->UnmapAll();
        SetLastError(GetExceptionCode());
        goto cBFileCorrupt;
    }

    CommonReturn:
        return(fRet);

    ErrorReturn:
        fRet = FALSE;
        goto CommonReturn;

    TRACE_ERROR_EX(DBG_SS, cBFileCorrupt);
}

BOOL cBFile_::GetNext(DWORD dwCurRec)
{
    BOOL    fRet;

    if (!(this->pbKMap))
    {
        return(FALSE);
    }

    if (dwCurRec == 0xffffffff)
    {
        dwCurRec = this->dwFirstNextRecNum;
    }

    DWORD       cbOff;

    dwCurRec++;

    if (((dwCurRec + 1) * BFILE_KEYSIZE) > sHeader.cbSortedEOF)
    {
        goto cBFileNoNext;
    }

    __try {

    memcpy(sRecord.pvKey, &this->pbKMap[dwCurRec * BFILE_KEYSIZE], sRecord.cbKey);
    memcpy(&cbOff, &this->pbKMap[(dwCurRec * BFILE_KEYSIZE) + sRecord.cbKey], sizeof(DWORD));

    if (!(this->GetDataRecord(cbOff)))
    {
        goto cBFileCorrupt;
    }

    this->dwFirstNextRecNum = dwCurRec;

    fRet = TRUE;

    }
    __except(EXCEPTION_EXECUTE_HANDLER) {
        this->UnmapAll();
        SetLastError(GetExceptionCode());
        goto cBFileCorrupt;
    }

    CommonReturn:
        return(fRet);

    ErrorReturn:
        fRet = FALSE;
        this->dwFirstNextRecNum = 0;
        goto CommonReturn;

    TRACE_ERROR_EX(DBG_SS, cBFileNoNext);
    TRACE_ERROR_EX(DBG_SS, cBFileCorrupt);
}

BOOL cBFile_::GetPrev(DWORD dwCurRec)
{
    BOOL    fRet;

    if (!(this->pbKMap) || (sHeader.cbSortedEOF == 0))
    {
        goto cBFileNoPrev;
    }

    if (dwCurRec == 0xffffffff)
    {
        dwCurRec = this->dwFirstNextRecNum;
    }

    DWORD       cb;
    DWORD       cbOff;

    if (dwCurRec < 1)
    {
        goto cBFileNoPrev;
    }

    dwCurRec--;

    if (((dwCurRec + 1) * BFILE_KEYSIZE) >= sHeader.cbSortedEOF)
    {
        goto cBFileNoPrev;
    }

    __try {

    memcpy(sRecord.pvKey, &this->pbKMap[dwCurRec * BFILE_KEYSIZE], sRecord.cbKey);
    memcpy(&cbOff, &this->pbKMap[(dwCurRec * BFILE_KEYSIZE) + sRecord.cbKey], sizeof(DWORD));

    if (!(this->GetDataRecord(cbOff)))
    {
        goto cBFileCorrupt;
    }

    this->dwFirstNextRecNum = dwCurRec;

    fRet = TRUE;

    }
    __except(EXCEPTION_EXECUTE_HANDLER) {
        this->UnmapAll();
        SetLastError(GetExceptionCode());
        goto cBFileCorrupt;
    }

    CommonReturn:
        return(fRet);

    ErrorReturn:
        fRet = FALSE;
        this->dwFirstNextRecNum = 0;
        goto CommonReturn;

    TRACE_ERROR_EX(DBG_SS, cBFileNoPrev);
    TRACE_ERROR_EX(DBG_SS, cBFileCorrupt);
}

BOOL cBFile_::Update(void)
{
    DWORD   cbDOff;

    if ( fReadOnly == TRUE )
    {
        SetLastError( ERROR_ACCESS_DENIED );
        return( FALSE );
    }

    if (this->BinaryFind(&cbDOff))
    {
        this->UpdateDataRecord(cbDOff);

        return(TRUE);
    }

    return(FALSE);
}

BOOL cBFile_::Add(void)
{
    if ( fReadOnly == TRUE )
    {
        SetLastError( ERROR_ACCESS_DENIED );
        return( FALSE );
    }

    if (!this->AddDirtyKey())
    {
        return(FALSE);
    }

    return(TRUE);
}

BOOL cBFile_::Lock(void)
{
    WaitForSingleObject( this->hDMutex, INFINITE );
    return(TRUE);
}

BOOL cBFile_::Unlock(void)
{
    ReleaseMutex( this->hDMutex );
    return( TRUE );
}

void cBFile_::SpeedSort(void)
{
    Stack_      *pcStack;

    if ( fReadOnly == TRUE )
    {
        assert( FALSE && "A sort should not be forced in read-only mode." );
        return;
    }

    pcStack = NULL;

    if (!(this->pbKMap))
    {
        goto RemapError;
    }

    if (!(this->Lock()))
    {
        goto LockFileError;
    }

    //
    // load dirty in memory and sort it
    //
    DWORD       cbCurKey;
    DWORD       cbTotal;

    if (!(pcStack = new Stack_(pCritical)))
    {
        goto MemoryError;
    }

    cbTotal     = GetFileSize(this->hKFile, NULL);
    cbCurKey    = sHeader.cbSortedEOF;

    __try {

    while (cbCurKey < cbTotal)
    {
        pcStack->Add(BFILE_KEYSIZE, &this->pbKMap[cbCurKey]);

        cbCurKey += BFILE_KEYSIZE;
    }

    pcStack->Sort(0, sRecord.cbKey, STACK_SORTTYPE_BINARY);

    //
    //  shuffle into sorted
    //
    DWORD   dwIdx;
    DWORD   dwInsertion;
    DWORD   cbFreeSpace;
    BYTE    *pbKey;
    BYTE    *pbCurrentKey;
    BYTE    *pbStartFreeSpace;

    pbStartFreeSpace    = &this->pbKMap[sHeader.cbSortedEOF];
    cbFreeSpace         = pcStack->Count() * BFILE_KEYSIZE;

    dwIdx = (long)pcStack->Count() - 1;

    while (pbKey = (BYTE *)pcStack->Get(dwIdx))
    {
        //
        //  get the starting point of our "window"
        //
        dwInsertion = this->GetInsertionPoint(pbKey);

        //
        //  move the old data to the current free space "window"
        //
        memmove(&this->pbKMap[dwInsertion + cbFreeSpace], &this->pbKMap[dwInsertion],
                sHeader.cbSortedEOF - dwInsertion);

        //
        //  after this, the insersion point has free space the size of the "window" - key size
        //
        cbFreeSpace -= BFILE_KEYSIZE;

        //
        // add the new key to the end of the "free space" window.
        //
        memcpy(&this->pbKMap[dwInsertion + cbFreeSpace], pbKey, BFILE_KEYSIZE);

        //
        //  keep the end of the search up to date for these insertions....
        //
        sHeader.cbSortedEOF = dwInsertion;

        if (dwIdx == 0)
        {
            break;
        }

        dwIdx--;
    }

    sHeader.cbSortedEOF     = cbTotal;
    sHeader.fDirty          = FALSE;
    fDirty                  = FALSE;

    this->UpdateHeader();

    }
    __except( EXCEPTION_EXECUTE_HANDLER ) {
        this->UnmapAll();
        SetLastError( GetExceptionCode() );
        goto RemapError;
    }

    ErrorReturn:
        this->Unlock();
        DELETE_OBJECT(pcStack);
        return;

    TRACE_ERROR_EX(DBG_SS, LockFileError);
    TRACE_ERROR_EX(DBG_SS, RemapError);

    SET_ERROR_VAR_EX(DBG_SS, MemoryError,       ERROR_NOT_ENOUGH_MEMORY);
}

DWORD cBFile_::GetInsertionPoint(void *pvIn)
{
    DWORD   dwEnd;
    DWORD   dwMiddle;
    DWORD   dwStart;
    DWORD   dwHalf;
    DWORD   dwCur;
    DWORD   dwRet;
    BYTE    *pb;
    int     cmp;

    dwStart     = 0;
    dwEnd       = sHeader.cbSortedEOF / BFILE_KEYSIZE;
    dwMiddle    = dwEnd / 2L;
    dwHalf      = dwMiddle;
    dwCur       = 0;
    if (sHeader.cbSortedEOF >= BFILE_KEYSIZE)
    {
        dwRet   = sHeader.cbSortedEOF - BFILE_KEYSIZE;
    }
    else
    {
        dwRet   = 0;
    }

    for EVER
    {
        pb = this->pbKMap + (dwMiddle * BFILE_KEYSIZE);

        cmp = memcmp(pvIn, pb, sRecord.cbKey);

        if (cmp == 0)
        {
            dwRet = (DWORD)(pb - this->pbKMap);
            break;
        }

        if ( (dwMiddle == 0) ||
             (dwMiddle == (sHeader.cbSortedEOF / BFILE_KEYSIZE)) ||
             ((dwHalf == 0) && (dwMiddle == dwStart)) )
        {
            DWORD   dwFind;

            dwFind  = dwRet;
            cmp     = (-1);

            while ((cmp < 0) && ((dwFind + BFILE_KEYSIZE) <= sHeader.cbSortedEOF))
            {
                cmp = memcmp(pvIn, &this->pbKMap[dwFind], sRecord.cbKey);

                dwRet = dwFind;

                dwFind += BFILE_KEYSIZE;
            }

            break;
        }

        if (cmp < 0)
        {
            dwEnd   = dwMiddle;
            dwRet   = (dwMiddle * BFILE_KEYSIZE);
        }
        else
        {
            dwStart = dwMiddle;
        }

        dwHalf      = (dwEnd - dwStart) / 2L;
        dwMiddle    = dwStart + dwHalf;
    }

    return(dwRet);
}

void *cBFile_::GetDumpKey(DWORD dwIdx, void *pvRetKey, DWORD *pdwRecOffset)
{
    DWORD   dwOffset;

    dwOffset = dwIdx * BFILE_KEYSIZE;

    if (dwOffset >= GetFileSize(this->hKFile, NULL))
    {
        return(NULL);
    }

    __try {

    memcpy(pvRetKey, &this->pbKMap[dwOffset], sRecord.cbKey);

    memcpy(pdwRecOffset, &this->pbKMap[dwOffset + sRecord.cbKey], sizeof(DWORD));

    return(pvRetKey);

    }
    __except(EXCEPTION_EXECUTE_HANDLER) {
        this->UnmapAll();
        SetLastError(GetExceptionCode());
    }

    return(NULL);
}

BOOL cBFile_::GetHeader(BFILE_HEADER *psHeader)
{
    __try {

    if (!this->ReadHeader())
    {
        return(FALSE);
    }

    memcpy(psHeader, &sHeader, sizeof(BFILE_HEADER));

    return(TRUE);

    }
    __except(EXCEPTION_EXECUTE_HANDLER) {
        this->UnmapAll();
        SetLastError(GetExceptionCode());
    }

    return(FALSE);
}

void cBFile_::Sort(void)
{
    if ( fReadOnly == TRUE )
    {
        assert( FALSE && "A sort should not be forced in read-only mode." );
        return;
    }

    EnterCriticalSection(pCritical);

    if (!FIsWinNT())
    {
        //
        // The only reason this is here is to work around some underlying
        // bug in the OS.  If we just call RemapKey() and RemapData() then
        // we end up with some bogus state on the key and data files which will
        // ultimately result in a sharing violation when trying to open the files
        // at a later time.
        //
        if (this->pbKMap != NULL)
        {
            UnmapViewOfFile(this->pbKMap);
            this->pbKMap = NULL;
        }

        if (this->pbDMap != NULL)
        {
            UnmapViewOfFile(this->pbDMap);
            this->pbDMap = NULL;
        }

        if (hDFile != INVALID_HANDLE_VALUE)
        {
            CloseHandle(hDFile);
            hDFile = INVALID_HANDLE_VALUE;
        }

        if (hKFile != INVALID_HANDLE_VALUE)
        {
            CloseHandle(hKFile);
            hKFile = INVALID_HANDLE_VALUE;
        }  

        this->OpenFiles();
    }
    else
    {
        this->RemapKey();
        this->RemapData();
    }

#if 0
    if (sHeader.cbSortedEOF > 0)
    {
        this->SpeedSort();
        LeaveCriticalSection(pCritical);
        return;
    }
#endif

    BYTE        *pb;
    Stack_      *pcStack;

    pcStack = NULL;
    pb      = NULL;

    if (!(this->Lock()))
    {
        goto LockFileError;
    }

    DWORD       dwLen;
    DWORD       cbFile;
    DWORD       dwIdx;
    DWORD       i;
    BYTE        *pbStack;

    if (!(pcStack = new Stack_(pCritical)))
    {
        goto MemoryError;
    }

    dwLen = BFILE_KEYSIZE;

    if (!(pb = new BYTE[dwLen]))
    {
        goto MemoryError;
    }

    cbFile = GetFileSize(this->hKFile, NULL);

    __try {

    for (i = 0; i < cbFile; i += dwLen)
    {
        pcStack->Add(dwLen, &this->pbKMap[i]);
    }

    pcStack->Sort(0, sRecord.cbKey, STACK_SORTTYPE_BINARY);

    dwIdx   = 0;
    i       = 0;
    while (pbStack = (BYTE *)pcStack->Get(dwIdx))
    {
        if ((i + dwLen) > cbFile)
        {
            goto FileSizeError;
        }

        memcpy(&this->pbKMap[i], pbStack, dwLen);

        dwIdx++;
        i += dwLen;
    }


    sHeader.cbSortedEOF     = cbFile;
    sHeader.fDirty          = FALSE;
    fDirty                  = FALSE;

    this->UpdateHeader();

    }
    __except( EXCEPTION_EXECUTE_HANDLER ) {
        this->UnmapAll();
        SetLastError( GetExceptionCode() );
    }

    ErrorReturn:
        this->Unlock();

        DELETE_OBJECT(pcStack);
        DELETE_OBJECT(pb);

        LeaveCriticalSection(pCritical);
        return;

    TRACE_ERROR_EX(DBG_SS, FileSizeError);
    TRACE_ERROR_EX(DBG_SS, LockFileError);

    SET_ERROR_VAR_EX(DBG_SS, MemoryError,       ERROR_NOT_ENOUGH_MEMORY);
}

BOOL cBFile_::AddDirtyKey(void)
{
    DWORD   cbOff;
    DWORD   cb;

    if ( fReadOnly == TRUE )
    {
        assert( FALSE && "Add dirty key should not occur in read-only mode." );
        return(FALSE);
    }

    this->Lock();

    __try {

    if (!this->ReadHeader())
    {
        return(FALSE);
    }

    sHeader.dwLastRecNum++;
    sRecord.dwRecNum = sHeader.dwLastRecNum;

    if ((cbOff = this->AddDataRecord()) != 0xffffffff)
    {
        if (fUseRecNumAsKey)
        {
            memcpy(sRecord.pvKey, &sRecord.dwRecNum, sizeof(DWORD));
        }

        memcpy((BYTE *)sRecord.pvKey + sRecord.cbKey, &cbOff, sizeof(DWORD));   // speed...
        SetFilePointer(this->hKFile, 0, NULL, FILE_END);
        
        if (FIsWinNT())
        {
            WriteFile(this->hKFile, sRecord.pvKey, sRecord.cbKey + sizeof(DWORD), &cb, NULL);
            // WriteFile(this->hKFile, &cbOff, sizeof(DWORD), &cb, NULL);
        }
        else
        {
            if (this->pbKMap != NULL)
            {
                UnmapViewOfFile(this->pbKMap);
                this->pbKMap = NULL;
                WriteFile(this->hKFile, sRecord.pvKey, sRecord.cbKey + sizeof(DWORD), &cb, NULL);
                this->RemapKey();
            }
            else
            {
                WriteFile(this->hKFile, sRecord.pvKey, sRecord.cbKey + sizeof(DWORD), &cb, NULL);
            }
        }

        sHeader.fDirty          = TRUE;
        this->fDirty            = TRUE;

        this->UpdateHeader();
    }

    }
    __except( EXCEPTION_EXECUTE_HANDLER ) {
        this->UnmapAll();
        SetLastError( GetExceptionCode() );
    }

    this->Unlock();

    return(TRUE);
}

BOOL cBFile_::GetDataRecord(DWORD cbDataOffset)
{
    DWORD   cb;

    memset(sRecord.pvData, 0x00, sRecord.cbData);

    if (this->cbDMap < (cbDataOffset + sizeof(DWORD) + sRecord.cbData))
    {
        return(FALSE);
    }

    memcpy(&sRecord.dwRecNum, &this->pbDMap[cbDataOffset], sizeof(DWORD));
    memcpy(sRecord.pvData, &this->pbDMap[cbDataOffset + sizeof(DWORD)], sRecord.cbData);

    return(TRUE);
}

void cBFile_::UpdateDataRecord(DWORD cbDataOffset)
{
    DWORD   cb;

    if ( fReadOnly == TRUE )
    {
        assert( FALSE && "Update data record should not occur in read-only mode." );
        return;
    }

    this->Lock();

    if (this->cbDMap < (cbDataOffset + sizeof(DWORD) + sRecord.cbData))
    {
        this->Unlock();
        return;
    }

    __try {

    memcpy(&this->pbDMap[cbDataOffset + sizeof(DWORD)], sRecord.pvData, sRecord.cbData);

    }
    __except( EXCEPTION_EXECUTE_HANDLER ) {
        this->UnmapAll();
        SetLastError( GetExceptionCode() );
    }

    this->Unlock();
}

DWORD cBFile_::AddDataRecord(void)
{
    DWORD   cbRet;

    if ( fReadOnly == TRUE )
    {
        SetLastError( ERROR_ACCESS_DENIED );
        return( 0xFFFFFFFF );
    }

    //
    // no lock here because it is called from add key which
    // does a lock
    //

    if ((cbRet = SetFilePointer(this->hDFile, 0, NULL, FILE_END)) != 0xffffffff)
    {
        DWORD   cb;
        BYTE    *pv;

        pv = (BYTE *)sRecord.pvData - sizeof(DWORD);
        memcpy(pv, &sRecord.dwRecNum, sizeof(DWORD));

        if (FIsWinNT())
        {
            WriteFile(this->hDFile, pv, sizeof(DWORD) + sRecord.cbData, &cb, NULL);
            // WriteFile(this->hDFile, sRecord.pvData, sRecord.cbData, &cb, NULL);
        }
        else
        {
            if (this->pbDMap != NULL)
            {
                UnmapViewOfFile(this->pbDMap);
                this->pbDMap = NULL;
                WriteFile(this->hDFile, pv, sizeof(DWORD) + sRecord.cbData, &cb, NULL);
                this->RemapData();
            }
            else
            {
                WriteFile(this->hDFile, pv, sizeof(DWORD) + sRecord.cbData, &cb, NULL);
            }           
        }
    }

    return(cbRet);
}

BOOL cBFile_::BinaryFind(DWORD *pcbDataOffset)
{
    if (sHeader.fDirty)
    {
        this->Sort();
    }

    if (sHeader.cbSortedEOF == 0)
    {
        return(FALSE);
    }

    if (!(this->pbKMap))
    {
        return(FALSE);
    }

    DWORD   dwEnd;
    DWORD   dwMiddle;
    DWORD   dwStart;
    DWORD   dwHalf;
    DWORD   dwCur;
    void    *pv;
    int     cmp;

    dwStart     = 0;
    dwEnd       = sHeader.cbSortedEOF / BFILE_KEYSIZE;
    dwHalf = dwMiddle    = dwEnd / 2L;
    dwCur       = 0;

    __try {

    for EVER
    {
        pv = (void *)(this->pbKMap + (dwMiddle * BFILE_KEYSIZE));

        cmp = memcmp(sRecord.pvKey, pv, sRecord.cbKey);

        if (cmp == 0)
        {
            memcpy(pcbDataOffset, (char *)pv + sRecord.cbKey, sizeof(DWORD));

            this->dwFirstNextRecNum = (DWORD)((BYTE *)pv - this->pbKMap) / BFILE_KEYSIZE;

            return(TRUE);
        }

        if ((dwMiddle == 0) || (dwMiddle == (sHeader.cbSortedEOF / BFILE_KEYSIZE)) ||
            ((dwHalf == 0) && (dwMiddle == dwStart)))
        {
            break;
        }

        if (cmp < 0)
        {
            dwEnd   = dwMiddle;
        }
        else
        {
            dwStart = dwMiddle;
        }

        dwHalf      = (dwEnd - dwStart) / 2L;
        dwMiddle    = dwStart + dwHalf;
    }

    }
    __except( EXCEPTION_EXECUTE_HANDLER ) {
        this->UnmapAll();
        SetLastError( GetExceptionCode() );
    }

    return(FALSE);
}

BOOL cBFile_::ReadHeader(void)
{
    if ((this->pbDMap) && (this->cbDMap >= (sizeof(BFILE_HEADER) + BFILE_SIZEOFSIG)))
    {
        memcpy(&this->sHeader, &this->pbDMap[BFILE_SIZEOFSIG], sizeof(BFILE_HEADER));
    }
    else if (SetFilePointer(this->hDFile, BFILE_SIZEOFSIG, NULL, FILE_BEGIN) != 0xffffffff)
    {
        DWORD   cb;

        if (!ReadFile(this->hDFile, &this->sHeader, sizeof(BFILE_HEADER), &cb, NULL))
        {
            return(FALSE);
        }
    }
    else
    {
        memset(&this->sHeader, 0x00, sizeof(BFILE_HEADER));
    }

    return(TRUE);
}

BOOL cBFile_::UpdateHeader(void)
{
    if ( fReadOnly == TRUE )
    {
        SetLastError( ERROR_ACCESS_DENIED );
        return( FALSE );
    }

    if ((this->pbDMap) && (this->cbDMap >= (sizeof(BFILE_HEADER) + BFILE_SIZEOFSIG)))
    {
        memcpy(&this->pbDMap[BFILE_SIZEOFSIG], &this->sHeader, sizeof(BFILE_HEADER));
        return(TRUE);
    }

    if (SetFilePointer(this->hDFile, BFILE_SIZEOFSIG, NULL, FILE_BEGIN) != 0xffffffff)
    {
        DWORD   cbWritten;

        WriteFile(this->hDFile, &this->sHeader, sizeof(BFILE_HEADER), &cbWritten, NULL);

        return(TRUE);
    }

    return(FALSE);
}

BOOL cBFile_::OpenFiles(void)
{
    BOOL    fRet = TRUE;
    WCHAR   wszFile[MAX_PATH];
    DWORD   cbWritten;
    int     iBaseEnd;

    wcscpy(&wszFile[0], pwszPath);
    if (wszFile[wcslen(pwszPath) - 1] != L'\\')
    {
        wcscat(&wszFile[0], L"\\");
    }
    wcscat(&wszFile[0], pwszBaseName);
    iBaseEnd = wcslen(&wszFile[0]);

    wcscpy(&wszFile[iBaseEnd], BFILE_DATAEXT);

    hDFile = CreateFileU(&wszFile[0], GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
                         NULL, OPEN_EXISTING, 0, NULL);

    if (hDFile == INVALID_HANDLE_VALUE)
    {
        if ( GetLastError() == ERROR_ACCESS_DENIED )
        {
            hDFile = CreateFileU(&wszFile[0], GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
                                 NULL, OPEN_EXISTING, 0, NULL);

            fReadOnly = TRUE;
        }
        else
        {
            hDFile = CreateFileU(&wszFile[0], GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
                                  NULL, CREATE_NEW, 0, NULL);
        }

        if (hDFile == INVALID_HANDLE_VALUE)
        {
            return(FALSE);
        }

        if ( fReadOnly == FALSE )
        {
            WriteFile(this->hDFile, BFILE_SIG, BFILE_SIZEOFSIG, &cbWritten, NULL);

            this->Lock();

            __try {

            fRet = this->UpdateHeader();

            }
            __except( EXCEPTION_EXECUTE_HANDLER ) {
                fRet = FALSE;
            }

            this->Unlock();

            if ( fRet == FALSE )
            {
                return( FALSE );
            }

            wcscpy(&wszFile[iBaseEnd], BFILE_KEYEXT);

            hKFile = CreateFileU(&wszFile[0], GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
                                 NULL, OPEN_ALWAYS, 0, NULL);
        }
        else
        {
            wcscpy(&wszFile[iBaseEnd], BFILE_KEYEXT);

            hKFile = CreateFileU(&wszFile[0], GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
                                 NULL, OPEN_EXISTING, 0, NULL);
        }
    }
    else
    {
        wcscpy(&wszFile[iBaseEnd], BFILE_KEYEXT);

        hKFile = CreateFileU(&wszFile[0], GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
                             NULL, OPEN_ALWAYS, 0, NULL);
    }

    if ((hDFile == INVALID_HANDLE_VALUE) || (hKFile == INVALID_HANDLE_VALUE))
    {
        return(FALSE);
    }

    fRet &= this->RemapKey();
    fRet &= this->RemapData();

    if ( fRet == FALSE )
    {
        return( FALSE );
    }

    __try {

    if (!this->ReadHeader())
    {
        return FALSE;
    }

    }
    __except( EXCEPTION_EXECUTE_HANDLER ) {
        fRet = FALSE;
    }

    return(fRet);
}


BOOL cBFile_::RemapKey(void)
{
    LPBYTE pbOldMap;
    HANDLE hMappedFile;
    DWORD  PageAccess = PAGE_READWRITE;
    DWORD  FileMapAccess = FILE_MAP_WRITE;

    pbOldMap = this->pbKMap;

    if ( fReadOnly == TRUE )
    {
        PageAccess = PAGE_READONLY;
        FileMapAccess = FILE_MAP_READ;
    }

    hMappedFile = CreateFileMapping(this->hKFile, NULL, PageAccess, 0, 0, NULL);

    if (!(hMappedFile) || (hMappedFile == INVALID_HANDLE_VALUE))
    {
        return(TRUE);   // could be the first call!
    }

    this->pbKMap = (BYTE *)MapViewOfFile(hMappedFile, FileMapAccess, 0, 0, 0);
    if ( this->pbKMap != NULL )
    {
        if ( pbOldMap != NULL )
        {
            UnmapViewOfFile( pbOldMap );
        }

        this->cbKMap = GetFileSize(this->hKFile, NULL);
    }
    else
    {
        this->pbKMap = pbOldMap;
    }

    CloseHandle(hMappedFile);

    if ( ( pbOldMap == NULL ) && ( this->pbKMap == NULL ) )
    {
        return(FALSE);
    }

    return(TRUE);
}

BOOL cBFile_::RemapData(void)
{
    LPBYTE pbOldMap;
    HANDLE hMappedFile;
    DWORD  PageAccess = PAGE_READWRITE;
    DWORD  FileMapAccess = FILE_MAP_WRITE;

    pbOldMap = this->pbDMap;

    if ( fReadOnly == TRUE )
    {
        PageAccess = PAGE_READONLY;
        FileMapAccess = FILE_MAP_READ;
    }

    hMappedFile = CreateFileMapping(this->hDFile, NULL, PageAccess, 0, 0, NULL);

    if (!(hMappedFile) || (hMappedFile == INVALID_HANDLE_VALUE))
    {
        return(TRUE);   // could be the first call!
    }

    this->pbDMap = (BYTE *)MapViewOfFile(hMappedFile, FileMapAccess, 0, 0, 0);
    if ( this->pbDMap != NULL )
    {
        if ( pbOldMap != NULL )
        {
            UnmapViewOfFile( pbOldMap );
        }

        this->cbDMap = GetFileSize(this->hDFile, NULL);
    }
    else
    {
        this->pbDMap = pbOldMap;
    }

    CloseHandle(hMappedFile);

    if ( ( pbOldMap == NULL ) && ( this->pbDMap == NULL ) )
    {
        return(FALSE);
    }

    return(TRUE);
}

void cBFile_::UnmapAll(void)
{
    if (this->pbKMap != NULL)
    {
        UnmapViewOfFile(this->pbKMap);
        this->pbKMap = NULL;
    }

    if (this->pbDMap != NULL)
    {
        UnmapViewOfFile(this->pbDMap);
        this->pbDMap = NULL;
    }
}

