//------------------------------------------------------------------------------
// Desc:	Backup and restore Routines
//
// Tabs:	3
//
//		Copyright (c) 1999-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: flbackup.cpp 3112 2006-01-19 13:12:40 -0700 (Thu, 19 Jan 2006) dsanders $
//------------------------------------------------------------------------------

#include "flaimsys.h"

// Typedefs

typedef struct
{
	FLMUINT64		ui64BytesToDo;
	FLMUINT64		ui64BytesDone;
} DB_BACKUP_INFO, * DB_BACKUP_INFO_p;

// Local classes

class	F_BackerStream : public F_Object
{
public:

	F_BackerStream( void);
	~F_BackerStream( void);

	RCODE setup(
		FLMUINT					uiMTUSize,
		IF_RestoreClient *	pRestoreObj);

	RCODE setup(
		FLMUINT					uiMTUSize,
		IF_BackupClient *		pClient);

	RCODE startThreads( void);

	void shutdownThreads( void);

	RCODE read(
		FLMUINT				uiLength,
		FLMBYTE *			pucData,
		FLMUINT *			puiBytesRead = NULL);

	RCODE write(
		FLMUINT				uiLength,
		FLMBYTE *			pucData,
		FLMUINT *			puiBytesWritten = NULL);

	RCODE flush( void);

	FINLINE FLMUINT64 getByteCount( void)
	{
		// Returns the total number of bytes read or written.

		return( m_ui64ByteCount);
	}

	FINLINE FLMUINT getMTUSize( void)
	{
		return( m_uiMTUSize);
	}

private:

	RCODE signalThread( void);

	RCODE _setup( void);

	static RCODE FLMAPI readThread(
		IF_Thread *			pThread);

	static RCODE FLMAPI writeThread(
		IF_Thread *			pThread);

	FLMBOOL					m_bSetup;
	FLMBOOL					m_bFirstRead;
	FLMBOOL					m_bFinalRead;
	FLMUINT					m_uiBufOffset;
	FLMUINT64				m_ui64ByteCount;
	IF_RestoreClient *	m_pRestoreObj;
	F_SEM						m_hDataSem;
	F_SEM						m_hIdleSem;
	IF_Thread *				m_pThread;
	RCODE						m_rc;
	FLMBYTE *				m_pucInBuf;
	FLMUINT *				m_puiInOffset;
	FLMBYTE *				m_pucOutBuf;
	FLMUINT *				m_puiOutOffset;
	FLMBYTE *				m_pucBufs[ 2];
	FLMUINT					m_uiOffsets[ 2];
	FLMUINT					m_uiMTUSize;
	IF_BackupClient *		m_pClient;
};

// Constants

#define FLM_BACKER_SIGNATURE_OFFSET			0
#define		FLM_BACKER_SIGNATURE				"!DB_BACKUP_FILE!"
#define		FLM_BACKER_SIGNATURE_SIZE		16
#define FLM_BACKER_VERSION_OFFSET			16
#define		FLM_BACKER_VERSION_5_0_0		500
#define		FLM_BACKER_VERSION				FLM_BACKER_VERSION_5_0_0
#define FLM_BACKER_DB_BLOCK_SIZE_OFFSET	20
#define FLM_BACKER_BFMAX_OFFSET				24
#define FLM_BACKER_MTU_OFFSET					28
#define FLM_BACKER_TIME_OFFSET				32
#define FLM_BACKER_DB_NAME_OFFSET			36
#define FLM_BACKER_BACKUP_TYPE_OFFSET		40
#define FLM_BACKER_NEXT_INC_SERIAL_NUM		44
#define FLM_BACKER_DB_VERSION					60

// The backer MTU size must be a multiple of the largest
// supported block size.  Additionally, it must be at least
// 2 * FLM_BACKER_MAX_DB_BLOCK_SIZE.  DO NOT CHANGE THE MTU
// SIZE UNLESS YOU ALSO BUMP THE BACKUP VERSION.

#define FLM_BACKER_MTU_SIZE				((FLMUINT) 1024 * 512)
#define FLM_BACKER_MAX_FILE_SIZE			((FLMUINT) 1024 * 1024 * 1024 * 2) // 2 Gigabytes
#define FLM_BACKER_MIN_DB_BLOCK_SIZE	((FLMUINT) 2 * 1024)
#define FLM_BACKER_MAX_DB_BLOCK_SIZE	((FLMUINT) 16 * 1024)

// Backup block header

#define FLM_BACKER_BLK_HDR_SIZE			((FLMUINT) 4)
#define FLM_BACKER_BLK_ADDR_OFFSET		0

// Local prototypes

FSTATIC RCODE flmRestoreFile(
	IF_RestoreClient *	pRestoreObj,
	IF_RestoreStatus *	pRestoreStatus,
	F_SuperFileHdl *		pSFile,
	FLMBOOL					bIncremental,
	FLMUINT *				puiDbVersion,
	FLMUINT *				puiNextIncSeqNum,
	FLMBOOL *				pbRflPreserved,
	eRestoreAction *		peAction,
	FLMBOOL *				pbOKToRetry);

// Functions

/***************************************************************************
Desc : Prepares FLAIM to backup a database.
Notes: Only one backup of a particular database can be active at any time
*END************************************************************************/
RCODE F_Db::backupBegin(
	eDbBackupType	eBackupType,
	eDbTransType	eTransType,
	FLMUINT			uiMaxLockWait,
	IF_Backup **	ppBackup)
{
	F_Backup *	pBackup = NULL;
	FLMBOOL		bBackupFlagSet = FALSE;
	FLMUINT		uiLastCPFileNum;
	FLMUINT		uiLastTransFileNum;
	FLMUINT		uiDbVersion;
	XFLM_DB_HDR *	pDbHdr;
	RCODE			rc = NE_XFLM_OK;

	// Initialize the handle

	*ppBackup = NULL;

	// Make sure we are not being called inside a transaction

	if( getTransType() != XFLM_NO_TRANS)
	{
		rc = RC_SET( NE_XFLM_TRANS_ACTIVE);
		goto Exit;
	}

	// Verify that the application has specified a valid transaction type.

	if( eTransType != XFLM_READ_TRANS && eTransType != XFLM_UPDATE_TRANS)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_NOT_IMPLEMENTED);
		goto Exit;
	}

	// Make sure a valid backup type has been specified

	uiDbVersion = getDbVersion();

	// See if a backup is currently running against the database.  If so,
	// return an error.  Otherwise, set the backup flag on the FFILE.

	m_pDatabase->lockMutex();
	if( m_pDatabase->m_bBackupActive)
	{
		m_pDatabase->unlockMutex();
		rc = RC_SET( NE_XFLM_BACKUP_ACTIVE);
		goto Exit;
	}
	else
	{
		bBackupFlagSet = TRUE;
		m_pDatabase->m_bBackupActive = TRUE;
	}
	m_pDatabase->unlockMutex();

	// Allocate the backup handle

	if( (pBackup = f_new F_Backup) == NULL)
	{
		rc = RC_SET( NE_XFLM_MEM);
		goto Exit;
	}

	pBackup->m_pDb = this;
	pBackup->m_uiDbVersion = uiDbVersion;

	// Start a transaction

	if( RC_BAD( rc = beginTrans( eTransType, uiMaxLockWait,
		XFLM_DONT_KILL_TRANS | XFLM_DONT_POISON_CACHE, &pBackup->m_dbHdr)))
	{
		goto Exit;
	}

	pBackup->m_bTransStarted = TRUE;
	pBackup->m_eTransType = eTransType;
	pDbHdr = &pBackup->m_dbHdr;

	// Don't allow an incremental backup to be performed
	// if a full backup has not yet been done.

	if( eBackupType == XFLM_INCREMENTAL_BACKUP &&
		 pDbHdr->ui64LastBackupTransID == 0)
	{
		rc = RC_SET( NE_XFLM_ILLEGAL_OP);
		goto Exit;
	}

	pBackup->m_eBackupType = eBackupType;

	// Set the next incremental backup serial number.  This is
	// done regardless of the backup type to prevent the wrong
	// set of incremental backup files from being applied
	// to a database.

	if( RC_BAD( rc = f_createSerialNumber(
		pBackup->m_ucNextIncSerialNum)))
	{
		goto Exit;
	}

	// Get the incremental sequence number from the DB header

	pBackup->m_uiIncSeqNum = (FLMUINT)pDbHdr->ui32IncBackupSeqNum;

	// Determine the transaction ID of the last backup

	pBackup->m_ui64LastBackupTransId = pDbHdr->ui64LastBackupTransID;

	// Get the block change count

	pBackup->m_uiBlkChgSinceLastBackup =
		(FLMUINT)pDbHdr->ui32BlksChangedSinceBackup;

	// Get the current transaction ID

	pBackup->m_ui64TransId = pBackup->m_pDb->getTransID();

	// Get the logical end of file

	pBackup->m_uiLogicalEOF = (FLMUINT)pDbHdr->ui32LogicalEOF;

	// Get the first required RFL file needed by the restore.

	uiLastCPFileNum = (FLMUINT)pDbHdr->ui32RflLastCPFileNum;
	uiLastTransFileNum = (FLMUINT)pDbHdr->ui32RflCurrFileNum;

	flmAssert( uiLastCPFileNum <= uiLastTransFileNum);

	pBackup->m_uiFirstReqRfl = uiLastCPFileNum < uiLastTransFileNum
								? uiLastCPFileNum
								: uiLastTransFileNum;

	flmAssert( pBackup->m_uiFirstReqRfl);

	// Get the database block size

	pBackup->m_uiBlockSize = getBlockSize();

	// Get the database path

	(void)getDbControlFileName( pBackup->m_szDbPath,
										sizeof( pBackup->m_szDbPath));

	// Done

	*ppBackup = pBackup;
	pBackup = NULL;

