//-------------------------------------------------------------------------
// Desc:	Routines for roll-forward logging.
// Tabs:	3
//
//		Copyright (c) 1998-2006 Novell, Inc. All Rights Reserved.
//
//		This program is free software; you can redistribute it and/or
//		modify it under the terms of version 2 of the GNU General Public
//		License as published by the Free Software Foundation.
//
//		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; if not, contact Novell, Inc.
//
//		To contact Novell about this file by physical or electronic mail,
//		you may find current contact information at www.novell.com
//
// $Id: rfl.cpp 12334 2006-01-23 12:45:35 -0700 (Mon, 23 Jan 2006) dsanders $
//-------------------------------------------------------------------------

#include "flaimsys.h"

#define MOD_512(uiNum) \
	(FLMUINT) ((uiNum) & 511)

#define ON_512_BYTE_BOUNDARY(uiNum) \
	(!MOD_512( uiNum))

#define ROUND_DOWN_TO_NEAREST_512(uiNum) \
	(FLMUINT) ((uiNum) & (~((FLMUINT) 511)))

FSTATIC RCODE RflCheckMaxLogged(
	FLMUINT *	puiMaxBytesNeededRV,
	FLMUINT		uiPacketsLogged,
	FLMUINT *	puiCurrTotalLoggedRV,
	FLMUINT		uiBytesToLog);

FSTATIC void RflChangeCallback(
	GRD_DifferenceData &	DiffData,
	void *					CallbackData);

/********************************************************************
Desc:
*********************************************************************/
F_Rfl::F_Rfl()
{
	m_pFile = NULL;
	m_hBufMutex = F_MUTEX_NULL;
	m_pCommitBuf = NULL;
	m_pCurrentBuf = NULL;
	m_uiRflWriteBufs = DEFAULT_RFL_WRITE_BUFFERS;
	m_uiBufferSize = DEFAULT_RFL_BUFFER_SIZE;
	f_memset( &m_Buf1, 0, sizeof( m_Buf1));
	f_memset( &m_Buf2, 0, sizeof( m_Buf2));
	m_bKeepRflFiles = FALSE;
	m_uiRflMinFileSize = DEFAULT_MIN_RFL_FILE_SIZE;
	m_uiRflMaxFileSize = DEFAULT_MAX_RFL_FILE_SIZE;
	m_pFileHdl = NULL;
	m_uiLastRecoverFileNum = 0;
	f_memset( m_ucCurrSerialNum, 0, sizeof( m_ucCurrSerialNum));
	m_bLoggingOff = FALSE;
	m_bLoggingUnknown = FALSE;
	m_uiUnknownPacketLen = 0;
	m_bReadingUnknown = FALSE;
	m_uiUnknownPacketBodyLen = 0;
	m_pucUnknownPacketBody = NULL;
	m_uiUnknownBodyLenProcessed = 0;
	m_uiUnknownPacketRc = FERR_OK;
	m_uiTransStartFile = 0;
	m_uiTransStartAddr = 0;
	m_uiCurrTransID = 0;
	m_uiLastTransID = 0;
	m_uiLastLoggedCommitTransID = 0;
	m_uiOperCount = 0;
	m_uiRflReadOffset = 0;
	m_uiFileEOF = 0;
	m_pRestore = NULL;
	f_memset( m_szDbPrefix, 0, sizeof(m_szDbPrefix));
	f_memset( m_szRflDir, 0, sizeof(m_szRflDir));
	m_bRflDirSameAsDb = FALSE;
	m_bCreateRflDir = FALSE;
	f_memset( m_ucNextSerialNum, 0, sizeof(m_ucNextSerialNum));
	m_bRflVolumeOk = TRUE;
	m_bRflVolumeFull = FALSE;
}

/********************************************************************
Desc:
*********************************************************************/
F_Rfl::~F_Rfl()
{
	flmAssert( !m_bLoggingUnknown);

	if( m_Buf1.pIOBuffer)
	{
		m_Buf1.pIOBuffer->Release();
		m_Buf1.pIOBuffer = NULL;
	}

	if( m_Buf2.pIOBuffer)
	{
		m_Buf2.pIOBuffer->Release();
		m_Buf2.pIOBuffer = NULL;
	}

	if( m_Buf1.pBufferMgr)
	{
		flmAssert( !m_Buf1.pBufferMgr->isIOPending());

		m_Buf1.pBufferMgr->Release();
		m_Buf1.pBufferMgr = NULL;
	}

	if( m_Buf2.pBufferMgr)
	{
		flmAssert( !m_Buf2.pBufferMgr->isIOPending());

		m_Buf2.pBufferMgr->Release();
		m_Buf2.pBufferMgr = NULL;
	}

	if( m_hBufMutex != F_MUTEX_NULL)
	{
		f_mutexDestroy( &m_hBufMutex);
	}

	if( m_pFileHdl)
	{
		m_pFileHdl->Release();
		m_pFileHdl = NULL;
		m_pFile = NULL;
	}
}

/********************************************************************
Desc:		Returns a boolean indicating whether or not we are at
			the end of the RFL log - will only be TRUE when we are
			doing recovery.
*********************************************************************/
FLMBOOL F_Rfl::atEndOfLog(void)
{
	return( (!m_pRestore && m_uiFileEOF &&
				m_pCurrentBuf->uiRflFileOffset +
				m_pCurrentBuf->uiRflBufBytes >= m_uiFileEOF &&
				m_uiRflReadOffset == m_pCurrentBuf->uiRflBufBytes &&
				m_pCurrentBuf->uiCurrFileNum == m_uiLastRecoverFileNum) 
					? TRUE 
					: FALSE);
}

/********************************************************************
Desc:	Gets the base RFL file name - does not have directory part.
		This needs to be separate from the F_Rfl object so it can
		be called without having to instantiate and set up an F_Rfl
		object.
*********************************************************************/
void rflGetBaseFileName(
	FLMUINT			uiDbVersion,
	const char *	pszDbPrefix,
	FLMUINT			uiFileNum,
	char *			pszBaseNameOut)
{
	FLMINT		iCnt = 0;
	FLMUINT		uiDigit;
	char *		pszTmp = pszBaseNameOut;

	if (uiDbVersion < FLM_FILE_FORMAT_VER_4_3)
	{

		// Output the database name prefix (up to three characters).

		f_strcpy( pszTmp, pszDbPrefix);
		while (*pszTmp)
		{
			pszTmp++;
		}

		// Output as five digit base 36 number.

		pszTmp += 4;
		while (iCnt < 5)
		{
			uiDigit = (FLMUINT) (uiFileNum % 36);
			uiFileNum /= 36;
			if (uiDigit <= 9)
			{
				uiDigit += NATIVE_ZERO;
			}
			else
			{
				uiDigit += (NATIVE_LOWER_A - 10);
			}

			*pszTmp = (FLMBYTE) uiDigit;
			pszTmp--;
			iCnt++;
		}

		// Skip to end of digits and append ".log" to name

		pszTmp += 6;
		f_strcpy( pszTmp, ".log");
	}
	else
	{

		// Output as eight digit hex number.

		pszTmp += 7;
		while (iCnt < 8)
		{
			uiDigit = (FLMUINT) (uiFileNum & 0xF);
			uiFileNum >>= 4;
			if (uiDigit <= 9)
			{
				uiDigit += NATIVE_ZERO;
			}
			else
			{
				uiDigit += (NATIVE_LOWER_A - 10);
			}

			*pszTmp = (FLMBYTE) uiDigit;
			pszTmp--;
			iCnt++;
		}

		// Skip to end of digits and append ".log" to name

		pszTmp += 9;
		f_strcpy( pszTmp, ".log");
	}
}

/********************************************************************
Desc:		Gets the base RFL file name - does not have directory part.
*********************************************************************/
void F_Rfl::getBaseRflFileName(
	FLMUINT	uiFileNum,
	char *	pszBaseName)
{
	rflGetBaseFileName( m_pFile->FileHdr.uiVersionNum, 
		m_szDbPrefix, uiFileNum, pszBaseName);
}

/********************************************************************
Desc:	Generates the full roll forward log file name. Name is based
		on the sequence number and the first three characters of the
		database if the DB is less than version 4.3. 
		Otherwise, it is a hex number.
*********************************************************************/
RCODE F_Rfl::getFullRflFileName(
	FLMUINT	uiFileNum,
	char *	pszRflFileName)
{
	RCODE 	rc = FERR_OK;
	char		szBaseName[F_FILENAME_SIZE];

	// Get the directory name.

	f_strcpy( pszRflFileName, m_szRflDir);

	// Get the base RFL file name.

	getBaseRflFileName( uiFileNum, szBaseName);

	// Append the two together.

	if (RC_BAD( rc = gv_FlmSysData.pFileSystem->pathAppend( 
		pszRflFileName, szBaseName)))
	{
		goto Exit;
	}

Exit:

	return (rc);
}

/********************************************************************
Desc:		Positions to the offset specified in the RFL file.
*********************************************************************/
RCODE F_Rfl::positionTo(
	FLMUINT			uiFileOffset)
{
	RCODE				rc = FERR_OK;
	FLMUINT			uiBytesRead;

	// Should never be attempting to position to something less than 512 -
	// the header is stored in the first 512 bytes.

	flmAssert( uiFileOffset >= 512);

	// If the position is within our current buffer, see if we can adjust
	// things without having to go back and re-read the buffer from disk.

	if (m_pCurrentBuf->uiRflBufBytes &&
		 uiFileOffset >= m_pCurrentBuf->uiRflFileOffset &&
		 uiFileOffset <= m_pCurrentBuf->uiRflFileOffset +
		 m_pCurrentBuf->uiRflBufBytes)
	{

		// Whatever is in the buffer beyond uiFileOffset is irrelevant and
		// can be discarded.

		m_pCurrentBuf->uiRflBufBytes = uiFileOffset - 
												 m_pCurrentBuf->uiRflFileOffset;
	}
	else
	{

		// Populate the buffer from the 512 byte boundary that is just
		// before the offset we are trying to position to.

		m_pCurrentBuf->uiRflFileOffset = ROUND_DOWN_TO_NEAREST_512( uiFileOffset);
		m_pCurrentBuf->uiRflBufBytes = MOD_512( uiFileOffset);

		if (m_pCurrentBuf->uiRflBufBytes)
		{
			if (RC_BAD( rc = m_pFileHdl->read( 
				m_pCurrentBuf->uiRflFileOffset, m_pCurrentBuf->uiRflBufBytes,
				m_pCurrentBuf->pIOBuffer->getBufferPtr(), &uiBytesRead)))
			{
				if (rc == FERR_IO_END_OF_FILE)
				{
					rc = RC_SET_AND_ASSERT( FERR_NOT_RFL);
				}
				else
				{
					m_bRflVolumeOk = FALSE;
				}

				goto Exit;
			}
			else if (uiBytesRead < m_pCurrentBuf->uiRflBufBytes)
			{
				rc = RC_SET_AND_ASSERT( FERR_NOT_RFL);
				goto Exit;
			}
		}
	}

Exit:

	return (rc);
}

/********************************************************************
Desc:	Get the ACTUAL RFL directory, using as input parameters the
		database version, the name of the database, and the
		user specified RFL directory.  Also return the database
		prefix.
*********************************************************************/
RCODE rflGetDirAndPrefix(
	FLMUINT			uiDbVersionNum,
	const char *	pszDbFileName,
	const char *	pszRflDirIn,
	char *			pszRflDirOut,
	char *			pszDbPrefixOut)
{
	RCODE 		rc = FERR_OK;
	char			szDbPath[ F_PATH_MAX_SIZE];
	char			szBaseName[ F_FILENAME_SIZE];

	// Parse the database name into directory and base name

	if (RC_BAD( rc = gv_FlmSysData.pFileSystem->pathReduce( 
		pszDbFileName, szDbPath, szBaseName)))
	{
		goto Exit;
	}

	// Get the base path

	flmGetDbBasePath( pszDbPrefixOut, szBaseName, NULL);

	if (uiDbVersionNum >= FLM_FILE_FORMAT_VER_4_3)
	{

		// Determine the RFL directory. If one was specified, it is
		// whatever was specified. Otherwise, it is relative to the database
		// directory.

		if (pszRflDirIn && *pszRflDirIn)
		{
			f_strcpy( pszRflDirOut, pszRflDirIn);
		}
		else
		{
			f_strcpy( pszRflDirOut, szDbPath);
		}

		// For 4.3 and above, the RFL files go in a subdirectory underneath
		// the directory where the database is located or the specified
		// directory.

		f_strcpy( szBaseName, pszDbPrefixOut);
		f_strcat( szBaseName, ".rfl");
		gv_FlmSysData.pFileSystem->pathAppend( pszRflDirOut, szBaseName);
	}
	else
	{
		f_strcpy( pszRflDirOut, szDbPath);
	}

Exit:

	return (rc);
}

/********************************************************************
Desc:		Set the RFL directory.  If pszRflDir is NULL or empty string,
			the RFL directory is set to the same directory as the
			database.
*********************************************************************/
RCODE F_Rfl::setRflDir(
	const char *	pszRflDir)
{

	// Better have set up the FFILE pointer.

	flmAssert( m_pFile != NULL);

	m_bRflDirSameAsDb = (!pszRflDir || !(*pszRflDir)) ? TRUE : FALSE;

	flmAssert( m_pFile->FileHdr.uiVersionNum);

	if (m_pFile->FileHdr.uiVersionNum < FLM_FILE_FORMAT_VER_4_3)
	{

		// Don't allow RFL directory to be specified for versions less than
		// 4.3

		pszRflDir = NULL;
		m_bRflDirSameAsDb = TRUE;
	}

	m_bCreateRflDir =
		(m_pFile->FileHdr.uiVersionNum >= FLM_FILE_FORMAT_VER_4_3)
		? TRUE
		: FALSE;
	return( rflGetDirAndPrefix(
					m_pFile->FileHdr.uiVersionNum, m_pFile->pszDbPath,
					pszRflDir, m_szRflDir, m_szDbPrefix));
}

/********************************************************************
Desc:	Gets an RFL file name - based on DB name and RFL directory.
*********************************************************************/
RCODE rflGetFileName(
	FLMUINT			uiDbVersion,
	const char *	pszDbName,
	const char *	pszRflDir,
	FLMUINT			uiFileNum,
	char *			pszRflFileName)
{
	RCODE			rc = FERR_OK;
	char			szDbPrefix[ F_FILENAME_SIZE];
	char			szBaseName[ F_FILENAME_SIZE];

	// Get the full RFL file name.

	if (RC_BAD( rc = rflGetDirAndPrefix( uiDbVersion, pszDbName, pszRflDir,
				  pszRflFileName, szDbPrefix)))
	{
		goto Exit;
	}

	rflGetBaseFileName( uiDbVersion, szDbPrefix, uiFileNum, szBaseName);
	if (RC_BAD( rc = gv_FlmSysData.pFileSystem->pathAppend(
		pszRflFileName, szBaseName)))
	{
		goto Exit;
	}

Exit:

	return (rc);
}

/********************************************************************
Desc:	Gets an RFL file number from the RFL file name.
*********************************************************************/
FLMBOOL rflGetFileNum(
	FLMUINT				uiDbVersion,
	const char *		pszDbPrefix,
	const char *		pszRflFileName,
	FLMUINT *			puiFileNum)
{
	FLMBOOL			bGotNum = FALSE;
	char				szDir[ F_PATH_MAX_SIZE];
	char				szBaseName[ F_FILENAME_SIZE];
	char *			pszTmp;
	FLMUINT			uiCharCnt;

	if (RC_BAD( gv_FlmSysData.pFileSystem->pathReduce( 
		pszRflFileName, szDir, szBaseName)))
	{
		goto Exit;
	}

	// See if it has a .log extension.

	pszTmp = &szBaseName[0];
	while (*pszTmp && *pszTmp != '.')
	{
		pszTmp++;
	}

	// If we do not have a .log extension, it is not a legitimate RFL file.

	if (f_stricmp( pszTmp, ".log") != 0)
	{
		goto Exit;
	}

	// Parse out the name according to the rules for this DB version.

	*pszTmp = 0;			// Set period to zero
	pszTmp = &szBaseName[0];
	*puiFileNum = 0;
	uiCharCnt = 0;
	if (uiDbVersion >= FLM_FILE_FORMAT_VER_4_3)
	{

		// Name up to the period should be a hex number

		while (*pszTmp)
		{
			(*puiFileNum) <<= 4;
			if (*pszTmp >= NATIVE_ZERO && *pszTmp <= NATIVE_NINE)
			{
				*puiFileNum += (FLMUINT) (*pszTmp - NATIVE_ZERO);
			}
			else if (*pszTmp >= NATIVE_LOWER_A && *pszTmp <= NATIVE_LOWER_F)
			{
				*puiFileNum += ((FLMUINT) (*pszTmp - NATIVE_LOWER_A) + 10);
			}
			else if (*pszTmp >= NATIVE_UPPER_A && *pszTmp <= NATIVE_UPPER_F)
			{
				*puiFileNum += ((FLMUINT) (*pszTmp - NATIVE_UPPER_A) + 10);
			}
			else
			{
				goto Exit;	// Not a hex number
			}

			uiCharCnt++;
			pszTmp++;
		}

		// Better have been exactly 8 hex digits.

		bGotNum = (FLMBOOL) ((uiCharCnt == 8) ? TRUE : FALSE);
	}
	else
	{
		FLMUINT	uiLen = f_strlen( pszTmp);
		FLMUINT	uiPrefixLen = f_strlen( pszDbPrefix);

		// Length of base name without the .log extension better be exactly
		// 5 more characters than the length of the prefix.

		if (uiLen != uiPrefixLen + 5)
		{
			flmAssert( 0);
			goto Exit;
		}

		// Prefix better match.

		while (uiPrefixLen)
		{
			if (f_toupper( *pszTmp) != f_toupper( *pszDbPrefix))
			{
				goto Exit;
			}

			uiPrefixLen--;
			pszTmp++;
			pszDbPrefix++;
		}

		// Rest of the name is the five digits that are a base 36 number.

		while (*pszTmp)
		{
			(*puiFileNum) *= 36;
			if (*pszTmp >= NATIVE_ZERO && *pszTmp <= NATIVE_NINE)
			{
				*puiFileNum += (FLMUINT) (*pszTmp - NATIVE_ZERO);
			}
			else if (*pszTmp >= NATIVE_LOWER_A && *pszTmp <= NATIVE_LOWER_Z)
			{
				*puiFileNum += ((FLMUINT) (*pszTmp - NATIVE_LOWER_A) + 10);
			}
			else if (*pszTmp >= NATIVE_UPPER_A && *pszTmp <= NATIVE_UPPER_Z)
			{
				*puiFileNum += ((FLMUINT) (*pszTmp - NATIVE_UPPER_A) + 10);
			}
			else
			{
				goto Exit;	// Not a base 36 number
			}

			pszTmp++;
		}

		bGotNum = TRUE;
	}

Exit:

	return (bGotNum);
}

/********************************************************************
Desc:		Sets up the RFL object - associating with a file, etc.
*********************************************************************/
RCODE F_Rfl::setup(
	FFILE *				pFile,
	const char *		pszRflDir)
{
	RCODE		rc = FERR_OK;

	// Better not already be associated with an FFILE

	flmAssert( m_pFile == NULL);
	m_pFile = pFile;

	// Allocate memory for the RFL buffers

	if( !gv_FlmSysData.pFileSystem->canDoAsync())
	{
		m_uiRflWriteBufs = 1;
		m_uiBufferSize = DEFAULT_RFL_WRITE_BUFFERS * DEFAULT_RFL_BUFFER_SIZE;
	}

	if( RC_BAD( rc = f_mutexCreate( &m_hBufMutex)))
	{
		goto Exit;
	}
	
	if( RC_BAD( rc = FlmAllocIOBufferMgr( m_uiRflWriteBufs, 
		m_uiRflWriteBufs * m_uiBufferSize, TRUE, &m_Buf1.pBufferMgr)))
	{
		goto Exit;
	}
	
	if( RC_BAD( rc = m_Buf1.pBufferMgr->getBuffer( 
		m_uiBufferSize, &m_Buf1.pIOBuffer)))
	{
		goto Exit;
	}

	if( RC_BAD( rc = FlmAllocIOBufferMgr( m_uiRflWriteBufs, 
		m_uiRflWriteBufs * m_uiBufferSize, TRUE, &m_Buf2.pBufferMgr)))
	{
		goto Exit;
	}

	if( RC_BAD( rc = m_Buf2.pBufferMgr->getBuffer( 
		m_uiBufferSize, &m_Buf2.pIOBuffer)))
	{
		goto Exit;
	}

	m_bLoggingOff = FALSE;
	m_pCurrentBuf = &m_Buf1;
	m_pCurrentBuf->uiRflBufBytes = 0;

	// Set the RFL directory and prefix if necessary.

	if (RC_BAD( rc = setRflDir( pszRflDir)))
	{
		goto Exit;
	}

Exit:

	return (rc);
}

/********************************************************************
Desc:	Wait for the writes of a buffer to finish.  This routine assumes
		that the m_hBufMutex is locked when coming in.  It will ALWAYS
		unlock the mutex before exiting.
*********************************************************************/
RCODE F_Rfl::waitForWrites(
	RFL_BUFFER *	pBuffer,
	FLMBOOL			bIsWriter)
{
	RCODE				rc = FERR_OK;
	RCODE				TempRc;
	RFL_WAITER		Waiter;
	FLMBOOL			bMutexLocked = TRUE;

	// Put self on the wait queue for the buffer.

	Waiter.uiThreadId = f_threadId();
	Waiter.bIsWriter = bIsWriter;
	Waiter.hESem = F_SEM_NULL;
	
	if( RC_BAD( rc = f_semCreate( &Waiter.hESem)))
	{
		goto Exit;
	}

	// Note: rc better be changed to success or write error by the process
	// that signals us.

	rc = RC_SET( FERR_FAILURE);
	Waiter.pRc = &rc;
	Waiter.pNext = NULL;
	
	if (pBuffer->pLastWaiter)
	{
		pBuffer->pLastWaiter->pNext = &Waiter;
	}
	else
	{
		pBuffer->pFirstWaiter = &Waiter;
	}

	pBuffer->pLastWaiter = &Waiter;
	f_mutexUnlock( m_hBufMutex);
	bMutexLocked = FALSE;

	// Now just wait to be signaled.

	if( RC_BAD( TempRc = f_semWait( Waiter.hESem, F_WAITFOREVER)))
	{
		flmAssert( 0);
		rc = TempRc;
	}
	else
	{
		// Process that signaled us better set the rc to something besides
		// FERR_FAILURE.
		
		flmAssert( rc != FERR_FAILURE);
	}

Exit:

	if( Waiter.hESem != F_SEM_NULL)
	{
		f_semDestroy( &Waiter.hESem);
	}

	if( bMutexLocked)
	{
		f_mutexUnlock( m_hBufMutex);
	}

	return (rc);
}

/********************************************************************
Desc:	If a commit is in progress, wait for it to finish.
*********************************************************************/
RCODE F_Rfl::waitForCommit( void)
{
	RCODE		rc = FERR_OK;
	FLMBOOL	bMutexLocked = FALSE;

	// NOTE: If m_pCommitBuf is NULL it cannot be set to something
	// non-NULL except by this thread when this thread ends the
	// transaction. So, there is no need to lock the mutex and re-check if
	// it is NULL.

	if (m_pCommitBuf)
	{
		f_mutexLock( m_hBufMutex);
		bMutexLocked = TRUE;

		// Check m_pCommitBuf again after locking mutex - may have finished.

		if (m_pCommitBuf)
		{

			// waitForWrites will unlock the mutex.

			bMutexLocked = FALSE;
			rc = waitForWrites( m_pCommitBuf, FALSE);
		}
	}

	if (bMutexLocked)
	{
		f_mutexUnlock( m_hBufMutex);
	}

	return (rc);
}

/********************************************************************
Desc:		Write out the header information for an RFL file.
*********************************************************************/
RCODE F_Rfl::writeHeader(
	FLMUINT		uiFileNum,
	FLMUINT		uiEof,
	FLMBYTE *	pucSerialNum,
	FLMBYTE *	pucNextSerialNum,
	FLMBOOL		bKeepSignature)
{
	RCODE			rc = FERR_OK;
	FLMBYTE *	pucBuffer = NULL;
	FLMUINT		uiBytesWritten;

	flmAssert( m_pFile);
	flmAssert( m_pFileHdl);

	if( RC_BAD( rc = f_allocAlignedBuffer( 512, &pucBuffer)))
	{
		goto Exit;
	}

	f_memset( pucBuffer, 0, 512);
	f_memcpy( &pucBuffer[ RFL_NAME_POS], RFL_NAME, RFL_NAME_LEN);
	f_memcpy( &pucBuffer[ RFL_VERSION_POS], RFL_VERSION, RFL_VERSION_LEN);

	UD2FBA( (FLMUINT32)uiFileNum, &pucBuffer[ RFL_FILE_NUMBER_POS]);
	UD2FBA( (FLMUINT32)uiEof, &pucBuffer[ RFL_EOF_POS]);

	if (m_pFile->FileHdr.uiVersionNum >= FLM_FILE_FORMAT_VER_4_3)
	{
		f_memcpy( &pucBuffer[ RFL_DB_SERIAL_NUM_POS],
					&m_pFile->ucLastCommittedLogHdr[LOG_DB_SERIAL_NUM],
					F_SERIAL_NUM_SIZE);
		f_memcpy( &pucBuffer[ RFL_SERIAL_NUM_POS], pucSerialNum, F_SERIAL_NUM_SIZE);
		f_memcpy( &pucBuffer[ RFL_NEXT_FILE_SERIAL_NUM_POS], pucNextSerialNum,
					F_SERIAL_NUM_SIZE);
		f_strcpy( (char *) &pucBuffer[ RFL_KEEP_SIGNATURE_POS],
					((bKeepSignature) ? RFL_KEEP_SIGNATURE : RFL_NOKEEP_SIGNATURE));
	}

	// Write out the header

	if( RC_BAD( rc = m_pFileHdl->write( 0L, 512, pucBuffer, &uiBytesWritten)))
	{
		// Remap disk full error

		if( rc == FERR_IO_DISK_FULL)
		{
			rc = RC_SET( FERR_RFL_DEVICE_FULL);
			m_bRflVolumeFull = TRUE;
		}

		m_bRflVolumeOk = FALSE;
		goto Exit;
	}

	// Flush the file handle to ensure it is forced to disk.

	if( RC_BAD( rc = m_pFileHdl->flush()))
	{

		// Remap disk full error

		if (rc == FERR_IO_DISK_FULL)
		{
			rc = RC_SET( FERR_RFL_DEVICE_FULL);
			m_bRflVolumeFull = TRUE;
		}

		m_bRflVolumeOk = FALSE;
		goto Exit;
	}

Exit:

	if( pucBuffer)
	{
		f_freeAlignedBuffer( &pucBuffer);
	}

	return (rc);
}

/********************************************************************
Desc:		Verifies the header of an RFL file.
*********************************************************************/
RCODE F_Rfl::verifyHeader(
	FLMBYTE *	pucHeader,
	FLMUINT		uiFileNum,
	FLMBYTE *	pucSerialNum)
{
	RCODE rc = FERR_OK;

	flmAssert( m_pFile);

	// Check the RFL name and version number

	if (f_memcmp( &pucHeader[RFL_NAME_POS], RFL_NAME, RFL_NAME_LEN) != 0)
	{
		rc = RC_SET_AND_ASSERT( FERR_NOT_RFL);
		goto Exit;
	}

	if (f_memcmp( &pucHeader[RFL_VERSION_POS], 
					  RFL_VERSION, RFL_VERSION_LEN) != 0)
	{
		rc = RC_SET_AND_ASSERT( FERR_NOT_RFL);
		goto Exit;
	}

	if (m_pFile->FileHdr.uiVersionNum >= FLM_FILE_FORMAT_VER_4_3)
	{

		// Verify the database serial number

		if (f_memcmp( &pucHeader[RFL_DB_SERIAL_NUM_POS],
						 &m_pFile->ucLastCommittedLogHdr[LOG_DB_SERIAL_NUM],
						 F_SERIAL_NUM_SIZE) != 0)
		{
			rc = RC_SET( FERR_BAD_RFL_DB_SERIAL_NUM);
			goto Exit;
		}

		// Verify the serial number that is expected to be on the RFL file.
		// If pucSerialNum is NULL, we will not verify it. This is generally
		// only done during recovery or restore when we are reading through
		// multiple RFL files and we need to verify their serial numbers.

		if (pucSerialNum &&
			 f_memcmp( &pucHeader[RFL_SERIAL_NUM_POS], pucSerialNum,
						 F_SERIAL_NUM_SIZE) != 0)
		{
			rc = RC_SET( FERR_BAD_RFL_SERIAL_NUM);
			goto Exit;
		}

		// Verify the file number.

		if (uiFileNum != (FLMUINT) FB2UD( &pucHeader[RFL_FILE_NUMBER_POS]))
		{
			rc = RC_SET( FERR_BAD_RFL_FILE_NUMBER);
			goto Exit;
		}

		// Save serial numbers from the header.

		f_memcpy( m_ucCurrSerialNum, &pucHeader[RFL_SERIAL_NUM_POS],
					F_SERIAL_NUM_SIZE);
		f_memcpy( m_ucNextSerialNum, &pucHeader[RFL_NEXT_FILE_SERIAL_NUM_POS],
					F_SERIAL_NUM_SIZE);
	}

	// Save some things from the header.

	m_uiFileEOF = (FLMUINT) FB2UD( &pucHeader[RFL_EOF_POS]);

Exit:

	return (rc);
}

/********************************************************************
Desc:		Opens an RFL file.  Verifies the serial number for 4.3 dbs.
*********************************************************************/
RCODE F_Rfl::openFile(
	FLMUINT		uiFileNum,
	FLMBYTE *	pucSerialNum)
{
	RCODE				rc = FERR_OK;
	char				szRflFileName[ F_PATH_MAX_SIZE];
	FLMBYTE *		pucBuffer = NULL;
	FLMUINT			uiBytesRead;

	flmAssert( m_pFile);

	if( RC_BAD( rc = f_allocAlignedBuffer( 512, &pucBuffer)))
	{
		goto Exit;
	}

	// If we have a file open and it is not the file number passed in,
	// close it.

	if( m_pFileHdl)
	{
		if (m_pCurrentBuf->uiCurrFileNum != uiFileNum)
		{
			if (RC_BAD( rc = waitForCommit()))
			{
				goto Exit;
			}

			closeFile();
		}
		else
		{
			goto Exit;	// Will return FERR_OK
		}
	}
	else
	{

		// Should not be able to be in the middle of a commit if we don't
		// have a file open!

		flmAssert( !m_pCommitBuf);
	}

	// Generate the log file name.

	if (RC_BAD( rc = getFullRflFileName( uiFileNum, szRflFileName)))
	{
		goto Exit;
	}

	// Open the file.
	
	f_assert( !m_pFileHdl);

	if (RC_BAD( rc = gv_FlmSysData.pFileSystem->openFile( szRflFileName,
			gv_FlmSysData.uiFileOpenFlags, &m_pFileHdl)))
	{
		goto Exit;
	}

	m_pFileHdl->setMaxAutoExtendSize( m_uiRflMaxFileSize);
	m_pFileHdl->setExtendSize( m_pFile->uiFileExtendSize);
	
	// Read the header.

	if (RC_BAD( rc = m_pFileHdl->read( 0, 512, pucBuffer, &uiBytesRead)))
	{
		if (rc == FERR_IO_END_OF_FILE)
		{
			rc = RC_SET_AND_ASSERT( FERR_NOT_RFL);
		}
		else
		{
			m_bRflVolumeOk = FALSE;
		}

		goto Exit;
	}

	// If there is not enough data in the buffer, it is not an RFL file.

	if (uiBytesRead < 512)
	{
		rc = RC_SET_AND_ASSERT( FERR_NOT_RFL);
		goto Exit;
	}

	// Verify the header information

	if (RC_BAD( rc = verifyHeader( pucBuffer, uiFileNum, pucSerialNum)))
	{
		goto Exit;
	}

	m_pCurrentBuf->uiRflBufBytes = 0;
	m_pCurrentBuf->uiRflFileOffset = 0;
	m_pCurrentBuf->uiCurrFileNum = uiFileNum;
	
Exit:

	if( RC_BAD( rc))
	{
		waitForCommit();
		closeFile();
	}

	if( pucBuffer)
	{
		f_freeAlignedBuffer( &pucBuffer);
	}

	return (rc);
}

