// // The developer of the original code and/or files is Tripwire, Inc. // Portions created by Tripwire, Inc. are copyright (C) 2000 Tripwire, // Inc. Tripwire is a registered trademark of Tripwire, Inc. All rights // reserved. // // This program is free software. The contents of this file are subject // to the terms of the GNU General Public License as published by the // Free Software Foundation; either version 2 of the License, or (at your // option) any later version. You may redistribute it and/or modify it // only in compliance with the GNU General Public License. // // This program is distributed in the hope that it will be useful. // However, this program is distributed AS-IS WITHOUT ANY // WARRANTY; INCLUDING THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS // FOR A PARTICULAR PURPOSE. Please 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, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, // USA. // // Nothing in the GNU General Public License or any other license to use // the code or files shall permit you to use Tripwire's trademarks, // service marks, or other intellectual property without Tripwire's // prior written consent. // // If you have any questions, please contact Tripwire, Inc. at either // info@tripwire.org or www.tripwire.org. // /////////////////////////////////////////////////////////////////////////////// // archive.cpp -- classes that abstract a raw byte archive // // cArchive -- interface for single-direction (one pass) reads and writes // cBidirArchive -- interface for a random-access archive // cMemArchive -- implementation of a bidirectional archive in memory // cFileArchive -- implementation of a bidirectional archive as a file #include "stdcore.h" #include "archive.h" #include "fsservices.h" #include #include #include #include #include #include "file.h" #include "stringutil.h" #include "corestrings.h" // for: STR_ERR2_ARCH_CRYPTO_ERR #if FSEEK_TAKES_INT32 #define FSEEK(x, y, z) fseek((x), (int32)(y), (z)) #else #define FSEEK(x, y, z) fseek((x), (y), (z)) #endif //============================================================================= // Utility Functions //============================================================================= /////////////////////////////////////////////////////////////////////////////// // util_IsDir -- returns true if a given file is a directory /////////////////////////////////////////////////////////////////////////////// bool util_IsDir( const TSTRING& fileName ) { cFSStatArgs s; try { iFSServices::GetInstance()->Stat( fileName, s ); } catch( eFSServices ) { return false; } return( s.mFileType == cFSStatArgs::TY_DIR ); } //============================================================================= // eArchiveCrypto //============================================================================= TSTRING eArchiveCrypto::GetMsg( ) const { // RAD: Updated this to use new stringtable return ( mMsg + TSS_GetString( cCore, core::STR_ERR2_ARCH_CRYPTO_ERR ) ); } //============================================================================= // cArchive //============================================================================= // convenience methods // // Specific Read functions throw eArchive if EOF is reached because // if the caller is requesting a certain amount of data to be present, // reaching EOF is unexpected // // ReadBlob and WriteBlob return number of bytes read or written. Notice // that ReadBlob does not throw an exception since eventually EOF is expected. // // ReadBlob can take NULL as a destination pointer // // All write functions throw exceptions for unexpected events like // running out of memory or disk space. // void cArchive::ReadInt16(int16& ret) // throw(eArchive) { if (ReadBlob(&ret, sizeof(int16)) != sizeof(int16)) throw eArchiveEOF(); ret = tw_ntohs(ret); } void cArchive::ReadInt32(int32& ret) // throw(eArchive) { if (ReadBlob(&ret, sizeof(int32)) != sizeof(int32)) throw eArchiveEOF(); ret = tw_ntohl(ret); } void cArchive::ReadInt64(int64& ret) // throw(eArchive) { if (ReadBlob(&ret, sizeof(int64)) != sizeof(int64)) throw eArchiveEOF(); ret = tw_ntohll(ret); } // NOTE:BAM 10/11/99 -- we store unsigned size, but it really only works with // lengths < INT16_MAX due to sign extension in integral promotion during the // resize() in ReadString(). // format for written string: 16-bit unsigned size, then a list of 16-bit UCS2 (Unicode) characters // not including terminating NULL void cArchive::ReadString(TSTRING& ret) // throw(eArchive) { // read in size of string int16 size; ReadInt16( size ); // create buffer for WCHAR16 string wc16_string ws; ws.resize( size ); WCHAR16* pwc = (WCHAR16*)ws.data(); for( int n = 0; n < size; n++ ) { int16 i16; ReadInt16( i16 ); *pwc++ = i16; } // convert WCHAR16 string to a TSTRING ret = cStringUtil::WstrToTstr( ws ); } int cArchive::ReadBlob(void* pBlob, int count) { return Read(pBlob, count); } void cArchive::WriteInt16(int16 i) // throw(eArchive) { i = tw_htons(i); WriteBlob(&i, sizeof(int16)); } void cArchive::WriteInt32(int32 i) // throw(eArchive) { i = tw_htonl(i); WriteBlob(&i, sizeof(int32)); } void cArchive::WriteInt64(int64 i) // throw(eArchive) { i = tw_htonll(i); WriteBlob(&i, sizeof(int64)); } // NOTE:BAM 10/11/99 -- we store unsigned size, but it really only works with // lengths < INT16_MAX due to sign extension in integral promotion during the // resize() in ReadString(). // format for written string: 16-bit unsigned size, then a list of 16-bit UCS2 (Unicode) characters // not including terminating NULL void cArchive::WriteString(TSTRING s) // throw(eArchive) { // convert string to a UCS2 string wc16_string ws; cStringUtil::Convert( ws, s ); // Make convert "type-dispatched" // we assume that we can represent the size as a unsigned 16-bit number // (we actually write it as a signed number, but we cast it) if( ws.length() > TSS_INT16_MAX ) ThrowAndAssert( eArchiveStringTooLong() ); WriteInt16( static_cast( ws.length() ) ); // write out each 16 bit character // RAD:09/03/99 -- Optimized for performance with "const" wc16_string::const_iterator at = ws.begin(); while ( at != ws.end() ) WriteInt16( *at++ ); } void cArchive::WriteBlob(const void* pBlob, int count) // throw(eArchive) { if (Write(pBlob, count) < count) ThrowAndAssert(eArchiveWrite()); } int32 cArchive::GetStorageSize(const TSTRING& str) { int32 size = sizeof(int32); // the length is always stored // // after the length, all of the characters in the string are written as 16-bit values, // except for the null character // size += ( str.length() * 2 ); return size; } int64 cArchive::Copy(cArchive* pFrom, int64 amt) { enum { BUF_SIZE = 2048 }; int8 buf[BUF_SIZE]; int64 amtLeft = amt; while(amtLeft > 0) { int64 amtToRead = amtLeft > BUF_SIZE ? BUF_SIZE : amtLeft; int64 amtRead = pFrom->ReadBlob(buf, static_cast( amtToRead ) ); amtLeft -= amtRead; WriteBlob(buf, static_cast( amtRead ) ); if(amtRead < amtToRead) break; } // return the amount copied ... return (amt - amtLeft); } /////////////////////////////////////////////////////////////////////////////// // class cMemMappedArchive -- Archive that can be memory mapped. /////////////////////////////////////////////////////////////////////////////// cMemMappedArchive::cMemMappedArchive() { mpMappedMem = 0; mMappedOffset = 0; mMappedLength = 0; } cMemMappedArchive::~cMemMappedArchive() { } int64 cMemMappedArchive::GetMappedOffset() const // throw(eArchive) { if (mpMappedMem == 0) ThrowAndAssert(eArchiveMemmap()); return mMappedOffset; } int64 cMemMappedArchive::GetMappedLength() const // throw(eArchive) { if (mpMappedMem == 0) ThrowAndAssert(eArchiveMemmap()); return mMappedLength; } const void* cMemMappedArchive::GetMap() const // throw(eArchive) { if (mpMappedMem == 0) ThrowAndAssert(eArchiveMemmap()); return mpMappedMem; } void* cMemMappedArchive::GetMap() // throw(eArchive) { if (mpMappedMem == 0) ThrowAndAssert(eArchiveMemmap()); return mpMappedMem; } void cMemMappedArchive::SetNewMap(void* pMap, int64 offset, int64 length) const { if (pMap == 0) { mpMappedMem = 0; mMappedOffset = 0; mMappedLength = 0; } else { mpMappedMem = pMap; mMappedOffset = offset; mMappedLength = length; } } /////////////////////////////////////////////////////////////////////////////// // class cMemoryArchive -- An archive that stores itself in a memory buffer. // This buffer can grow as needed up until a pre-specified maximum // size. The buffer can be read and written to and can be memory // mapped. /////////////////////////////////////////////////////////////////////////////// cMemoryArchive::cMemoryArchive(int maxSize) : mMaxAllocatedLen(maxSize) { ASSERT(maxSize > 0); mpMemory = 0; mAllocatedLen = 0; mLogicalSize = 0; mReadHead = 0; } cMemoryArchive::~cMemoryArchive() { delete [] mpMemory; } bool cMemoryArchive::EndOfFile() { return mReadHead >= mLogicalSize; } void cMemoryArchive::Seek(int64 offset, SeekFrom from) // throw(eArchive) { switch (from) { case cBidirArchive::BEGINNING: break; case cBidirArchive::CURRENT: offset = mReadHead + (int)offset; break; case cBidirArchive::END: offset = mLogicalSize + (int)offset; break; default: ThrowAndAssert(eArchiveSeek(TSS_GetString( cCore, core::STR_MEMARCHIVE_FILENAME), TSS_GetString( cCore, core::STR_MEMARCHIVE_ERRSTR))); } if (offset > mLogicalSize) ThrowAndAssert(eArchiveSeek(TSS_GetString( cCore, core::STR_MEMARCHIVE_FILENAME), TSS_GetString( cCore, core::STR_MEMARCHIVE_ERRSTR))); mReadHead = static_cast( offset ); } int64 cMemoryArchive::CurrentPos() const { return mReadHead; } int64 cMemoryArchive::Length() const { return mLogicalSize; } void cMemoryArchive::Truncate() { ASSERT(mReadHead >= 0); mLogicalSize = mReadHead; AllocateMemory(mLogicalSize); } void cMemoryArchive::MapArchive(int64 offset, int64 len) // throw(eArchive) { if ( offset + (int)len > mLogicalSize ) AllocateMemory( static_cast( offset + len ) ); SetNewMap(mpMemory + offset, offset, len); } void cMemoryArchive::MapArchive(int64 offset, int64 len) const // throw(eArchive) { if (offset + (int)len > mLogicalSize) ThrowAndAssert(eArchiveMemmap()); SetNewMap(mpMemory + offset, offset, len); } int cMemoryArchive::Read(void* pDest, int count) { if (mReadHead + count > mLogicalSize) count = mLogicalSize - mReadHead; if (pDest != 0) memcpy(pDest, mpMemory + mReadHead, count); mReadHead += count; return count; } int cMemoryArchive::Write(const void* pDest, int count) // throw(eArchive) { if (mReadHead + count > mLogicalSize) { AllocateMemory(mReadHead + count); } memcpy(mpMemory + mReadHead, pDest, count); mReadHead += count; return count; } void cMemoryArchive::AllocateMemory(int len) // throw(eArchive) { const int MIN_ALLOCATED_SIZE = 1024; if (len > mAllocatedLen) { // grow the buffer // only error if we are in debug mode #ifdef _DEBUG if (len > mMaxAllocatedLen) ThrowAndAssert(eArchiveOutOfMem()); #endif if( 0 == mAllocatedLen ) mAllocatedLen = MIN_ALLOCATED_SIZE; while (mAllocatedLen < len) mAllocatedLen *= 2; int8* pNewMem = new int8[mAllocatedLen]; if (mpMemory != 0) { memcpy(pNewMem, mpMemory, mLogicalSize); delete [] mpMemory; } mpMemory = pNewMem; mLogicalSize = len; // update memory map if there is one if (mpMappedMem) SetNewMap(mpMemory + mMappedOffset, mMappedOffset, mMappedLength); } else { // check for memory map conflict if (mpMappedMem && len < mMappedOffset + mMappedLength) ThrowAndAssert(eArchiveMemmap()); if (len < (mAllocatedLen >> 1) && mAllocatedLen > MIN_ALLOCATED_SIZE) { // shrink the buffer int8* pNewMem = new int8[len]; ASSERT(mpMemory); memcpy(pNewMem, mpMemory, len); delete [] mpMemory; mpMemory = pNewMem; mLogicalSize = len; // update memory map if there is one if (mpMappedMem) SetNewMap(mpMemory + mMappedOffset, mMappedOffset, mMappedLength); } else { // no need to grow or shrink mLogicalSize = len; } } } /* class cFixedMemArchive : public cBidirArchive { public: int8* mpMemory; int32 mSize; int32 mReadHead; }; */ //----------------------------------------------------------------------------- // cFixedMemArchive //----------------------------------------------------------------------------- cFixedMemArchive::cFixedMemArchive() : mpMemory (0), mSize (0), mReadHead (0) { } cFixedMemArchive::cFixedMemArchive( int8* pMem, int32 size ) : mpMemory (0), mSize (0), mReadHead (0) { Attach( pMem, size ); } cFixedMemArchive::~cFixedMemArchive() { } void cFixedMemArchive::Attach( int8* pMem, int32 size ) { mpMemory = pMem; mSize = size; mReadHead = 0; } void cFixedMemArchive::Seek(int64 offset, SeekFrom from) // throw(eArchive) { switch (from) { case cBidirArchive::BEGINNING: break; case cBidirArchive::CURRENT: offset = mReadHead + (int)offset; break; case cBidirArchive::END: offset = mSize + (int)offset; break; default: ThrowAndAssert(eArchiveSeek(TSS_GetString( cCore, core::STR_MEMARCHIVE_FILENAME), TSS_GetString( cCore, core::STR_MEMARCHIVE_ERRSTR))); } if (offset > mSize) ThrowAndAssert(eArchiveSeek(TSS_GetString( cCore, core::STR_MEMARCHIVE_FILENAME), TSS_GetString( cCore, core::STR_MEMARCHIVE_ERRSTR))); mReadHead = static_cast( offset ); } int64 cFixedMemArchive::CurrentPos() const { return mReadHead; } int64 cFixedMemArchive::Length() const { return mSize; } bool cFixedMemArchive::EndOfFile() { return (mReadHead >= mSize); } int cFixedMemArchive::Read(void* pDest, int count) // throw(eArchive) { ASSERT( pDest ); if (mReadHead + count > mSize) { count = static_cast( mSize - mReadHead ); if (count <= 0) return 0; } if (pDest != 0) memcpy(pDest, mpMemory + mReadHead, count); mReadHead += count; return count; } int cFixedMemArchive::Write(const void* pDest, int count) // throw(eArchive) { if (mReadHead + count > mSize) { ASSERT( false ); throw eArchiveWrite(); } memcpy(mpMemory + mReadHead, pDest, count); mReadHead += count; return count; } /////////////////////////////////////////////////////////////////////////////// // class cFileArchive -- Archive for files... /////////////////////////////////////////////////////////////////////////////// //Ctor -- Initialize member variables to 0 or NULL equivalents. cFileArchive::cFileArchive() : mFileSize(0), mReadHead(0), isWritable(false) {} cFileArchive::~cFileArchive() { } bool cFileArchive::EndOfFile() { return ( mReadHead >= mFileSize ); } //////////////////////////////////////////////////////////////////////// // Seek -- This is where the actual offset is performed. The default // for each archive will be 0. ///////////////////////////////////////////////////////////////////////// void cFileArchive::Seek( int64 offset, SeekFrom from) // throw(eArchive) { try { switch (from) { case cBidirArchive::BEGINNING: break; case cBidirArchive::CURRENT: offset = mReadHead + offset; break; case cBidirArchive::END: offset = mFileSize + offset; break; default: throw eArchiveSeek( mCurrentFilename, iFSServices::GetInstance()->GetErrString() ) ; } if ( offset > mFileSize ) throw eArchiveSeek( mCurrentFilename, iFSServices::GetInstance()->GetErrString() ) ; mReadHead = offset; mCurrentFile.Seek(mReadHead, cFile::SEEK_BEGIN); //This is where the actual read/writehead is set!! }//try catch( eFile& fileError ) { throw( eArchiveSeek( mCurrentFilename, fileError.GetDescription() ) ); } } int64 cFileArchive::CurrentPos(void) const { return mReadHead; } ///////////////////////////////////////////////////////////////////////// // Length -- Returns the size of the current file archive. ///////////////////////////////////////////////////////////////////////// int64 cFileArchive::Length(void) const { try { return mCurrentFile.GetSize(); } catch(eFile& fileError) { throw( eArchiveSeek( mCurrentFilename, fileError.GetDescription() ) ); } } ///////////////////////////////////////////////////////////////////////// // OpenRead -- Opens the file to be read only. ///////////////////////////////////////////////////////////////////////// void cFileArchive::OpenRead(const TCHAR* filename, uint32 openFlags) { try { // set up open flags uint32 flags = cFile::OPEN_READ; flags |= ( ( openFlags & FA_OPEN_TRUNCATE ) ? cFile::OPEN_TRUNCATE : 0 ); flags |= ( ( openFlags & FA_OPEN_TEXT ) ? cFile::OPEN_TEXT : 0 ); flags |= ( ( openFlags & FA_NONBLOCKING ) ? cFile::OPEN_NONBLOCKING :0 ); mCurrentFilename = filename; mCurrentFile.Open( filename, flags ); isWritable = false; mFileSize = mCurrentFile.GetSize(); mReadHead = mCurrentFile.Seek( 0, cFile::SEEK_BEGIN ); } catch(eFile& fileError) { throw(eArchiveOpen( mCurrentFilename, fileError.GetDescription() ) ); } } ///////////////////////////////////////////////////////////////////////// // OpenReadWrite -- Opens the file to be read or written to ///////////////////////////////////////////////////////////////////////// void cFileArchive::OpenReadWrite(const TCHAR* filename, uint32 openFlags) { try { // set up open flags uint32 flags = cFile::OPEN_WRITE; flags |= ( ( openFlags & FA_OPEN_TRUNCATE ) ? cFile::OPEN_TRUNCATE : 0 ); flags |= ( ( openFlags & FA_OPEN_TEXT ) ? cFile::OPEN_TEXT : 0 ); flags |= ( ( openFlags & FA_NONBLOCKING ) ? cFile::OPEN_NONBLOCKING :0 ); mCurrentFilename = filename; mCurrentFile.Open( filename, flags ); isWritable = true; mFileSize = mCurrentFile.GetSize(); mReadHead = mCurrentFile.Seek( 0, cFile::SEEK_BEGIN ); } catch(eFile& fileError) { throw( eArchiveOpen( mCurrentFilename, fileError.GetDescription() ) ); } } ///////////////////////////////////////////////////////////////////////// // GetCurrentFilename -- Returns the name of the file currently associated // with the FileArchive. ///////////////////////////////////////////////////////////////////////// TSTRING cFileArchive::GetCurrentFilename(void) const { return mCurrentFilename; } ///////////////////////////////////////////////////////////////////////// // Close -- Closes the file currently referenced by mpCurrStream ///////////////////////////////////////////////////////////////////////// void cFileArchive::Close() { try { mCurrentFile.Close(); mFileSize = 0; mReadHead = 0; mCurrentFilename = _T(""); } catch(eFile& fileError) { throw( eArchive( mCurrentFilename, fileError.GetDescription() ) ); } } ///////////////////////////////////////////////////////////////////////// // Read -- Read places bytes in location designated by pDest. Returns // The actual amount read into *pDest. ///////////////////////////////////////////////////////////////////////// int cFileArchive::Read(void* pDest, int count) { try { if ( mReadHead + count > mFileSize ) count = static_cast( mFileSize - mReadHead ); if ( pDest != NULL ) { int nbRead = static_cast( mCurrentFile.Read( pDest, count ) ); // 'count' may not be equal to 'nbRead' if the file is open in // text mode. count = nbRead; if(count < 0) count = 0; } else { int i; int32 dummy; for (i = count; ; i -= sizeof(int32)) { if (i < (int)sizeof(int32)) { if (i > 0) mCurrentFile.Read( &dummy, i ); break; } mCurrentFile.Read( &dummy, i ); } } mReadHead += count; return count; } catch( eFile& fileError ) { throw( eArchiveRead( mCurrentFilename, fileError.GetDescription() ) ); } } ///////////////////////////////////////////////////////////////////////// // Write -- Writes to file designated by fh. If isWritable is not set, // function returns 0. Otherwise, the actual # written is returned. ///////////////////////////////////////////////////////////////////////// int cFileArchive::Write(const void* pDest, int count) // throw(eArchive) { try { int64 actual_count = 0; ASSERT( mCurrentFile.isWritable ); actual_count = mCurrentFile.Write( pDest, count ); if ( actual_count < count ) { //Disk full?? throw eArchiveWrite( mCurrentFilename, iFSServices::GetInstance()->GetErrString() ) ; } // increment the read/write head mReadHead += actual_count; // increase the size, if needed if( mReadHead > mFileSize ) { #if 0 // IS_SUNPRO // These two lines seem to be all there is between code that crashes and code that works for sunpro cDebug d("cFileArchive::Write()"); d.TraceDebug(_T("file(%s) adjusted mFileSize = %d mReadHead = %d\n"), mCurrentFilename.c_str(), (int)mFileSize, (int)mReadHead); #endif mFileSize = mReadHead; } return (int)actual_count; } catch( eFile& fileError ) { throw( eArchiveWrite( mCurrentFilename, fileError.GetDescription() ) ); } } ///////////////////////////////////////////////////////////////////////// // Truncate ///////////////////////////////////////////////////////////////////////// void cFileArchive::Truncate() // throw(eArchive) { ASSERT( mCurrentFile.IsOpen() ); ASSERT( mCurrentFile.isWritable ); try { mCurrentFile.Truncate ( mReadHead ); } catch( eFile& fileError ) { //TODO: create an error number for truncate... throw( eArchiveWrite( mCurrentFilename, fileError.GetDescription() ) ); } mFileSize = mReadHead; } ///////////////////////////////////////////////////////////////////////// // OpenReadWrite -- Opens the file to be read or written to // // since we'll never open an existing file, the truncateFile flag is unnecessary. ///////////////////////////////////////////////////////////////////////// void cLockedTemporaryFileArchive::OpenReadWrite( const TCHAR* filename, uint32 openFlags ) { TSTRING strTempFile; try { ASSERT( !mCurrentFile.IsOpen() ); // shouldn't be able to create a new file when we're already open if ( mCurrentFile.IsOpen() ) throw( eArchive( mCurrentFilename, _T("Internal Error") ) ); /////////////////////////////////////////////////////////////////////////////// // if filename is NULL, create a temp file for the caller if( filename == NULL ) { try { iFSServices::GetInstance()->GetTempDirName( strTempFile ); strTempFile += _T("twtempXXXXXX"); iFSServices::GetInstance()->MakeTempFilename( strTempFile ); } catch( eFSServices& fileError) { TSTRING errStr = TSS_GetString( cCore, core::STR_BAD_TEMPDIRECTORY ); throw eArchiveOpen(strTempFile, errStr); } } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // create file // set up flags uint32 flags = cFile::OPEN_WRITE | cFile::OPEN_LOCKED_TEMP | cFile::OPEN_CREATE | cFile::OPEN_EXCLUSIVE; if ( openFlags & FA_OPEN_TRUNCATE ) flags |= cFile::OPEN_TRUNCATE; if ( openFlags & FA_OPEN_TEXT ) flags |= cFile::OPEN_TEXT; // open file mCurrentFilename = filename ? filename : strTempFile.c_str(); mCurrentFile.Open( mCurrentFilename, flags ); isWritable = true; mFileSize = mCurrentFile.GetSize(); mReadHead = mCurrentFile.Seek( 0, cFile::SEEK_BEGIN ); #if 0 // IS_SUNPRO cDebug d("cLockedTemporaryFileArchive::OpenReadWrite()"); d.TraceDebug(_T("file(%s) set mFileSize to %d mReadHead to %d\n"), mCurrentFilename.c_str(), (int)mFileSize, (int)mReadHead); #endif }//try catch (eFile& fileError) { TSTRING errStr = TSS_GetString( cCore, core::STR_BAD_TEMPDIRECTORY ); eArchiveOpen e(strTempFile, errStr); throw e; } /////////////////////////////////////////////////////////////////////////////// } ///////////////////////////////////////////////////////////////////////// // Close -- Closes the file currently referenced by fh void cLockedTemporaryFileArchive::Close() { // Note: this deletes the file as well cFileArchive::Close(); }