Files
mars-flaim/xflaim/src/fdbcopy.cpp
2006-05-30 22:32:41 +00:00

886 lines
20 KiB
C++

//------------------------------------------------------------------------------
// Desc: This file contains the F_DbSystem::dbCopy method.
//
// Tabs: 3
//
// Copyright (c) 1998-2006 Novell, Inc. All Rights Reserved.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of version 2 of the GNU General Public
// License as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, contact Novell, Inc.
//
// To contact Novell about this file by physical or electronic mail,
// you may find current contact information at www.novell.com
//
// $Id: fdbcopy.cpp 3112 2006-01-19 13:12:40 -0700 (Thu, 19 Jan 2006) dsanders $
//------------------------------------------------------------------------------
#include "flaimsys.h"
// Local prototypes
typedef struct Copied_Name * COPIED_NAME_p;
typedef struct Copied_Name
{
char szPath[ F_PATH_MAX_SIZE];
COPIED_NAME_p pNext;
} COPIED_NAME;
typedef struct
{
FLMUINT64 ui64BytesToCopy;
FLMUINT64 ui64BytesCopied;
FLMBOOL bNewSrcFile;
char szSrcFileName[ F_PATH_MAX_SIZE];
char szDestFileName[ F_PATH_MAX_SIZE];
} DB_COPY_INFO, * DB_COPY_INFO_p;
FSTATIC RCODE flmCopyFile(
DB_COPY_INFO * pDbCopyInfo,
COPIED_NAME ** ppCopiedListRV,
FLMBOOL bOkToTruncate,
IF_DbCopyStatus * ifpStatus);
/****************************************************************************
Desc: Copies a database, including roll-forward log files.
****************************************************************************/
RCODE F_DbSystem::dbCopy(
const char * pszSrcDbName,
// [IN] Name of source database to be copied.
const char * pszSrcDataDir,
// [IN] Name of source data directory.
const char * pszSrcRflDir,
// [IN] RFL directory of source database. NULL can be
// passed to indicate that the log files are located
// in the same directory as the other database files.
const char * pszDestDbName,
// [IN] Destination name of database - will be overwritten if it
// already exists.
const char * pszDestDataDir,
// [IN] Name of destination data directory.
const char * pszDestRflDir,
// [IN] RFL directory of destination database. NULL can be
// passed to indicate that the log files are to be located
// in the same directory as the other database files.
IF_DbCopyStatus * ifpStatus)
// [IN] Status callback interface.
{
RCODE rc = NE_XFLM_OK;
IF_Db * pDb = NULL;
FLMBOOL bDbLocked = FALSE;
// Make sure the destination database is closed
if (RC_BAD( rc = checkDatabaseClosed( pszDestDbName, pszDestDataDir)))
{
goto Exit;
}
// Open the source database so we can force a checkpoint.
if (RC_BAD( rc = openDb( pszSrcDbName, pszSrcDataDir, pszSrcRflDir,
NULL, 0, &pDb)))
{
goto Exit;
}
// Need to lock the database, because we want to do a checkpoint
// and then the copy immediately after without letting other
// threads have the opportunity to get in and update the
// database.
if (RC_BAD( rc = pDb->dbLock( FLM_LOCK_EXCLUSIVE, 0, FLM_NO_TIMEOUT)))
{
goto Exit;
}
bDbLocked = TRUE;
// Force a checkpoint
if (RC_BAD( rc = pDb->doCheckpoint( FLM_NO_TIMEOUT)))
{
goto Exit;
}
// Once we get this far, we have exclusive access to the database
// and we have forced a checkpoint. The database's contents are
// guaranteed to be on disk at this point, and they will not
// change.
rc = copyDb( pszSrcDbName, pszSrcDataDir, pszSrcRflDir,
pszDestDbName, pszDestDataDir, pszDestRflDir,
ifpStatus);
Exit:
// Unlock and close the database
if (bDbLocked)
{
pDb->dbUnlock();
}
if (pDb)
{
pDb->Release();
}
return( rc);
}
/****************************************************************************
Desc: Copy a database's files, including roll-forward log files.
*****************************************************************************/
RCODE F_DbSystem::copyDb(
const char * pszSrcDbName,
const char * pszSrcDataDir,
const char * pszSrcRflDir,
const char * pszDestDbName,
const char * pszDestDataDir,
const char * pszDestRflDir,
IF_DbCopyStatus * ifpStatus)
{
RCODE rc = NE_XFLM_OK;
DB_COPY_INFO DbCopyInfo;
F_SuperFileHdl SrcSFileHdl;
F_SuperFileHdl DestSFileHdl;
FLMUINT uiFileNumber;
FLMUINT uiHighFileNumber;
FLMUINT uiHighLogFileNumber;
FLMUINT64 ui64FileSize;
F_Database * pDatabase = NULL;
FLMBOOL bMutexLocked = FALSE;
IF_FileHdl * pLockFileHdl = NULL;
IF_FileHdl * pTmpFileHdl = NULL;
IF_DirHdl * pDirHdl = NULL;
FLMBOOL bDatabaseLocked = FALSE;
FLMBOOL bWriteLocked = FALSE;
IF_LockObject * pWriteLockObj = NULL;
IF_LockObject * pDatabaseLockObj = NULL;
COPIED_NAME * pCopiedList = NULL;
FLMBOOL bUsedDatabase = FALSE;
eLockType currLockType;
FLMUINT uiThreadId;
FLMUINT uiNumExclQueued;
FLMUINT uiNumSharedQueued;
FLMUINT uiPriorityCount;
char * pszActualSrcRflPath = NULL;
char * pszActualDestRflPath = NULL;
FLMBOOL bCreatedDestRflDir = FALSE;
FLMBOOL bWaited;
F_SEM hWaitSem = F_SEM_NULL;
f_memset( &DbCopyInfo, 0, sizeof( DbCopyInfo));
// Should not do anything if the source and destination names
// are the same.
if (f_stricmp( pszSrcDbName, pszDestDbName) == 0)
{
goto Exit;
}
// Create a "wait" semaphore
if( RC_BAD( rc = f_semCreate( &hWaitSem)))
{
goto Exit;
}
// Allocate memory for paths we don't want to push onto the stack.
if (RC_BAD( rc = f_calloc( F_PATH_MAX_SIZE * 2,
&pszActualSrcRflPath)))
{
goto Exit;
}
pszActualDestRflPath = &pszActualSrcRflPath[ F_PATH_MAX_SIZE];
// Set up the super file object for the source database.
// Must at least open the control file.
if (RC_BAD( rc = SrcSFileHdl.setup( pszSrcDbName, pszSrcDataDir)))
{
goto Exit;
}
// Lock the destination database, if not already locked.
// This is so we can overwrite it without necessarily
// deleting it. May unlock and re-lock the global mutex.
f_mutexLock( gv_XFlmSysData.hShareMutex);
bMutexLocked = TRUE;
retry:
if (RC_BAD( rc = F_DbSystem::findDatabase( pszDestDbName,
pszDestDataDir, &pDatabase)))
{
goto Exit;
}
// If we didn't find an FFILE structure, get an
// exclusive lock on the file.
if (!pDatabase)
{
f_mutexUnlock( gv_XFlmSysData.hShareMutex);
bMutexLocked = FALSE;
// Attempt to get an exclusive lock on the file.
if (RC_BAD( rc = flmCreateLckFile( pszDestDbName, &pLockFileHdl)))
{
goto Exit;
}
}
else
{
// The call to verifyOkToUse will wait if the database is in
// the process of being opened by another thread.
if (RC_BAD( rc = pDatabase->verifyOkToUse( &bWaited)))
{
goto Exit;
}
if (bWaited)
{
goto retry;
}
// Increment the open count on the F_Database object so it will not
// disappear while we are copying the database.
pDatabase->incrOpenCount();
bUsedDatabase = TRUE;
f_mutexUnlock( gv_XFlmSysData.hShareMutex);
bMutexLocked = FALSE;
// Lock the destination file object and transaction
// object, if not already locked.
pDatabase->m_pDatabaseLockObj->getLockInfo( (FLMINT)0,
&currLockType,
&uiThreadId, &uiNumExclQueued,
&uiNumSharedQueued,
&uiPriorityCount);
if (currLockType != FLM_LOCK_EXCLUSIVE ||
uiThreadId != f_threadId())
{
pDatabaseLockObj = pDatabase->m_pDatabaseLockObj;
pDatabaseLockObj->AddRef();
if (RC_BAD( rc = pDatabaseLockObj->lock(
hWaitSem, TRUE, FLM_NO_TIMEOUT, 0)))
{
goto Exit;
}
bDatabaseLocked = TRUE;
}
// Lock the write object, if not already locked
pDatabase->m_pWriteLockObj->getLockInfo( (FLMINT)0,
&currLockType,
&uiThreadId, &uiNumExclQueued,
&uiNumSharedQueued,
&uiPriorityCount);
if (currLockType != FLM_LOCK_EXCLUSIVE ||
uiThreadId != (FLMUINT)f_threadId())
{
pWriteLockObj = pDatabase->m_pWriteLockObj;
pWriteLockObj->AddRef();
// Only contention here is with the checkpoint thread - wait
// forever until the checkpoint thread gives it up.
if (RC_BAD( rc = pDatabase->dbWriteLock( hWaitSem)))
{
goto Exit;
}
bWriteLocked = TRUE;
}
}
// Set up the super file object for the destination database.
if (RC_BAD( rc = DestSFileHdl.setup( pszDestDbName, pszDestDataDir)))
{
goto Exit;
}
// See how many files we have and calculate the total size.
uiHighFileNumber = 0;
for (;;)
{
if ((RC_BAD( rc = SrcSFileHdl.getFileSize(
uiHighFileNumber, &ui64FileSize))) || !ui64FileSize )
{
if (rc == NE_FLM_IO_PATH_NOT_FOUND ||
rc == NE_FLM_IO_INVALID_FILENAME ||
!ui64FileSize)
{
// If the control file doesn't exist, we will return
// path not found.
if (!uiHighFileNumber)
{
goto Exit;
}
uiHighFileNumber--;
rc = NE_XFLM_OK;
break;
}
goto Exit;
}
DbCopyInfo.ui64BytesToCopy += ui64FileSize;
if (uiHighFileNumber == MAX_DATA_BLOCK_FILE_NUMBER)
{
break;
}
uiHighFileNumber++;
}
// See how many rollback log files we have, and calculate
// their total size.
uiHighLogFileNumber = FIRST_LOG_BLOCK_FILE_NUMBER;
for (;;)
{
if ((RC_BAD( rc = SrcSFileHdl.getFileSize(
uiHighLogFileNumber, &ui64FileSize))) || !ui64FileSize)
{
if (rc == NE_FLM_IO_PATH_NOT_FOUND ||
rc == NE_FLM_IO_INVALID_FILENAME ||
!ui64FileSize)
{
if (uiHighLogFileNumber ==
FIRST_LOG_BLOCK_FILE_NUMBER)
{
uiHighLogFileNumber = 0;
}
else
{
uiHighLogFileNumber--;
}
rc = NE_XFLM_OK;
break;
}
goto Exit;
}
DbCopyInfo.ui64BytesToCopy += ui64FileSize;
if (uiHighLogFileNumber == MAX_LOG_BLOCK_FILE_NUMBER)
{
break;
}
uiHighLogFileNumber++;
}
// Get the sizes of the roll-forward log files
if (RC_BAD( rc = rflGetDirAndPrefix( pszSrcDbName,
pszSrcRflDir, pszActualSrcRflPath)))
{
goto Exit;
}
if (RC_BAD( rc = gv_XFlmSysData.pFileSystem->openDir(
pszActualSrcRflPath, (char *)"*", &pDirHdl)))
{
goto Exit;
}
for (;;)
{
if (RC_BAD( rc = pDirHdl->next()))
{
if (rc == NE_FLM_IO_NO_MORE_FILES)
{
rc = NE_XFLM_OK;
break;
}
else
{
goto Exit;
}
}
// If the current file is an RFL file, increment ui64BytesToCopy
if (rflGetFileNum( pDirHdl->currentItemName(), &uiFileNumber))
{
DbCopyInfo.ui64BytesToCopy += (FLMUINT64)pDirHdl->currentItemSize();
}
}
pDirHdl->Release();
pDirHdl = NULL;
// Close all file handles in the source and destination
SrcSFileHdl.releaseFiles( TRUE);
DestSFileHdl.releaseFiles( TRUE);
// Copy the database files.
for (uiFileNumber = 0; uiFileNumber <= uiHighFileNumber; uiFileNumber++)
{
// Get the source file path and destination file path.
if( RC_BAD( rc = SrcSFileHdl.getFilePath(
uiFileNumber, DbCopyInfo.szSrcFileName)))
{
goto Exit;
}
if( RC_BAD( rc = DestSFileHdl.getFilePath(
uiFileNumber, DbCopyInfo.szDestFileName)))
{
goto Exit;
}
DbCopyInfo.bNewSrcFile = TRUE;
if (RC_BAD( rc = flmCopyFile(
&DbCopyInfo, &pCopiedList, TRUE, ifpStatus)))
{
goto Exit;
}
}
// Copy the additional rollback log files, if any.
for (uiFileNumber = FIRST_LOG_BLOCK_FILE_NUMBER;
uiFileNumber <= uiHighLogFileNumber; uiFileNumber++)
{
// Get the source file path and destination file path.
if (RC_BAD( rc = SrcSFileHdl.getFilePath( uiFileNumber,
DbCopyInfo.szSrcFileName)))
{
goto Exit;
}
if (RC_BAD( rc = DestSFileHdl.getFilePath( uiFileNumber,
DbCopyInfo.szDestFileName)))
{
goto Exit;
}
DbCopyInfo.bNewSrcFile = TRUE;
if (RC_BAD( rc = flmCopyFile(
&DbCopyInfo, &pCopiedList, TRUE, ifpStatus)))
{
goto Exit;
}
}
// Copy the RFL files
// Create the destination RFL directory, if needed. The purpose of this
// code is two-fold: 1) We want to keep track of the fact that we tried
// to create the destination RFL directory so we can try to remove it
// if the copy fails; 2) If the destination RFL directory path specifies
// a directory with existing files, we want to remove them.
if( RC_BAD( rc = rflGetDirAndPrefix( pszDestDbName,
pszDestRflDir, pszActualDestRflPath)))
{
goto Exit;
}
if( RC_OK( gv_XFlmSysData.pFileSystem->doesFileExist( pszActualDestRflPath)))
{
if( gv_XFlmSysData.pFileSystem->isDir( pszActualDestRflPath))
{
// Remove the existing directory and all files, etc.
(void)gv_XFlmSysData.pFileSystem->removeDir(
pszActualDestRflPath, TRUE);
}
else
{
(void)gv_XFlmSysData.pFileSystem->deleteFile( pszActualDestRflPath);
}
}
// Try to create the destination RFL directory. This might fail if
// another process was accessing the directory for some reason
// (i.e., from a command prompt), when we tried to remove it above.
// We really don't care if the call to CreateDir is sucessful, because
// when we try to create the destination files (below), the FLAIM file
// file system code will try to create any necessary directories.
(void)gv_XFlmSysData.pFileSystem->createDir( pszActualDestRflPath);
bCreatedDestRflDir = TRUE;
// Copy the RFL files. NOTE: We need to copy all of the RFL files
// in the source RFL directory so that they will be available
// when performing a database restore operation.
if (RC_BAD( rc = gv_XFlmSysData.pFileSystem->openDir(
pszActualSrcRflPath, (char *)"*", &pDirHdl)))
{
goto Exit;
}
for (;;)
{
if( RC_BAD( rc = pDirHdl->next()))
{
if (rc == NE_FLM_IO_NO_MORE_FILES)
{
rc = NE_XFLM_OK;
break;
}
else
{
goto Exit;
}
}
// If the current file is an RFL file, copy it to the destination
if( rflGetFileNum( pDirHdl->currentItemName(), &uiFileNumber))
{
// Get the source file path and the destination file path.
if (RC_BAD( rc = rflGetFileName( pszSrcDbName,
pszSrcRflDir, uiFileNumber,
DbCopyInfo.szSrcFileName)))
{
goto Exit;
}
if (RC_BAD( rc = rflGetFileName( pszDestDbName,
pszDestRflDir, uiFileNumber,
DbCopyInfo.szDestFileName)))
{
goto Exit;
}
DbCopyInfo.bNewSrcFile = TRUE;
if (RC_BAD( rc = flmCopyFile(
&DbCopyInfo, &pCopiedList, TRUE, ifpStatus)))
{
goto Exit;
}
}
}
pDirHdl->Release();
pDirHdl = NULL;
Exit:
if (bUsedDatabase)
{
if (!bMutexLocked)
{
f_mutexLock( gv_XFlmSysData.hShareMutex);
bMutexLocked = TRUE;
}
pDatabase->decrOpenCount();
}
if (bMutexLocked)
{
f_mutexUnlock( gv_XFlmSysData.hShareMutex);
bMutexLocked = FALSE;
}
// Unlock the database, if it is locked.
if (bWriteLocked)
{
pDatabase->dbWriteUnlock();
bWriteLocked = FALSE;
}
if (bDatabaseLocked)
{
RCODE rc3;
if (RC_BAD( rc3 = pDatabaseLockObj->unlock()))
{
if (RC_OK( rc))
rc = rc3;
}
bDatabaseLocked = FALSE;
}
if (pWriteLockObj)
{
pWriteLockObj->Release();
pWriteLockObj = NULL;
}
if (pDatabaseLockObj)
{
pDatabaseLockObj->Release();
pDatabaseLockObj = NULL;
}
if (pLockFileHdl)
{
(void)pLockFileHdl->close();
pLockFileHdl->Release();
pLockFileHdl = NULL;
}
if (pTmpFileHdl)
{
pTmpFileHdl->Release();
}
if (pDirHdl)
{
pDirHdl->Release();
}
// Free all the names of files that were copied.
// If the copy didn't finish, try to delete any files
// that were copied.
while (pCopiedList)
{
COPIED_NAME_p pNext = pCopiedList->pNext;
// If the overall copy failed, delete the copied file.
if (RC_BAD( rc))
{
(void)gv_XFlmSysData.pFileSystem->deleteFile( pCopiedList->szPath);
}
f_free( &pCopiedList);
pCopiedList = pNext;
}
if (RC_BAD( rc) && bCreatedDestRflDir)
{
(void)gv_XFlmSysData.pFileSystem->removeDir( pszActualDestRflPath);
}
if (pszActualSrcRflPath)
{
f_free( &pszActualSrcRflPath);
}
if( hWaitSem != F_SEM_NULL)
{
f_semDestroy( &hWaitSem);
}
return( rc);
}
/****************************************************************************
Desc: Copy a file that is one of the files of the database.
*****************************************************************************/
FSTATIC RCODE flmCopyFile(
DB_COPY_INFO * pDbCopyInfo,
COPIED_NAME ** ppCopiedListRV,
FLMBOOL bOkToTruncate,
IF_DbCopyStatus * ifpStatus)
{
RCODE rc = NE_XFLM_OK;
FLMBYTE * pucBuffer = NULL;
IF_FileHdl * pSrcFileHdl = NULL;
IF_FileHdl * pDestFileHdl = NULL;
FLMUINT uiBufferSize = 32768;
FLMUINT uiBytesToRead;
FLMUINT uiBytesRead;
FLMUINT uiBytesWritten;
FLMUINT uiOffset;
FLMBOOL bCreatedDestFile = FALSE;
// Open the source file.
if( RC_BAD( rc = gv_XFlmSysData.pFileSystem->openFile(
pDbCopyInfo->szSrcFileName,
FLM_IO_RDWR | FLM_IO_SH_DENYNONE | FLM_IO_DIRECT,
&pSrcFileHdl)))
{
goto Exit;
}
// Get a file handle for the destination file.
// First attempt to open the destination file. If it does
// not exist, attempt to create it.
if (RC_BAD( rc = gv_XFlmSysData.pFileSystem->openFile( pDbCopyInfo->szDestFileName,
FLM_IO_RDWR | FLM_IO_SH_DENYNONE | FLM_IO_DIRECT,
&pDestFileHdl)))
{
if (rc != NE_FLM_IO_PATH_NOT_FOUND &&
rc != NE_FLM_IO_INVALID_FILENAME)
{
goto Exit;
}
if( RC_BAD( rc = gv_XFlmSysData.pFileSystem->createFile(
pDbCopyInfo->szDestFileName,
FLM_IO_RDWR | FLM_IO_EXCL | FLM_IO_SH_DENYNONE |
FLM_IO_CREATE_DIR | FLM_IO_DIRECT,
&pDestFileHdl)))
{
goto Exit;
}
bCreatedDestFile = TRUE;
}
// Allocate a buffer for reading and writing.
if (RC_BAD( rc = f_alloc( uiBufferSize, &pucBuffer)))
{
goto Exit;
}
// Read from source file until we hit EOF in the file or
// we hit the end offset.
uiOffset = 0;
for (;;)
{
uiBytesToRead = (FLMUINT)((0xFFFFFFFF - uiOffset >=
uiBufferSize)
? uiBufferSize
: (FLMUINT)(0xFFFFFFFF - uiOffset));
// Read data from source file.
if (RC_BAD( rc = pSrcFileHdl->sectorRead( uiOffset, uiBytesToRead,
pucBuffer, &uiBytesRead)))
{
if (rc == NE_FLM_IO_END_OF_FILE)
{
rc = NE_XFLM_OK;
if (!uiBytesRead)
{
break;
}
}
else
{
goto Exit;
}
}
// Write data to destination file.
if (RC_BAD( rc = pDestFileHdl->write( uiOffset,
uiBytesRead, pucBuffer, &uiBytesWritten)))
{
goto Exit;
}
// Do callback to report progress.
if (ifpStatus)
{
pDbCopyInfo->ui64BytesCopied += (FLMUINT64)uiBytesWritten;
if (RC_BAD( rc = ifpStatus->dbCopyStatus(
pDbCopyInfo->ui64BytesToCopy,
pDbCopyInfo->ui64BytesCopied,
pDbCopyInfo->bNewSrcFile,
pDbCopyInfo->szSrcFileName,
pDbCopyInfo->szDestFileName)))
{
goto Exit;
}
pDbCopyInfo->bNewSrcFile = FALSE;
}
if (0xFFFFFFFF - uiBytesWritten < uiOffset)
{
uiOffset = 0xFFFFFFFF;
break;
}
uiOffset += uiBytesWritten;
// Quit once we reach the end offset or we read fewer bytes
// than we asked for.
if (uiBytesRead < uiBytesToRead)
{
break;
}
}
// If we overwrote the destination file, as opposed to creating
// it, truncate it in case it was larger than the number of
// bytes we actually copied.
if (!bCreatedDestFile && bOkToTruncate)
{
if (RC_BAD( rc = pDestFileHdl->truncate( uiOffset)))
{
goto Exit;
}
}
// If the copy succeeded, add the destination name to a list
// of destination files. This is done so we can clean up
// copied files if we fail somewhere in the overall database
// copy.
if (ppCopiedListRV)
{
COPIED_NAME_p pCopyName;
if( RC_BAD( rc = f_alloc( (FLMUINT)sizeof( COPIED_NAME), &pCopyName)))
{
goto Exit;
}
f_strcpy( pCopyName->szPath, pDbCopyInfo->szDestFileName);
pCopyName->pNext = *ppCopiedListRV;
*ppCopiedListRV = pCopyName;
}
Exit:
if (pucBuffer)
{
f_free( &pucBuffer);
}
if (pSrcFileHdl)
{
pSrcFileHdl->Release();
}
if (pDestFileHdl)
{
pDestFileHdl->flush();
pDestFileHdl->Release();
}
// Attempt to delete the destination file if
// we didn't successfully copy it.
if (RC_BAD( rc))
{
(void)gv_XFlmSysData.pFileSystem->deleteFile( pDbCopyInfo->szDestFileName);
}
return( rc);
}