// // 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. // /////////////////////////////////////////////////////////////////////////////// // hierdatabase.cpp #include "stddb.h" #include "hierdatabase.h" #include "hierdbnode.h" #include "core/archive.h" #include "core/upperbound.h" #include "core/errorbucket.h" // TODO -- all of these util_ functions should throw an eArchive if an attempt is made to // write to a null address static inline void util_ThrowIfNull( const cHierAddr& addr, const TSTRING& context ) { ASSERT( ! addr.IsNull() ); if( addr.IsNull() ) { TSTRING msg(_T("Attempt to access null address")); if( ! context.empty() ) { msg += _T(" in ") + context; } throw eHierDatabase( msg ); } } /////////////////////////////////////////////////////////////////////////////// // util_ReadObject -- this will read the given object from the database from the // given address. eArchive is thrown on exception. /////////////////////////////////////////////////////////////////////////////// static void util_ReadObject( cHierDatabase* pDb, cHierNode* pNode, const cHierAddr& addr ) { int32 dataSize; int8* pData = pDb->GetDataForReading( cBlockRecordFile::tAddr(addr.mBlockNum, addr.mIndex), dataSize ); // // make sure we aren't trying to read a null object // util_ThrowIfNull( addr, _T("util_ReadObject") ); cFixedMemArchive arch( pData, dataSize ); pNode->Read( arch ); } /////////////////////////////////////////////////////////////////////////////// // util_WriteObject -- this will write the given object to the database, returning // the address that it was written to, and throwing eArchive on error. /////////////////////////////////////////////////////////////////////////////// static cHierAddr util_WriteObject( cHierDatabase* pDb, cHierNode* pNode ) { static cMemoryArchive arch; arch.Seek( 0, cBidirArchive::BEGINNING ); pNode->Write( arch ); cBlockRecordFile::tAddr addr = pDb->AddItem( arch.GetMemory(), arch.CurrentPos() ); return cHierAddr( addr.mBlockNum, addr.mIndex ); } /////////////////////////////////////////////////////////////////////////////// // util_RewriteObject -- call this when the data in a structure has changed, but // its size has not; this will update the database without changing the // object's address. /////////////////////////////////////////////////////////////////////////////// static void util_RewriteObject( cHierDatabase* pDb, cHierNode* pNode, const cHierAddr& addr ) { int32 dataSize; int8* pData = pDb->GetDataForWriting( cBlockRecordFile::tAddr(addr.mBlockNum, addr.mIndex), dataSize ); util_ThrowIfNull( addr, _T("util_RewriteObject") ); cFixedMemArchive arch( pData, dataSize ); pNode->Write( arch ); } /////////////////////////////////////////////////////////////////////////////// // ctor /////////////////////////////////////////////////////////////////////////////// cHierDatabase::cHierDatabase( bool bCaseSensitive, TCHAR delChar ) : mRootArrayAddr (), mbCaseSensitive ( bCaseSensitive ), mDelimitingChar ( delChar ) { } /////////////////////////////////////////////////////////////////////////////// // dtor /////////////////////////////////////////////////////////////////////////////// cHierDatabase::~cHierDatabase() { } /////////////////////////////////////////////////////////////////////////////// // Open /////////////////////////////////////////////////////////////////////////////// void cHierDatabase::Open ( cBidirArchive* pArch, int numPages ) //throw (eArchive, eHierDatabase) { bool bTruncate = ( pArch->Length() == 0 ); inherited::Open( pArch, numPages ); OpenImpl( bTruncate ); } void cHierDatabase::Open ( const TSTRING& fileName, int numPages, bool bTruncate ) //throw (eArchive, eHierDatabase) { inherited::Open(fileName, numPages, bTruncate); #ifdef _BLOCKFILE_DEBUG AssertValid(); #endif OpenImpl( bTruncate ); } /////////////////////////////////////////////////////////////////////////////// // OpenImpl /////////////////////////////////////////////////////////////////////////////// void cHierDatabase::OpenImpl ( bool bTruncate ) //throw (eArchive, eHierDatabase) { // now, we should write the root node at (0,0) if we are creating, and assert that the root node // is there if we are opening an existing one... // if( bTruncate ) { // // make the root node that we are creating... // cHierRoot rootNode; rootNode.mChild = cHierAddr( 0, 1 ); // the first array will be placed at (0,1) rootNode.mbCaseSensitive = mbCaseSensitive; rootNode.mDelimitingChar = mDelimitingChar; cHierAddr addr = util_WriteObject( this, &rootNode ); // // assert that it was written to (0, 0) // ASSERT( (addr.mBlockNum == 0) && (addr.mIndex == 0) ); // // make an empty array to serve as the root array. // cHierArrayInfo arrayInfo; arrayInfo.mParent = cHierAddr(); arrayInfo.mArray = cHierAddr(); // // actually write the objects and assert that they were written to the right places. // addr = util_WriteObject( this, &arrayInfo ); ASSERT( (addr.mBlockNum == 0) && (addr.mIndex == 1) ); mRootArrayAddr = cHierAddr( 0, 1 ); } else { cHierRoot rootNode; util_ReadObject( this, &rootNode, cHierAddr( 0, 0 ) ); // // set the location of the top-level array and the other root parameters... // mRootArrayAddr = rootNode.mChild; mbCaseSensitive = rootNode.mbCaseSensitive; mDelimitingChar = rootNode.mDelimitingChar; } } //----------------------------------------------------------------------------- // cHierDatabaseIter //----------------------------------------------------------------------------- cHierDatabaseIter::cHierDatabaseIter( cHierDatabase* pDb ) //throw(eArchive) : mpDb ( pDb ), mCurPath( ) { ASSERT( pDb != 0); mCurPath.SetCaseSensitive ( mpDb->IsCaseSensitive() ); mCurPath.SetDelimiter ( mpDb->GetDelimitingChar() ); // // load in the root array... // SeekToRoot(); } cHierDatabaseIter::~cHierDatabaseIter() { } /////////////////////////////////////////////////////////////////////////////// // SeekToRoot /////////////////////////////////////////////////////////////////////////////// void cHierDatabaseIter::SeekToRoot() //throw (eArchive) { ASSERT( mpDb != 0); ASSERT( ! mpDb->mRootArrayAddr.IsNull() ); mCurPath.Clear(); LoadArrayAt( mpDb->mRootArrayAddr ); } /////////////////////////////////////////////////////////////////////////////// // copy ctor /////////////////////////////////////////////////////////////////////////////// cHierDatabaseIter::cHierDatabaseIter( const cHierDatabaseIter& rhs ) { *this = rhs; } /////////////////////////////////////////////////////////////////////////////// // operator= /////////////////////////////////////////////////////////////////////////////// cHierDatabaseIter& cHierDatabaseIter::operator=( const cHierDatabaseIter& rhs ) { mpDb = rhs.mpDb; mEntries = rhs.mEntries; mInfo = rhs.mInfo; mInfoAddr = rhs.mInfoAddr; mCurPath = rhs.mCurPath; // // the iterator is a little trickier // if( rhs.Done() ) { mIter = mEntries.end(); } else { EntryArray::const_iterator p1, p2; p1 = rhs.mIter; p2 = rhs.mEntries.begin(); mIter = mEntries.begin() + (p1 - p2); } return (*this); } /////////////////////////////////////////////////////////////////////////////// // LoadArrayAt /////////////////////////////////////////////////////////////////////////////// void cHierDatabaseIter::LoadArrayAt(const cHierAddr& addr) //throw (eArchive, eHierDatabase) { ASSERT( mpDb != 0); // // make sure the address is non-null // util_ThrowIfNull(addr, _T("cHierDatabaseIter::LoadArrayAt")); // // load the info and the array // mInfoAddr = addr; util_ReadObject( mpDb, &mInfo, mInfoAddr ); cHierAddr curAddr = mInfo.mArray; // // load in all the array entries... // mEntries.clear(); while( ! curAddr.IsNull() ) { mEntries.push_back( cHierEntry() ); util_ReadObject( mpDb, &mEntries.back(), curAddr ); curAddr = mEntries.back().mNext; } // // seek to the beginning of the array // SeekBegin(); } /////////////////////////////////////////////////////////////////////////////// // AtRoot /////////////////////////////////////////////////////////////////////////////// bool cHierDatabaseIter::AtRoot() const //throw (eHierDatabase) { bool bResult = ( mInfoAddr == mpDb->mRootArrayAddr ); // TODO -- maybe I want to put this in a #ifdef? // if( bResult ) { // we are at the root; assert that we have no parent... // ASSERT( mInfo.mParent.IsNull() ); if( ! mInfo.mParent.IsNull() ) { throw eHierDatabase( _T("Root node of db has a non-null parent") ); } } else { // if we are not at the root, assert that we have a parent... ASSERT( ! mInfo.mParent.IsNull() ); if( mInfo.mParent.IsNull() ) { throw eHierDatabase( _T("Non-root node of db has a null parent!") ); } } return bResult; } /////////////////////////////////////////////////////////////////////////////// // CanDescend /////////////////////////////////////////////////////////////////////////////// bool cHierDatabaseIter::CanDescend() const { if( Done() ) return false; return (! mIter->mChild.IsNull() ); } /////////////////////////////////////////////////////////////////////////////// // Descend /////////////////////////////////////////////////////////////////////////////// void cHierDatabaseIter::Descend() { ASSERT( CanDescend() ); // // alter the cwd... // mCurPath.Push( mIter->mName ); LoadArrayAt ( mIter->mChild ); } /////////////////////////////////////////////////////////////////////////////// // Ascend /////////////////////////////////////////////////////////////////////////////// void cHierDatabaseIter::Ascend() { ASSERT( ! AtRoot() ); ASSERT( mCurPath.GetSize() > 0 ); // // alter the cwd... // mCurPath.Pop(); LoadArrayAt ( mInfo.mParent ); } /////////////////////////////////////////////////////////////////////////////// // SeekBegin /////////////////////////////////////////////////////////////////////////////// void cHierDatabaseIter::SeekBegin() { mIter = mEntries.begin(); } /////////////////////////////////////////////////////////////////////////////// // Done /////////////////////////////////////////////////////////////////////////////// bool cHierDatabaseIter::Done() const { return ( mIter == mEntries.end() ); } /////////////////////////////////////////////////////////////////////////////// // Next /////////////////////////////////////////////////////////////////////////////// void cHierDatabaseIter::Next() { mIter++; } /////////////////////////////////////////////////////////////////////////////// // SeekTo /////////////////////////////////////////////////////////////////////////////// bool cHierDatabaseIter::SeekTo(const TCHAR* pName) { EntryArray::iterator i = UpperBound( pName ); if( i != mEntries.end() ) { if( 0 == Compare( pName, (*i).mName.c_str() ) ) { mIter = i; return true; } } mIter = mEntries.end(); return false; } /////////////////////////////////////////////////////////////////////////////// // GetName /////////////////////////////////////////////////////////////////////////////// const TCHAR* cHierDatabaseIter::GetName() const { ASSERT( ! Done() ); if( Done() ) { throw eHierDatabase( _T("Attempt to call iter::GetName() when done is true")); } return mIter->mName.c_str(); } /////////////////////////////////////////////////////////////////////////////// // GetCwd /////////////////////////////////////////////////////////////////////////////// const TSTRING cHierDatabaseIter::GetCwd() const { return mCurPath.AsString(); } /////////////////////////////////////////////////////////////////////////////// // GetCurrentAddr /////////////////////////////////////////////////////////////////////////////// cHierAddr cHierDatabaseIter::GetCurrentAddr() const { ASSERT( ! Done() ); if( Done() ) { return cHierAddr(); } if( mIter == mEntries.begin() ) { return mInfo.mArray; } return (mIter-1)->mNext; } /////////////////////////////////////////////////////////////////////////////// // CreateChildArray /////////////////////////////////////////////////////////////////////////////// void cHierDatabaseIter::CreateChildArray() //throw (eArchive, eHierDatabase) { ASSERT( ! Done() ); ASSERT( ! CanDescend() ); if( Done() ) { throw eHierDatabase( _T("Attempt to call iter::CreateChildArray() when done is true")); } if( CanDescend() ) { throw eHierDatabase( _T("Attempt to call iter::CreateChildArray() when child already exists")); } cHierArrayInfo newInfo; cHierAddr infoAddr; // write the new info newInfo.mParent = mInfoAddr; infoAddr = util_WriteObject( mpDb, &newInfo ); mIter->mChild = infoAddr; // // rewrite the current object, since its child info just changed // util_RewriteObject( mpDb, &(*mIter), GetCurrentAddr() ); } /////////////////////////////////////////////////////////////////////////////// // CreateEntry // /////////////////////////////////////////////////////////////////////////////// void cHierDatabaseIter::CreateEntry( const TSTRING& name ) //throw (eArchive, eHierDatabase) { cHierEntry newEntry; newEntry.mName = name; // // insert this in order in the set... // mIter = UpperBound( newEntry.mName.c_str() ); // // make sure that no duplicate names are added to the database... // if( mIter != mEntries.end() ) { if( 0 == Compare( mIter->mName.c_str(), newEntry.mName.c_str() ) ) { return; //throw eHierDbDupeName( name ); } } // Note -- insert() inserts directly _before_ the iterator // we need to get the current address so we can set the new object's // next pointer appropriately // cHierAddr nextAddr; if( ! Done() ) { nextAddr = GetCurrentAddr(); } mIter = mEntries.insert( mIter, newEntry ); // // first, we should write the new object to the database // mIter->mNext = nextAddr; cHierAddr newAddr = util_WriteObject( mpDb, &(*mIter) ); // // set the previous object's next pointer, and rewrite that object to disk... // if( mIter == mEntries.begin() ) { // we need to rewrite the array info... // mInfo.mArray = newAddr; util_RewriteObject( mpDb, &mInfo, mInfoAddr ); } else { // altering the previous node... // mIter--; mIter->mNext = newAddr; util_RewriteObject( mpDb, &(*mIter), GetCurrentAddr() ); mIter++; } } /////////////////////////////////////////////////////////////////////////////// // GetData /////////////////////////////////////////////////////////////////////////////// int8* cHierDatabaseIter::GetData(int32& length) const //throw (eArchive, eHierDatabase) { ASSERT( HasData() ); if( ! HasData() ) { throw eHierDatabase( _T("Attempt to get data from a node when HasData() is false")); } // // note that we can only get data for reading; perhaps in the future I will add // support for retrieving data for writing as well. // return mpDb->GetDataForReading( cBlockRecordFile::tAddr(mIter->mData.mBlockNum, mIter->mData.mIndex), length ); } /////////////////////////////////////////////////////////////////////////////// // HasData /////////////////////////////////////////////////////////////////////////////// bool cHierDatabaseIter::HasData() const { ASSERT( ! Done() ); if( Done() ) return false; return ( ! mIter->mData.IsNull() ); } /////////////////////////////////////////////////////////////////////////////// // SetData /////////////////////////////////////////////////////////////////////////////// void cHierDatabaseIter::SetData(int8* pData, int32 length) //throw (eArchive, eHierDatabase) { ASSERT( ! Done() ); ASSERT( ! HasData() ); // remove the item's data, if there is any... // if( HasData() ) { RemoveData(); } // // add the data and set the entry's data pointer appropriately // cBlockRecordFile::tAddr addr = mpDb->AddItem( pData, length ); mIter->mData = cHierAddr( addr.mBlockNum, addr.mIndex ); // // update the entry on disk... // util_RewriteObject( mpDb, &(*mIter), GetCurrentAddr() ); } /////////////////////////////////////////////////////////////////////////////// // RemoveData /////////////////////////////////////////////////////////////////////////////// void cHierDatabaseIter::RemoveData() //throw (eArchive, eHierDatabase) { ASSERT( ! Done() ); if( ! HasData() ) { ASSERT( false ); return; } mpDb->RemoveItem( cBlockRecordFile::tAddr( mIter->mData.mBlockNum, mIter->mData.mIndex ) ); // // now, we need to update the node's data pointer and save the node to disk // mIter->mData = cHierAddr(); util_RewriteObject( mpDb, &(*mIter), GetCurrentAddr() ); } /////////////////////////////////////////////////////////////////////////////// // DeleteEntry /////////////////////////////////////////////////////////////////////////////// void cHierDatabaseIter::DeleteEntry() //throw (eArchive, eHierDatabase) { ASSERT( ! Done() ); ASSERT( ! CanDescend() ); // this node can't have any children. cDebug d("cHierDatabaseIter::DeleteEntry"); if( Done() ) { d.TraceDetail("Attempt to DeleteEntry() when Done() == true; returning\n"); return; } if( CanDescend() ) { throw eHierDatabase( _T("Attempt to delete an entry that still has children.\n")); } // // first, we should set the previous node's next pointer... // cHierAddr curAddr = GetCurrentAddr(); if( mIter == mEntries.begin() ) { // we are changing the info's mArray pointer // mInfo.mArray = mIter->mNext; util_RewriteObject( mpDb, &mInfo, mInfoAddr ); } else { // altering the previous node... // mIter--; mIter->mNext = (mIter+1)->mNext; util_RewriteObject( mpDb, &(*mIter), GetCurrentAddr() ); mIter++; } // // now, delete the node from the file and from our array... // mpDb->RemoveItem( cBlockRecordFile::tAddr( curAddr.mBlockNum, curAddr.mIndex ) ); mIter = mEntries.erase(mIter); } /////////////////////////////////////////////////////////////////////////////// // CanDeleteChildArray /////////////////////////////////////////////////////////////////////////////// bool cHierDatabaseIter::ChildArrayEmpty() { ASSERT( ! Done() ); ASSERT( CanDescend() ); cHierArrayInfo info; util_ReadObject ( mpDb, &info, mIter->mChild ); return ( info.mArray.IsNull() ); } /////////////////////////////////////////////////////////////////////////////// // DeleteChildArray /////////////////////////////////////////////////////////////////////////////// void cHierDatabaseIter::DeleteChildArray() //throw (eArchive, eHierDatabase) { ASSERT( ! Done() ); ASSERT( CanDescend() ); cDebug d("cHierDatabaseIter::DeleteChildArray"); if( Done() ) { d.TraceDetail("Attempt to DeleteChildArray() when Done() == true; returning\n"); return; } if( ! CanDescend() ) { d.TraceDetail("Attempt to DeleteChildArray() when none exists; returning\n"); return; } // // make sure that the child index is empty... // if( ! ChildArrayEmpty() ) { ASSERT( false ); throw eHierDatabase( _T("Attempt to delete a child array that still has entries") ); } // // ok, no we can remove it... // mpDb->RemoveItem( cBlockRecordFile::tAddr( mIter->mChild.mBlockNum, mIter->mChild.mIndex ) ); mIter->mChild = cHierAddr(); util_RewriteObject( mpDb, &(*mIter), GetCurrentAddr() ); } /////////////////////////////////////////////////////////////////////////////// // CompareForUpperBound /////////////////////////////////////////////////////////////////////////////// bool cHierDatabaseIter::CompareForUpperBound( const cHierEntry& he, const TCHAR* pchName ) const { return( 0 > Compare( he.mName.c_str(), pchName ) ); } /////////////////////////////////////////////////////////////////////////////// // cHierDatabaseIterCallCompare // -- used to sneak a pointer-to-member-function where a // pointer-to-non-member-function is expected /////////////////////////////////////////////////////////////////////////////// class cHierDatabaseIterCallCompare { public: cHierDatabaseIterCallCompare( const cHierDatabaseIter* pcls ) : pc( pcls ) {}; bool operator()( const cHierEntry& a1, const TCHAR* a2 ) { return pc->CompareForUpperBound( a1, a2 ); } private: const cHierDatabaseIter* pc; }; /////////////////////////////////////////////////////////////////////////////// // UpperBound /////////////////////////////////////////////////////////////////////////////// cHierDatabaseIter::EntryArray::iterator cHierDatabaseIter::UpperBound( const TCHAR* pchName ) { cHierDatabaseIterCallCompare comp( this ); return ::UpperBound( mEntries.begin(), mEntries.end(), pchName, comp ); }