//------------------------------------------------------------------------- // Desc: Check a database via HTTP monitoring. // Tabs: 3 // // Copyright (c) 2002-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: imonchk.cpp 12329 2006-01-20 17:49:30 -0700 (Fri, 20 Jan 2006) ahodgkinson $ //------------------------------------------------------------------------- #include "flaimsys.h" #define CHECK_FORM_NAME "CheckForm" #define DATABASE_NAME_FIELD "databasename" #define DATA_DIR_FIELD "datadir" #define RFL_DIR_FIELD "rfldir" #define LOG_FILE_NAME_FIELD "logfilename" #define CHECK_INDEXES_FIELD "checkindexes" #define REPAIR_INDEXES_FIELD "repairindexes" #define DETAILED_STATS_FIELD "detailedstats" FSTATIC void format64Num( FLMUINT64 ui64Num, char * pszNum); FSTATIC RCODE copyStr( char ** ppszDestStr, const char * pszSrcStr); FSTATIC void copyNames( CHECK_STATUS * pDestCheckStatus, CHECK_STATUS * pSrcCheckStatus); FSTATIC void freeCheckStatus( CHECK_STATUS * pCheckStatus, FLMBOOL bFreeStruct); FSTATIC void imonLogField( IF_FileHdl * pLogFile, F_NameTable * pNameTable, FlmRecord * pRecord, void * pvField, FLMUINT uiStartCol, FLMUINT uiLevelOffset); FSTATIC void imonLogKeyError( IF_FileHdl * pLogFile, F_NameTable * pNameTable, CORRUPT_INFO * pCorrupt); FSTATIC void imonLogCorruptError( IF_FileHdl * pLogFile, F_NameTable * pNameTable, CORRUPT_INFO * pCorrupt); FSTATIC RCODE CheckStatusCB( eStatusType eStatus, void * pvParm1, void * pvParm2, void * pvAppData); FSTATIC RCODE imonDoCheck( IF_Thread * pThread); FSTATIC void imonLogStr( IF_FileHdl * pLogFile, FLMUINT uiIndent, const char * pszStr); /**************************************************************************** Desc: Prints the web page for checking a database. ****************************************************************************/ RCODE F_CheckDbPage::display( FLMUINT uiNumParams, const char ** ppszParams) { RCODE rc = FERR_OK; const char * pszErrType = NULL; RCODE runRc = FERR_OK; F_Session * pFlmSession = m_pFlmSession; HFDB hDb = HFDB_NULL; F_NameTable * pNameTable = NULL; char szTmp[ 32]; char * pszTmp; char * pszOperation = NULL; char * pszDbName = NULL; char * pszDataDir = NULL; char * pszRflDir = NULL; FLMBOOL bCheckingIndexes = FALSE; FLMBOOL bRepairingIndexes = FALSE; FLMBOOL bDetailedStatistics = FALSE; char * pszLogFileName = NULL; FLMBOOL bPerformCheck = FALSE; FLMBOOL bStopCheck = FALSE; FLMUINT uiCheckThreadId; CHECK_STATUS CheckStatus; char szDbKey[ F_SESSION_DB_KEY_LEN]; f_memset( &CheckStatus, 0, sizeof( CHECK_STATUS)); // Acquire a FLAIM session if (!pFlmSession) { rc = RC_SET( m_uiSessionRC); goto ReportErrorExit; } // Get the database handle, if any if( RC_BAD( rc = getDatabaseHandleParam( uiNumParams, ppszParams, pFlmSession, &hDb, szDbKey))) { hDb = HFDB_NULL; } else { if( IsInCSMode( hDb)) { rc = RC_SET( FERR_NOT_IMPLEMENTED); goto ReportErrorExit; } if (RC_BAD( rc = pFlmSession->getNameTable( hDb, &pNameTable))) { goto ReportErrorExit; } } // Get the value of the Operation field, if present. getFormValueByName( "Operation", &pszOperation, 0, NULL); if (pszOperation) { if (f_stricmp( pszOperation, OPERATION_CHECK) == 0) { bPerformCheck = TRUE; } else if (f_stricmp( pszOperation, OPERATION_STOP) == 0) { bStopCheck = TRUE; } } // Get the database name, if any if (getFormValueByName( DATABASE_NAME_FIELD, &pszDbName, 0, NULL) == 0) { if (pszDbName && *pszDbName) { fcsDecodeHttpString( pszDbName); } } // Get the database directory, if any if (getFormValueByName( DATA_DIR_FIELD, &pszDataDir, 0, NULL) == 0) { if (pszDataDir && *pszDataDir) { fcsDecodeHttpString( pszDataDir); } } // Get the RFL directory, if any if (getFormValueByName( RFL_DIR_FIELD, &pszRflDir, 0, NULL) == 0) { if (pszRflDir && *pszRflDir) { fcsDecodeHttpString( pszRflDir); } } // Get the log file name, if any if (getFormValueByName( LOG_FILE_NAME_FIELD, &pszLogFileName, 0, NULL) == 0) { if (pszLogFileName && *pszLogFileName) { fcsDecodeHttpString( pszLogFileName); } } // Get the flag for whether or not to check indexes. szTmp [0] = 0; pszTmp = &szTmp [0]; if( RC_BAD( getFormValueByName( CHECK_INDEXES_FIELD, &pszTmp, sizeof( szTmp), NULL))) { if( RC_BAD( ExtractParameter( uiNumParams, ppszParams, CHECK_INDEXES_FIELD, sizeof( szTmp), szTmp))) { szTmp [0] = 0; } } if (f_strcmp( szTmp, "yes") == 0) { bCheckingIndexes = TRUE; } // Get the flag for whether or not to repair indexes szTmp [0] = 0; pszTmp = &szTmp [0]; if (RC_BAD( getFormValueByName( REPAIR_INDEXES_FIELD, &pszTmp, sizeof( szTmp), NULL))) { if( RC_BAD( ExtractParameter( uiNumParams, ppszParams, REPAIR_INDEXES_FIELD, sizeof( szTmp), szTmp))) { szTmp [0] = 0; } } if (f_strcmp( szTmp, "yes") == 0) { bRepairingIndexes = TRUE; } // Get the flag for whether or not to collect detailed statistics. szTmp [0] = 0; pszTmp = &szTmp [0]; if (RC_BAD( getFormValueByName( DETAILED_STATS_FIELD, &pszTmp, sizeof( szTmp), NULL))) { if (RC_BAD( ExtractParameter( uiNumParams, ppszParams, DETAILED_STATS_FIELD, sizeof( szTmp), szTmp))) { szTmp [0] = 0; } } if (f_strcmp( szTmp, "yes") == 0) { bDetailedStatistics = TRUE; } // See if we had a check running. Get the check thread ID // if any. szTmp [0] = '\0'; uiCheckThreadId = 0; if (RC_OK( ExtractParameter( uiNumParams, ppszParams, "Running", sizeof( szTmp), szTmp))) { if (szTmp [0]) { uiCheckThreadId = f_atoud( szTmp); CheckStatus.bCheckRunning = TRUE; } } if (bPerformCheck) { // Better not have both bCheckRunning and bPerformCheck set! flmAssert( !CheckStatus.bCheckRunning); if (RC_BAD( runRc = runCheck( pFlmSession, &hDb, szDbKey, pszDbName, pszDataDir, pszRflDir, pszLogFileName, bCheckingIndexes, bRepairingIndexes, bDetailedStatistics, &uiCheckThreadId))) { pszErrType = "RUNNING CHECK"; } else { CheckStatus.bCheckRunning = TRUE; } } // Stop the check, if requested, or get the check data. if (CheckStatus.bCheckRunning) { // getCheckStatus could change CheckStatus.bCheckRunning // to FALSE. getCheckStatus( uiCheckThreadId, bStopCheck, &CheckStatus); } // Output the web page. if (!CheckStatus.bCheckRunning && CheckStatus.bHaveCheckStatus) { // If we have check results, output a page for viewing/editing them. printDocStart( "Check Results"); } else if (!CheckStatus.bCheckRunning) { printDocStart( "Run Check"); if (pszErrType) { fnPrintf( m_pHRequest, "
ERROR %04X (%s) %s