/********************************************************************
Desc:		Creates a new roll forward log file.
*********************************************************************/
RCODE F_Rfl::createFile(
	FLMUINT		uiFileNum,
	FLMBYTE *	pucSerialNum,
	FLMBYTE *	pucNextSerialNum,
	FLMBOOL		bKeepSignature)
{
	RCODE			rc = FERR_OK;
	char			szRflFileName[ F_PATH_MAX_SIZE];

	flmAssert( m_pFile);

	// Better not be trying to create the current file

	flmAssert( uiFileNum != m_pCurrentBuf->uiCurrFileNum);

	// If we have a file open close it.

	if (RC_BAD( rc = waitForCommit()))
	{
		goto Exit;
	}

	closeFile();

	// Generate the log file name.

	if (RC_BAD( rc = getFullRflFileName( uiFileNum, szRflFileName)))
	{
		goto Exit;
	}

	// If DB is 4.3 or greater and we are in the same directory as our
	// database files, see if we need to create the subdirectory.
	// Otherwise, the RFL directory should already have been created. If
	// the directory already exists, it is OK - we only try this the first
	// time after setRflDir is called - to either verify that the directory
	// exists, or if it doesn't, to create it.

	if (m_bCreateRflDir)
	{
		// If it already exists, don't attempt to create it.

		if (RC_BAD( rc = gv_FlmSysData.pFileSystem->doesFileExist( m_szRflDir)))
		{
			if (rc != FERR_IO_PATH_NOT_FOUND && rc != FERR_IO_INVALID_PATH)
			{
				goto Exit;
			}
			else
			{
				if (RC_BAD( rc = gv_FlmSysData.pFileSystem->createDir( m_szRflDir)))
				{
					goto Exit;
				}
			}
		}

		m_bCreateRflDir = FALSE;
	}

	// Create the file
	
	f_assert( !m_pFileHdl);

	if( RC_BAD( rc = gv_FlmSysData.pFileSystem->openFile( szRflFileName,
		gv_FlmSysData.uiFileOpenFlags, &m_pFileHdl)))
	{
		if( rc != FERR_IO_PATH_NOT_FOUND)
		{
			goto Exit;
		}
		
		if( RC_BAD( rc = gv_FlmSysData.pFileSystem->createFile( szRflFileName,
			gv_FlmSysData.uiFileCreateFlags, &m_pFileHdl)))
		{
			goto Exit;
		}
	}
	else
	{
		if( RC_BAD( rc = m_pFileHdl->truncateFile( m_pFile->uiFileExtendSize)))
		{
			goto Exit;
		}
	}

	m_pFileHdl->setMaxAutoExtendSize( m_uiRflMaxFileSize);
	m_pFileHdl->setExtendSize( m_pFile->uiFileExtendSize);

	// Initialize the header.

	if (RC_BAD( rc = writeHeader( uiFileNum, 0, pucSerialNum, pucNextSerialNum,
				  bKeepSignature)))
	{
		goto Exit;
	}

	m_pCurrentBuf->uiRflBufBytes = 0;
	m_pCurrentBuf->uiRflFileOffset = 512;
	m_pCurrentBuf->uiCurrFileNum = uiFileNum;
	
	// Update the size of the RFL

	if( m_bKeepRflFiles)
	{
		FLMUINT64		ui64RflDiskUsage;
		
		if( RC_BAD( rc = flmRflCalcDiskUsage( m_szRflDir, m_szDbPrefix,
			m_pFile->FileHdr.uiVersionNum, &ui64RflDiskUsage)))
		{
			goto Exit;
		}
		
		f_mutexLock( gv_FlmSysData.hShareMutex);
		m_pFile->ui64RflDiskUsage = ui64RflDiskUsage;
		f_mutexUnlock( gv_FlmSysData.hShareMutex);
	}

	
Exit:

	// Close the RFL log file AND delete it if we were not successful.

	if (RC_BAD( rc))
	{
		closeFile();
		(void) gv_FlmSysData.pFileSystem->deleteFile( szRflFileName);
	}

	return (rc);
}

/********************************************************************
Desc:		Copy last partial block of last buffer written (or to be
			written) into a new buffer.
*********************************************************************/
void F_Rfl::copyLastBlock(
	RFL_BUFFER *	pBuffer,
	FLMBYTE *		pucOldBuffer,
	FLMBYTE *		pucNewBuffer,
	FLMUINT			uiCurrPacketLen,
	FLMBOOL			bStartingNewFile)
{
	FLMUINT	uiOldBufBytes = pBuffer->uiRflBufBytes;

	// If we will be starting a new file, no need to keep any of
	// what is in the buffer.  Only the current packet needs to
	// be copied - at the beginning of the buffer.

	// OTHERWISE:

	// If there are fewer than 512 bytes in the buffer, we simply
	// keep them and keep appending to the buffer the next time
	// we output stuff.  The beginning of the buffer must ALWAYS be
	// a 512 byte boundary in the file, because we want to always
	// do our writing on 512 byte boundaries - because of direct IO.

	// If the number of bytes in the buffer is over 512 and it is
	// evenly divisible by 512, we can clear the buffer.  Otherwise,
	// we want to move the extra bytes over the last 512 byte boundary
	// down to the beginning of the buffer and adjust the buffer bytes
	// to reflect just these left-over bytes.

	if (bStartingNewFile)
	{
		pBuffer->uiRflBufBytes = 0;
		pBuffer->uiRflFileOffset = 512;
	}
	else if (pBuffer->uiRflBufBytes >= 512)
	{

		// See if the number of bytes in the buffer is an exact multiple of
		// 512.

		if (pBuffer->uiRflBufBytes & 511)	// Not exact multiple
		{

			// Round m_uiRflBufBytes down to next 512 byte boundary

			FLMUINT	ui512Offset = ROUND_DOWN_TO_NEAREST_512( 
												pBuffer->uiRflBufBytes);

			// Move all bytes above the nearest 512 byte boundary down to
			// the beginning of the buffer and adjust pBuffer->uiRflBufBytes
			// and pBuffer->uiRflFileOffset accordingly.

			f_memcpy( pucNewBuffer, &pucOldBuffer[ui512Offset],
						pBuffer->uiRflBufBytes - ui512Offset);
			pBuffer->uiRflBufBytes -= ui512Offset;
			pBuffer->uiRflFileOffset += ui512Offset;
		}
		else
		{
			pBuffer->uiRflFileOffset += pBuffer->uiRflBufBytes;
			pBuffer->uiRflBufBytes = 0;
		}
	}
	else if (pucNewBuffer != pucOldBuffer)
	{
		f_memcpy( pucNewBuffer, pucOldBuffer, pBuffer->uiRflBufBytes);
	}

	if (uiCurrPacketLen)
	{
		flmAssert( uiOldBufBytes + uiCurrPacketLen <= m_uiBufferSize);
		f_memmove( &pucNewBuffer[pBuffer->uiRflBufBytes],
					 &pucOldBuffer[uiOldBufBytes], uiCurrPacketLen);
	}
}

/********************************************************************
Desc:		Flush the RFL data from the buffer to disk.
*********************************************************************/
RCODE F_Rfl::flush(
	RFL_BUFFER *	pBuffer,
	FLMBOOL			bFinalWrite,
	FLMUINT			uiCurrPacketLen,
	FLMBOOL			bStartingNewFile)
{
	RCODE				rc = FERR_OK;
	FLMUINT			uiBytesWritten;
	IF_IOBuffer *	pNewBuffer = NULL;
	IF_IOBuffer *	pIOBuffer = NULL;
	FLMBYTE *		pucOldBuffer;
	FLMUINT			uiFileOffset;
	FLMUINT			uiBufBytes;

	if (m_pFileHdl && pBuffer->uiRflBufBytes)
	{

		// Must wait for stuff in committing buffer, if any, before going
		// ahead here.

		if( pBuffer != m_pCommitBuf)
		{
			if (RC_BAD( rc = waitForCommit()))
			{
				goto Exit;
			}
		}

		if( m_uiRflWriteBufs > 1 && m_pFileHdl->canDoAsync())
		{
			pIOBuffer = pBuffer->pIOBuffer;
		}

		if ((FLMUINT) (-1) - pBuffer->uiRflFileOffset <= pBuffer->uiRflBufBytes)
		{
			rc = RC_SET( FERR_DB_FULL);
			goto Exit;
		}

		pucOldBuffer = pBuffer->pIOBuffer->getBufferPtr();
		uiFileOffset = pBuffer->uiRflFileOffset;
		uiBufBytes = pBuffer->uiRflBufBytes;
		
		if (m_uiRflWriteBufs > 1)
		{
			if (RC_BAD( rc = pBuffer->pBufferMgr->getBuffer( 
				m_uiBufferSize, &pNewBuffer)))
			{
				goto Exit;
			}

			// No need to copy data if it is the final write, because it
			// won't be reused anyway - the data for the next transaction has
			// already been copied to another buffer.

			if (!bFinalWrite)
			{
				copyLastBlock( pBuffer, pucOldBuffer, pNewBuffer->getBufferPtr(),
									uiCurrPacketLen, bStartingNewFile);
			}
		}
		
		if( pIOBuffer)
		{
			FLMUINT		uiBytesToWrite =	(FLMUINT)f_roundUp( uiBufBytes, 
														m_pFileHdl->getSectorSize());

			rc = m_pFileHdl->write( uiFileOffset, uiBytesToWrite, pIOBuffer);
		}
		else
		{
			pBuffer->pIOBuffer->setPending();

			rc = m_pFileHdl->write( uiFileOffset, uiBufBytes, 
											  pucOldBuffer, &uiBytesWritten);
		}

		if( RC_OK( rc))
		{
			if( m_bKeepRflFiles)
			{
				f_mutexLock( gv_FlmSysData.hShareMutex);

				if( m_pFile->uiFileExtendSize)
				{
					FLMUINT		uiTmpSize;

					uiTmpSize = (uiFileOffset % m_pFile->uiFileExtendSize) + 
										(FLMUINT)f_roundUp( uiBufBytes, 
												m_pFileHdl->getSectorSize());

					if( uiTmpSize > m_pFile->uiFileExtendSize)
					{
						m_pFile->ui64RflDiskUsage += m_pFile->uiFileExtendSize;
					}
				}
				else
				{
					m_pFile->ui64RflDiskUsage += uiBytesWritten;
				}

				f_mutexUnlock( gv_FlmSysData.hShareMutex);
			}
		}
		
		if (m_uiRflWriteBufs == 1)
		{
			// We are counting on the fact that the write completed. When we
			// only have one buffer, we cannot do async writes.

			flmAssert( !pIOBuffer);
			pBuffer->pIOBuffer->notifyComplete( rc);											  
			
			if( RC_OK( rc) && !bFinalWrite)
			{
				copyLastBlock( pBuffer, pucOldBuffer, pucOldBuffer,
									uiCurrPacketLen, bStartingNewFile);
			}
		}
		else
		{
			// No need to call copyLastBlock, because it was called above
			// before calling write. The part of the old buffer that
			// needs to be transferred to the new buffer has already been
			// transferred.

			if( !pIOBuffer)
			{
				pBuffer->pIOBuffer->notifyComplete( rc);
			}

			pBuffer->pIOBuffer->Release();
			pBuffer->pIOBuffer = pNewBuffer;
		}

		if( RC_BAD( rc))
		{
			if (rc == FERR_IO_DISK_FULL)
			{
				rc = RC_SET( FERR_RFL_DEVICE_FULL);
				m_bRflVolumeFull = TRUE;
			}

			m_bRflVolumeOk = FALSE;
			goto Exit;
		}
	}

Exit:

	return (rc);
}

/********************************************************************
Desc:	Switch buffers.  This routine assumes the m_hBufMutex is locked.
*********************************************************************/
void F_Rfl::switchBuffers(void)
{
	RFL_BUFFER *	pOldBuffer = m_pCurrentBuf;

	if (m_pCurrentBuf == &m_Buf1)
	{
		m_pCurrentBuf = &m_Buf2;
	}
	else
	{
		m_pCurrentBuf = &m_Buf1;
	}

	m_pCurrentBuf->bTransInProgress = pOldBuffer->bTransInProgress;
	m_pCurrentBuf->uiCurrFileNum = pOldBuffer->uiCurrFileNum;
	m_pCurrentBuf->uiRflBufBytes = pOldBuffer->uiRflBufBytes;
	m_pCurrentBuf->uiRflFileOffset = pOldBuffer->uiRflFileOffset;
	if (pOldBuffer->uiRflBufBytes)
	{
		copyLastBlock( m_pCurrentBuf, pOldBuffer->pIOBuffer->getBufferPtr(),
							m_pCurrentBuf->pIOBuffer->getBufferPtr(), 0, FALSE);
	}
}

/********************************************************************
Desc: Wait for all RFL transaction writes to be finished.  The caller
		has the write lock on the database, which will prevent further
		writes to the RFL.
*********************************************************************/
FLMBOOL F_Rfl::seeIfRflWritesDone(
	FLMBOOL	bForceWait)
{
	FLMBOOL	bWritesDone;

	f_mutexLock( m_hBufMutex);

	if (!bForceWait)
	{
		bWritesDone = (FLMBOOL) ((m_pCurrentBuf->pFirstWaiter || m_pCommitBuf) 
													? FALSE 
													: TRUE);
		f_mutexUnlock( m_hBufMutex);
	}
	else
	{

		// If the current buffer has a waiter, add self to that list to
		// wait, because it will be notified after the commit buffer has
		// been notified. Otherwise, if there is a commit in progress, add
		// self to that list to wait.

		if (m_pCurrentBuf->pFirstWaiter)
		{

			// If bTransInProgress is TRUE and m_pCommitBuf is NULL then
			// this thread is the current transaction, and nobody is going to
			// wake up the first waiter until we are done! Hence, we must
			// wake him up.

			if (!m_pCommitBuf)
			{

				// If m_pCommitBuf is NULL, this could only be possible if
				// there is a transaction in progress. Otherwise, there would
				// not have been a pFirstWaiter, because when the commit
				// buffer finishes writing, if there is a waiter, it will set
				// commitbuf=currentbuf if there is no transaction active.

				flmAssert( m_pCurrentBuf->bTransInProgress);

				m_pCommitBuf = m_pCurrentBuf;
				switchBuffers();
				wakeUpWaiter( FERR_OK, TRUE);
				(void) waitForWrites( m_pCommitBuf, FALSE);
			}
			else
			{
				FLMBOOL	bSaveTransInProgress = m_pCurrentBuf->bTransInProgress;

				// Must set bTransInProgress to FALSE so that when the writer
				// of m_pCommitBuf finishes, it will signal the first waiter
				// on m_pCurrentBuf. If we don't do this, m_pCommitBuf will
				// simply be set to NULL, and the first waiter will never be
				// woke up.

				m_pCurrentBuf->bTransInProgress = FALSE;
				(void) waitForWrites( m_pCurrentBuf, FALSE);

				// It is OK to restore the trans in progress flag to what it
				// was before, because whoever called this routine has a lock
				// on the database, and it is his trans-in-progress state that
				// should be preserved. No other thread will have been able to
				// change that state because the database is locked.

				f_mutexLock( m_hBufMutex);
				m_pCurrentBuf->bTransInProgress = bSaveTransInProgress;
				f_mutexUnlock( m_hBufMutex);
			}
		}
		else if (m_pCommitBuf)
		{
			(void) waitForWrites( m_pCommitBuf, FALSE);
		}
		else
		{
			f_mutexUnlock( m_hBufMutex);
		}

		bWritesDone = TRUE;
	}

	return (bWritesDone);
}

/********************************************************************
Desc: Wake up the first thread that is waiting on the commit buffer.
*********************************************************************/
void F_Rfl::wakeUpWaiter(
	RCODE				rc,
	FLMBOOL			bIsWriter	// Only used for debug
	)
{
	F_SEM			hESem;

#ifndef FLM_DEBUG
	F_UNREFERENCED_PARM( bIsWriter);
#else
	if (bIsWriter)
	{
		flmAssert( m_pCommitBuf->pFirstWaiter->bIsWriter);
	}
	else
	{
		flmAssert( !m_pCommitBuf->pFirstWaiter->bIsWriter);
	}
#endif

	*(m_pCommitBuf->pFirstWaiter->pRc) = rc;
	hESem = m_pCommitBuf->pFirstWaiter->hESem;
	if ((m_pCommitBuf->pFirstWaiter = m_pCommitBuf->pFirstWaiter->pNext) == NULL)
	{
		m_pCommitBuf->pLastWaiter = NULL;
	}

	f_semSignal( hESem);
}

/********************************************************************
Desc: Wait for the transaction writes to be finished.
*********************************************************************/
RCODE F_Rfl::completeTransWrites(
	FDB *			pDb,
	FLMBOOL		bCommitting,
	FLMBOOL		bOkToUnlock)
{
	RCODE			rc = FERR_OK;
	RCODE			tmpRc;
	FLMBOOL		bMutexLocked = FALSE;
	FLMBOOL		bNotifyWaiters = FALSE;
	FLMBOOL		bDbUnlocked = FALSE;
	DB_STATS *	pDbStats = NULL;
	F_TMSTAMP	StartTime;

	f_mutexLock( m_hBufMutex);
	bMutexLocked = TRUE;
	m_pCurrentBuf->bTransInProgress = FALSE;

	flmAssert( pDb->uiFlags & FDB_HAS_WRITE_LOCK);

	// If we are not logging, we are probably recovering or restoring the
	// database. All we need to do in this case is write out the log
	// header.

	if (pDb->uiFlags & FDB_REPLAYING_RFL)
	{
		if (pDb->bHadUpdOper && m_pCurrentBuf->bOkToWriteHdrs)
		{
			f_mutexUnlock( m_hBufMutex);
			bMutexLocked = FALSE;
			if (RC_BAD( rc = flmWriteLogHdr( pDb->pDbStats, pDb->pSFileHdl,
						  pDb->pFile, m_pCurrentBuf->ucLogHdr, m_pCurrentBuf->ucCPHdr,
						  FALSE)))
			{
				flmSetMustCloseFlags( pDb->pFile, rc, FALSE);
			}
		}

		goto Exit;
	}

	// Handle empty transactions differently. These transactions should
	// not do any writing and do not need to wait for all writes to
	// complete, unless the bOkToUnlock flag is set to FALSE. In that case
	// they must wait for all writes to complete before unlocking.

	if (!pDb->bHadUpdOper)
	{

		// If the current buffer has a waiter, add self to that list to
		// wait, because it will be notified after the commit buffer has
		// been notified. Otherwise, if there is a commit in progress, add
		// self to that list to wait.

		if (m_pCurrentBuf->pFirstWaiter)
		{

			// If m_pCommitBuf is NULL then nobody is going to wake up the
			// first waiter - we must do it.

			if (!m_pCommitBuf)
			{
				if (bOkToUnlock)
				{
					flmUnlinkDbFromTrans( pDb, bCommitting);
					bDbUnlocked = TRUE;
				}

				m_pCommitBuf = m_pCurrentBuf;
				switchBuffers();
				wakeUpWaiter( FERR_OK, TRUE);

				if (!bOkToUnlock)
				{
					bMutexLocked = FALSE;
					(void) waitForWrites( m_pCommitBuf, FALSE);
				}
			}
			else if (!bOkToUnlock)
			{
				bMutexLocked = FALSE;
				(void) waitForWrites( m_pCurrentBuf, FALSE);
			}
		}
		else if (m_pCommitBuf)
		{
			if (!bOkToUnlock)
			{
				bMutexLocked = FALSE;
				rc = waitForWrites( m_pCommitBuf, FALSE);
			}
		}

		goto Exit;
	}

	// If there is a transaction committing, put self into the wait list
	// on the current buffer. When the committer finishes, he will wake up
	// the first thread in the list and that thread will commit the buffer.

	if (m_pCommitBuf)
	{
		FLMBOOL	bIsWriter;

		// Another thread has to be doing the writes to m_pCommitBuf, which
		// means that m_pCurrentBuf better not be equal to m_pCommitBuf.

		flmAssert( m_pCommitBuf != m_pCurrentBuf);

		// If there are no waiters, we are the first one, so when we get
		// signaled, we should proceed and do the write.

		bIsWriter = m_pCurrentBuf->pFirstWaiter ? FALSE : TRUE;
		if (bOkToUnlock)
		{
			flmUnlinkDbFromTrans( pDb, bCommitting);
			bDbUnlocked = TRUE;
		}

		bMutexLocked = FALSE;
		rc = waitForWrites( m_pCurrentBuf, bIsWriter);

		// If we were the first one in the queue, we must now do the write.

		if (!bIsWriter)
		{
			goto Exit;
		}

		// First one in the queue, fall through to do the write. The thread
		// that woke me up better have set m_pCommitBuf See below.

		flmAssert( m_pCommitBuf);
	}
	else if (m_pCurrentBuf->pFirstWaiter)
	{

		// Another thread is ready to commit the next set of buffers, but
		// just needs to be woke up.

		if (bOkToUnlock)
		{
			flmUnlinkDbFromTrans( pDb, bCommitting);
			bDbUnlocked = TRUE;
		}

		// Need to set things up for that first waiter and get him going.

		m_pCommitBuf = m_pCurrentBuf;
		switchBuffers();
		wakeUpWaiter( rc, TRUE);

		// Wait for the write to be completed.

		bMutexLocked = FALSE;
		rc = waitForWrites( m_pCommitBuf, FALSE);
		goto Exit;
	}
	else
	{
		m_pCommitBuf = m_pCurrentBuf;
		switchBuffers();
		if (bOkToUnlock)
		{
			flmUnlinkDbFromTrans( pDb, bCommitting);
			bDbUnlocked = TRUE;
		}

		f_mutexUnlock( m_hBufMutex);
		bMutexLocked = FALSE;
	}

	// NOTE: From this point on we use tmpRc because we don't want to lose
	// the rc that may have been set above in the call to waitForWrites;
	// At this point the mutex better not be locked.
	flmAssert( !bMutexLocked);
	bNotifyWaiters = TRUE;

	if ((pDbStats = pDb->pDbStats) != NULL)
	{
		f_timeGetTimeStamp( &StartTime);
	}

	// Must write out whatever we have in the commit buffer before
	// unlocking the database.

	if (RC_BAD( tmpRc = flush( m_pCommitBuf, TRUE)))
	{
		if (RC_OK( rc))
		{
			rc = tmpRc;
		}

		goto Exit;
	}

	// Wait for any pending I/O off of the log buffer

	if (RC_BAD( tmpRc = m_pCommitBuf->pBufferMgr->waitForAllPendingIO()))
	{
		if (RC_OK( rc))
		{
			rc = tmpRc;
		}

		goto Exit;
	}

	// Force the RFL writes to disk if necessary. NOTE: It is possible for
	// m_pFileHdl to be NULL at this point if there were no operations
	// actually logged. This happens in FlmDbUpgrade (see flconvrt.cpp).
	// Even though nothing was logged the transaction is not an empty
	// transaction, because it still needs to write out the log header.

	if (m_pFileHdl)
	{
		if (RC_BAD( tmpRc = m_pFileHdl->flush()))
		{

			// Remap disk full error

			if (tmpRc == FERR_IO_DISK_FULL)
			{
				rc = RC_SET( FERR_RFL_DEVICE_FULL);
				m_bRflVolumeFull = TRUE;
			}
			else if (RC_OK( rc))
			{
				rc = tmpRc;
			}

			m_bRflVolumeOk = FALSE;
			goto Exit;
		}
	}

	// Write the log header

	if (m_pCommitBuf->bOkToWriteHdrs)
	{
		if (RC_BAD( tmpRc = flmWriteLogHdr( pDb->pDbStats, pDb->pSFileHdl,
					  pDb->pFile, m_pCommitBuf->ucLogHdr, m_pCommitBuf->ucCPHdr,
					  FALSE)))
		{
			if (RC_OK( rc))
			{
				rc = tmpRc;
			}

			flmSetMustCloseFlags( pDb->pFile, tmpRc, FALSE);
			goto Exit;
		}
	}

Exit:

	if (!bDbUnlocked && bOkToUnlock)
	{
		flmUnlinkDbFromTrans( pDb, bCommitting);
	}

	if (bNotifyWaiters)
	{
		FLMUINT	uiNumFinished = 1;	// For self

		flmAssert( !bMutexLocked);
		f_mutexLock( m_hBufMutex);
		bMutexLocked = TRUE;

		// Wake up any waiters

		while (m_pCommitBuf->pFirstWaiter)
		{
			uiNumFinished++;
			wakeUpWaiter( rc, FALSE);
		}

		// If there are waiters on the current buffer, the first one should
		// be woke up so it can start the next set of writes.

		if (m_pCurrentBuf->pFirstWaiter && !m_pCurrentBuf->bTransInProgress)
		{
			flmAssert( m_pCurrentBuf != m_pCommitBuf);
			m_pCommitBuf = m_pCurrentBuf;
			switchBuffers();
			wakeUpWaiter( rc, TRUE);
		}
		else
		{
			m_pCommitBuf = NULL;
		}

		if (pDbStats)
		{
			flmAddElapTime( &StartTime,
								&pDbStats->UpdateTransStats.GroupCompletes.ui64ElapMilli);
			pDbStats->UpdateTransStats.GroupCompletes.ui64Count++;
			pDbStats->bHaveStats = TRUE;
			pDbStats->UpdateTransStats.ui64GroupFinished += uiNumFinished;
		}
	}

	if (bMutexLocked)
	{
		f_mutexUnlock( m_hBufMutex);
	}

	return (rc);
}

/********************************************************************
Desc: Calculate the checksum for a packet.
*********************************************************************/
FLMBYTE RflCalcChecksum(
	const FLMBYTE *	pucPacket,
	FLMUINT				uiPacketBodyLen)
{
	FLMUINT				uiBytesToChecksum;
	const FLMBYTE *	pucStart;
	
	// Checksum is calculated for every byte in the packet that comes
	// after the checksum byte.

	pucStart = &pucPacket[RFL_PACKET_CHECKSUM_OFFSET + 1];
	uiBytesToChecksum = (FLMUINT)(uiPacketBodyLen +
												RFL_PACKET_OVERHEAD -
												(RFL_PACKET_CHECKSUM_OFFSET + 1));

	return( f_calcPacketChecksum( pucStart, uiBytesToChecksum));
}

/********************************************************************
Desc:	Flush all completed packets out of the RFL buffer, and shift
		the new partial packet down.  This guarantees that there is
		now room in the buffer for the maximum packet size.
*********************************************************************/
RCODE F_Rfl::shiftPacketsDown(
	FLMUINT		uiCurrPacketLen,
	FLMBOOL		bStartingNewFile)
{
	RCODE 		rc = FERR_OK;

	// The call to flush will move whatever needs to be moved from the
	// current buffer into a new buffer if multiple buffers are being used.
	// If only one buffer is being used, it will move the part of the
	// packet that needs to be moved down to the beginning of the buffer -
	// AFTER writing out the buffer.

	if( RC_BAD( rc = flush( m_pCurrentBuf, FALSE, uiCurrPacketLen,
				  bStartingNewFile)))
	{
		goto Exit;
	}

	// NOTE: If multiple buffers are being used, whatever was moved to the
	// new buffer has not yet been written out

	if (bStartingNewFile)
	{
		if (RC_BAD( rc = waitPendingWrites()))
		{
			goto Exit;
		}
	}

Exit:

	return (rc);
}

/********************************************************************
Desc:		Determine if we should start a new file.  If we are over the
			low limit, and the bDoNewIfOverLowLimit flag is set, we
			will start a new log file.  Or, if this packet size would
			put us over the upper limit, we will start a new log file.
*********************************************************************/
RCODE F_Rfl::seeIfNeedNewFile(
	FLMUINT	uiPacketLen,
	FLMBOOL	bDoNewIfOverLowLimit)
{
	RCODE		rc = FERR_OK;
	FLMBYTE	ucNextSerialNum[F_SERIAL_NUM_SIZE];

	flmAssert( m_pFile);

	// If the keep files flag is FALSE, we won't start a new file. NOTE:
	// This should ALWAYS be false for pre 4.3 databases.

	if (!m_bKeepRflFiles)
	{
		goto Exit;	// Should return FERR_OK;
	}

	// VERY IMPORTANT NOTE: It is preferrable that we keep transactions
	// entirely contained in the same RFL file if at all possible. Note
	// that it is NOT a hard and fast requirement. The system will work
	// just fine if we don't. However, it would be nice if RFL files always
	// ended with a commit or abort packet. This preferences is due to what
	// happens after a restore operation. After a restore operation, we
	// always need to start a new RFL file, but if possible, we would like
	// that new RFL file to be the next one in the sequence after the last
	// RFL file that was restored. We can only do this if we were able to
	// restore EVERY transaction that was in the last restored RFL file -
	// which we can only do if the last restored RFL file ended with a
	// commit or abort packet. To accomplish this end, we try to roll to
	// new files on the first transaction begin packet that occurs after we
	// have exceeded our low threshold - which is why bDoNewIfOverLowLimit
	// is only set to TRUE on transaction begin packets. It is set to FALSE
	// on other packets so that we will continue logging the transaction in
	// the same file that we started the transaction in - if possible. The
	// only thing that will cause a non-transaction-begin packet to roll to
	// a new file is if we would exceed the high limit.

	if ((bDoNewIfOverLowLimit &&
		  m_pCurrentBuf->uiRflFileOffset + m_pCurrentBuf->uiRflBufBytes >=
				m_uiRflMinFileSize) ||
		 (m_pCurrentBuf->uiRflFileOffset + m_pCurrentBuf->uiRflBufBytes +
			uiPacketLen >= m_uiRflMaxFileSize))
	{
		FLMUINT	uiCurrFileEOF = m_pCurrentBuf->uiRflFileOffset +
										 m_pCurrentBuf->uiRflBufBytes;

		// Shift the current packet to the beginning of the buffer. Any
		// packets in the buffer before that one will be written out to the
		// current file.

		if (RC_BAD( rc = shiftPacketsDown( uiPacketLen, TRUE)))
		{
			goto Exit;
		}

		// Update the header of the current file and close it.

		if (RC_BAD( rc = writeHeader( m_pCurrentBuf->uiCurrFileNum, uiCurrFileEOF,
					  m_ucCurrSerialNum, m_ucNextSerialNum, TRUE)))
		{
			goto Exit;
		}

		// Truncate the file.

		if (!ON_512_BYTE_BOUNDARY( uiCurrFileEOF))
		{
			uiCurrFileEOF = ROUND_DOWN_TO_NEAREST_512( uiCurrFileEOF) + 512;
		}

		if (RC_BAD( rc = m_pFileHdl->truncateFile( uiCurrFileEOF)))
		{
			goto Exit;
		}

		// Close the file handle.

		m_pFileHdl->Release();
		m_pFileHdl = NULL;

		// Get the next serial number that will be used for the RFL file
		// after this one.

		if (RC_BAD( rc = f_createSerialNumber( ucNextSerialNum)))
		{
			goto Exit;
		}

		// Create next file in the sequence. Use the next serial number
		// stored in the FDB's log header for the serial number on this RFL
		// file. Use the serial number we just generated as the next RFL
		// serial number.

		if (RC_BAD( rc = createFile( m_pCurrentBuf->uiCurrFileNum + 1,
					  m_ucNextSerialNum, ucNextSerialNum, TRUE)))
		{
			goto Exit;
		}

		// Move the next serial number to the current serial number and the
		// serial number we generated above into the next serial number.

		f_memcpy( m_ucCurrSerialNum, m_ucNextSerialNum, F_SERIAL_NUM_SIZE);
		f_memcpy( m_ucNextSerialNum, ucNextSerialNum, F_SERIAL_NUM_SIZE);
	}

Exit:

	return (rc);
}