Exit:

	if( RC_BAD( rc))
	{
		if( pBackup)
		{
			if( pBackup->m_bTransStarted)
			{
				abortTrans();
			}

			pBackup->Release();
		}

		if( bBackupFlagSet)
		{
			m_pDatabase->lockMutex();
			m_pDatabase->m_bBackupActive = FALSE;
			m_pDatabase->unlockMutex();
		}
	}

	return( rc);
}

/****************************************************************************
Desc : Constructor
****************************************************************************/
F_Backup::F_Backup()
{
	m_pDb = NULL;
	m_bTransStarted = FALSE;
	reset();
}

/****************************************************************************
Desc : Destructor
****************************************************************************/
F_Backup::~F_Backup()
{
	endBackup();
}

/****************************************************************************
Desc : Reset member variables to their initial state
****************************************************************************/
void F_Backup::reset( void)
{
	if( m_bTransStarted)
	{
		m_pDb->abortTrans();
		m_bTransStarted = FALSE;
	}

	m_pDb = NULL;
	m_eTransType = XFLM_NO_TRANS;
	m_ui64TransId = 0;
	m_ui64LastBackupTransId = 0;
	m_uiDbVersion = 0;
	m_uiBlkChgSinceLastBackup = 0;
	m_uiBlockSize = 0;
	m_uiLogicalEOF = 0;
	m_uiFirstReqRfl = 0;
	m_uiIncSeqNum = 0;
	m_bCompletedBackup = FALSE;
	m_eBackupType = XFLM_FULL_BACKUP;
	m_backupRc = NE_XFLM_OK;
}

