//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1991 - 1999. // // File: CIDIR.CXX // // Contents: Persistent directory // // Classes: CiDirectory // // History: 3-Apr-91 BartoszM Created stub. // 3-May-96 dlee Don't read it in -- map on-disk data // 4-Nov-97 dlee Support merge shrink from front // // Notes: File format is two initial ULONGs with the count of level 1 // and count of level 2 keys, then the actual level 1 and level // 2 keys. // //---------------------------------------------------------------------------- #include #pragma hdrstop #include #include #include #include //+--------------------------------------------------------------------------- // // Member: CiDirectory::CiDirectory, public // // Synopsis: Open existing or create empty directory // // Arguments: [storage] -- Through which streams are opened // [objectId] -- Wid to open // [mode] -- Mode to open the stream // // History: 13-Jun-91 BartoszM Created. // //---------------------------------------------------------------------------- CiDirectory::CiDirectory( CiStorage & storage, WORKID objectId, PStorage::EOpenMode mode ) : _storage( storage ), _objectId( objectId ), _cKeys( 0 ), _cLevel2Keys( 0 ), _pbCurrent( 0 ), _pcKeys( 0 ), _fReadOnly( PStorage::eOpenForRead == mode ) { // If opening for create, don't try reading in existing data if the file // exists since we want to blow it all away later anyway. if ( PStorage::eCreate != mode ) ReadIn( !_fReadOnly ); } //CiDirectory //+--------------------------------------------------------------------------- // // Member: CiDirectory::DoSeekForKeyBuf // // Synopsis: Find the key in the array of keys. // // Arguments: [key] -- search key // [aKeys] -- array of keys to search // [low] -- the index of the lower bound of the search // [cKeys] -- # of keys over which the search happens // // Returns: index of the greatest key <= the search key // // History: 5-May-96 dlee Created // //---------------------------------------------------------------------------- inline ULONG CiDirectory::DoSeekForKeyBuf( const CKeyBuf & key, CDirectoryKey ** aKeys, ULONG low, ULONG cKeys ) { #if CIDBG == 1 Win4Assert( 0 != cKeys ); ULONG cArray = cKeys; #endif // CIDBG == 1 ULONG iHi = low + cKeys - 1; ULONG iLo = low; // do a binary search looking for the key do { ULONG cHalf = cKeys / 2; if ( 0 != cHalf ) { ULONG cTmp = cHalf - 1 + ( cKeys & 1 ); ULONG iMid = iLo + cTmp; Win4Assert( iMid < ( low + cArray ) ); if ( aKeys[ iMid ]->IsGreaterThanKeyBuf( key ) ) { iHi = iMid - 1; cKeys = cTmp; } else if ( ! aKeys[ iMid + 1 ]->IsGreaterThanKeyBuf( key ) ) { iLo = iMid + 1; cKeys = cHalf; } else { return iMid; } } else if ( cKeys > 1 ) { Win4Assert( ( iLo + 1 ) < ( low + cArray ) ); if ( aKeys[ iLo + 1 ]->IsGreaterThanKeyBuf( key ) ) return iLo; else return iLo + 1; } else return iLo; } while ( TRUE ); Win4Assert(( ! "Invalid doseek function exit point" )); return 0; } //DoSeekForKeyBuf //+--------------------------------------------------------------------------- // // Member: CiDirectory::Seek // // Synopsis: Find bit offset and key of the greatest key less than or // equal to search key. // If none is found <= the search key, return the first item. // // Arguments: [key] -- search key // [pKeyInit] -- key found: less or equal to search key. // only returned if non-zero // [off] -- bit offset of keyInit // // History: 22-Apr-92 BartoszM Created // //---------------------------------------------------------------------------- void CiDirectory::Seek( const CKeyBuf & key, CKeyBuf * pKeyInit, BitOffset & off ) { // If a master merge failed, the stream may not be mapped. Map it. ReMapIfNeeded(); ULONG iKey; if ( ! key.IsMinKey() ) { // // Check if there is no level 2 (ie the dir is being created) // In the case where there is no level 2, there is no max key // at the end of the array. That's ok because queries will go // to the old index for keys greater than the last key in this // directory. // if ( 0 == _cLevel2Keys ) { iKey = DoSeekForKeyBuf( key, _aKeys.GetPointer(), 0, _cKeys ); Win4Assert( iKey < _cKeys ); Win4Assert( ( iKey == ( _cKeys - 1 ) ) || ( _aKeys[ iKey + 1 ]->IsGreaterThanKeyBuf( key ) ) ); } else { iKey = DoSeekForKeyBuf( key, _aLevel2Keys.GetPointer(), 0, _cLevel2Keys ); iKey *= eDirectoryFanOut; Win4Assert( iKey < _cKeys ); unsigned cKeys = __min( eDirectoryFanOut, _cKeys - iKey ); iKey = DoSeekForKeyBuf( key, _aKeys.GetPointer(), iKey, cKeys ); Win4Assert( ( 1 == _cKeys ) || ( iKey < ( _cKeys - 1 ) ) ); Win4Assert( ( 1 == _cKeys) || ( _aKeys[ iKey + 1 ]->IsGreaterThanKeyBuf( key ) ) ); } Win4Assert( 0 == iKey || !_aKeys[ iKey ]->IsGreaterThanKeyBuf( key ) ); // If the search key is <= the first key, return the min key if ( ( 0 == iKey ) && ( ( 0 == _cKeys ) || ( _aKeys[ 0 ]->IsGreaterThanKeyBuf( key ) ) ) ) { // the key is less than the first key, return minkey if ( 0 != pKeyInit ) pKeyInit->FillMin(); off.Init( 0, 0 ); return; } } else { iKey = 0; } _aKeys[ iKey ]->Offset( off ); if ( 0 != pKeyInit ) _aKeys[ iKey ]->MakeKeyBuf( *pKeyInit ); } //Seek //+--------------------------------------------------------------------------- // // Member: CiDirectory::Seek // // Synopsis: Find bit offset and key of the greatest key less than or // equal to search key. // If none is found <= the search key, return the first item. // // Arguments: [key] -- search key // [pKeyInit] -- key found: less or equal to search key. // only returned if non-zero // [off] -- bit offset of keyInit // // History: 22-Apr-92 BartoszM Created // //---------------------------------------------------------------------------- void CiDirectory::Seek( const CKey & key, CKeyBuf * pKeyInit, BitOffset & off ) { CKeyBuf keyBuf; keyBuf = key; Seek( keyBuf, pKeyInit, off ); } //Seek //+--------------------------------------------------------------------------- // // Member: CiDirectory::SeekNext // // Synopsis: Find the first key in the next page // // Arguments: [key] -- the search key in this page // [off] -- the offset of the first key // // History: 8-Mar-94 t-joshh Created // //---------------------------------------------------------------------------- void CiDirectory::SeekNext ( const CKeyBuf & key, CKeyBuf * pKeyInit, BitOffset & off ) { ULONG iKey; if ( _cKeys > 0 && ! key.IsMinKey() ) { if ( 0 == _cLevel2Keys ) { iKey = DoSeekForKeyBuf( key, _aKeys.GetPointer(), 0, _cKeys ); } else { iKey = DoSeekForKeyBuf( key, _aLevel2Keys.GetPointer(), 0, _cLevel2Keys ); iKey *= eDirectoryFanOut; Win4Assert( iKey < _cKeys ); unsigned cKeys = __min( eDirectoryFanOut, _cKeys - iKey ); iKey = DoSeekForKeyBuf( key, _aKeys.GetPointer(), iKey, cKeys ); } // this IS a seek next iKey++; } else { iKey = 0; } Win4Assert ( iKey < _cKeys ); _aKeys[ iKey ]->Offset( off ); if ( 0 != pKeyInit ) _aKeys[ iKey ]->MakeKeyBuf( *pKeyInit ); #if CIDBG == 1 _aKeys[ iKey ]->MakeKeyBuf( _keyLast ); #endif // CIDBG == 1 } //SeekNext //+--------------------------------------------------------------------------- // // Member: CiDirectory::Add // // Synopsis: Adds entry to directory // // Arguments: [posKey] -- offset of key in file // [key] -- key to add // // History: 22-Apr-92 BartoszM Created // //---------------------------------------------------------------------------- void CiDirectory::Add( BitOffset & posKey, const CKeyBuf & key ) { Win4Assert( !_fReadOnly ); Win4Assert( &key != 0 ); ciDebugOut(( DEB_PDIR, "PDir::Add %.*ws at %d:%d\n", key.StrLen(), key.GetStr(), posKey.Page(), posKey.Offset() )); LokWriteKey( key, posKey ); #if CIDBG==1 _bitOffLastAdded = posKey; #endif // CIDBG } //Add //+--------------------------------------------------------------------------- // // Member: CiDirectory::ReMapIfNeeded, private // // Synopsis: ReMaps the directory stream if it isn't mapped due to a // failure to extend or map the directory stream during a // shrink from front master merge. // // History: 6-Aug-98 dlee Created // //---------------------------------------------------------------------------- void CiDirectory::ReMapIfNeeded() { // If it's already mapped, we're set if ( 0 != _streamBuf.Get() ) return; Win4Assert( !_fReadOnly ); // Map the file _stream->MapAll( _streamBuf ); // Rebase pointers if the new stream pointer is different if ( 0 == _cKeys ) { _pcKeys = (ULONG *) _streamBuf.Get(); _pbCurrent = (BYTE *) ( _pcKeys + 2 ); } else if ( _pcKeys != (ULONG *) _streamBuf.Get() ) { BYTE *pbOldBase = (BYTE *) _pcKeys; BYTE *pbNewBase = (BYTE *) _streamBuf.Get(); UINT_PTR cb = _pbCurrent - pbOldBase; for ( unsigned i = 0; i < _cKeys; i++ ) { BYTE * p = (BYTE *) _aKeys[ i ]; p = p - pbOldBase + pbNewBase; _aKeys[ i ] = (CDirectoryKey *) p; } _pcKeys = (ULONG *) _streamBuf.Get(); _pbCurrent = pbNewBase + cb; } } //ReMapIfNeeded #if CIDBG == 1 // for testing failure path BOOL g_fFailDirectoryMergeExtend = FALSE; #endif // CIDBG == 1 //+--------------------------------------------------------------------------- // // Member: CiDirectory::GrowIfNeeded, private // // Synopsis: Grows the file if necessary and maps the new section // // Arguments: [cbToGrow] -- # of bytes to append to the file // // History: 8-May-96 dlee Created // //---------------------------------------------------------------------------- void CiDirectory::GrowIfNeeded( unsigned cbToGrow ) { if ( ( _pbCurrent + cbToGrow ) >= ( (BYTE *) _streamBuf.Get() + _streamBuf.Size() ) ) { BYTE * pbOldBase = (BYTE *) _streamBuf.Get(); unsigned cbOld = (unsigned)(_pbCurrent - (BYTE *) _streamBuf.Get()); _stream->Unmap( _streamBuf ); ULONG sizeNew = CommonPageRound( cbOld + cbToGrow ); _stream->SetSize( _storage, sizeNew ); #if CIDBG == 1 // For testing failure at this point... if ( g_fFailDirectoryMergeExtend ) { g_fFailDirectoryMergeExtend = FALSE; THROW( CException( E_OUTOFMEMORY ) ); } #endif // CIDBG == 1 _stream->MapAll( _streamBuf ); _pcKeys = (ULONG *) _streamBuf.Get(); _pbCurrent = (BYTE*) _streamBuf.Get() + cbOld; // // Rebase all the pointers in the key array if the new base // address of the mapping is different than the old one. // BYTE * pbNewBase = (BYTE *) _streamBuf.Get(); if ( pbOldBase != pbNewBase ) { for ( unsigned i = 0; i < _cKeys; i++ ) { BYTE * p = (BYTE *) _aKeys[ i ]; p = p - pbOldBase + pbNewBase; _aKeys[ i ] = (CDirectoryKey *) p; } } else { ciDebugOut(( DEB_ITRACE, "no cidir rebasing needed\n" )); } // Toss the .dir file pages out of the working set _streamBuf.PurgeFromWorkingSet( -1, -1 ); } } //GrowIfNeeded //+--------------------------------------------------------------------------- // // Member: CiDirectory::LokWriteKey, private // // Synopsis: Adds entry to directory // // Arguments: [key] -- key to add // [bitOffset] -- offset of key in file // // History: 02-May-94 DwightKr Created // // Notes: Writes a single key to the mapped buffer. // //---------------------------------------------------------------------------- inline void CiDirectory::LokWriteKey( const CKeyBuf & key, BitOffset & bitOffset ) { // If this is the first key, open the stream for writing if ( 0 == _cKeys ) { // Close any existing stream from a failed merge LokClose(); _stream.Set( _storage.QueryExistingDirStream ( _objectId, TRUE ) ); if ( 0 == _stream.GetPointer() || !_stream->Ok() ) { _stream.Free(); _stream.Set( _storage.QueryNewDirStream ( _objectId ) ); } if ( ( 0 == _stream.GetPointer() ) || !_stream->Ok() ) THROW( CException ( STATUS_NO_MEMORY ) ); _stream->SetSize( _storage, COMMON_PAGE_SIZE ); _stream->MapAll( _streamBuf ); _pcKeys = (ULONG *) _streamBuf.Get(); _pbCurrent = (BYTE*) ( _pcKeys + 2 ); // Make sure the stream is in good shape in case we stop and restart // the merge. _pcKeys[0] = 0; _pcKeys[1] = 0; _stream->Flush( _streamBuf, 2 * sizeof ULONG, TRUE ); } Win4Assert( key.Count() <= 0xff ); BYTE count = (BYTE)key.Count(); unsigned size = CDirectoryKey::ComputeSize( count, key.Pid() ); GrowIfNeeded( size ); CDirectoryKey *pkey = new( _pbCurrent ) CDirectoryKey; // write the key pkey->Write( count, bitOffset, key.Pid(), key.GetBuf() ); if ( _cKeys >= _aKeys.Count() ) _aKeys.ReSize( __max( 32, _cKeys * 2 + 1 ) ); _aKeys[ _cKeys ] = pkey; _pbCurrent += size; _cKeys++; } //LokWriteKey //+--------------------------------------------------------------------------- // // Member: CiDirectory::LokBuildDir, public // // Synopsis: Builds the directory tree correspdong to the leaf pages // // Arguments: [maxKey] -- largest key to add to directory // // History: ??-???-?? ???????? Created // 02-May-94 DwightKr Added maxKey argument // // Notes: Writes keys from the beginning of the array up to and including // maxKey (which is usually the splitKey). This will result in // a directory that contains entries upto and including maxKey. // It is certainly possible that the key array has keys past // maxKey, but we don't want to add them to the directory since // maxKey represents the largest key that has persistently // written to disk during a master merge. // // The above would matter if downlevel master merges were // atomic throughout. If they ever become atomic, we // need to truncate the file after this key. // //---------------------------------------------------------------------------- void CiDirectory::LokBuildDir( const CKeyBuf & maxKey ) { ciDebugOut(( DEB_PDIR, "PDir::LokBuildDir %d\n", _cKeys )); CKeyBuf maxKeyValue; maxKeyValue.FillMax(); // // Write out a maximum key at the end. // The offset for the maximum key is not actually used my anyone, // since the directory is structured to find a key <= the desired // key. If someone generates a query for maxKey then everything // will be returned. The offset of maxKey is therefore arbitrary. // BitOffset maxBitOffset; maxBitOffset.Init( 0, 0 ); LokWriteKey( maxKeyValue, maxBitOffset ); // // Write the keys in the level 2 tree. // Write every DirectoryFanOut'th, so we can do a search on this Level2 // first, and reduce the working set. // _cLevel2Keys = 0; CDirectoryKey *pkey = _aKeys[ 0 ]; for ( unsigned x = 0; x < _cKeys; x++ ) { if ( 0 == ( x & ( eDirectoryFanOut - 1 ) ) ) { unsigned cb = pkey->Size(); unsigned oKey = (unsigned)((BYTE *) pkey - (BYTE *) _streamBuf.Get()); GrowIfNeeded( cb ); pkey = (CDirectoryKey *) ( (BYTE *) _streamBuf.Get() + oKey ); RtlCopyMemory( _pbCurrent, pkey, cb ); _pbCurrent += cb; _cLevel2Keys++; } pkey = pkey->NextKey(); } // // Write another max-key at the end. Decrement the # of keys since it // is incorrectly incremented in LokWriteKey() // LokWriteKey( maxKeyValue, maxBitOffset ); _cLevel2Keys++; _cKeys--; // write the # of keys in the file and the # of keys in level 2. _pcKeys[0] = _cKeys; _pcKeys[1] = _cLevel2Keys; // remember the size of the file used and release the mapping unsigned cbFile = (unsigned)(_pbCurrent - (BYTE *) _streamBuf.Get()); _stream->Flush( _streamBuf, cbFile, TRUE ); _stream->Unmap( _streamBuf ); // truncate and close the file _stream->SetSize( _storage, cbFile ); _stream->Close(); _stream.Free(); _cKeys = 0; // read the directory back in, in read-only mode ReadIn( FALSE ); } //LokBuildDir //+------------------------------------------------------------------------- // // Method: CiDirectory::ReadIn // // Synopsis: Load directory from storage // // Arguments: [fWrite] -- If TRUE, we're recovering from a stopped master // merge, either clean or dirty. This can be TRUE // even on read only catalogs if a MM is paused. // // History: 17-Feb-1994 KyleP Added header // //-------------------------------------------------------------------------- void CiDirectory::ReadIn( BOOL fWrite ) { _stream.Set( _storage.QueryExistingDirStream( _objectId, fWrite ) ); if ( ( 0 == _stream.GetPointer() ) || !_stream->Ok() ) { if ( fWrite ) { // New index; it'll be created later _stream.Free(); return; } else { Win4Assert( !"Corrupt directory" ); _storage.ReportCorruptComponent( L"IndexDirectory1" ); THROW( CException( CI_CORRUPT_DATABASE ) ); } } _stream->MapAll( _streamBuf ); BYTE * pbBuf = (BYTE *) _streamBuf.Get(); BYTE * pbEnd = pbBuf + _streamBuf.Size(); if ( _streamBuf.Size() <= ( 2 * sizeof ULONG ) ) { Win4Assert( !"Corrupt directory" ); _storage.ReportCorruptComponent( L"IndexDirectory2" ); THROW( CException( CI_CORRUPT_DATABASE ) ); } // The count of keys is stored in the first 4 bytes // The count of level 2 keys is stored in the next 4 bytes // There can be 0 level 2 keys if we stopped during the middle // of a master merge. // An empty index has a directory with 1 level 1 max key and 2 level // 2 max keys. _pcKeys = (ULONG *) pbBuf; _cKeys = _pcKeys[0]; _cLevel2Keys = _pcKeys[1]; if ( ( !fWrite && 0 == _cKeys ) || ( _cLevel2Keys > ( _cKeys + 1 ) ) || ( !fWrite && ( 0 == _cLevel2Keys ) ) ) { Win4Assert( !"Corrupt directory" ); _storage.ReportCorruptComponent( L"IndexDirectory3" ); THROW( CException( CI_CORRUPT_DATABASE ) ); } pbBuf += ( 2 * sizeof ULONG ); // Store pointers to each of the keys _aKeys.Free(); _aKeys.Init( _cKeys ); CDirectoryKey *pkey = new( pbBuf ) CDirectoryKey; for ( unsigned x = 0; x < _cKeys; x++ ) { if ( (BYTE *) pkey >= pbEnd ) { Win4Assert( !"Corrupt directory" ); _storage.ReportCorruptComponent( L"IndexDirectory5" ); THROW( CException( CI_CORRUPT_DATABASE ) ); } _aKeys[ x ] = pkey; pkey = pkey->NextKey(); } _aLevel2Keys.Free(); // // Level2 keys can exist when fWrite is TRUE if we failed a master // merge after building the level2 directory. It's likely the failure // is in recording the transaction that the merge completed. // if ( fWrite ) _cLevel2Keys = 0; else if ( 0 != _cLevel2Keys ) { _aLevel2Keys.Init( _cLevel2Keys ); for ( x = 0; x < _cLevel2Keys; x++ ) { if ( (BYTE *) pkey >= pbEnd ) { Win4Assert( !"Corrupt directory" ); _storage.ReportCorruptComponent( L"IndexDirectory6" ); THROW( CException( CI_CORRUPT_DATABASE ) ); } _aLevel2Keys[ x ] = pkey; pkey = pkey->NextKey(); } // If we didn't read as many keys as expected, the file is corrupt. // This is only true if the directory has level 2 keys, since the // file may have bogus data at the end if we are in the middle of // a master merge. if ( (BYTE *) pkey != pbEnd ) { ciDebugOut(( DEB_WARN, "pkey, pbEnd: 0x%x, 0x%x\n", pkey, pbEnd )); Win4Assert( !"Corrupt directory" ); _storage.ReportCorruptComponent( L"IndexDirectory4" ); THROW( CException( CI_CORRUPT_DATABASE ) ); } } // Initialize _pbCurrent, so we can restart a failed master merge if ( fWrite ) _pbCurrent = (BYTE *) pkey; // Toss the .dir file pages out of the working set _streamBuf.PurgeFromWorkingSet( -1, -1 ); // toss the level1 directory from the working set VirtualUnlock( _aKeys.GetPointer(), _aKeys.SizeOf() ); _fReadOnly = !fWrite; } //ReadIn //+------------------------------------------------------------------------- // // Member: CiDirectory::Close, public // // Effects: Closes the directory. Usually called when a merge fails. // // History: 21-Apr-92 BartoszM Created // //-------------------------------------------------------------------------- void CiDirectory::Close() { LokClose(); } //Close //+------------------------------------------------------------------------- // // Member: CiDirectory::LokClose, public // // Effects: Closes the directory. Usually called when a merge fails. // // History: 18-Nov-97 dlee Created // //-------------------------------------------------------------------------- void CiDirectory::LokClose() { if ( !_stream.IsNull() ) { // The _stream object can exist but not have a file open at this // point if a merge failed, so only unmap what is mapped. if ( ( _stream->Ok() ) && ( 0 != _streamBuf.Get() ) ) _stream->Unmap( _streamBuf ); _stream.Free(); } } //LokClose //+--------------------------------------------------------------------------- // // Function: DeleteKeysAfter // // Synopsis: Deletes all keys in the tree after the specified key. // // Arguments: [key] - The key after which all keys in the tree must be // deleted. // // History: 13-Nov-97 dlee Created // // Notes: Note that "key" is NOT deleted - only keys after "key" are // deleted. // //---------------------------------------------------------------------------- void CiDirectory::DeleteKeysAfter( const CKeyBuf & key ) { // // If we're restarting a failed master merge after LokBuildDir // succeeded, we have to close re-open the stream for write. // if ( _fReadOnly ) { LokClose(); ReadIn( TRUE ); } else if ( 0 != _cKeys ) ReMapIfNeeded(); // Now, wipe the level 2, since we're still building level 1 _cLevel2Keys = 0; _aLevel2Keys.Free(); // Truncate level 1 at keys <= key if ( 0 != _cKeys ) { // use the tree to find the greatest key <= key ULONG iKey = DoSeekForKeyBuf( key, _aKeys.GetPointer(), 0, _cKeys ); // // Move to the next key which must be > the seek key, but may be // beyond the # of keys in the directory. // iKey++; if ( iKey < _cKeys ) { Win4Assert( _aKeys[ iKey ]->IsGreaterThanKeyBuf( key ) ); _cKeys = iKey; _pbCurrent = (BYTE *) _aKeys[iKey]; } } } //DeleteKeysAfter //+--------------------------------------------------------------------------- // // Function: LokFlushDir // // Synopsis: Flushes the current state of the directory, up to and // including the key specified. Called at checkpoints during // a master merge. There may be some keys after the key // specified. Leave them in the file, but don't include them // in the count of keys. // // Arguments: [key] - The key before which and including is flushed // // History: 10-Aug-98 dlee Created // //---------------------------------------------------------------------------- void CiDirectory::LokFlushDir( const CKeyBuf & key ) { Win4Assert( !_fReadOnly ); Win4Assert( 0 == _cLevel2Keys ); // Truncate level 1 at keys <= key if ( 0 != _cKeys ) { ReMapIfNeeded(); // use the tree to find the greatest key <= key ULONG iKey = DoSeekForKeyBuf( key, _aKeys.GetPointer(), 0, _cKeys ); _pcKeys[0] = iKey + 1; _pcKeys[1] = 0; _streamBuf.Flush( TRUE ); } } //LokFlushDir //+--------------------------------------------------------------------------- // // Member: CiDirectory::MakeBackupCopy // // Synopsis: Creates a backup of the persistent directory using the // storage provided. // // Arguments: [storage] - Destination storage // [progressTracker] - Track progress and aborts. // // History: 3-18-97 srikants Created // //---------------------------------------------------------------------------- void CiDirectory::MakeBackupCopy( PStorage & storage, PSaveProgressTracker & progressTracker ) { CiStorage & dstStorage = *((CiStorage *)&storage); XPtr dstStream; CMmStreamBuf dstStreamBuf; dstStream.Set( dstStorage.QueryExistingDirStream( _objectId, TRUE ) ); if ( 0 == dstStream.GetPointer() || !dstStream->Ok() ) { dstStream.Free(); dstStream.Set( dstStorage.QueryNewDirStream( _objectId ) ); } Win4Assert( 0 != _stream.GetPointer() ); ULONG cb = _streamBuf.Size(); dstStream->SetSize( dstStorage, cb ); dstStream->MapAll( dstStreamBuf ); RtlCopyMemory( dstStreamBuf.Get(), _streamBuf.Get(), cb ); dstStream->Flush( dstStreamBuf, cb ); dstStream->Unmap( dstStreamBuf ); } //MakeBackupCopy