/********************************************************************
Desc: Finish the current RFL file - set up so that next transaction
		will begin a new RFL file.
********************************************************************/
RCODE F_Rfl::finishCurrFile(
	FDB *		pDb,
	FLMBOOL	bNewKeepState)
{
	RCODE			rc = FERR_OK;
	FLMBOOL		bDbLocked = FALSE;
	FLMUINT		uiTransFileNum;
	FLMUINT		uiTransOffset;
	FLMUINT		uiTruncateSize;
	FLMBYTE *	pucUncommittedLogHdr;
	FLMBYTE 		ucCheckpointLogHdr[ LOG_HEADER_SIZE];

	// Make sure we don't have a transaction going

	if (pDb->uiTransType != FLM_NO_TRANS)
	{
		rc = RC_SET( FERR_TRANS_ACTIVE);
		goto Exit;
	}

	// Make sure there is no active backup running

	f_mutexLock( gv_FlmSysData.hShareMutex);
	if (m_pFile->bBackupActive)
	{
		f_mutexUnlock( gv_FlmSysData.hShareMutex);
		rc = RC_SET( FERR_BACKUP_ACTIVE);
		goto Exit;
	}

	f_mutexUnlock( gv_FlmSysData.hShareMutex);

	// Lock the database - need to prevent update transactions and
	// checkpoint thread from running.

	if (RC_BAD( rc = dbLock( pDb, FLM_NO_TIMEOUT)))
	{
		goto Exit;
	}

	bDbLocked = TRUE;

	// Must wait for all RFL writes before switching files.

	(void) seeIfRflWritesDone( TRUE);

	// Better not be in the middle of logging unknown stuff for the
	// application

	flmAssert( !m_bLoggingUnknown);

	// Better not be in the middle of a transaction.

	flmAssert( !m_uiCurrTransID);

	// If DB version is less than 4.3 we cannot do this.

	if (m_pFile->FileHdr.uiVersionNum < FLM_FILE_FORMAT_VER_4_3)
	{
		goto Exit;		// Will return FERR_OK
	}

	pucUncommittedLogHdr = &m_pFile->ucUncommittedLogHdr[0];

	// Don't want to copy last committed log header into uncommitted log
	// header if bNewKeepState is TRUE because the caller has already done
	// it, and has made modifications to the uncommitted log header that we
	// don't want to lose.

	if (!bNewKeepState)
	{
		f_memcpy( pucUncommittedLogHdr, m_pFile->ucLastCommittedLogHdr,
					LOG_HEADER_SIZE);

		// If we are in a no-keep state, but we were not told that we have
		// a new keep state, we cannot roll to the next RFL file, because a
		// checkpoint has not been done. This is not an error - it is just
		// the case where FlmDbConfig was asked to roll to the next RFL file
		// when the keep flag was still FALSE.

		if (!pucUncommittedLogHdr[LOG_KEEP_RFL_FILES])
		{
			goto Exit;	// Will return FERR_OK
		}
	}

	// Get the last committed serial numbers from the file's log header
	// buffer.

	f_memcpy( m_ucCurrSerialNum,
				&pucUncommittedLogHdr[LOG_LAST_TRANS_RFL_SERIAL_NUM],
				F_SERIAL_NUM_SIZE);
				
	f_memcpy( m_ucNextSerialNum, &pucUncommittedLogHdr[LOG_RFL_NEXT_SERIAL_NUM],
				F_SERIAL_NUM_SIZE);
				
	uiTransFileNum = (FLMUINT) FB2UD( &pucUncommittedLogHdr[LOG_RFL_FILE_NUM]);
	uiTransOffset = (FLMUINT) FB2UD( &pucUncommittedLogHdr[LOG_RFL_LAST_TRANS_OFFSET]);

	// If the LOG_RFL_LAST_TRANS_OFFSET is zero, there is no need to go
	// set up to go to the next file, because we are already poised to do
	// so at the beginning of the next transaction. Just return if this is
	// the case. Same for if the file does not exist.

	if (!uiTransOffset)
	{
		if (!bNewKeepState)
		{
			goto Exit;	// Will return FERR_OK
		}
	}
	else if (RC_BAD( rc = openFile( uiTransFileNum, m_ucCurrSerialNum)))
	{
		if (rc == FERR_IO_PATH_NOT_FOUND || rc == FERR_IO_INVALID_PATH)
		{
			rc = FERR_OK;
			if (!bNewKeepState)
			{
				goto Exit;
			}
		}
		else
		{
			goto Exit;
		}
	}
	else
	{

		// At this point, we know the file exists, so we will update its
		// header and then update the log header. Note that we use the keep
		// RFL state from the last committed log header, not the uncommitted
		// log header - because it will contain the correct keep-state for
		// the current RFL file.

		if (RC_BAD( rc = writeHeader( m_pCurrentBuf->uiCurrFileNum, uiTransOffset,
										m_ucCurrSerialNum, m_ucNextSerialNum,
										m_pFile->ucLastCommittedLogHdr [LOG_KEEP_RFL_FILES]
											? TRUE
											: FALSE)))
		{
			goto Exit;
		}

		// Truncate the file down to its EOF size - the nearest 512 byte
		// boundary.

		uiTruncateSize = uiTransOffset;
		if (!ON_512_BYTE_BOUNDARY( uiTruncateSize))
		{
			uiTruncateSize = ROUND_DOWN_TO_NEAREST_512( uiTruncateSize) + 512;
		}

		if (RC_BAD( rc = m_pFileHdl->truncateFile( uiTruncateSize)))
		{
			goto Exit;
		}

		// Close the file handle.

		m_pFileHdl->Release();
		m_pFileHdl = NULL;

		// Set things up in the log header to go to the next file when we
		// begin the next transaction. NOTE: NO need to lock the mutex,
		// because nobody but an update transaction looks at the uncommitted
		// log header.

		uiTransFileNum++;
		UD2FBA( (FLMUINT32) uiTransFileNum,
				 &pucUncommittedLogHdr[LOG_RFL_FILE_NUM]);
	}

	// Generate a new current serial number if bNewKeepState is TRUE.
	// Otherwise, move the next serial number into the current serial
	// number.

	if (bNewKeepState)
	{
		if (RC_BAD( rc = f_createSerialNumber( m_ucCurrSerialNum)))
		{
			goto Exit;
		}
	}
	else
	{
		f_memcpy( m_ucCurrSerialNum, m_ucNextSerialNum, F_SERIAL_NUM_SIZE);
	}

	// Always generate a new next serial number.

	if (RC_BAD( rc = f_createSerialNumber( m_ucNextSerialNum)))
	{
		goto Exit;
	}

	// Set transaction offset to zero. This will force the next RFL file
	// to be created on the next transaction begin. It will be created even
	// if it is already there.

	UD2FBA( (FLMUINT32) 0, &pucUncommittedLogHdr[LOG_RFL_LAST_TRANS_OFFSET]);
	
	f_memcpy( &pucUncommittedLogHdr[LOG_LAST_TRANS_RFL_SERIAL_NUM],
				m_ucCurrSerialNum, F_SERIAL_NUM_SIZE);
				
	f_memcpy( &pucUncommittedLogHdr[LOG_RFL_NEXT_SERIAL_NUM], m_ucNextSerialNum,
				F_SERIAL_NUM_SIZE);

	// Set the CP file number and CP offset to point into the new file.
	// The outer code (FlmDbConfig) has done a checkpoint and the database
	// is still locked. We need to set these values here, otherwise if we
	// crash before the next checkpoint, recovery will start in the old RFL
	// file, causing an FERR_BAD_RFL_SERIAL_NUM to be returned when
	// traversing from the old RFL file to the new RFL file. NOTE: These
	// changes must be made to the uncommitted log header AND the CP log
	// header (so that they will be written out even though we are not
	// forcing a checkpoint).

	if (bNewKeepState)
	{
#ifdef FLM_DEBUG
		// Do a quick check to see if it looks like we are in a
		// checkpointed state

		if (!m_pFile->ucLastCommittedLogHdr[LOG_KEEP_RFL_FILES] &&
			 (FLMUINT) FB2UD( &m_pFile->ucLastCommittedLogHdr[
										LOG_RFL_LAST_TRANS_OFFSET]) > 512)
		{
			flmAssert( 0);
		}
#endif

		f_memcpy( ucCheckpointLogHdr, m_pFile->ucCheckpointLogHdr,
					LOG_HEADER_SIZE);
		
		UD2FBA( (FLMUINT32) uiTransFileNum,
				 &ucCheckpointLogHdr[LOG_RFL_LAST_CP_FILE_NUM]);
				 
		UD2FBA( (FLMUINT32) uiTransFileNum,
				 &pucUncommittedLogHdr[LOG_RFL_LAST_CP_FILE_NUM]);
				 
		UD2FBA( (FLMUINT32) 512, &ucCheckpointLogHdr[LOG_RFL_LAST_CP_OFFSET]);
		
		UD2FBA( (FLMUINT32) 512, &pucUncommittedLogHdr[LOG_RFL_LAST_CP_OFFSET]);
	}

	// Write out the log header to disk.

	if (RC_BAD( rc = flmWriteLogHdr( pDb->pDbStats, pDb->pSFileHdl,
									m_pFile, pucUncommittedLogHdr,
									bNewKeepState 
										? ucCheckpointLogHdr
										: m_pFile->ucCheckpointLogHdr, FALSE)))
	{
		goto Exit;
	}

	// Copy the uncommitted log header back to the committed log header
	// and copy the CP log header (if changed above).

	f_mutexLock( gv_FlmSysData.hShareMutex);
	f_memcpy( m_pFile->ucLastCommittedLogHdr, pucUncommittedLogHdr,
				LOG_HEADER_SIZE);

	if (bNewKeepState)
	{
		f_memcpy( m_pFile->ucCheckpointLogHdr, ucCheckpointLogHdr,
					LOG_HEADER_SIZE);
	}

	f_mutexUnlock( gv_FlmSysData.hShareMutex);

Exit:

	if (bDbLocked)
	{
		(void) dbUnlock( pDb);
	}

	return (rc);
}

/********************************************************************
Desc:		Finish packet by outputting header information for it.
*********************************************************************/
RCODE F_Rfl::finishPacket(
	FLMUINT	uiPacketType,
	FLMUINT	uiPacketBodyLen,
	FLMBOOL	bDoNewIfOverLowLimit)
{
	RCODE			rc = FERR_OK;
	FLMUINT		uiEncryptPacketBodyLen;
	FLMUINT		uiPacketLen;
	FLMBYTE *	pucPacket;

	// Encrypt the packet body, if requested.

	uiEncryptPacketBodyLen = getEncryptPacketBodyLen( uiPacketType,
																	 uiPacketBodyLen);
	uiPacketLen = uiEncryptPacketBodyLen + RFL_PACKET_OVERHEAD;

	// See if this packet will cause us to overflow the limits on the
	// current file. If so, create a new file.

	if (RC_BAD( rc = seeIfNeedNewFile( uiPacketLen, bDoNewIfOverLowLimit)))
	{
		goto Exit;
	}

	// Get a pointer to packet header.

	pucPacket = &(m_pCurrentBuf->pIOBuffer->getBufferPtr()[
											m_pCurrentBuf->uiRflBufBytes]);

	// Set the packet address in the packet header.

	m_uiPacketAddress = m_pCurrentBuf->uiRflFileOffset + 
									m_pCurrentBuf->uiRflBufBytes;
									
	UD2FBA( (FLMUINT32) m_uiPacketAddress, &pucPacket[
															RFL_PACKET_ADDRESS_OFFSET]);

	// Set the packet type and packet body length.

	pucPacket[RFL_PACKET_TYPE_OFFSET] = (FLMBYTE) uiPacketType;
	
	UW2FBA( (FLMUINT16) uiPacketBodyLen,
			 &pucPacket[RFL_PACKET_BODY_LENGTH_OFFSET]);

	// Set the checksum for the packet.

	pucPacket[RFL_PACKET_CHECKSUM_OFFSET] = 
						RflCalcChecksum( pucPacket, uiEncryptPacketBodyLen);

	// Increment bytes in the buffer to reflect the fact that this packet
	// is now complete.

	m_pCurrentBuf->uiRflBufBytes += uiPacketLen;
	
Exit:

	return (rc);
}

/********************************************************************
Desc:	Truncate roll-forward log file to a certain size - only do if 
		not keeping RFL files.
*********************************************************************/
RCODE F_Rfl::truncate(
	FLMUINT		uiTruncateSize)
{
	RCODE			rc = FERR_OK;
	FLMUINT		uiFileNum;

	flmAssert( uiTruncateSize >= 512);

	// Keeping of log files better not be enabled.

	flmAssert( !m_pFile->ucLastCommittedLogHdr[LOG_KEEP_RFL_FILES]);

	// Better not be in the middle of logging unknown stuff for the
	// application

	flmAssert( !m_bLoggingUnknown);

	// Better not be in the middle of a transaction.

	flmAssert( !m_uiCurrTransID);

	// Open the current RFL file. If it does not exist, it is OK - there
	// is nothing to truncate.

	uiFileNum = (FLMUINT) FB2UD( &m_pFile->ucLastCommittedLogHdr[
																		LOG_RFL_FILE_NUM]);
	
	if (RC_BAD( rc = openFile( uiFileNum, &m_pFile->ucLastCommittedLogHdr[
					  LOG_LAST_TRANS_RFL_SERIAL_NUM])))
	{
		if (rc == FERR_IO_PATH_NOT_FOUND || rc == FERR_IO_INVALID_PATH)
		{
			rc = FERR_OK;
		}

		goto Exit;
	}

	if (RC_BAD( rc = m_pFileHdl->truncateFile( uiTruncateSize)))
	{
		m_bRflVolumeOk = FALSE;
		goto Exit;
	}

Exit:

	return (rc);
}

/********************************************************************
Desc:		Setup to begin a transaction
*********************************************************************/
RCODE F_Rfl::setupTransaction(void)
{
	RCODE			rc = FERR_OK;
	FLMUINT		uiFileNum;
	FLMUINT		uiLastTransOffset;
	FLMBOOL		bCreateFile;

	f_mutexLock( m_hBufMutex);
	m_pCurrentBuf->bTransInProgress = TRUE;
	f_mutexUnlock( m_hBufMutex);

	// Get the last committed serial numbers from the file's log header
	// buffer.

	f_memcpy( m_ucCurrSerialNum,
				&m_pFile->ucLastCommittedLogHdr[LOG_LAST_TRANS_RFL_SERIAL_NUM],
				F_SERIAL_NUM_SIZE);
				
	f_memcpy( m_ucNextSerialNum,
				&m_pFile->ucLastCommittedLogHdr[LOG_RFL_NEXT_SERIAL_NUM],
				F_SERIAL_NUM_SIZE);
				
	uiFileNum = (FLMUINT) FB2UD( &m_pFile->ucLastCommittedLogHdr[
															LOG_RFL_FILE_NUM]);
	
	uiLastTransOffset = (FLMUINT) FB2UD( &m_pFile->ucLastCommittedLogHdr[ 
															LOG_RFL_LAST_TRANS_OFFSET]);

	// If the LOG_RFL_LAST_TRANS_OFFSET is zero, we need to create the
	// next RFL file number no matter what. There are two cases where this
	// happens: 1) when the database is first created, and 2) after a
	// restore operation.

	if (!uiLastTransOffset)
	{
		bCreateFile = TRUE;

		// Close the current file, just in case we had opened it before. At
		// this point, it doesn't matter because we are going to overwrite
		// it.

		if (RC_BAD( rc = waitForCommit()))
		{
			goto Exit;
		}

		closeFile();
	}
	else if (RC_BAD( rc = openFile( uiFileNum, m_ucCurrSerialNum)))
	{
		if (rc != FERR_IO_PATH_NOT_FOUND && rc != FERR_IO_INVALID_PATH)
		{
			goto Exit;
		}

		bCreateFile = TRUE;
	}
	else
	{
		bCreateFile = FALSE;
	}

	if (bCreateFile)
	{

		// If the log header indicates that data has already been logged to
		// the file, we need to return the I/O error rather than just
		// re-creating the file. This may mean that someone changed the RFL
		// directory without moving the RFL files properly.

		if (uiLastTransOffset > 512)
		{
			rc = RC_SET( FERR_RFL_FILE_NOT_FOUND);
			goto Exit;
		}

		// Create the RFL file if not found. Use the next serial number
		// stored in the FDB's log header for the serial number on this RFL
		// file. Use the serial number we just generated as the next RFL
		// serial number.

		if (RC_BAD( rc = createFile( uiFileNum,
									m_ucCurrSerialNum, m_ucNextSerialNum,
				m_pFile->ucLastCommittedLogHdr [LOG_KEEP_RFL_FILES]
				? TRUE
				: FALSE)))
		{
			goto Exit;
		}
	}
	else
	{

		// Read in enough of the buffer from the RFL file so that we are
		// positioned on a 512 byte boundary.

		if (RC_BAD( positionTo( uiLastTransOffset)))
		{
			goto Exit;
		}
	}

	// These can only be changed when starting a transaction.

	if (m_pFile->FileHdr.uiVersionNum >= FLM_FILE_FORMAT_VER_4_3)
	{
		m_bKeepRflFiles = m_pFile->ucLastCommittedLogHdr[LOG_KEEP_RFL_FILES] 
									? TRUE 
									: FALSE;
		
		m_uiRflMaxFileSize = (FLMUINT) FB2UD( &m_pFile->ucLastCommittedLogHdr[
													LOG_RFL_MAX_FILE_SIZE]);

		// Round maximum down to nearest 512 boundary. This is necessary
		// because we always write a minimum of 512 byte units in direct IO
		// mode. If we did not round the maximum down, our last packet could
		// end at an offset that is less than the maximum, but greater than
		// the nearest 512 byte boundary - technically within the
		// user-specified size limit. However, because we always write a
		// full 512 bytes of data to fill out the last block when we are in
		// direct IO mode, we would end up with a file that was slightly
		// larger than the user-specified limit. The EOF in the header of
		// the file would be below the limit, but the actual file size would
		// not be. Thus, the need to round down.

		m_uiRflMaxFileSize = ROUND_DOWN_TO_NEAREST_512( m_uiRflMaxFileSize);

		// The maximum cannot go below a certain threshold - must have room
		// for least one packet plus the header.

		if (m_uiRflMaxFileSize < RFL_MAX_PACKET_SIZE + 512)
		{
			m_uiRflMaxFileSize = RFL_MAX_PACKET_SIZE + 512;
		}
		else if (m_uiRflMaxFileSize > gv_FlmSysData.uiMaxFileSize)
		{
			m_uiRflMaxFileSize = gv_FlmSysData.uiMaxFileSize;
		}
	}
	else
	{
		m_bKeepRflFiles = FALSE;
		m_uiRflMaxFileSize = gv_FlmSysData.uiMaxFileSize;
	}
	
	m_uiRflMinFileSize = (FLMUINT) FB2UD( &m_pFile->ucLastCommittedLogHdr[
												LOG_RFL_MIN_FILE_SIZE]);

	// Minimum RFL file size should not be allowed to be larger than
	// maximum!

	if (m_uiRflMinFileSize > m_uiRflMaxFileSize)
	{
		m_uiRflMinFileSize = m_uiRflMaxFileSize;
	}

	// Set the operation count to zero.

	m_uiOperCount = 0;

	// Set file extend sizes

	m_pFileHdl->setMaxAutoExtendSize( m_uiRflMaxFileSize);
	m_pFileHdl->setExtendSize( m_pFile->uiFileExtendSize);

Exit:

	return (rc);
}

/********************************************************************
Desc:		Log transaction begin.  This routine will also make sure
			we have opened an RFL file.
			NOTE: The prior version of FLAIM (before 4.3) would log
			a time and set the RFL_TIME_LOGGED_FLAG bit in the packet
			type.  This is no longer done.  Old code should be
			compatible because it reads the flag.
*********************************************************************/
RCODE F_Rfl::logBeginTransaction(
	FDB *			pDb)
{
	RCODE			rc = FERR_OK;
	FLMUINT		uiDbVersion = pDb->pFile->FileHdr.uiVersionNum;
	FLMBYTE *	pucPacketBody;
	FLMUINT		uiPacketBodyLen;
	FLMUINT		uiGMTTime;

	// Do nothing if logging is disabled.

	if (m_bLoggingOff)
	{
		goto Exit;
	}

	// Better not be in the middle of logging unknown stuff for the
	// application

	flmAssert( !m_bLoggingUnknown);

	// Better not be in the middle of a transaction.

	flmAssert( !m_uiCurrTransID);

	if (RC_BAD( rc = setupTransaction()))
	{
		goto Exit;
	}

	uiPacketBodyLen = uiDbVersion >= FLM_FILE_FORMAT_VER_4_31 ? 12 : 8;

	// Make sure we have space in the RFL buffer for a complete packet.

	if (!haveBuffSpace( uiPacketBodyLen + RFL_PACKET_OVERHEAD))
	{
		if (RC_BAD( rc = flush( m_pCurrentBuf)))
		{
			goto Exit;
		}
	}

	// Get a pointer to where we will be laying down the packet body.

	pucPacketBody = getPacketBodyPtr();

	// Output the transaction ID.

	UD2FBA( (FLMUINT32) pDb->LogHdr.uiCurrTransID, pucPacketBody);
	pucPacketBody += 4;

	// This used to be a FLM_GET_TIMER() value in pre-4.3 code, but it was
	// never really used. Set it to GMT time now.

	f_timeGetSeconds( &uiGMTTime);
	UD2FBA( (FLMUINT32) uiGMTTime, pucPacketBody);
	pucPacketBody += 4;

	// NOTE: In the pre-4.3 code the next four bytes would be zero, but
	// that is really unnecessary. We will simply no longer set the
	// RFL_TIME_LOGGED_FLAG bit in the packet type. Pre-4.3 code should be
	// compatible.

	if (uiDbVersion >= FLM_FILE_FORMAT_VER_4_31)
	{
		FLMUINT	uiLastLoggedCommitID;

		uiLastLoggedCommitID = FB2UD( 
					&m_pFile->ucLastCommittedLogHdr[LOG_LAST_RFL_COMMIT_ID]);

		UD2FBA( (FLMUINT32) uiLastLoggedCommitID, pucPacketBody);
		pucPacketBody += 4;

		if (RC_BAD( rc = finishPacket( RFL_TRNS_BEGIN_EX_PACKET, uiPacketBodyLen,
					  TRUE)))
		{
			goto Exit;
		}
	}
	else
	{
		if (RC_BAD( rc = finishPacket( RFL_TRNS_BEGIN_PACKET, uiPacketBodyLen,
					  TRUE)))
		{
			goto Exit;
		}
	}

	// Save the file offset for the start transaction packet.

	m_uiTransStartFile = m_pCurrentBuf->uiCurrFileNum;
	m_uiTransStartAddr = m_pCurrentBuf->uiRflFileOffset +
		m_pCurrentBuf->uiRflBufBytes -
		uiPacketBodyLen -
		RFL_PACKET_OVERHEAD;
	m_uiCurrTransID = pDb->LogHdr.uiCurrTransID;

Exit:

	return (rc);
}

/********************************************************************
Desc:		Flushes the RFL and sets some things in the log header.
*********************************************************************/
void F_Rfl::finalizeTransaction(void)
{
	FLMUINT		uiRflTransEndOffset;
	FLMBYTE *	pucLogHdr = &m_pFile->ucUncommittedLogHdr[0];

	// Save the serial numbers and file numbers into the file's
	// uncommitted log header.

	UD2FBA( (FLMUINT32) m_pCurrentBuf->uiCurrFileNum,
			 &pucLogHdr[LOG_RFL_FILE_NUM]);

	uiRflTransEndOffset = getCurrWriteOffset();
	UD2FBA( (FLMUINT32) uiRflTransEndOffset,
			 &pucLogHdr[LOG_RFL_LAST_TRANS_OFFSET]);

	f_memcpy( &pucLogHdr[LOG_LAST_TRANS_RFL_SERIAL_NUM], m_ucCurrSerialNum,
				F_SERIAL_NUM_SIZE);

	f_memcpy( &pucLogHdr[LOG_RFL_NEXT_SERIAL_NUM], m_ucNextSerialNum,
				F_SERIAL_NUM_SIZE);
}

/********************************************************************
Desc:		Handles the commit and abort log operations. If aborting
			the transaction, or if the transaction was empty, we will
			simply throw away the entire transaction and not bother
			to log it.  In that case we will reset transaction pointers,
			etc. back to the file and offset where the transaction began. 
			We will also delete RFL files that were created during the
			transaction if necessary.  NOTE: It is not essential that
			the RFL files be deleted.  If they are not successfully
			deleted, they will be overwritten if need be when creating
			new ones.
Note:		The prior version of FLAIM (before 4.3) would log
			a time and set the RFL_TIME_LOGGED_FLAG bit in the packet
			type.  This is no longer done.  Old code should be
			compatible because it reads the flag.
*********************************************************************/
RCODE F_Rfl::logEndTransaction(
	FLMUINT		uiPacketType,
	FLMBOOL		bThrowLogAway,
	FLMBOOL *	pbLoggedTransEnd)
{
	RCODE			rc = FERR_OK;
	RCODE			rc2 = FERR_OK;
	FLMUINT		uiLowFileNum;
	FLMUINT		uiHighFileNum;
	char			szRflFileName[ F_PATH_MAX_SIZE];
	FLMBYTE *	pucPacketBody;
	FLMUINT		uiPacketBodyLen;

	// Better not be in the middle of logging unknown stuff for the
	// application

	flmAssert( !m_bLoggingUnknown);

	// Initialize the "logged trans end" flag

	if (pbLoggedTransEnd)
	{
		*pbLoggedTransEnd = FALSE;
	}

	// Do nothing if logging is disabled.

	if (m_bLoggingOff)
	{
		goto Exit;
	}

	flmAssert( m_pFileHdl);
	flmAssert( m_pFile);

	// If the transaction had no operations, throw it away - don't even
	// log the packet. An abort operation may also elect to throw the log
	// away even if there were operations. That is determined by the
	// bThrowLogAway flag. The bThrowLogAway flag may be TRUE when doing a
	// commit if the caller knows that nothing happened during the
	// transction.

	if (bThrowLogAway || !m_uiOperCount)
	{
Throw_Away_Transaction:

		// If we have switched files, delete all but the file we started in.

		if (m_pCurrentBuf->uiCurrFileNum != m_uiTransStartFile)
		{
			flmAssert( m_pCurrentBuf->uiCurrFileNum > m_uiTransStartFile);

			// File number in uncommitted log header better not have been
			// changed yet. It is only supposed to be changed when the
			// transaction finishes - i.e., in this routine. Up until this
			// point, it should only be changed in
			// m_pCurrentBuf->uiCurrFileNum.

			flmAssert( m_uiTransStartFile == (FLMUINT) FB2UD(
							 &m_pFile->ucUncommittedLogHdr[LOG_RFL_FILE_NUM]));

			uiLowFileNum = m_uiTransStartFile + 1;
			uiHighFileNum = m_pCurrentBuf->uiCurrFileNum;

			// Close the current file so it can be deleted.

			if (RC_BAD( rc = waitForCommit()))
			{
				goto Exit;
			}

			closeFile();

			// Delete as many of the files as possible. Don't worry about
			// errors here.

			while (uiLowFileNum <= uiHighFileNum)
			{
				if (RC_OK( getFullRflFileName( uiLowFileNum, szRflFileName)))
				{
					(void) gv_FlmSysData.pFileSystem->deleteFile( szRflFileName);
				}

				uiLowFileNum++;
			}
		}
		else
		{

			// If we are in the file the transaction started in, simply
			// reset to where the transaction started.

			if (RC_BAD( rc2 = positionTo( m_uiTransStartAddr)))
			{

				// If we got to this point because of a "goto
				// Throw_Away_Transaction", we don't want to clobber the
				// original error code. So, we use rc2 temporarily and then
				// determine if its value should be set into rc.

				if (RC_OK( rc))
				{
					rc = rc2;
				}

				rc2 = FERR_OK;
				goto Exit;
			}
		}
	}
	else
	{

		// Log a commit or abort packet.

		uiPacketBodyLen = 8;

		// Make sure we have space in the RFL buffer for a complete packet.

		if (!haveBuffSpace( uiPacketBodyLen + RFL_PACKET_OVERHEAD))
		{
			if (RC_BAD( rc = flush( m_pCurrentBuf)))
			{
				goto Throw_Away_Transaction;
			}
		}

		// Get a pointer to where we will be laying down the packet body.

		pucPacketBody = getPacketBodyPtr();

		// Output the transaction ID.

		UD2FBA( (FLMUINT32) m_uiCurrTransID, pucPacketBody);
		pucPacketBody += 4;
		UD2FBA( (FLMUINT32) m_uiTransStartAddr, pucPacketBody);
		pucPacketBody += 4;
		if (RC_BAD( rc = finishPacket( uiPacketType, uiPacketBodyLen, FALSE)))
		{
			goto Throw_Away_Transaction;
		}

		finalizeTransaction();

		if (pbLoggedTransEnd)
		{
			*pbLoggedTransEnd = TRUE;
		}
	}

Exit:

	if (!m_bLoggingOff)
	{
		m_uiCurrTransID = 0;
	}

	return (RC_BAD( rc) ? rc : rc2);
}

