git-svn-id: https://svn.code.sf.net/p/flaim/code/trunk@729 0109f412-320b-0410-ab79-c3e0c5ffbbe6
2545 lines
57 KiB
C++
2545 lines
57 KiB
C++
//------------------------------------------------------------------------------
|
|
// 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$
|
|
//------------------------------------------------------------------------------
|
|
|
|
#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:
|
|
|
|
// Methods
|
|
|
|
RCODE signalThread( void);
|
|
|
|
RCODE _setup( void);
|
|
|
|
static RCODE FLMAPI readThread(
|
|
IF_Thread * pThread);
|
|
|
|
static RCODE FLMAPI writeThread(
|
|
IF_Thread * pThread);
|
|
|
|
// Data
|
|
|
|
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,
|
|
F_Backup ** ppBackup)
|
|
{
|
|
F_Backup * pBackup = NULL;
|
|
FLMBOOL bBackupFlagSet = FALSE;
|
|
FLMUINT uiLastCPFileNum;
|
|
FLMUINT uiLastTransFileNum;
|
|
FLMUINT uiDbVersion;
|
|
SFLM_DB_HDR * pDbHdr;
|
|
RCODE rc = NE_SFLM_OK;
|
|
|
|
// Initialize the handle
|
|
|
|
*ppBackup = NULL;
|
|
|
|
// Make sure we are not being called inside a transaction
|
|
|
|
if( getTransType() != SFLM_NO_TRANS)
|
|
{
|
|
rc = RC_SET( NE_SFLM_TRANS_ACTIVE);
|
|
goto Exit;
|
|
}
|
|
|
|
// Verify that the application has specified a valid transaction type.
|
|
|
|
if( eTransType != SFLM_READ_TRANS && eTransType != SFLM_UPDATE_TRANS)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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_SFLM_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_SFLM_MEM);
|
|
goto Exit;
|
|
}
|
|
|
|
pBackup->m_pDb = this;
|
|
pBackup->m_uiDbVersion = uiDbVersion;
|
|
|
|
// Start a transaction
|
|
|
|
if( RC_BAD( rc = beginTrans( eTransType, uiMaxLockWait,
|
|
SFLM_DONT_KILL_TRANS | SFLM_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 == SFLM_INCREMENTAL_BACKUP &&
|
|
pDbHdr->ui64LastBackupTransID == 0)
|
|
{
|
|
rc = RC_SET( NE_SFLM_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 = SFLM_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 = SFLM_FULL_BACKUP;
|
|
m_backupRc = NE_SFLM_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)
|
|
{
|
|
FLMBOOL bFullBackup = TRUE;
|
|
FLMINT iFileNum;
|
|
FLMUINT uiBlkAddr;
|
|
FLMUINT uiTime;
|
|
F_CachedBlock * pSCache = NULL;
|
|
SFLM_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;
|
|
RCODE rc = NE_SFLM_OK;
|
|
|
|
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_SFLM_FAILURE);
|
|
goto Exit;
|
|
}
|
|
|
|
if( RC_BAD( m_backupRc))
|
|
{
|
|
rc = m_backupRc;
|
|
goto Exit;
|
|
}
|
|
|
|
// Look at the backup type
|
|
|
|
if( m_eBackupType == SFLM_INCREMENTAL_BACKUP)
|
|
{
|
|
if( puiIncSeqNum)
|
|
{
|
|
*puiIncSeqNum = m_uiIncSeqNum;
|
|
}
|
|
|
|
bFullBackup = FALSE;
|
|
}
|
|
|
|
// Set up the callback
|
|
|
|
if( !ifpClient)
|
|
{
|
|
if( !pszBackupPath)
|
|
{
|
|
rc = RC_SET( NE_SFLM_INVALID_PARM);
|
|
goto Exit;
|
|
}
|
|
|
|
ifpClient = f_new F_DefaultBackupClient( pszBackupPath);
|
|
|
|
if (ifpClient == NULL)
|
|
{
|
|
rc = RC_SET( NE_SFLM_MEM);
|
|
goto Exit;
|
|
}
|
|
|
|
bReleaseClient = TRUE;
|
|
}
|
|
|
|
// Allocate and initialize the backer stream object
|
|
|
|
if( (pBackerStream = f_new F_BackerStream) == NULL)
|
|
{
|
|
rc = RC_SET( NE_SFLM_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, SFLM_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( SFLM_DB_HDR));
|
|
pDbHdr = (SFLM_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.
|
|
// (Carson found this in his random testing when one thread did
|
|
// a wrapKey while another did a backup.)
|
|
|
|
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->ucDbKey, 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_SFLM_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);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Area : MISC
|
|
Desc : Ends the backup, updating the log header if needed.
|
|
****************************************************************************/
|
|
RCODE F_Backup::endBackup( void)
|
|
{
|
|
RCODE rc = NE_SFLM_OK;
|
|
FLMBOOL bStartedTrans = FALSE;
|
|
|
|
if( !m_bCompletedBackup)
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// End the transaction
|
|
|
|
flmAssert( m_eTransType != SFLM_NO_TRANS);
|
|
if( RC_BAD( rc = m_pDb->abortTrans()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
m_eTransType = SFLM_NO_TRANS;
|
|
m_bTransStarted = FALSE;
|
|
|
|
// Start an update transaction.
|
|
|
|
if( RC_BAD( rc = m_pDb->beginTrans( SFLM_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 == SFLM_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, SFLM_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_SFLM_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 = SFLM_RESTORE_ACTION_CONTINUE; // default action...
|
|
FLMBOOL bRflPreserved;
|
|
FLMBOOL bMutexLocked = FALSE;
|
|
F_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_SFLM_INVALID_PARM);
|
|
goto Exit;
|
|
}
|
|
|
|
if( (pFSRestoreObj = f_new F_FSRestore) == NULL)
|
|
{
|
|
rc = RC_SET( NE_SFLM_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_SFlmSysData.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_SFLM_DATABASE_OPEN);
|
|
pDatabase = NULL;
|
|
f_mutexUnlock( gv_SFlmSysData.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_SFlmSysData.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_SFlmSysData.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_SFLM_MEM);
|
|
goto Exit;
|
|
}
|
|
|
|
if( RC_BAD( rc = SFileClient.setup( pszDbPath, pszDataDir)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if( RC_BAD( rc = pSFile->setup( &SFileClient, gv_SFlmSysData.pFileHdlCache,
|
|
gv_SFlmSysData.uiFileOpenFlags, gv_SFlmSysData.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 == SFLM_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_SFLM_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 == SFLM_RESTORE_ACTION_RETRY ||
|
|
eAction == SFLM_RESTORE_ACTION_CONTINUE)
|
|
{
|
|
// Abort the current file (if any)
|
|
|
|
if( RC_BAD( rc = pRestoreObj->abortFile()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if( eAction == SFLM_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 == SFLM_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, SFLM_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_SFlmSysData.hShareMutex);
|
|
bMutexLocked = TRUE;
|
|
}
|
|
|
|
if (RC_BAD( rc))
|
|
{
|
|
pDatabase->newDatabaseFinish( rc);
|
|
}
|
|
|
|
if( !pDatabase->m_uiOpenIFDbCount)
|
|
{
|
|
pDatabase->freeDatabase();
|
|
}
|
|
|
|
f_mutexUnlock( gv_SFlmSysData.hShareMutex);
|
|
bMutexLocked = FALSE;
|
|
}
|
|
|
|
if( bMutexLocked)
|
|
{
|
|
f_mutexUnlock( gv_SFlmSysData.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))
|
|
{
|
|
dropDatabase( 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;
|
|
SFLM_DB_HDR * pDbHdr;
|
|
FLMBYTE ucIncSerialNum[ SFLM_SERIAL_NUM_SIZE];
|
|
FLMBYTE ucNextIncSerialNum[ SFLM_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_SFLM_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_SFLM_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_SFlmSysData.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_SFLM_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_SFLM_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_SFLM_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_SFLM_INCONSISTENT_BACKUP);
|
|
goto Exit;
|
|
}
|
|
|
|
// Make sure the backup type is correct
|
|
|
|
eBackupType = (eDbBackupType)FB2UD(
|
|
&pucBlkBuf[ FLM_BACKER_BACKUP_TYPE_OFFSET]);
|
|
|
|
if( (eBackupType == SFLM_INCREMENTAL_BACKUP && !bIncremental) ||
|
|
(eBackupType == SFLM_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_SFLM_ILLEGAL_OP);
|
|
goto Exit;
|
|
}
|
|
|
|
// Grab the "next" incremental backup serial number
|
|
|
|
f_memcpy( ucNextIncSerialNum,
|
|
&pucBlkBuf[ FLM_BACKER_NEXT_INC_SERIAL_NUM],
|
|
SFLM_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 = (SFLM_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_SFLM_HDR_CRC);
|
|
goto Exit;
|
|
}
|
|
|
|
if( uiBlockSize != (FLMUINT)pDbHdr->ui16BlockSize)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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_SFLM_INCONSISTENT_BACKUP);
|
|
goto Exit;
|
|
}
|
|
uiMaxFileSize = (FLMUINT)pDbHdr->ui32MaxFileSize;
|
|
|
|
// Set the database version number and block size into the
|
|
// super file handle. We only do this if the file being restored
|
|
// is the full backup. It will always be first in the restore sequence,
|
|
// and thus we only need to set these values into the super file handle
|
|
// at that time.
|
|
|
|
if( !bIncremental)
|
|
{
|
|
pSFile->setBlockSize( uiBlockSize);
|
|
}
|
|
|
|
// Make sure the maximum block file size matches what was read from the
|
|
// backup header.
|
|
|
|
if( uiBackupMaxFileSize != uiMaxFileSize)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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;
|
|
SFLM_DB_HDR dbHdr;
|
|
|
|
f_memcpy( ucIncSerialNum, pDbHdr->ucIncBackupSerialNum,
|
|
SFLM_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( SFLM_DB_HDR),
|
|
&dbHdr, &uiTmp)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (hdrIsNonNativeFormat( &dbHdr))
|
|
{
|
|
convertDbHdr( &dbHdr);
|
|
}
|
|
|
|
if( (FLMUINT)dbHdr.ui32IncBackupSeqNum != uiIncSeqNum)
|
|
{
|
|
rc = RC_SET( NE_SFLM_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,
|
|
SFLM_SERIAL_NUM_SIZE) != 0)
|
|
{
|
|
rc = RC_SET( NE_SFLM_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, SFLM_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_SFLM_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_SFLM_BLOCK_CRC)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_INCONSISTENT_BACKUP);
|
|
}
|
|
goto Exit;
|
|
}
|
|
if( (FLMUINT)pBlkHdr->ui32BlkAddr != uiBlkAddr)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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_SFLM_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 == SFLM_RESTORE_ACTION_STOP)
|
|
{
|
|
rc = RC_SET( NE_SFLM_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 == SFLM_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_SFLM_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_SFLM_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_SFLM_OK;
|
|
|
|
if( m_pThread)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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_SFlmSysData.pThreadMgr->createThread( &m_pThread,
|
|
F_BackerStream::writeThread, "backup",
|
|
0, 0, (void *)this)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else if( m_pRestoreObj)
|
|
{
|
|
if( RC_BAD( rc = gv_SFlmSysData.pThreadMgr->createThread( &m_pThread,
|
|
F_BackerStream::readThread, "restore",
|
|
0, 0, (void *)this)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_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_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 = NE_SFLM_OK;
|
|
|
|
flmAssert( m_bSetup);
|
|
|
|
// Return an error if we don't have a thread.
|
|
|
|
if( !m_pThread)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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 == 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 F_BackerStream::readThread(
|
|
IF_Thread * pThread)
|
|
{
|
|
F_BackerStream * pBackerStream = (F_BackerStream *)pThread->getParm1();
|
|
RCODE rc = NE_SFLM_OK;
|
|
|
|
for( ;;)
|
|
{
|
|
f_semSignal( pBackerStream->m_hIdleSem);
|
|
|
|
if( RC_BAD( rc = f_semWait( pBackerStream->m_hDataSem,
|
|
F_SEM_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_SFLM_OK;
|
|
|
|
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_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);
|
|
}
|