/****************************************************************************
Desc : Streams the contents of a database to the write hook supplied by
		 the application.
Notes: This routine attempts to create a backup of a database without
		 excluding any readers or updaters.  However, if the backup runs
		 too long in an environment where extensive updates are happening,
		 an old view error could be returned.
****************************************************************************/
RCODE F_Backup::backup(
	const char *			pszBackupPath,
	const char *			pszPassword,
	IF_BackupClient *		ifpClient,
	IF_BackupStatus *		ifpStatus,
	FLMUINT *				puiIncSeqNum)
{
	RCODE						rc = NE_XFLM_OK;
	FLMBOOL					bFullBackup = TRUE;
	FLMINT					iFileNum;
	FLMUINT					uiBlkAddr;
	FLMUINT					uiTime;
	F_CachedBlock *		pSCache = NULL;
	XFLM_DB_HDR *			pDbHdr;
	DB_BACKUP_INFO			backupInfo;
	FLMUINT					uiBlockFileOffset;
	FLMUINT					uiCount;
	FLMUINT					uiBlockCount;
	FLMUINT					uiBlockCountLastCB = 0;
	FLMUINT					uiBytesToPad;
	F_BackerStream *		pBackerStream = NULL;
	FLMBYTE *				pucBlkBuf = NULL;
	FLMUINT					uiBlkBufOffset;
	FLMUINT					uiBlkBufSize;
	FLMUINT					uiMaxCSBlocks;
	FLMUINT					uiCPTransOffset;
	FLMUINT					uiMaxFileSize;
	FLMBOOL					bReleaseClient = FALSE;
	FLMBOOL					bMustUnlock = FALSE;

	if( puiIncSeqNum)
	{
		*puiIncSeqNum = 0;
	}

	// Setup the status callback info

	f_memset( &backupInfo, 0, sizeof( DB_BACKUP_INFO));

	// Make sure a backup attempt has not been made with this
	// backup handle.

	if( m_bCompletedBackup)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_FAILURE);
		goto Exit;
	}

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

	// Look at the backup type

	if( m_eBackupType == XFLM_INCREMENTAL_BACKUP)
	{
		if( puiIncSeqNum)
		{
			*puiIncSeqNum = m_uiIncSeqNum;
		}

		bFullBackup = FALSE;
	}

	// Set up the callback

	if( !ifpClient)
	{
		if( !pszBackupPath)
		{
			rc = RC_SET( NE_XFLM_INVALID_PARM);
			goto Exit;
		}

		ifpClient = f_new F_DefaultBackupClient( pszBackupPath);
		
		if (ifpClient == NULL)
		{
			rc = RC_SET( NE_XFLM_MEM);
			goto Exit;
		}
		
		bReleaseClient = TRUE;
	}

	// Allocate and initialize the backer stream object

	if( (pBackerStream = f_new F_BackerStream) == NULL)
	{
		rc = RC_SET( NE_XFLM_MEM);
		goto Exit;
	}

	if( RC_BAD( rc = pBackerStream->setup( FLM_BACKER_MTU_SIZE,	ifpClient)))
	{
		goto Exit;
	}

	// Allocate a temporary buffer

	uiBlkBufSize = FLM_BACKER_MTU_SIZE;
	uiMaxCSBlocks = uiBlkBufSize / m_uiBlockSize;
	if( RC_BAD( rc = f_alloc( uiBlkBufSize, &pucBlkBuf)))
	{
		goto Exit;
	}

	// Setup the backup file header

	uiBlkBufOffset = 0;
	f_memset( pucBlkBuf, 0, m_uiBlockSize);
	f_memcpy( &pucBlkBuf[ FLM_BACKER_SIGNATURE_OFFSET],
		FLM_BACKER_SIGNATURE, FLM_BACKER_SIGNATURE_SIZE);

	UD2FBA( FLM_BACKER_VERSION,
		&pucBlkBuf[ FLM_BACKER_VERSION_OFFSET]);
	UD2FBA( (FLMUINT32)m_uiBlockSize,
		&pucBlkBuf[ FLM_BACKER_DB_BLOCK_SIZE_OFFSET]);
	uiMaxFileSize = (FLMUINT)m_dbHdr.ui32MaxFileSize;
	UD2FBA( (FLMUINT32)uiMaxFileSize,
		&pucBlkBuf[ FLM_BACKER_BFMAX_OFFSET]);
	UD2FBA( (FLMUINT32)FLM_BACKER_MTU_SIZE,
		&pucBlkBuf[ FLM_BACKER_MTU_OFFSET]);
	f_timeGetSeconds( &uiTime);
	UD2FBA( (FLMUINT32)uiTime,
		&pucBlkBuf[ FLM_BACKER_TIME_OFFSET]);

	uiCount = f_strlen( m_szDbPath);

	if( uiCount <= 3)
	{
		pucBlkBuf[ FLM_BACKER_DB_NAME_OFFSET] = 
			(FLMBYTE)m_szDbPath[ uiCount - 6];
		pucBlkBuf[ FLM_BACKER_DB_NAME_OFFSET + 1] = 
			(FLMBYTE)m_szDbPath[ uiCount - 5];
		pucBlkBuf[ FLM_BACKER_DB_NAME_OFFSET + 2] = 
			(FLMBYTE)m_szDbPath[ uiCount - 4];
		pucBlkBuf[ FLM_BACKER_DB_NAME_OFFSET + 3] = '\0';
	}

	UD2FBA( (FLMUINT32)m_eBackupType,
		&pucBlkBuf[ FLM_BACKER_BACKUP_TYPE_OFFSET]);

	// Set the next incremental serial number in the backup's
	// header so that it can be put into the database's log header
	// after the backup has been restored.

	f_memcpy( &pucBlkBuf[ FLM_BACKER_NEXT_INC_SERIAL_NUM],
		m_ucNextIncSerialNum, XFLM_SERIAL_NUM_SIZE);

	// Set the database version number

	UD2FBA( (FLMUINT32)m_uiDbVersion,
		&pucBlkBuf[ FLM_BACKER_DB_VERSION]);

	uiBlkBufOffset += m_uiBlockSize;

	// Copy the database header into the backup's buffer

	f_memset( &pucBlkBuf[ uiBlkBufOffset], 0, m_uiBlockSize);
	f_memcpy( &pucBlkBuf[ uiBlkBufOffset], &m_dbHdr, sizeof( XFLM_DB_HDR));
	pDbHdr = (XFLM_DB_HDR *)(&pucBlkBuf[ uiBlkBufOffset]);
	uiBlkBufOffset += m_uiBlockSize;

	// Fix up the log header

	if( !pDbHdr->ui8RflKeepFiles)
	{
		// Put zero in as the last transaction offset so that the current
		// RFL file will be created if it does not exist after the database
		// is restored.  This has basically the same effect as setting the
		// offset to 512 if the RFL file has already been created.

		pDbHdr->ui32RflLastTransOffset = 0;
		uiCPTransOffset = 512;

		// Create new serial numbers for the RFL.  We don't want anyone
		// to be able to branch into a "no-keep" RFL sequence.

		if (RC_BAD( rc = f_createSerialNumber(
							pDbHdr->ucLastTransRflSerialNum)))
		{
			goto Exit;
		}

		if (RC_BAD( rc = f_createSerialNumber(
									pDbHdr->ucNextRflSerialNum)))
		{
			goto Exit;
		}
	}
	else
	{
		uiCPTransOffset = (FLMUINT)pDbHdr->ui32RflLastTransOffset;
		if( !uiCPTransOffset)
		{
			uiCPTransOffset = 512;
		}
	}

	// Set the CP offsets to the last trans offsets.  This is done
	// because the backup could actually read dirty (committed) blocks
	// from the cache, resulting in a backup set that contains blocks
	// that are more recent than the ones currently on disk.

	pDbHdr->ui32RflLastCPFileNum = pDbHdr->ui32RflCurrFileNum;
	pDbHdr->ui64RflLastCPTransID = pDbHdr->ui64CurrTransID;
	pDbHdr->ui32RflLastCPOffset = (FLMUINT32)uiCPTransOffset;
	pDbHdr->ui32RblEOF = (FLMUINT32)m_uiBlockSize;
	pDbHdr->ui32RblFirstCPBlkAddr = 0;

	// If a password was used, wrap the database key in that password

	if( pszPassword && *pszPassword)
	{
		FLMBYTE *	pucTmp = NULL;
		
		// Need to get a lock on the database - mostly to prevent the very
		// unlikely possibility of another thread attempting to use the
		// database key at the same time we are.
		
		if ((m_pDb->m_uiFlags & FDB_HAS_FILE_LOCK) == 0)
		{
			if	(RC_BAD( rc = m_pDb->dbLock( FLM_LOCK_EXCLUSIVE, 
				0, FLM_NO_TIMEOUT)))
			{
				goto Exit;
			}

			bMustUnlock = TRUE;
		}

		rc = m_pDb->getDatabase()->m_pWrappingKey->getKeyToStore( &pucTmp,
								 &pDbHdr->ui32DbKeyLen,
								 (FLMBYTE *)pszPassword, NULL);
		if( bMustUnlock)
		{
			m_pDb->dbUnlock();
			bMustUnlock = FALSE;
		}
		
		if( RC_BAD( rc))
		{
			if( pucTmp)
			{
				f_free( &pucTmp);
			}

			goto Exit;
		}
		
		f_memcpy( pDbHdr->DbKey, pucTmp, pDbHdr->ui32DbKeyLen);
		f_free(  &pucTmp);
	}

	// Header should already be in native format.

	flmAssert( !hdrIsNonNativeFormat( pDbHdr));

	// Calculate and set the CRC

	pDbHdr->ui32HdrCRC = calcDbHdrCRC( pDbHdr);

	// Output the header

	if( RC_BAD( rc = pBackerStream->write( uiBlkBufOffset, pucBlkBuf)))
	{
		goto Exit;
	}

	// There is no way to quickly compute the actual number of bytes
	// that will be written to the backup.  This is due, in part, to the
	// fact that the backup compresses unused space out of blocks before
	// storing them.

	backupInfo.ui64BytesToDo = FSGetSizeInBytes( uiMaxFileSize, m_uiLogicalEOF);
	uiBlockFileOffset = 0;
	uiBlockCount = 0;
	iFileNum = 1;

	for( ;;)
	{
		if( uiBlockFileOffset >= uiMaxFileSize)
		{
			uiBlockFileOffset = 0;
			iFileNum++;
		}

		uiBlkAddr = FSBlkAddress( iFileNum, uiBlockFileOffset);
		if( !FSAddrIsBelow( uiBlkAddr, m_uiLogicalEOF))
		{
			break;
		}

		// Get the block

		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, NULL,
			uiBlkAddr, NULL, &pSCache)))
		{
			goto Exit;
		}

		if( bFullBackup ||
			 pSCache->getBlockPtr()->ui64TransID > m_ui64LastBackupTransId)
		{
			uiBlkBufOffset = 0;
			if ((FLMUINT)pSCache->getBlockPtr()->ui16BlkBytesAvail >
								m_uiBlockSize - blkHdrSize( pSCache->getBlockPtr()))
			{
				rc = RC_SET_AND_ASSERT( NE_XFLM_DATA_ERROR);
				goto Exit;
			}

			// Output the backup header for the block

			UD2FBA( (FLMUINT32)uiBlkAddr,
				&pucBlkBuf[ FLM_BACKER_BLK_ADDR_OFFSET]);

			uiBlkBufOffset += FLM_BACKER_BLK_HDR_SIZE;

			// Copy the block into the block buffer and prepare it
			// for writing.

			f_memcpy( &pucBlkBuf[ uiBlkBufOffset],
				pSCache->getBlockPtr(), m_uiBlockSize);

			// Encrypt the block if needed.
			
			if (RC_BAD( rc = m_pDb->m_pDatabase->encryptBlock( m_pDb->m_pDict,
															&pucBlkBuf[ uiBlkBufOffset])))
			{
				goto Exit;
			}

			if (RC_BAD( rc = flmPrepareBlockToWrite( m_uiBlockSize,
				(F_BLK_HDR *)(&pucBlkBuf [uiBlkBufOffset]))))
			{
				goto Exit;
			}

			uiBlkBufOffset += m_uiBlockSize;

			// Write the block to the backup stream

			if( RC_BAD( rc = pBackerStream->write(
				uiBlkBufOffset, pucBlkBuf)))
			{
				goto Exit;
			}

			uiBlockCount++;
		}

		ScaReleaseCache( pSCache, FALSE);
		pSCache = NULL;
		uiBlockFileOffset += m_uiBlockSize;

		// Call the status callback

		if ((uiBlockCount - uiBlockCountLastCB) > 100)
		{
			if( ifpStatus)
			{
				backupInfo.ui64BytesDone = FSGetSizeInBytes( uiMaxFileSize,
														uiBlkAddr);
				if( RC_BAD( rc = ifpStatus->backupStatus(
														backupInfo.ui64BytesToDo,
														backupInfo.ui64BytesDone)))
				{
					goto Exit;
				}
			}

			uiBlockCountLastCB = uiBlockCount;
		}
	}

	// Output the end-of-backup marker

	f_memset( pucBlkBuf, 0xFF, sizeof( FLM_BACKER_BLK_HDR_SIZE));
	if( RC_BAD( rc = pBackerStream->write( FLM_BACKER_BLK_HDR_SIZE,
		pucBlkBuf)))
	{
		goto Exit;
	}

	// Pad the backup so that FlmDbRestore will never read more
	// data from the input stream than the backup wrote to it.

	uiBytesToPad = (FLMUINT32)(pBackerStream->getMTUSize() -
		(pBackerStream->getByteCount() % pBackerStream->getMTUSize()));

	if( uiBytesToPad < pBackerStream->getMTUSize())
	{
		f_memset( pucBlkBuf, 0, uiBytesToPad);
		if( RC_BAD( rc = pBackerStream->write( uiBytesToPad, pucBlkBuf)))
		{
			goto Exit;
		}
	}

	// Because of the double buffering, we need to have one empty
	// buffer at the end of the file.

	f_memset( pucBlkBuf, 0, pBackerStream->getMTUSize());
	if( RC_BAD( rc = pBackerStream->write( pBackerStream->getMTUSize(),
		pucBlkBuf)))
	{
		goto Exit;
	}

	if( RC_BAD( rc = pBackerStream->flush()))
	{
		goto Exit;
	}

Exit:

	if( pSCache)
	{
		ScaReleaseCache( pSCache, FALSE);
	}

	if( pBackerStream)
	{
		pBackerStream->Release();
	}

	// Call the status callback now that the background
	// thread has terminated.

	if( RC_OK( rc) && ifpStatus)
	{
		backupInfo.ui64BytesDone = backupInfo.ui64BytesToDo;
		(void)ifpStatus->backupStatus( backupInfo.ui64BytesToDo,
											  backupInfo.ui64BytesDone);
	}

	if( pucBlkBuf)
	{
		f_free( &pucBlkBuf);
	}

	if( RC_OK( rc))
	{
		m_bCompletedBackup = TRUE;
	}

	if ( bReleaseClient)
	{
		ifpClient->Release();
	}

	m_backupRc = rc;
	return( rc);
}