/********************************************************************
Desc:		Log add, modify, delete, and reserve DRN packets
*********************************************************************/
RCODE F_Rfl::logUpdatePacket(
	FLMUINT	uiPacketType,
	FLMUINT	uiContainer,
	FLMUINT	uiDrn,
	FLMUINT	uiAutoTrans)
{
	RCODE			rc = FERR_OK;
	FLMBYTE *	pucPacketBody;
	FLMUINT		uiPacketBodyLen;

	// Do nothing if logging is disabled.

	if (m_bLoggingOff)
	{
		goto Exit;
	}

	// Better not be in the middle of logging unknown stuff for the
	// application

	flmAssert( !m_bLoggingUnknown);

	// Better be in the middle of a transaction.

	flmAssert( m_uiCurrTransID);

	m_uiOperCount++;

	if (uiPacketType == RFL_ADD_RECORD_PACKET_VER_2 ||
		 uiPacketType == RFL_MODIFY_RECORD_PACKET_VER_2 ||
		 uiPacketType == RFL_DELETE_RECORD_PACKET_VER_2)
	{
		uiPacketBodyLen = 11;
	}
	else
	{
		uiPacketBodyLen = 10;
	}

	// Make sure we have space in the RFL buffer for a complete packet.

	if (!haveBuffSpace( uiPacketBodyLen + RFL_PACKET_OVERHEAD))
	{
		if (RC_BAD( rc = flush( m_pCurrentBuf)))
		{
			goto Exit;
		}
	}

	// Get a pointer to where we will be laying down the packet body.

	pucPacketBody = getPacketBodyPtr();

	// Output the transaction ID.

	UD2FBA( (FLMUINT32) m_uiCurrTransID, pucPacketBody);
	pucPacketBody += 4;

	// Output the container number.

	UW2FBA( (FLMUINT16) uiContainer, pucPacketBody);
	pucPacketBody += 2;

	// Output the DRN.

	UD2FBA( (FLMUINT32) uiDrn, pucPacketBody);
	pucPacketBody += 4;

	// Output the flags

	if (uiPacketType == RFL_ADD_RECORD_PACKET_VER_2 ||
		 uiPacketType == RFL_MODIFY_RECORD_PACKET_VER_2 ||
		 uiPacketType == RFL_DELETE_RECORD_PACKET_VER_2)
	{
		FLMUINT	uiFlags = 0;

		// For now, these are the only flags we log

		if (uiAutoTrans & FLM_DO_IN_BACKGROUND)
		{
			uiFlags |= RFL_UPDATE_BACKGROUND;
		}

		if (uiAutoTrans & FLM_SUSPENDED)
		{
			uiFlags |= RFL_UPDATE_SUSPENDED;
		}

		*pucPacketBody++ = (FLMBYTE) uiFlags;
	}

	// Finish the packet

	if (RC_BAD( rc = finishPacket( uiPacketType, uiPacketBodyLen, FALSE)))
	{
		goto Exit;
	}

Exit:

	return (rc);
}

/********************************************************************
Desc:		Log index suspend and resume packets
*********************************************************************/
RCODE F_Rfl::logIndexSuspendOrResume(
	FLMUINT	uiIndexNum,
	FLMUINT	uiPacketType)
{
	RCODE			rc = FERR_OK;
	FLMBYTE *	pucPacketBody;
	FLMUINT		uiPacketBodyLen;

	// This call is new with 4.51 databases - not supported in older
	// versions, so don't log it.

	if (m_pFile->FileHdr.uiVersionNum < FLM_FILE_FORMAT_VER_4_51)
	{
		goto Exit;
	}

	// Do nothing if logging is disabled.

	if (m_bLoggingOff)
	{
		goto Exit;
	}

	// Better not be in the middle of logging unknown stuff for the
	// application

	flmAssert( !m_bLoggingUnknown);

	// Better be in the middle of a transaction.

	flmAssert( m_uiCurrTransID);

	m_uiOperCount++;
	uiPacketBodyLen = 6;

	// Make sure we have space in the RFL buffer for a complete packet.

	if (!haveBuffSpace( uiPacketBodyLen + RFL_PACKET_OVERHEAD))
	{
		if (RC_BAD( rc = flush( m_pCurrentBuf)))
		{
			goto Exit;
		}
	}

	// Get a pointer to where we will be laying down the packet body.

	pucPacketBody = getPacketBodyPtr();

	// Output the transaction ID.

	UD2FBA( (FLMUINT32) m_uiCurrTransID, pucPacketBody);
	pucPacketBody += 4;

	// Output the index number.

	UW2FBA( (FLMUINT16) uiIndexNum, pucPacketBody);
	pucPacketBody += 2;

	// Finish the packet

	if (RC_BAD( rc = finishPacket( uiPacketType, uiPacketBodyLen, FALSE)))
	{
		goto Exit;
	}

Exit:

	return (rc);
}

/********************************************************************
Desc:
*********************************************************************/
RCODE F_Rfl::logSizeEventConfig(
	FLMUINT		uiTransID,
	FLMUINT		uiSizeThreshold,
	FLMUINT		uiSecondsBetweenEvents,
	FLMUINT		uiBytesBetweenEvents)
{
	RCODE			rc = FERR_OK;
	FLMBYTE *	pucPacketBody;
	FLMUINT		uiPacketBodyLen;

	// Don't log the operation if it isn't supported by the current database
	// version

	if (m_pFile->FileHdr.uiVersionNum < FLM_FILE_FORMAT_VER_4_61)
	{
		goto Exit;
	}

	// Do nothing if logging is disabled.

	if (m_bLoggingOff)
	{
		goto Exit;
	}

	// Better not be in the middle of logging unknown stuff for the
	// application

	flmAssert( !m_bLoggingUnknown);

	// We need to set up to log this packet as if we were logging a
	// transaction. The only difference is that we don't log the begin
	// transaction packet.

	if (RC_BAD( rc = setupTransaction()))
	{
		goto Exit;
	}

	uiPacketBodyLen = 16;

	// Make sure we have space in the RFL buffer for a complete packet.

	if( !haveBuffSpace( uiPacketBodyLen + RFL_PACKET_OVERHEAD))
	{
		if (RC_BAD( rc = flush( m_pCurrentBuf)))
		{
			goto Exit;
		}
	}

	// Get a pointer to where we will be laying down the packet body.

	pucPacketBody = getPacketBodyPtr();

	// Output the transaction ID.

	UD2FBA( (FLMUINT32) uiTransID, pucPacketBody);
	pucPacketBody += 4;

	// Output the size threshold

	UD2FBA( (FLMUINT32) uiSizeThreshold, pucPacketBody);
	pucPacketBody += 4;

	// Output the time frequency

	UD2FBA( (FLMUINT32) uiSecondsBetweenEvents, pucPacketBody);
	pucPacketBody += 4;
	
	// Output the size frequency

	UD2FBA( (FLMUINT32) uiBytesBetweenEvents, pucPacketBody);
	pucPacketBody += 4;
	
	// Finish the packet

	if (RC_BAD( rc = finishPacket( RFL_CONFIG_SIZE_EVENT_PACKET, 
		uiPacketBodyLen, TRUE)))
	{
		goto Exit;
	}

Exit:

	return (rc);
}

/********************************************************************
Desc:		Make room in the RFL buffer for the additional bytes.
			This is done by flushing the log buffer and shifting down
			the bytes already used in the current packet.  If that doesn't
			make room, the current packet will be finished and a new one
			started.
*********************************************************************/
RCODE F_Rfl::makeRoom(
	FLMUINT		uiAdditionalBytesNeeded,
	FLMUINT *	puiCurrPacketLenRV,
	FLMUINT		uiPacketType,
	FLMUINT *	puiBytesAvailableRV,
	FLMUINT *	puiPacketCountRV)
{
	RCODE		rc = FERR_OK;
	FLMUINT	uiBytesNeeded;

	// Must account for encryption, so round bytes needed to nearest four
	// byte boundary.

	uiBytesNeeded = *puiCurrPacketLenRV + uiAdditionalBytesNeeded;
	if (uiBytesNeeded & 0x3)
	{
		uiBytesNeeded += (4 - (uiBytesNeeded & 0x3));
	}

	if (uiBytesNeeded <= (FLMUINT) RFL_MAX_PACKET_SIZE)
	{
		FLMUINT	uiTmp = uiBytesNeeded;

		if (haveBuffSpace( uiTmp))
		{
			if (puiBytesAvailableRV)
			{
				*puiBytesAvailableRV = uiAdditionalBytesNeeded;
			}
		}
		else
		{

			// Bytes requested will fit into a packet, but not the buffer,
			// so we need to shift the packets in the buffer down. The
			// shiftPacketsDown guarantees that there is room in the buffer
			// for a full size packet.

			if (RC_BAD( rc = shiftPacketsDown( *puiCurrPacketLenRV, FALSE)))
			{
				goto Exit;
			}

			// If a non-NULL puwBytesAvailableRV is passed in it means that
			// we are to return the number of bytes that we can actually
			// output. Since we know there is enough for the bytes needed, we
			// simply return the number of bytes that were requested.

			if (puiBytesAvailableRV)
			{
				*puiBytesAvailableRV = uiAdditionalBytesNeeded;
			}
		}
	}
	else	// (uiBytesNeeded > RFL_MAX_PACKET_SIZE)
	{

		// This is the case where the bytes needed would overflow the
		// maximum packet size. If puwBytesAvailableRV is NULL, it means
		// that all of the requested additional bytes must fit into the
		// packet. In that case, since the requested bytes would put us over
		// the packet size limit, we must finish the current packet and then
		// flush the packets out of the buffer so we can start a new packet.

		if (!puiBytesAvailableRV)
		{

			// Finish the current packet and start a new one.

			if (puiPacketCountRV)
			{
				(*puiPacketCountRV)++;
			}

			if (RC_BAD( rc = finishPacket( uiPacketType,
						  *puiCurrPacketLenRV - RFL_PACKET_OVERHEAD, FALSE)))
			{
				goto Exit;
			}

			if (RC_BAD( rc = flush( m_pCurrentBuf)))
			{
				goto Exit;
			}

			*puiCurrPacketLenRV = RFL_PACKET_OVERHEAD;
		}
		else
		{

			// When puiBytesAvailableRV is non-NULL, it means we can fill up
			// the rest of the packet with part of the bytes. In this case we
			// return the number of bytes available and then shift the
			// packets down in the buffer to make sure there is room for a
			// full-size packet.

			*puiBytesAvailableRV = RFL_MAX_PACKET_SIZE -*puiCurrPacketLenRV;
			if (RC_BAD( rc = shiftPacketsDown( *puiCurrPacketLenRV, FALSE)))
			{
				goto Exit;
			}
		}
	}

Exit:

	return (rc);
}

/********************************************************************
Desc:		Log a chunk of data to the RFL log - typically used to log
			field data.  Will spill over into multiple packets if
			necessary.
*********************************************************************/
RCODE F_Rfl::logData(
	FLMUINT				uiDataLen,
	const FLMBYTE *	pucData,
	FLMUINT				uiPacketType,
	FLMUINT *			puiPacketLenRV,
	FLMUINT *			puiPacketCountRV,
	FLMUINT *			puiMaxLogBytesNeededRV,
	FLMUINT *			puiTotalBytesLoggedRV)
{
	RCODE			rc = FERR_OK;
	FLMUINT		uiBytesAvail;
	FLMBYTE *	pucDest;

	while (uiDataLen)
	{
		if (RC_BAD( rc = makeRoom( uiDataLen, puiPacketLenRV, uiPacketType,
					  &uiBytesAvail, puiPacketCountRV)))
		{
			goto Exit;
		}

		if (uiBytesAvail)
		{
			if (puiMaxLogBytesNeededRV)
			{
				if (RC_BAD( rc = RflCheckMaxLogged( puiMaxLogBytesNeededRV,
							  *puiPacketCountRV, puiTotalBytesLoggedRV, uiBytesAvail)))
				{
					goto Exit;
				}
			}

			pucDest = getPacketPtr() + (*puiPacketLenRV);
			f_memcpy( pucDest, pucData, uiBytesAvail);
			uiDataLen -= uiBytesAvail;
			pucData += uiBytesAvail;
			(*puiPacketLenRV) += uiBytesAvail;
		}

		// If we didn't get all of the data into the RFL buffer, finish and
		// flush the current packet.

		if (uiDataLen)
		{
			if (puiPacketCountRV)
			{
				(*puiPacketCountRV)++;
			}

			if (RC_BAD( rc = finishPacket( uiPacketType,
						  *puiPacketLenRV - RFL_PACKET_OVERHEAD, FALSE)))
			{
				goto Exit;
			}

			if (RC_BAD( rc = flush( m_pCurrentBuf)))
			{
				goto Exit;
			}

			*puiPacketLenRV = RFL_PACKET_OVERHEAD;
			if (puiMaxLogBytesNeededRV)
			{
				if (RC_BAD( rc = RflCheckMaxLogged( puiMaxLogBytesNeededRV,
							  *puiPacketCountRV, puiTotalBytesLoggedRV,
							  RFL_PACKET_OVERHEAD)))
				{
					goto Exit;
				}
			}
		}
	}

Exit:

	return (rc);
}

/********************************************************************
Desc: Check to see if by logging the requested number of bytes we
		will end up exceeding the maximum bytes needed.  If so, and
		we have not yet actually logged a packet, return
		FERR_FAILURE so that we will discard this packet that is
		being built.  If we have already logged a packet, it is
		too late to discard what has been done.
*********************************************************************/
FSTATIC RCODE RflCheckMaxLogged(
	FLMUINT *	puiMaxBytesNeededRV,
	FLMUINT		uiPacketsLogged,
	FLMUINT *	puiCurrTotalLoggedRV,
	FLMUINT		uiBytesToLog)
{
	RCODE rc = FERR_OK;

	*puiCurrTotalLoggedRV += uiBytesToLog;

	if ((!uiPacketsLogged) && (*puiCurrTotalLoggedRV > *puiMaxBytesNeededRV))
	{
		rc = RC_SET( FERR_FAILURE);
		goto Exit;
	}

Exit:

	return (rc);
}

/********************************************************************
Desc: Callback function that captures the changes being logged by
		the call to flmRecordDifference.
*********************************************************************/
FSTATIC void RflChangeCallback(
	GRD_DifferenceData&	DiffData,
	void *					CallbackData)
{
	RFL_CHANGE_DATA *	pRflChangeData = (RFL_CHANGE_DATA*) CallbackData;
	F_Rfl *				pRfl = pRflChangeData->pRfl;
	void *				pvField;
	const FLMBYTE *	pucExportPtr;
	FLMBYTE *			pucTmp;
	FLMUINT				uiOverhead = 0;
	FLMUINT				uiBytesToLog;
	FLMUINT				uiPos;
	FLMUINT				uiTagNum;
	FLMUINT				uiDataLen = 0;
	FLMBOOL				bEncrypted = FALSE;
	FLMUINT				uiEncId;
	FLMBYTE				ucChangeType = 0;

	// If we had an error before this callback, do nothing.

	if (RC_BAD( pRflChangeData->rc))
	{
		goto Exit;
	}

	switch (DiffData.type)
	{
		case GRD_Inserted:
		{
			bEncrypted = DiffData.pAfterRecord->isEncryptedField( DiffData.pvAfterField);
			uiDataLen = DiffData.pAfterRecord->getDataLength( DiffData.pvAfterField);
			if (pRflChangeData->uiVersionNum < FLM_FILE_FORMAT_VER_4_61)
			{
				if (bEncrypted)
				{
					uiOverhead = 13;
					ucChangeType = RFL_INSERT_ENC_FIELD;
				}
				else
				{
					uiOverhead = 9;
					ucChangeType = RFL_INSERT_FIELD;
				}
			}
			else
			{
				if (bEncrypted)
				{
					uiOverhead = 17;
					ucChangeType = RFL_INSERT_ENC_LARGE_FIELD;
				}
				else
				{
					uiOverhead = 11;
					ucChangeType = RFL_INSERT_LARGE_FIELD;
				}
			}
			break;
		}
			
		case GRD_Deleted:
		{

			// Ignore these for versions of the database >= 4.60

			if (pRflChangeData->uiVersionNum >= FLM_FILE_FORMAT_VER_4_60)
			{
				goto Exit;
			}

			uiOverhead = 3;
			break;
		}
		
		case GRD_DeletedSubtree:
		{

			// Ignore these for versions of the database < 4.60

			if (pRflChangeData->uiVersionNum < FLM_FILE_FORMAT_VER_4_60)
			{
				goto Exit;
			}

			uiOverhead = 3;
			break;
		}
		
		case GRD_Modified:
		{
			bEncrypted = DiffData.pAfterRecord->isEncryptedField( DiffData.pvAfterField);
			uiDataLen = DiffData.pAfterRecord->getDataLength( DiffData.pvAfterField);
			if (pRflChangeData->uiVersionNum < FLM_FILE_FORMAT_VER_4_61)
			{
				if (bEncrypted)
				{
					uiOverhead = 10;
					ucChangeType = RFL_MODIFY_ENC_FIELD;
				}
				else
				{
					uiOverhead = 6;
					ucChangeType = RFL_MODIFY_FIELD;
				}
			}
			else
			{
				if (bEncrypted)
				{
					uiOverhead = 14;
					ucChangeType = RFL_MODIFY_ENC_LARGE_FIELD;
				}
				else
				{
					uiOverhead = 8;
					ucChangeType = RFL_MODIFY_LARGE_FIELD;
				}
			}
			break;
		}
		
		default:
		{
			flmAssert( 0);
			break;
		}
	}

	// Determine the number of bytes that will actually be logged with
	// this overhead. If it won't fit in the current packet, we will have
	// to create a new packet - hence, we add RFL_PACKET_OVERHEAD to the
	// amount that will be logged.

	uiBytesToLog = uiOverhead;
	if (RFL_MAX_PACKET_SIZE - uiOverhead < pRflChangeData->uiCurrPacketLen)
	{
		uiBytesToLog += RFL_PACKET_OVERHEAD;
	}

	// See if the bytes we are going log will exceed the maximum bytes
	// needed.

	if (RC_BAD( pRflChangeData->rc = RflCheckMaxLogged(
					  &pRflChangeData->uiMaxLogBytesNeeded,
				  pRflChangeData->uiPacketCount,
				  &pRflChangeData->uiTotalBytesLogged, uiBytesToLog)))
	{
		goto Exit;
	}

	// Make room to log the overhead

	if (RC_BAD( pRflChangeData->rc = pRfl->makeRoom( uiOverhead,
				  &pRflChangeData->uiCurrPacketLen, RFL_CHANGE_FIELDS_PACKET, NULL,
				  &pRflChangeData->uiPacketCount)))
	{
		goto Exit;
	}

	pucTmp = pRfl->getPacketPtr() + pRflChangeData->uiCurrPacketLen;
	uiPos = DiffData.uiAbsolutePosition;
	UW2FBA( (FLMUINT16) uiPos, &pucTmp[1]);
	pRflChangeData->uiCurrPacketLen += uiOverhead;
	pvField = DiffData.pvAfterField;

	switch (DiffData.type)
	{
		case GRD_Inserted:
		{
			*pucTmp = ucChangeType;
			pucTmp += 3;
			uiTagNum = DiffData.pAfterRecord->getFieldID( pvField);
			UW2FBA( (FLMUINT16) uiTagNum, pucTmp);
			pucTmp += 2;
			*pucTmp++ = (FLMBYTE) DiffData.pAfterRecord->getDataType( pvField);
			*pucTmp++ = (FLMBYTE) DiffData.pAfterRecord->getLevel( pvField);
			if (pRflChangeData->uiVersionNum < FLM_FILE_FORMAT_VER_4_61)
			{
				UW2FBA( (FLMUINT16)uiDataLen, pucTmp);
				pucTmp += 2;
			}
			else
			{
				UD2FBA( (FLMUINT32)uiDataLen, pucTmp);
				pucTmp += 4;
			}

			if (bEncrypted)
			{
				uiEncId = DiffData.pAfterRecord->getEncryptionID( pvField);
				flmAssert( uiEncId);
				UW2FBA( (FLMUINT16) uiEncId, pucTmp);
				pucTmp += 2;

				uiDataLen = DiffData.pAfterRecord->getEncryptedDataLength( pvField);
				if (pRflChangeData->uiVersionNum < FLM_FILE_FORMAT_VER_4_61)
				{
					UW2FBA( (FLMUINT16)uiDataLen, pucTmp);
					pucTmp += 2;
				}
				else
				{
					UD2FBA( (FLMUINT32)uiDataLen, pucTmp);
					pucTmp += 4;
				}
			}

			// Log the data, if any.

			if (uiDataLen)
			{
				if (bEncrypted)
				{
					pucExportPtr = DiffData.pAfterRecord->getEncryptionDataPtr( pvField);
				}
				else
				{
					pucExportPtr = DiffData.pAfterRecord->getDataPtr( pvField);
				}

				if (!pucExportPtr)
				{
					pRflChangeData->rc = RC_SET( FERR_MEM);
					goto Exit;
				}

				if (RC_BAD( pRflChangeData->rc = pRfl->logData( uiDataLen,
							  pucExportPtr, RFL_CHANGE_FIELDS_PACKET,
							  &pRflChangeData->uiCurrPacketLen,
							  &pRflChangeData->uiPacketCount,
							  &pRflChangeData->uiMaxLogBytesNeeded,
							  &pRflChangeData->uiTotalBytesLogged)))
				{
					goto Exit;
				}
			}
			
			break;
		}
		
		case GRD_Deleted:
		case GRD_DeletedSubtree:
		{
			*pucTmp = RFL_DELETE_FIELD;
			break;
		}
		
		case GRD_Modified:
		{
			*pucTmp = ucChangeType;
			pucTmp += 3;

			// For now, just log the new bytes using RFL_REPLACE_BYTES option

			*pucTmp++ = RFL_REPLACE_BYTES;
			uiDataLen = DiffData.pAfterRecord->getDataLength( pvField);
			if (pRflChangeData->uiVersionNum < FLM_FILE_FORMAT_VER_4_61)
			{
				UW2FBA( (FLMUINT16)uiDataLen, pucTmp);
				pucTmp += 2;
			}
			else
			{
				UD2FBA( (FLMUINT32)uiDataLen, pucTmp);
				pucTmp += 4;
			}

			if (bEncrypted)
			{
				uiEncId = DiffData.pAfterRecord->getEncryptionID( pvField);
				flmAssert( uiEncId);
				UW2FBA( (FLMUINT16) uiEncId, pucTmp);
				pucTmp += 2;

				uiDataLen = DiffData.pAfterRecord->getEncryptedDataLength( pvField);
				if (pRflChangeData->uiVersionNum < FLM_FILE_FORMAT_VER_4_61)
				{
					UW2FBA( (FLMUINT16)uiDataLen, pucTmp);
					pucTmp += 2;
				}
				else
				{
					UD2FBA( (FLMUINT32)uiDataLen, pucTmp);
					pucTmp += 4;
				}
			}

			// Log the data, if any.

			if (uiDataLen)
			{
				if (bEncrypted)
				{
					pucExportPtr = DiffData.pAfterRecord->getEncryptionDataPtr( pvField);
				}
				else
				{
					pucExportPtr = DiffData.pAfterRecord->getDataPtr( pvField);
				}

				if (pucExportPtr == NULL)
				{
					pRflChangeData->rc = RC_SET( FERR_MEM);
					goto Exit;
				}

				if (RC_BAD( pRflChangeData->rc = pRfl->logData( uiDataLen,
							  pucExportPtr, RFL_CHANGE_FIELDS_PACKET,
							  &pRflChangeData->uiCurrPacketLen,
							  &pRflChangeData->uiPacketCount,
							  &pRflChangeData->uiMaxLogBytesNeeded,
							  &pRflChangeData->uiTotalBytesLogged)))
				{
					goto Exit;
				}
			}
			
			break;
		}
		
		default:
		{
			flmAssert( 0);
			break;
		}
	}

Exit:

	return;
}

/********************************************************************
Desc:		Log change fields for a record modify operation.
*********************************************************************/
RCODE F_Rfl::logChangeFields(
	FlmRecord *	pOldRecord,
	FlmRecord *	pNewRecord)
{
	RFL_CHANGE_DATA	RflChangeData;
	FLMUINT				uiTmpBodyLen;
	FLMUINT				uiDataLen;
	void *				pvNewField;
	FLMBOOL				bEncrypted;
	FLMUINT				uiOverhead;

	RflChangeData.rc = FERR_OK;
	RflChangeData.pRfl = this;
	RflChangeData.uiVersionNum = m_pFile->FileHdr.uiVersionNum;

	// Determine the total amount that would have to be logged if we just
	// logged the new record.

	RflChangeData.uiMaxLogBytesNeeded = RFL_PACKET_OVERHEAD;
	uiTmpBodyLen = 0;
	pvNewField = pNewRecord->root();
	for (; pvNewField; pvNewField = pNewRecord->next( pvNewField))
	{
		bEncrypted = pNewRecord->isEncryptedField( pvNewField);
		uiOverhead = (bEncrypted ? 10 : 6);
		if (uiTmpBodyLen + uiOverhead <= RFL_MAX_PACKET_BODY_SIZE)
		{
			uiTmpBodyLen += uiOverhead;
		}
		else
		{
			uiTmpBodyLen = uiOverhead;
			RflChangeData.uiMaxLogBytesNeeded += RFL_PACKET_OVERHEAD;
		}

		RflChangeData.uiMaxLogBytesNeeded += uiOverhead;
		if (bEncrypted)
		{
			uiDataLen = pNewRecord->getEncryptedDataLength( pvNewField);
		}
		else
		{
			uiDataLen = pNewRecord->getDataLength( pvNewField);
		}

		while (uiDataLen)
		{
			FLMUINT	uiTmp;

			uiTmp = RFL_MAX_PACKET_BODY_SIZE - uiTmpBodyLen;
			if (uiTmp >= uiDataLen)
			{
				uiTmp = uiDataLen;
				uiTmpBodyLen += uiDataLen;
			}
			else
			{
				uiTmpBodyLen = 0;
				RflChangeData.uiMaxLogBytesNeeded += RFL_PACKET_OVERHEAD;
			}

			RflChangeData.uiMaxLogBytesNeeded += uiTmp;
			uiDataLen -= uiTmp;
		}
	}

	// Account for terminating 0 at the end.

	if (uiTmpBodyLen + 2 > RFL_MAX_PACKET_BODY_SIZE)
	{
		RflChangeData.uiMaxLogBytesNeeded += RFL_PACKET_OVERHEAD;
	}

	RflChangeData.uiMaxLogBytesNeeded += 2;

	RflChangeData.uiPacketCount = 0;
	RflChangeData.uiTotalBytesLogged = RFL_PACKET_OVERHEAD;
	RflChangeData.uiCurrPacketLen = RFL_PACKET_OVERHEAD;

	if (!haveBuffSpace( RFL_PACKET_OVERHEAD))
	{
		if (RC_BAD( RflChangeData.rc = flush( m_pCurrentBuf)))
		{
			goto Exit;
		}
	}

	flmRecordDifference( pOldRecord, pNewRecord, RflChangeCallback,
							  (void *) &RflChangeData);

	// See if we exceeded the maximum log bytes. If so, just log the
	// changed record in its entirety.

	if (RC_BAD( RflChangeData.rc))
	{
		if (RflChangeData.rc == FERR_FAILURE)
		{
			RflChangeData.rc = logRecord( pNewRecord);
		}

		goto Exit;
	}
	else
	{
		FLMBYTE *		pucTmp;

		// Make room to log the 3 bytes of terminator

		if (RC_BAD( RflChangeData.rc = makeRoom( 3,
					  &RflChangeData.uiCurrPacketLen, RFL_CHANGE_FIELDS_PACKET, NULL,
					  &RflChangeData.uiPacketCount)))
		{
			if (RflChangeData.rc == FERR_FAILURE)
			{
				RflChangeData.rc = logRecord( pNewRecord);
			}

			goto Exit;
		}

		pucTmp = getPacketPtr() + RflChangeData.uiCurrPacketLen;
		*pucTmp++ = RFL_END_FIELD_CHANGES;
		UW2FBA( (FLMUINT16) 0, pucTmp);
		RflChangeData.uiCurrPacketLen += 3;

		if (RC_BAD( RflChangeData.rc = finishPacket( RFL_CHANGE_FIELDS_PACKET,
					  RflChangeData.uiCurrPacketLen - RFL_PACKET_OVERHEAD, FALSE)))
		{
			goto Exit;
		}
	}

Exit:

	return (RflChangeData.rc);
}