\n", (unsigned)runRc, FlmErrorString( runRc), pszErrType); } } else { stdHdr(); fnPrintf( m_pHRequest, HTML_DOCTYPE); fnPrintf( m_pHRequest, "\n" "\n"); printStyle(); // Output html that will cause a refresh to occur. fnPrintf( m_pHRequest, "" "Check Status\n", m_pszURLString, (unsigned)uiCheckThreadId, szDbKey); fnPrintf( m_pHRequest, "\n" "\n"); } // Output the form for entering the check parameters and // check status. outputCheckForm( hDb, szDbKey, &CheckStatus, pNameTable, uiCheckThreadId); // End the document printDocEnd(); Exit: fnEmit(); if (pszOperation) { f_free( &pszOperation); } if (pszDbName) { f_free( &pszDbName); } if (pszDataDir) { f_free( &pszDataDir); } if (pszRflDir) { f_free( &pszRflDir); } if (pszLogFileName) { f_free( &pszLogFileName); } freeCheckStatus( &CheckStatus, FALSE); return( FERR_OK); ReportErrorExit: printErrorPage( rc); goto Exit; } /**************************************************************************** Desc: Output a string parameter. ****************************************************************************/ void F_CheckDbPage::outputStrParam( CHECK_STATUS * pCheckStatus, FLMBOOL bHighlight, const char * pszParamName, const char * pszFieldName, FLMUINT uiMaxValueLen, const char * pszFieldValue) { printTableRowStart( bHighlight); printTableDataStart( TRUE, JUSTIFY_LEFT, 35); fnPrintf( m_pHRequest, "%s", pszParamName); printTableDataEnd(); printTableDataStart( TRUE, JUSTIFY_LEFT, 65); if (pCheckStatus->bCheckRunning || !pszFieldName) { if (pszFieldValue) { printEncodedString( pszFieldValue, HTML_ENCODING); } else { fnPrintf( m_pHRequest, " "); } } else { fnPrintf( m_pHRequest, "bHaveCheckStatus && pszFieldValue && *pszFieldValue) { fnPrintf( m_pHRequest, " value=\"", pszFieldValue); printEncodedString( pszFieldValue, HTML_ENCODING); fnPrintf( m_pHRequest, "\">\n"); } else { fnPrintf( m_pHRequest, ">\n"); } } printTableDataEnd(); printTableRowEnd(); } /**************************************************************************** Desc: Output a flag parameter. ****************************************************************************/ void F_CheckDbPage::outputFlagParam( CHECK_STATUS * pCheckStatus, FLMBOOL bHighlight, const char * pszParamName, const char * pszFieldName, FLMBOOL bFieldValue) { printTableRowStart( bHighlight); if (pCheckStatus->bCheckRunning) { printTableDataStart( TRUE, JUSTIFY_LEFT, 35); fnPrintf( m_pHRequest, "%s", pszParamName); printTableDataEnd(); printTableDataStart( TRUE, JUSTIFY_LEFT, 65); fnPrintf( m_pHRequest, "%s", (char *)(bFieldValue ? "yes" : "no")); printTableDataEnd(); } else { printTableDataStart( TRUE, JUSTIFY_LEFT, 35); fnPrintf( m_pHRequest, "bHaveCheckStatus && bFieldValue) { fnPrintf( m_pHRequest, " checked"); } fnPrintf( m_pHRequest, " value=\"yes\"> %s\n", pszParamName); printTableDataEnd(); printTableDataStart( TRUE, JUSTIFY_LEFT, 65); fnPrintf( m_pHRequest, " "); printTableDataEnd(); } printTableRowEnd(); } /**************************************************************************** Desc: format a 64 bit number with commas. ****************************************************************************/ FSTATIC void format64Num( FLMUINT64 ui64Num, char * pszNum ) { FLMUINT uiNums [15]; FLMUINT uiNumNums = 0; FLMBOOL bFirstNum; // Format the number with commas. do { uiNums [uiNumNums++] = (FLMUINT)(ui64Num % (FLMUINT64)1000); ui64Num /= 1000; } while (ui64Num); bFirstNum = TRUE; while (uiNumNums) { uiNumNums--; if (bFirstNum) { f_sprintf( pszNum, "%u", (unsigned)uiNums [uiNumNums]); bFirstNum = FALSE; } else { f_sprintf( pszNum, ",%03u", (unsigned)uiNums [uiNumNums]); } while (*pszNum) { pszNum++; } } } /**************************************************************************** Desc: Output a flag parameter. ****************************************************************************/ void F_CheckDbPage::outputNum64Param( FLMBOOL bHighlight, const char * pszParamName, FLMUINT64 ui64Num) { char szNum [60]; printTableRowStart( bHighlight); printTableDataStart( TRUE, JUSTIFY_LEFT, 35); fnPrintf( m_pHRequest, "%s", pszParamName); printTableDataEnd(); // Format the number with commas format64Num( ui64Num, szNum); // Output the number printTableDataStart( TRUE, JUSTIFY_LEFT, 65); fnPrintf( m_pHRequest, "%s", szNum); printTableDataEnd(); printTableRowEnd(); } /**************************************************************************** Desc: Output the form for the user to run a check. ****************************************************************************/ void F_CheckDbPage::outputCheckForm( HFDB hDb, const char * pszDbKey, CHECK_STATUS * pCheckStatus, F_NameTable * pNameTable, FLMUINT uiCheckThreadId) { FLMBOOL bHighlight = FALSE; char szTmp [128]; char * pszTmp; char * pszName; IF_FileHdl * pFileHdl = NULL; fnPrintf( m_pHRequest, "
bCheckRunning) { fnPrintf( m_pHRequest, "?Running=%u&dbhandle=%s\">\n", (unsigned)uiCheckThreadId, pszDbKey); } else { if (hDb != HFDB_NULL) { fnPrintf( m_pHRequest, "?dbhandle=%s\">\n", pszDbKey); } else { fnPrintf( m_pHRequest, "\">\n"); } } printStartCenter(); if (pCheckStatus->bCheckRunning) { printTableStart( "CHECK PROGRESS", 2, 75); } else if (pCheckStatus->bHaveCheckStatus) { printTableStart( "CHECK RESULTS", 2, 75); } else { printTableStart( "CHECK PARAMETERS", 2, 74); } // Column headers printTableRowStart(); printColumnHeading( "Parameter", JUSTIFY_LEFT, NULL, 1, 1, TRUE, 35); printColumnHeading( "Value", JUSTIFY_LEFT, NULL, 1, 1, TRUE, 65); printTableRowEnd(); if (hDb == HFDB_NULL) { // Output the database name outputStrParam( pCheckStatus, bHighlight = !bHighlight, "Database Name", DATABASE_NAME_FIELD, F_PATH_MAX_SIZE + 1, pCheckStatus->pszDbName); // Output the data directory outputStrParam( pCheckStatus, bHighlight = !bHighlight, "Data Directory", DATA_DIR_FIELD, F_PATH_MAX_SIZE + 1, pCheckStatus->pszDataDir); // Output the rfl directory outputStrParam( pCheckStatus, bHighlight = !bHighlight, "RFL Directory", RFL_DIR_FIELD, F_PATH_MAX_SIZE + 1, pCheckStatus->pszRflDir); } else { FDB * pDb = (FDB *)hDb; // Output the database name outputStrParam( pCheckStatus, bHighlight = !bHighlight, "Database Name", NULL, 0, pDb->pFile->pszDbPath); outputStrParam( pCheckStatus, bHighlight = !bHighlight, "Data Directory", NULL, 0, pDb->pFile->pszDataDir); } // Output the log file name outputStrParam( pCheckStatus, bHighlight = !bHighlight, "Log File Name", LOG_FILE_NAME_FIELD, F_PATH_MAX_SIZE + 1, pCheckStatus->pszLogFileName); // Output the checking indexes flag. outputFlagParam( pCheckStatus, bHighlight = !bHighlight, "Check Indexes", CHECK_INDEXES_FIELD, pCheckStatus->bCheckingIndexes); // Output the repairing indexes flag. outputFlagParam( pCheckStatus, bHighlight = !bHighlight, "Repair Indexes", REPAIR_INDEXES_FIELD, pCheckStatus->bRepairingIndexes); #if 0 // Output the collecting detailed stats flag. outputFlagParam( pCheckStatus, bHighlight = !bHighlight, "Detailed Statistics", DETAILED_STATS_FIELD, pCheckStatus->bDetailedStatistics); #endif if (pCheckStatus->bHaveCheckStatus) { // Output what we are currently doing switch (pCheckStatus->Progress.iCheckPhase) { case CHECK_LFH_BLOCKS: pszTmp = (char *)"LFH BLOCKS"; break; case CHECK_B_TREE: pszTmp = &szTmp [0]; if (pCheckStatus->Progress.uiLfType == LF_INDEX) { if (pCheckStatus->Progress.bUniqueIndex) { f_strcpy( pszTmp, "UNIQUE INDEX: "); } else { f_strcpy( pszTmp, "INDEX: "); } } else { f_strcpy( pszTmp, "CONTAINER: "); } pszName = &pszTmp [f_strlen( pszTmp)]; if (!pNameTable || !pNameTable->getFromTagNum( pCheckStatus->Progress.uiLfNumber, NULL, pszName, sizeof( szTmp) - (pszName - &szTmp [0]))) { f_sprintf( pszName, "#%u", (unsigned)pCheckStatus->Progress.uiLfNumber); } else { f_sprintf( &pszTmp [f_strlen( pszTmp)], " (%u)", (unsigned)pCheckStatus->Progress.uiLfNumber); } break; case CHECK_AVAIL_BLOCKS: pszTmp = (char *)"AVAIL BLOCKS"; break; case CHECK_RS_SORT: pszTmp = (char *)"SORTING INDEX KEYS"; break; default: pszTmp = &szTmp [0]; f_sprintf( pszTmp, "UNKNOWN: %u", (unsigned)pCheckStatus->Progress.iCheckPhase); break; } outputStrParam( pCheckStatus, bHighlight = !bHighlight, "Doing", NULL, 0, pszTmp); // Output various statistics as we go outputNum64Param( bHighlight = !bHighlight, "Database Size", pCheckStatus->Progress.ui64DatabaseSize); if (pCheckStatus->Progress.iCheckPhase == CHECK_RS_SORT) { FLMUINT uiPercent = 0; if (pCheckStatus->Progress.ui64NumRSUnits > (FLMUINT64)0) { uiPercent = (FLMUINT)((pCheckStatus->Progress.ui64NumRSUnitsDone * (FLMUINT64)100) / pCheckStatus->Progress.ui64NumRSUnits); } outputNum64Param( bHighlight = !bHighlight, "Percent Sorted", (FLMUINT64)uiPercent); } else { outputNum64Param( bHighlight = !bHighlight, "Bytes Checked", pCheckStatus->Progress.ui64BytesExamined); } outputNum64Param( bHighlight = !bHighlight, "Total Index Keys", pCheckStatus->Progress.ui64NumKeys); outputNum64Param( bHighlight = !bHighlight, "Num. Keys Checked", pCheckStatus->Progress.ui64NumKeysExamined); outputNum64Param( bHighlight = !bHighlight, "Invalid Index Keys", pCheckStatus->Progress.ui64NumKeysNotFound); outputNum64Param( bHighlight = !bHighlight, "Missing Index Keys", pCheckStatus->Progress.ui64NumRecKeysNotFound); outputNum64Param( bHighlight = !bHighlight, "Non-unique Index Keys", pCheckStatus->Progress.ui64NumNonUniqueKeys); outputNum64Param( bHighlight = !bHighlight, "Key Conflicts", pCheckStatus->Progress.ui64NumConflicts); outputNum64Param( bHighlight = !bHighlight, "Total Corruptions", (FLMUINT64)pCheckStatus->uiCorruptCount); outputNum64Param( bHighlight = !bHighlight, "Problems Repaired", (FLMUINT64)pCheckStatus->Progress.uiNumProblemsFixed); outputNum64Param( bHighlight = !bHighlight, "Old View Count", (FLMUINT64)pCheckStatus->uiOldViewCount); // Output the return status if the check is finished. if (!pCheckStatus->bCheckRunning) { if (pCheckStatus->CheckRc == FERR_OK) { pszTmp = (char *)"Database OK"; } else if (pCheckStatus->CheckRc == FERR_USER_ABORT) { pszTmp = (char *)"User Halted"; } else { pszTmp = &szTmp [0]; f_sprintf( pszTmp, "Error %04X, (%s)", (unsigned)pCheckStatus->CheckRc, FlmErrorString( pCheckStatus->CheckRc)); } outputStrParam( pCheckStatus, bHighlight = !bHighlight, "Check Status", NULL, 0, pszTmp); } } // End the table printTableEnd(); printEndCenter( FALSE); fnPrintf( m_pHRequest, "
\n"); // Output the setOperation function printSetOperationScript(); printStartCenter(); if (!pCheckStatus->bCheckRunning) { // If we are not running a check, add a Perform Check button printOperationButton( CHECK_FORM_NAME, "Perform Check", OPERATION_CHECK); } else { // If we are running a check, output a stop button. printOperationButton( CHECK_FORM_NAME, "Stop Check", OPERATION_STOP); } printEndCenter( TRUE); // Close the form fnPrintf( m_pHRequest, "
\n"); // If the check is done, and we have a log file, output it. if (!pCheckStatus->bCheckRunning && pCheckStatus->bHaveCheckStatus && pCheckStatus->uiCorruptCount && pCheckStatus->pszLogFileName) { fnPrintf( m_pHRequest, "