/****************************************************************************
Desc : Ends the backup, updating the log header if needed.
****************************************************************************/
RCODE F_Backup::endBackup( void)
{
	RCODE			rc = NE_XFLM_OK;
	FLMBOOL		bStartedTrans = FALSE;

	if( !m_bCompletedBackup)
	{
		goto Exit;
	}

	// End the transaction

	flmAssert( m_eTransType != XFLM_NO_TRANS);
	if( RC_BAD( rc = m_pDb->abortTrans()))
	{
		goto Exit;
	}
	m_eTransType = XFLM_NO_TRANS;
	m_bTransStarted = FALSE;

	// Start an update transaction.

	if( RC_BAD( rc = m_pDb->beginTrans( XFLM_UPDATE_TRANS)))
	{
		goto Exit;
	}
	bStartedTrans = TRUE;

	// Update log header fields

	m_pDb->m_pDatabase->m_uncommittedDbHdr.ui64LastBackupTransID =
		m_ui64TransId;

	// Since there may have been transactions during the backup,
	// we need to take into account the number of blocks that have
	// changed during the backup when updating the
	// ui32BlksChangedSinceBackup statistic.

	m_pDb->m_pDatabase->m_uncommittedDbHdr.ui32BlksChangedSinceBackup -=
		(FLMUINT32)m_uiBlkChgSinceLastBackup;

	// Bump the incremental backup sequence number

	if( m_eBackupType == XFLM_INCREMENTAL_BACKUP)
	{
		m_pDb->m_pDatabase->m_uncommittedDbHdr.ui32IncBackupSeqNum++;
	}

	// Always change the incremental backup serial number.  This is
	// needed so that if the user performs a full backup, runs some
	// transactions against the database, performs another full backup,
	// and then performs an incremental backup we will know that the
	// incremental backup cannot be restored against the first full
	// backup.

	f_memcpy(
		m_pDb->m_pDatabase->m_uncommittedDbHdr.ucIncBackupSerialNum,
		m_ucNextIncSerialNum, XFLM_SERIAL_NUM_SIZE);

	// Commit the transaction and perform a checkpoint so that the
	// modified log header values will be written.

	bStartedTrans = FALSE;
	if( RC_BAD( m_pDb->commitTrans( 0, TRUE)))
	{
		goto Exit;
	}

Exit:

	// Abort the active transaction (if any)

	if( bStartedTrans)
	{
		m_pDb->abortTrans();
	}

	// Unset the backup flag

	if( m_pDb)
	{
		m_pDb->m_pDatabase->lockMutex();
		m_pDb->m_pDatabase->m_bBackupActive = FALSE;
		m_pDb->m_pDatabase->unlockMutex();
	}

	// Clear the object

	reset();

	// Done.

	return( rc);
}

/****************************************************************************
Desc:		Restores a database from backup
Notes:	This routine does not restore referenced BLOBs.
****************************************************************************/
RCODE F_DbSystem::dbRestore(
	const char *			pszDbPath,
		// [IN] Path of database that is being restored.  This is the
		// same path format that FlmDbCreate expects
		// (i.e., c:\flaim\flm.db).
	const char *			pszDataDir,
		// [IN] Directory where data files are located.
	const char *			pszRflDir,
		// [IN] RFL log file directory.  NULL can be passed to indicate
		// that the files are located in the same directory as the
		// database (specified above).
	const char *			pszBackupPath,
		// [IN] Directory and name of the backup file set.
		// This parameter is required only if the default
		// BACKER_READ_HOOK is used.  Otherwise, NULL can be
		// passed as the value of this parameter.
	const char *			pszPassword,
		// [IN] Password that was used durning the backup
	IF_RestoreClient *	pRestoreObj,
		// [IN] Object to be used to read data from the backup set.
	IF_RestoreStatus *	pRestoreStatus)
		// [IN] Object for reporting the status of the restore
		// operation
{
	RCODE						rc = NE_XFLM_OK;
	IF_FileHdl *			pFileHdl = NULL;
	IF_FileHdl *			pLockFileHdl = NULL;
	F_SuperFileHdl *		pSFile = NULL;
	F_SuperFileClient		SFileClient;	
	FLMBYTE					szBasePath[ F_PATH_MAX_SIZE];
	char						szTmpPath[ F_PATH_MAX_SIZE];
	FLMUINT					uiDbVersion;
	FLMUINT					uiNextIncNum;
	eRestoreAction			eAction = XFLM_RESTORE_ACTION_CONTINUE; // default action...
	FLMBOOL					bRflPreserved;
	FLMBOOL					bMutexLocked = FALSE;
	IF_Db *					pDb = NULL;
	F_Database *			pDatabase = NULL;
	F_FSRestore *			pFSRestoreObj = NULL;
	FLMBOOL					bOKToRetry;

	// Set up the callback

	if( !pRestoreObj)
	{
		if( !pszBackupPath || *pszBackupPath == 0)
		{
			rc = RC_SET_AND_ASSERT( NE_XFLM_INVALID_PARM);
			goto Exit;
		}

		if( (pFSRestoreObj = f_new F_FSRestore) == NULL)
		{
			rc = RC_SET( NE_XFLM_MEM);
			goto Exit;
		}

		if( RC_BAD( rc = pFSRestoreObj->setup( pszDbPath,
			pszBackupPath, pszRflDir)))
		{
			goto Exit;
		}

		// Note: If we wanted to be absolutely correct, we'd do an AddRef on
		// pFSRestoreObj because there's going to be two pointers pointing at
		// it.  It really doesn't matter in this case, though because
		// pFSRestoreObj is local to this function and will get deleted before
		// the function exits.

		pRestoreObj = (IF_RestoreClient *)pFSRestoreObj;
	}

	// Get the base path

	flmGetDbBasePath( (char *)szBasePath, pszDbPath, NULL);

	// Lock the global mutex

	f_mutexLock( gv_XFlmSysData.hShareMutex);
	bMutexLocked = TRUE;

	// Look up the file using findDatabase to see if the file is already open.
	// May unlock and re-lock the global mutex..

	if( RC_BAD( rc = findDatabase( pszDbPath, pszDataDir, &pDatabase)))
	{
		goto Exit;
	}

	// If the database is open, we cannot perform a restore

	if( pDatabase)
	{
		rc = RC_SET( NE_XFLM_DATABASE_OPEN);
		pDatabase = NULL;
		f_mutexUnlock( gv_XFlmSysData.hShareMutex);
		bMutexLocked = FALSE;
		goto Exit;
	}

	// Allocate the F_Database object.  This will prevent other threads from
	// opening the database while the restore is being performed.

	if( RC_BAD( rc = allocDatabase( pszDbPath, pszDataDir, FALSE, &pDatabase)))
	{
		goto Exit;
	}

	// Unlock the global mutex

	f_mutexUnlock( gv_XFlmSysData.hShareMutex);
	bMutexLocked = FALSE;

	// Create a lock file.  If this fails, it could indicate
	// that the destination database exists and is in use by another
	// process.

	f_sprintf( szTmpPath, "%s.lck", szBasePath);
	if( RC_BAD( rc = flmCreateLckFile( szTmpPath, &pLockFileHdl)))
	{
		goto Exit;
	}

	// Create the control file and set up the super file object

	if( RC_BAD( rc = gv_XFlmSysData.pFileSystem->createFile( 
		pszDbPath, FLM_IO_RDWR, &pFileHdl)))
	{
		goto Exit;
	}

	// Allocate a super file object
	// NOTE: Do not use extended cache for this super-file object.

	if( (pSFile = f_new F_SuperFileHdl) == NULL)
	{
		rc = RC_SET( NE_XFLM_MEM);
		goto Exit;
	}
	
	if( RC_BAD( rc = SFileClient.setup( pszDbPath, pszDataDir, 0)))
	{
		goto Exit;
	}

	if( RC_BAD( rc = pSFile->setup( &SFileClient,
		gv_XFlmSysData.pFileHdlCache, gv_XFlmSysData.uiFileOpenFlags,
		gv_XFlmSysData.uiFileCreateFlags)))
	{
		goto Exit;
	}

	// Open the backup set

	if( RC_BAD( rc = pRestoreObj->openBackupSet()))
	{
		goto Exit;
	}

	// Restore the data in the backup set

	if( RC_BAD( rc = flmRestoreFile( pRestoreObj, pRestoreStatus,
		pSFile, FALSE, &uiDbVersion, &uiNextIncNum, &bRflPreserved,
		&eAction, NULL)))
	{
		goto Exit;
	}

	// See if we should continue

	if( eAction == XFLM_RESTORE_ACTION_STOP)
	{
		goto Exit;
	}

	// Close the backup set

	if( RC_BAD( rc = pRestoreObj->close()))
	{
		goto Exit;
	}

	// Apply any available incremental backups.  uiNextIncNum will be 0 if
	// the database version does not support incremental backups.

	if( uiNextIncNum)
	{
		FLMUINT	uiCurrentIncNum;

		for( ;;)
		{
			uiCurrentIncNum = uiNextIncNum;
			if( RC_BAD( rc = pRestoreObj->openIncFile( uiCurrentIncNum)))
			{
				if( rc == NE_FLM_IO_PATH_NOT_FOUND)
				{
					rc = NE_XFLM_OK;
					break;
				}
				else
				{
					goto Exit;
				}
			}
			else
			{
				if( RC_BAD( rc = flmRestoreFile( pRestoreObj, pRestoreStatus,
					pSFile, TRUE, &uiDbVersion, &uiNextIncNum, &bRflPreserved,
					&eAction, &bOKToRetry)))
				{
					RCODE		tmpRc;

					if( !bOKToRetry)
					{
						// Cannot retry the operation or continue ... the
						// database is in an unknown state.

						goto Exit;
					}

					if( pRestoreStatus)
					{
						if( RC_BAD( tmpRc = 
							pRestoreStatus->reportError( &eAction, rc)))
						{
							rc = tmpRc;
							goto Exit;
						}
					}

					if( eAction == XFLM_RESTORE_ACTION_RETRY ||
						eAction == XFLM_RESTORE_ACTION_CONTINUE)
					{
						// Abort the current file (if any)

						if( RC_BAD( rc = pRestoreObj->abortFile()))
						{
							goto Exit;
						}

						if( eAction == XFLM_RESTORE_ACTION_CONTINUE)
						{
							// Break out and begin processing the RFL

							break;
						}

						// Otherwise, retry opening the incremental file

						uiNextIncNum = uiCurrentIncNum;
						continue;
					}
					goto Exit;
				}

				// See if we should continue

				if( eAction == XFLM_RESTORE_ACTION_STOP)
				{
					goto Exit;
				}

				// Close the current file

				if( RC_BAD( rc = pRestoreObj->close()))
				{
					goto Exit;
				}
			}
		}
	}

	// Force everything out to disk

	if( RC_BAD( rc = pSFile->flush()))
	{
		goto Exit;
	}

	pSFile->Release();
	pSFile = NULL;

	// Don't do anything with the RFL if the preserve flag
	// isn't set.

	if( !bRflPreserved)
	{
		pRestoreObj = NULL;
		pRestoreStatus = NULL;
	}

	// Open the file and apply any available RFL files.  The
	// lock file handle is passed to the openDatabase call so
	// that we don't have to give up our lock until the
	// restore is complete.  Also, we don't want to resume
	// any indexing at this point.  By not resuming the indexes,
	// we can perform a DB diff of two restored databases that
	// should be identical without having differences in the
	// tracker container due to background indexing.

	rc = openDatabase( pDatabase,
		pszDbPath, pszDataDir,
		pszRflDir, pszPassword, XFLM_DONT_RESUME_THREADS,
		TRUE, pRestoreObj, pRestoreStatus, pLockFileHdl, &pDb);
	pLockFileHdl = NULL;

	if( RC_BAD( rc))
	{
		pDatabase = NULL;		
		goto Exit;
	}
	
	// If a password was needed to open the database, we need to clear it so it
	//can be opened without a password.
	
	if (pszPassword && pszPassword[0] != 0)
	{
		if (RC_BAD( rc = pDb->wrapKey()))
		{
			goto Exit;
		}
	}

	// Close the database

	pDb->Release();
	pDb = NULL;

Exit:

	if( pSFile)
	{
		// Need to release the super file handle before cleaning up the
		// FFILE because the super file still has a reference to the
		// FFILE's file ID list.

		pSFile->Release();
	}

	if( pDatabase)
	{
		if( !bMutexLocked)
		{
			f_mutexLock( gv_XFlmSysData.hShareMutex);
			bMutexLocked = TRUE;
		}

		if (RC_BAD( rc))
		{
			pDatabase->newDatabaseFinish( rc);
		}

		if( !pDatabase->m_uiOpenIFDbCount)
		{
			pDatabase->freeDatabase();
		}

		f_mutexUnlock( gv_XFlmSysData.hShareMutex);
		bMutexLocked = FALSE;
	}

	if( bMutexLocked)
	{
		f_mutexUnlock( gv_XFlmSysData.hShareMutex);
	}

	if( pDb)
	{
		pDb->Release();
	}

	if( pFileHdl)
	{
		pFileHdl->Release();
	}

	if( pLockFileHdl)
	{
		pLockFileHdl->Release();
	}

	if( pFSRestoreObj)
	{
		pFSRestoreObj->Release();
	}

	// If restore failed, remove all database files (excluding RFL files)

	if( RC_BAD( rc))
	{
		dbRemove( pszDbPath, pszDataDir, NULL, FALSE);
	}

	return( rc);
}