/********************************************************************
Desc:		Log a record for the record add or modify operations.
*********************************************************************/
RCODE F_Rfl::logRecord(
	FlmRecord *	pRecord)
{
	RCODE			rc = FERR_OK;
	FLMUINT		uiPacketLen = RFL_PACKET_OVERHEAD;
	void *		pvField;
	FLMBYTE *	pucTmp;
	FLMUINT		uiTagNum;
	FLMUINT		uiDataLen;
	FLMBOOL		bEncrypted;
	FLMUINT		uiEncId;
	FLMUINT		uiPacketType;
	FLMUINT		uiOverhead;

	if (!haveBuffSpace( RFL_PACKET_OVERHEAD))
	{
		if (RC_BAD( rc = flush( m_pCurrentBuf)))
		{
			goto Exit;
		}
	}

	if (m_pFile->FileHdr.uiVersionNum < FLM_FILE_FORMAT_VER_4_60)
	{
		uiPacketType = RFL_DATA_RECORD_PACKET;
	}
	else if (m_pFile->FileHdr.uiVersionNum < FLM_FILE_FORMAT_VER_4_61)
	{
		uiPacketType = RFL_ENC_DATA_RECORD_PACKET;
	}
	else
	{
		uiPacketType = RFL_DATA_RECORD_PACKET_VER_3;
	}

	pvField = pRecord->root();
	for (; pvField; pvField = pRecord->next( pvField))
	{
		if (uiPacketType == RFL_DATA_RECORD_PACKET)
		{
			bEncrypted = FALSE;
			uiOverhead = 6;
		}
		else if (uiPacketType == RFL_ENC_DATA_RECORD_PACKET)
		{
			bEncrypted = pRecord->isEncryptedField( pvField);
			uiOverhead = (bEncrypted ? 11 : 7);
		}
		else
		{
			bEncrypted = pRecord->isEncryptedField( pvField);
			uiOverhead = (bEncrypted ? 15 : 9);
		}

		if (RC_BAD( rc = makeRoom( uiOverhead, &uiPacketLen, uiPacketType, NULL,
					  NULL)))
		{
			goto Exit;
		}

		pucTmp = getPacketPtr() + uiPacketLen;
		uiPacketLen += uiOverhead;

		uiTagNum = pRecord->getFieldID( pvField);
		UW2FBA( (FLMUINT16) uiTagNum, pucTmp);
		pucTmp += 2;
		*pucTmp++ = (FLMBYTE) pRecord->getDataType( pvField);
		*pucTmp++ = (FLMBYTE) pRecord->getLevel( pvField);
		uiDataLen = pRecord->getDataLength( pvField);
		if (uiPacketType != RFL_DATA_RECORD_PACKET_VER_3)
		{
			UW2FBA( (FLMUINT16) uiDataLen, pucTmp);
			pucTmp += 2;
		}
		else
		{
			UD2FBA( (FLMUINT32) uiDataLen, pucTmp);
			pucTmp += 4;
		}

		// Record if this field is encrypted. If it is, then there will be
		// more data to follow.

		if (uiPacketType != RFL_DATA_RECORD_PACKET)
		{
			*pucTmp = (bEncrypted ? (FLMBYTE) 1 : (FLMBYTE) 0);
			pucTmp++;

			// Check for encrypted field and add the results.

			if (bEncrypted)
			{
				uiEncId = pRecord->getEncryptionID( pvField);
				flmAssert( uiEncId);
				UW2FBA( (FLMUINT16) uiEncId, pucTmp);
				pucTmp += 2;

				uiDataLen = pRecord->getEncryptedDataLength( pvField);
				if (uiPacketType == RFL_DATA_RECORD_PACKET_VER_3)
				{
					UD2FBA( (FLMUINT32)uiDataLen, pucTmp);
					pucTmp += 4;
				}
				else
				{
					UW2FBA( (FLMUINT16)uiDataLen, pucTmp);
					pucTmp += 2;
				}
			}
		}

		// Log the data, if any.

		if (uiDataLen)
		{
			const FLMBYTE *		pucExportPtr;

			if (bEncrypted)
			{
				pucExportPtr = pRecord->getEncryptionDataPtr( pvField);
			}
			else
			{
				pucExportPtr = pRecord->getDataPtr( pvField);
			}

			if (pucExportPtr == NULL)
			{
				rc = RC_SET( FERR_MEM);
				goto Exit;
			}

			if (RC_BAD( rc = logData( uiDataLen, pucExportPtr, uiPacketType,
						  &uiPacketLen, NULL, NULL, NULL)))
			{
				goto Exit;
			}
		}
	}

	// Add null to terminate the record.

	if (RC_BAD( rc = makeRoom( 2, &uiPacketLen, uiPacketType, NULL, NULL)))
	{
		goto Exit;
	}

	pucTmp = getPacketPtr() + uiPacketLen;
	uiPacketLen += 2;
	UW2FBA( 0, pucTmp);
	pucTmp += 2;

	// Finish the packet.

	if (RC_BAD( rc = finishPacket( uiPacketType, 
		uiPacketLen - RFL_PACKET_OVERHEAD, FALSE)))
	{
		goto Exit;
	}

Exit:

	return (rc);
}

/********************************************************************
Desc:		Log record add, modify, or delete operation
*********************************************************************/
RCODE F_Rfl::logUpdate(
	FLMUINT		uiContainer,
	FLMUINT		uiDrn,
	FLMUINT		uiAutoTrans,
	FlmRecord *	pOldRecord,
	FlmRecord *	pNewRecord)
{
	RCODE		rc = FERR_OK;
	FLMUINT	uiPacketType;

	// Do nothing if logging is disabled.

	if (m_bLoggingOff)
	{
		goto Exit;
	}

	// Better not be in the middle of logging unknown stuff for the
	// application

	flmAssert( !m_bLoggingUnknown);

	// Better be in the middle of a transaction.

	flmAssert( m_uiCurrTransID);

	if (pOldRecord && pNewRecord)
	{
		if (m_pFile->FileHdr.uiVersionNum >= FLM_FILE_FORMAT_VER_4_60)
		{
			uiPacketType = RFL_MODIFY_RECORD_PACKET_VER_2;
		}
		else
		{
			uiPacketType = RFL_MODIFY_RECORD_PACKET;
		}
	}
	else if (pNewRecord)
	{
		if (m_pFile->FileHdr.uiVersionNum >= FLM_FILE_FORMAT_VER_4_60)
		{
			uiPacketType = RFL_ADD_RECORD_PACKET_VER_2;
		}
		else
		{
			uiPacketType = RFL_ADD_RECORD_PACKET;
		}
	}
	else
	{
		if (m_pFile->FileHdr.uiVersionNum >= FLM_FILE_FORMAT_VER_4_60)
		{
			uiPacketType = RFL_DELETE_RECORD_PACKET_VER_2;
		}
		else
		{
			uiPacketType = RFL_DELETE_RECORD_PACKET;
		}
	}

	if (RC_BAD( rc = logUpdatePacket( uiPacketType, uiContainer, uiDrn,
				  uiAutoTrans)))
	{
		goto Exit;
	}

	// If it is a record modify, log the change fields. If it is a record
	// add, log the new record.

	if (pOldRecord && pNewRecord)
	{
		if (RC_BAD( rc = logChangeFields( pOldRecord, pNewRecord)))
		{
			goto Exit;
		}
	}
	else if (pNewRecord)
	{
		if (RC_BAD( rc = logRecord( pNewRecord)))
		{
			goto Exit;
		}
	}

Exit:

	return (rc);
}

/********************************************************************
Desc:		Log a set of records that is indexed for a specific index.
*********************************************************************/
RCODE F_Rfl::logIndexSet(
	FLMUINT	uiIndex,
	FLMUINT	uiContainerNum,
	FLMUINT	uiStartDrn,
	FLMUINT	uiEndDrn)
{
	RCODE			rc = FERR_OK;
	FLMBYTE *	pucPacketBody;
	FLMUINT		uiPacketBodyLen;

	// This call is a new database version. Database better have been
	// upgraded.

	flmAssert( m_pFile->FileHdr.uiVersionNum >= FLM_FILE_FORMAT_VER_3_02);

	// Do nothing if logging is disabled.

	if (m_bLoggingOff)
	{
		goto Exit;
	}

	// Better not be in the middle of logging unknown stuff for the
	// application

	flmAssert( !m_bLoggingUnknown);

	// Better be in the middle of a transaction.

	flmAssert( m_uiCurrTransID);

	m_uiOperCount++;
	uiPacketBodyLen = 
		(FLMUINT)((m_pFile->FileHdr.uiVersionNum >= FLM_FILE_FORMAT_VER_4_50) 
											? (FLMUINT) 16 
											: (FLMUINT) 14);

	// Make sure we have space in the RFL buffer for a complete packet.

	if (!haveBuffSpace( uiPacketBodyLen + RFL_PACKET_OVERHEAD))
	{
		if (RC_BAD( rc = flush( m_pCurrentBuf)))
		{
			goto Exit;
		}
	}

	// Get a pointer to where we will be laying down the packet body.

	pucPacketBody = getPacketBodyPtr();

	// Output the transaction ID.

	UD2FBA( (FLMUINT32) m_uiCurrTransID, pucPacketBody);
	pucPacketBody += 4;

	// Output the container number, if db version is >= 4.50

	if (m_pFile->FileHdr.uiVersionNum >= FLM_FILE_FORMAT_VER_4_50)
	{
		UW2FBA( (FLMUINT16) uiContainerNum, pucPacketBody);
		pucPacketBody += 2;
	}

	// Output the index number.

	UW2FBA( (FLMUINT16) uiIndex, pucPacketBody);
	pucPacketBody += 2;

	// Output the starting DRN.

	UD2FBA( (FLMUINT32) (uiStartDrn), pucPacketBody);
	pucPacketBody += 4;

	// Output the ending DRN.

	UD2FBA( (FLMUINT32) (uiEndDrn), pucPacketBody);
	pucPacketBody += 4;

	// Finish the packet

	if (RC_BAD( rc = finishPacket( (FLMUINT) (
					  (m_pFile->FileHdr.uiVersionNum >= FLM_FILE_FORMAT_VER_4_50) 
								? (FLMUINT) RFL_INDEX_SET_PACKET_VER_2
								: (FLMUINT) RFL_INDEX_SET_PACKET), 
					  uiPacketBodyLen, FALSE)))
	{
		goto Exit;
	}

Exit:

	return (rc);
}

/********************************************************************
Desc:		Start logging unknown packets.
*********************************************************************/
RCODE F_Rfl::startLoggingUnknown(void)
{
	RCODE			rc = FERR_OK;
	FLMBYTE *	pucPacketBody;
	FLMUINT		uiPacketBodyLen;

	flmAssert( m_pFile);

	// Do nothing if logging is disabled. Also, ignore these packets if we
	// are operating on a pre-4.3 database.

	if (m_bLoggingOff || m_pFile->FileHdr.uiVersionNum < FLM_FILE_FORMAT_VER_4_3)
	{
		goto Exit;
	}

	// Better not already be in the middle of logging unknown stuff for
	// the application

	flmAssert( !m_bLoggingUnknown);

	// Better be inside a transaction.

	flmAssert( m_uiCurrTransID);

	m_uiOperCount++;
	uiPacketBodyLen = 4;

	// Make sure we have space in the RFL buffer for a complete packet.

	if (!haveBuffSpace( uiPacketBodyLen + RFL_PACKET_OVERHEAD))
	{
		if (RC_BAD( rc = flush( m_pCurrentBuf)))
		{
			goto Exit;
		}
	}

	// Get a pointer to where we will be laying down the packet body.

	pucPacketBody = getPacketBodyPtr();

	// Output the transaction ID.

	UD2FBA( (FLMUINT32) m_uiCurrTransID, pucPacketBody);
	pucPacketBody += 4;

	// Finish the packet

	if (RC_BAD( rc = finishPacket( RFL_START_UNKNOWN_PACKET, uiPacketBodyLen,
				  FALSE)))
	{
		goto Exit;
	}

	m_bLoggingUnknown = TRUE;
	m_uiUnknownPacketLen = RFL_PACKET_OVERHEAD;
	
Exit:

	return (rc);
}

/********************************************************************
Desc:		Log unknown data.
*********************************************************************/
RCODE F_Rfl::logUnknown(
	FLMBYTE *	pucUnknown,
	FLMUINT		uiLen)
{
	RCODE rc = FERR_OK;

	// Do nothing if logging is disabled. Also, ignore these packets if we
	// are operating on a pre-4.3 database.

	if (m_bLoggingOff || m_pFile->FileHdr.uiVersionNum < FLM_FILE_FORMAT_VER_4_3)
	{
		goto Exit;
	}

	flmAssert( m_bLoggingUnknown);
	if (RC_BAD( rc = logData( uiLen, pucUnknown, RFL_UNKNOWN_PACKET,
				  &m_uiUnknownPacketLen, NULL, NULL, NULL)))
	{
		goto Exit;
	}

Exit:

	return (rc);
}

/********************************************************************
Desc:		End logging unknown packets.
*********************************************************************/
RCODE F_Rfl::endLoggingUnknown(void)
{
	RCODE rc = FERR_OK;

	flmAssert( m_pFile);

	// Do nothing if logging is disabled. Also, ignore these packets if we
	// are operating on a pre-4.3 database.

	if (m_bLoggingOff || m_pFile->FileHdr.uiVersionNum < FLM_FILE_FORMAT_VER_4_3)
	{
		goto Exit;
	}

	// Better be in the middle of logging unknown stuff for the application

	flmAssert( m_bLoggingUnknown);
	if (m_uiUnknownPacketLen > RFL_PACKET_OVERHEAD)
	{
		if (RC_BAD( rc = finishPacket( RFL_UNKNOWN_PACKET,
					  m_uiUnknownPacketLen - RFL_PACKET_OVERHEAD, FALSE)))
		{
			goto Exit;
		}
	}

Exit:

	m_bLoggingUnknown = FALSE;
	m_uiUnknownPacketLen = RFL_PACKET_OVERHEAD;
	return (rc);
}

/********************************************************************
Desc:		Log a reduce packet
*********************************************************************/
RCODE F_Rfl::logReduce(
	FLMUINT	uiTransID,
	FLMUINT	uiCount)
{
	RCODE			rc = FERR_OK;
	FLMBYTE *	pucPacketBody;
	FLMUINT		uiPacketBodyLen;

	// This call is new with 4.3 databases - not supported in older
	// versions, so don't log it.

	if (m_pFile->FileHdr.uiVersionNum < FLM_FILE_FORMAT_VER_4_3)
	{
		goto Exit;
	}

	// Do nothing if logging is disabled.

	if (m_bLoggingOff)
	{
		goto Exit;
	}

	// Better not be in the middle of logging unknown stuff for the
	// application

	flmAssert( !m_bLoggingUnknown);

	// We need to set up to log this packet as if we were logging a
	// transaction. The only difference is that we don't log the begin
	// transaction packet.

	if (RC_BAD( rc = setupTransaction()))
	{
		goto Exit;
	}

	uiPacketBodyLen = 8;

	// Make sure we have space in the RFL buffer for a complete packet.

	if (!haveBuffSpace( uiPacketBodyLen + RFL_PACKET_OVERHEAD))
	{
		if (RC_BAD( rc = flush( m_pCurrentBuf)))
		{
			goto Exit;
		}
	}

	// Get a pointer to where we will be laying down the packet body.

	pucPacketBody = getPacketBodyPtr();

	// Output the transaction ID.

	UD2FBA( (FLMUINT32) uiTransID, pucPacketBody);
	pucPacketBody += 4;

	// Output the count

	UD2FBA( (FLMUINT32) uiCount, pucPacketBody);
	pucPacketBody += 4;

	// Finish the packet

	if (RC_BAD( rc = finishPacket( RFL_REDUCE_PACKET, uiPacketBodyLen, TRUE)))
	{
		goto Exit;
	}

	// Finalize the transaction (as if we were committing a transaction)

	finalizeTransaction();

Exit:

	return (rc);
}

/********************************************************************
Desc:		Log a database conversion packet
Note:		This routine performs most of the setup for logging a full
			transaction, but it does not cause begin and commit packets
			to be logged.  It is a "standalone" transaction.
*********************************************************************/
RCODE F_Rfl::logUpgrade(
	FLMUINT		uiTransID,
	FLMUINT		uiOldVersion,
	FLMBYTE *	pucDBKey,
	FLMUINT32	ui32DBKeyLen)
{
	RCODE			rc = FERR_OK;
	FLMBYTE *	pucPacketBody;
	FLMUINT		uiPacketBodyLen;

	// Do nothing if logging is disabled.

	if (m_bLoggingOff)
	{
		goto Exit;
	}

	// Better not be in the middle of logging unknown stuff for the
	// application

	flmAssert( !m_bLoggingUnknown);

	// We need to set up to log this packet as if we were logging a
	// transaction. The only difference is that we don't log the begin
	// transaction packet.

	if (RC_BAD( rc = setupTransaction()))
	{
		goto Exit;
	}

	uiPacketBodyLen = 14 + ui32DBKeyLen;

	// Make sure we have space in the RFL buffer for a complete packet.

	if (!haveBuffSpace( uiPacketBodyLen + RFL_PACKET_OVERHEAD))
	{
		if (RC_BAD( rc = flush( m_pCurrentBuf)))
		{
			goto Exit;
		}
	}

	// Get a pointer to where we will be laying down the packet body.

	pucPacketBody = getPacketBodyPtr();

	// Output the transaction ID

	UD2FBA( (FLMUINT32) uiTransID, pucPacketBody);
	pucPacketBody += 4;

	// Output the old database version

	UD2FBA( (FLMUINT32) uiOldVersion, pucPacketBody);
	pucPacketBody += 4;

	// Output the new database version

	UD2FBA( (FLMUINT32) FLM_CUR_FILE_FORMAT_VER_NUM, pucPacketBody);
	pucPacketBody += 4;

	// For versions >= 4.60, the next two bytes will give the length of
	// the DB Key.

	flmAssert( ui32DBKeyLen <= 0xFFFF);
	UW2FBA( (FLMUINT16) ui32DBKeyLen, pucPacketBody);
	pucPacketBody += 2;

	// If we were built without encryption, the key length will be zero,
	// so no need to store the key.

	if (ui32DBKeyLen)
	{
		f_memcpy( pucPacketBody, pucDBKey, ui32DBKeyLen);
		pucPacketBody += ui32DBKeyLen;
	}

	// Finish the packet

	if (RC_BAD( rc = finishPacket( RFL_UPGRADE_PACKET, uiPacketBodyLen, TRUE)))
	{
		goto Exit;
	}

	// Finalize the transaction (as if we were committing a transaction)

	finalizeTransaction();

Exit:

	if (!m_bLoggingOff)
	{
		m_uiCurrTransID = 0;
	}

	return (rc);
}

/********************************************************************
Desc:		Log the wrapped database key
*********************************************************************/
RCODE F_Rfl::logWrappedKey(
	FLMUINT		uiTransID,
	FLMBYTE *	pucDBKey,
	FLMUINT32	ui32DBKeyLen)
{
	RCODE			rc = FERR_OK;
	FLMBYTE *	pucPacketBody;
	FLMUINT		uiPacketBodyLen;

	// Do nothing if logging is disabled.

	if (m_bLoggingOff)
	{
		goto Exit;
	}

	// Better not be in the middle of logging unknown stuff for the
	// application

	flmAssert( !m_bLoggingUnknown);

	if (RC_BAD( rc = setupTransaction()))
	{
		goto Exit;
	}

	uiPacketBodyLen = 6 + ui32DBKeyLen;

	// Make sure we have space in the RFL buffer for a complete packet.

	if (!haveBuffSpace( uiPacketBodyLen + RFL_PACKET_OVERHEAD))
	{
		if (RC_BAD( rc = flush( m_pCurrentBuf)))
		{
			goto Exit;
		}
	}

	// Get a pointer to where we will be laying down the packet body.

	pucPacketBody = getPacketBodyPtr();

	// Output the transaction ID

	UD2FBA( (FLMUINT32) uiTransID, pucPacketBody);
	pucPacketBody += 4;

	// The next two bytes will give the length of the DB Key.

	flmAssert( ui32DBKeyLen <= 0xFFFF);
	UW2FBA( (FLMUINT16) ui32DBKeyLen, pucPacketBody);
	pucPacketBody += 2;

	// If we were built without encryption, the key length will be zero,
	// so no need to store the key.

	if (ui32DBKeyLen)
	{
		f_memcpy( pucPacketBody, pucDBKey, ui32DBKeyLen);
		pucPacketBody += ui32DBKeyLen;
	}

	// Finish the packet

	if (RC_BAD( rc = finishPacket( RFL_WRAP_KEY_PACKET, uiPacketBodyLen, TRUE)))
	{
		goto Exit;
	}

	finalizeTransaction();

Exit:

	return (rc);
}

/********************************************************************
Desc:		Log that we have enabled encryption
*********************************************************************/
RCODE F_Rfl::logEnableEncryption(
	FLMUINT		uiTransID,
	FLMBYTE *	pucDBKey,
	FLMUINT32	ui32DBKeyLen)
{
	RCODE			rc = FERR_OK;
	FLMBYTE *	pucPacketBody;
	FLMUINT		uiPacketBodyLen;

	// Do nothing if logging is disabled.

	if (m_bLoggingOff)
	{
		goto Exit;
	}

	// Better not be in the middle of logging unknown stuff for the
	// application

	flmAssert( !m_bLoggingUnknown);

	if (RC_BAD( rc = setupTransaction()))
	{
		goto Exit;
	}

	uiPacketBodyLen = 6 + ui32DBKeyLen;

	// Make sure we have space in the RFL buffer for a complete packet.

	if (!haveBuffSpace( uiPacketBodyLen + RFL_PACKET_OVERHEAD))
	{
		if (RC_BAD( rc = flush( m_pCurrentBuf)))
		{
			goto Exit;
		}
	}

	// Get a pointer to where we will be laying down the packet body.

	pucPacketBody = getPacketBodyPtr();

	// Output the transaction ID

	UD2FBA( (FLMUINT32) uiTransID, pucPacketBody);
	pucPacketBody += 4;

	// The next two bytes will give the length of the DB Key.

	flmAssert( ui32DBKeyLen <= 0xFFFF);
	UW2FBA( (FLMUINT16) ui32DBKeyLen, pucPacketBody);
	pucPacketBody += 2;

	// If we were built without encryption, the key length will be zero,
	// so no need to store the key.

	if (ui32DBKeyLen)
	{
		f_memcpy( pucPacketBody, pucDBKey, ui32DBKeyLen);
		pucPacketBody += ui32DBKeyLen;
	}

	// Finish the packet

	if (RC_BAD( rc = finishPacket( RFL_ENABLE_ENCRYPTION_PACKET, uiPacketBodyLen,
				  TRUE)))
	{
		goto Exit;
	}

	finalizeTransaction();

Exit:

	return (rc);
}

/********************************************************************
Desc:		Log a block chain free operation
*********************************************************************/
RCODE F_Rfl::logBlockChainFree(
	FLMUINT	uiTrackerDrn,
	FLMUINT	uiCount,
	FLMUINT	uiEndAddr)
{
	RCODE			rc = FERR_OK;
	FLMBYTE *	pucPacketBody;
	FLMUINT		uiPacketBodyLen;

	// This call is new with 4.52 databases - not supported in older
	// versions, so don't log it.

	if (m_pFile->FileHdr.uiVersionNum < FLM_FILE_FORMAT_VER_4_60)
	{
		flmAssert( 0);
		goto Exit;
	}

	// Do nothing if logging is disabled.

	if (m_bLoggingOff)
	{
		goto Exit;
	}

	// Better not be in the middle of logging unknown stuff for the
	// application

	flmAssert( !m_bLoggingUnknown);

	// Better be in the middle of a transaction.

	flmAssert( m_uiCurrTransID);
	m_uiOperCount++;

	uiPacketBodyLen = 16;

	// Make sure we have space in the RFL buffer for a complete packet.

	if (!haveBuffSpace( uiPacketBodyLen + RFL_PACKET_OVERHEAD))
	{
		if (RC_BAD( rc = flush( m_pCurrentBuf)))
		{
			goto Exit;
		}
	}

	// Get a pointer to where we will be laying down the packet body.

	pucPacketBody = getPacketBodyPtr();

	// Output the transaction ID.

	UD2FBA( (FLMUINT32) m_uiCurrTransID, pucPacketBody);
	pucPacketBody += 4;

	// Output the tracker record number

	UD2FBA( (FLMUINT32) uiTrackerDrn, pucPacketBody);
	pucPacketBody += 4;

	// Output the count

	UD2FBA( (FLMUINT32) uiCount, pucPacketBody);
	pucPacketBody += 4;

	// Output the ending block address

	UD2FBA( (FLMUINT32) uiEndAddr, pucPacketBody);
	pucPacketBody += 4;

	// Finish the packet

	if (RC_BAD( rc = finishPacket( RFL_BLK_CHAIN_FREE_PACKET, uiPacketBodyLen,
				  TRUE)))
	{
		goto Exit;
	}

Exit:

	return (rc);
}

/********************************************************************
Desc:		Reads a full packet, based on what file offset and read
			offset are currently set to.
*********************************************************************/
RCODE F_Rfl::readPacket(
	FLMUINT	uiMinBytesNeeded)
{
	RCODE		rc = FERR_OK;
	FLMUINT	uiTmpOffset;
	FLMUINT	uiReadLen;
	FLMUINT	uiBytesRead;

	// If we have enough bytes in the buffer for the minimum bytes needed,
	// we don't need to retrieve any more bytes.

	if (m_pCurrentBuf->uiRflBufBytes - m_uiRflReadOffset >= uiMinBytesNeeded)
	{
		goto Exit;
	}

	// If we are doing restore, we have to do only sequential reads -
	// cannot depend on doing reads on 512 byte boundaries. Otherwise, we
	// read directly from disk on 512 byte boundaries.

	if (m_pRestore)
	{
		FLMUINT	uiCurrFilePos = m_pCurrentBuf->uiRflFileOffset +
			m_pCurrentBuf->uiRflBufBytes;

		if (m_uiRflReadOffset > 0)
		{

			// Move the bytes left in the buffer down to the beginning of
			// the buffer.

			f_memmove( m_pCurrentBuf->pIOBuffer->getBufferPtr(),
						 &(m_pCurrentBuf->pIOBuffer->getBufferPtr()[m_uiRflReadOffset]),
							 m_pCurrentBuf->uiRflBufBytes - m_uiRflReadOffset);
			m_pCurrentBuf->uiRflBufBytes -= m_uiRflReadOffset;
			m_pCurrentBuf->uiRflFileOffset += m_uiRflReadOffset;
			m_uiRflReadOffset = 0;
		}

		uiReadLen = m_uiBufferSize - m_pCurrentBuf->uiRflBufBytes;

		// Read enough to fill the rest of the buffer, which is guaranteed
		// to hold at least one full packet.

		if (!m_uiFileEOF)
		{
			if (uiCurrFilePos > (FLMUINT) (-1) - uiReadLen)
			{
				uiReadLen = (FLMUINT) (-1) - uiCurrFilePos;
			}
		}
		else
		{
			if (uiCurrFilePos + uiReadLen > m_uiFileEOF)
			{
				uiReadLen = m_uiFileEOF - uiCurrFilePos;
			}
		}

		// If reading will not give us the minimum bytes needed, we cannot
		// satisfy this request from the current file.

		if (uiReadLen + m_pCurrentBuf->uiRflBufBytes < uiMinBytesNeeded)
		{
			rc = RC_SET( FERR_BAD_RFL_PACKET);
			goto Exit;
		}

		// Read enough to get the entire packet.

		if (RC_BAD( rc = m_pRestore->read( uiReadLen, &(
						  m_pCurrentBuf->pIOBuffer->getBufferPtr()[
						  m_pCurrentBuf->uiRflBufBytes]), &uiBytesRead)))
		{
			if (rc == FERR_IO_END_OF_FILE)
			{
				rc = FERR_OK;
			}
			else
			{
				goto Exit;
			}
		}

		// If we didn't read enough to satisfy the minimum bytes needed, we
		// cannot satisfy this request from the current file.

		if (uiBytesRead + m_pCurrentBuf->uiRflBufBytes < uiMinBytesNeeded)
		{
			rc = RC_SET( FERR_BAD_RFL_PACKET);
			goto Exit;
		}

		m_pCurrentBuf->uiRflBufBytes += uiBytesRead;
	}
	else
	{

		// Set offsets so we are on a 512 byte boundary for our next read.
		// No need to move data, since we will be re-reading it anyway.

		if (m_uiRflReadOffset > 0)
		{
			uiTmpOffset = m_uiRflReadOffset - (m_uiRflReadOffset & 511);
			m_pCurrentBuf->uiRflFileOffset += uiTmpOffset;
			m_uiRflReadOffset -= uiTmpOffset;
		}
		else if (m_pCurrentBuf->uiRflFileOffset & 511)
		{
			m_uiRflReadOffset = m_pCurrentBuf->uiRflFileOffset & 511;
			m_pCurrentBuf->uiRflFileOffset -= m_uiRflReadOffset;
		}

		m_pCurrentBuf->uiRflBufBytes = 0;

		// Read enough to fill the rest of the buffer, which is guaranteed
		// to hold at least one full packet.

		uiReadLen = m_uiBufferSize;

		// m_uiFileEOF better not be zero at this point - we should always
		// know precisely where the RFL file ends when we are doing recovery
		// as opposed to doing a restore.

		flmAssert( m_uiFileEOF >= 512);
		if (m_pCurrentBuf->uiRflFileOffset + uiReadLen > m_uiFileEOF)
		{
			uiReadLen = m_uiFileEOF - m_pCurrentBuf->uiRflFileOffset;
		}

		// If reading will not give us the minimum number of bytes needed,
		// we have a bad packet.

		if (uiReadLen < m_uiRflReadOffset ||
			 uiReadLen - m_uiRflReadOffset < uiMinBytesNeeded)
		{
			rc = RC_SET( FERR_BAD_RFL_PACKET);
			goto Exit;
		}

		// Read to get the entire packet.

		if (RC_BAD( rc = m_pFileHdl->read( m_pCurrentBuf->uiRflFileOffset,
					  uiReadLen, m_pCurrentBuf->pIOBuffer->getBufferPtr(), 
					  &uiBytesRead)))
		{
			if (rc == FERR_IO_END_OF_FILE)
			{
				rc = FERR_OK;
			}
			else
			{
				m_bRflVolumeOk = FALSE;
				goto Exit;
			}
		}

		if (uiBytesRead < uiReadLen)
		{
			rc = RC_SET( FERR_BAD_RFL_PACKET);
			goto Exit;
		}

		m_pCurrentBuf->uiRflBufBytes = uiReadLen;
	}

Exit:

	return (rc);
}

