// // 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. // /////////////////////////////////////////////////////////////////////////////// //dbdebug.cpp // #include "stdtw.h" #include "dbdebug.h" #include "db/hierdatabase.h" #include "db/hierdbnode.h" #include "db/blockrecordarray.h" #include "db/blockfile.h" #include "dbdatasource.h" #include "fcodatabasefile.h" #include "db/blockrecordfile.h" #include "fco/twfactory.h" #include "fco/fconametranslator.h" #include "core/error.h" #include "core/errorbucketimpl.h" #include "core/debug.h" #include "fco/genreswitcher.h" #include "fco/fcopropdisplayer.h" #include "fco/fcopropvector.h" #include "fco/fcopropset.h" #include "fco/fcoprop.h" #include #include #include #include #include "core/tchar.h" #include "core/types.h" ////////////////////////////////////////////////////////////////////////////////////////////////// // UTILITY METHODS ////////////////////////////////////////////////////////////////////////////////////////////////// // static void util_PrintFCO(const iFCO* pFCO, iFCOPropDisplayer * pPropDisplayer); static void util_InitBlockArray( cBlockRecordArray& block ); static void util_MapHierRoot( std::map< std::pair< int, int>, int > dbMap ); ////////////////////////////////////////////////////////////////////////////////////////////////// // Insulated implementation: ////////////////////////////////////////////////////////////////////////////////////////////////// struct cDbDebug_i { cDbDebug_i() {}; ~cDbDebug_i() {}; typedef std::map< std::pair, int > hierDbMap; // This is the data structure that we will use to match the information stored in a quantum database // with its hierarchical representation! For now, it will hold the tuples representing // valid offsets in each block (referenced by a block number). The bool value will be set to false // on the first run, and then flipped to true for each entry that is found while traversing the // hierarchical database. typedef std::list< std::pair< int, int> > AddressList; hierDbMap mDbMap; // The map itself. AddressList mlMissingFromBlockFile; // A list for keeping track of nodes found in the hierarchy, but not the blockfile AddressList mlMissingFromHierarchy; // The opposite case. }; cDbDebug::cDbDebug() { mpData = new cDbDebug_i(); } cDbDebug::~cDbDebug() { delete mpData; } /////////////////////////////////////////////////////////////////////////////// // util_InitBlockArray /////////////////////////////////////////////////////////////////////////////// static void util_InitBlockArray( cBlockRecordArray& block ) { if( ! block.Initialized() ) { block.InitForExistingBlock(); } } /////////////////////////////////////////////////////////////////////////////// // util_MapHierRoot : Map the Root and RootArray in the HierDatabase: /////////////////////////////////////////////////////////////////////////////// static void util_MapHierRoot( std::map< std::pair< int, int>, int >* dbMap ) { cDbDebug_i::hierDbMap::iterator i = dbMap->find( std::pair ( 0, 0 ) ); //dbMap->insert( cDbDebug_i::hierDbMap::value_type( std::pair< int, int > ( 0, 0 ), 1 ) ); // // This insert statement should work to change the value, but it doesn't ???? (*i).second = 1; i = dbMap->find( std::pair ( 0, 1 ) ); (*i).second = 1; i = dbMap->find( std::pair ( 0, 2 ) ); (*i).second = 1; } /////////////////////////////////////////////////////////////////////////////// // DisplayDbMap -- Displays the entire map. /////////////////////////////////////////////////////////////////////////////// void cDbDebug::DisplayDbMap() { int loop_count = 0; for(cDbDebug_i::hierDbMap::iterator i = cDbDebug::mpData->mDbMap.begin(); i!= cDbDebug::mpData->mDbMap.end(); ++i, ++loop_count) { TCOUT.width(6); TCOUT<< (*i).first.first << _T(",") << (*i).first.second<<_T(" "); TCOUT<<_T("(")<< (*i).second<<_T(")"); if( (loop_count % 5) == 0 ) TCOUT<< std::endl; } TCOUT<mDbMap); } ////////////////////////////////////////////////////////////////////////////////////////////////////// // Execute : // Drives the DebugDb mode of tripwire, which will only be used for internal use. ////////////////////////////////////////////////////////////////////////////////////////////////////// void cDbDebug::Execute( cFCODatabaseFileIter& dbIter, const TSTRING& dbFile ) { cDebug d("cDebugDb::Execute"); cDbDebug DbDebug; // //A derived class with extra methods for obtaining blockfile information. cDebugHierDb* db; // // Cast the database object that we have inherited from the "common" command line // information to a cDebugHierDb. This will expose all the information that we // need to debug the database. // db = static_cast(&(dbIter.GetDb())); try { cDebugHierDbIter pIter( db ); // //First, map the state of the underlying quantyum database. Store this info. in //the hierDbMap. // DbDebug.MapQuantumDatabase( *db ); //DbDebug.DisplayDbMap(); //TODO : get rid of these display calls. // // Uncomment ManipDb() to allow manipulation of the database // This may be handy if one does not want to do a full update... //DbDebug.ManipDb( dbIter ); // Next, traverse the hierarchical overlay and "tag" all entries in the map that // we see: ASSERT( pIter.AtRoot() ); // // check to make sure we are at the root of the hierarchy, if so, map the root // before calling the recursive traversal function. // util_MapHierRoot( DbDebug.GetHierDbMap() ); DbDebug.TraverseHierarchy( pIter /*, db*/ ); //DbDebug.DisplayDbMap(); //TODO: get rid of these display calls. // // Finally, iterate over the map and see if there is any data that the hierarchy // does not account for: // DbDebug.CongruencyTest(); // // Output the results // DbDebug.OutputResults(); }//try catch( eError& e ) { cErrorReporter::PrintErrorMsg( e ); } } //////////////////////////////////////////////////////////////////////////////////////////////// // MapQuantumDatabase -- // // Our objective is to map all the information that the database is currently storing. It // may be necessary to do some hacking about with the database object in order to expose // all the information necessary to accomplish this. // // Addition: 2/15/99: We want to print out statistics on the current state of the block // file, so let's do it here ( since we're traversing it to map the addresses, anyway ). ////////////////////////////////////////////////////////////////////////////////////////////// void cDbDebug::MapQuantumDatabase( cDebugHierDb& db ) { //mirroring the implementation in blockrecordfile.h: typedef std::vector BlockArray; //Using the two added methods to gain access to the low-level implementation of the //quantum database. BlockArray* pBlkArray = db.myGetBlockArray(); // Print some statistics on the blockfile if we're in debug mode: #ifdef _BLOCKFILE_DEBUG db.myGetBlockFile()->TraceContents(); #endif // Prints as much information about the current state of the block file as can be // assessed. // time to iterate over the database: std::vector< cBlockRecordArray >::iterator i; int count = 0; for( i = (*pBlkArray).begin(); i != (*pBlkArray).end(); ++i, ++count ) { util_InitBlockArray( *i ); //This is necessary to make sure that each block is initialized as we iterate //over the block array. // // Print out statistics on this block, if we're in debug mode: #ifdef _BLOCKFILE_DEBUG i->TraceContents(); #endif //Search through all the indexes and determine which of them references valid //information. Store these pairs ( the first int being the value of //count.... for( int j = 0; j <= i->GetNumItems(); ++j ) { if( i->IsItemValid( j ) ) { // // We found a valid node in the database, so store it in the map. // mpData->mDbMap.insert( cDbDebug_i::hierDbMap::value_type( std::pair (count, j) , 0 )); } // if not, just don't store it. The index is no longer valid. } } } //////////////////////////////////////////////////////////////////////////////////////////////// // TraverseHierarchy -- // // A recursive function for traversing a hierarchical database, accounting for all the data // structures that make up the object. Looks at data addresses as well as structures that are // necessary to maintain the hierarchy ( namely cHierArrayInfo records ). //////////////////////////////////////////////////////////////////////////////////////////////// void cDbDebug::TraverseHierarchy( cDebugHierDbIter pIter) { if( ! pIter.CanDescend() ) return; //Done with this call. pIter.Descend(); // //Descend once from the root, into the first child set. Also, descend once for each recursive call. // for( pIter.SeekBegin(); !pIter.Done(); pIter.Next() ) { //Try to match the parent's address in the database. if( !pIter.Done() ) { if( pIter.myGetEntryArrayIt() == pIter.myGetEntryArray().begin() ) //We're dealing with a cHierArrayInfo object, so treat it differently. { // // Let's map the parent address, and the array address, so we account for all the data structures // that make up this particular node. // MapHierDbNodes( mpData->mDbMap, std::pair(pIter.myGetArrayInfo().mParent.mBlockNum, pIter.myGetArrayInfo().mParent.mIndex), pIter ); // and the array... MapHierDbNodes( mpData->mDbMap, std::pair(pIter.myGetArrayInfo().mArray.mBlockNum, pIter.myGetArrayInfo().mArray.mIndex), pIter ); } else //This is a regular cHierEntry, so look at the Data and Child (if it exists ). { cDebugHierDbIter::EntryArray::iterator lEntryArrayIt = pIter.myGetEntryArrayIt(); MapHierDbNodes( mpData->mDbMap, std::pair(lEntryArrayIt->mData.mBlockNum, lEntryArrayIt->mData.mIndex), pIter ); --lEntryArrayIt; // Get the address of this node by examining the previous next pointer in the entry list. // // // Map the next peer entry in the list. TODO: This may very well be overkill... if so, lose this call to MapHierDbNodes. // MapHierDbNodes( mpData->mDbMap, std::pair(lEntryArrayIt->mNext.mBlockNum, lEntryArrayIt->mNext.mIndex), pIter ); // // We also want to map the address of the child array, if non-null. // if( lEntryArrayIt->mChild.mBlockNum != -1 ) MapHierDbNodes( mpData->mDbMap, std::pair(lEntryArrayIt->mChild.mBlockNum, lEntryArrayIt->mChild.mIndex), pIter ); // // Finally, map the address of the data. // MapHierDbNodes( mpData->mDbMap, std::pair(lEntryArrayIt->mData.mBlockNum, lEntryArrayIt->mData.mIndex), pIter ); } }//if // // Check to see if this particular node has a child array. If so, make recursive call. // if( pIter.CanDescend() ) { //This node has children, call myself TraverseHierarchy( pIter ); } }//for } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // MapHierDbNodes -- // // Maps a given address in the hierarchy. This function looks up the address in the map and flips an integer flag // to record the event. If the address is not found, it is placed in a list of addresses for error output. /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void cDbDebug::MapHierDbNodes( std::map< std::pair< int, int > , int >& dbMap, std::pair address, cDebugHierDbIter& pIter ) { cDbDebug_i::hierDbMap::iterator i = dbMap.find( std::pair< int, int> (address.first, address.second) ); if( i != dbMap.end() ) { (*i).second = 1; //Flip the integer flag to 1, since we've found the address in the hierarchy. } else mpData->mlMissingFromBlockFile.push_back( std::pair (address.first, address.second)); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // CongruencyTest -- // // Iterate over the map and see if there are any entries that haven't been tagged by our // hierarchy traversal. If there are, push the offending addresses onto the address list. /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void cDbDebug::CongruencyTest( void ) { cDbDebug_i::hierDbMap::iterator i = mpData->mDbMap.begin(); for( ; i != mpData->mDbMap.end(); ++i ) { if ( (*i).second != 1 ) { //We've found something in the blockfile map that is not accounted for by the //hierarchy. Add this address to the appropriate list. //TCOUT<< (*i).first.first << _T(",") << (*i).first.second << std::endl; //TCOUT<< (*i).second <mlMissingFromHierarchy.push_back( std::pair< int, int> ( (*i).first.first, (*i).first.second ) ); } } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // OutputResults -- // // Output the results of the congruency test. Print information contained in the two // "error lists" if they have entries. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void cDbDebug::OutputResults( void ) { TCOUT<< _T("\nResults of Blockfile / Hierarchy congruency tests: \n") << std::endl; // Check to see if our error lists are empty. If so, output a message that indicates // that the two databases checked clean. if( mpData->mlMissingFromHierarchy.empty() && mpData->mlMissingFromBlockFile.empty() ) TCOUT<< _T("The database representations match!\n") << std::endl; else { // Check to see if there was anything in the hierarchy that was not accounted for by the // blockfile (quantum database). if( !mpData->mlMissingFromBlockFile.empty() ) { TCOUT<< _T(" Objects (referenced by address) found in the hierarchy that were not\n"); TCOUT<< _T(" accounted for by the underlying quantum database: \n"); cDbDebug_i::AddressList::iterator i = mpData->mlMissingFromBlockFile.begin(); for( ; i != mpData->mlMissingFromBlockFile.end(); ++i ) TCOUT << _T("(") << (*i).first << _T(",") << (*i).second << _T(")") << std::endl; } else TCOUT<< _T(" All objects in Blockfile accounted for!\n"); //check to see if there was anything in the blockfile that was not accounted for by the // hierarchy: if ( !mpData->mlMissingFromHierarchy.empty() ) { TCOUT<< _T(" Database information (referenced by a address)\n"); TCOUT<< _T(" not accounted for by the hierarchy :\n"); cDbDebug_i::AddressList::iterator i = mpData->mlMissingFromHierarchy.begin(); for( ; i != mpData->mlMissingFromHierarchy.end(); ++i ) TCOUT << _T("(") << (*i).first << _T(",") << (*i).second << _T(")") << std::endl; } else TCOUT<<_T("All objects in Hierarchy accounted for!\n"); }//else } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Begin code for manipulating Database -- This is just a straight port from the DbExplore code. It's presence is // purely for convenience, and for debugging this class. /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// static inline bool IsSpace( TCHAR c ) { return ( (c == _T(' ')) || (c == _T('\t')) || (c == _T('\r')) || (c == _T('\n')) || (c == _T('\0')) ); } static inline bool IsEnd( TCHAR c ) { return ( (c == _T('\0')) ); } /* /////////////////////////////////////////////////////////////////////////////// // util_PrintFCO /////////////////////////////////////////////////////////////////////////////// static void util_PrintFCO( const iFCO* pFCO, const iFCOPropDisplayer* pDisplayer ) { TCOUT.setf(std::ios::left); TCOUT << "------- " << iTWFactory::GetInstance()->GetNameTranslator()->ToStringDisplay( pFCO->GetName() ) << " -------" << std::endl; // // iterate over all of the properties // const iFCOPropSet* pPropSet = pFCO->GetPropSet(); cFCOPropVector v = pPropSet->GetValidVector(); for( int i=0; i < pPropSet->GetNumProps(); i++ ) { if( v.ContainsItem( i ) ) { TCOUT << "["; TCOUT.width(2); TCOUT << i << "]" ; TCOUT.width(25); TCOUT << pPropSet->GetPropName(i); TCOUT.width(0); TCOUT << pDisplayer->PropAsString( pFCO, i ) << std::endl; } } TCOUT << "--------------------------------------------" << std::endl; } /////////////////////////////////////////////////////////////////////////////// // GetNoun // note -- you can only call this once after the verb (it eats the rest // of the line!) /////////////////////////////////////////////////////////////////////////////// static void GetNoun( TSTRING& noun ) { static TSTRING prevNoun; TCHAR buf[1024]; TCIN.getline( buf, 1024 ); // // find the end of the noun... // TCHAR* pStart = buf; TCHAR* end = &buf[1023]; while( IsSpace(*pStart) && (! IsEnd(*pStart)) && (pStart < end) ) pStart++; if( IsEnd( *pStart ) || (pStart >= end) ) { // no noun! noun = _T(""); return; } TCHAR* pCur = pStart; bool bQuote = false; if( *pCur == _T('\"') ) { bQuote = true; pCur++; pStart++; } while( pCur < end ) { if( (! bQuote) && IsSpace(*pCur) ) break; if( *pCur == _T('\"') && bQuote) break; pCur++; } noun.assign(pStart, (pCur - pStart)); if( noun.compare( _T("!$") ) == 0 ) { noun = prevNoun; } prevNoun = noun; } /////////////////////////////////////////////////////////////////////////////// // SeekTo -- seeks to the named noun; this takes care of interpriting special // nouns. If the seek fails, then Done() is true and false is returned. // If noun is a special character, then it is altered to what it mapped to. /////////////////////////////////////////////////////////////////////////////// static bool SeekTo(cDbDataSourceIter* pIter, TSTRING& noun ) { if( noun[0] == _T('*') ) { // interprite the rest of the string as a number index to seek to... int index = _ttoi( &noun[1] ); if( index < 0 ) return false; pIter->SeekBegin(); for( int i=0; ((i < index) && (! pIter->Done())); i++, pIter->Next() ) { } if( pIter->Done() ) return false; noun = pIter->GetShortName(); return true; } else return ( pIter->SeekTo( noun.c_str() ) ); } void cDbDebug::ManipDb( cFCODatabaseFileIter& dbIter ) { cDbDataSourceIter* pIter = new cDbDataSourceIter( &dbIter.GetDb(), dbIter.GetGenre() ); const iFCOPropDisplayer* pDisplayer = dbIter.GetGenreHeader().GetPropDisplayer(); //////////////////////////// // the main event loop... //////////////////////////// while( true ) { TSTRING verb, noun; TCOUT << _T(">>"); TCIN >> verb; // // ok, now we switch on the command... // //----------------------------------------------------------------- // quit //----------------------------------------------------------------- if( verb.compare( _T("quit") ) == 0 ) { // the quit command... break; } //----------------------------------------------------------------- // print //----------------------------------------------------------------- if( verb.compare( _T("print") ) == 0 ) { GetNoun(noun); if( SeekTo( pIter, noun ) ) { if( pIter->HasFCOData() ) { iFCO* pFCO = pIter->CreateFCO(); util_PrintFCO( pFCO, pDisplayer ); pFCO->Release(); } else { TCOUT << "Object has no data associated with it." << std::endl; } } else { TCOUT << "Unable to find object " << noun << std::endl; } } //----------------------------------------------------------------- // pwd //----------------------------------------------------------------- else if( verb.compare( _T("pwd") ) == 0 ) { TCOUT << iTWFactory::GetInstance()->GetNameTranslator()->ToStringDisplay( pIter->GetParentName() ) << std::endl; } //----------------------------------------------------------------- // ls //----------------------------------------------------------------- else if( verb.compare( _T("ls") ) == 0 ) { int cnt = 0; for( pIter->SeekBegin(); ! pIter->Done(); pIter->Next(), cnt++ ) { TCOUT << "[" << cnt ; if( pIter->CanDescend() ) { TCOUT << "]*\t" ; } else { TCOUT << "]\t" ; } TCOUT << pIter->GetShortName() << std::endl; } } //----------------------------------------------------------------- // cd //----------------------------------------------------------------- else if( verb.compare( _T("cd") ) == 0 ) { GetNoun(noun); if( noun.compare( _T("..") ) == 0 ) { if( pIter->AtRoot() ) { TCOUT << "Can't ascend above root." << std::endl; } else { TCOUT << "Ascending..." << std::endl; pIter->Ascend(); } } else { if( SeekTo( pIter, noun ) ) { if( pIter->CanDescend() ) { TCOUT << "Descending into " << noun << std::endl; pIter->Descend(); } else { TCOUT << noun << " has no children; can't descend." << std::endl; } } else { TCOUT << "Unable to find object " << noun << std::endl; } } } //----------------------------------------------------------------- // cg //----------------------------------------------------------------- else if( verb.compare( _T("cg") ) == 0 ) { GetNoun(noun); cGenre::Genre newGenre = cGenreSwitcher::GetInstance()->StringToGenre( noun.c_str() ); if (newGenre != cGenre::GENRE_INVALID) { dbIter.SeekToGenre( newGenre ); if( !dbIter.Done() ) { TCOUT << _T("Changing to Genre ") << noun << std::endl; // // create a new db iter for the new genre (db iters can only be // assocaited with a single genre :-( ) // delete pIter; pIter = new cDbDataSourceIter( &dbIter.GetDb(), newGenre ); } else { TCOUT << _T("Unable to find Genre ") << noun << std::endl; } } else { TCOUT << _T("Invalid Genre ") << noun << std::endl; } } //----------------------------------------------------------------- // pwg //----------------------------------------------------------------- else if( verb.compare( _T("pwg") ) == 0 ) { TCOUT << _T("Current Genre: ") << cGenreSwitcher::GetInstance()->GenreToString( (cGenre::Genre)dbIter.GetGenre(), true ) << std::endl; } //----------------------------------------------------------------- // mkdir //----------------------------------------------------------------- else if( verb.compare( _T("mkdir") ) == 0 ) { GetNoun(noun); TCOUT << "Making a child of " << noun << std::endl; if( pIter->SeekTo( noun.c_str() ) ) { pIter->AddChildArray(); } else { TCOUT << "Unable to find object " << noun << std::endl; } } //----------------------------------------------------------------- // mk //----------------------------------------------------------------- else if( verb.compare( _T("mk") ) == 0 ) { GetNoun(noun); TCOUT << "Making object " << noun << std::endl; if( pIter->SeekTo( noun.c_str() ) ) { TCOUT << "Error: object already exists!" << std::endl; } else { pIter->AddFCO( noun, 0 ); // add a null fco for now } } //----------------------------------------------------------------- // rmdir //----------------------------------------------------------------- // TODO -- still needs to be implemented in the pIterator class! // else if( verb.compare( _T("rmdir") ) == 0 ) { GetNoun(noun); TCOUT << "Removing the child of " << noun << std::endl; if( pIter->SeekTo( noun.c_str() ) ) { //TODO -- check that it has an empty child pIter->RemoveChildArray(); } else { TCOUT << "Unable to find object " << noun << std::endl; } } //----------------------------------------------------------------- // rm //----------------------------------------------------------------- else if( verb.compare( _T("rm") ) == 0 ) { GetNoun(noun); TCOUT << "Removing object " << noun << std::endl; if( pIter->SeekTo( noun.c_str() ) ) { if( pIter->CanDescend() ) { TCOUT << "Can't delete object; it still has children." << std::endl; } else { pIter->RemoveFCO(); } } else { TCOUT << "Unable to find object " << noun << std::endl; } } //----------------------------------------------------------------- // help //----------------------------------------------------------------- if( verb.compare( _T("help") ) == 0 ) { TCOUT << _T("Commands: ") << std::endl << _T(" cd ") << std::endl << _T(" pwd ") << std::endl << _T(" ls ") << std::endl << _T(" print ") << std::endl << _T(" cg (FS | NTFS | NTREG)")<< std::endl << _T(" pwg") << std::endl << _T(" quit") << std::endl; } // make sure the file is still valid... // } delete pIter; TCOUT << "Exiting..." << std::endl; } */