/***************************************************************************
Desc : Restores a full or incremental backup
*END************************************************************************/
FSTATIC RCODE flmRestoreFile(
	IF_RestoreClient *	pRestoreObj,
	IF_RestoreStatus *	pRestoreStatus,
	F_SuperFileHdl *		pSFile,
	FLMBOOL					bIncremental,
	FLMUINT *				puiDbVersion,
	FLMUINT *				puiNextIncSeqNum,
	FLMBOOL *				pbRflPreserved,
	eRestoreAction *		peAction,
	FLMBOOL *				pbOKToRetry)
{
	FLMUINT				uiBytesWritten;
	FLMUINT				uiLogicalEOF;
	FLMUINT				uiBlkAddr;
	FLMUINT				uiBlockCount = 0;
	FLMUINT				uiBlockSize;
	FLMUINT				uiDbVersion;
	FLMUINT				uiMaxFileSize;
	FLMUINT				uiBackupMaxFileSize;
	FLMUINT				uiPriorBlkFile = 0;
	FLMUINT				uiSectorSize;
	XFLM_DB_HDR *		pDbHdr;
	FLMBYTE				ucIncSerialNum[ XFLM_SERIAL_NUM_SIZE];
	FLMBYTE				ucNextIncSerialNum[ XFLM_SERIAL_NUM_SIZE];
	FLMUINT				uiIncSeqNum;
	FLMBYTE *			pucBlkBuf = NULL;
	char					szPath[ F_PATH_MAX_SIZE];
	FLMUINT				uiBlkBufSize;
	FLMUINT				uiPriorBlkAddr = 0;
	FLMUINT64			ui64BytesToDo = 0;
	FLMUINT64			ui64BytesDone = 0;
	eDbBackupType		eBackupType;
	F_BackerStream *	pBackerStream = NULL;
	RCODE					rc = NE_XFLM_OK;
	FLMUINT32			ui32CRC;
	F_BLK_HDR *			pBlkHdr;

	// Initialize the "ok-to-retry" flag

	if( pbOKToRetry)
	{
		*pbOKToRetry = TRUE;
	}

	// Set up the backer stream object

	if( (pBackerStream = f_new F_BackerStream) == NULL)
	{
		rc = RC_SET( NE_XFLM_MEM);
		goto Exit;
	}

	if( RC_BAD( rc = pBackerStream->setup( FLM_BACKER_MTU_SIZE, pRestoreObj)))
	{
		goto Exit;
	}

	// Get the path of the .DB file (file 0).

	if( RC_BAD( rc = pSFile->getFilePath( 0, szPath)))
	{
		goto Exit;
	}

	// Get the sector size

	if( RC_BAD( rc = gv_XFlmSysData.pFileSystem->getSectorSize(
		szPath, &uiSectorSize)))
	{
		goto Exit;
	}

	// Allocate a temporary buffer.  Try to align the buffer on a sector
	// boundary to avoid memcpy operatons in the file system.

	uiBlkBufSize = FLM_BACKER_MTU_SIZE;
	if( uiSectorSize)
	{
		uiBlkBufSize = (((uiBlkBufSize / uiSectorSize) + 1) * uiSectorSize);
	}
	
	if( RC_BAD( rc = f_allocAlignedBuffer( uiBlkBufSize, (void **)&pucBlkBuf)))
	{
		goto Exit;
	}

	// Read and verify the backup header

	if( RC_BAD( rc = pBackerStream->read( FLM_BACKER_MIN_DB_BLOCK_SIZE,
		pucBlkBuf)))
	{
		goto Exit;
	}

	if( FB2UD( &pucBlkBuf[ FLM_BACKER_VERSION_OFFSET]) !=	FLM_BACKER_VERSION)
	{
		rc = RC_SET( NE_XFLM_UNSUPPORTED_VERSION);
		goto Exit;
	}

	if( f_strncmp( (const char *)&pucBlkBuf[ FLM_BACKER_SIGNATURE_OFFSET],
		FLM_BACKER_SIGNATURE, FLM_BACKER_SIGNATURE_SIZE) != 0)
	{
		rc = RC_SET( NE_XFLM_UNSUPPORTED_VERSION);
		goto Exit;
	}

	uiBlockSize = (FLMUINT)FB2UW( &pucBlkBuf[ FLM_BACKER_DB_BLOCK_SIZE_OFFSET]);
	if( uiBlockSize > FLM_BACKER_MAX_DB_BLOCK_SIZE)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_INCONSISTENT_BACKUP);
		goto Exit;
	}

	// Get the maximum file size from the backup header.

	uiBackupMaxFileSize = (FLMUINT)FB2UD( &pucBlkBuf[ FLM_BACKER_BFMAX_OFFSET]);

	// Make sure the MTU is correct

	if( FB2UD( &pucBlkBuf[ FLM_BACKER_MTU_OFFSET]) != FLM_BACKER_MTU_SIZE)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_INCONSISTENT_BACKUP);
		goto Exit;
	}

	// Make sure the backup type is correct

	eBackupType = (eDbBackupType)FB2UD(
		&pucBlkBuf[ FLM_BACKER_BACKUP_TYPE_OFFSET]);

	if( (eBackupType == XFLM_INCREMENTAL_BACKUP && !bIncremental) ||
		(eBackupType == XFLM_FULL_BACKUP && bIncremental))
	{
		// Do not allow an incremental backup to be restored directly.  The
		// only way to restore an incremental backup is to provide the
		// incremental files when requested by FlmDbRestore.  Also, we don't
		// want to allow the user to mistakenly hand us a full backup when
		// we are expecting an incremental backup.

		rc = RC_SET( NE_XFLM_ILLEGAL_OP);
		goto Exit;
	}

	// Grab the "next" incremental backup serial number

	f_memcpy( ucNextIncSerialNum,
		&pucBlkBuf[ FLM_BACKER_NEXT_INC_SERIAL_NUM],
		XFLM_SERIAL_NUM_SIZE);

	// Get the database version from the backup header

	uiDbVersion = FB2UD( &pucBlkBuf[ FLM_BACKER_DB_VERSION]);
	if( puiDbVersion)
	{
		*puiDbVersion = uiDbVersion;
	}

	// Seek to the database header block

	if( uiBlockSize > FLM_BACKER_MIN_DB_BLOCK_SIZE)
	{
		if( RC_BAD( rc = pBackerStream->read(
			uiBlockSize - FLM_BACKER_MIN_DB_BLOCK_SIZE, pucBlkBuf)))
		{
			goto Exit;
		}
	}

	// Read the database header block from the backup

	if( RC_BAD( rc = pBackerStream->read( uiBlockSize, pucBlkBuf)))
	{
		goto Exit;
	}

	// Sanity check - make sure the block size in the backup header
	// is the same as the size in the database header

	pDbHdr = (XFLM_DB_HDR *)pucBlkBuf;

	// Calculate the CRC before doing any conversions.

	ui32CRC = calcDbHdrCRC( pDbHdr);

	// Convert to native platform format, if necessary.

	if (hdrIsNonNativeFormat( pDbHdr))
	{
		convertDbHdr( pDbHdr);
	}

	// Validate the checksum

	if (ui32CRC != pDbHdr->ui32HdrCRC)
	{
		rc = RC_SET( NE_XFLM_HDR_CRC);
		goto Exit;
	}

	if( uiBlockSize != (FLMUINT)pDbHdr->ui16BlockSize)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_INCONSISTENT_BACKUP);
		goto Exit;
	}

	// Compare the database version in the DB header with
	// the one extracted from the backup header

	if( (FLMUINT)pDbHdr->ui32DbVersion != uiDbVersion)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_INCONSISTENT_BACKUP);
		goto Exit;
	}

	uiMaxFileSize = (FLMUINT)pDbHdr->ui32MaxFileSize;

	// Make sure the maximum block file size matches what was read from the
	// backup header.

	if( uiBackupMaxFileSize != uiMaxFileSize)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_INCONSISTENT_BACKUP);
		goto Exit;
	}

	// Get the logical EOF from the log header

	uiLogicalEOF = (FLMUINT)pDbHdr->ui32LogicalEOF;

	// Are RFL files being preserved?

	if( pbRflPreserved)
	{
		*pbRflPreserved = pDbHdr->ui8RflKeepFiles
								? TRUE
								: FALSE;
	}

	// Get the incremental backup sequence number

	uiIncSeqNum = (FLMUINT)pDbHdr->ui32IncBackupSeqNum;
	*puiNextIncSeqNum = uiIncSeqNum;

	if( bIncremental)
	{
		(*puiNextIncSeqNum)++;
	}

	// Get information about the incremental backup

	if( bIncremental)
	{
		FLMUINT	uiTmp;
		XFLM_DB_HDR	dbHdr;

		f_memcpy( ucIncSerialNum, pDbHdr->ucIncBackupSerialNum,
			XFLM_SERIAL_NUM_SIZE);

		// Compare the incremental backup sequence number to the value in the
		// database's DB header.

		if( RC_BAD( rc = pSFile->readBlock( 0, sizeof( XFLM_DB_HDR),
											&dbHdr, &uiTmp)))
		{
			goto Exit;
		}
		if (hdrIsNonNativeFormat( &dbHdr))
		{
			convertDbHdr( &dbHdr);
		}

		if( (FLMUINT)dbHdr.ui32IncBackupSeqNum != uiIncSeqNum)
		{
			rc = RC_SET( NE_XFLM_INVALID_FILE_SEQUENCE);
			goto Exit;
		}

		// Compare the incremental backup serial number to the value in the
		// database's log header.

		if( f_memcmp( ucIncSerialNum, dbHdr.ucIncBackupSerialNum,
			XFLM_SERIAL_NUM_SIZE) != 0)
		{
			rc = RC_SET( NE_XFLM_SERIAL_NUM_MISMATCH);
			goto Exit;
		}

		// Increment the incremental backup sequence number

		pDbHdr->ui32IncBackupSeqNum = (FLMUINT32)(uiIncSeqNum + 1);
	}

	// At the start of a backup, either incremental or full,
	// we generate a new incremental serial number.  This is needed so
	// that if the user performs a full backup, runs some transactions
	// against the database, performs another full backup, and then
	// performs an incremental backup we will know that the incremental
	// backup cannot be restored against the first full backup.

	// Since the new serial number is not written to the database's log
	// header until after the backup completes, we need to put the
	// new serial number in the log header during the restore.  In doing
	// so, the log header will contain the correct serial number for a
	// subsequent incremental backup that may have been made.

	f_memcpy( pDbHdr->ucIncBackupSerialNum,
			ucNextIncSerialNum, XFLM_SERIAL_NUM_SIZE);

	// DB Header is in native format.  Set the CRC
	// before writing it out.

	pDbHdr->ui32HdrCRC = calcDbHdrCRC( pDbHdr);
	pDbHdr = NULL;

	// Set the "ok-to-retry" flag

	if( pbOKToRetry)
	{
		*pbOKToRetry = FALSE;
	}

	// Write the database header

	if( RC_BAD( rc = pSFile->writeBlock( 0,
		uiBlockSize, pucBlkBuf, &uiBytesWritten)))
	{
		goto Exit;
	}

	// The status callback will give a general idea of how much work
	// is left to do.  We don't have any way to get the total size
	// of the stream to give a correct count, so a close estimate
	// will have to suffice.
	ui64BytesToDo = FSGetSizeInBytes( uiMaxFileSize, uiLogicalEOF);

	// Write the blocks in the backup file to the database

	for (;;)
	{
		if( RC_BAD( rc = pBackerStream->read( FLM_BACKER_BLK_HDR_SIZE,
			pucBlkBuf)))
		{
			goto Exit;
		}

		uiBlockCount++;
		uiBlkAddr = FB2UD( &pucBlkBuf[ FLM_BACKER_BLK_ADDR_OFFSET]);

		// Are we done?

		if( uiBlkAddr == 0xFFFFFFFF)
		{
			break;
		}

		if( !uiBlkAddr ||
			!FSAddrIsBelow( uiBlkAddr, uiLogicalEOF) ||
			(uiPriorBlkAddr && !FSAddrIsBelow( uiPriorBlkAddr, uiBlkAddr)))
		{
			rc = RC_SET_AND_ASSERT( NE_XFLM_INCONSISTENT_BACKUP);
			goto Exit;
		}

		// Read and process the block

		if( RC_BAD( rc = pBackerStream->read( uiBlockSize, pucBlkBuf)))
		{
			goto Exit;
		}

		pBlkHdr = (F_BLK_HDR *)pucBlkBuf;

		// Convert the entire block to native format if necessary.

		if (RC_BAD( rc = flmPrepareBlockForUse( uiBlockSize, pBlkHdr)))
		{
			if (rc == NE_XFLM_BLOCK_CRC)
			{
				rc = RC_SET_AND_ASSERT( NE_XFLM_INCONSISTENT_BACKUP);
			}
			goto Exit;
		}
		if( (FLMUINT)pBlkHdr->ui32BlkAddr != uiBlkAddr)
		{
			rc = RC_SET_AND_ASSERT( NE_XFLM_INCONSISTENT_BACKUP);
			goto Exit;
		}

		// Prepare the block for writing.

		if (RC_BAD( rc = flmPrepareBlockToWrite( uiBlockSize, pBlkHdr)))
		{
			goto Exit;
		}

		// Write the block to the database

		if( RC_BAD( rc = pSFile->writeBlock( uiBlkAddr,
			uiBlockSize, pucBlkBuf, &uiBytesWritten)))
		{
			if( rc == NE_FLM_IO_PATH_NOT_FOUND ||
				 rc == NE_FLM_IO_INVALID_FILENAME)
			{
				// Create a new block file

				if( FSGetFileNumber( uiBlkAddr) != (uiPriorBlkFile + 1))
				{
					rc = RC_SET_AND_ASSERT( NE_XFLM_INCONSISTENT_BACKUP);
					goto Exit;
				}

				if( RC_BAD( rc = pSFile->createFile( FSGetFileNumber( uiBlkAddr))))
				{
					goto Exit;
				}

				if( RC_BAD( rc = pSFile->writeBlock( uiBlkAddr,
					uiBlockSize, pucBlkBuf, &uiBytesWritten)))
				{
					goto Exit;
				}
			}
			else
			{
				goto Exit;
			}
		}

		uiPriorBlkAddr = uiBlkAddr;
		uiPriorBlkFile = FSGetFileNumber( uiBlkAddr);

		if( pRestoreStatus && (uiBlockCount & 0x7F) == 0x7F)
		{
			ui64BytesDone = FSGetSizeInBytes( uiMaxFileSize, uiBlkAddr);
			if( RC_BAD( rc = pRestoreStatus->reportProgress( peAction,
																ui64BytesToDo,
																ui64BytesDone)))
			{
				goto Exit;
			}

			if( *peAction == XFLM_RESTORE_ACTION_STOP)
			{
				rc = RC_SET( NE_XFLM_USER_ABORT);
				goto Exit;
			}
		}
	}

	if( pRestoreStatus)
	{
		// Call the status callback one last time.

		ui64BytesDone = ui64BytesToDo;
		if( RC_BAD( rc = pRestoreStatus->reportProgress( peAction, ui64BytesToDo,
																	 ui64BytesDone)))
		{
			goto Exit;
		}

		if( *peAction == XFLM_RESTORE_ACTION_STOP)
		{
			// It is safe to jump to exit at this point

			goto Exit;
		}
	}