/********************************************************************
Desc:		Gets and verifies the next packet from the roll-forward
			log file.  Packet checksum will be verified.
*********************************************************************/
RCODE F_Rfl::getPacket(
	FLMBOOL		bForceNextFile,
	FLMUINT *	puiPacketTypeRV,
	FLMBYTE **	ppucPacketBodyRV,
	FLMUINT *	puiPacketBodyLenRV,
	FLMBOOL *	pbLoggedTimes)
{
	RCODE			rc = FERR_OK;
	FLMBYTE *	pucPacket;
	FLMBYTE *	pucPacketBody;
	FLMUINT		uiPacketType;
	FLMUINT		uiEncryptPacketBodyLen;
	FLMBYTE		ucHdr[512];
	FLMUINT		uiBytesRead;

	// See if we need to go to the next file. Note that we only check for
	// this exactly on packet boundaries. We do not expect packets to be
	// split across files. If we are not at the end of processing what is
	// in the buffer, we should be able to read the rest of the packet from
	// the current file.

Get_Next_File:

	if (bForceNextFile || 
		 (m_uiFileEOF && m_uiRflReadOffset == m_pCurrentBuf->uiRflBufBytes &&
		  m_pCurrentBuf->uiRflFileOffset + m_pCurrentBuf->uiRflBufBytes ==
						m_uiFileEOF))
	{
		if (m_bKeepRflFiles)
		{
			if (!m_pRestore)
			{

				// Only doing recovery after a failure, see if we are at the
				// last file already.

				if (m_pCurrentBuf->uiCurrFileNum == m_uiLastRecoverFileNum)
				{
					rc = RC_SET( FERR_END);
					goto Exit;
				}
				else if( (m_pCurrentBuf->uiCurrFileNum + 1 ) ==
								m_uiLastRecoverFileNum &&
					!(FLMUINT)FB2UD( &m_pFile->ucLastCommittedLogHdr[ 
						LOG_RFL_LAST_TRANS_OFFSET]))
				{

					// We are going to try to open the last file. Since the
					// log header shows a current offset of 0, the file may
					// have been created but nothing was logged to it. We don't
					// want to try to open it here because it may not have been
					// initialized fully at the time of the server crash.

					m_pCurrentBuf->uiCurrFileNum = m_uiLastRecoverFileNum;
					rc = RC_SET( FERR_END);
					goto Exit;
				}

				// Open the next file in the sequence.

				if (RC_BAD( rc = openFile( m_pCurrentBuf->uiCurrFileNum + 1,
							  m_ucNextSerialNum)))
				{
					if (rc == FERR_IO_PATH_NOT_FOUND || rc == FERR_IO_INVALID_PATH)
					{
						rc = RC_SET( FERR_END);
					}

					goto Exit;
				}

				// If this is the last RFL file, the EOF is contained in the
				// log header. Otherwise, it will be in the RFL file's header,
				// and openFile will already have retrieved it.

				if (m_pCurrentBuf->uiCurrFileNum == m_uiLastRecoverFileNum)
				{
					m_uiFileEOF = (FLMUINT) FB2UD( &m_pFile->ucLastCommittedLogHdr[
														LOG_RFL_LAST_TRANS_OFFSET]);

					// Could be zero if RFL file wasn't created yet.

					if (!m_uiFileEOF)
					{
						m_uiFileEOF = 512;
					}
				}

				// By this point, the EOF better be greater than or equal to
				// 512.

				flmAssert( m_uiFileEOF >= 512);
			}
			else
			{
				if (RC_BAD( rc = m_pRestore->close()))
				{
					goto Exit;
				}

				// Ask the recovery object to open the file.

				if (RC_BAD( rc = m_pRestore->openRflFile(
								  m_pCurrentBuf->uiCurrFileNum + 1)))
				{
					if (rc == FERR_IO_PATH_NOT_FOUND)
					{
						rc = RC_SET( FERR_END);
					}

					goto Exit;
				}

				// Get the first 512 bytes from the file and verify the
				// header.

				if (RC_BAD( rc = m_pRestore->read( 512, ucHdr, &uiBytesRead)))
				{
					goto Exit;
				}

				if (uiBytesRead < 512)
				{
					rc = RC_SET_AND_ASSERT( FERR_NOT_RFL);
					goto Exit;
				}

				if (RC_BAD( rc = verifyHeader( ucHdr,
							  m_pCurrentBuf->uiCurrFileNum + 1, m_ucNextSerialNum)))
				{
					goto Exit;
				}

				// We may not know the actual EOF of files during restore
				// operations. m_uiFileEOF could be zero here.

				m_uiFileEOF = (FLMUINT) FB2UD( &ucHdr[RFL_EOF_POS]);

				// File EOF may be zero or >= 512 at this point.

				flmAssert( !m_uiFileEOF || m_uiFileEOF >= 512);

				// Need to increment current file number.

				m_pCurrentBuf->uiCurrFileNum++;
			}

			m_pCurrentBuf->uiRflFileOffset = 512;
			m_uiRflReadOffset = 0;
			m_pCurrentBuf->uiRflBufBytes = 0;

			// Get the next packet from the new file.

			if (RC_BAD( rc = readPacket( RFL_PACKET_OVERHEAD)))
			{
				if (m_uiFileEOF == 512 && m_bKeepRflFiles)
				{

					// File was empty, try to go to the next file.

					bForceNextFile = TRUE;
					goto Get_Next_File;
				}

				goto Exit;
			}
		}
		else
		{

			// This is the case where we are not keeping the RFL files. So,
			// there is no next file to go to. If we get to this point, we
			// had better not be doing a restore.

			flmAssert( m_pRestore == NULL && !bForceNextFile);
			rc = RC_SET( FERR_END);
			goto Exit;
		}
	}

	// Make sure we at least have a packet header in the buffer.

	if (RC_BAD( rc = readPacket( RFL_PACKET_OVERHEAD)))
	{
		goto Exit;
	}

	// Verify the packet address.

	m_uiPacketAddress = m_pCurrentBuf->uiRflFileOffset + m_uiRflReadOffset;
	pucPacket = &(m_pCurrentBuf->pIOBuffer->getBufferPtr()[m_uiRflReadOffset]);
	if ((FLMUINT) FB2UD( &pucPacket[RFL_PACKET_ADDRESS_OFFSET]) != m_uiPacketAddress)
	{
		rc = RC_SET( FERR_BAD_RFL_PACKET);
		goto Exit;
	}

	// Get packet type, time flag, and packet body length

	*puiPacketTypeRV = uiPacketType = RFL_GET_PACKET_TYPE( 
														pucPacket[RFL_PACKET_TYPE_OFFSET]);

	if (pbLoggedTimes)
	{
		*pbLoggedTimes = (pucPacket[RFL_PACKET_TYPE_OFFSET] & RFL_TIME_LOGGED_FLAG) 
											? TRUE 
											: FALSE;
	}

	*puiPacketBodyLenRV = (FLMUINT) FB2UW( 
									&pucPacket[RFL_PACKET_BODY_LENGTH_OFFSET]);

	// Adjust the packet body length for encryption if necessary. NOTE:
	// This adjusted length is NOT returned to the caller. The actual body
	// length is what will be returned.

	uiEncryptPacketBodyLen = getEncryptPacketBodyLen( uiPacketType,
																	 *puiPacketBodyLenRV);

	// Make sure we have the entire packet in the buffer.

	if (RC_BAD( rc = readPacket( uiEncryptPacketBodyLen + RFL_PACKET_OVERHEAD)))
	{
		goto Exit;
	}

	pucPacket = &(m_pCurrentBuf->pIOBuffer->getBufferPtr()[m_uiRflReadOffset]);

	// At this point, we are guaranteed to have the entire packet in the
	// buffer.

	*ppucPacketBodyRV = pucPacketBody = &pucPacket[RFL_PACKET_OVERHEAD];

	// Validate the packet checksum

	if (RflCalcChecksum( pucPacket, uiEncryptPacketBodyLen) != 
								pucPacket[RFL_PACKET_CHECKSUM_OFFSET])
	{
		rc = RC_SET( FERR_BAD_RFL_PACKET);
		goto Exit;
	}

	if (uiPacketType == RFL_TRNS_BEGIN_PACKET ||
		 uiPacketType == RFL_TRNS_BEGIN_EX_PACKET ||
		 uiPacketType == RFL_UPGRADE_PACKET ||
		 uiPacketType == RFL_REDUCE_PACKET ||
		 uiPacketType == RFL_WRAP_KEY_PACKET ||
		 uiPacketType == RFL_ENABLE_ENCRYPTION_PACKET)
	{

		// Current transaction ID better be zero, otherwise, we have two or
		// more begin packets in a row.

		if (m_uiCurrTransID)
		{
			rc = RC_SET( FERR_BAD_RFL_PACKET);
			goto Exit;
		}

		m_uiCurrTransID = (FLMUINT) FB2UD( pucPacketBody);
		pucPacketBody += 4;

		// Make sure the transaction numbers are ascending

		if (m_uiCurrTransID <= m_uiLastTransID)
		{
			rc = RC_SET( FERR_BAD_RFL_PACKET);
			goto Exit;
		}

		if (uiPacketType == RFL_TRNS_BEGIN_EX_PACKET)
		{
			FLMUINT	uiLastLoggedCommitTransID;

			// Skip past seconds

			pucPacketBody += 4;

			uiLastLoggedCommitTransID = (FLMUINT) FB2UD( pucPacketBody);
			pucPacketBody += 4;

			if (m_pFile->FileHdr.uiVersionNum >= FLM_FILE_FORMAT_VER_4_31 &&
				 m_uiLastLoggedCommitTransID != uiLastLoggedCommitTransID)
			{
				rc = RC_SET( FERR_RFL_TRANS_GAP);
				goto Exit;
			}
		}
	}
	else
	{

		// If transaction ID is not zero, we are not inside a transaction,
		// and it is likely that we have a corrupt packet.

		if (!m_uiCurrTransID)
		{
			rc = RC_SET( FERR_BAD_RFL_PACKET);
			goto Exit;
		}

		// Decrypt the packet if it is a packet type that was encrypted.

		if (uiPacketType == RFL_TRNS_COMMIT_PACKET ||
			 uiPacketType == RFL_TRNS_ABORT_PACKET)
		{
			if ((FLMUINT) FB2UD( pucPacketBody) != m_uiCurrTransID)
			{
				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}
		}
	}

	// Set read offset to beginning of next packet.

	m_uiRflReadOffset += (RFL_PACKET_OVERHEAD + uiEncryptPacketBodyLen);
	
Exit:

	return (rc);
}

/********************************************************************
Desc:		Get a record from the packets in the roll-forward log.
			This expects a series of RFL_DATA_RECORD_PACKETs.
*********************************************************************/
RCODE F_Rfl::getRecord(
	FDB *			pDb,
	FLMUINT		uiPacketType,
	FLMBYTE *	pucPacketBody,
	FLMUINT		uiPacketBodyLen,
	FlmRecord *	pRecord)
{
	RCODE			rc = FERR_OK;
	FLMUINT		uiTagNum;
	FLMUINT		uiDataType;
	FLMUINT		uiLevel;
	FLMUINT		uiDataLen;
	FLMBYTE *	pucFieldData = NULL;
	void *		pvField;
	FLMBOOL		bEncrypted = FALSE;
	FLMUINT		uiEncId = 0;
	FLMUINT		uiEncDataLen = 0;
	F_Pool		pool;

	pool.poolInit( 512);

	// Go into a loop processing packets until we have retrieved all of
	// the fields of the record. At that point, we had better be at the end
	// of the record.

	for (;;)
	{

		// If we don't currently have a packet, get one Packet type had
		// better be RFL_DATA_RECORD_PACKET.

		if (!uiPacketBodyLen)
		{
			if (RC_BAD( rc = getPacket( FALSE, &uiPacketType, &pucPacketBody,
						  &uiPacketBodyLen, NULL)))
			{
				goto Exit;
			}

			if (uiPacketType != RFL_DATA_RECORD_PACKET &&
				 uiPacketType != RFL_ENC_DATA_RECORD_PACKET &&
				 uiPacketType != RFL_DATA_RECORD_PACKET_VER_3)
			{
				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}
		}

		// Packet body length better be at least two or we have an
		// incomplete packet - we need to at least be able to get the tag
		// number at this point.

		if (uiPacketBodyLen < 2)
		{
			rc = RC_SET( FERR_BAD_RFL_PACKET);
			goto Exit;
		}
		else if (uiPacketBodyLen == 2)
		{

			// If the packet body length is only two, we had better be at
			// the end of the record with a tag number of zero. Otherwise, we
			// have an incomplete packet.

			if ((uiTagNum = (FLMUINT) FB2UW( pucPacketBody)) != 0)
			{
				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}
			break;
		}
		else if (uiPacketType == RFL_DATA_RECORD_PACKET)
		{
			flmAssert( m_pFile->FileHdr.uiVersionNum <= FLM_FILE_FORMAT_VER_4_60);
			if (uiPacketBodyLen < 6)
			{

				// If we have a packet body length less than six (for
				// RFL_DATA_RECORD_PACKETs), we have an incomplete field
				// header.

				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}
		}
		else if (uiPacketType == RFL_ENC_DATA_RECORD_PACKET)
		{

			// This type of packet is only valid with versions of flaim >=
			// 4.60

			flmAssert( m_pFile->FileHdr.uiVersionNum == FLM_FILE_FORMAT_VER_4_60);

			if (uiPacketBodyLen < 7)
			{

				// If we have a packet body length less than seven we have an
				// incomplete field header.

				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}
		}
		else
		{
			flmAssert( uiPacketType == RFL_DATA_RECORD_PACKET_VER_3);
			flmAssert( m_pFile->FileHdr.uiVersionNum >= FLM_FILE_FORMAT_VER_4_61);
			if (uiPacketBodyLen < 9)
			{

				// If we have a packet body length less than nine we have an
				// incomplete field header.

				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}
		}

		// At this point, we have a packet body length that is greater than
		// or equal to seven (or six), meaning we could not possibly be on
		// the last field of the record. Hence, a zero tag number is invalid
		// here.

		if ((uiTagNum = (FLMUINT) FB2UW( pucPacketBody)) == 0)
		{
			rc = RC_SET( FERR_BAD_RFL_PACKET);
			goto Exit;
		}

		pucPacketBody += 2;
		uiDataType = *pucPacketBody++;
		uiLevel = *pucPacketBody++;
		if (uiPacketType != RFL_DATA_RECORD_PACKET_VER_3)
		{
			uiDataLen = (FLMUINT) FB2UW( pucPacketBody);
			pucPacketBody += 2;
			uiPacketBodyLen -= 6;
		}
		else
		{
			uiDataLen = (FLMUINT) FB2UD( pucPacketBody);
			pucPacketBody += 4;
			uiPacketBodyLen -= 8;
		}

		// If the database version supports encryption, we need to check
		// for it.

		if (uiPacketType == RFL_ENC_DATA_RECORD_PACKET)
		{
			bEncrypted = (FLMBOOL) * pucPacketBody++;
			--uiPacketBodyLen;

			if (bEncrypted)
			{
				if (uiPacketBodyLen < 4)
				{
					flmAssert( 0);
					rc = RC_SET( FERR_BAD_RFL_PACKET);
					goto Exit;
				}

				// Extract the encryption ID and the encrypted length.

				uiEncId = FB2UW( pucPacketBody);
				pucPacketBody += 2;

				uiEncDataLen = FB2UW( pucPacketBody);
				pucPacketBody += 2;

				uiPacketBodyLen -= 4;
			}
		}
		else if (uiPacketType == RFL_DATA_RECORD_PACKET_VER_3)
		{
			bEncrypted = (FLMBOOL) * pucPacketBody++;
			--uiPacketBodyLen;

			if (bEncrypted)
			{
				if (uiPacketBodyLen < 6)
				{
					flmAssert( 0);
					rc = RC_SET( FERR_BAD_RFL_PACKET);
					goto Exit;
				}

				// Extract the encryption ID and the encrypted length.

				uiEncId = FB2UW( pucPacketBody);
				pucPacketBody += 2;

				uiEncDataLen = FB2UD( pucPacketBody);
				pucPacketBody += 4;

				uiPacketBodyLen -= 6;
			}
		}

		// Create a new field.

		if (RC_BAD( rc = pRecord->insertLast( uiLevel, uiTagNum, uiDataType,
					  &pvField)))
		{
			goto Exit;
		}

		if (!bEncrypted && uiDataLen)
		{
			if (RC_BAD( rc = pRecord->allocStorageSpace( pvField, uiDataType,
						  uiDataLen, 0, 0, 0, &pucFieldData, NULL)))
			{
				goto Exit;
			}

			while (uiDataLen)
			{
				if (uiDataLen > uiPacketBodyLen)
				{
					f_memcpy( pucFieldData, pucPacketBody, uiPacketBodyLen);
					pucFieldData += uiPacketBodyLen;
					pucPacketBody += uiPacketBodyLen;
					uiDataLen -= uiPacketBodyLen;

					uiPacketBodyLen = 0;

					// Get the next packet. Packet type had better be
					// RFL_DATA_RECORD_PACKET.

					if (RC_BAD( rc = getPacket( FALSE, &uiPacketType, &pucPacketBody,
								  &uiPacketBodyLen, NULL)))
					{
						goto Exit;
					}

					if (uiPacketType != RFL_DATA_RECORD_PACKET &&
						 uiPacketType != RFL_ENC_DATA_RECORD_PACKET &&
						 uiPacketType != RFL_DATA_RECORD_PACKET_VER_3)
					{
						rc = RC_SET( FERR_BAD_RFL_PACKET);
						goto Exit;
					}
				}
				else
				{
					f_memcpy( pucFieldData, pucPacketBody, uiDataLen);
					pucFieldData += uiDataLen;
					uiPacketBodyLen -= uiDataLen;
					pucPacketBody += uiDataLen;
					uiDataLen = 0;
				}
			}

			pucFieldData = NULL;
		}
		else if (bEncrypted)
		{
			FLMBYTE *		pucEncFieldData;

			if (uiEncDataLen)
			{
				if (RC_BAD( rc = pRecord->allocStorageSpace( pvField, uiDataType,
							  uiDataLen, uiEncDataLen, uiEncId,
							  FLD_HAVE_ENCRYPTED_DATA, &pucFieldData, &pucEncFieldData
							  )))
				{
					goto Exit;
				}
			}

			while (uiEncDataLen)
			{
				if (uiEncDataLen > uiPacketBodyLen)
				{
					f_memcpy( pucEncFieldData, pucPacketBody, uiPacketBodyLen);
					pucEncFieldData += uiPacketBodyLen;
					pucPacketBody += uiPacketBodyLen;
					uiEncDataLen -= uiPacketBodyLen;

					uiPacketBodyLen = 0;

					// Get the next packet. Packet type had better be
					// RFL_ENC_DATA_RECORD_PACKET.

					if (RC_BAD( rc = getPacket( FALSE, &uiPacketType, &pucPacketBody,
								  &uiPacketBodyLen, NULL)))
					{
						goto Exit;
					}

					if (uiPacketType != RFL_ENC_DATA_RECORD_PACKET &&
						 uiPacketType != RFL_DATA_RECORD_PACKET_VER_3)
					{
						rc = RC_SET( FERR_BAD_RFL_PACKET);
						goto Exit;
					}
				}
				else
				{
					f_memcpy( pucEncFieldData, pucPacketBody, uiEncDataLen);
					pucEncFieldData += uiEncDataLen;
					uiPacketBodyLen -= uiEncDataLen;
					pucPacketBody += uiEncDataLen;
					uiEncDataLen = 0;
				}
			}

			pucEncFieldData = NULL;

			if (!m_pFile->bInLimitedMode)
			{
				if (RC_BAD( rc = flmDecryptField( pDb->pDict, pRecord, pvField,
							  uiEncId, &pool)))
				{
					goto Exit;
				}
			}
		}
	}

Exit:

	return (rc);
}

/********************************************************************
Desc:		Modify a record using RFL_DATA_RECORD_PACKETs or
			RFL_CHANGE_FIELD_PACKETs.
*********************************************************************/
RCODE F_Rfl::modifyRecord(
	HFDB			hDb,
	FlmRecord *	pRecord)
{
	RCODE			rc = FERR_OK;
	FLMUINT		uiPacketType;
	FLMBYTE *	pucPacketBody;
	FLMUINT		uiPacketBodyLen;

	FLMUINT		uiChangeType;
	FLMUINT		uiPosition;
	FLMUINT		uiTagNum = 0;
	FLMUINT		uiDataType = 0;
	FLMUINT		uiLevel = 0;
	FLMUINT		uiDataLen = 0;
	FLMBYTE *	pucData;
	FLMBYTE *	pucEncData;
	FDB *			pDb = (FDB *) hDb;
	FLMBOOL		bEncrypted = FALSE;
	FLMUINT		uiEncDataLen;
	FLMUINT		uiEncId;
	FLMUINT		uiFlags;
	FlmField *	pField;
	FLMUINT		uiCurPos = 1;
	void *		pvField;

	// Get the first packet and see what it is. If it is an
	// RFL_DATA_RECORD_PACKET, just call Rfl3GetRecord to get the entire
	// new record.

	if (RC_BAD( rc = getPacket( FALSE, &uiPacketType, &pucPacketBody,
				  &uiPacketBodyLen, NULL)))
	{
		goto Exit;
	}

	if (uiPacketType == RFL_DATA_RECORD_PACKET ||
		 uiPacketType == RFL_ENC_DATA_RECORD_PACKET ||
		 uiPacketType == RFL_DATA_RECORD_PACKET_VER_3)
	{
		pRecord->clear();
		rc = getRecord( pDb, uiPacketType, pucPacketBody, uiPacketBodyLen, pRecord);
		goto Exit;
	}
	else if (uiPacketType != RFL_CHANGE_FIELDS_PACKET)
	{
		rc = RC_SET( FERR_BAD_RFL_PACKET);
		goto Exit;
	}

	// Go into a loop processing packets until we have processed all of
	// the changed fields for the record.

	pField = pRecord->getFieldPointer( pRecord->root());

	flmAssert( pField);

	for (;;)
	{
		uiEncDataLen = 0;
		uiEncId = 0;

		// If we don't currently have a packet, get one Packet type had
		// better be RFL_CHANGE_FIELDS_PACKET.

		if (!uiPacketBodyLen)
		{
			if (RC_BAD( rc = getPacket( FALSE, &uiPacketType, &pucPacketBody,
						  &uiPacketBodyLen, NULL)))
			{
				goto Exit;
			}

			if (uiPacketType != RFL_CHANGE_FIELDS_PACKET)
			{
				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}
		}

		// Packet body length better be at least three or we have an
		// incomplete packet - we need to at least be able to get the type
		// of change and the absolute position of the change.

		if (uiPacketBodyLen < 3)
		{
			rc = RC_SET( FERR_BAD_RFL_PACKET);
			goto Exit;
		}

		// Get the change type and the absolute position where the change
		// is to be put. A position of zero is illegal.

		uiChangeType = *pucPacketBody++;
		uiPosition = (FLMUINT) FB2UW( pucPacketBody);
		pucPacketBody += 2;
		uiPacketBodyLen -= 3;

		if (uiChangeType == RFL_END_FIELD_CHANGES)
		{

			// If we are not at the end of the packet, it must be a bad
			// packet. Also, uiPosition should be a zero for this packet.

			if (uiPacketBodyLen || uiPosition)
			{
				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}
			break;
		}

		// If not RFL_END_FIELD_CHANGES, a position of zero is illegal.

		if (!uiPosition)
		{
			rc = RC_SET( FERR_BAD_RFL_PACKET);
			goto Exit;
		}

		switch (uiChangeType)
		{
			case RFL_INSERT_FIELD:
			{
				flmAssert( m_pFile->FileHdr.uiVersionNum <= FLM_FILE_FORMAT_VER_4_60);
				bEncrypted = FALSE;
				if (uiPacketBodyLen < 6)
				{
					rc = RC_SET( FERR_BAD_RFL_PACKET);
					goto Exit;
				}

				uiTagNum = (FLMUINT) FB2UW( pucPacketBody);
				pucPacketBody += 2;
				uiDataType = *pucPacketBody++;
				uiLevel = *pucPacketBody++;
				uiDataLen = (FLMUINT)FB2UW( pucPacketBody);
				pucPacketBody += 2;
				uiPacketBodyLen -= 6;
				break;
			}
			case RFL_INSERT_ENC_FIELD:
			{
				flmAssert( m_pFile->FileHdr.uiVersionNum == FLM_FILE_FORMAT_VER_4_60);
				bEncrypted = TRUE;
				if (uiPacketBodyLen < 10)
				{
					rc = RC_SET( FERR_BAD_RFL_PACKET);
					goto Exit;
				}

				uiTagNum = (FLMUINT) FB2UW( pucPacketBody);
				pucPacketBody += 2;
				uiDataType = *pucPacketBody++;
				uiLevel = *pucPacketBody++;
				uiDataLen = (FLMUINT)FB2UW( pucPacketBody);
				pucPacketBody += 2;

				uiEncId = FB2UW( pucPacketBody);
				pucPacketBody += 2;

				uiEncDataLen = FB2UW( pucPacketBody);
				pucPacketBody += 2;

				uiPacketBodyLen -= 10;
				break;
			}
			case RFL_INSERT_LARGE_FIELD:
			{
				flmAssert( m_pFile->FileHdr.uiVersionNum >= FLM_FILE_FORMAT_VER_4_61);
				bEncrypted = FALSE;
				if (uiPacketBodyLen < 8)
				{
					rc = RC_SET( FERR_BAD_RFL_PACKET);
					goto Exit;
				}
				uiTagNum = (FLMUINT) FB2UW( pucPacketBody);
				pucPacketBody += 2;
				uiDataType = *pucPacketBody++;
				uiLevel = *pucPacketBody++;
				uiDataLen = (FLMUINT)FB2UD( pucPacketBody);
				pucPacketBody += 4;
				uiPacketBodyLen -= 8;
				break;
			}
			case RFL_INSERT_ENC_LARGE_FIELD:
			{
				flmAssert( m_pFile->FileHdr.uiVersionNum >= FLM_FILE_FORMAT_VER_4_61);
				bEncrypted = TRUE;
				if (uiPacketBodyLen < 14)
				{
					rc = RC_SET( FERR_BAD_RFL_PACKET);
					goto Exit;
				}
				uiTagNum = (FLMUINT) FB2UW( pucPacketBody);
				pucPacketBody += 2;
				uiDataType = *pucPacketBody++;
				uiLevel = *pucPacketBody++;
				uiDataLen = (FLMUINT)FB2UD( pucPacketBody);
				pucPacketBody += 4;

				uiEncId = FB2UW( pucPacketBody);
				pucPacketBody += 2;

				uiEncDataLen = FB2UW( pucPacketBody);
				pucPacketBody += 4;

				uiPacketBodyLen -= 14;
				break;
			}

			case RFL_MODIFY_FIELD:
			{
				flmAssert( m_pFile->FileHdr.uiVersionNum <= FLM_FILE_FORMAT_VER_4_60);
				bEncrypted = FALSE;
				if (uiPacketBodyLen < 3 || *pucPacketBody != RFL_REPLACE_BYTES)
				{
					rc = RC_SET( FERR_BAD_RFL_PACKET);
					goto Exit;
				}

				pucPacketBody++;
				uiDataLen = (FLMUINT) FB2UW( pucPacketBody);
				pucPacketBody += 2;
				uiPacketBodyLen -= 3;
				break;
			}

			case RFL_MODIFY_ENC_FIELD:
			{
				flmAssert( m_pFile->FileHdr.uiVersionNum == FLM_FILE_FORMAT_VER_4_60);
				bEncrypted = TRUE;
				if (uiPacketBodyLen < 7 || *pucPacketBody != RFL_REPLACE_BYTES)
				{
					rc = RC_SET( FERR_BAD_RFL_PACKET);
					goto Exit;
				}

				pucPacketBody++;
				uiDataLen = (FLMUINT) FB2UW( pucPacketBody);
				pucPacketBody += 2;

				uiEncId = FB2UW( pucPacketBody);
				pucPacketBody += 2;

				uiEncDataLen = FB2UW( pucPacketBody);
				pucPacketBody += 2;

				uiPacketBodyLen -= 7;
				break;
			}
			case RFL_MODIFY_LARGE_FIELD:
			{
				flmAssert( m_pFile->FileHdr.uiVersionNum >= FLM_FILE_FORMAT_VER_4_61);
				bEncrypted = FALSE;
				if (uiPacketBodyLen < 5 || *pucPacketBody != RFL_REPLACE_BYTES)
				{
					rc = RC_SET( FERR_BAD_RFL_PACKET);
					goto Exit;
				}
				pucPacketBody++;
				uiDataLen = (FLMUINT)FB2UD( pucPacketBody);
				pucPacketBody += 4;
				uiPacketBodyLen -= 5;
				break;
			}
			case RFL_MODIFY_ENC_LARGE_FIELD:
			{
				flmAssert( m_pFile->FileHdr.uiVersionNum >= FLM_FILE_FORMAT_VER_4_61);
				bEncrypted = TRUE;
				if (uiPacketBodyLen < 11 || *pucPacketBody != RFL_REPLACE_BYTES)
				{
					rc = RC_SET( FERR_BAD_RFL_PACKET);
					goto Exit;
				}

				pucPacketBody++;
				uiDataLen = (FLMUINT)FB2UD( pucPacketBody);
				pucPacketBody += 4;

				uiEncId = FB2UW( pucPacketBody);
				pucPacketBody += 2;

				uiEncDataLen = FB2UD( pucPacketBody);
				pucPacketBody += 4;

				uiPacketBodyLen -= 11;
				break;
			}

			case RFL_DELETE_FIELD:
			{
				break;
			}

			default:
			{

				// Bad change type in packet.

				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}
		}

		// Now position to the target field.

		switch (uiChangeType)
		{
			case RFL_DELETE_FIELD:
			case RFL_MODIFY_FIELD:
			case RFL_MODIFY_ENC_FIELD:
			case RFL_MODIFY_LARGE_FIELD:
			case RFL_MODIFY_ENC_LARGE_FIELD:
			{
				while (uiCurPos != uiPosition)
				{
					if (uiPosition < uiCurPos)
					{
						flmAssert( pField->uiPrev);
						pField = pRecord->prevField( pField);
						--uiCurPos;
					}
					else
					{
						flmAssert( pField->uiNext);
						pField = pRecord->nextField( pField);
						uiCurPos++;
					}
				}

				if (uiChangeType != RFL_DELETE_FIELD)
				{

					// Get the data type ... not supplied in the modify field
					// packet.

					uiDataType = pRecord->getFieldDataType( pField);
					uiTagNum = pField->ui16FieldID;
				}
				break;
			}

			case RFL_INSERT_FIELD:
			case RFL_INSERT_ENC_FIELD:
			case RFL_INSERT_LARGE_FIELD:
			case RFL_INSERT_ENC_LARGE_FIELD:
			{
				FlmField *	pNewField;

				// On insert, we may be trying to position to a field that
				// does not exist yet. Therefore we need to position to the
				// field prior to the field position we want to insert.

				flmAssert( uiPosition > 1);	// cannot insert at the root
														///position.

				while (uiCurPos != uiPosition - 1)
				{
					if (uiPosition - 1 < uiCurPos)
					{
						flmAssert( pField->uiPrev);
						pField = pRecord->prevField( pField);
						--uiCurPos;
					}
					else
					{
						flmAssert( pField->uiNext);
						pField = pRecord->nextField( pField);
						uiCurPos++;
					}
				}

				// Insert the new field at the specified position and get
				// back a new field to use later.

				if (RC_BAD( rc = pRecord->createField( pField, &pNewField)))
				{
					goto Exit;
				}

				if (RC_BAD( rc = pRecord->setFieldLevel( pNewField, uiLevel)))
				{
					goto Exit;
				}

				pField = pNewField;
				pField->ui16FieldID = (FLMUINT16) uiTagNum;
				uiCurPos++;							// Bump the position as we have
														///just added a new field and

				// we are positioned on it.

				break;
			}
		}

		if (uiChangeType == RFL_DELETE_FIELD)
		{

			// Remove the specified field or subtree

			pvField = (void *) ((FLMUINT) (pField->uiPrev));
			--uiCurPos;
			if (!pvField)
			{
				pvField = pRecord->root();
				uiCurPos = 1;
			}

			// For versions 4.60 and greater, the interpretation for
			// RFL_DELETE_FIELD is to delete the entire sub-tree. Prior to
			// that, it is to delete only a single field.

			if (m_pFile->FileHdr.uiVersionNum >= FLM_FILE_FORMAT_VER_4_60)
			{
				if (RC_BAD( rc = pRecord->remove( pField)))
				{
					goto Exit;
				}
			}
			else
			{

				// Passing in the same pointer to removeFields will
				// effectively delete just pField.

				if (RC_BAD( rc = pRecord->removeFields( pField, pField)))
				{
					goto Exit;
				}
			}

			// We need to reset our pField.

			pField = pRecord->getFieldPointer( pvField);

			continue;	// Next field...
		}

		// Both insert & modify need to have space allocated.

		if (bEncrypted)
		{
			uiFlags = FLD_HAVE_ENCRYPTED_DATA;
		}
		else
		{
			uiFlags = 0;
		}

		flmAssert( pField);

		// Allocate space for the data. We call this even if uiDataLen is
		// zero so that the appropriate data type will be set in the node as
		// well. ;
		// Before we allocate storage space, save the field offset. The
		// field buffer may get reallocated, resulting in a nwe address for
		// pField.
		pvField = pRecord->getFieldVoid( pField);
		if (RC_BAD( rc = pRecord->getNewDataPtr( pField, uiDataType, uiDataLen,
					  uiEncDataLen, uiEncId, uiFlags, &pucData, &pucEncData)))
		{
			goto Exit;
		}

		pField = pRecord->getFieldPointer( pvField);

		// Get the data for insert or modify, if any

		if (bEncrypted)
		{
			while (uiEncDataLen)
			{
				if (uiEncDataLen > uiPacketBodyLen)
				{
					f_memcpy( pucEncData, pucPacketBody, uiPacketBodyLen);
					pucEncData += uiPacketBodyLen;
					uiEncDataLen -= uiPacketBodyLen;
					uiPacketBodyLen = 0;

					// Get the next packet. Packet type had better be
					// RFL_CHANGE_FIELDS_PACKET.

					if (RC_BAD( rc = getPacket( FALSE, &uiPacketType, &pucPacketBody,
								  &uiPacketBodyLen, NULL)))
					{
						goto Exit;
					}

					if (uiPacketType != RFL_CHANGE_FIELDS_PACKET)
					{
						rc = RC_SET( FERR_BAD_RFL_PACKET);
						goto Exit;
					}
				}
				else
				{
					f_memcpy( pucEncData, pucPacketBody, uiEncDataLen);
					pucEncData += uiEncDataLen;
					uiPacketBodyLen -= uiEncDataLen;
					pucPacketBody += uiEncDataLen;
					uiEncDataLen = 0;
				}
			}
		}
		else				// Not encrypted
		{
			while (uiDataLen)
			{
				if (uiDataLen > uiPacketBodyLen)
				{
					f_memcpy( pucData, pucPacketBody, uiPacketBodyLen);
					pucData += uiPacketBodyLen;
					uiDataLen -= uiPacketBodyLen;
					uiPacketBodyLen = 0;

					// Get the next packet. Packet type had better be
					// RFL_CHANGE_FIELDS_PACKET.

					if (RC_BAD( rc = getPacket( FALSE, &uiPacketType, &pucPacketBody,
								  &uiPacketBodyLen, NULL)))
					{
						goto Exit;
					}

					if (uiPacketType != RFL_CHANGE_FIELDS_PACKET)
					{
						rc = RC_SET( FERR_BAD_RFL_PACKET);
						goto Exit;
					}
				}
				else
				{
					f_memcpy( pucData, pucPacketBody, uiDataLen);
					pucData += uiDataLen;
					uiPacketBodyLen -= uiDataLen;
					pucPacketBody += uiDataLen;
					uiDataLen = 0;
				}
			}
		}

		// If this field is involved in an index, and it is encrypted, we
		// need to make sure we decrypt it too. If it is not encrypted, we
		// don't care if it involved in an index.

		if (bEncrypted && !(pDb->pFile->bInLimitedMode))
		{
			IFD *	pIfd;

			if (RC_BAD( rc = fdictGetField( pDb->pDict, uiTagNum, NULL, &pIfd, NULL)))
			{
				goto Exit;
			}

			if (pIfd)
			{
				if (RC_BAD( rc = flmDecryptField( pDb->pDict, pRecord,
						pRecord->getFieldVoid( pField), uiEncId, &pDb->TempPool)))
				{
					goto Exit;
				}
			}
		}
	}

Exit:

	return (rc);
}

