#include #include #include "rw.h" #include "mapfile.h" #include "addon.h" #ifndef _ASSERT #define _ASSERT( f ) if( (f) ) ; else DebugBreak() #endif DWORD ScanWS( char* pchBegin, DWORD cb ) { // // This is a utility used when reading a newsgroup // info. from disk. // for( DWORD i=0; i < cb; i++ ) { if( pchBegin[i] == ' ' || pchBegin[i] == '\t' ) { return i+1 ; } } return 0 ; } DWORD Scan( char* pchBegin, char ch, DWORD cb ) { // // This is a utility used when reading a newsgroup // info. from disk. // for( DWORD i=0; i < cb; i++ ) { if( pchBegin[i] == ch ) { return i+1 ; } } return 0 ; } DWORD ScanEOL( char* pchBegin, DWORD cb ) { // // This is a utility used when reading a newsgroup // info. from disk. // for( DWORD i=0; i < cb; i++ ) { if( pchBegin[i] == '\n' || pchBegin[i] == '\r' ) { i++ ; return i ; } } return 0 ; } DWORD Scan( char* pchBegin, DWORD cb ) { // // This is a utility used when reading a newsgroup // info. from disk. // for( DWORD i=0; i < cb; i++ ) { if( pchBegin[i] == ' ' || pchBegin[i] == '\n' ) { return i+1 ; } } return 0 ; } DWORD ScanDigits( char* pchBegin, DWORD cb ) { // // This is a utility used when reading a newsgroup // info. from disk. // for( DWORD i=0; i < cb; i++ ) { if( pchBegin[i] == ' ' || pchBegin[i] == '\n' || pchBegin[i] == '\r' ) { return i+1 ; } if( !isdigit( pchBegin[i] ) && pchBegin[i] != '-' ) { return 0 ; } } return 0 ; } DWORD CountNonNulls( char* pchStart, DWORD cb ) { char* pchEnd = pchStart + cb ; DWORD count = 0 ; while( pchStart < pchEnd ) { if( *pchStart != '\0' ) { count++ ; } pchStart ++ ; } return count ; } CAddon::CAddon() : m_hFile( INVALID_HANDLE_VALUE ), m_pMapFile( 0 ), m_cbInuse( 0 ), m_cbAvailable( 0 ), m_cbDeleted( 0 ) { } void CAddon::ReadData() { m_Access.ShareLock() ; } void CAddon::FinishReadData() { m_Access.ShareUnlock() ; } void CAddon::ExclusiveData() { m_Access.ExclusiveLock() ; } void CAddon::UnlockExclusiveData() { m_Access.ExclusiveUnlock() ; } BOOL CAddon::Init( LPSTR lpstrAddonFile, BOOL fCompact, DWORD cbGrow ) { m_Access.ExclusiveLock() ; // // User must provide some minimum amount of padding space. // #ifdef UNIT_TEST cbGrow = min( cbGrow, 128) ; #else cbGrow = min( cbGrow, 8192 ) ; #endif #ifdef DEBUG cbGrow = min( cbGrow, 4096 ) ; #endif // // Open the file ! // HANDLE hFile = CreateFile( lpstrAddonFile, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_FLAG_RANDOM_ACCESS, INVALID_HANDLE_VALUE ) ; if( hFile == INVALID_HANDLE_VALUE ) { m_Access.ExclusiveUnlock() ; return FALSE ; } BY_HANDLE_FILE_INFORMATION fileInfo ; if( !GetFileInformationByHandle( hFile, &fileInfo ) ) { m_Access.ExclusiveUnlock() ; return FALSE ; } #ifdef DEBUG DWORD curSize = GetFileSize( hFile, NULL ); DWORD cbNewSize = curSize + cbGrow; SYSTEM_INFO si; GetSystemInfo(&si); // ensure that the new size is a multiple of the page size DWORD cbExtra = cbNewSize % si.dwPageSize ; if( cbExtra ) cbGrow += ( si.dwPageSize - cbExtra ); cbNewSize = curSize + cbGrow; _ASSERT( (cbNewSize % si.dwPageSize) == 0 ); m_pMapFile = new CMapFileEx( hFile, TRUE, FALSE, cbGrow ) ; // use guard pages in debug build #else m_pMapFile = new CMapFile( hFile, TRUE, FALSE, cbGrow ) ; #endif if( !m_pMapFile->fGood() ) { delete m_pMapFile ; m_pMapFile = 0 ; CloseHandle( hFile ) ; m_Access.ExclusiveUnlock() ; return FALSE ; } m_cbInuse = fileInfo.nFileSizeLow ; m_cbAvailable = cbGrow ; if( fCompact ) { CompactImage() ; #ifdef DEBUG { DWORD cbJunk ; char* pchBegin = (char*)m_pMapFile->pvAddress( & cbJunk ) ; _ASSERT( CountNonNulls( pchBegin, m_cbInuse ) == m_cbInuse ) ; } #endif } if( !ScanImage() ) { ResetAddons() ; delete m_pMapFile ; m_pMapFile = 0 ; CloseHandle( hFile ) ; m_cbInuse = 0 ; m_cbAvailable = 0 ; m_Access.ExclusiveUnlock() ; return FALSE ; } m_hFile = hFile ; m_Access.ExclusiveUnlock() ; return TRUE ; } BOOL CAddon::Close( BOOL fCompact ) { /*++ Routine Description : Close a CAddon object. We set ourselves to the state we are in just after our constructor is called, so we can be reused immediately. Arguments : fCompact - if TRUE the memory mapping is compressed before we close everything. Return Value : TRUE if successfull, false otherwise. --*/ m_Access.ExclusiveLock() ; if( m_pMapFile == 0 ) { m_Access.ExclusiveUnlock() ; return FALSE ; } if( fCompact ) { CompactImage() ; #ifdef DEBUG { DWORD cbJunk ; char* pchBegin = (char*)m_pMapFile->pvAddress( & cbJunk ) ; _ASSERT( CountNonNulls( pchBegin, m_cbInuse ) == m_cbInuse ) ; } #endif } if( m_cbInuse > 0 ) { DWORD cb ; LPVOID lpv = m_pMapFile->pvAddress( &cb ) ; #ifndef DEBUG FlushViewOfFile( lpv, m_cbInuse ) ; #endif } delete m_pMapFile ; m_pMapFile = 0 ; // // Set the file length to the number of bytes inuse ! // if( SetFilePointer( m_hFile, m_cbInuse, NULL, FILE_BEGIN ) == m_cbInuse ) { SetEndOfFile( m_hFile ) ; } CloseHandle( m_hFile ) ; m_hFile = INVALID_HANDLE_VALUE ; m_cbInuse = 0 ; m_cbDeleted = 0 ; m_cbAvailable = 0 ; m_Access.ExclusiveUnlock() ; return TRUE ; } BOOL CAddon::ScanImage() { /*++ Routine Description : This function parses the input file and splits the lines up. As we split up each line, we call LookupFunction to let the derived class figure out where to squirrel away the data pointer. Arguments : None. Return Value : TRUE if Successfull, FALSE otherwise --*/ if( m_pMapFile == 0 ) { return FALSE ; } DWORD cb ; char* pchBegin = (char*)(m_pMapFile->pvAddress( &cb )) ; char szBuff[ 2*MAX_PATH ] ; _ASSERT( cb == m_cbInuse + m_cbAvailable ) ; _ASSERT( cb >= m_cbInuse ) ; cb = m_cbInuse ; #ifdef DEBUG _ASSERT( m_pMapFile->UnprotectMapping() ); #endif while( cb != 0 ) { while( *pchBegin == '\0' && cb != 0 ) { pchBegin ++ ; cb -- ; } if( cb == 0 ) { break ; } DWORD cbGroup = ScanWS( pchBegin, cb ) ; DWORD cbEol = ScanEOL( pchBegin + cbGroup, cb - cbGroup ) ; if( cbGroup == 0 || cbEol == 0 ) { #ifdef DEBUG _ASSERT( m_pMapFile->ProtectMapping() ); #endif return FALSE ; } CHAR chTemp = pchBegin[cbGroup-1] ; pchBegin[cbGroup-1] = '\0' ; CHAR chTemp2 = pchBegin[cbGroup-1+cbEol] ; pchBegin[cbGroup+cbEol-1] = '\0' ; LookupFunction( pchBegin, cbGroup-1, pchBegin+cbGroup, cbEol-1, NULL ) ; pchBegin[cbGroup-1] = chTemp ; pchBegin[cbGroup+cbEol-1] = chTemp2 ; cb -= cbGroup ; pchBegin += cbGroup ; while( cbEol < cb ) { if( !(pchBegin[cbEol] == '\r' || pchBegin[cbEol] == '\n') ) break ; cbEol++ ; } cb -= cbEol ; pchBegin += cbEol ; } #ifdef DEBUG _ASSERT( m_pMapFile->ProtectMapping() ); #endif return TRUE ; } BOOL CAddon::AppendLine( LPSTR lpstrGroup, DWORD cbGroup, LPSTR lpstrData, DWORD cbData, void* lpvContext ) { /*++ Routine Description : This function adds a line of text to the memory mapping. If there is room within our current memory mapping, we will append the text to the end. Otherwise, we will try to grow the memory mapping. Arguments : lpstrGroup - Index string, starts the line of text in the file This line must not contain white space. cbGroup - size of index string lpstrData - The data for the line. May contain spaces and tabs. cbData - Length of data string lpvContext - Context value to be passed to LookupFunction(). Return Value : TRUE if successfull, FALSE otherwise. --*/ // // Validate Arguments // _ASSERT( lpstrGroup != 0 ) ; _ASSERT( cbGroup != 0 ) ; _ASSERT( lpstrData != 0 ) ; _ASSERT( cbData != 0 ) ; _ASSERT( strcspn( lpstrGroup, "\t\r\n " ) >= cbGroup ) ; _ASSERT( strcspn( lpstrData, "\t\r\n" ) >= cbData ) ; m_Access.ExclusiveLock() ; if( m_pMapFile == 0 ) { m_Access.ExclusiveUnlock() ; return FALSE ; } DWORD cb ; // // Check whether there is room to append the new string. // if( m_cbAvailable <= cbGroup + cbData + 3 /* 3 chars for CRLF 1 for TAB separator*/ ) { // // Get the old base address // LPVOID lpv = m_pMapFile->pvAddress( &cb ) ; // // Need to grow memory mapping !! // delete m_pMapFile ; // // Grow by 1 MB at a time. // DWORD minGrow = min( 1000*(cbGroup + cbData + 3), 1024 * 1024 ) ; #ifdef DEBUG DWORD curSize = GetFileSize( m_hFile, NULL ); DWORD cbNewSize = curSize + minGrow; SYSTEM_INFO si; GetSystemInfo(&si); // ensure that the new size is a multiple of the page size DWORD cbExtra = cbNewSize % si.dwPageSize ; if( cbExtra ) minGrow += ( si.dwPageSize - cbExtra ); cbNewSize = curSize + minGrow; _ASSERT( (cbNewSize % si.dwPageSize) == 0 ); m_pMapFile = new CMapFileEx( m_hFile, TRUE, FALSE, minGrow ) ; #else m_pMapFile = new CMapFile( m_hFile, TRUE, FALSE, minGrow ) ; #endif if( !m_pMapFile ) { m_Access.ExclusiveUnlock() ; return FALSE ; } if( !m_pMapFile->fGood() ) { delete m_pMapFile ; m_pMapFile = 0 ; m_Access.ExclusiveUnlock() ; return FALSE ; } m_cbAvailable += minGrow ; _ASSERT( (CountNonNulls( (char*)(m_pMapFile->pvAddress( &cb )), m_cbInuse ) + m_cbDeleted) == m_cbInuse ) ; // // Check whether the memory mapping moved - if it did we // need to rescan the file. // if( lpv != m_pMapFile->pvAddress( &cb ) ) { ResetAddons() ; ScanImage() ; } } // // Get the address where we wish to append. // CHAR* pchBegin = (CHAR*)(m_pMapFile->pvAddress( &cb )) ; CHAR* pchAppend = pchBegin + m_cbInuse ; _ASSERT( (CountNonNulls( pchBegin, m_cbInuse ) + m_cbDeleted) == m_cbInuse ) ; // // There should always be room by the time we reach here ! // _ASSERT( (cbGroup + cbData + 3) < m_cbAvailable ) ; _ASSERT( cb == m_cbInuse + m_cbAvailable ) ; #ifdef DEBUG _ASSERT( m_pMapFile->UnprotectMapping() ); #endif // // copy lpstrGroup in first and add a tab separator. // CopyMemory( pchAppend, lpstrGroup, cbGroup ) ; pchAppend[cbGroup] = '\0' ; // // Copy in the data portion and terminate with CRLF // CopyMemory( pchAppend+cbGroup+1, lpstrData, cbData ) ; pchAppend[cbGroup+cbData+1 ] = '\0' ; m_cbInuse += cbGroup+cbData+3 ; m_cbAvailable -= (cbGroup+cbData+3) ; // // Let derived class know of new data // LookupFunction( pchAppend, cbGroup, pchAppend+cbGroup+1, cbData, lpvContext ) ; pchAppend[cbGroup] = '\t' ; pchAppend[cbGroup+cbData+1 ] = '\r' ; pchAppend[cbGroup+cbData+2 ] = '\n' ; #ifdef DEBUG _ASSERT( m_pMapFile->ProtectMapping() ); #endif _ASSERT( cb == m_cbInuse + m_cbAvailable ) ; _ASSERT( (CountNonNulls( pchBegin, m_cbInuse ) + m_cbDeleted) == m_cbInuse ) ; m_Access.ExclusiveUnlock() ; return TRUE ; } BOOL CAddon::DeleteLine( LPSTR lpstrLine ) { /*++ Routine Description : Delete a line within the mapping. We will fill the entry with NULL's. We may decide to Compact the image (using CompactImage()) if after the deletion there is a substantial number of NULL's within the file. If that is the case, we will rescan the file using ScanImage(), and the derived class's LookupFunction() may be called. Arguments : lpstrLine - A pointer anywhere into the line that needs to be deleted. We will find the beginning of the line and zero the line through to the start of the next line. Return Value : TRUE if successfull, FALSE otherwise. --*/ BOOL fRtn = TRUE ; if( lpstrLine == 0 ) { return TRUE ; } m_Access.ExclusiveLock() ; if( m_pMapFile == 0 ) { m_Access.ExclusiveUnlock() ; return FALSE ; } DWORD cb = 0 ; CHAR* pchBegin = (CHAR*) m_pMapFile->pvAddress( &cb ) ; _ASSERT( (CountNonNulls( pchBegin, m_cbInuse ) + m_cbDeleted) == m_cbInuse ) ; // // Validate Arguments - insure that lpstrLine is actually in the // memory mapping. // _ASSERT( lpstrLine >= pchBegin && lpstrLine <= (pchBegin + cb) ) ; // // Check that the member variables are legit. // _ASSERT( cb == m_cbInuse + m_cbAvailable ) ; // // Find the beginning of the line. // while( lpstrLine > pchBegin && *lpstrLine != '\n' && *lpstrLine != '\0' ) { lpstrLine -- ; } // // Either we are at the start of the mapping, or at the end // of the previous line - which may also have been deleted. // _ASSERT( *lpstrLine == '\n' || *lpstrLine == '\0' || lpstrLine == pchBegin ) ; // // Advance if necessary and then zero out the line. // if( lpstrLine != pchBegin ) { lpstrLine ++ ; } #ifdef DEBUG _ASSERT( m_pMapFile->UnprotectMapping() ); #endif while( *lpstrLine != '\n' && *lpstrLine != '\0' && m_cbDeleted < m_cbInuse ) { *lpstrLine ++ = '\0'; m_cbDeleted ++ ; } while( m_cbDeleted < m_cbInuse && (*lpstrLine == '\n' || *lpstrLine == '\r') ) { *lpstrLine ++ = '\0' ; m_cbDeleted ++ ; } #ifdef DEBUG _ASSERT( m_pMapFile->ProtectMapping() ); #endif _ASSERT( (CountNonNulls( pchBegin, m_cbInuse ) + m_cbDeleted) == m_cbInuse ) ; // // Make sure we don't go past the end. // _ASSERT( lpstrLine <= pchBegin + cb ) ; // // Check to see if we've emptied the entire file ! // if( m_cbDeleted == m_cbInuse ) { m_cbInuse = 0 ; m_cbAvailable += m_cbDeleted ; m_cbDeleted = 0 ; } else if( m_cbDeleted > 16 * 1024) { // // Lots of deleted space - compact it // CompactImage() ; fRtn = ScanImage() ; } _ASSERT( (CountNonNulls( pchBegin, m_cbInuse ) + m_cbDeleted) == m_cbInuse ) ; m_Access.ExclusiveUnlock() ; return fRtn ; } void CAddon::CompactImage() { /*++ Routine Description : Remove dead space in the memory mapping. Assume that the lock is held exclusively. Arguments : None. Return Value : None. --*/ // // Notify derived class that we are moving things around. // ResetAddons() ; DWORD cb = 0 ; char* pchBegin = (char*)m_pMapFile->pvAddress( &cb ) ; if( pchBegin == 0 ) { return ; } _ASSERT( cb == m_cbAvailable + m_cbInuse ) ; char* pchEnd = pchBegin + m_cbInuse ; char* pchDest = pchBegin ; #ifdef DEBUG _ASSERT( m_pMapFile->UnprotectMapping() ); #endif while( pchBegin < pchEnd ) { if( *pchBegin != '\0' ) { *pchDest = *pchBegin ; if( pchBegin > pchDest ) { *pchBegin = '\0' ; } pchDest ++ ; } pchBegin ++ ; } #ifdef DEBUG _ASSERT( m_pMapFile->ProtectMapping() ); #endif DWORD cbDeleted = pchEnd - pchDest ; _ASSERT( cbDeleted == m_cbDeleted || m_cbDeleted == 0 ) ; m_cbInuse -= cbDeleted ; m_cbAvailable += cbDeleted ; if( m_cbDeleted != 0 ) m_cbDeleted -= cbDeleted ; }