Exit:

	if( pucBlkBuf)
	{
		f_freeAlignedBuffer( (void **)&pucBlkBuf);
	}

	if( pBackerStream)
	{
		pBackerStream->Release();
	}

	return( rc);
}

/****************************************************************************
Desc: Constructor
****************************************************************************/
F_DefaultBackupClient::F_DefaultBackupClient(
	const char *	pszBackupPath)
{
	m_pMultiFileHdl = NULL;
	m_ui64Offset = 0;
	m_rc = NE_XFLM_OK;

	f_strncpy( m_szPath, pszBackupPath, F_PATH_MAX_SIZE - 1);	
}

/****************************************************************************
Desc: Destructor
****************************************************************************/
F_DefaultBackupClient::~F_DefaultBackupClient()
{
	if (m_pMultiFileHdl)
	{
		m_pMultiFileHdl->closeFile();
		m_pMultiFileHdl->Release();
	}
}


/****************************************************************************
Desc: Default hook for creating a backup file set
****************************************************************************/
RCODE F_DefaultBackupClient::WriteData(
	const void *		pvBuffer,
	FLMUINT				uiBytesToWrite)
{
	FLMUINT				uiBytesWritten;
	RCODE					rc = m_rc;

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

	if( m_pMultiFileHdl == 0)
	{
		// Remove any existing backup files
		
		if( RC_BAD( rc = FlmAllocMultiFileHdl( &m_pMultiFileHdl)))
		{
			goto Exit;
		}

		if( RC_BAD( rc = m_pMultiFileHdl->deleteMultiFile( m_szPath)) &&
			rc != NE_FLM_IO_PATH_NOT_FOUND &&
			rc != NE_FLM_IO_INVALID_FILENAME)
		{
			m_pMultiFileHdl->Release();
			m_pMultiFileHdl = NULL;
			goto Exit;
		}

		if( RC_BAD( rc = m_pMultiFileHdl->createFile( m_szPath)))
		{
			m_pMultiFileHdl->Release();
			m_pMultiFileHdl = NULL;
			goto Exit;
		}
	}

	rc = m_pMultiFileHdl->write( m_ui64Offset,
		uiBytesToWrite, (FLMBYTE *)pvBuffer, &uiBytesWritten);
	m_ui64Offset += uiBytesWritten;

Exit:

	if( RC_BAD( rc))
	{
		m_rc = rc;
		if( m_pMultiFileHdl)
		{
			m_pMultiFileHdl->Release();
			m_pMultiFileHdl = NULL;
		}
	}

	return( rc);
}

