Files
mars-flaim/flaim/src/flopen.cpp
ahodgkinson d64d1bc5c0 Various message logging and async I/O changes.
git-svn-id: https://svn.code.sf.net/p/flaim/code/trunk@528 0109f412-320b-0410-ab79-c3e0c5ffbbe6
2006-06-06 22:49:49 +00:00

2283 lines
52 KiB
C++

//-------------------------------------------------------------------------
// Desc: Open database
// Tabs: 3
//
// Copyright (c) 1990-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: flopen.cpp 12329 2006-01-20 17:49:30 -0700 (Fri, 20 Jan 2006) ahodgkinson $
//-------------------------------------------------------------------------
#include "flaimsys.h"
FSTATIC RCODE flmPhysFileOpen(
FDB * pDb,
const char * pszFilePath,
const char * pszRflDir,
FLMUINT uiOpenFlags,
FLMBOOL bNewFile,
F_Restore * pRestoreObj);
FSTATIC RCODE flmReadFileHdr(
const char * pszDbPath,
DB_STATS * pDbStats,
FFILE * pFile,
LOG_HDR * pLogHdr,
FLMBOOL bAllowLimitedMode);
FSTATIC void flmFreeCPInfo(
CP_INFO ** ppCPInfoRV);
FSTATIC RCODE flmCPThread(
IF_Thread * pThread);
FSTATIC RCODE flmDoRecover(
FDB * pDb,
F_Restore * pRestoreObj);
FSTATIC RCODE flmDbMonitor(
IF_Thread * pThread);
/****************************************************************************
Desc : Opens an existing FLAIM database.
****************************************************************************/
FLMEXP RCODE FLMAPI FlmDbOpen(
const char * pszDbFileName,
const char * pszDataDir,
const char * pszRflDir,
FLMUINT uiOpenFlags,
const char * pszPassword,
HFDB * phDbRV)
{
RCODE rc = FERR_OK;
CS_CONTEXT * pCSContext;
rc = FERR_OK;
*phDbRV = HFDB_NULL;
if( !pszDbFileName || *pszDbFileName == 0)
{
rc = RC_SET( FERR_IO_INVALID_PATH);
goto Exit;
}
if (RC_BAD( rc = flmGetCSConnection( pszDbFileName, &pCSContext)))
{
goto Exit;
}
if (pCSContext)
{
if (RC_BAD( rc = flmOpenOrCreateDbClientServer(
pszDbFileName, pszDataDir, pszRflDir,
uiOpenFlags, NULL,
NULL, NULL, TRUE,
pCSContext, (FDB * *)phDbRV)))
{
(void)flmCloseCSConnection( &pCSContext);
}
goto Exit;
}
// Open the file
if (RC_BAD( rc = flmOpenFile( NULL,
pszDbFileName, pszDataDir, pszRflDir,
uiOpenFlags, FALSE,
NULL, NULL, pszPassword, (FDB **)phDbRV)))
{
goto Exit;
}
Exit:
return( rc);
}
/***************************************************************************
Desc: Opens a database via the server.
****************************************************************************/
RCODE flmOpenOrCreateDbClientServer(
const char * pszDbPath,
const char * pszDataDir,
const char * pszRflDir,
FLMUINT uiOpenFlags,
const char * pszDictFileName,
const char * pszDictBuf,
CREATE_OPTS * pCreateOpts,
FLMBOOL bOpening,
CS_CONTEXT * pCSContext,
FDB * * ppDb)
{
RCODE rc = FERR_OK;
FLMBOOL bAllocatedFdb = FALSE;
FLMUNICODE * puzDbPath;
FLMUNICODE * puzDataDir = NULL;
FLMUNICODE * puzRflDir = NULL;
FLMUNICODE * puzTmp;
F_Pool pool;
FCL_WIRE Wire( pCSContext);
FDB * pDb;
pool.poolInit( 128);
// Allocate and initialize an FDB structure
if (RC_BAD( rc = flmAllocFdb( ppDb)))
{
goto Exit;
}
pDb = *ppDb;
bAllocatedFdb = TRUE;
// Convert the paths to Unicode
if( RC_BAD( rc = fcsConvertNativeToUnicode(
&pool, pszDbPath, &puzDbPath)))
{
goto Exit;
}
if( pszDataDir)
{
if( RC_BAD( rc = fcsConvertNativeToUnicode(
&pool, pszDataDir, &puzDataDir)))
{
goto Exit;
}
}
if( pszRflDir)
{
if( RC_BAD( rc = fcsConvertNativeToUnicode(
&pool, pszRflDir, &puzRflDir)))
{
goto Exit;
}
}
// Send a request to open or create the database
if( RC_BAD( rc = Wire.sendOp( FCS_OPCLASS_DATABASE,
(FLMUINT)((bOpening)
? (FLMUINT)FCS_OP_DATABASE_OPEN
: (FLMUINT)FCS_OP_DATABASE_CREATE))))
{
goto Exit;
}
if( RC_BAD( rc = Wire.sendString( WIRE_VALUE_FILE_PATH, puzDbPath)))
{
goto Transmission_Error;
}
if( puzRflDir)
{
if( RC_BAD( rc = Wire.sendString( WIRE_VALUE_FILE_PATH_2, puzRflDir)))
{
goto Transmission_Error;
}
}
if( puzDataDir)
{
if( RC_BAD( rc = Wire.sendString( WIRE_VALUE_FILE_PATH_3, puzDataDir)))
{
goto Transmission_Error;
}
}
if (uiOpenFlags)
{
if (RC_BAD( rc = Wire.sendNumber( WIRE_VALUE_FLAGS, uiOpenFlags)))
{
goto Transmission_Error;
}
}
if (!bOpening)
{
if (pszDictFileName)
{
// Convert the path to Unicode
pool.poolReset();
if (RC_BAD( rc = fcsConvertNativeToUnicode( &pool, pszDictFileName,
&puzTmp)))
{
goto Exit;
}
if (RC_BAD( rc = Wire.sendString( WIRE_VALUE_DICT_FILE_PATH, puzTmp)))
{
goto Transmission_Error;
}
}
if (pszDictBuf)
{
// Convert the path to Unicode
pool.poolReset();
if (RC_BAD( rc = fcsConvertNativeToUnicode( &pool, pszDictBuf,
&puzTmp)))
{
goto Exit;
}
if (RC_BAD( rc = Wire.sendString( WIRE_VALUE_DICT_BUFFER, puzTmp)))
{
goto Transmission_Error;
}
}
if (pCreateOpts)
{
if (RC_BAD( rc = Wire.sendCreateOpts( WIRE_VALUE_CREATE_OPTS,
pCreateOpts)))
{
goto Transmission_Error;
}
}
}
if (RC_BAD( rc = Wire.sendTerminate()))
{
goto Transmission_Error;
}
// Read the response
if (RC_BAD( rc = Wire.read()))
{
goto Transmission_Error;
}
if (RC_BAD( rc = Wire.getRCode()))
{
goto Exit;
}
if (bOpening)
{
if (pCreateOpts)
{
Wire.copyCreateOpts( pCreateOpts);
}
}
pDb->pCSContext = pCSContext;
*ppDb = pDb;
Exit:
if (RC_BAD( rc))
{
(void)FlmDbClose( (HFDB *)ppDb);
}
return( rc);
Transmission_Error:
pCSContext->bConnectionGood = FALSE;
goto Exit;
}
/***************************************************************************
Desc: Allocates and initializes an FDB structure for a database which
is to be opened or created.
****************************************************************************/
RCODE flmAllocFdb(
FDB * * ppDb)
{
RCODE rc = FERR_OK;
FDB * pDb = NULL;
// Allocate the FDB structure.
*ppDb = NULL;
if( RC_BAD( rc = f_calloc( sizeof( FDB), ppDb)))
{
goto Exit;
}
pDb = *ppDb;
pDb->hWaitSem = F_SEM_NULL;
// Initialize pool for temporary allocations, making the block size
// what we need for most read operations - several Key buffers
pDb->tmpKrefPool.poolInit( 8192);
pDb->TempPool.poolInit( MAX_KEY_SIZ * 4);
#if defined( FLM_DEBUG)
// Create a mutex for controlling access to the structure
pDb->hMutex = F_MUTEX_NULL;
if (RC_BAD( rc = f_mutexCreate( &pDb->hMutex)))
{
goto Exit;
}
#endif
if( RC_BAD( rc = f_semCreate( &pDb->hWaitSem)))
{
goto Exit;
}
// Set up statistics.
if (RC_BAD( rc = flmStatInit( &pDb->Stats, FALSE)))
{
goto Exit;
}
pDb->bStatsInitialized = TRUE;
Exit:
if (RC_BAD( rc) && pDb)
{
flmDbClose( (HFDB *)ppDb, FALSE);
}
return( rc);
}
/****************************************************************************
Desc: This routine performs all of the necessary steps to complete
a create or open of an FFILE, including notifying other threads
waiting for the open or create to complete.
****************************************************************************/
RCODE flmCompleteOpenOrCreate(
FDB * * ppDb,
RCODE rc,
FLMBOOL bNewFile,
FLMBOOL bAllocatedFdb)
{
FDB * pDb = *ppDb;
if (RC_OK( rc))
{
// If this is a newly created FFILE, we need to notify any
// threads waiting for the file to be created or opened that
// the create or open is now complete.
if (bNewFile)
{
f_mutexLock( gv_FlmSysData.hShareMutex);
rc = flmNewFileFinish( pDb->pFile, rc);
f_mutexUnlock( gv_FlmSysData.hShareMutex);
}
}
else if (bAllocatedFdb)
{
FFILE * pFile = pDb->pFile;
// Temporarily increment the use count on the FFILE structure
// so that it will NOT be put into the UNUSED list by the call
// to flmDbClose below. If it is put into the UNUSED list,
// it can be freed by another thread.
if (bNewFile)
{
f_mutexLock( gv_FlmSysData.hShareMutex);
pFile->uiUseCount++;
f_mutexUnlock( gv_FlmSysData.hShareMutex);
}
flmDbClose( (HFDB *)ppDb, FALSE);
// If we allocated the FFILE, notify any
// waiting threads.
if (bNewFile)
{
f_mutexLock( gv_FlmSysData.hShareMutex);
// Decrement the use count to compensate for the increment
// that occurred above.
pFile->uiUseCount--;
// If this is a newly created FFILE, we need to notify any
// threads waiting for the file to be created or opened that
// the create or open is now complete.
rc = flmNewFileFinish( pFile, rc);
flmFreeFile( pFile);
f_mutexUnlock ( gv_FlmSysData.hShareMutex);
}
}
return( rc);
}
/****************************************************************************
Desc: This routine will open a file, returning a pointer to the FDB and
FFILE structures.
****************************************************************************/
RCODE flmOpenFile(
FFILE * pFile,
const char * pszDbPath,
const char * pszDataDir,
const char * pszRflDir,
FLMUINT uiOpenFlags,
FLMBOOL bInternalOpen,
F_Restore * pRestoreObj,
IF_FileHdl * pLockFileHdl,
const char * pszPassword,
FDB ** ppDb)
{
RCODE rc;
FLMBOOL bNewFile = FALSE;
FLMBOOL bMutexLocked = FALSE;
FLMBOOL bAllocatedFdb = FALSE;
FDB * pDb;
FLMBOOL bFirstOpen = FALSE;
// Allocate and initialize an FDB structure
if (RC_BAD( rc = flmAllocFdb( ppDb)))
{
goto Exit;
}
pDb = *ppDb;
if (bInternalOpen)
{
pDb->uiFlags |= FDB_INTERNAL_OPEN;
}
bAllocatedFdb = TRUE;
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 we already
// have the file open.
if (!pFile)
{
bFirstOpen = TRUE;
// May unlock and re-lock the global mutex.
if (RC_BAD( rc = flmFindFile( pszDbPath, pszDataDir, &pFile)))
{
goto Exit;
}
}
if( RC_BAD( rc = flmCheckFFileState( pFile)))
{
goto Exit;
}
if (!pFile)
{
if (RC_BAD( rc = flmAllocFile( pszDbPath, pszDataDir,
pszPassword, &pFile)))
{
goto Exit;
}
flmAssert( !pLockFileHdl);
bNewFile = TRUE;
}
else if( pLockFileHdl)
{
flmAssert( pFile);
flmAssert( !pFile->uiUseCount);
flmAssert( pFile->uiFlags & DBF_BEING_OPENED);
flmAssert( !(pFile->uiFlags & DBF_IN_NU_LIST));
// Put the FFILE back in the NU list. FlmDbRestore removed the FFILE from
// the NU list after allocating it so that it would not disappear during
// the restore. When the FFILE is linked to the FDB (below), the FFILE
// will be removed from the NU list.
flmLinkFileToNUList( pFile);
// Assign the lock file handle
pFile->pLockFileHdl = pLockFileHdl;
pLockFileHdl = NULL;
bNewFile = TRUE;
bFirstOpen = TRUE;
}
else
{
flmAssert( !pLockFileHdl);
if (RC_BAD( rc = flmVerifyFileUse( gv_FlmSysData.hShareMutex, &pFile)))
{
goto Exit;
}
}
// If there is a password, verify that it matches the current password.
if( pszPassword && pszPassword[0])
{
if( pFile->pszDbPassword)
{
if( f_strcmp( pszPassword, pFile->pszDbPassword) != 0)
{
if( uiOpenFlags & FO_ALLOW_LIMITED)
{
pFile->bInLimitedMode = TRUE;
pFile->rcLimitedCode = RC_SET( FERR_PASSWD_INVALID);
}
else
{
rc = RC_SET( FERR_PASSWD_INVALID);
goto Exit;
}
}
}
else if( uiOpenFlags & FO_ALLOW_LIMITED)
{
pFile->bInLimitedMode = TRUE;
pFile->rcLimitedCode = RC_SET( FERR_PASSWD_INVALID);
}
else
{
rc = RC_SET( FERR_PASSWD_INVALID);
goto Exit;
}
}
else if (pFile->pszDbPassword && pFile->pszDbPassword[ 0])
{
if (uiOpenFlags & FO_ALLOW_LIMITED)
{
pFile->bInLimitedMode = TRUE;
pFile->rcLimitedCode = RC_SET( FERR_REQUIRE_PASSWD);
}
else
{
rc = RC_SET( FERR_REQUIRE_PASSWD);
goto Exit;
}
}
// Link the FDB to the file.
rc = flmLinkFdbToFile( pDb, pFile);
f_mutexUnlock( gv_FlmSysData.hShareMutex);
bMutexLocked = FALSE;
if (RC_BAD(rc))
{
goto Exit;
}
(void)flmStatGetDb( &pDb->Stats, pFile,
0, &pDb->pDbStats, NULL, NULL);
if (bFirstOpen)
{
if (RC_BAD( rc = flmPhysFileOpen( pDb, pszDbPath, pszRflDir,
uiOpenFlags, bNewFile,
pRestoreObj)))
{
goto Exit;
}
}
else
{
// Allocate the super file object
flmAssert( !pDb->pSFileHdl);
if( (pDb->pSFileHdl = f_new F_SuperFileHdl) == NULL)
{
rc = RC_SET( FERR_MEM);
goto Exit;
}
flmAssert( pFile->FileHdr.uiVersionNum);
flmAssert( pFile->FileHdr.uiBlockSize);
if( RC_BAD( rc = pDb->pSFileHdl->setup(
pFile->pszDbPath, pFile->pszDataDir, pFile->FileHdr.uiVersionNum)))
{
goto Exit;
}
}
if (bNewFile && !(uiOpenFlags & FO_DONT_REDO_LOG))
{
// Start the checkpoint thread
flmAssert( pFile->pCPThrd == NULL);
if (RC_BAD( rc = flmStartCPThread( pFile)))
{
goto Exit;
}
// Start the database monitor thread
flmAssert( pFile->pMonitorThrd == NULL);
if (RC_BAD( rc = flmStartDbMonitorThread( pFile)))
{
goto Exit;
}
if( !(uiOpenFlags & FO_DONT_RESUME_BACKGROUND_THREADS))
{
if (RC_BAD( rc = flmStartBackgrndIxThrds( pDb)))
{
goto Exit;
}
if( RC_BAD( rc = flmStartMaintThread( pFile)))
{
goto Exit;
}
}
}
Exit:
if (bMutexLocked)
{
f_mutexUnlock( gv_FlmSysData.hShareMutex);
}
if( pLockFileHdl)
{
pLockFileHdl->Release();
}
rc = flmCompleteOpenOrCreate( ppDb, rc, bNewFile, bAllocatedFdb);
return( rc);
}
/****************************************************************************
Desc: This routine checks to see if it is OK for another FDB to use a file.
If so, it increments the file's use counter. NOTE: This routine
assumes that the calling routine has locked the global mutex.
****************************************************************************/
RCODE flmVerifyFileUse(
F_MUTEX hMutex,
FFILE ** ppFileRV
)
{
RCODE rc = FERR_OK;
FFILE * pFile = *ppFileRV;
// Can't open the file if it is being closed by someone else.
if (pFile->uiFlags & DBF_BEING_CLOSED)
{
rc = RC_SET( FERR_IO_ACCESS_DENIED);
goto Exit;
}
// If the file is in the process of being opened by another
// thread, wait for the open to complete.
if (pFile->uiFlags & DBF_BEING_OPENED)
{
if (RC_BAD( rc = flmWaitNotifyReq( hMutex,
&pFile->pOpenNotifies,
(void *)0)))
{
// GW Bug #24307. If flmWaitNotifyReq returns a bad RC, assume that
// the other thread will unlock and free the pFile structure. This
// routine should only unlock the pFile if an error occurs at some
// other point. See flmVerifyFileUse.
// *ppFileRV is set to NULL at Exit.
goto Exit;
}
}
Exit:
if (RC_BAD( rc))
{
*ppFileRV = NULL;
}
return( rc);
}
/****************************************************************************
Desc: Returns the length of the base part of a database name. If the
name ends with a '.' or ".db", this will not be included in the
returned length.
****************************************************************************/
void flmGetDbBasePath(
char * pszBaseDbName,
const char * pszDbName,
FLMUINT * puiBaseDbNameLen)
{
FLMUINT uiBaseLen = f_strlen( pszDbName);
if( uiBaseLen <= 3 ||
f_stricmp( &pszDbName[ uiBaseLen - 3], ".db") != 0)
{
if( pszDbName[ uiBaseLen - 1] == '.')
{
uiBaseLen--;
}
}
else
{
uiBaseLen -= 3;
}
f_memcpy( pszBaseDbName, pszDbName, uiBaseLen);
pszBaseDbName[ uiBaseLen] = 0;
if( puiBaseDbNameLen)
{
*puiBaseDbNameLen = uiBaseLen;
}
}
/****************************************************************************
Desc: This routine obtains exclusive access to a database by creating
a .lck file. FLAIM holds the .lck file open as long as the database
is open. When the database is finally closed, it deletes the .lck
file. This is only used for 3.x databases.
****************************************************************************/
RCODE flmCreateLckFile(
const char * pszFilePath,
IF_FileHdl ** ppLockFileHdl)
{
RCODE rc = FERR_OK;
char szLockPath [F_PATH_MAX_SIZE];
IF_FileHdl * pLockFileHdl = NULL;
FLMUINT uiBaseLen;
// Extract the base name and put a .lck extension on it to create
// the full path for the .lck file.
flmGetDbBasePath( szLockPath, pszFilePath, &uiBaseLen);
f_strcpy( &szLockPath[ uiBaseLen], ".lck");
if( RC_BAD( rc = gv_FlmSysData.pFileSystem->createLockFile(
szLockPath, &pLockFileHdl)))
{
goto Exit;
}
*ppLockFileHdl = pLockFileHdl;
pLockFileHdl = NULL;
Exit:
if (pLockFileHdl)
{
pLockFileHdl->Release();
pLockFileHdl = NULL;
}
return( rc);
}
/****************************************************************************
Desc: This routine obtains exclusive access to a database by creating
a .lck file. FLAIM holds the .lck file open as long as the database
is open. When the database is finally closed, it deletes the .lck
file. This is only used for 3.x databases.
****************************************************************************/
RCODE flmGetExclAccess(
const char * pszFilePath,
FDB * pDb)
{
RCODE rc = FERR_OK;
FFILE * pFile = pDb->pFile;
FLMBOOL bNotifyWaiters = FALSE;
FLMBOOL bMutexLocked = FALSE;
// If pFile->pLockFileHdl is non-NULL, it means that we currently
// have the file locked with a lock file. There is no need to make
// this test inside a mutex lock, because the lock file handle can only
// be set to NULL when the use count goes to zero, meaning that the thread
// that sets it to NULL will be the only thread accessing it.
//
// However, it is possible that two or more threads will simultaneously
// test pLockFileHdl and discover that it is NULL. In that case,
// we allow one thread to proceed and attempt to get a lock on the file
// while the other threads wait to be notified of the results of the
// attempt to lock the file.
if (pFile->pLockFileHdl)
{
goto Exit;
}
f_mutexLock( gv_FlmSysData.hShareMutex);
bMutexLocked = TRUE;
if (pFile->bBeingLocked)
{
// If the file is in the process of being locked by another
// thread, wait for the lock to complete. NOTE: flmWaitNotifyReq will
// re-lock the mutex before returning.
rc = flmWaitNotifyReq( gv_FlmSysData.hShareMutex, &pFile->pLockNotifies,
(void *)0);
goto Exit;
}
else
{
// No other thread was attempting to lock the file, so
// set this thread up to make the attempt. Other threads
// coming in at this point will be required to wait and
// be notified of the results.
pFile->bBeingLocked = TRUE;
bNotifyWaiters = TRUE;
f_mutexUnlock( gv_FlmSysData.hShareMutex);
bMutexLocked = FALSE;
}
if (RC_BAD( rc = flmCreateLckFile( pszFilePath, &pFile->pLockFileHdl)))
{
goto Exit;
}
Exit:
if (bNotifyWaiters)
{
FNOTIFY * pNotify;
F_SEM hSem;
// Notify any thread waiting on the lock what its status is
if( !bMutexLocked)
{
f_mutexLock( gv_FlmSysData.hShareMutex);
bMutexLocked = TRUE;
}
pNotify = pFile->pLockNotifies;
while (pNotify)
{
*(pNotify->pRc) = rc;
hSem = pNotify->hSem;
pNotify = pNotify->pNext;
f_semSignal( hSem);
}
pFile->bBeingLocked = FALSE;
pFile->pLockNotifies = NULL;
f_mutexUnlock( gv_FlmSysData.hShareMutex);
bMutexLocked = FALSE;
}
if( bMutexLocked)
{
f_mutexUnlock( gv_FlmSysData.hShareMutex);
}
return( rc);
}
/****************************************************************************
Desc: This routine checks to see if it is OK for another FDB to use a file.
If so, it increments the file's use counter. NOTE: This routine
assumes that the global mutex is NOT locked.
****************************************************************************/
FSTATIC RCODE flmPhysFileOpen(
FDB * pDb,
const char * pszFilePath, // File name
const char * pszRflDir, // RFL directory
FLMUINT uiOpenFlags, // Flags for doing physical open
FLMBOOL bNewFile, // Is this a new file structure?
F_Restore * pRestoreObj) // Restore object
{
RCODE rc = FERR_OK;
FFILE * pFile = pDb->pFile;
FLMBOOL bAllowLimitedMode = FALSE;
// If this is the first open of the database, read the header block
if( bNewFile)
{
LOG_HDR LogHdr;
#ifdef FLM_USE_NICI
if( uiOpenFlags & FO_ALLOW_LIMITED)
#endif
{
bAllowLimitedMode = TRUE;
}
if( RC_BAD( rc = flmReadFileHdr( pszFilePath, pDb->pDbStats, pFile,
&LogHdr, bAllowLimitedMode)))
{
goto Exit;
}
// Allocate the pRfl object. Could not do this until this point
// because we need to have the version number, block size, etc.
// setup in the pFile->FileHdr.
flmAssert( !pFile->pRfl);
if( (pFile->pRfl = f_new F_Rfl) == NULL)
{
rc = RC_SET( FERR_MEM);
goto Exit;
}
if( RC_BAD( rc = pFile->pRfl->setup( pFile, pszRflDir)))
{
goto Exit;
}
}
else
{
if (pFile->bInLimitedMode && !(uiOpenFlags & FO_ALLOW_LIMITED))
{
// Should we override the open parameter to allow limited mode?
if (pFile->bHaveEncKey)
{
rc = RC_SET( pFile->rcLimitedCode);
goto Exit;
}
}
}
// Allocate the super file object
flmAssert( !pDb->pSFileHdl);
if( (pDb->pSFileHdl = f_new F_SuperFileHdl) == NULL)
{
rc = RC_SET( FERR_MEM);
goto Exit;
}
flmAssert( pFile->FileHdr.uiVersionNum);
flmAssert( pFile->FileHdr.uiBlockSize);
if( RC_BAD( rc = pDb->pSFileHdl->setup(
pFile->pszDbPath, pFile->pszDataDir, pFile->FileHdr.uiVersionNum)))
{
goto Exit;
}
// We must have exclusive access. Create a lock file for that
// purpose, if there is not already a lock file.
if (!pFile->pLockFileHdl)
{
if (RC_BAD( rc = flmGetExclAccess( pszFilePath, pDb)))
{
goto Exit;
}
}
// Do a recovery to ensure a consistent database
// state before going any further. The FO_DONT_REDO_LOG
// flag is used ONLY by the VIEW program.
if (bNewFile && !(uiOpenFlags & FO_DONT_REDO_LOG))
{
if (RC_BAD( rc = flmDoRecover( pDb, pRestoreObj)))
{
goto Exit;
}
}
Exit:
return( rc);
}
/****************************************************************************
Desc: This routine finishes up after creating a new FFILE structure. It
will notify any other threads waiting for the operation to complete
of the status of the operation.
****************************************************************************/
RCODE flmNewFileFinish(
FFILE * pFile,
RCODE OpenRc)
{
FNOTIFY * pNotify;
F_SEM hSem;
if (!pFile)
{
goto Exit;
}
// Notify anyone waiting on the operation what its status is
pNotify = pFile->pOpenNotifies;
while (pNotify)
{
*(pNotify->pRc) = OpenRc;
hSem = pNotify->hSem;
pNotify = pNotify->pNext;
f_semSignal( hSem);
}
pFile->pOpenNotifies = NULL;
pFile->uiFlags &= (~(DBF_BEING_OPENED));
Exit:
return OpenRc;
}
/****************************************************************************
Desc: This routine is used to see if a file is already in use somewhere.
This is only called for files which are opened directly by the
application.
Notes: This routine assumes that the global mutex is locked, but it
may unlock and re-lock the mutex if needed.
****************************************************************************/
RCODE flmFindFile(
const char * pszDbPath,
const char * pszDataDir,
FFILE ** ppFileRV)
{
RCODE rc = FERR_OK;
FBUCKET * pBucket;
FLMUINT uiBucket;
FLMBOOL bMutexLocked = TRUE;
FFILE * pFile;
char szDbPathStr1[ F_PATH_MAX_SIZE];
char szDbPathStr2[ F_PATH_MAX_SIZE];
*ppFileRV = NULL;
// Normalize the path to a string before looking for it.
// NOTE: On non-UNIX platforms, this will basically convert
// the string to upper case.
if (RC_BAD( rc = gv_FlmSysData.pFileSystem->pathToStorageString(
pszDbPath, szDbPathStr1)))
{
goto Exit;
}
Retry:
*ppFileRV = NULL;
if( !bMutexLocked)
{
f_mutexLock( gv_FlmSysData.hShareMutex);
bMutexLocked = TRUE;
}
pBucket = gv_FlmSysData.pFileHashTbl;
uiBucket = f_strHashBucket( szDbPathStr1, pBucket, FILE_HASH_ENTRIES);
pFile = (FFILE *)pBucket [uiBucket].pFirstInBucket;
while( pFile)
{
if (RC_BAD( rc = gv_FlmSysData.pFileSystem->pathToStorageString(
pFile->pszDbPath, szDbPathStr2)))
{
goto Exit;
}
// Compare the strings. It is OK to use f_strcmp on all platforms
// because on non-UNIX platforms the calls to pathToStorageString
// has already converted the characters to upper case - hence, we
// are doing a case-insensitive comparison.
if( f_strcmp( szDbPathStr1, szDbPathStr2) == 0)
{
// Make sure data paths match.
if( pszDataDir && *pszDataDir)
{
if( RC_BAD( rc = gv_FlmSysData.pFileSystem->pathToStorageString(
pszDataDir, szDbPathStr2)))
{
goto Exit;
}
if( pFile->pszDataDir)
{
if( RC_BAD( rc = gv_FlmSysData.pFileSystem->pathToStorageString(
pFile->pszDataDir, szDbPathStr1)))
{
goto Exit;
}
// f_strcmp is ok, because pathToStorageString converts to upper-case
// on non-UNIX platforms. On UNIX we want the comparison to be
// case-sensitive.
if( f_strcmp( szDbPathStr1, szDbPathStr2) != 0)
{
rc = RC_SET( FERR_DATA_PATH_MISMATCH);
goto Exit;
}
}
else
{
rc = RC_SET( FERR_DATA_PATH_MISMATCH);
goto Exit;
}
}
else if( pFile->pszDataDir)
{
rc = RC_SET( FERR_DATA_PATH_MISMATCH);
goto Exit;
}
*ppFileRV = pFile;
break;
}
pFile = pFile->pNext;
}
if( *ppFileRV && pFile->uiFlags & DBF_BEING_CLOSED)
{
// Put ourselves into the notify list and then re-try
// the lookup when we wake up
if (RC_BAD( rc = flmWaitNotifyReq( gv_FlmSysData.hShareMutex,
&pFile->pCloseNotifies,
(void *)0)))
{
goto Exit;
}
// The mutex will be locked at this point. Re-try the lookup.
// IMPORTANT NOTE: pFile will have been destroyed by this
// time. DO NOT use it for anything!
goto Retry;
}
Exit:
// Make sure the global mutex is re-locked before exiting
if( !bMutexLocked)
{
f_mutexLock( gv_FlmSysData.hShareMutex);
}
return( rc);
}
/****************************************************************************
Desc: This routine allocates a new FFILE structure and links it
into its hash buckets.
NOTE: This routine assumes that the global mutex has already
been locked. It may unlock it temporarily if there is an error,
but will always relock it before exiting.
****************************************************************************/
RCODE flmAllocFile(
const char * pszDbPath,
const char * pszDataDir,
const char * pszDbPassword,
FFILE * * ppFile)
{
RCODE rc = FERR_OK;
FLMUINT uiAllocLen;
FLMUINT uiDbNameLen = f_strlen( pszDbPath) + 1;
FLMUINT uiDirNameLen;
FFILE * pFile = NULL;
uiDirNameLen = (pszDataDir && *pszDataDir)
? f_strlen( pszDataDir) + 1
: 0;
uiAllocLen = (FLMUINT)(sizeof( FFILE) + uiDbNameLen + uiDirNameLen);
if (RC_BAD( rc = f_calloc( uiAllocLen, &pFile)))
{
goto Exit;
}
pFile->hMaintSem = F_SEM_NULL;
pFile->uiFFileId = gv_FlmSysData.uiNextFFileId++;
pFile->pCPInfo = NULL;
pFile->uiFileExtendSize = DEFAULT_FILE_EXTEND_SIZE;
pFile->krefPool.poolInit( 8192);
// Allocate a buffer for writing the database header
if( RC_BAD( rc = f_allocAlignedBuffer( MAX_BLOCK_SIZE,
(void **)&pFile->pucLogHdrWriteBuf)))
{
goto Exit;
}
f_memset( pFile->pucLogHdrWriteBuf, 0, MAX_BLOCK_SIZE);
// If a password was passed in, allocate a buffer for it.
if (pszDbPassword && pszDbPassword[0])
{
if (RC_BAD( rc = f_calloc( f_strlen( pszDbPassword) + 1,
&pFile->pszDbPassword)))
{
goto Exit;
}
f_memcpy( pFile->pszDbPassword, pszDbPassword, f_strlen(pszDbPassword));
}
// Setup the write buffer managers.
if( RC_BAD( rc = FlmAllocIOBufferMgr( &pFile->pBufferMgr)))
{
goto Exit;
}
pFile->pBufferMgr->setMaxBuffers( MAX_PENDING_WRITES);
pFile->pBufferMgr->setMaxBytes( MAX_WRITE_BUFFER_BYTES);
// Initialize members of FFILE.
pFile->uiBucket = 0xFFFF;
pFile->uiFlags = DBF_BEING_OPENED;
// Copy the file name immediately following the FFILE structure.
// and the data directory immediately following that.
// NOTE: uiDbNameLen includes the null terminating byte.
// and uiDirNameLen includes the null terminating byte.
pFile->pszDbPath = (char *)(&pFile[1]);
f_memcpy( pFile->pszDbPath, pszDbPath, uiDbNameLen);
if (uiDirNameLen)
{
pFile->pszDataDir = pFile->pszDbPath + uiDbNameLen;
f_memcpy( pFile->pszDataDir, pszDataDir, uiDirNameLen);
}
// Link the file into the various lists it needs to be linked into.
if (RC_BAD( rc = flmLinkFileToBucket( pFile)))
{
goto Exit;
}
flmLinkFileToNUList( pFile);
// Allocate a lock object for write locking.
if( RC_BAD( rc = FlmAllocLockObject( &pFile->pWriteLockObj)))
{
goto Exit;
}
// Allocate a lock object for file locking.
if( RC_BAD( rc = FlmAllocLockObject( &pFile->pFileLockObj)))
{
goto Exit;
}
// Initialize the maintenance thread's semaphore
if( RC_BAD( rc = f_semCreate( &pFile->hMaintSem)))
{
goto Exit;
}
*ppFile = pFile;
Exit:
if( RC_BAD( rc))
{
if( pFile)
{
flmFreeFile( pFile);
}
}
return( rc);
}
/***************************************************************************
Desc: This routine reads the header information for an existing
flaim database and makes sure we have a valid database. It
also reads the log file header record.
*****************************************************************************/
FSTATIC RCODE flmReadFileHdr(
const char * pszDbPath,
DB_STATS * pDbStats,
FFILE * pFile,
LOG_HDR * pLogHdr,
FLMBOOL bAllowLimitedMode)
{
RCODE rc = FERR_OK;
IF_FileHdl * pFileHdl = NULL;
// Read and verify the file and log headers.
if( RC_BAD( rc = gv_FlmSysData.pFileSystem->openFile( pszDbPath,
FLM_IO_RDWR | FLM_IO_SH_DENYNONE | FLM_IO_DIRECT, &pFileHdl)))
{
goto Exit;
}
if (RC_BAD( rc = flmReadAndVerifyHdrInfo( pDbStats,
pFileHdl, pFile->pucLogHdrWriteBuf,
&pFile->FileHdr, pLogHdr, NULL)))
{
goto Exit;
}
// Shove stuff into the log header area of the pFile.
// IMPORTANT NOTE! - This code assumes that DB_LOG_HEADER_START
// is found in the first 2K of the file - i.e., it will be inside
// the pFile->pucLogHdrWriteBuf that we read above.
f_memcpy( pFile->ucLastCommittedLogHdr,
&pFile->pucLogHdrWriteBuf[ DB_LOG_HEADER_START], LOG_HEADER_SIZE);
// Create the database wrapping key from the data in Log Header
#ifdef FLM_USE_NICI
if( pFile->FileHdr.uiVersionNum >= FLM_FILE_FORMAT_VER_4_60)
{
FLMUINT32 ui32KeyLen;
ui32KeyLen = FB2UW( &pFile->ucLastCommittedLogHdr[ LOG_DATABASE_KEY_LEN]);
// Looks like the database was created by a version of FLAIM that did not
// have encryption. Now we are opening it with a version that does.
// Allow the database to be opened in limited mode (i.e., no support
// for encryption).
if (ui32KeyLen == 0)
{
pFile->rcLimitedCode = FERR_ENCRYPTION_UNAVAILABLE;
pFile->bInLimitedMode = TRUE;
pFile->bHaveEncKey = FALSE;
}
else
{
// We do have an encryption key. If we end up in limited mode, it is an error
// condition from NICI that got us there.
pFile->bHaveEncKey = TRUE;
if ((pFile->pDbWrappingKey = f_new F_CCS) == NULL)
{
rc = RC_SET( FERR_MEM);
goto Exit;
}
if( RC_BAD( rc = pFile->pDbWrappingKey->init( TRUE, FLM_NICI_AES)))
{
goto Exit;
}
// If the key was encrypted in a password, then the pszDbPassword
// parameter better be the key used to encrypt it.
// If the key was not encrypted in a password, the pszDbPassword
// parameter should be NULL.
if( RC_BAD( rc = pFile->pDbWrappingKey->setKeyFromStore(
&pFile->ucLastCommittedLogHdr[LOG_DATABASE_KEY],
ui32KeyLen, pFile->pszDbPassword, NULL, FALSE)))
{
if (bAllowLimitedMode)
{
pFile->rcLimitedCode = rc;
rc = FERR_OK;
pFile->bInLimitedMode = TRUE;
}
else
{
goto Exit;
}
}
}
}
else
{
pFile->rcLimitedCode = FERR_ENCRYPTION_UNAVAILABLE;
pFile->bInLimitedMode = TRUE;
pFile->bHaveEncKey = FALSE;
}
#else
F_UNREFERENCED_PARM( bAllowLimitedMode);
pFile->rcLimitedCode = FERR_ENCRYPTION_UNAVAILABLE;
pFile->bInLimitedMode = TRUE;
pFile->bHaveEncKey = FALSE;
#endif
// Get the maximum file size. On 4.3 and greater it is stored
// in the log header. For now, we will only get this when we
// first open a database. If we ever implement code to allow
// it to be changed during a transaction, we would need to
// reset pFile->uiMaxFileSize.
pFile->uiMaxFileSize = flmGetMaxFileSize(
pFile->FileHdr.uiVersionNum,
pFile->ucLastCommittedLogHdr);
Exit:
if( pFileHdl)
{
pFileHdl->Release();
}
return( rc);
}
/***************************************************************************
Desc: This routine frees a CP_INFO structure and all associated data.
*****************************************************************************/
FSTATIC void flmFreeCPInfo(
CP_INFO ** ppCPInfoRV)
{
CP_INFO * pCPInfo;
if ((pCPInfo = *ppCPInfoRV) != NULL)
{
if (pCPInfo->pSFileHdl)
{
pCPInfo->pSFileHdl->Release();
}
if (pCPInfo->bStatsInitialized)
{
FlmFreeStats( &pCPInfo->Stats);
}
f_free( ppCPInfoRV);
}
}
/***************************************************************************
Desc: This routine begins a thread that will do checkpoints for the
passed in file. It gives the thread its own FLAIM session and its
own handle to the database.
*****************************************************************************/
RCODE flmStartCPThread(
FFILE * pFile)
{
RCODE rc = FERR_OK;
CP_INFO * pCPInfo = NULL;
char szThreadName[ F_PATH_MAX_SIZE];
char szBaseName[ F_FILENAME_SIZE];
// Allocate a CP_INFO structure that will be passed into the
// thread when it is created.
if (RC_BAD( rc = f_calloc( (FLMUINT)(sizeof( CP_INFO)), &pCPInfo)))
{
goto Exit;
}
pCPInfo->pFile = pFile;
// Allocate a super file handle.
if ((pCPInfo->pSFileHdl = f_new F_SuperFileHdl) == NULL)
{
rc = RC_SET( FERR_MEM);
goto Exit;
}
// Set up the super file
flmAssert( pFile->FileHdr.uiVersionNum);
if (RC_BAD( rc = pCPInfo->pSFileHdl->setup(
pFile->pszDbPath, pFile->pszDataDir, pFile->FileHdr.uiVersionNum)))
{
goto Exit;
}
if (RC_BAD( rc = flmStatInit( &pCPInfo->Stats, FALSE)))
{
goto Exit;
}
pCPInfo->bStatsInitialized = TRUE;
// Generate the thread name
if (RC_BAD( rc = gv_FlmSysData.pFileSystem->pathReduce( pFile->pszDbPath,
szThreadName, szBaseName)))
{
goto Exit;
}
f_sprintf( (char *)szThreadName,
"Checkpoint (%s)", (char *)szBaseName);
// Start the checkpoint thread.
if (RC_BAD( rc = f_threadCreate( &pFile->pCPThrd,
flmCPThread, szThreadName,
gv_uiCPThrdGrp, 0, pCPInfo, NULL, 32000)))
{
goto Exit;
}
pFile->pCPInfo = pCPInfo;
Exit:
if (RC_BAD( rc))
{
flmFreeCPInfo( &pCPInfo);
}
return( rc);
}
/***************************************************************************
Desc:
*****************************************************************************/
RCODE flmStartDbMonitorThread(
FFILE * pFile)
{
RCODE rc = FERR_OK;
flmAssert( pFile->pMonitorThrd == NULL);
if (RC_BAD( rc = f_threadCreate( &pFile->pMonitorThrd,
flmDbMonitor, "FLAIM Database Monitor",
gv_uiDbThrdGrp, 0, pFile, NULL, 32000)))
{
goto Exit;
}
Exit:
return( rc);
}
/****************************************************************************
Desc: This routine functions as a thread. It monitors open files and
frees up files which have been closed longer than the maximum
close time.
****************************************************************************/
FSTATIC RCODE flmCPThread(
IF_Thread * pThread)
{
RCODE rc = FERR_OK;
CP_INFO * pCPInfo = (CP_INFO *)pThread->getParm1();
FFILE * pFile = pCPInfo->pFile;
F_SuperFileHdl * pSFileHdl = pCPInfo->pSFileHdl;
FLMBOOL bTerminate = FALSE;
FLMBOOL bForceCheckpoint;
FLMINT iForceReason;
FLMUINT uiCurrTime;
DB_STATS * pDbStats;
F_SEM hWaitSem = F_SEM_NULL;
if( RC_BAD( rc = f_semCreate( &hWaitSem)))
{
goto Exit;
}
pThread->setThreadStatus( FLM_THREAD_STATUS_SLEEPING);
while (!bTerminate)
{
f_sleep( 1000);
// See if we should terminate the thread - pointer to
// super file handle should be NULL.
if( pThread->getShutdownFlag())
{
// Set terminate flag to TRUE and then see if
// we have been set up to do one final checkpoint
// to flush dirty buffers to disk.
bTerminate = TRUE;
}
// Determine if we need to force a checkpoint.
bForceCheckpoint = FALSE;
iForceReason = 0;
uiCurrTime = (FLMUINT)FLM_GET_TIMER();
if (bTerminate)
{
bForceCheckpoint = TRUE;
iForceReason = CP_SHUTTING_DOWN_REASON;
}
else if (!pFile->pRfl->seeIfRflVolumeOk() ||
RC_BAD( pFile->CheckpointRc))
{
bForceCheckpoint = TRUE;
iForceReason = CP_RFL_VOLUME_PROBLEM;
}
else if ((FLM_ELAPSED_TIME( uiCurrTime, pFile->uiLastCheckpointTime) >=
gv_FlmSysData.uiMaxCPInterval) ||
(!gv_FlmSysData.uiMaxCPInterval))
{
bForceCheckpoint = TRUE;
iForceReason = CP_TIME_INTERVAL_REASON;
}
if (gv_FlmSysData.Stats.bCollectingStats)
{
// Statistics are being collected for the system. Therefore,
// if we are not currently collecting statistics in the
// start. If we were collecting statistics, but the
// start time was earlier than the start time in the system
// statistics structure, reset the statistics.
if (!pCPInfo->Stats.bCollectingStats)
{
flmStatStart( &pCPInfo->Stats);
}
else if (pCPInfo->Stats.uiStartTime <
gv_FlmSysData.Stats.uiStartTime)
{
flmStatReset( &pCPInfo->Stats, FALSE, FALSE);
}
(void)flmStatGetDb( &pCPInfo->Stats, pFile,
0, &pDbStats, NULL, NULL);
}
else
{
pDbStats = NULL;
}
// Lock write object - If we are forcing a checkpoint
// wait until we get the lock. Otherwise, if we can't get
// the lock without waiting, don't do anything.
if (bForceCheckpoint ||
(gv_FlmSysData.SCacheMgr.uiMaxDirtyCache &&
(pFile->uiDirtyCacheCount + pFile->uiLogCacheCount) *
pFile->FileHdr.uiBlockSize >
gv_FlmSysData.SCacheMgr.uiMaxDirtyCache))
{
if( RC_BAD( pFile->pWriteLockObj->lock(
hWaitSem, TRUE, FLM_NO_TIMEOUT, 0,
pDbStats ? &pDbStats->LockStats : NULL)))
{
flmAssert( 0);
continue;
}
pThread->setThreadStatus( "Forcing checkpoint");
// Must wait for any RFL writes to complete.
(void)pFile->pRfl->seeIfRflWritesDone( TRUE);
}
else
{
if( RC_BAD( pFile->pWriteLockObj->lock(
hWaitSem, TRUE, FLM_NO_TIMEOUT, 0,
pDbStats ? &pDbStats->LockStats : NULL)))
{
continue;
}
pThread->setThreadStatus( FLM_THREAD_STATUS_RUNNING);
// See if we actually need to do the checkpoint. If the
// current transaction ID and the last checkpoint transaction
// ID are the same, no updates have occurred that would require
// a checkpoint to take place.
if (FB2UD( &pFile->ucLastCommittedLogHdr [LOG_LAST_CP_TRANS_ID]) ==
FB2UD( &pFile->ucLastCommittedLogHdr [LOG_CURR_TRANS_ID]) ||
!pFile->pRfl->seeIfRflWritesDone( FALSE))
{
pFile->pWriteLockObj->unlock();
continue;
}
}
// Do the checkpoint.
(void)ScaDoCheckpoint( pDbStats, pSFileHdl, pFile, FALSE,
bForceCheckpoint, iForceReason, 0, 0);
if (pDbStats)
{
(void)flmStatUpdate( &gv_FlmSysData.Stats, &pCPInfo->Stats);
}
pFile->pWriteLockObj->unlock();
// Unlink FDB from the FFILE - will be relinked
// by next thread that wakes us up.
f_mutexLock( gv_FlmSysData.hShareMutex);
// If we are terminating, we don't want the FFILE structure
// to appear in the not-used list, even for a moment.
// Therefore, we unlink it from that, if it got into it,
// before we unlock the mutex.
if (bTerminate)
{
flmUnlinkFileFromNUList( pFile);
}
// Unlock the mutex
f_mutexUnlock( gv_FlmSysData.hShareMutex);
// Set the thread's status
pThread->setThreadStatus( FLM_THREAD_STATUS_SLEEPING);
}
Exit:
pThread->setThreadStatus( FLM_THREAD_STATUS_TERMINATING);
if( pCPInfo)
{
flmFreeCPInfo( &pCPInfo);
}
if( hWaitSem != F_SEM_NULL)
{
f_semDestroy( &hWaitSem);
}
return( rc);
}
/****************************************************************************
Desc: Recover a database on startup.
****************************************************************************/
FSTATIC RCODE flmDoRecover(
FDB * pDb,
F_Restore * pRestoreObj)
{
RCODE rc = FERR_OK;
FFILE * pFile = pDb->pFile;
FLMBYTE * pucLastCommittedLogHdr;
// At this point, pFile->ucLastCommittedLogHdr contains the log header
// that was read from disk, which will be the state of the
// log header as of the last completed checkpoint. Therefore,
// we copy it into pFile->ucCheckpointLogHdr.
pucLastCommittedLogHdr = &pFile->ucLastCommittedLogHdr [0];
f_memcpy( pFile->ucCheckpointLogHdr, pucLastCommittedLogHdr,
LOG_HEADER_SIZE);
// Do a physical rollback on the database to restore the last
// checkpoint.
if (RC_BAD( rc = flmPhysRollback( pDb,
(FLMUINT)FB2UD( &pucLastCommittedLogHdr [LOG_ROLLBACK_EOF]),
(FLMUINT)FB2UD( &pucLastCommittedLogHdr [LOG_PL_FIRST_CP_BLOCK_ADDR]),
TRUE,
(FLMUINT)FB2UD( &pucLastCommittedLogHdr [LOG_LAST_CP_TRANS_ID]))))
{
goto Exit;
}
UD2FBA( 0, &pucLastCommittedLogHdr [LOG_PL_FIRST_CP_BLOCK_ADDR]);
UD2FBA( (FLMUINT32)pFile->FileHdr.uiBlockSize,
&pucLastCommittedLogHdr [LOG_ROLLBACK_EOF]);
if (RC_BAD( rc = flmWriteLogHdr( pDb->pDbStats, pDb->pSFileHdl,
pFile, pucLastCommittedLogHdr,
pFile->ucCheckpointLogHdr, TRUE)))
{
goto Exit;
}
// Set uiFirstLogCPBlkAddress to zero to indicate that no
// physical blocks have been logged for the current checkpoint.
// The above call to flmPhysRollback will have set the log header
// to the same thing.
pFile->uiFirstLogCPBlkAddress = 0;
// Set the ucCheckpointLogHdr to be the same as the log header
f_memcpy( pFile->ucCheckpointLogHdr, pFile->ucLastCommittedLogHdr,
LOG_HEADER_SIZE);
// Open roll forward log and redo the transactions that
// occurred since the last checkpoint, if any.
if (RC_BAD( rc = pFile->pRfl->recover( pDb, pRestoreObj)))
{
goto Exit;
}
Exit:
return( rc);
}
/**************************************************************************
Desc: See if a given database URL name is a client/server connection.
If so, return a pointer to the CS_SESSION for the URL. Otherwise,
return NULL.
**************************************************************************/
RCODE flmGetCSConnection(
const char * pszUrlName,
CS_CONTEXT * * ppCSContextRV)
{
RCODE rc = FERR_OK;
FCL_WIRE Wire;
FLMINT iSubProtocol;
CS_CONTEXT * pCSContext = NULL;
FUrl_p pUrl = NULL;
FLMINT iIPPort = 0;
FCS_BIOS * pBIStream = NULL;
FCS_BIOS * pBOStream = NULL;
FCS_DIS * pIDataStream = NULL;
FCS_DOS * pODataStream = NULL;
FLMUINT uiAddrType;
FLMUINT uiClientVersion;
*ppCSContextRV = NULL;
// Allocate a C/S context
if (RC_BAD( rc = f_calloc( sizeof( CS_CONTEXT), &pCSContext)))
{
goto Exit;
}
pCSContext->pool.poolInit( 8192);
// Create a URL out of the URL name
if ((pUrl = f_new FUrl) == NULL)
{
rc = RC_SET( FERR_MEM);
goto Exit;
}
if( RC_BAD( rc = pUrl->SetUrl( pszUrlName)))
{
goto Exit;
}
if( pUrl->IsLocal())
{
goto Exit;
}
// Determine the sub-protocol to use
iSubProtocol = pUrl->GetSubProtocol();
if( iSubProtocol == NO_SUB_PROTOCOL)
{
goto Exit;
}
// Get the address type
uiAddrType = pUrl->GetAddrType();
if( iSubProtocol == STREAM_SUB_PROTOCOL)
{
if( uiAddrType == FLM_CS_IP_ADDR)
{
iIPPort = pUrl->GetIPPort();
}
}
else
{
rc = RC_SET( FERR_NOT_IMPLEMENTED);
goto Exit;
}
if ((pIDataStream = f_new FCS_DIS) == NULL)
{
rc = RC_SET( FERR_MEM);
goto Exit;
}
if ((pODataStream = f_new FCS_DOS) == NULL)
{
rc = RC_SET( FERR_MEM);
goto Exit;
}
// Configure the I/O streams
if( iSubProtocol == STREAM_SUB_PROTOCOL)
{
if ((pBIStream = f_new FCS_BIOS) == NULL)
{
rc = RC_SET( FERR_MEM);
goto Exit;
}
if ((pBOStream = f_new FCS_BIOS) == NULL)
{
rc = RC_SET( FERR_MEM);
goto Exit;
}
pBOStream->setEventHook( flmStreamEventDispatcher,
(void *)pCSContext);
if (RC_BAD( rc = pODataStream->setup( pBOStream)))
{
goto Exit;
}
if (RC_BAD( rc = pIDataStream->setup( pBIStream)))
{
goto Exit;
}
}
else
{
rc = RC_SET( FERR_NOT_IMPLEMENTED);
goto Exit;
}
if (iSubProtocol == STREAM_SUB_PROTOCOL)
{
pCSContext->pIStream = pBIStream;
pBIStream = NULL;
pCSContext->pOStream = pBOStream;
pBOStream = NULL;
}
else
{
rc = RC_SET( FERR_NOT_IMPLEMENTED);
goto Exit;
}
pCSContext->pIDataStream = pIDataStream;
pIDataStream = NULL;
pCSContext->pODataStream = pODataStream;
pODataStream = NULL;
pCSContext->iSubProtocol = iSubProtocol;
pCSContext->uiSessionId = FCS_INVALID_ID;
f_memcpy( pCSContext->pucAddr, pUrl->GetAddress(), FLM_CS_MAX_ADDR_LEN);
f_strncpy( pCSContext->pucUrl, pszUrlName, FLM_CS_MAX_ADDR_LEN - 1);
pCSContext->pucUrl[ FLM_CS_MAX_ADDR_LEN - 1] = '\0';
// Configure the wire object
Wire.setContext( pCSContext);
uiClientVersion = FCS_VERSION_1_1_1;
Retry_Connect:
// Send a request to open a session
if (RC_BAD( rc = Wire.sendOpcode( FCS_OPCLASS_SESSION, FCS_OP_SESSION_OPEN)))
{
goto Exit;
}
if (RC_BAD( rc = Wire.sendNumber(
WIRE_VALUE_CLIENT_VERSION, FCS_VERSION_1_1_1)))
{
goto Exit;
}
if (RC_BAD( rc = Wire.sendNumber(
WIRE_VALUE_FLAGS, FCS_SESSION_GEDCOM_SUPPORT)))
{
goto Exit;
}
if (RC_BAD( rc = Wire.sendTerminate()))
{
goto Exit;
}
// Read the response
if (RC_BAD( rc = Wire.read()))
{
goto Exit;
}
if (RC_BAD( rc = Wire.getRCode()))
{
// Try a lower version number.
switch (uiClientVersion)
{
case FCS_VERSION_1_1_0:
{
break;
}
case FCS_VERSION_1_1_1:
{
uiClientVersion = FCS_VERSION_1_1_0;
goto Retry_Connect;
}
}
goto Exit;
}
pCSContext->bConnectionGood = TRUE;
pCSContext->uiSessionId = Wire.getSessionId();
pCSContext->uiSessionCookie = Wire.getSessionCookie();
pCSContext->uiServerFlaimVer = Wire.getFlaimVersion();
if( pCSContext->uiServerFlaimVer < FLM_FILE_FORMAT_VER_4_3)
{
// Versions of FLAIM prior to 4.3 did not send the server's code
// version. However, they all supported GEDCOM as a wire format.
pCSContext->bGedcomSupport = TRUE;
}
else
{
pCSContext->bGedcomSupport = (Wire.getFlags() & FCS_SESSION_GEDCOM_SUPPORT)
? TRUE
: FALSE;
}
*ppCSContextRV = pCSContext;
pCSContext = NULL;
Exit:
if (RC_BAD( rc) || pCSContext)
{
flmCloseCSConnection( &pCSContext);
}
if (pUrl)
{
pUrl->Release();
}
return( rc);
}
/**************************************************************************
Desc: Close a client/server connection.
**************************************************************************/
void flmCloseCSConnection(
CS_CONTEXT * * ppCSContext)
{
if( !(*ppCSContext))
{
return;
}
CS_CONTEXT * pCSContext = *ppCSContext;
FCL_WIRE Wire( pCSContext);
// Send a message to the FLAIM server indicating we are closing
// the session.
if( (pCSContext->uiSessionId != FCS_INVALID_ID) &&
(pCSContext->bConnectionGood))
{
if( RC_BAD( Wire.sendOpcode(
FCS_OPCLASS_SESSION, FCS_OP_SESSION_CLOSE)))
{
goto Clear_Session_ID;
}
if( RC_BAD( Wire.sendNumber(
WIRE_VALUE_SESSION_ID, pCSContext->uiSessionId)))
{
goto Clear_Session_ID;
}
if( RC_BAD( Wire.sendNumber(
WIRE_VALUE_SESSION_COOKIE, pCSContext->uiSessionCookie)))
{
goto Clear_Session_ID;
}
if( RC_BAD( Wire.sendTerminate()))
{
goto Clear_Session_ID;
}
// Read the response. Ignore the return code.
(void)Wire.read();
Clear_Session_ID:
pCSContext->uiSessionId = FCS_INVALID_ID;
}
// Free all of the input and output streams and the URL
if( pCSContext->pODataStream)
{
pCSContext->pODataStream->Release();
pCSContext->pODataStream = NULL;
}
if( pCSContext->pIDataStream)
{
pCSContext->pIDataStream->Release();
pCSContext->pIDataStream = NULL;
}
if( pCSContext->pOStream)
{
pCSContext->pOStream->Release();
pCSContext->pOStream = NULL;
}
if( pCSContext->pIStream)
{
pCSContext->pIStream->Release();
pCSContext->pIStream = NULL;
}
pCSContext->pool.poolFree();
f_free( ppCSContext);
}
/****************************************************************************
Desc:
****************************************************************************/
RCODE flmDbMonitor(
IF_Thread * pThread)
{
RCODE rc = FERR_OK;
FFILE * pFile = (FFILE *)pThread->getParm1();
FLMUINT uiLastRflEventTime = 0;
FLMUINT64 ui64LastRflEventSize = 0;
for (;;)
{
if( pThread->getShutdownFlag())
{
break;
}
if (pFile->FileHdr.uiVersionNum >= FLM_FILE_FORMAT_VER_4_61)
{
FLMUINT64 ui64RflDiskUsage;
FLMUINT64 ui64RflDiskThreshold;
FLMUINT uiRflDiskLimitSpaceFreq;
FLMUINT uiRflDiskLimitTimeFreq;
f_mutexLock( gv_FlmSysData.hShareMutex);
ui64RflDiskThreshold = ((FLMUINT64)
FB2UD( &pFile->ucLastCommittedLogHdr[
LOG_RFL_DISK_SPACE_THRESHOLD])) << 10;
uiRflDiskLimitSpaceFreq =
FB2UD( &pFile->ucLastCommittedLogHdr[ LOG_RFL_LIMIT_SPACE_FREQ]);
uiRflDiskLimitTimeFreq =
FB2UD( &pFile->ucLastCommittedLogHdr[ LOG_RFL_LIMIT_TIME_FREQ]);
ui64RflDiskUsage = pFile->ui64RflDiskUsage;
f_mutexUnlock( gv_FlmSysData.hShareMutex);
if( ui64RflDiskThreshold && ui64RflDiskUsage > ui64RflDiskThreshold)
{
FLMUINT uiCurrentTime;
char szRflDir[ F_PATH_MAX_SIZE];
char szRflPrefix[ F_FILENAME_SIZE];
f_mutexLock( gv_FlmSysData.hShareMutex);
f_strcpy( szRflDir, pFile->pRfl->getRflDirPtr());
f_strcpy( szRflPrefix, pFile->pRfl->getDbPrefixPtr());
f_mutexUnlock( gv_FlmSysData.hShareMutex);
f_timeGetSeconds( &uiCurrentTime);
if( !uiRflDiskLimitSpaceFreq && !uiRflDiskLimitTimeFreq)
{
uiRflDiskLimitTimeFreq = 30;
}
if( uiLastRflEventTime || ui64LastRflEventSize)
{
if( !uiRflDiskLimitSpaceFreq ||
(ui64LastRflEventSize && ui64RflDiskUsage <
ui64LastRflEventSize + uiRflDiskLimitSpaceFreq))
{
if( !uiRflDiskLimitTimeFreq ||
(uiLastRflEventTime && uiCurrentTime <
uiLastRflEventTime + uiRflDiskLimitTimeFreq))
{
goto DoneWithRflSizeEvent;
}
}
}
// Calculate the actual disk usage to make sure we are still
// over the limit
if( RC_BAD( rc = flmRflCalcDiskUsage( szRflDir, szRflPrefix,
pFile->FileHdr.uiVersionNum, &ui64RflDiskUsage)))
{
goto Exit;
}
if( ui64RflDiskUsage > ui64RflDiskThreshold)
{
// Log a message
flmLogMessage( F_WARN_MESSAGE, FLM_YELLOW, FLM_BLACK,
"WARNING: The RFL has exceeded the specified size limit of %i64u",
ui64RflDiskThreshold);
if( gv_FlmSysData.SizeEvents.pEventCBList)
{
FLM_RFL_SIZE_EVENT rflSizeEvent;
rflSizeEvent.pszRflDir = szRflDir;
rflSizeEvent.ui64RflDiskUsage = ui64RflDiskUsage;
flmDoEventCallback( F_EVENT_SIZE, F_EVENT_RFL_SIZE,
&rflSizeEvent, NULL);
}
uiLastRflEventTime = uiCurrentTime;
ui64LastRflEventSize = ui64RflDiskUsage;
}
else
{
// Reset the disk usage. There may have been changes since
// calling flmRflCalcDiskUsage, but this is only an estimate
// anyway.
f_mutexLock( gv_FlmSysData.hShareMutex);
pFile->ui64RflDiskUsage = ui64RflDiskUsage;
f_mutexUnlock( gv_FlmSysData.hShareMutex);
uiLastRflEventTime = 0;
ui64LastRflEventSize = 0;
}
}
DoneWithRflSizeEvent:;
}
Exit:
f_sleep( 1000);
}
return( FERR_OK);
}