------LOG FILE CONTENTS------\n");

		// Open the log file

		if (RC_OK( gv_FlmSysData.pFileSystem->openFile( 
			pCheckStatus->pszLogFileName, FLM_IO_RDWR | FLM_IO_SH_DENYNONE,
			&pFileHdl)))
		{
			RCODE		rc;
			FLMUINT	uiBytesRead;

			// Read and output until we run out of data

			for (;;)
			{
				if (RC_BAD( rc = pFileHdl->read( FLM_IO_CURRENT_POS,
						sizeof( szTmp) - 1, szTmp, &uiBytesRead)))
				{
					if (rc != FERR_IO_END_OF_FILE || !uiBytesRead)
					{
						break;
					}
				}
				if (uiBytesRead)
				{
					szTmp [uiBytesRead] = 0;
					fnPrintf( m_pHRequest, "%s", szTmp);
				}
				if (uiBytesRead < sizeof( szTmp) - 1)
				{
					break;
				}
			}
			
			pFileHdl->Release();
			pFileHdl = NULL;
		}
		
		fnPrintf( m_pHRequest, "\n------END OF LOG FILE------\n");
		fnPrintf( m_pHRequest, "
\n"); } if( pFileHdl) { pFileHdl->Release(); } } /**************************************************************************** Desc: Copy one string into another - allocating memory if needed. ****************************************************************************/ FSTATIC RCODE copyStr( char ** ppszDestStr, const char * pszSrcStr) { RCODE rc = FERR_OK; FLMUINT uiLen; if (pszSrcStr && *pszSrcStr) { uiLen = f_strlen( pszSrcStr) + 1; if (RC_BAD( rc = f_alloc( uiLen, ppszDestStr))) { goto Exit; } f_memcpy( *ppszDestStr, pszSrcStr, uiLen); } else { *ppszDestStr = NULL; } Exit: return( rc); } /**************************************************************************** Desc: Copy the database names, etc. from one check status to another. ****************************************************************************/ FSTATIC void copyNames( CHECK_STATUS * pDestCheckStatus, CHECK_STATUS * pSrcCheckStatus) { (void)copyStr( &pDestCheckStatus->pszDbName, pSrcCheckStatus->pszDbName); (void)copyStr( &pDestCheckStatus->pszDataDir, pSrcCheckStatus->pszDataDir); (void)copyStr( &pDestCheckStatus->pszRflDir, pSrcCheckStatus->pszRflDir); (void)copyStr( &pDestCheckStatus->pszLogFileName, pSrcCheckStatus->pszLogFileName); } /**************************************************************************** Desc: Free a CHECK_STATUS structure and all associated memory. ****************************************************************************/ FSTATIC void freeCheckStatus( CHECK_STATUS * pCheckStatus, FLMBOOL bFreeStruct ) { f_free( &pCheckStatus->pszDbName); f_free( &pCheckStatus->pszDataDir); f_free( &pCheckStatus->pszRflDir); f_free( &pCheckStatus->pszLogFileName); if (bFreeStruct) { if (pCheckStatus->hDb != HFDB_NULL) { FlmDbClose( &pCheckStatus->hDb); } if (pCheckStatus->pLogFile) { pCheckStatus->pLogFile->Release(); pCheckStatus->pLogFile = NULL; } if (pCheckStatus->pNameTable) { pCheckStatus->pNameTable->Release(); pCheckStatus->pNameTable = NULL; } f_free( &pCheckStatus); } } /**************************************************************************** Desc: Run a database check. ****************************************************************************/ RCODE F_CheckDbPage::runCheck( F_Session * pFlmSession, HFDB * phDb, char * pszDbKey, const char * pszDbName, const char * pszDataDir, const char * pszRflDir, const char * pszLogFileName, FLMBOOL bCheckingIndexes, FLMBOOL bRepairingIndexes, FLMBOOL bDetailedStatistics, FLMUINT * puiCheckThreadId) { RCODE rc = FERR_OK; CHECK_STATUS * pCheckStatus = NULL; IF_Thread * pThread; HFDB hDb = HFDB_NULL; FDB * pDb; if (*phDb == HFDB_NULL) { // Open the database if (RC_BAD( rc = FlmDbOpen( pszDbName, pszDataDir, pszRflDir, 0, NULL, phDb))) { goto Exit; } else { // Insert the handle into the session if (RC_BAD( rc = pFlmSession->addDbHandle( *phDb, pszDbKey))) { FlmDbClose( phDb); goto Exit; } } } else { pDb = (FDB *)(*phDb); pszDbName = pDb->pFile->pszDbPath; if ((pszDataDir = pDb->pFile->pszDataDir) != NULL) { if (!(*pszDataDir)) { pszDataDir = NULL; } } pszRflDir = NULL; } // Open the database for the thread - so it doesn't have // to worry about the handle going away. The thread will close the // new handle when it exits. if (RC_BAD( rc = flmOpenFile( ((FDB *)(*phDb))->pFile, NULL, NULL, NULL, 0, TRUE, NULL, NULL, (((FDB *)(*phDb))->pFile)->pszDbPassword, &pDb))) { goto Exit; } hDb = (HFDB)pDb; // Create an object to track the check. if (RC_BAD( rc = f_calloc( sizeof( CHECK_STATUS), &pCheckStatus))) { goto Exit; } pCheckStatus->hDb = hDb; // Set hDb to HFDB_NULL so it won't be closed below. hDb = HFDB_NULL; // Copy database names. if (RC_BAD( rc = copyStr( &pCheckStatus->pszDbName, pszDbName))) { goto Exit; } if (RC_BAD( rc = copyStr( &pCheckStatus->pszDataDir, pszDataDir))) { goto Exit; } if (RC_BAD( rc = copyStr( &pCheckStatus->pszRflDir, pszRflDir))) { goto Exit; } if (RC_BAD( rc = copyStr( &pCheckStatus->pszLogFileName, pszLogFileName))) { goto Exit; } // Create the log file, if one was specified. if (pCheckStatus->pszLogFileName) { gv_FlmSysData.pFileSystem->deleteFile( pCheckStatus->pszLogFileName); if (RC_BAD( gv_FlmSysData.pFileSystem->createFile( pCheckStatus->pszLogFileName, FLM_IO_RDWR | FLM_IO_SH_DENYNONE, &pCheckStatus->pLogFile))) { f_free( &pCheckStatus->pszLogFileName); } } // Get a name table for the database - if we can. if ((pCheckStatus->pNameTable = f_new F_NameTable) != NULL) { if (RC_BAD( pCheckStatus->pNameTable->setupFromDb( hDb))) { pCheckStatus->pNameTable->Release(); pCheckStatus->pNameTable = NULL; } } pCheckStatus->bCheckingIndexes = bCheckingIndexes; pCheckStatus->bRepairingIndexes = bRepairingIndexes; pCheckStatus->bDetailedStatistics = bDetailedStatistics; pCheckStatus->bCheckRunning = TRUE; pCheckStatus->uiLastTimeBrowserChecked = FLM_GET_TIMER(); // If browser does not check status at least every 15 seconds, we will // assume it has gone away and the thread will terminate itself. pCheckStatus->uiCheckTimeout = FLM_SECS_TO_TIMER_UNITS( 15); // Start a thread to do the check. if (RC_BAD( rc = f_threadCreate( &pThread, imonDoCheck, "WEB DB CHECK", gv_uiDbThrdGrp, 1, (void *)pCheckStatus, (void *)hDb))) { goto Exit; } *puiCheckThreadId = pThread->getThreadId(); // Set pCheckStatus to NULL so it won't be freed below. The thread // will free it when it stops. pCheckStatus = NULL; Exit: if (pThread) { pThread->Release(); } if (pCheckStatus) { freeCheckStatus( pCheckStatus, TRUE); } if (hDb != HFDB_NULL) { FlmDbClose( &hDb); } return( rc); } /**************************************************************************** Desc: Output the current thread status to the web page. ****************************************************************************/ void F_CheckDbPage::getCheckStatus( FLMUINT uiCheckThreadId, FLMBOOL bStopCheck, CHECK_STATUS * pCheckStatus ) { FLMUINT uiThreadId; IF_Thread * pThread = NULL; CHECK_STATUS * pThreadCheckStatus; FLMBOOL bMutexLocked = FALSE; // pCheckStatus->bHaveCheckStatus should be set to FALSE by the caller. flmAssert( !pCheckStatus->bHaveCheckStatus); // See if the thread is still running. f_mutexLock( gv_FlmSysData.hShareMutex); bMutexLocked = TRUE; uiThreadId = 0; for (;;) { if (RC_BAD( gv_FlmSysData.pThreadMgr->getNextGroupThread( &pThread, gv_uiDbThrdGrp, &uiThreadId))) { pCheckStatus->bCheckRunning = FALSE; goto Exit; } if (uiThreadId == uiCheckThreadId) { // If the app ID is zero, the thread is on its way out or already // out. Can no longer get thread status. if (!pThread->getThreadAppId()) { pCheckStatus->bCheckRunning = FALSE; goto Exit; } // Found thread, get its check status data pThreadCheckStatus = (CHECK_STATUS *)pThread->getParm1(); pThreadCheckStatus->uiLastTimeBrowserChecked = FLM_GET_TIMER(); // Tell the thread to stop the check before telling it // to stop. This is so we can get partial results. if (bStopCheck) { pThreadCheckStatus->bStopCheck = TRUE; // Go into a while loop, waiting for the thread // to finish its check. while (pThreadCheckStatus->bCheckRunning) { f_mutexUnlock( gv_FlmSysData.hShareMutex); bMutexLocked = FALSE; f_sleep( 200); f_mutexLock( gv_FlmSysData.hShareMutex); bMutexLocked = TRUE; // If the thread app ID goes to zero, it has been // told to shut down, and has either already gone // away or is in the process of doing so, in which // case pThreadCheckStatus has either already been // deleted, or will be - so it is not safe to access // it any more! if (!pThread->getThreadAppId()) { pCheckStatus->bCheckRunning = FALSE; goto Exit; } } } break; } pThread->Release(); pThread = NULL; } // Mutex better still be locked at this point. flmAssert( bMutexLocked); // Note that we test pThreadCheckStatus->bCheckRunning BEFORE // doing the memcpy. This is because puiDrnList is not guaranteed // to be set until bCheckRunning is FALSE. If bCheckRunning is TRUE, // we will NULL out whatever got copied into puiDrnList. if (!pThreadCheckStatus->bCheckRunning) { f_memcpy( pCheckStatus, pThreadCheckStatus, sizeof( CHECK_STATUS)); copyNames( pCheckStatus, pThreadCheckStatus); // Need to unlock the mutex so that the thread can stop. f_mutexUnlock( gv_FlmSysData.hShareMutex); bMutexLocked = FALSE; pThread->stopThread(); } else { f_memcpy( pCheckStatus, pThreadCheckStatus, sizeof( CHECK_STATUS)); copyNames( pCheckStatus, pThreadCheckStatus); // Set bCheckRunning to TRUE. This takes care of a race // race condition of pThreadCheckStatus->bCheckRunning getting // set to FALSE by the check thread after we test it above. // we make the test on pThreadCheckStatus->bCheckRunning. We will // simply get that fact next time we get status. pCheckStatus->bCheckRunning = TRUE; } // NULL out certain members so we won't attempt to use them. They // may go away if the background thread has gone away. pCheckStatus->hDb = HFDB_NULL; pCheckStatus->pLogFile = NULL; pCheckStatus->pNameTable = NULL; pCheckStatus->bHaveCheckStatus = TRUE; Exit: if (bMutexLocked) { f_mutexUnlock( gv_FlmSysData.hShareMutex); bMutexLocked = FALSE; } if (pThread) { pThread->Release(); } } /******************************************************************** Desc: Log a string to the log file. *********************************************************************/ FSTATIC void imonLogStr( IF_FileHdl * pLogFile, FLMUINT uiIndent, const char * pszStr) { char szBuffer [100]; FLMUINT uiLoop; FLMUINT uiBytesWritten; if ((uiLoop = uiIndent) != 0) { f_memset( szBuffer, ' ', uiIndent); uiLoop = uiIndent; } if (pszStr) { while (*pszStr) { if (uiLoop == sizeof( szBuffer)) { pLogFile->write( FLM_IO_CURRENT_POS, uiLoop, szBuffer, &uiBytesWritten); uiLoop = 0; } szBuffer [uiLoop++] = *pszStr; pszStr++; } } if (uiLoop >= sizeof( szBuffer) - 2) { pLogFile->write( FLM_IO_CURRENT_POS, uiLoop, szBuffer, &uiBytesWritten); uiLoop = 0; } szBuffer [uiLoop++] = '\r'; szBuffer [uiLoop++] = '\n'; pLogFile->write( FLM_IO_CURRENT_POS, uiLoop, szBuffer, &uiBytesWritten); } /*************************************************************************** Desc: Log a field's data. *****************************************************************************/ FSTATIC void imonLogField( IF_FileHdl * pLogFile, F_NameTable * pNameTable, FlmRecord * pRecord, void * pvField, FLMUINT uiStartCol, FLMUINT uiLevelOffset) { char szTmpBuf [200]; char * pszTmp; FLMUINT uiFieldNum; FLMUINT uiLen; FLMUINT uiBinLen; FLMUINT uiTmpLen; FLMBYTE * pucTmp; FLMBYTE ucTmpBin [80]; FLMUINT uiNum; FLMUINT uiLevel = pRecord->getLevel( pvField) + uiLevelOffset; FLMUINT uiIndent = (uiLevel * 2) + uiStartCol; // Insert leading spaces to indent for level if (uiIndent) { f_memset(szTmpBuf, ' ', uiIndent); } // Output level and tag f_sprintf( &szTmpBuf [uiIndent], "%u ", (unsigned)uiLevel); pszTmp = &szTmpBuf [f_strlen( szTmpBuf)]; uiFieldNum = pRecord->getFieldID( pvField); if (!pNameTable || !pNameTable->getFromTagNum( uiFieldNum, NULL, pszTmp, sizeof( szTmpBuf) - (pszTmp - &szTmpBuf [0]))) { f_sprintf( pszTmp, "#%u", (unsigned)uiFieldNum); } // Output what will fit of the value on the rest of the line uiLen = f_strlen( szTmpBuf); szTmpBuf [uiLen++] = ' '; szTmpBuf [uiLen] = 0; if (!pRecord->getDataLength( pvField)) { goto Exit; } switch (pRecord->getDataType( pvField)) { case FLM_TEXT_TYPE: pszTmp = &szTmpBuf [uiLen]; uiLen = 80 - uiLen; pRecord->getNative( pvField, pszTmp, &uiLen); break; case FLM_NUMBER_TYPE: pRecord->getUINT( pvField, &uiNum); f_sprintf( &szTmpBuf [uiLen], "%u", (unsigned)uiNum); break; case FLM_BINARY_TYPE: pRecord->getBinaryLength( pvField, &uiBinLen); uiTmpLen = sizeof( ucTmpBin); pRecord->getBinary( pvField, ucTmpBin, &uiTmpLen); pucTmp = &ucTmpBin [0]; while (uiBinLen && uiLen < 77) { f_sprintf( &szTmpBuf [uiLen], "%02X ", (unsigned)*pucTmp); uiBinLen--; pucTmp++; uiLen += 3; } szTmpBuf [uiLen - 1] = 0; break; case FLM_CONTEXT_TYPE: pRecord->getUINT( pvField, &uiNum); f_sprintf( &szTmpBuf[ uiLen], "@%u@", (unsigned)uiNum); break; } Exit: imonLogStr( pLogFile, 0, szTmpBuf); } /******************************************************************** Desc: Log an index key corruption error. *********************************************************************/ FSTATIC void imonLogKeyError( IF_FileHdl * pLogFile, F_NameTable * pNameTable, CORRUPT_INFO * pCorrupt) { FLMUINT uiLogItem; FlmRecord * pRecord = NULL; void * pvField; REC_KEY * pTempKeyList = NULL; FLMUINT uiIndent; FLMUINT uiLevelOffset; char szNameBuf [128]; char szTmpBuf [128]; if (!pNameTable || !pNameTable->getFromTagNum( pCorrupt->uiErrLfNumber, NULL, szNameBuf, sizeof( szNameBuf))) { f_sprintf( (char *)szNameBuf, "#%u", (unsigned)pCorrupt->uiErrLfNumber); } imonLogStr( pLogFile, 0, NULL); imonLogStr( pLogFile, 0, NULL); f_sprintf( szTmpBuf, "ERROR IN INDEX: %s", szNameBuf); imonLogStr( pLogFile, 0, szTmpBuf); uiLogItem = 'R'; uiLevelOffset = 0; for (;;) { uiIndent = 2; if (uiLogItem == 'K') { if ((pRecord = pCorrupt->pErrIxKey) == NULL) { uiLogItem = 'L'; continue; } imonLogStr( pLogFile, 0, NULL); imonLogStr( pLogFile, 0, " PROBLEM KEY"); } else if (uiLogItem == 'R') { if ((pRecord = pCorrupt->pErrRecord) == NULL) { uiLogItem = 'K'; continue; } imonLogStr( pLogFile, 0, NULL); imonLogStr( pLogFile, 0, " RECORD"); } else if (uiLogItem == 'L') { if ((pTempKeyList = pCorrupt->pErrRecordKeyList) == NULL) { break; } pRecord = pTempKeyList->pKey; imonLogStr( pLogFile, 0, NULL); imonLogStr( pLogFile, 0, " RECORD KEYS"); imonLogStr( pLogFile, 0, " 0 Key"); uiLevelOffset = 1; } for (pvField = pRecord->root();;) { if (!pvField) { if (uiLogItem != 'L') { break; } if ((pTempKeyList = pTempKeyList->pNextKey) == NULL) { break; } pRecord = pTempKeyList->pKey; pvField = pRecord->root(); imonLogStr( pLogFile, 0, " 0 Key"); continue; } else { imonLogField( pLogFile, pNameTable, pRecord, pvField, uiIndent, uiLevelOffset); } pvField = pRecord->next( pvField); } if (uiLogItem == 'L') { break; } else if (uiLogItem == 'R') { uiLogItem = 'K'; } else { uiLogItem = 'L'; } } } /******************************************************************** Desc: Log corruptions to log file. *********************************************************************/ FSTATIC void imonLogCorruptError( IF_FileHdl * pLogFile, F_NameTable * pNameTable, CORRUPT_INFO * pCorrupt) { char szWhat [20]; char szTmpBuf [100]; switch (pCorrupt->eErrLocale) { case LOCALE_LFH_LIST: imonLogStr( pLogFile, 0, "ERROR IN LFH LINKED LIST:"); break; case LOCALE_AVAIL_LIST: imonLogStr( pLogFile, 0, "ERROR IN AVAIL LINKED LIST:"); break; case LOCALE_B_TREE: if (pCorrupt->eCorruption == FLM_OLD_VIEW) { imonLogStr( pLogFile, 0, "OLD VIEW"); } else { if (pCorrupt->uiErrFieldNum) { f_strcpy( szWhat, "FIELD"); } else if (pCorrupt->uiErrElmOffset) { f_strcpy( szWhat, "ELEMENT"); } else if (pCorrupt->uiErrBlkAddress) { f_strcpy( szWhat, "BLOCK"); } else { f_strcpy( szWhat, "LAST BLOCK"); } f_sprintf( szTmpBuf, "BAD %s", szWhat); imonLogStr( pLogFile, 0, szTmpBuf); } // Log the logical file number, name, and type f_sprintf( szTmpBuf, "Logical File Number: %u", (unsigned)pCorrupt->uiErrLfNumber); imonLogStr( pLogFile, 2, szTmpBuf); switch( pCorrupt->uiErrLfType) { case LF_CONTAINER: f_strcpy( szWhat, "Container"); break; case LF_INDEX: f_strcpy( szWhat, "Index"); break; default: f_sprintf( (char *)szWhat, "?%u", (unsigned)pCorrupt->uiErrLfType); break; } f_sprintf( szTmpBuf, "Logical File Type: %s", szWhat); imonLogStr( pLogFile, 2, szTmpBuf); // Log the level in the B-Tree, if known if (pCorrupt->uiErrBTreeLevel != 0xFF) { f_sprintf( szTmpBuf, "Level in B-Tree: %u", (unsigned)pCorrupt->uiErrBTreeLevel); imonLogStr( pLogFile, 2, szTmpBuf); } break; case LOCALE_IXD_TBL: f_sprintf( szTmpBuf, "ERROR IN IXD TABLE, Index Number: %u", (unsigned)pCorrupt->uiErrLfNumber); imonLogStr( pLogFile, 0, szTmpBuf); break; case LOCALE_INDEX: f_strcpy( szWhat, "Index"); imonLogKeyError( pLogFile, pNameTable, pCorrupt); break; default: pCorrupt->eErrLocale = LOCALE_NONE; break; } // Log the block address, if known if (pCorrupt->uiErrBlkAddress) { f_sprintf( szTmpBuf, "Block Address: 0x%08X (%u)", (unsigned)pCorrupt->uiErrBlkAddress, (unsigned)pCorrupt->uiErrBlkAddress); imonLogStr( pLogFile, 2, szTmpBuf); } // Log the parent block address, if known if (pCorrupt->uiErrParentBlkAddress) { if (pCorrupt->uiErrParentBlkAddress != 0xFFFFFFFF) { f_sprintf( szTmpBuf, "Parent Block Address: 0x%08X (%u)", (unsigned)pCorrupt->uiErrParentBlkAddress, (unsigned)pCorrupt->uiErrParentBlkAddress); } else { f_sprintf( szTmpBuf, "Parent Block Address: NONE, Root Block"); } imonLogStr( pLogFile, 2, szTmpBuf); } // Log the element offset, if known if (pCorrupt->uiErrElmOffset) { f_sprintf( szTmpBuf, "Element Offset: %u", (unsigned)pCorrupt->uiErrElmOffset); imonLogStr( pLogFile, 2, szTmpBuf); } // Log the record number, if known if (pCorrupt->uiErrDrn) { f_sprintf( szTmpBuf, "Record Number: %u", (unsigned)pCorrupt->uiErrDrn); imonLogStr( pLogFile, 2, szTmpBuf); } // Log the offset within the element record, if known if (pCorrupt->uiErrElmRecOffset != 0xFFFF) { f_sprintf( szTmpBuf, "Offset Within Element: %u", (unsigned)pCorrupt->uiErrElmRecOffset); imonLogStr( pLogFile, 2, szTmpBuf); } // Log the field number, if known if (pCorrupt->uiErrFieldNum) { f_sprintf( szTmpBuf, "Field Number: %u", (unsigned)pCorrupt->uiErrFieldNum); imonLogStr( pLogFile, 2, szTmpBuf); } f_strcpy( szTmpBuf, FlmVerifyErrToStr( pCorrupt->eCorruption)); f_sprintf( &szTmpBuf[ f_strlen( szTmpBuf)], " (%d)", (int)pCorrupt->eCorruption); imonLogStr( pLogFile, 2, szTmpBuf); imonLogStr( pLogFile, 0, NULL); pLogFile->flush(); } /*************************************************************************** Desc: Check status callback. ***************************************************************************/ FSTATIC RCODE CheckStatusCB( eStatusType eStatus, void * pvParm1, void * pvParm2, void * pvAppData) { RCODE rc = FERR_OK; FLMUINT uiCurrTime; CORRUPT_INFO * pCorrupt; CHECK_STATUS * pCheckStatus = (CHECK_STATUS *)pvAppData; uiCurrTime = FLM_GET_TIMER(); if (pCheckStatus->bStopCheck) { rc = RC_SET( FERR_USER_ABORT); goto Exit; } else if (FLM_ELAPSED_TIME( uiCurrTime, pCheckStatus->uiLastTimeBrowserChecked) >= pCheckStatus->uiCheckTimeout) { rc = RC_SET( FERR_TIMEOUT); goto Exit; } // Handle each of the status types if (eStatus == FLM_PROBLEM_STATUS) { FLMBOOL * pbFixCorruptions = (FLMBOOL *)pvParm2; pCorrupt = (CORRUPT_INFO *)pvParm1; if (pCheckStatus->pLogFile && pCorrupt->eCorruption != FLM_OLD_VIEW) { imonLogCorruptError( pCheckStatus->pLogFile, pCheckStatus->pNameTable, pCorrupt); } if (pCorrupt->eCorruption == FLM_OLD_VIEW) { pCheckStatus->uiOldViewCount++; } else { pCheckStatus->uiCorruptCount++; } if (pbFixCorruptions) { *pbFixCorruptions = pCheckStatus->bRepairingIndexes; } } else if (eStatus == FLM_CHECK_STATUS) { // Capture the progress information. f_memcpy( &pCheckStatus->Progress, pvParm1, sizeof( DB_CHECK_PROGRESS)); // Update thread status if (FLM_ELAPSED_TIME( uiCurrTime, pCheckStatus->uiLastTimeSetStatus) >= pCheckStatus->uiUpdateStatusInterval) { if (pCheckStatus->Progress.iCheckPhase == CHECK_RS_SORT) { FLMUINT uiPercent = 0; if (pCheckStatus->Progress.ui64NumRSUnits > (FLMUINT64)0) { uiPercent = (FLMUINT)((pCheckStatus->Progress.ui64NumRSUnitsDone * (FLMUINT64)100) / pCheckStatus->Progress.ui64NumRSUnits); } pCheckStatus->pThread->setThreadStatus( "Sorting, %u percent done", (unsigned)uiPercent); } else { char szFileSize [60]; char szBytesDone [60]; format64Num( pCheckStatus->Progress.ui64DatabaseSize, szFileSize); format64Num( pCheckStatus->Progress.ui64BytesExamined, szBytesDone); pCheckStatus->pThread->setThreadStatus( "%s of %s bytes checked", szBytesDone, szFileSize); } pCheckStatus->uiLastTimeSetStatus = uiCurrTime; } } Exit: return( rc); } /**************************************************************************** Desc: Thread to perform a database check for a web page. ****************************************************************************/ FSTATIC RCODE imonDoCheck( IF_Thread * pThread) { RCODE rc; CHECK_STATUS * pCheckStatus = (CHECK_STATUS *)pThread->getParm1(); FLMUINT uiFlags; F_Pool pool; DB_CHECK_PROGRESS CheckProgress; FLMUINT uiCurrTime; pThread->setThreadStatus( FLM_THREAD_STATUS_INITIALIZING); pCheckStatus->pThread = pThread; pCheckStatus->uiUpdateStatusInterval = FLM_SECS_TO_TIMER_UNITS( 5); uiFlags = FLM_CHK_FIELDS; if (pCheckStatus->bCheckingIndexes) { uiFlags |= FLM_CHK_INDEX_REFERENCING; } pThread->setThreadStatus( FLM_THREAD_STATUS_RUNNING); pool.poolInit( 512); rc = FlmDbCheck( pCheckStatus->hDb, NULL, NULL, NULL, uiFlags, &pool, &CheckProgress, CheckStatusCB, pCheckStatus); pool.poolFree(); // Close the database and log file before doing anything else. FlmDbClose( &pCheckStatus->hDb); if (pCheckStatus->pLogFile) { pCheckStatus->pLogFile->Release(); pCheckStatus->pLogFile = NULL; } pCheckStatus->CheckRc = rc; pCheckStatus->bCheckRunning = FALSE; if (RC_BAD( rc)) { if (rc == FERR_USER_ABORT) { // Callback forced us to quit. pThread->setThreadStatus( "User halted"); } else if (rc == FERR_TIMEOUT) { pThread->setThreadStatus( "Timed out"); goto Exit; } else { pThread->setThreadStatus( "Check Error %04X,", (unsigned)rc); } } // Wait for the user to tell us to quit. for (;;) { // See if we should shut down. if (pThread->getShutdownFlag()) { // Transaction will be aborted below pThread->setThreadStatus( FLM_THREAD_STATUS_TERMINATING); goto Exit; } // See if we timed out uiCurrTime = FLM_GET_TIMER(); if (FLM_ELAPSED_TIME( uiCurrTime, pCheckStatus->uiLastTimeBrowserChecked) >= pCheckStatus->uiCheckTimeout) { goto Exit; } // Pause one second f_sleep( 1000); } Exit: // Set the thread's app ID to 0, so that it will not // be found now that the thread is terminating (we don't // want getCheckStatus() to find the thread). pThread->setThreadAppId( 0); // Free the check status. Must do inside mutex lock so // that it doesn't go away after getCheckStatus finds the // thread. f_mutexLock( gv_FlmSysData.hShareMutex); freeCheckStatus( pCheckStatus, TRUE); f_mutexUnlock( gv_FlmSysData.hShareMutex); return( FERR_OK); }