/********************************************************************
Desc:		Read the next operation from the roll-forward log.
*********************************************************************/
RCODE F_Rfl::readOp(
	FDB *				pDb,
	FLMBOOL			bForceNextFile,
	RFL_OP_INFO *	pOpInfo,
	FlmRecord *		pRecord)
{
	RCODE				rc = FERR_OK;
	FLMBYTE *		pucPacketBody;
	FLMUINT			uiPacketBodyLen;
	FLMUINT			uiExpectedBodyLen;
	FLMBOOL			bLoggedTimes;
	
	f_memset( pOpInfo, 0, sizeof( RFL_OP_INFO));

	// Get the next packet.

	if (RC_BAD( rc = getPacket( bForceNextFile, &pOpInfo->uiPacketType,
				  &pucPacketBody, &uiPacketBodyLen, &bLoggedTimes)))
	{
		goto Exit;
	}

	// Must be one of our packet types that represents an operation.

	switch (pOpInfo->uiPacketType)
	{
		case RFL_TRNS_BEGIN_PACKET:
		{
			uiExpectedBodyLen = 8;
			if (bLoggedTimes)
			{
				uiExpectedBodyLen += 4;
			}

			if (uiExpectedBodyLen != uiPacketBodyLen)
			{
				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}

			pOpInfo->uiTransId = (FLMUINT) FB2UD( pucPacketBody);
			pucPacketBody += 4;
			pOpInfo->uiStartTime = (FLMUINT) FB2UD( pucPacketBody);
			pucPacketBody += 4;
			break;
		}

		case RFL_TRNS_BEGIN_EX_PACKET:
		{
			uiExpectedBodyLen = 12;
			if (uiExpectedBodyLen != uiPacketBodyLen)
			{
				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}

			pOpInfo->uiTransId = (FLMUINT) FB2UD( pucPacketBody);
			pucPacketBody += 4;
			pOpInfo->uiStartTime = (FLMUINT) FB2UD( pucPacketBody);
			pucPacketBody += 4;
			pOpInfo->uiLastLoggedCommitTransId = (FLMUINT) FB2UD( pucPacketBody);
			pucPacketBody += 4;
			break;
		}

		case RFL_TRNS_COMMIT_PACKET:
		case RFL_TRNS_ABORT_PACKET:
		{
			uiExpectedBodyLen = 8;
			if (bLoggedTimes)
			{
				uiExpectedBodyLen += 8;
			}

			if (uiExpectedBodyLen != uiPacketBodyLen)
			{
				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}

			pOpInfo->uiTransId = (FLMUINT) FB2UD( pucPacketBody);
			pucPacketBody += 8;
			break;
		}

		case RFL_ADD_RECORD_PACKET:
		case RFL_MODIFY_RECORD_PACKET:
		case RFL_DELETE_RECORD_PACKET:
		case RFL_RESERVE_DRN_PACKET:
		{
			uiExpectedBodyLen = 10;
			if (bLoggedTimes)
			{
				uiExpectedBodyLen += 16;
			}

			if (uiExpectedBodyLen != uiPacketBodyLen)
			{
				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}

			if ((pOpInfo->uiTransId = 
				(FLMUINT) FB2UD( pucPacketBody)) != m_uiCurrTransID)
			{
				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}
			pucPacketBody += 4;
			
			pOpInfo->uiContainer = (FLMUINT) FB2UW( pucPacketBody);
			pucPacketBody += 2;
			
			pOpInfo->uiDrn = (FLMUINT) FB2UD( pucPacketBody);
			pucPacketBody += 4;
			
			if (pOpInfo->uiPacketType == RFL_ADD_RECORD_PACKET)
			{
				if (RC_BAD( rc = getRecord( pDb, 0, NULL, 0, pRecord)))
				{
					goto Exit;
				}
			}
			break;
		}

		case RFL_ADD_RECORD_PACKET_VER_2:
		case RFL_MODIFY_RECORD_PACKET_VER_2:
		case RFL_DELETE_RECORD_PACKET_VER_2:
		{
			uiExpectedBodyLen = 11;
			if (bLoggedTimes)
			{
				uiExpectedBodyLen += 16;
			}

			if (uiExpectedBodyLen != uiPacketBodyLen)
			{
				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}

			if ((pOpInfo->uiTransId = 
				(FLMUINT) FB2UD( pucPacketBody)) != m_uiCurrTransID)
			{
				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}
			pucPacketBody += 4;

			pOpInfo->uiContainer = (FLMUINT) FB2UW( pucPacketBody);
			pucPacketBody += 2;

			pOpInfo->uiDrn = (FLMUINT) FB2UD( pucPacketBody);
			pucPacketBody += 4;

			pOpInfo->uiFlags = *pucPacketBody;
			pucPacketBody++;

			// Translate the flags

			if (pOpInfo->uiFlags)
			{
				FLMUINT	uiTmp = 0;

				if (pOpInfo->uiFlags & RFL_UPDATE_BACKGROUND)
				{
					uiTmp |= FLM_DO_IN_BACKGROUND;
				}

				if (pOpInfo->uiFlags & RFL_UPDATE_SUSPENDED)
				{
					uiTmp |= FLM_SUSPENDED;
				}

				pOpInfo->uiFlags = uiTmp;
			}

			if (pOpInfo->uiPacketType == RFL_ADD_RECORD_PACKET_VER_2)
			{
				if (RC_BAD( rc = getRecord( pDb, 0, NULL, 0, pRecord)))
				{
					goto Exit;
				}
			}
			break;
		}

		case RFL_INDEX_SET_PACKET:
		case RFL_INDEX_SET_PACKET_VER_2:
		{
			uiExpectedBodyLen = 
					(FLMUINT) ((pOpInfo->uiPacketType == RFL_INDEX_SET_PACKET_VER_2) 
																? (FLMUINT) 16 
																: (FLMUINT) 14);

			if (bLoggedTimes)
			{
				uiExpectedBodyLen += 16;
			}

			if (uiExpectedBodyLen != uiPacketBodyLen)
			{
				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}

			if ((pOpInfo->uiTransId = 
				(FLMUINT) FB2UD( pucPacketBody)) != m_uiCurrTransID)
			{
				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}
			pucPacketBody += 4;
			
			if (pOpInfo->uiPacketType == RFL_INDEX_SET_PACKET_VER_2)
			{
				pOpInfo->uiContainer = (FLMUINT) FB2UW( pucPacketBody);
				pucPacketBody += 2;
			}

			pOpInfo->uiIndex = (FLMUINT) FB2UW( pucPacketBody);
			pucPacketBody += 2;
			
			pOpInfo->uiDrn = (FLMUINT) FB2UD( pucPacketBody);
			pucPacketBody += 4;
			
			pOpInfo->uiEndDrn = (FLMUINT) FB2UD( pucPacketBody);
			pucPacketBody += 4;
			
			break;
		}

		case RFL_START_UNKNOWN_PACKET:
		{
			uiExpectedBodyLen = 4;
			if (uiExpectedBodyLen != uiPacketBodyLen)
			{
				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}

			if ((pOpInfo->uiTransId = 
				(FLMUINT) FB2UD( pucPacketBody)) != m_uiCurrTransID)
			{
				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}

			pucPacketBody += 4;
			break;
		}

		case RFL_REDUCE_PACKET:
		{
			uiExpectedBodyLen = 8;
			if (uiExpectedBodyLen != uiPacketBodyLen)
			{
				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}

			pOpInfo->uiTransId = (FLMUINT) FB2UD( pucPacketBody);
			pOpInfo->uiLastLoggedCommitTransId = pOpInfo->uiTransId;
			pucPacketBody += 4;

			pOpInfo->uiCount = (FLMUINT) FB2UD( pucPacketBody);
			pucPacketBody += 4;
			
			break;
		}

		case RFL_BLK_CHAIN_FREE_PACKET:
		{
			uiExpectedBodyLen = 16;

			if (bLoggedTimes)
			{
				uiExpectedBodyLen += 16;
			}

			if (uiExpectedBodyLen != uiPacketBodyLen)
			{
				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}

			if ((pOpInfo->uiTransId = 
				(FLMUINT) FB2UD( pucPacketBody)) != m_uiCurrTransID)
			{
				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}

			pucPacketBody += 4;

			// Tracker record ID

			pOpInfo->uiDrn = (FLMUINT) FB2UD( pucPacketBody);
			pucPacketBody += 4;

			// Count

			pOpInfo->uiCount = (FLMUINT) FB2UD( pucPacketBody);
			pucPacketBody += 4;

			// Ending block address

			pOpInfo->uiEndBlock = (FLMUINT) FB2UD( pucPacketBody);
			pucPacketBody += 4;

			break;
		}

		case RFL_INDEX_SUSPEND_PACKET:
		case RFL_INDEX_RESUME_PACKET:
		{
			uiExpectedBodyLen = 6;
			if (uiExpectedBodyLen != uiPacketBodyLen)
			{
				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}

			if ((pOpInfo->uiTransId = 
				(FLMUINT) FB2UD( pucPacketBody)) != m_uiCurrTransID)
			{
				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}

			pucPacketBody += 4;

			pOpInfo->uiIndex = (FLMUINT) FB2UW( pucPacketBody);
			pucPacketBody += 2;
			break;
		}

		case RFL_UPGRADE_PACKET:
		{
			FLMUINT	uiDBKeyLen;

			uiExpectedBodyLen = 12;
			if (uiExpectedBodyLen > uiPacketBodyLen)
			{
				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}

			pOpInfo->uiTransId = (FLMUINT) FB2UD( pucPacketBody);
			pucPacketBody += 4;
			uiPacketBodyLen -= 4;
			
			pOpInfo->uiOldVersion = (FLMUINT) FB2UD( pucPacketBody);
			pucPacketBody += 4;
			uiPacketBodyLen -= 4;
			
			pOpInfo->uiNewVersion = (FLMUINT) FB2UD( pucPacketBody);
			pucPacketBody += 4;
			uiPacketBodyLen -= 4;

			// Only look for the wrapping key if the new database version is
			// greater than 4.60 and there isn't already a key.

			if (pOpInfo->uiEndDrn >= FLM_FILE_FORMAT_VER_4_60 && 
				 !m_pFile->pDbWrappingKey)
			{
				if (uiPacketBodyLen < 2)
				{
					rc = RC_SET( FERR_BAD_RFL_PACKET);
					goto Exit;
				}

				uiDBKeyLen = FB2UW( pucPacketBody);
				pucPacketBody += 2;
				uiPacketBodyLen -= 2;
				
				if (uiDBKeyLen)
				{
					if (uiPacketBodyLen != uiDBKeyLen)
					{
						rc = RC_SET( FERR_BAD_RFL_PACKET);
						goto Exit;
					}

					if ((m_pFile->pDbWrappingKey = f_new F_CCS) == NULL)
					{
						rc = RC_SET( FERR_MEM);
						goto Exit;
					}

					if (RC_BAD( rc = m_pFile->pDbWrappingKey->init( TRUE,
								  FLM_NICI_AES)))
					{
						goto Exit;
					}

					if (RC_BAD( rc = m_pFile->pDbWrappingKey->setKeyFromStore(
								pucPacketBody, (FLMUINT32) uiDBKeyLen, NULL, NULL,
								FALSE)))
					{
						goto Exit;
					}

					pucPacketBody += uiDBKeyLen;
					uiPacketBodyLen -= uiDBKeyLen;
					flmAssert( !uiPacketBodyLen);
				}
			}
			break;
		}

		case RFL_CONFIG_SIZE_EVENT_PACKET:
		{
			uiExpectedBodyLen = 16;
			if (uiExpectedBodyLen != uiPacketBodyLen)
			{
				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}

			pOpInfo->uiTransId = (FLMUINT) FB2UD( pucPacketBody);
			pOpInfo->uiLastLoggedCommitTransId = pOpInfo->uiTransId;
			pucPacketBody += 4;

			pOpInfo->uiSizeThreshold = (FLMUINT) FB2UD( pucPacketBody);
			pucPacketBody += 4;
			
			pOpInfo->uiTimeInterval = (FLMUINT) FB2UD( pucPacketBody);
			pucPacketBody += 4;
			
			pOpInfo->uiSizeInterval = (FLMUINT) FB2UD( pucPacketBody);
			pucPacketBody += 4;
			
			break;
		}
		
		case RFL_WRAP_KEY_PACKET:
		case RFL_ENABLE_ENCRYPTION_PACKET:
		{
			FLMUINT					uiDBKeyLen;
			FLMBYTE *				pucUncommittedLogHdr = &m_pFile->ucUncommittedLogHdr[0];
			eRestoreActionType	eRestoreAction;

			uiExpectedBodyLen = 6;
			if (uiExpectedBodyLen >= uiPacketBodyLen)
			{
				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}

			pOpInfo->uiTransId = (FLMUINT) FB2UD( pucPacketBody);
			pOpInfo->uiLastLoggedCommitTransId = pOpInfo->uiTransId;
			pucPacketBody += 4;
			uiPacketBodyLen -= 4;

			if (uiPacketBodyLen < 2)
			{
				rc = RC_SET( FERR_BAD_RFL_PACKET);
				goto Exit;
			}

			uiDBKeyLen = FB2UW( pucPacketBody);
			pucPacketBody += 2;
			uiPacketBodyLen -= 2;

			if (m_pRestore)
			{
				if (RC_BAD( rc = m_pRestore->status( 
						  pOpInfo->uiPacketType == RFL_WRAP_KEY_PACKET 
								? RESTORE_WRAP_KEY 
								: RESTORE_ENABLE_ENCRYPTION,
						  pOpInfo->uiTransId, (void *) uiDBKeyLen, (void *) 0, 
						  (void *) 0, &eRestoreAction)))
				{
					goto Exit;
				}

				if (eRestoreAction == RESTORE_ACTION_STOP)
				{
					m_uiCurrTransID = 0;
					break;
				}
			}

			if (uiDBKeyLen)
			{
				if (uiPacketBodyLen != uiDBKeyLen)
				{
					rc = RC_SET( FERR_BAD_RFL_PACKET);
					goto Exit;
				}

				// We cannot directly set the key at this point as it may be
				// encrypted using a password, which we do not have here. We
				// will write the key out to the log header and trust the user
				// to know whether or not a password is needed to open the
				// database.

				if (RC_BAD( rc = flmBeginDbTrans( pDb, FLM_UPDATE_TRANS, FLM_NO_TIMEOUT, 0)))
				{
					goto Exit;
				}

				f_memcpy( &pucUncommittedLogHdr[ LOG_DATABASE_KEY], pucPacketBody,
							uiDBKeyLen);
							
				UW2FBA( uiDBKeyLen, &pucUncommittedLogHdr[ LOG_DATABASE_KEY_LEN]);

				if (RC_BAD( rc = flmCommitDbTrans( pDb, 0, TRUE)))
				{
					goto Exit;
				}

				pucPacketBody += uiDBKeyLen;
				uiPacketBodyLen -= uiDBKeyLen;
				flmAssert( !uiPacketBodyLen);
			}

			m_uiCurrTransID = 0;
			break;
		}

		default:
		{
			flmAssert( 0);
			rc = RC_SET( FERR_BAD_RFL_PACKET);
			break;
		}
	}

Exit:

	return (rc);
}

/********************************************************************
Desc:		Reads through unknown packets.
*********************************************************************/
RCODE F_Rfl::readUnknown(
	FLMUINT		uiLenToRead,
	FLMBYTE *	pucBuffer,
	FLMUINT *	puiBytesRead)
{
	RCODE			rc = FERR_OK;
	FLMUINT		uiPacketType;
	FLMUINT		uiBytesRead = 0;
	FLMUINT		uiBytesToCopy;

	// If we have read through all of the unknown packets, return
	// FERR_EOF_HIT.

	if (!m_bReadingUnknown)
	{
		rc = RC_SET( FERR_EOF_HIT);
		goto Exit;
	}

	// Process packets until we have satisfied the read request or until
	// we run out of unknown packets.

	while (uiLenToRead)
	{

		// Get a packet, if we don't have one.

		if (!m_uiUnknownPacketBodyLen)
		{
			if (RC_BAD( rc = getPacket( FALSE, &uiPacketType,
						  &m_pucUnknownPacketBody, &m_uiUnknownPacketBodyLen, NULL)))
			{
				m_bReadingUnknown = FALSE;
				m_uiUnknownPacketRc = rc;
				goto Exit;
			}

			if (uiPacketType != RFL_UNKNOWN_PACKET)
			{
				if (!uiBytesRead)
				{
					rc = RC_SET( FERR_EOF_HIT);
				}

				m_bReadingUnknown = FALSE;

				// At this point, we know that the entire packet is inside
				// our memory buffer, so it is safe to reset m_uiRflReadOffset
				// back to the beginning of the packet. The call to readOp()
				// will call getPacket again, which will get this exact same
				// packet for processing.

				m_uiRflReadOffset -= (RFL_PACKET_OVERHEAD + m_uiUnknownPacketBodyLen);
				goto Exit;
			}

			m_uiUnknownBodyLenProcessed = 0;
		}

		uiBytesToCopy = uiLenToRead;
		if (uiBytesToCopy > m_uiUnknownPacketBodyLen -
			 m_uiUnknownBodyLenProcessed)
		{
			uiBytesToCopy = m_uiUnknownPacketBodyLen - m_uiUnknownBodyLenProcessed;
		}

		f_memcpy( pucBuffer, m_pucUnknownPacketBody + m_uiUnknownBodyLenProcessed,
					uiBytesToCopy);
		pucBuffer += uiBytesToCopy;
		uiLenToRead -= uiBytesToCopy;
		uiBytesRead += uiBytesToCopy;
		m_uiUnknownBodyLenProcessed += uiBytesToCopy;

		// If we have exhausted the current packet, reset things so that we
		// will get a new packet the next time around.

		if (m_uiUnknownBodyLenProcessed == m_uiUnknownPacketBodyLen)
		{
			m_uiUnknownPacketBodyLen = 0;
			m_uiUnknownBodyLenProcessed = 0;
			m_pucUnknownPacketBody = NULL;
		}
	}

Exit:

	*puiBytesRead = uiBytesRead;
	return (rc);
}