// F_BackerStream methods

/****************************************************************************
Desc:	Constructor
****************************************************************************/
F_BackerStream::F_BackerStream( void)
{
	m_bSetup = FALSE;
	m_bFirstRead = TRUE;
	m_bFinalRead = FALSE;
	m_ui64ByteCount = 0;
	m_uiBufOffset = 0;
	m_pRestoreObj = NULL;
	m_hDataSem = F_SEM_NULL;
	m_hIdleSem = F_SEM_NULL;
	m_pThread = NULL;
	m_rc = NE_XFLM_OK;
	m_pucInBuf = NULL;
	m_puiInOffset = NULL;
	m_pucOutBuf = NULL;
	m_puiOutOffset = NULL;
	m_pucBufs[ 0] = NULL;
	m_pucBufs[ 1] = NULL;
	m_uiOffsets[ 0] = 0;
	m_uiOffsets[ 1] = 0;
	m_uiMTUSize = 0;
	m_pClient = NULL;
}

/****************************************************************************
Desc: Destructor
****************************************************************************/
F_BackerStream::~F_BackerStream( void)
{
	shutdownThreads();

	if( m_hDataSem != F_SEM_NULL)
	{
		f_semDestroy( &m_hDataSem);
	}

	if( m_hIdleSem != F_SEM_NULL)
	{
		f_semDestroy( &m_hIdleSem);
	}

	if( m_pucBufs[ 0])
	{
		f_free( &m_pucBufs[ 0]);
	}

	if( m_pucBufs[ 1])
	{
		f_free( &m_pucBufs[ 1]);
	}
}


/****************************************************************************
Desc: Start any background threads
****************************************************************************/
RCODE F_BackerStream::startThreads( void)
{
	RCODE		rc = NE_XFLM_OK;

	if( m_pThread)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_FAILURE);
		goto Exit;
	}

	// The semaphore handles better be null

	flmAssert( m_hDataSem == F_SEM_NULL);
	flmAssert( m_hIdleSem == F_SEM_NULL);

	// Create a semaphore to signal the background thread
	// that data is available

	if( RC_BAD( rc = f_semCreate( &m_hDataSem)))
	{
		goto Exit;
	}

	// Create a semaphore to signal when the background thread
	// is idle

	if( RC_BAD( rc = f_semCreate( &m_hIdleSem)))
	{
		goto Exit;
	}

	// Start the thread

	if( m_pClient)
	{
		if( RC_BAD( rc = gv_XFlmSysData.pThreadMgr->createThread( &m_pThread,
			F_BackerStream::writeThread, "backup",
			0, 0, (void *)this)))
		{
			goto Exit;
		}
	}
	else if( m_pRestoreObj)
	{
		if( RC_BAD( rc = gv_XFlmSysData.pThreadMgr->createThread( &m_pThread,
			F_BackerStream::readThread, "restore",
			0, 0, (void *)this)))
		{
			goto Exit;
		}
	}
	else
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_FAILURE);
		goto Exit;
	}

Exit:

	return( rc);
}

/****************************************************************************
Desc: Shut down any background threads
****************************************************************************/
void F_BackerStream::shutdownThreads( void)
{
	if( m_pThread)
	{
		// Shut down the background read or write thread.

		m_pThread->setShutdownFlag();
		f_semSignal( m_hDataSem);
		m_pThread->stopThread();
		m_pThread->Release();
		m_pThread = NULL;

		// Now that the thread has terminated, it is safe
		// to destroy the data and idle semaphores.

		f_semDestroy( &m_hDataSem);
		f_semDestroy( &m_hIdleSem);
	}
}

/****************************************************************************
Desc: Setup method to use the backer stream as an input stream
****************************************************************************/
RCODE F_BackerStream::setup(
	FLMUINT					uiMTUSize,
	IF_RestoreClient *	pRestoreObj)
{
	RCODE			rc = NE_XFLM_OK;

	flmAssert( pRestoreObj);
	flmAssert( !m_bSetup);

	m_pRestoreObj = pRestoreObj;
	m_uiMTUSize = uiMTUSize;

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

	// Fire up the background threads

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

	m_bSetup = TRUE;

Exit:

	return( rc);
}

/****************************************************************************
Desc: Setup method to use the backer stream as an output stream
****************************************************************************/
RCODE F_BackerStream::setup(
	FLMUINT					uiMTUSize,
	IF_BackupClient *		pClient)
{
	RCODE			rc = NE_XFLM_OK;

	flmAssert( pClient);
	flmAssert( !m_bSetup);

	m_pClient = pClient;
	m_uiMTUSize = uiMTUSize;

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

	// Fire up the background threads

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

	m_bSetup = TRUE;

Exit:

	return( rc);
}

/****************************************************************************
Desc: Performs setup operations common to read and write streams
****************************************************************************/
RCODE F_BackerStream::_setup( void)
{
	RCODE			rc = NE_XFLM_OK;

	// Allocate a buffer for reading or writing blocks

	if( (m_uiMTUSize < (2 * FLM_BACKER_MAX_DB_BLOCK_SIZE)) ||
		m_uiMTUSize % FLM_BACKER_MAX_DB_BLOCK_SIZE)
	{
		rc = RC_SET( NE_XFLM_INVALID_PARM);
		goto Exit;
	}

	// Allocate buffers for reading or writing

	if( RC_BAD( rc = f_alloc( m_uiMTUSize, &m_pucBufs[ 0])))
	{
		goto Exit;
	}

	if( RC_BAD( rc = f_alloc( m_uiMTUSize, &m_pucBufs[ 1])))
	{
		goto Exit;
	}

	m_pucInBuf = m_pucBufs[ 0];
	m_puiInOffset = &m_uiOffsets[ 0];

	m_pucOutBuf = m_pucBufs[ 1];
	m_puiOutOffset = &m_uiOffsets[ 1];

Exit:

	return( rc);
}

