Files
mars-flaim/flaim/src/flbackup.cpp
ahodgkinson b8a3f28938 FLAIM change. Fixed misaligned I/O operations.
git-svn-id: https://svn.code.sf.net/p/flaim/code/trunk@672 0109f412-320b-0410-ab79-c3e0c5ffbbe6
2006-07-14 22:30:57 +00:00

3173 lines
69 KiB
C++

//-------------------------------------------------------------------------
// Desc: Backup and restore routines.
// Tabs: 3
//
// Copyright (c) 2000-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 12334 2006-01-23 12:45:35 -0700 (Mon, 23 Jan 2006) dsanders $
//-------------------------------------------------------------------------
#include "flaimsys.h"
typedef struct
{
char szPath[ F_PATH_MAX_SIZE];
IF_MultiFileHdl * pMultiFileHdl;
FLMUINT64 ui64Offset;
void * pvAppData;
RCODE rc;
} BACKER_HOOK_STATE;
#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_1_0_1 101
#define FLM_BACKER_VERSION FLM_BACKER_VERSION_1_0_1
#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) 8)
#define FLM_BACKER_BLK_ADDR_OFFSET 0
#define FLM_BACKER_BLK_SIZE_OFFSET 4
FSTATIC RCODE flmDefaultBackerWriteHook(
void * pvBuffer,
FLMUINT uiBytesToWrite,
void * pvCallbackData);
FSTATIC RCODE flmRestoreFile(
F_Restore * pRestoreObj,
const char * pszDbPath,
const char * pszDataDir,
const char * pszPassword,
F_SuperFileHdl ** ppSFile,
FLMBOOL bIncremental,
FLMUINT * puiDbVersion,
FLMUINT * puiNextIncSeqNum,
FLMBOOL * pbRflPreserved,
eRestoreActionType * peRestoreAction,
FLMBOOL * pbOKToRetry,
FLMBYTE ** ppucKeyToSave,
FLMBYTE * pucKeyToUse,
FLMUINT * puiKeyLen);
/*******************************************************************************
Desc:
*******************************************************************************/
class F_BackerStream : public F_Object
{
public:
F_BackerStream( void);
~F_BackerStream( void);
RCODE setup(
FLMUINT uiMTUSize,
F_Restore * pRestoreObj);
RCODE setup(
FLMUINT uiMTUSize,
BACKER_WRITE_HOOK fnWrite,
void * pvCallbackData);
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;
FLMUINT m_uiBufOffset;
FLMUINT64 m_ui64ByteCount;
F_Restore * 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;
FLMUINT m_uiPendingIO;
BACKER_WRITE_HOOK m_fnWrite;
void * m_pvCallbackData;
};
/*******************************************************************************
Desc: Prepares FLAIM to backup a database.
Notes: Only one backup of a particular database can be active at any time
*******************************************************************************/
FLMEXP RCODE FLMAPI FlmDbBackupBegin(
HFDB hDb,
FBackupType eBackupType,
FLMBOOL bHotBackup,
HFBACKUP * phBackup
)
{
FDB * pDb = (FDB *)hDb;
FBak * pFBak = NULL;
FLMBOOL bBackupFlagSet = FALSE;
FLMUINT uiLastCPFileNum;
FLMUINT uiLastTransFileNum;
FLMUINT uiDbVersion;
FLMUINT uiTmp;
FLMBYTE * pLogHdr;
RCODE rc = FERR_OK;
FLMUINT uiTransType = bHotBackup ? FLM_READ_TRANS : FLM_UPDATE_TRANS;
// Initialize the handle
*phBackup = HFBACKUP_NULL;
// Make sure we are not being called inside a transaction
if( RC_BAD( rc = FlmDbGetTransType( hDb, &uiTmp)))
{
goto Exit;
}
if( uiTmp != FLM_NO_TRANS)
{
rc = RC_SET( FERR_TRANS_ACTIVE);
goto Exit;
}
// Make sure a valid backup type has been specified
if( RC_BAD( rc = FlmDbGetConfig( hDb,
FDB_GET_VERSION, (FLMUINT *)&uiDbVersion)))
{
goto Exit;
}
if( uiDbVersion < FLM_FILE_FORMAT_VER_4_3 &&
eBackupType != FLM_FULL_BACKUP)
{
rc = RC_SET( FERR_NOT_IMPLEMENTED);
goto Exit;
}
// See if a backup is currently running against the database. If so,
// return an error. Otherwise, set the backup flag on the FFILE.
if( !IsInCSMode( hDb))
{
f_mutexLock( gv_FlmSysData.hShareMutex);
if( pDb->pFile->bBackupActive)
{
f_mutexUnlock( gv_FlmSysData.hShareMutex);
rc = RC_SET( FERR_BACKUP_ACTIVE);
goto Exit;
}
else
{
bBackupFlagSet = TRUE;
pDb->pFile->bBackupActive = TRUE;
}
f_mutexUnlock( gv_FlmSysData.hShareMutex);
}
else
{
if( RC_BAD( rc = fcsSetBackupActiveFlag( hDb, TRUE)))
{
goto Exit;
}
bBackupFlagSet = TRUE;
}
// Allocate the backup handle
if( RC_BAD( rc = f_calloc( sizeof( FBak), &pFBak)))
{
goto Exit;
}
if( RC_BAD( rc = f_allocAlignedBuffer( 2048, &pFBak->pucDbHeader)))
{
goto Exit;
}
pFBak->hDb = hDb;
pFBak->uiDbVersion = uiDbVersion;
// Set the C/S mode flag
pFBak->bCSMode = IsInCSMode( hDb);
// Start a transaction
if( RC_BAD( rc = FlmDbTransBegin( hDb,
uiTransType | FLM_DONT_KILL_TRANS | FLM_DONT_POISON_CACHE,
FLM_NO_TIMEOUT, pFBak->pucDbHeader)))
{
goto Exit;
}
pFBak->bTransStarted = TRUE;
pFBak->uiTransType = uiTransType;
pLogHdr = &pFBak->pucDbHeader[ DB_LOG_HEADER_START];
// Don't allow an incremental backup to be performed
// if a full backup has not yet been done.
if( eBackupType == FLM_INCREMENTAL_BACKUP &&
FB2UD( &pLogHdr[ LOG_LAST_BACKUP_TRANS_ID]) == 0)
{
rc = RC_SET( FERR_ILLEGAL_OP);
goto Exit;
}
pFBak->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( uiDbVersion >= FLM_FILE_FORMAT_VER_4_3)
{
if( !pFBak->bCSMode)
{
if( RC_BAD( rc = f_createSerialNumber(
pFBak->ucNextIncSerialNum)))
{
goto Exit;
}
}
else
{
fdbInitCS( pDb);
rc = fcsCreateSerialNumber(
pDb->pCSContext, pFBak->ucNextIncSerialNum);
fdbExit( pDb);
if( RC_BAD( rc))
{
goto Exit;
}
}
}
// Get the incremental sequence number from the log header
pFBak->uiIncSeqNum = FB2UD( &pLogHdr[ LOG_INC_BACKUP_SEQ_NUM]);
// Get version 4.3+ values from the header
if( uiDbVersion >= FLM_FILE_FORMAT_VER_4_3)
{
// Determine the transaction ID of the last backup
pFBak->uiLastBackupTransId =
FB2UD( &pLogHdr[ LOG_LAST_BACKUP_TRANS_ID]);
// Get the block change count
pFBak->uiBlkChgSinceLastBackup =
FB2UD( &pLogHdr[ LOG_BLK_CHG_SINCE_BACKUP]);
}
// Get the current transaction ID
if( RC_BAD( rc = FlmDbGetConfig( hDb, FDB_GET_TRANS_ID,
(void *)&pFBak->uiTransId)))
{
goto Exit;
}
// Get the logical end of file
pFBak->uiLogicalEOF = FB2UD( &pLogHdr[ LOG_LOGICAL_EOF]);
// Get the first required RFL file needed by the restore.
uiLastCPFileNum = FB2UD( &pLogHdr[ LOG_RFL_LAST_CP_FILE_NUM]);
uiLastTransFileNum = FB2UD( &pLogHdr[ LOG_RFL_FILE_NUM]);
flmAssert( uiLastCPFileNum <= uiLastTransFileNum);
pFBak->uiFirstReqRfl = uiLastCPFileNum < uiLastTransFileNum
? uiLastCPFileNum
: uiLastTransFileNum;
flmAssert( pFBak->uiFirstReqRfl);
// Get the database block size
if( RC_BAD( rc = FlmDbGetConfig( hDb, FDB_GET_BLKSIZ,
&pFBak->uiBlockSize)))
{
goto Exit;
}
// Get the database path
if( RC_BAD( rc = FlmDbGetConfig( hDb,
FDB_GET_PATH, pFBak->ucDbPath)))
{
goto Exit;
}
*phBackup = pFBak;
Exit:
if( RC_BAD( rc))
{
if( pFBak)
{
if( pFBak->bTransStarted)
{
(void)FlmDbTransAbort( hDb);
}
if( pFBak->pucDbHeader)
{
f_freeAlignedBuffer( &pFBak->pucDbHeader);
}
f_free( &pFBak);
}
if( bBackupFlagSet)
{
if( !IsInCSMode( hDb))
{
f_mutexLock( gv_FlmSysData.hShareMutex);
pDb->pFile->bBackupActive = FALSE;
f_mutexUnlock( gv_FlmSysData.hShareMutex);
}
else
{
(void)fcsSetBackupActiveFlag( hDb, FALSE);
}
}
}
return( rc);
}
/****************************************************************************
Desc : Returns information about a backup
****************************************************************************/
FLMEXP RCODE FLMAPI FlmBackupGetConfig(
HFBACKUP hBackup,
eBackupGetConfigType eConfigType,
void * pvValue1,
void * // pvValue2
)
{
RCODE rc = FERR_OK;
FBak * pFBak = (FBak *)hBackup;
switch( eConfigType)
{
case FBAK_GET_BACKUP_TRANS_ID:
{
*((FLMUINT *)pvValue1) = pFBak->uiTransId;
break;
}
case FBAK_GET_LAST_BACKUP_TRANS_ID:
{
*((FLMUINT *)pvValue1) = pFBak->uiLastBackupTransId;
break;
}
default:
{
rc = RC_SET( FERR_NOT_IMPLEMENTED);
goto Exit;
}
}
Exit:
return( rc);
}
/****************************************************************************
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.
****************************************************************************/
FLMEXP RCODE FLMAPI FlmDbBackup(
HFBACKUP hBackup,
const char * pszBackupPath,
const char * pszPassword,
BACKER_WRITE_HOOK fnWrite,
STATUS_HOOK fnStatus,
void * pvAppData,
FLMUINT * puiIncSeqNum)
{
FDB * pDb = NULL;
FLMBOOL bDbInitialized = FALSE;
FLMBOOL bFullBackup = TRUE;
FLMINT iFileNum;
FLMUINT uiBlkAddr;
FLMUINT uiTime;
SCACHE * pSCache = NULL;
BACKER_HOOK_STATE hookState;
FLMBYTE * pLogHdr;
DB_BACKUP_INFO backupInfo;
FLMUINT uiBlockFileOffset;
FLMUINT uiActualBlkSize;
FLMUINT uiCount;
FLMUINT uiBlockCount;
FLMUINT uiBlockCountLastCB = 0;
FLMUINT uiBytesToPad;
void * pvCallbackData;
FBak * pFBak = (FBak *)hBackup;
FLMUINT uiBlockSize = pFBak->uiBlockSize;
F_BackerStream * pBackerStream = NULL;
FLMBYTE * pucBlkBuf = NULL;
FLMUINT uiBlkBufOffset;
FLMUINT uiBlkBufSize;
FLMUINT uiMaxCSBlocks;
FLMUINT uiCPTransOffset;
FLMUINT uiMaxFileSize;
RCODE rc = FERR_OK;
F_CCS * pDbKey;
#ifndef FLM_USE_NICI
F_UNREFERENCED_PARM( pszPassword);
#endif
pDb = (FDB *)(pFBak->hDb);
if( puiIncSeqNum)
{
*puiIncSeqNum = 0;
}
f_memset( &hookState, 0, sizeof( BACKER_HOOK_STATE));
// Make sure a backup attempt has not been made with this
// backup handle.
if( pFBak->bCompletedBackup)
{
rc = RC_SET( FERR_FAILURE);
goto Exit;
}
if( RC_BAD( pFBak->backupRc))
{
rc = pFBak->backupRc;
goto Exit;
}
// Look at the backup type
if( pFBak->eBackupType == FLM_INCREMENTAL_BACKUP)
{
if( puiIncSeqNum)
{
*puiIncSeqNum = pFBak->uiIncSeqNum;
}
bFullBackup = FALSE;
}
// Set up the callback
if( !fnWrite)
{
fnWrite = flmDefaultBackerWriteHook;
}
if( fnWrite == flmDefaultBackerWriteHook)
{
if( !pszBackupPath)
{
rc = RC_SET( FERR_INVALID_PARM);
goto Exit;
}
f_strcpy( hookState.szPath, pszBackupPath);
hookState.pvAppData = pvAppData;
pvCallbackData = &hookState;
}
else
{
pvCallbackData = pvAppData;
}
// Allocate and initialize the backer stream object
if( (pBackerStream = f_new F_BackerStream) == NULL)
{
rc = RC_SET( FERR_MEM);
goto Exit;
}
if( RC_BAD( rc = pBackerStream->setup( FLM_BACKER_MTU_SIZE,
fnWrite, pvCallbackData)))
{
goto Exit;
}
// Allocate a temporary buffer
uiBlkBufSize = FLM_BACKER_MTU_SIZE;
uiMaxCSBlocks = uiBlkBufSize / uiBlockSize;
if( RC_BAD( rc = f_alloc( uiBlkBufSize, &pucBlkBuf)))
{
goto Exit;
}
// Setup the status callback info
f_memset( &backupInfo, 0, sizeof( DB_BACKUP_INFO));
// Setup the backup file header
uiBlkBufOffset = 0;
f_memset( pucBlkBuf, 0, 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)uiBlockSize,
&pucBlkBuf[ FLM_BACKER_DB_BLOCK_SIZE_OFFSET]);
uiMaxFileSize = flmGetMaxFileSize( pFBak->uiDbVersion,
&pFBak->pucDbHeader [DB_LOG_HEADER_START]);
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( (const char *)pFBak->ucDbPath);
if( uiCount <= 3)
{
pucBlkBuf[ FLM_BACKER_DB_NAME_OFFSET] =
pFBak->ucDbPath[ uiCount - 6];
pucBlkBuf[ FLM_BACKER_DB_NAME_OFFSET + 1] =
pFBak->ucDbPath[ uiCount - 5];
pucBlkBuf[ FLM_BACKER_DB_NAME_OFFSET + 2] =
pFBak->ucDbPath[ uiCount - 4];
pucBlkBuf[ FLM_BACKER_DB_NAME_OFFSET + 3] = '\0';
}
UD2FBA( (FLMUINT32)pFBak->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],
pFBak->ucNextIncSerialNum, F_SERIAL_NUM_SIZE);
// Set the database version number
UD2FBA( (FLMUINT32)pFBak->uiDbVersion,
&pucBlkBuf[ FLM_BACKER_DB_VERSION]);
uiBlkBufOffset += uiBlockSize;
// Copy the database header into the backup's buffer
f_memset( &pucBlkBuf[ uiBlkBufOffset], 0, uiBlockSize);
f_memcpy( &pucBlkBuf[ uiBlkBufOffset],
pFBak->pucDbHeader, F_TRANS_HEADER_SIZE);
pLogHdr = &pucBlkBuf[ uiBlkBufOffset + DB_LOG_HEADER_START];
uiBlkBufOffset += uiBlockSize;
// Fix up the log header
if( !pLogHdr[ LOG_KEEP_RFL_FILES] || pFBak->uiDbVersion < FLM_FILE_FORMAT_VER_4_3)
{
pLogHdr[ LOG_KEEP_RFL_FILES] = 0;
// 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.
UD2FBA( (FLMUINT32)0, &pLogHdr[ LOG_RFL_LAST_TRANS_OFFSET]);
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( pFBak->uiDbVersion >= FLM_FILE_FORMAT_VER_4_3)
{
if (RC_BAD( rc = f_createSerialNumber(
&pLogHdr [LOG_LAST_TRANS_RFL_SERIAL_NUM])))
{
goto Exit;
}
if (RC_BAD( rc = f_createSerialNumber(
&pLogHdr [LOG_RFL_NEXT_SERIAL_NUM])))
{
goto Exit;
}
}
}
else
{
uiCPTransOffset = FB2UD( &pLogHdr[ LOG_RFL_LAST_TRANS_OFFSET]);
if( !uiCPTransOffset)
{
uiCPTransOffset = 512;
}
}
// Shroud the database key (stored in the log header) in the password
// so we can restore this backup to a different server
if ( pDb->pFile->FileHdr.uiVersionNum >= FLM_FILE_FORMAT_VER_4_60 &&
pszPassword && *pszPassword &&
FB2UW( &pLogHdr[ LOG_DATABASE_KEY_LEN]) > 0)
{
FLMBYTE * pucTmpBuf = NULL;
FLMUINT32 ui32KeyLen = 0;
pDbKey = pDb->pFile->pDbWrappingKey;
if( RC_BAD( rc = pDbKey->getKeyToStore( &pucTmpBuf, &ui32KeyLen,
pszPassword, NULL, FALSE)))
{
goto Exit;
}
// Verify that the field in the log header is long enough to
// hold the key.
if( ui32KeyLen > FLM_MAX_DB_ENC_KEY_LEN)
{
rc = RC_SET_AND_ASSERT( FERR_BAD_ENC_KEY);
goto Exit;
}
// Verify that the field in the log header is long enough to
// hold the key.
if( ui32KeyLen > FLM_MAX_DB_ENC_KEY_LEN)
{
f_free( &pucTmpBuf);
rc = RC_SET_AND_ASSERT( FERR_BAD_ENC_KEY);
goto Exit;
}
UW2FBA( ui32KeyLen, &pLogHdr[ LOG_DATABASE_KEY_LEN]);
f_memcpy( &pLogHdr[ LOG_DATABASE_KEY], pucTmpBuf, ui32KeyLen);
f_free( &pucTmpBuf);
}
// 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.
f_memcpy( &pLogHdr[ LOG_RFL_LAST_CP_FILE_NUM],
&pLogHdr[ LOG_RFL_FILE_NUM], 4);
f_memcpy( &pLogHdr[ LOG_LAST_CP_TRANS_ID],
&pLogHdr[ LOG_CURR_TRANS_ID], 4);
UD2FBA( (FLMUINT32)uiCPTransOffset, &pLogHdr[ LOG_RFL_LAST_CP_OFFSET]);
UD2FBA( (FLMUINT32) uiBlockSize, &pLogHdr[ LOG_ROLLBACK_EOF]);
UD2FBA( 0, &pLogHdr[ LOG_PL_FIRST_CP_BLOCK_ADDR]);
// Compute the log header checksum
UW2FBA( (FLMUINT16)lgHdrCheckSum( pLogHdr, FALSE),
&pLogHdr[ LOG_HDR_CHECKSUM]);
// 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,
pFBak->uiLogicalEOF);
// Initialize the FDB
pDb = (FDB *)(pFBak->hDb);
if( !pFBak->bCSMode)
{
FLMBOOL bDummy;
bDbInitialized = TRUE;
if( RC_BAD( rc = fdbInit( pDb, FLM_NO_TRANS,
FDB_TRANS_GOING_OK, 0, &bDummy)))
{
goto Exit;
}
flmAssert( !bDummy);
}
else
{
bDbInitialized = TRUE;
fdbInitCS( pDb);
}
uiBlockFileOffset = 0;
uiBlockCount = 0;
iFileNum = 1;
for( ;;)
{
if( uiBlockFileOffset >= uiMaxFileSize)
{
uiBlockFileOffset = 0;
iFileNum++;
}
uiBlkAddr = FSBlkAddress( iFileNum, uiBlockFileOffset);
if( !FSAddrIsBelow( uiBlkAddr, pFBak->uiLogicalEOF))
{
break;
}
if( !pFBak->bCSMode)
{
// Get the block
if( RC_BAD( rc = ScaGetBlock( pDb, NULL, BHT_FREE,
uiBlkAddr, NULL, &pSCache)))
{
goto Exit;
}
if( bFullBackup ||
FB2UD( &pSCache->pucBlk[ BH_TRANS_ID]) >
pFBak->uiLastBackupTransId)
{
uiBlkBufOffset = 0;
uiActualBlkSize = getEncryptSize( pSCache->pucBlk);
if( uiActualBlkSize < BH_OVHD)
{
flmAssert( 0);
rc = RC_SET( FERR_DATA_ERROR);
goto Exit;
}
// Output the backup header for the block
UD2FBA( (FLMUINT32)uiBlkAddr,
&pucBlkBuf[ FLM_BACKER_BLK_ADDR_OFFSET]);
UD2FBA( (FLMUINT32)uiActualBlkSize,
&pucBlkBuf[ FLM_BACKER_BLK_SIZE_OFFSET]);
uiBlkBufOffset += FLM_BACKER_BLK_HDR_SIZE;
// Copy the block into the block buffer and compute the checksum
f_memcpy( &pucBlkBuf[ uiBlkBufOffset],
pSCache->pucBlk, uiActualBlkSize);
// If this is an encrypted block, we need to make sure it gets encrypted.
if ( pucBlkBuf[ BH_ENCRYPTED + uiBlkBufOffset])
{
if (RC_BAD( rc = ScaEncryptBlock( pSCache->pFile,
&pucBlkBuf[uiBlkBufOffset],
uiActualBlkSize,
pSCache->pFile->
FileHdr.uiBlockSize)))
{
goto Exit;
}
}
BlkCheckSum( &pucBlkBuf[ uiBlkBufOffset],
CHECKSUM_SET, uiBlkAddr, uiBlockSize);
uiBlkBufOffset += uiActualBlkSize;
// Write the block to the backup stream
if( RC_BAD( rc = pBackerStream->write(
uiBlkBufOffset, pucBlkBuf)))
{
goto Exit;
}
uiBlockCount++;
}
ScaReleaseCache( pSCache, FALSE);
pSCache = NULL;
uiBlockFileOffset += uiBlockSize;
}
else
{
rc = RC_SET( FERR_ILLEGAL_OP);
goto Exit;
}
// Call the status callback
if( fnStatus && (uiBlockCount - uiBlockCountLastCB) > 100)
{
backupInfo.ui64BytesDone = FSGetSizeInBytes( uiMaxFileSize,
uiBlkAddr);
if( RC_BAD( rc = fnStatus( FLM_DB_BACKUP_STATUS,
(void *)&backupInfo, NULL, pvAppData)))
{
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() -
(FLMUINT)(pBackerStream->getByteCount() %
(FLMUINT64)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( bDbInitialized)
{
fdbExit( pDb);
}
if( pBackerStream)
{
pBackerStream->Release();
}
// Call the status callback now that the background
// thread has terminated.
if( RC_OK( rc) && fnStatus)
{
backupInfo.ui64BytesDone = backupInfo.ui64BytesToDo;
(void)fnStatus( FLM_DB_BACKUP_STATUS,
(void *)&backupInfo, NULL, pvAppData);
}
if( hookState.pMultiFileHdl)
{
hookState.pMultiFileHdl->Release();
}
if( pucBlkBuf)
{
f_free( &pucBlkBuf);
}
if( RC_OK( rc))
{
pFBak->bCompletedBackup = TRUE;
}
pFBak->backupRc = rc;
return( rc);
}
/****************************************************************************
Desc : Ends the backup, updating the log header if needed.
****************************************************************************/
FLMEXP RCODE FLMAPI FlmDbBackupEnd(
HFBACKUP * phBackup)
{
RCODE rc = FERR_OK;
FBak * pFBak = (FBak *)*phBackup;
FDB * pDb = (FDB *)pFBak->hDb;
FLMBOOL bDbInitialized = FALSE;
FLMBOOL bStartedTrans = FALSE;
FLMBYTE * pLogHdr = NULL;
// End the transaction
flmAssert( pFBak->uiTransType != FLM_NO_TRANS);
if( RC_BAD( rc = FlmDbTransAbort( (HFDB)pDb)))
{
goto Exit;
}
pFBak->uiTransType = FLM_NO_TRANS;
pFBak->bTransStarted = FALSE;
// Update log header fields
if( pFBak->bCompletedBackup &&
pFBak->uiDbVersion >= FLM_FILE_FORMAT_VER_4_3)
{
// Start an update transaction.
if( !pFBak->bCSMode)
{
bDbInitialized = TRUE;
if (RC_BAD( rc = fdbInit( pDb, FLM_UPDATE_TRANS,
0, FLM_NO_TIMEOUT | FLM_AUTO_TRANS, &bStartedTrans)))
{
goto Exit;
}
}
else
{
if( RC_BAD( rc = FlmDbTransBegin( (HFDB)pDb,
FLM_UPDATE_TRANS, FLM_NO_TIMEOUT, pFBak->pucDbHeader)))
{
goto Exit;
}
pLogHdr = &pFBak->pucDbHeader[ DB_LOG_HEADER_START];
bStartedTrans = TRUE;
}
// Update the log header fields.
if( !pFBak->bCSMode)
{
UD2FBA( (FLMUINT32)pFBak->uiTransId,
&pDb->pFile->ucUncommittedLogHdr [LOG_LAST_BACKUP_TRANS_ID]);
}
else
{
UD2FBA( (FLMUINT32)pFBak->uiTransId,
&pLogHdr[ LOG_LAST_BACKUP_TRANS_ID]);
}
// 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 LOG_BLK_CHG_SINCE_BACKUP
// statistic.
if( !pFBak->bCSMode)
{
flmDecrUint(
&pDb->pFile->ucUncommittedLogHdr [LOG_BLK_CHG_SINCE_BACKUP],
pFBak->uiBlkChgSinceLastBackup);
}
else
{
flmDecrUint(
&pLogHdr [LOG_BLK_CHG_SINCE_BACKUP],
pFBak->uiBlkChgSinceLastBackup);
}
// Bump the incremental backup sequence number
if( pFBak->eBackupType == FLM_INCREMENTAL_BACKUP)
{
if( !pFBak->bCSMode)
{
flmIncrUint(
&pDb->pFile->ucUncommittedLogHdr [LOG_INC_BACKUP_SEQ_NUM], 1);
}
else
{
flmIncrUint( &pLogHdr [LOG_INC_BACKUP_SEQ_NUM], 1);
}
}
// 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.
if( !pFBak->bCSMode)
{
f_memcpy(
&pDb->pFile->ucUncommittedLogHdr [LOG_INC_BACKUP_SERIAL_NUM],
pFBak->ucNextIncSerialNum, F_SERIAL_NUM_SIZE);
}
else
{
f_memcpy( &pLogHdr[ LOG_INC_BACKUP_SERIAL_NUM],
pFBak->ucNextIncSerialNum, F_SERIAL_NUM_SIZE);
}
// Commit the transaction and perform a checkpoint so that the
// modified log header values will be written.
if( !pFBak->bCSMode)
{
if (RC_BAD( rc = flmCommitDbTrans( pDb, 0, TRUE)))
{
goto Exit;
}
bStartedTrans = FALSE;
}
else
{
if( RC_BAD( rc = fcsDbTransCommitEx( (HFDB)pDb, TRUE,
pFBak->pucDbHeader)))
{
goto Exit;
}
bStartedTrans = FALSE;
}
}
Exit:
// Abort the active transaction (if any)
if( bStartedTrans)
{
if( !pFBak->bCSMode)
{
flmAbortDbTrans( pDb);
}
else
{
FlmDbTransAbort( (HFDB)pDb);
}
}
// Release the FDB
if( bDbInitialized)
{
fdbExit( pDb);
}
// Free the backup handle
if( pFBak->pucDbHeader)
{
f_freeAlignedBuffer( &pFBak->pucDbHeader);
}
f_free( &pFBak);
// Clear the handle
*phBackup = HFBACKUP_NULL;
// Unset the backup flag
if( !IsInCSMode( (HFDB)pDb))
{
f_mutexLock( gv_FlmSysData.hShareMutex);
pDb->pFile->bBackupActive = FALSE;
f_mutexUnlock( gv_FlmSysData.hShareMutex);
}
else
{
(void)fcsSetBackupActiveFlag( (HFDB)pDb, FALSE);
}
return( rc);
}
/****************************************************************************
Desc: Restores a database and supporting files.
****************************************************************************/
FLMEXP RCODE FLMAPI FlmDbRestore(
const char * pszDbPath,
const char * pszDataDir,
const char * pszBackupPath,
const char * pszRflDir,
const char * pszPassword,
F_Restore * pRestoreObj)
{
RCODE rc = FERR_OK;
HFDB hDb = HFDB_NULL;
IF_FileHdl * pFileHdl = NULL;
IF_FileHdl * pLockFileHdl = NULL;
F_SuperFileHdl * pSFile = NULL;
char szBasePath[ F_PATH_MAX_SIZE];
char szTmpPath[ F_PATH_MAX_SIZE];
FLMUINT uiDbVersion;
FLMUINT uiNextIncNum;
eRestoreActionType eRestoreAction;
FLMBOOL bRflPreserved;
FLMBOOL bMutexLocked = FALSE;
FFILE * pFile = NULL;
F_FSRestore * pFSRestoreObj = NULL;
FLMBOOL bOKToRetry;
FLMBYTE * pucDbKey = NULL;
FLMUINT uiKeyLen = 0;
char * pszTempPassword = NULL;
// Set up the callback
if( !pRestoreObj)
{
if( (pFSRestoreObj = f_new F_FSRestore) == NULL)
{
rc = RC_SET( FERR_MEM);
goto Exit;
}
if( RC_BAD( rc = pFSRestoreObj->setup( pszDbPath,
pszBackupPath, pszRflDir)))
{
goto Exit;
}
pRestoreObj = pFSRestoreObj;
}
// Get the base path
flmGetDbBasePath( szBasePath, pszDbPath, NULL);
// Force the file to close if it is not used by another thread
(void)FlmConfig( FLM_CLOSE_FILE, (void *)pszDbPath,
(void *)pszDataDir);
gv_FlmSysData.pFileHdlCache->closeUnusedFiles();
// Lock the global mutex
f_mutexLock( gv_FlmSysData.hShareMutex);
bMutexLocked = TRUE;
// Free any unused structures that have been unused for the maximum
// amount of time. May unlock and re-lock the global mutex.
flmCheckNUStructs( 0);
// Look up the file using flmFindFile to see if the file is already open.
// May unlock and re-lock the global mutex..
if( RC_BAD( rc = flmFindFile( pszDbPath, pszDataDir, &pFile)))
{
goto Exit;
}
// If the file is open, we cannot perform a restore
if( pFile)
{
rc = RC_SET( FERR_ACCESS_DENIED);
pFile = NULL;
f_mutexUnlock( gv_FlmSysData.hShareMutex);
bMutexLocked = FALSE;
goto Exit;
}
// Allocate the FFILE. This will prevent other threads from opening the
// database while the restore is being performed.
if( RC_BAD( rc = flmAllocFile( pszDbPath, pszDataDir, NULL, &pFile)))
{
goto Exit;
}
// Remove the FFILE from the NU list -- it was put in the NU list by
// flmAllocFile. We don't want the FFILE to disappear while we are
// doing the restore because it happens to age out of the NU list.
flmAssert( !pFile->uiUseCount);
flmUnlinkFileFromNUList( pFile);
// Unlock the global mutex
f_mutexUnlock( gv_FlmSysData.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
if( RC_BAD( rc = gv_FlmSysData.pFileSystem->createFile(
pszDbPath, FLM_IO_RDWR, &pFileHdl)))
{
goto Exit;
}
// Open the backup set
if( RC_BAD( rc = pRestoreObj->openBackupSet()))
{
goto Exit;
}
// Make a copy of the password as flmRestoreFile may zero-out the original
// after unshrouding the key.
if ( pszPassword)
{
if ( RC_BAD( rc = f_alloc(
f_strlen( pszPassword) + 1, &pszTempPassword)))
{
goto Exit;
}
f_strcpy( pszTempPassword, pszPassword);
}
// Restore the data in the backup set
if( RC_BAD( rc = flmRestoreFile( pRestoreObj, pszDbPath, pszDataDir,
pszTempPassword, &pSFile, FALSE, &uiDbVersion, &uiNextIncNum,
&bRflPreserved, &eRestoreAction, NULL, &pucDbKey, NULL, &uiKeyLen)))
{
goto Exit;
}
// See if we should continue
if( eRestoreAction == 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 && uiDbVersion >= FLM_FILE_FORMAT_VER_4_3)
{
FLMUINT uiCurrentIncNum;
for( ;;)
{
uiCurrentIncNum = uiNextIncNum;
if( RC_BAD( rc = pRestoreObj->openIncFile( uiCurrentIncNum)))
{
if( rc == FERR_IO_PATH_NOT_FOUND)
{
rc = FERR_OK;
break;
}
else
{
goto Exit;
}
}
else
{
if( RC_BAD( rc = flmRestoreFile( pRestoreObj, pszDbPath, pszDataDir,
pszTempPassword, &pSFile, TRUE, &uiDbVersion, &uiNextIncNum,
&bRflPreserved, &eRestoreAction, &bOKToRetry, NULL,
pucDbKey, &uiKeyLen)))
{
RCODE tmpRc;
if( !bOKToRetry)
{
// Cannot retry the operation or continue ... the
// database is in an unknown state.
goto Exit;
}
if (RC_BAD( tmpRc = pRestoreObj->status( RESTORE_ERROR,
0, (void *)(FLMUINT)rc, NULL, NULL, &eRestoreAction)))
{
rc = tmpRc;
goto Exit;
}
if( eRestoreAction == RESTORE_ACTION_RETRY ||
eRestoreAction == RESTORE_ACTION_CONTINUE)
{
// Abort the current file (if any)
if( RC_BAD( rc = pRestoreObj->abortFile()))
{
goto Exit;
}
if( eRestoreAction == 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( eRestoreAction == 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)
{
if( pFSRestoreObj == pRestoreObj)
{
pFSRestoreObj->Release();
pFSRestoreObj = NULL;
}
pRestoreObj = NULL;
}
// Open the file and apply any available RFL files. The
// lock file handle is passed to the flmOpenFile 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 = flmOpenFile( pFile, pszDbPath, pszDataDir,
pszRflDir, FO_DONT_RESUME_BACKGROUND_THREADS,
TRUE, pRestoreObj, pLockFileHdl, NULL, (FDB **)&hDb);
pLockFileHdl = NULL;
pFile = NULL;
if( RC_BAD( rc))
{
goto Exit;
}
// Close the database
(void)FlmDbClose( &hDb);
(void)FlmConfig( FLM_CLOSE_FILE, (void *)pszDbPath,
(void *)pszDataDir);
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( pFile)
{
// We should only get to this point if rc is bad.
flmAssert( RC_BAD( rc));
if( !bMutexLocked)
{
f_mutexLock( gv_FlmSysData.hShareMutex);
bMutexLocked = TRUE;
}
rc = flmNewFileFinish( pFile, rc);
flmFreeFile( pFile);
f_mutexUnlock( gv_FlmSysData.hShareMutex);
bMutexLocked = FALSE;
}
if( bMutexLocked)
{
f_mutexUnlock( gv_FlmSysData.hShareMutex);
}
if( hDb != HFDB_NULL)
{
FlmDbClose( &hDb);
}
if( pFileHdl)
{
pFileHdl->Release();
}
if( pLockFileHdl)
{
pLockFileHdl->Release();
}
if( pFSRestoreObj)
{
pFSRestoreObj->Release();
}
if (pucDbKey)
{
f_free( &pucDbKey);
}
if ( pszTempPassword)
{
f_free( &pszTempPassword);
}
// If restore failed, remove all database files (excluding RFL files)
if( RC_BAD( rc))
{
(void)FlmDbRemove( pszDbPath, pszDataDir, NULL, FALSE);
}
return( rc);
}
/***************************************************************************
Desc : Restores a full or incremental backup
****************************************************************************/
FSTATIC RCODE flmRestoreFile(
F_Restore * pRestoreObj,
const char * pszDbPath,
const char * pszDataDir,
const char * pszPassword,
F_SuperFileHdl ** ppSFile,
FLMBOOL bIncremental,
FLMUINT * puiDbVersion,
FLMUINT * puiNextIncSeqNum,
FLMBOOL * pbRflPreserved,
eRestoreActionType * peRestoreAction,
FLMBOOL * pbOKToRetry,
FLMBYTE ** ppucKeyToSave,
FLMBYTE * pucKeyToUse,
FLMUINT * puiKeyLen)
{
RCODE rc = FERR_OK;
FLMUINT uiBytesWritten;
FLMUINT uiLogicalEOF;
FLMUINT uiBlkAddr;
FLMUINT uiBlockCount = 0;
FLMUINT uiActualBlkSize;
FLMUINT uiBlockSize;
FLMUINT uiDbVersion;
FLMUINT uiMaxFileSize;
FLMUINT uiBackupMaxFileSize;
FLMUINT uiPriorBlkFile = 0;
FLMUINT uiSectorSize;
FLMBYTE * pLogHdr;
FLMBYTE ucIncSerialNum[ F_SERIAL_NUM_SIZE];
FLMBYTE ucNextIncSerialNum[ F_SERIAL_NUM_SIZE];
FLMUINT uiIncSeqNum;
FLMBYTE * pucBlkBuf = NULL;
FLMBYTE ucLowChecksumByte;
FLMUINT uiBlkBufSize;
FLMUINT uiPriorBlkAddr = 0;
BYTE_PROGRESS byteProgress;
FBackupType eBackupType;
F_BackerStream * pBackerStream = NULL;
F_CCS * pTmpCCS = NULL;
F_SuperFileHdl * pSFile = NULL;
F_SuperFileClient * pSFileClient = NULL;
#ifndef FLM_USE_NICI
F_UNREFERENCED_PARM( pszPassword);
#endif
// Initialize the "ok-to-retry" flag
if( pbOKToRetry)
{
*pbOKToRetry = TRUE;
}
// Initialize the progress struct
f_memset( &byteProgress, 0, sizeof( BYTE_PROGRESS));
// Set up the backer stream object
if( (pBackerStream = f_new F_BackerStream) == NULL)
{
rc = RC_SET( FERR_MEM);
goto Exit;
}
if( RC_BAD( rc = pBackerStream->setup( FLM_BACKER_MTU_SIZE, pRestoreObj)))
{
goto Exit;
}
// Get the sector size
if( RC_BAD( rc = gv_FlmSysData.pFileSystem->getSectorSize(
pszDbPath, &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_AND_ASSERT( FERR_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_AND_ASSERT( FERR_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( FERR_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( FERR_INCONSISTENT_BACKUP);
goto Exit;
}
// Make sure the backup type is correct
eBackupType = (FBackupType)FB2UD(
&pucBlkBuf[ FLM_BACKER_BACKUP_TYPE_OFFSET]);
if( (eBackupType == FLM_INCREMENTAL_BACKUP && !bIncremental) ||
(eBackupType == FLM_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( FERR_ILLEGAL_OP);
goto Exit;
}
// Grab the "next" incremental backup serial number
f_memcpy( ucNextIncSerialNum,
&pucBlkBuf[ FLM_BACKER_NEXT_INC_SERIAL_NUM],
F_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
if( uiBlockSize !=
FB2UD( &pucBlkBuf[ FLAIM_HEADER_START + DB_BLOCK_SIZE]))
{
rc = RC_SET_AND_ASSERT( FERR_INCONSISTENT_BACKUP);
goto Exit;
}
// Get a pointer to the log header and verify the checksum
pLogHdr = &pucBlkBuf[ DB_LOG_HEADER_START];
if( lgHdrCheckSum( pLogHdr, TRUE))
{
rc = RC_SET( FERR_BLOCK_CHECKSUM);
goto Exit;
}
// Compare the database version in the log header with
// the one extracted from the backup header
if( (FLMUINT)FB2UW( &pLogHdr[ LOG_FLAIM_VERSION]) != uiDbVersion)
{
rc = RC_SET_AND_ASSERT( FERR_INCONSISTENT_BACKUP);
goto Exit;
}
uiMaxFileSize = flmGetMaxFileSize( uiDbVersion, pLogHdr);
// Make sure the maximum block file size matches what was read from the
// backup header.
if( uiBackupMaxFileSize != uiMaxFileSize)
{
rc = RC_SET_AND_ASSERT( FERR_INCONSISTENT_BACKUP);
goto Exit;
}
// Allocate a super file object
if( (pSFile = *ppSFile) != NULL)
{
pSFile->AddRef();
}
else
{
flmAssert( !bIncremental);
if( (pSFile = f_new F_SuperFileHdl) == NULL)
{
rc = RC_SET( FERR_MEM);
goto Exit;
}
if( (pSFileClient = f_new F_SuperFileClient) == NULL)
{
rc = RC_SET( FERR_MEM);
goto Exit;
}
if( RC_BAD( rc = pSFileClient->setup( pszDbPath, pszDataDir, uiDbVersion)))
{
goto Exit;
}
if( RC_BAD( rc = pSFile->setup( pSFileClient,
gv_FlmSysData.pFileHdlCache, gv_FlmSysData.uiFileOpenFlags,
gv_FlmSysData.uiFileCreateFlags)))
{
goto Exit;
}
pSFile->setBlockSize( uiBlockSize);
*ppSFile = pSFile;
(*ppSFile)->AddRef();
}
// Unshroud the database key (stored in the log header) using the
// password the user gave us. (Note: this only re-writes the data
// in the log header. It's up to the database open call to actually
// create the F_CCS object when it initializes the FFILE.)
if( pszPassword && *pszPassword &&
FB2UD( &pLogHdr[ LOG_FLAIM_VERSION]) >= FLM_FILE_FORMAT_VER_4_60 &&
FB2UW( &pLogHdr[ LOG_DATABASE_KEY_LEN]) > 0 )
{
FLMBYTE * pucTmpBuf = NULL;
FLMUINT32 ui32KeyLen = 0;
FLMUINT uiNewChecksum;
if ( (pTmpCCS = f_new F_CCS) == NULL)
{
rc = RC_SET( FERR_MEM);
goto Exit;
}
if ( RC_BAD( rc = pTmpCCS->init( TRUE, FLM_NICI_AES)))
{
goto Exit;
}
ui32KeyLen = FB2UW( &pLogHdr [ LOG_DATABASE_KEY_LEN]);
if( RC_BAD( rc = pTmpCCS->setKeyFromStore( &pLogHdr[ LOG_DATABASE_KEY],
ui32KeyLen, pszPassword, NULL, FALSE)))
{
goto Exit;
}
if( RC_BAD( rc = pTmpCCS->getKeyToStore( &pucTmpBuf, &ui32KeyLen,
NULL, NULL, FALSE)))
{
goto Exit;
}
// Verify that the field in the log header is long enough to
// hold the key.
if( ui32KeyLen > FLM_MAX_DB_ENC_KEY_LEN)
{
f_free( &pucTmpBuf);
rc = RC_SET_AND_ASSERT( FERR_BAD_ENC_KEY);
goto Exit;
}
UW2FBA( ui32KeyLen, &pLogHdr[ LOG_DATABASE_KEY_LEN]);
f_memcpy( &pLogHdr[LOG_DATABASE_KEY], pucTmpBuf, ui32KeyLen);
uiNewChecksum = lgHdrCheckSum( pLogHdr, FALSE);
UW2FBA( (FLMUINT16)uiNewChecksum, &pLogHdr[ LOG_HDR_CHECKSUM]);
f_free( &pucTmpBuf);
pTmpCCS->Release();
pTmpCCS = NULL;
if( ppucKeyToSave)
{
// Need to allocate a buffer to save the key in
if ( RC_BAD( rc = f_alloc( ui32KeyLen, ppucKeyToSave)))
{
goto Exit;
}
f_memcpy( *ppucKeyToSave, &pLogHdr[ LOG_DATABASE_KEY], ui32KeyLen);
*puiKeyLen = ui32KeyLen;
}
}
else if( pucKeyToUse)
{
UW2FBA( (FLMUINT16)*puiKeyLen, &pLogHdr[ LOG_DATABASE_KEY_LEN]);
f_memcpy( &pLogHdr[ LOG_DATABASE_KEY], pucKeyToUse, *puiKeyLen);
}
// Get the logical EOF from the log header
uiLogicalEOF = FB2UD( &pLogHdr[ LOG_LOGICAL_EOF]);
// Are RFL files being preserved?
if( pbRflPreserved)
{
*pbRflPreserved = pLogHdr[ LOG_KEEP_RFL_FILES]
? TRUE
: FALSE;
}
// Get the incremental backup sequence number
uiIncSeqNum = FB2UD( &pLogHdr[ LOG_INC_BACKUP_SEQ_NUM]);
*puiNextIncSeqNum = uiIncSeqNum;
if( bIncremental)
{
(*puiNextIncSeqNum)++;
}
// Get information about the incremental backup
if( bIncremental)
{
FLMBYTE ucTmpSerialNum[ F_SERIAL_NUM_SIZE];
FLMBYTE ucTmpSeqNum[ 4];
FLMUINT uiTmp;
f_memcpy( ucIncSerialNum, &pLogHdr[ LOG_INC_BACKUP_SERIAL_NUM],
F_SERIAL_NUM_SIZE);
// Compare the incremental backup sequence number to the value in the
// database's log header.
if( RC_BAD( rc = pSFile->readBlock(
FSBlkAddress( 0, DB_LOG_HEADER_START + LOG_INC_BACKUP_SEQ_NUM),
4, ucTmpSeqNum, &uiTmp)))
{
goto Exit;
}
if( FB2UD( &ucTmpSeqNum[ 0]) != uiIncSeqNum)
{
rc = RC_SET( FERR_INVALID_FILE_SEQUENCE);
goto Exit;
}
// Compare the incremental backup serial number to the value in the
// database's log header.
if( RC_BAD( rc = pSFile->readBlock(
FSBlkAddress( 0, DB_LOG_HEADER_START + LOG_INC_BACKUP_SERIAL_NUM),
F_SERIAL_NUM_SIZE, ucTmpSerialNum, &uiTmp)))
{
goto Exit;
}
if( f_memcmp( ucIncSerialNum,
ucTmpSerialNum, F_SERIAL_NUM_SIZE) != 0)
{
rc = RC_SET( FERR_SERIAL_NUM_MISMATCH);
goto Exit;
}
// Increment the incremental backup sequence number
UD2FBA( (FLMUINT32)(uiIncSeqNum + 1),
&pLogHdr[ LOG_INC_BACKUP_SEQ_NUM]);
}
// 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.
if( uiDbVersion >= FLM_FILE_FORMAT_VER_4_3)
{
f_memcpy( &pLogHdr[ LOG_INC_BACKUP_SERIAL_NUM],
ucNextIncSerialNum, F_SERIAL_NUM_SIZE);
}
// Re-calculate the log header checksum
UW2FBA( (FLMUINT16)lgHdrCheckSum( pLogHdr, FALSE),
&pLogHdr[ LOG_HDR_CHECKSUM]);
pLogHdr = NULL;
// Set the "ok-to-retry" flag
if( pbOKToRetry)
{
*pbOKToRetry = FALSE;
}
// Write the database header
if( RC_BAD( rc = pSFile->writeBlock( FSBlkAddress( 0, 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.
byteProgress.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]);
uiActualBlkSize = FB2UD( &pucBlkBuf[ FLM_BACKER_BLK_SIZE_OFFSET]);
// Are we done?
if( uiBlkAddr == 0xFFFFFFFF)
{
break;
}
if( !uiBlkAddr ||
!FSAddrIsBelow( uiBlkAddr, uiLogicalEOF) ||
(uiPriorBlkAddr && !FSAddrIsBelow( uiPriorBlkAddr, uiBlkAddr)))
{
rc = RC_SET_AND_ASSERT( FERR_INCONSISTENT_BACKUP);
goto Exit;
}
// Read and process the block
if( uiActualBlkSize > uiBlockSize || uiActualBlkSize < BH_OVHD)
{
rc = RC_SET_AND_ASSERT( FERR_INCONSISTENT_BACKUP);
goto Exit;
}
if( RC_BAD( rc = pBackerStream->read( uiActualBlkSize, pucBlkBuf)))
{
goto Exit;
}
if( (GET_BH_ADDR( pucBlkBuf) & 0xFFFFFF00) != (uiBlkAddr & 0xFFFFFF00))
{
rc = RC_SET_AND_ASSERT( FERR_INCONSISTENT_BACKUP);
goto Exit;
}
if( uiActualBlkSize < uiBlockSize)
{
f_memset( &pucBlkBuf[ uiActualBlkSize], 0,
uiBlockSize - uiActualBlkSize);
}
// Verify the checksum
ucLowChecksumByte = pucBlkBuf[ BH_CHECKSUM_LOW];
if( RC_BAD( rc = BlkCheckSum( pucBlkBuf, CHECKSUM_CHECK,
uiBlkAddr, uiBlockSize)))
{
if( rc == FERR_BLOCK_CHECKSUM)
{
rc = RC_SET_AND_ASSERT( FERR_INCONSISTENT_BACKUP);
}
goto Exit;
}
pucBlkBuf[ BH_CHECKSUM_LOW] = ucLowChecksumByte;
// Write the block to the database
if( RC_BAD( rc = pSFile->writeBlock( uiBlkAddr,
uiBlockSize, pucBlkBuf, &uiBytesWritten)))
{
if( rc == FERR_IO_PATH_NOT_FOUND ||
rc == FERR_IO_INVALID_PATH)
{
// Create a new block file
if( FSGetFileNumber( uiBlkAddr) != (uiPriorBlkFile + 1))
{
rc = RC_SET_AND_ASSERT( FERR_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( (uiBlockCount & 0x7F) == 0x7F)
{
byteProgress.ui64BytesDone = FSGetSizeInBytes( uiMaxFileSize,
uiBlkAddr);
if( RC_BAD( rc = pRestoreObj->status( RESTORE_PROGRESS, 0,
(void *)&byteProgress, NULL, NULL, peRestoreAction)))
{
goto Exit;
}
if( *peRestoreAction == RESTORE_ACTION_STOP)
{
rc = RC_SET( FERR_USER_ABORT);
goto Exit;
}
}
}
// Call the status callback one last time.
byteProgress.ui64BytesDone = byteProgress.ui64BytesToDo;
if( RC_BAD( rc = pRestoreObj->status( RESTORE_PROGRESS, 0,
(void *)&byteProgress, NULL, NULL, peRestoreAction)))
{
goto Exit;
}
if( *peRestoreAction == 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();
}
if( pTmpCCS)
{
pTmpCCS->Release();
}
if( pSFile)
{
pSFile->Release();
}
if( pSFileClient)
{
pSFileClient->Release();
}
return( rc);
}
/****************************************************************************
Desc: Default hook for creating a backup file set
****************************************************************************/
FSTATIC RCODE flmDefaultBackerWriteHook(
void * pvBuffer,
FLMUINT uiBytesToWrite,
void * pvCallbackData)
{
BACKER_HOOK_STATE * pState = (BACKER_HOOK_STATE *)pvCallbackData;
FLMUINT uiBytesWritten;
RCODE rc = pState->rc;
if( RC_BAD( rc))
{
goto Exit;
}
if( !pState->pMultiFileHdl)
{
// Remove any existing backup files
if( RC_BAD( rc = FlmAllocMultiFileHdl( &pState->pMultiFileHdl)))
{
goto Exit;
}
if( RC_BAD( rc = pState->pMultiFileHdl->deleteMultiFile(
pState->szPath)) && rc != FERR_IO_PATH_NOT_FOUND &&
rc != FERR_IO_INVALID_PATH)
{
pState->pMultiFileHdl->Release();
pState->pMultiFileHdl = NULL;
goto Exit;
}
if( RC_BAD( rc = pState->pMultiFileHdl->create( pState->szPath)))
{
pState->pMultiFileHdl->Release();
pState->pMultiFileHdl = NULL;
goto Exit;
}
}
rc = pState->pMultiFileHdl->write( pState->ui64Offset,
uiBytesToWrite, pvBuffer, &uiBytesWritten);
pState->ui64Offset += uiBytesWritten;
Exit:
if( RC_BAD( rc))
{
pState->rc = rc;
if( pState->pMultiFileHdl)
{
pState->pMultiFileHdl->Release();
pState->pMultiFileHdl = NULL;
}
}
return( rc);
}
// F_BackerStream methods
/****************************************************************************
Desc: Constructor
****************************************************************************/
F_BackerStream::F_BackerStream( void)
{
m_bSetup = FALSE;
m_bFirstRead = TRUE;
m_ui64ByteCount = 0;
m_uiBufOffset = 0;
m_pRestoreObj = NULL;
m_hDataSem = F_SEM_NULL;
m_hIdleSem = F_SEM_NULL;
m_pThread = NULL;
m_rc = FERR_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_uiPendingIO = 0;
m_fnWrite = NULL;
m_pvCallbackData = 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 = FERR_OK;
if( m_pThread)
{
rc = RC_SET( FERR_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_fnWrite)
{
if( RC_BAD( rc = f_threadCreate( &m_pThread,
F_BackerStream::writeThread, "backup",
0, 0, (void *)this)))
{
goto Exit;
}
}
else if( m_pRestoreObj)
{
if( RC_BAD( rc = f_threadCreate( &m_pThread,
F_BackerStream::readThread, "restore",
0, 0, (void *)this)))
{
goto Exit;
}
}
else
{
flmAssert( 0);
rc = RC_SET( FERR_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);
f_threadDestroy( &m_pThread);
// 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,
F_Restore * pRestoreObj)
{
RCODE rc = FERR_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,
BACKER_WRITE_HOOK fnWrite,
void * pvCallbackData)
{
RCODE rc = FERR_OK;
flmAssert( fnWrite);
flmAssert( !m_bSetup);
m_fnWrite = fnWrite;
m_uiMTUSize = uiMTUSize;
m_pvCallbackData = pvCallbackData;
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 = FERR_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( FERR_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 = FERR_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 = FERR_OK;
flmAssert( m_bSetup);
flmAssert( m_fnWrite);
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 = FERR_OK;
flmAssert( m_bSetup);
if( m_fnWrite && 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_SEM_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 = FERR_OK;
flmAssert( m_bSetup);
// Return an error if we don't have a thread.
if( !m_pThread)
{
rc = RC_SET( FERR_FAILURE);
goto Exit;
}
// Wait for the thread to become idle
if( RC_BAD( rc = f_semWait( m_hIdleSem, F_SEM_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 != FERR_IO_END_OF_FILE)
{
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_rc is bad, the background thread has terminated.
if( RC_OK( m_rc))
{
// Signal the thread to read or write data
// NOTE: The background thread will never be decrementing
// m_uiPendingIO while we are incrementing it because it
// will be blocked on m_hDataSem waiting for it to
// be signaled.
m_uiPendingIO++;
f_semSignal( m_hDataSem);
}
Exit:
return( rc);
}
/****************************************************************************
Desc: This thread reads data in the background
****************************************************************************/
RCODE FLMAPI F_BackerStream::readThread(
IF_Thread * pThread)
{
RCODE rc = FERR_OK;
F_BackerStream * pBackerStream = (F_BackerStream *)pThread->getParm1();
for( ;;)
{
f_semSignal( pBackerStream->m_hIdleSem);
if( RC_BAD( rc = f_semWait( pBackerStream->m_hDataSem,
F_SEM_WAITFOREVER)))
{
goto Exit;
}
if( !pBackerStream->m_uiPendingIO &&
pThread->getShutdownFlag())
{
break;
}
if( RC_BAD( rc = pBackerStream->m_pRestoreObj->read(
pBackerStream->m_uiMTUSize, pBackerStream->m_pucInBuf,
pBackerStream->m_puiInOffset)))
{
goto Exit;
}
flmAssert( pBackerStream->m_uiPendingIO);
pBackerStream->m_uiPendingIO--;
}
Exit:
pBackerStream->m_rc = rc;
pBackerStream->m_uiPendingIO = 0;
f_semSignal( pBackerStream->m_hIdleSem);
return( rc);
}
/****************************************************************************
Desc: This thread writes data in the background
****************************************************************************/
RCODE FLMAPI F_BackerStream::writeThread(
IF_Thread * pThread)
{
RCODE rc = FERR_OK;
F_BackerStream * pBackerStream = (F_BackerStream *)pThread->getParm1();
for( ;;)
{
f_semSignal( pBackerStream->m_hIdleSem);
if( RC_BAD( rc = f_semWait( pBackerStream->m_hDataSem,
F_SEM_WAITFOREVER)))
{
goto Exit;
}
if( *(pBackerStream->m_puiOutOffset))
{
if( RC_BAD( rc = pBackerStream->m_fnWrite(
pBackerStream->m_pucOutBuf, *(pBackerStream->m_puiOutOffset),
pBackerStream->m_pvCallbackData)))
{
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;
pBackerStream->m_uiPendingIO = 0;
f_semSignal( pBackerStream->m_hIdleSem);
return( rc);
}
/****************************************************************************
Desc:
****************************************************************************/
F_FSRestore::F_FSRestore()
{
m_pFileHdl = NULL;
m_pMultiFileHdl = NULL;
m_ui64Offset = 0;
m_bSetupCalled = FALSE;
m_szDbPath[ 0] = 0;
m_uiDbVersion = 0;
m_szBackupSetPath[ 0] = 0;
m_szRflDir[ 0] = 0;
m_bOpen = FALSE;
}
/****************************************************************************
Desc:
****************************************************************************/
F_FSRestore::~F_FSRestore()
{
if( m_bOpen)
{
(void)close();
}
}
/****************************************************************************
Desc:
****************************************************************************/
RCODE F_FSRestore::setup(
const char * pucDbPath,
const char * pucBackupSetPath,
const char * pucRflDir)
{
flmAssert( !m_bSetupCalled);
flmAssert( pucDbPath);
flmAssert( pucBackupSetPath);
f_strcpy( m_szDbPath, pucDbPath);
f_strcpy( m_szBackupSetPath, pucBackupSetPath);
if( pucRflDir)
{
f_strcpy( m_szRflDir, pucRflDir);
}
m_bSetupCalled = TRUE;
return( FERR_OK);
}
/****************************************************************************
Desc:
****************************************************************************/
RCODE F_FSRestore::openBackupSet( void)
{
RCODE rc = FERR_OK;
flmAssert( m_bSetupCalled);
flmAssert( !m_pMultiFileHdl);
if( RC_BAD( rc = FlmAllocMultiFileHdl( &m_pMultiFileHdl)))
{
goto Exit;
}
if( RC_BAD( rc = m_pMultiFileHdl->open( m_szBackupSetPath)))
{
m_pMultiFileHdl->Release();
m_pMultiFileHdl = NULL;
goto Exit;
}
m_ui64Offset = 0;
m_bOpen = TRUE;
Exit:
return( rc);
}
/****************************************************************************
Desc:
****************************************************************************/
RCODE F_FSRestore::openRflFile(
FLMUINT uiFileNum)
{
RCODE rc = FERR_OK;
char szRflPath[ F_PATH_MAX_SIZE];
char szDbPrefix[ F_FILENAME_SIZE];
char szBaseName[ F_FILENAME_SIZE];
FLMBYTE * pBuf = NULL;
FILE_HDR fileHdr;
LOG_HDR logHdr;
IF_FileHdl * pFileHdl = NULL;
flmAssert( m_bSetupCalled);
flmAssert( uiFileNum);
flmAssert( !m_pFileHdl);
// Read the database header to determine the version number
if( !m_uiDbVersion)
{
if (RC_BAD( rc = f_alloc( 2048, &pBuf)))
{
goto Exit;
}
if( RC_BAD( rc = gv_FlmSysData.pFileSystem->openFile(
m_szDbPath, FLM_IO_RDWR | FLM_IO_SH_DENYNONE, &pFileHdl)))
{
goto Exit;
}
if( RC_BAD( rc = flmReadAndVerifyHdrInfo( NULL, pFileHdl,
pBuf, &fileHdr, &logHdr, NULL)))
{
goto Exit;
}
pFileHdl->Release();
pFileHdl = NULL;
m_uiDbVersion = fileHdr.uiVersionNum;
}
// Generate the log file name.
if( RC_BAD( rc = rflGetDirAndPrefix(
m_uiDbVersion, m_szDbPath, m_szRflDir, szRflPath, szDbPrefix)))
{
goto Exit;
}
rflGetBaseFileName( m_uiDbVersion, szDbPrefix, uiFileNum, szBaseName);
gv_FlmSysData.pFileSystem->pathAppend( szRflPath, szBaseName);
// Open the file.
if( RC_BAD( rc = gv_FlmSysData.pFileSystem->openFile(
szRflPath, gv_FlmSysData.uiFileOpenFlags, &m_pFileHdl)))
{
goto Exit;
}
m_bOpen = TRUE;
m_ui64Offset = 0;
Exit:
if( pBuf)
{
f_free( &pBuf);
}
if( pFileHdl)
{
pFileHdl->Release();
}
return( rc);
}
/****************************************************************************
Desc:
****************************************************************************/
RCODE F_FSRestore::openIncFile(
FLMUINT uiFileNum)
{
RCODE rc = FERR_OK;
char szIncPath[ F_PATH_MAX_SIZE];
char szIncFile[ F_FILENAME_SIZE];
flmAssert( m_bSetupCalled);
flmAssert( !m_pMultiFileHdl);
// Since this is a non-interactive restore, we will "guess"
// that incremental backups are located in the same parent
// directory as the main backup set. We will further assume
// that the incremental backup sets have been named XXXXXXXX.INC,
// where X is a hex digit.
if( RC_BAD( rc = gv_FlmSysData.pFileSystem->pathReduce( m_szBackupSetPath,
szIncPath, NULL)))
{
goto Exit;
}
f_sprintf( szIncFile, "%08X.INC", (unsigned)uiFileNum);
gv_FlmSysData.pFileSystem->pathAppend( szIncPath, szIncFile);
if( RC_BAD( rc = FlmAllocMultiFileHdl( &m_pMultiFileHdl)))
{
goto Exit;
}
if( RC_BAD( rc = m_pMultiFileHdl->open( szIncPath)))
{
m_pMultiFileHdl->Release();
m_pMultiFileHdl = NULL;
goto Exit;
}
m_ui64Offset = 0;
m_bOpen = TRUE;
Exit:
return( rc);
}
/****************************************************************************
Desc:
****************************************************************************/
RCODE F_FSRestore::read(
FLMUINT uiLength,
void * pvBuffer,
FLMUINT * puiBytesRead)
{
RCODE rc = FERR_OK;
FLMUINT uiBytesRead = 0;
flmAssert( m_bSetupCalled);
flmAssert( m_pFileHdl || m_pMultiFileHdl);
if( m_pMultiFileHdl)
{
if( RC_BAD( rc = m_pMultiFileHdl->read( m_ui64Offset,
uiLength, pvBuffer, &uiBytesRead)))
{
goto Exit;
}
}
else
{
if( RC_BAD( rc = m_pFileHdl->read( (FLMUINT)m_ui64Offset,
uiLength, pvBuffer, &uiBytesRead)))
{
goto Exit;
}
}
f_assert( uiBytesRead <= uiLength);
Exit:
m_ui64Offset += uiBytesRead;
if( puiBytesRead)
{
*puiBytesRead = uiBytesRead;
}
return( rc);
}
/****************************************************************************
Desc:
****************************************************************************/
RCODE F_FSRestore::close( void)
{
flmAssert( m_bSetupCalled);
if( m_pMultiFileHdl)
{
m_pMultiFileHdl->Release();
m_pMultiFileHdl = NULL;
}
if( m_pFileHdl)
{
m_pFileHdl->Release();
m_pFileHdl = NULL;
}
m_bOpen = FALSE;
m_ui64Offset = 0;
return( FERR_OK);
}