/********************************************************************
Desc:		Restore transactions from the roll-forward log to the
			database.
*********************************************************************/
RCODE F_Rfl::recover(
	FDB *			pDb,
	F_Restore *	pRestore)
{
	RCODE						rc = FERR_OK;
	HFDB						hDb = (HFDB) pDb;
	FLMUINT					uiStartFileNum;
	FLMUINT					uiStartOffset;
	FLMUINT					uiOffset;
	FLMUINT					uiReadLen;
	FLMUINT					uiBytesRead;
	FLMBYTE					ucHdr[ 512];
	FLMUINT					uiCount;
	RFL_OP_INFO				opInfo;
	FlmRecord *				pRecord = NULL;
	FlmRecord *				pTmpRecord = NULL;
	eRestoreActionType	eRestoreAction;
	FLMBOOL					bTransActive = FALSE;
	FLMBOOL					bHadOperations = FALSE;
	FLMBOOL					bLastTransEndedAtFileEOF = FALSE;
	FLMBOOL					bForceNextFile;

	flmAssert( m_pFile);

	m_pCurrentBuf = &m_Buf1;
	m_uiLastLoggedCommitTransID = 0;

	// We need to allow all updates logged in the RFL (including
	// dictionary updates).

	pDb->bFldStateUpdOk = TRUE;

	// If we are less than version 4.3, we cannot do restore.

	if (pRestore && m_pFile->FileHdr.uiVersionNum < FLM_FILE_FORMAT_VER_4_3)
	{
		goto Exit;
	}

	// Turn off logging.

	m_bLoggingOff = TRUE;

	// Set the replay flag on the database.

	pDb->uiFlags |= FDB_REPLAYING_RFL;

	// Set the flag as to whether or not we are using multiple RFL files.

	if (m_pFile->FileHdr.uiVersionNum < FLM_FILE_FORMAT_VER_4_3)
	{
		m_bKeepRflFiles = FALSE;
	}
	else
	{
		m_bKeepRflFiles = m_pFile->ucLastCommittedLogHdr[LOG_KEEP_RFL_FILES] 
														? TRUE 
														: FALSE;
	}
	
	// Determine the current, on-disk size of the RFL

	if( m_bKeepRflFiles)
	{
		FLMUINT64		ui64RflDiskUsage;
		
		if( RC_BAD( rc = flmRflCalcDiskUsage( m_szRflDir, m_szDbPrefix,
			m_pFile->FileHdr.uiVersionNum, &ui64RflDiskUsage)))
		{
			goto Exit;
		}
		
		f_mutexLock( gv_FlmSysData.hShareMutex);
		m_pFile->ui64RflDiskUsage = ui64RflDiskUsage;
		f_mutexUnlock( gv_FlmSysData.hShareMutex);
	}

	// If pRestore is NULL, we are doing a database recovery after open.
	// In that case, we start from the last checkpoint offset and only run
	// until the last transaction offset.

	if ((m_pRestore = pRestore) == NULL)
	{
		FLMBYTE *	pucCheckSerialNum;
		FLMUINT		uiEndOffset;

		uiStartFileNum = (FLMUINT) FB2UD( 
				&m_pFile->ucLastCommittedLogHdr[ LOG_RFL_LAST_CP_FILE_NUM]);
														
		m_uiLastRecoverFileNum = (FLMUINT) FB2UD( 
			&m_pFile->ucLastCommittedLogHdr[ LOG_RFL_FILE_NUM]);
			
		uiStartOffset = (FLMUINT) FB2UD( 
			&m_pFile->ucLastCommittedLogHdr[ LOG_RFL_LAST_CP_OFFSET]);
			
		uiEndOffset = (FLMUINT) FB2UD( 
			&m_pFile->ucLastCommittedLogHdr[LOG_RFL_LAST_TRANS_OFFSET]);

		// Could be zero if the file was created, but no transactions were
		// ever committed to it.

		if (!uiEndOffset)
		{
			uiEndOffset = 512;
		}

		// Start offset better not be less than 512.

		flmAssert( uiStartOffset >= 512);
		flmAssert( uiEndOffset >= 512);

		// If start and end are at the same place, there is nothing to
		// recover.

		if (uiStartFileNum == m_uiLastRecoverFileNum &&
			 uiStartOffset == uiEndOffset)
		{
			goto Finish_Recovery;
		}

		// We have not recorded the serial number of the last checkpoint
		// file number, so we pass in NULL, unless it happens to be the same
		// as the last transaction file number, in which case we can pass in
		// the serial number we have stored in the log header.

		pucCheckSerialNum = (uiStartFileNum == m_uiLastRecoverFileNum)
				? &m_pFile->ucLastCommittedLogHdr[LOG_LAST_TRANS_RFL_SERIAL_NUM] 
				: NULL;
					
		if (RC_BAD( rc = openFile( uiStartFileNum, pucCheckSerialNum)))
		{
			goto Exit;
		}

		// If this is the last RFL file, the EOF is contained in the log
		// header. Otherwise, it will be in the RFL file's header, and
		// openFile will already have retrieved it.

		if (uiStartFileNum == m_uiLastRecoverFileNum)
		{
			m_uiFileEOF = uiEndOffset;
		}

		// At this point, file EOF better be greater than or equal to 512.

		flmAssert( m_uiFileEOF >= 512);
	}
	else if (!m_bKeepRflFiles)
	{

		// FlmDbRestore should be checking the "keep" flag and not
		// attempting to do a restore of the RFL.

		flmAssert( 0);
		rc = RC_SET( FERR_CANNOT_RESTORE_RFL_FILES);
		goto Exit;
	}
	else
	{
		uiStartFileNum = (FLMUINT) FB2UD( 
						&m_pFile->ucLastCommittedLogHdr[LOG_RFL_FILE_NUM]);
									
		uiStartOffset = (FLMUINT) FB2UD( 
						&m_pFile->ucLastCommittedLogHdr[LOG_RFL_LAST_TRANS_OFFSET]);

		// Could be zero if the RFL file had never been created.

		if (!uiStartOffset)
		{
			uiStartOffset = 512;
		}

		// Ask the recovery object to open the file.

Retry_Open:

		flmAssert( uiStartFileNum);
		if (RC_BAD( rc = m_pRestore->openRflFile( uiStartFileNum)))
		{
			if (rc == FERR_IO_PATH_NOT_FOUND)
			{

				// Need to set m_pCurrentBuf->uiCurrFileNum in case the first
				// call to openRflFile fails. This will cause the code at the
				// Finish_Recovery label to correctly set up the log header.

				if (!uiStartOffset)
				{
					m_pCurrentBuf->uiCurrFileNum = uiStartFileNum - 1;
				}
				else
				{
					m_pCurrentBuf->uiCurrFileNum = uiStartFileNum;
				}

				rc = FERR_OK;
				goto Finish_Recovery;
			}
			else
			{
				goto Exit;
			}
		}

		// Get the first 512 bytes from the file and verify the header.

		if (RC_BAD( rc = m_pRestore->read( 512, ucHdr, &uiBytesRead)))
		{
			goto Exit;
		}

		if (uiBytesRead < 512)
		{
			rc = RC_SET_AND_ASSERT( FERR_NOT_RFL);
			goto Exit;
		}

		if (RC_BAD( rc = verifyHeader( ucHdr, uiStartFileNum,
				&m_pFile->ucLastCommittedLogHdr[LOG_LAST_TRANS_RFL_SERIAL_NUM])))
		{
			RCODE tmpRc;

			if (RC_BAD( tmpRc = m_pRestore->status( RESTORE_ERROR, 0,
						  (void *) ((FLMUINT) rc), (void *) 0, (void *) 0,
						  &eRestoreAction)))
			{
				rc = tmpRc;
				goto Exit;
			}

			if (eRestoreAction == RESTORE_ACTION_RETRY)
			{
				if (RC_BAD( rc = m_pRestore->close()))
				{
					goto Exit;
				}

				goto Retry_Open;
			}

			goto Exit;
		}

		// We may not know the actual EOF of files during restore
		// operations.

		if ((m_uiFileEOF = (FLMUINT) FB2UD( &ucHdr[RFL_EOF_POS])) == 0)
		{
			bLastTransEndedAtFileEOF = TRUE;
		}
		else
		{
			bLastTransEndedAtFileEOF = (m_uiFileEOF == uiStartOffset) 
														? TRUE 
														: FALSE;
		}

		// Position to the start offset. Unfortunately, this means reading
		// through the data and discarding it.

		uiOffset = 512;
		while (uiOffset < uiStartOffset)
		{
			uiReadLen = (uiStartOffset - uiOffset);
			if (uiReadLen > m_uiBufferSize)
			{
				uiReadLen = m_uiBufferSize;
			}

			if (RC_BAD( rc = m_pRestore->read( uiReadLen,
						  m_pCurrentBuf->pIOBuffer->getBufferPtr(), &uiBytesRead)))
			{
				goto Exit;
			}

			// RFL file is incomplete if we could not read up to the last
			// committed transaction.

			if (uiBytesRead < uiReadLen)
			{
				rc = RC_SET( FERR_RFL_INCOMPLETE);
				goto Exit;
			}

			uiOffset += uiBytesRead;
		}

		// Need to set current file number

		m_pCurrentBuf->uiCurrFileNum = uiStartFileNum;

		// Better not be any transactions to recover - last database state
		// needs to be a completed checkpoint.

		flmAssert(
			FB2UD( &m_pFile->ucLastCommittedLogHdr [LOG_LAST_CP_TRANS_ID]) ==
			FB2UD( &m_pFile->ucLastCommittedLogHdr [LOG_CURR_TRANS_ID]));

		// Use uiStartOffset here instead of LOG_RFL_LAST_TRANS_OFFSET,
		// because LOG_RFL_LAST_TRANS_OFFSET may be zero, but we in that
		// case we should be comparing to 512, and uiStartOffset will have
		// been adjusted to 512 if that is the case.

		flmAssert( FB2UD( &m_pFile->ucLastCommittedLogHdr [
							LOG_RFL_LAST_CP_OFFSET]) == uiStartOffset);

		flmAssert( FB2UD( &m_pFile->ucLastCommittedLogHdr[
							LOG_RFL_LAST_CP_FILE_NUM]) == uiStartFileNum);
	}

	// Set last transaction ID to the last transaction that was
	// checkpointed - transaction numbers should ascend from here.

	m_uiLastTransID = (FLMUINT) FB2UD( 
							&m_pFile->ucLastCommittedLogHdr[LOG_LAST_CP_TRANS_ID]);

	// Set the last committed trans ID if this is a 4.31+ database

	if (m_pFile->FileHdr.uiVersionNum >= FLM_FILE_FORMAT_VER_4_31)
	{
		m_uiLastLoggedCommitTransID = (FLMUINT) FB2UD( 
							&m_pFile->ucLastCommittedLogHdr[LOG_LAST_RFL_COMMIT_ID]);
	}

	m_pCurrentBuf->uiRflFileOffset = uiStartOffset;
	m_uiRflReadOffset = 0;
	m_pCurrentBuf->uiRflBufBytes = 0;

	// Now, read until we are done.

	bForceNextFile = FALSE;
	for (;;)
	{
		if (!pRecord)
		{
			if ((pRecord = f_new FlmRecord) == NULL)
			{
				rc = RC_SET( FERR_MEM);
				goto Exit;
			}
		}

		// Get the next operation from the file.

		rc = readOp( pDb, bForceNextFile, &opInfo, pRecord); 
		bForceNextFile = FALSE;

		if (RC_BAD( rc))
		{
Handle_Packet_Error:

			if (rc == FERR_END)
			{
				if (!m_pRestore)
				{

					// If we didn't end exactly where we should have, we have
					// an incomplete log. The same is true if we are in the
					// middle of a transaction.

					if (m_pCurrentBuf->uiCurrFileNum != m_uiLastRecoverFileNum ||
						 bTransActive)
					{
						rc = RC_SET( FERR_RFL_INCOMPLETE);
					}
					else
					{
						rc = FERR_OK;
						goto Finish_Recovery;
					}
				}
				else
				{

					// If we are doing a restore, and we get to the end of the
					// log, it is OK - even if we are in the middle of a
					// transaction - the transaction will simply be aborted.

					rc = FERR_OK;
					goto Finish_Recovery;
				}
			}
			else if (rc == FERR_BAD_RFL_PACKET)
			{

				// If we don't know the current file size, and we are doing a
				// restore, it is OK to end on a bad packet - we will simply
				// abort the current transaction, if any. Then, try to go to
				// the next file, because we really don't know where this file
				// ends.

				if (m_pRestore && !m_uiFileEOF)
				{
					if (bTransActive)
					{
						FlmDbTransAbort( hDb);
						bTransActive = FALSE;
					}

					// Set current transaction ID to zero - as if we had
					// encountered an abort packet.

					m_uiCurrTransID = 0;
					bLastTransEndedAtFileEOF = TRUE;

					// Force to go to the next file

					bForceNextFile = TRUE;
					rc = FERR_OK;
					continue;
				}
			}

			goto Exit;
		}

		// At this point, we know we have a good packet, see what it is and
		// handle it.

		bHadOperations = TRUE;
		switch (opInfo.uiPacketType)
		{
			case RFL_TRNS_BEGIN_EX_PACKET:
			case RFL_TRNS_BEGIN_PACKET:
			{
				if (m_pRestore)
				{
					if (RC_BAD( rc = m_pRestore->status( RESTORE_BEGIN_TRANS,
								  opInfo.uiTransId, (void *) opInfo.uiStartTime, 
								  (void *) 0, (void *) 0, &eRestoreAction)))
					{
						goto Exit;
					}

					if (eRestoreAction == RESTORE_ACTION_STOP)
					{

						// Need to set m_uiCurrTransID to 0 since it was set by
						// getPacket(). We are not going to start a transaction
						// because of the user's request to exit.

						m_uiCurrTransID = 0;
						bLastTransEndedAtFileEOF = FALSE;
						goto Finish_Recovery;
					}
				}

				// If we already have a transaction active, we have a problem.

				flmAssert( !bTransActive);

				if (RC_BAD( rc = FlmDbTransBegin( hDb, FLM_UPDATE_TRANS, 0)))
				{
					goto Exit;
				}

				bTransActive = TRUE;
				break;
			}

			case RFL_TRNS_COMMIT_PACKET:
			{

				// Commit the current transaction.

				if (m_pRestore)
				{
					if (RC_BAD( rc = m_pRestore->status( RESTORE_COMMIT_TRANS,
								  opInfo.uiTransId, (void *) 0, (void *) 0, (void *) 0,
								  &eRestoreAction)))
					{
						goto Exit;
					}

					if (eRestoreAction == RESTORE_ACTION_STOP)
					{
						bLastTransEndedAtFileEOF = FALSE;
						goto Finish_Recovery;
					}
				}

				flmAssert( bTransActive);
				pDb->uiFlags |= FDB_REPLAYING_COMMIT;
				rc = FlmDbTransCommit( hDb);
				pDb->uiFlags &= ~FDB_REPLAYING_COMMIT;
				bTransActive = FALSE;

				if (RC_BAD( rc))
				{
					goto Exit;
				}

				m_uiLastLoggedCommitTransID = opInfo.uiTransId;
				
Finish_Transaction:

				if (!m_uiFileEOF)
				{
					bLastTransEndedAtFileEOF = TRUE;
				}
				else
				{
					bLastTransEndedAtFileEOF = 
						(m_uiRflReadOffset == m_pCurrentBuf->uiRflBufBytes &&
						 m_pCurrentBuf->uiRflFileOffset +
						 m_pCurrentBuf->uiRflBufBytes == m_uiFileEOF) 
									? TRUE 
									: FALSE;
				}

				m_uiLastTransID = opInfo.uiTransId;
				m_uiCurrTransID = 0;
				break;
			}

			case RFL_TRNS_ABORT_PACKET:
			{

				// Abort the current transaction.

				if (m_pRestore)
				{
					if (RC_BAD( rc = m_pRestore->status( RESTORE_ABORT_TRANS,
								  opInfo.uiTransId, (void *) 0, (void *) 0, (void *) 0,
								  &eRestoreAction)))
					{
						goto Exit;
					}

					if (eRestoreAction == RESTORE_ACTION_STOP)
					{
						bLastTransEndedAtFileEOF = FALSE;
						goto Finish_Recovery;
					}
				}

				flmAssert( bTransActive);
				rc = FlmDbTransAbort( hDb);
				bTransActive = FALSE;

				if (RC_BAD( rc))
				{
					goto Exit;
				}

				goto Finish_Transaction;
			}

			case RFL_ADD_RECORD_PACKET:
			case RFL_ADD_RECORD_PACKET_VER_2:
			{
				if (m_pRestore)
				{
					if (RC_BAD( rc = m_pRestore->status( RESTORE_ADD_REC, 
								  opInfo.uiTransId, (void *) opInfo.uiContainer,
								  (void *) opInfo.uiDrn, 
								  (void *) pRecord, &eRestoreAction)))
					{
						goto Exit;
					}

					if (eRestoreAction == RESTORE_ACTION_STOP)
					{
						bLastTransEndedAtFileEOF = FALSE;
						goto Finish_Recovery;
					}
				}

				rc = FlmRecordAdd( hDb, opInfo.uiContainer, &opInfo.uiDrn, 
										 pRecord, opInfo.uiFlags);
				pRecord->Release();
				pRecord = NULL;
				if (RC_BAD( rc))
				{
					goto Exit;
				}
				break;
			}

			case RFL_MODIFY_RECORD_PACKET:
			case RFL_MODIFY_RECORD_PACKET_VER_2:
			{

				// Must retrieve the record and then get the modify packet(s)
				// to alter it.

				if (RC_BAD( rc = FlmRecordRetrieve( hDb, opInfo.uiContainer, 
							  opInfo.uiDrn, FO_EXACT, &pRecord, NULL)))
				{
					goto Exit;
				}

				if ((pTmpRecord = pRecord->copy()) == NULL)
				{
					rc = RC_SET( FERR_MEM);
					goto Exit;
				}

				pRecord->Release();
				pRecord = NULL;

				if (RC_BAD( rc = modifyRecord( hDb, pTmpRecord)))
				{
					goto Handle_Packet_Error;
				}

				// Finally, modify the record in the database.

				if (m_pRestore)
				{
					if (RC_BAD( rc = m_pRestore->status( RESTORE_MOD_REC, 
								  opInfo.uiTransId,
								  (void *) opInfo.uiContainer, (void *) opInfo.uiDrn,
								  (void *) pTmpRecord, &eRestoreAction)))
					{
						goto Exit;
					}

					if (eRestoreAction == RESTORE_ACTION_STOP)
					{
						bLastTransEndedAtFileEOF = FALSE;
						goto Finish_Recovery;
					}
				}

				rc = FlmRecordModify( hDb, opInfo.uiContainer, opInfo.uiDrn, 
											 pTmpRecord, opInfo.uiFlags);

				pTmpRecord->Release();
				pTmpRecord = NULL;

				if (RC_BAD( rc))
				{
					goto Exit;
				}
				
				break;
			}

			case RFL_DELETE_RECORD_PACKET:
			case RFL_DELETE_RECORD_PACKET_VER_2:
			{
				if (m_pRestore)
				{
					if (RC_BAD( rc = m_pRestore->status( RESTORE_DEL_REC, 
								  opInfo.uiTransId,
								  (void *) opInfo.uiContainer, (void *) opInfo.uiDrn, 
								  (void *) 0, &eRestoreAction)))
					{
						goto Exit;
					}

					if (eRestoreAction == RESTORE_ACTION_STOP)
					{
						bLastTransEndedAtFileEOF = FALSE;
						goto Finish_Recovery;
					}
				}

				if (RC_BAD( rc = FlmRecordDelete( hDb, opInfo.uiContainer, 
								opInfo.uiDrn, opInfo.uiFlags)))
				{
					goto Exit;
				}
				break;
			}

			case RFL_RESERVE_DRN_PACKET:
			{
				if (m_pRestore)
				{
					if (RC_BAD( rc = m_pRestore->status( RESTORE_RESERVE_DRN,
								  opInfo.uiTransId, (void *) opInfo.uiContainer, 
								  (void *) opInfo.uiDrn, (void *) 0, &eRestoreAction)))
					{
						goto Exit;
					}

					if (eRestoreAction == RESTORE_ACTION_STOP)
					{
						bLastTransEndedAtFileEOF = FALSE;
						goto Finish_Recovery;
					}
				}

				if (RC_BAD( rc = FlmReserveNextDrn( hDb, opInfo.uiContainer, 
					&opInfo.uiDrn)))
				{
					goto Exit;
				}
				
				break;
			}

			case RFL_INDEX_SUSPEND_PACKET:
			{

				if (m_pRestore)
				{
					if (RC_BAD( rc = m_pRestore->status( RESTORE_INDEX_SUSPEND,
								  opInfo.uiTransId, (void *) opInfo.uiIndex, 
								  (void *) 0, (void *) 0, &eRestoreAction)))
					{
						goto Exit;
					}

					if (eRestoreAction == RESTORE_ACTION_STOP)
					{
						bLastTransEndedAtFileEOF = FALSE;
						goto Finish_Recovery;
					}
				}

				if (RC_BAD( rc = FlmIndexSuspend( hDb, opInfo.uiIndex)))
				{
					goto Exit;
				}
				
				break;
			}

			case RFL_INDEX_RESUME_PACKET:
			{

				if (m_pRestore)
				{
					if (RC_BAD( rc = m_pRestore->status( RESTORE_INDEX_RESUME,
								  opInfo.uiTransId, (void *) opInfo.uiIndex, (void *) 0, 
								  (void *) 0, &eRestoreAction)))
					{
						goto Exit;
					}

					if (eRestoreAction == RESTORE_ACTION_STOP)
					{
						bLastTransEndedAtFileEOF = FALSE;
						goto Finish_Recovery;
					}
				}

				if (RC_BAD( rc = FlmIndexResume( hDb, opInfo.uiIndex)))
				{
					goto Exit;
				}
				break;
			}

			case RFL_INDEX_SET_PACKET:
			case RFL_INDEX_SET_PACKET_VER_2:
			{
				if (m_pRestore)
				{
					if (RC_BAD( rc = m_pRestore->status( RESTORE_INDEX_SET,
								  opInfo.uiTransId, (void *) opInfo.uiIndex, 
								  (void *) opInfo.uiDrn, (void *) opInfo.uiEndDrn,
								  &eRestoreAction)))
					{
						goto Exit;
					}

					if (eRestoreAction == RESTORE_ACTION_STOP)
					{
						bLastTransEndedAtFileEOF = FALSE;
						goto Finish_Recovery;
					}
				}

				if (m_pFile->FileHdr.uiVersionNum < FLM_FILE_FORMAT_VER_4_50 &&
					 opInfo.uiPacketType != RFL_INDEX_SET_PACKET)
				{
					rc = RC_SET( FERR_BAD_RFL_PACKET);
					goto Exit;
				}
				
				if (RC_BAD( rc = flmDbIndexSetOfRecords( hDb, opInfo.uiIndex, 
							  opInfo.uiContainer, opInfo.uiDrn, opInfo.uiEndDrn)))
				{
					goto Exit;
				}
				
				break;
			}

			case RFL_BLK_CHAIN_FREE_PACKET:
			{
				if (m_pRestore)
				{
					if (RC_BAD( rc = m_pRestore->status( RESTORE_BLK_CHAIN_DELETE,
								  opInfo.uiTransId, (void *) opInfo.uiDrn, 
								  (void *) opInfo.uiCount,
								  (void *) opInfo.uiEndBlock, &eRestoreAction)))
					{
						goto Exit;
					}

					if (eRestoreAction == RESTORE_ACTION_STOP)
					{
						bLastTransEndedAtFileEOF = FALSE;
						goto Finish_Recovery;
					}
				}

				if (RC_BAD( rc = flmMaintFreeBlockChain( pDb, opInfo.uiDrn, 
							  opInfo.uiCount, opInfo.uiEndBlock, NULL)))
				{
					goto Exit;
				}
				break;
			}

			case RFL_START_UNKNOWN_PACKET:
			{
				if (m_pRestore)
				{
					F_RflUnknownStream	unkStrm;

					unkStrm.setup( this, TRUE);
					m_bReadingUnknown = TRUE;
					m_uiUnknownPacketBodyLen = 0;
					m_pucUnknownPacketBody = NULL;
					m_uiUnknownBodyLenProcessed = 0;
					m_uiUnknownPacketRc = FERR_OK;

					if (RC_BAD( rc = m_pRestore->processUnknown(
									  (F_UnknownStream*) &unkStrm)))
					{
						if (m_uiUnknownPacketRc != FERR_OK)
						{
							rc = m_uiUnknownPacketRc;
							goto Handle_Packet_Error;
						}

						goto Exit;
					}

					// If we did not read through all of the unknown packets,
					// skip them at this time.

					if (m_bReadingUnknown)
					{
						goto Skip_Unknown_Packets;
					}
				}
				else
				{
Skip_Unknown_Packets:

					// Skip all unknown packets.

					for (;;)
					{
						FLMUINT		uiPacketType;
						FLMBYTE *	pucPacketBody;
						FLMUINT		uiPacketBodyLen;

						if (RC_BAD( rc = getPacket( FALSE, &uiPacketType,
									  &pucPacketBody, &uiPacketBodyLen, NULL)))
						{
							goto Handle_Packet_Error;
						}

						// If we hit something other than an unknown packet,
						// "push" it back into the pipe so it will be processed
						// by readOp() up above.

						if (uiPacketType != RFL_UNKNOWN_PACKET)
						{

							// At this point, we know that the entire packet is
							// inside our memory buffer, so it is safe to reset
							// m_uiRflReadOffset back to the beginning of the
							// packet. The call to readOp() above will call
							// getPacket again, which will get this exact same
							// packet for processing.

							m_uiRflReadOffset -= 
									(RFL_PACKET_OVERHEAD + uiPacketBodyLen);
							break;
						}
					}
				}
				break;
			}

			case RFL_REDUCE_PACKET:
			{

				if (m_pRestore)
				{
					if (RC_BAD( rc = m_pRestore->status( RESTORE_REDUCE, 
								  opInfo.uiTransId, (void *) opInfo.uiCount,
								  (void *) 0, (void *) 0, &eRestoreAction)))
					{
						goto Exit;
					}

					if (eRestoreAction == RESTORE_ACTION_STOP)
					{

						// Need to set m_uiCurrTransID to 0 since it was set by
						// getPacket(). We are not going to start a transaction
						// because of the user's request to exit.

						m_uiCurrTransID = 0;
						bLastTransEndedAtFileEOF = FALSE;
						goto Finish_Recovery;
					}
				}

				if (RC_BAD( rc = FlmDbReduceSize( hDb, opInfo.uiCount, &uiCount)))
				{
					goto Exit;
				}

				goto Finish_Transaction;
			}

			case RFL_UPGRADE_PACKET:
			{
				if (m_pRestore)
				{
					if (RC_BAD( rc = m_pRestore->status( RESTORE_UPGRADE, 
								  opInfo.uiTransId,
								  (void *) opInfo.uiOldVersion,
								  (void *) opInfo.uiNewVersion,
								  (void *) 0, &eRestoreAction)))
					{
						goto Exit;
					}

					if (eRestoreAction == RESTORE_ACTION_STOP)
					{

						// Need to set m_uiCurrTransID to 0 since it was set by
						// getPacket(). We are not going to start a transaction
						// because of the user's request to exit.

						m_uiCurrTransID = 0;
						bLastTransEndedAtFileEOF = FALSE;
						goto Finish_Recovery;
					}
				}

				// Attempt the conversion if the current version is less than
				// the target version and the target version is less than or
				// equal to the highest version supported by this code.

				if (opInfo.uiNewVersion > FLM_CUR_FILE_FORMAT_VER_NUM)
				{
					rc = RC_SET( FERR_UNALLOWED_UPGRADE);
					goto Exit;
				}
				else
				{
					flmAssert( m_pFile->FileHdr.uiVersionNum < opInfo.uiNewVersion);

					// The logged "new" version may be a lesser version than
					// FLM_CURRENT_FILE_FORMAT_VERSION, which is what FlmDbUpgrade
					// upgrades to. This is OK because the current version
					// should support all packets in the RFL for versions that
					// are less than it. Otherwise, the RFL chain would have
					// been broken by the original upgrade and it would not
					// have logged an upgrade packet.

					if (RC_BAD( rc = FlmDbUpgrade( hDb, opInfo.uiNewVersion, 
						NULL, NULL)))
					{
						goto Exit;
					}
				}

				goto Finish_Transaction;
			}

			case RFL_WRAP_KEY_PACKET:
			case RFL_ENABLE_ENCRYPTION_PACKET:
			{
				goto Finish_Transaction;
			}

			case RFL_CONFIG_SIZE_EVENT_PACKET: // here
			{
				if (m_pRestore)
				{
					if (RC_BAD( rc = m_pRestore->status( RESTORE_CONFIG_SIZE_EVENT, 
								  opInfo.uiTransId,
								  (void *) opInfo.uiSizeThreshold,
								  (void *) opInfo.uiTimeInterval, 
								  (void *) opInfo.uiSizeInterval, &eRestoreAction)))
					{
						goto Exit;
					}

					if (eRestoreAction == RESTORE_ACTION_STOP)
					{

						// Need to set m_uiCurrTransID to 0 since it was set by
						// getPacket(). We are not going to start a transaction
						// because of the user's request to exit.

						m_uiCurrTransID = 0;
						bLastTransEndedAtFileEOF = FALSE;
						goto Finish_Recovery;
					}
				}

				if (RC_BAD( rc = flmSetRflSizeThreshold( 
					hDb, opInfo.uiSizeThreshold, opInfo.uiTimeInterval, 
					opInfo.uiSizeInterval)))
				{
					goto Exit;
				}

				goto Finish_Transaction;
			}
			
			default:
			{

				// Should not be getting other packet types at this point. ;
				// If we don't know the current file size, and we are doing a
				// restore, it is OK to end on a bad packet - we will simply
				// abort the current transaction, if any.
				
				if (m_pRestore && !m_uiFileEOF)
				{
					rc = FERR_OK;
					goto Finish_Recovery;
				}
				else
				{
					rc = RC_SET( FERR_BAD_RFL_PACKET);
				}

				goto Exit;
			}
		}
	}

Finish_Recovery:

	if (bTransActive)
	{
		FlmDbTransAbort( hDb);
		bTransActive = FALSE;
	}

	if (m_pRestore)
	{
		FLMUINT	uiNextRflFileNum = m_pCurrentBuf->uiCurrFileNum + 1;

		// At the end of the restore operation, we need to set things up so
		// that the next transaction will begin a new RFL file. If we ended
		// the restore in the middle of an RFL file, we need to set it up so
		// that the new RFL file will have a new serial number. If we ended
		// at the end of an RFL file, we can set it up so that the new RFL
		// file will have the next serial number. ;
		// Set up the next RFL file number and offset.
		
		UD2FBA( uiNextRflFileNum,
				 &m_pFile->ucLastCommittedLogHdr[LOG_RFL_FILE_NUM]);

		// Set a zero into the offset, this is a special case which tells
		// us that we should create the file no matter what - even if it
		// already exists - it should be overwritten.

		UD2FBA( 0, &m_pFile->ucLastCommittedLogHdr[LOG_RFL_LAST_TRANS_OFFSET]);

		if (bLastTransEndedAtFileEOF)
		{

			// Move the next serial number of the last RFL file processed
			// into into the current RFL serial number so that the log header
			// will be correct

			f_memcpy( &m_pFile->ucLastCommittedLogHdr[
				LOG_LAST_TRANS_RFL_SERIAL_NUM], m_ucNextSerialNum, 
				F_SERIAL_NUM_SIZE);
		}
		else
		{

			// Must create a new serial number so that when the new RFL file
			// is created, it will have that next serial number.

			if (RC_BAD( rc = f_createSerialNumber( &m_pFile->ucLastCommittedLogHdr[
							  LOG_LAST_TRANS_RFL_SERIAL_NUM])))
			{
				goto Exit;
			}
		}

		// Save the last logged commit transaction ID.

		if (m_pFile->FileHdr.uiVersionNum >= FLM_FILE_FORMAT_VER_4_31 &&
			 m_uiLastLoggedCommitTransID)
		{
			UD2FBA( m_uiLastLoggedCommitTransID,
					 &m_pFile->ucLastCommittedLogHdr[LOG_LAST_RFL_COMMIT_ID]);
		}

		// No matter what, we must generate a new next serial number. This
		// is what will be written to the new RFL file's header when it is
		// created.

		if (RC_BAD( rc = f_createSerialNumber(
						  &m_pFile->ucLastCommittedLogHdr[LOG_RFL_NEXT_SERIAL_NUM])))
		{
			goto Exit;
		}
	}

	if (!bHadOperations)
	{

		// No transactions were recovered, but still need to setup a few
		// things.

		m_pFile->uiFirstLogCPBlkAddress = 0;
		m_pFile->uiLastCheckpointTime = (FLMUINT) FLM_GET_TIMER();

		// Save the state of the log header into the ucCheckpointLogHdr
		// buffer.

		f_memcpy( m_pFile->ucCheckpointLogHdr, m_pFile->ucLastCommittedLogHdr,
					LOG_HEADER_SIZE);
	}

	// Force a checkpoint to force the log files to be truncated and
	// everything to be reset. This is done because during recovery the
	// checkpoints that are executed do NOT truncate the RFL file - because
	// it is using the log file to recover!

	closeFile();
	m_pRestore = NULL;
	m_bLoggingOff = FALSE;
	pDb->uiFlags &= ~FDB_REPLAYING_RFL;
	
	if (RC_BAD( rc = FlmDbCheckpoint( hDb, 0)))
	{
		goto Exit;
	}

Exit:

	if (pRecord)
	{
		pRecord->Release();
	}

	if (pTmpRecord)
	{
		pTmpRecord->Release();
	}

	if (bTransActive)
	{
		FlmDbTransAbort( hDb);
	}

	pDb->bFldStateUpdOk = FALSE;
	pDb->uiFlags &= ~FDB_REPLAYING_RFL;

	return (rc);
}

/********************************************************************
Desc:
********************************************************************/
RCODE flmRflCalcDiskUsage(
	const char *	pszRflDir,
	const char *	pszRflPrefix,
	FLMUINT			uiDbVersionNum,
	FLMUINT64 *		pui64DiskUsage)
{
	RCODE				rc = FERR_OK;
	IF_DirHdl *		pDirHdl = NULL;
	FLMUINT			uiFileNumber;
	FLMUINT64		ui64DiskUsage;
	
	ui64DiskUsage = 0;
	
	if( RC_BAD( rc = gv_FlmSysData.pFileSystem->openDir( pszRflDir,
		(char *) "*", &pDirHdl)))
	{
		if( rc == FERR_IO_PATH_NOT_FOUND)
		{
			rc = FERR_OK;
		}

		goto Exit;
	}
	
	for( ;;)
	{
		if( RC_BAD( rc = pDirHdl->next()))
		{
			if( rc != FERR_IO_NO_MORE_FILES && rc != FERR_IO_PATH_NOT_FOUND)
			{
				goto Exit;
			}
			
			rc = FERR_OK;
			break;
		}
	
		// If the current file is an RFL file, increment the disk usage
	
		if( rflGetFileNum( uiDbVersionNum, pszRflPrefix, 
			pDirHdl->currentItemName(), &uiFileNumber))
		{
			ui64DiskUsage += pDirHdl->currentItemSize();
		}
	}
	
Exit:

	*pui64DiskUsage = ui64DiskUsage;

	if( pDirHdl)
	{
		pDirHdl->Release();
	}
	
	return( rc);
}

/********************************************************************
Desc:	Returns the name of an RFL file given its number
********************************************************************/
FLMEXP RCODE FLMAPI FlmDbGetRflFileName(
	HFDB			hDb,
	FLMUINT		uiFileNum,
	char *		pszFileName)
{
	((FDB *) hDb)->pFile->pRfl->getBaseRflFileName( uiFileNum, pszFileName);
	return (FERR_OK);
}

/****************************************************************************
Desc:
****************************************************************************/
F_RflUnknownStream::F_RflUnknownStream() 
{
	m_pRfl = NULL;
	m_bStartedWriting = FALSE;
	m_bInputStream = FALSE;
	m_bSetupCalled = FALSE;
}

/****************************************************************************
Desc:
****************************************************************************/
F_RflUnknownStream::~F_RflUnknownStream() 
{
	if (m_bSetupCalled)
	{
		(void)close();
	}
}

/****************************************************************************
Desc:
****************************************************************************/
RCODE F_RflUnknownStream::setup(
		F_Rfl *			pRfl,
		FLMBOOL			bInputStream)
{
	RCODE			rc = FERR_OK;

	flmAssert( !m_bSetupCalled);

	if (!pRfl)
	{
		flmAssert( 0);
		rc = RC_SET( FERR_INVALID_PARM);
		goto Exit;
	}
	m_pRfl = pRfl;
	m_bInputStream = bInputStream;
	m_bSetupCalled = TRUE;
	m_bStartedWriting = FALSE;

Exit:
	return( rc);
}
	
/****************************************************************************
Desc:
****************************************************************************/
RCODE F_RflUnknownStream::close( void)
{
	RCODE			rc = FERR_OK;

	flmAssert( m_bSetupCalled);

	// There is nothing to do for input streams, because the RFL
	// code handles skipping over any unknown data that may not have
	// been read yet.
	// For output streams, we need to call the endLoggingUnknown
	// routine so that the last packet gets written out.

	if (!m_bInputStream)
	{
		if (m_bStartedWriting)
		{
			m_bStartedWriting = FALSE;
			if (RC_BAD( rc = m_pRfl->endLoggingUnknown()))
			{
				goto Exit;
			}
		}
	}
Exit:
	return( rc);
}

/****************************************************************************
Desc:
****************************************************************************/
RCODE F_RflUnknownStream::read(
	FLMUINT			uiLength,
	void *			pvBuffer,
	FLMUINT *		puiBytesRead)
{
	RCODE			rc = FERR_OK;

	flmAssert( m_bSetupCalled);

	if (!m_bInputStream)
	{

		// Cannot read from an output stream.

		flmAssert( 0);
		rc = RC_SET( FERR_ILLEGAL_OP);
		goto Exit;
	}

	if (RC_BAD( rc = m_pRfl->readUnknown( uiLength, (FLMBYTE *)pvBuffer,
										puiBytesRead)))
	{
		goto Exit;
	}

Exit:
	return( rc);
}

/****************************************************************************
Desc:
****************************************************************************/
RCODE F_RflUnknownStream::write(
	FLMUINT			uiLength,
	void *			pvBuffer)
{
	RCODE			rc = FERR_OK;

	flmAssert( m_bSetupCalled);
	flmAssert( m_pRfl);

	if (m_bInputStream)
	{

		// Cannot write to an input stream.

		flmAssert( 0);
		rc = RC_SET( FERR_ILLEGAL_OP);
		goto Exit;
	}

	// Need to start logging on the first write.

	if (!m_bStartedWriting)
	{
		if (RC_BAD( rc = m_pRfl->startLoggingUnknown()))
		{
			goto Exit;
		}
		m_bStartedWriting = TRUE;
	}

	// Log the data.

	if (RC_BAD( rc = m_pRfl->logUnknown( (FLMBYTE *)pvBuffer, uiLength)))
	{
		goto Exit;
	}
Exit:
	return( rc);
}

/****************************************************************************
Desc:	Returns an unknown stream object - suitable for writing unknown
		streams into the roll-forward log.
****************************************************************************/
FLMEXP RCODE FLMAPI FlmDbGetUnknownStreamObj(
	HFDB						hDb,
	F_UnknownStream **	ppUnknownStream)
{
	RCODE						rc = FERR_OK;
	FDB *						pDb = (FDB *)hDb;
	F_RflUnknownStream *	pUnkStream = NULL;

	flmAssert( pDb);
	flmAssert( ppUnknownStream);

	// See if the database is being forced to close

	if( RC_BAD( rc = flmCheckDatabaseState( pDb)))
	{
		goto Exit;
	}

	// This is only valid on 4.3 and greater.

	if (pDb->pFile->FileHdr.uiVersionNum < FLM_FILE_FORMAT_VER_4_3)
	{
		goto Exit;	// Will return FERR_OK and a NULL pointer.
	}

	// Must be in an update transaction.

	if (pDb->uiTransType == FLM_NO_TRANS)
	{
		rc = RC_SET( FERR_NO_TRANS_ACTIVE);
		goto Exit;
	}
	if (pDb->uiTransType != FLM_UPDATE_TRANS)
	{
		rc = RC_SET( FERR_ILLEGAL_TRANS_OP);
		goto Exit;
	}

	// Allocate the stream object we want.

	if ((pUnkStream = f_new F_RflUnknownStream) == NULL)
	{
		rc = RC_SET( FERR_MEM);
		goto Exit;
	}

	// Setup the unknown stream object.

	if (RC_BAD( rc = pUnkStream->setup( pDb->pFile->pRfl, FALSE)))
	{
		goto Exit;
	}

Exit:

	if (RC_BAD( rc) && pUnkStream)
	{
		pUnkStream->Release();
		pUnkStream = NULL;
	}
	
	*ppUnknownStream = (F_UnknownStream *)pUnkStream;
	return( rc);
}