/****************************************************************************
Desc: Reads data from the input stream
****************************************************************************/
RCODE F_BackerStream::read(
	FLMUINT				uiLength,
	FLMBYTE *			pucData,
	FLMUINT *			puiBytesRead)
{
	FLMUINT		uiBufSize;
	FLMBYTE *	pucBuf;
	FLMUINT		uiBytesRead = 0;
	FLMUINT		uiBytesAvail;
	RCODE			rc = NE_XFLM_OK;

	flmAssert( m_bSetup);
	flmAssert( m_pRestoreObj);
	flmAssert( uiLength);

	if( m_bFirstRead)
	{
		m_bFirstRead = FALSE;

		// Prime the pump.  Call signalThread twice ... once to
		// get the first chunk of data and a second time to have
		// the background thread pre-fetch the next chunk.  A backup
		// will always have at least two MTU data chunks, so we should
		// never get an IO_END_OF_FILE error.  If we do, the restore
		// operation needs to abort (which will happen because the
		// error will be returned to the caller of this routine).

		if( RC_BAD( rc = signalThread()) ||
			RC_BAD( rc = signalThread()))
		{
			goto Exit;
		}
	}

	while( uiLength)
	{
		uiBufSize = *m_puiOutOffset;
		pucBuf = m_pucOutBuf;

		uiBytesAvail = uiBufSize - m_uiBufOffset;
		flmAssert( uiBytesAvail);

		if( uiBytesAvail < uiLength)
		{
			f_memcpy( &pucData[ uiBytesRead],
				&pucBuf[ m_uiBufOffset], uiBytesAvail);
			m_uiBufOffset += uiBytesAvail;
			uiBytesRead += uiBytesAvail;
			uiLength -= uiBytesAvail;
		}
		else
		{
			f_memcpy( &pucData[ uiBytesRead],
				&pucBuf[ m_uiBufOffset], uiLength);
			m_uiBufOffset += uiLength;
			uiBytesRead += uiLength;
			uiLength = 0;
		}

		if( m_uiBufOffset == uiBufSize)
		{
			m_uiBufOffset = 0;
			if( RC_BAD( rc = signalThread()))
			{
				// Since we are reading MTU-sized units and the restore
				// code knows when to stop reading, we should never
				// get an IO_END_OF_FILE error back from a call to
				// signalThread().  If we do, we need to return the
				// error to the caller (FlmDbRestore).
				goto Exit;
			}
		}
	}

Exit:

	if( puiBytesRead)
	{
		*puiBytesRead = uiBytesRead;
	}
	m_ui64ByteCount += (FLMUINT64)uiBytesRead;
	return( rc);
}

/****************************************************************************
Desc:		Writes data to the output stream
****************************************************************************/
RCODE F_BackerStream::write(
	FLMUINT				uiLength,
	FLMBYTE *			pucData,
	FLMUINT *			puiBytesWritten)
{
	FLMUINT		uiMaxWriteSize;
	FLMUINT		uiBytesWritten = 0;
	RCODE			rc = NE_XFLM_OK;

	flmAssert( m_bSetup);
	flmAssert( m_pClient);
	flmAssert( uiLength);

	while( uiLength)
	{
		uiMaxWriteSize = m_uiMTUSize - *m_puiInOffset;
		flmAssert( uiMaxWriteSize);

		if( uiMaxWriteSize < uiLength)
		{
			f_memcpy( &m_pucInBuf[ *m_puiInOffset],
				&pucData[ uiBytesWritten], uiMaxWriteSize);
			(*m_puiInOffset) += uiMaxWriteSize;
			uiBytesWritten += uiMaxWriteSize;
			uiLength -= uiMaxWriteSize;
		}
		else
		{
			f_memcpy( &m_pucInBuf[ *m_puiInOffset],
				&pucData[ uiBytesWritten], uiLength);
			(*m_puiInOffset) += uiLength;
			uiBytesWritten += uiLength;
			uiLength = 0;
		}

		if( (*m_puiInOffset) == m_uiMTUSize)
		{
			if( RC_BAD( rc = signalThread()))
			{
				goto Exit;
			}
		}
	}

Exit:

	if( puiBytesWritten)
	{
		*puiBytesWritten = uiBytesWritten;
	}

	m_ui64ByteCount += (FLMUINT64)uiBytesWritten;
	return( rc);
}

/****************************************************************************
Desc: Flushes any pending writes to the output stream
****************************************************************************/
RCODE F_BackerStream::flush( void)
{
	RCODE			rc = NE_XFLM_OK;

	flmAssert( m_bSetup);

	if( m_pClient && m_pThread)
	{
		if( *m_puiInOffset)
		{
			if( RC_BAD( rc = signalThread()))
			{
				goto Exit;
			}
		}

		// Wait for the background thread to become idle.  When it
		// does, we know that all writes have completed.

		if( RC_BAD( rc = f_semWait( m_hIdleSem, F_WAITFOREVER)))
		{
			goto Exit;
		}

		// If the background thread set an error code, we need to return it.

		rc = m_rc;

		// At this point, we know the background thread is either waiting
		// for the data semaphore to be signaled or it has exited due to
		// an error.  We need to re-signal the idle semaphore so that
		// other F_BackerStream calls (i.e., additional calls to
		// flush, etc.) will not block waiting for it to be signaled
		// since it won't be signaled by the background thread until
		// after the data semaphore has been signaled again.

		f_semSignal( m_hIdleSem);

		// Jump to exit if we have a bad rc

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

Exit:

	return( rc);
}

/****************************************************************************
Desc:	Signals the read or write thread indicating that data is needed or
		that data is available.
****************************************************************************/
RCODE F_BackerStream::signalThread( void)
{
	FLMBYTE *	pucTmp;
	FLMUINT *	puiTmp;
	RCODE			rc = NE_XFLM_OK;

	flmAssert( m_bSetup);

	// Return an error if we don't have a thread.

	if( !m_pThread)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_FAILURE);
		goto Exit;
	}

	// Wait for the thread to become idle

	if( RC_BAD( rc = f_semWait( m_hIdleSem, F_WAITFOREVER)))
	{
		goto Exit;
	}

	if( RC_BAD( rc = m_rc))
	{
		// If m_rc is bad, we know that the background thread has
		// exited and will not signal the idle semaphore again.
		// Thus, we will re-signal the idle semaphore so that if the
		// code using this class happens to call flush() or some
		// other method that waits on the idle semaphore, we
		// won't wait forever on something that will never happen.

		f_semSignal( m_hIdleSem);

		// Check the error code

		if( rc == NE_FLM_IO_END_OF_FILE && !m_bFinalRead)
		{
			m_bFinalRead = TRUE;
		}
		else
		{
			goto Exit;
		}
	}

	pucTmp = m_pucOutBuf;
	puiTmp = m_puiOutOffset;

	m_pucOutBuf = m_pucInBuf;
	m_puiOutOffset = m_puiInOffset;

	m_pucInBuf = pucTmp;
	m_puiInOffset = puiTmp;

	*(m_puiInOffset) = 0;

	if( !m_bFinalRead)
	{
		// Signal the thread to read or write data

		f_semSignal( m_hDataSem);
	}

Exit:

	return( rc);
}

/****************************************************************************
Desc: This thread reads data in the background
****************************************************************************/
RCODE FLMAPI F_BackerStream::readThread(
	IF_Thread *			pThread)
{
	F_BackerStream *	pBackerStream = (F_BackerStream *)pThread->getParm1();
	RCODE					rc = NE_XFLM_OK;

	for( ;;)
	{
		f_semSignal( pBackerStream->m_hIdleSem);

		if( RC_BAD( rc = f_semWait( pBackerStream->m_hDataSem,
			F_WAITFOREVER)))
		{
			goto Exit;
		}

		if( pThread->getShutdownFlag())
		{
			break;
		}

		if( RC_BAD( rc = pBackerStream->m_pRestoreObj->read(
			pBackerStream->m_uiMTUSize, pBackerStream->m_pucInBuf,
			pBackerStream->m_puiInOffset)))
		{
			goto Exit;
		}
	}

Exit:

	pBackerStream->m_rc = rc;
	f_semSignal( pBackerStream->m_hIdleSem);
	return( rc);
}

/****************************************************************************
Desc: This thread writes data in the background
****************************************************************************/
RCODE FLMAPI F_BackerStream::writeThread(
	IF_Thread *			pThread)
{
	F_BackerStream *	pBackerStream = (F_BackerStream *)pThread->getParm1();
	RCODE					rc = NE_XFLM_OK;

	for( ;;)
	{
		f_semSignal( pBackerStream->m_hIdleSem);

		if( RC_BAD( rc = f_semWait( pBackerStream->m_hDataSem,
			F_WAITFOREVER)))
		{
			goto Exit;
		}

		if( *(pBackerStream->m_puiOutOffset))
		{
			if( RC_BAD( rc = pBackerStream->m_pClient->WriteData(
				pBackerStream->m_pucOutBuf, *(pBackerStream->m_puiOutOffset))))
			{
				goto Exit;
			}

			// Reset *puiOutOffset so that we won't re-write
			// the same data again if we receive a shut-down
			// signal.

			*(pBackerStream->m_puiOutOffset) = 0;
		}

		// Need to put the thread shutdown check here
		// so that if the thread is signaled twice,
		// once to do final work and once to shut down,
		// we will actually do the work before we exit.

		if( pThread->getShutdownFlag())
		{
			break;
		}
	}

Exit:

	pBackerStream->m_rc = rc;
	f_semSignal( pBackerStream->m_hIdleSem);
	return( rc);
}
