//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1991 - 2000. // // File: PropStor.cxx // // Contents: Persistent property store (external to docfile) // // Classes: CPropertyStore // // History: 27-Dec-1995 KyleP Created // //---------------------------------------------------------------------------- #include #pragma hdrstop #include #include #include #include #include #include #include #include #include #include #include #include #include unsigned const MAX_DIRECT = 10; const ULONG lowDiskWaterMark = 3 * 512 * 1024; // 1.5 MB //+--------------------------------------------------------------------------- // // Member: CPropStoreInfo::CPropStoreInfo, public // // Synopsis: Required for C++ EH. // // History: 27-Dec-95 KyleP Created. // //---------------------------------------------------------------------------- CPropStoreInfo::CPropStoreInfo(DWORD dwStoreLevel) : _fOwned(FALSE) { _info.dwStoreLevel = dwStoreLevel; _info.fDirty = 0; // // We intend to have a lean primary property store and a normal secondary // property store. Whatever is set here can be changed later as new info // becomes available. // _info.eRecordFormat = (PRIMARY_STORE == dwStoreLevel) ? eLean : eNormal; SYSTEM_INFO si; GetSystemInfo(&si); _ulOSPageSize = si.dwPageSize; Win4Assert(0 == COMMON_PAGE_SIZE%_ulOSPageSize); _cOSPagesPerLargePage = COMMON_PAGE_SIZE/_ulOSPageSize; } //+--------------------------------------------------------------------------- // // Member: CPropStoreInfo::CPropStoreInfo, public // // Synopsis: Copy constructor // // Arguments: [psi] -- Source // // History: 16-Jan-96 KyleP Created. // //---------------------------------------------------------------------------- CPropStoreInfo::CPropStoreInfo( CPropStoreInfo const & psi ) { _cRecPerPage = psi._cRecPerPage; _info = psi._info; _info.fDirty = fDirtyPropStore; _info.widMax = 0; _info.widFreeHead = 0; _info.widFreeTail = 0; _info.widStream = widInvalid; _info.cTopLevel = 0; _info.dwStoreLevel = psi._info.dwStoreLevel; _info.eRecordFormat = psi._info.eRecordFormat; _fOwned = FALSE; _aProp.Init( psi._aProp ); _cOSPagesPerLargePage = psi._cOSPagesPerLargePage; _ulOSPageSize = psi._ulOSPageSize; } //+--------------------------------------------------------------------------- // // Member: CPropStoreInfo::Empty // // Synopsis: Empties the contents. This method is called due to a failed // initialization of CI. After cleanup, another call to Init // may be made. // // History: 3-18-96 srikants Created // //---------------------------------------------------------------------------- void CPropStoreInfo::Empty() { delete [] _aProp.Acquire(); if ( _fOwned ) _xrsoPropStore.Free(); else _xrsoPropStore.Acquire(); } //+--------------------------------------------------------------------------- // // Member: CPropStoreInfo::FastTransfer // // Synopsis: Companion call to CPropertyStore::FastTransfer. Adjusts // metadata assuming CPropertyStore::FastTransfer has just // been called. // // History: 10-Oct-1997 KyleP Created // //---------------------------------------------------------------------------- void CPropStoreInfo::FastTransfer( CPropStoreInfo const & psi ) { _info.widMax = psi._info.widMax; _info.widFreeHead = psi._info.widFreeHead; _info.widFreeTail = psi._info.widFreeTail; _info.cTopLevel = psi._info.cTopLevel; } //+--------------------------------------------------------------------------- // // Member: CPropStoreInfo::Init, public // // Synopsis: Loads metadata from persistent location into memory. // // Arguments: [pobj] -- Stream(s) in which metadata is stored. // // History: 27-Dec-95 KyleP Created. // //---------------------------------------------------------------------------- void CPropStoreInfo::Init( XPtr & xobj, DWORD dwStoreLevel ) { _xrsoPropStore.Set( xobj.Acquire() ); _fOwned = TRUE; // // Load header // CRcovStorageHdr & hdr = _xrsoPropStore->GetHeader(); struct CRcovUserHdr data; hdr.GetUserHdr( hdr.GetPrimary(), data ); RtlCopyMemory( &_info, &data._abHdr, sizeof(_info) ); _info.dwStoreLevel = dwStoreLevel; // // If we only have no properties, set the record format based on // storage type. Else, assert that we have the expected format type. // if ( 0 == CountProps() ) { _info.eRecordFormat = (PRIMARY_STORE == dwStoreLevel) ? eLean : eNormal; } #if CIDBG == 1 else { if ( CountFixedProps() == CountProps() ) Win4Assert( eLean == GetRecordFormat() ); else Win4Assert( eNormal == GetRecordFormat() ); } #endif // CIDBG // // For consistency... // if ( 0 == _info.widStream ) _info.widStream = widInvalid; if ( 0 == _info.culRecord ) _info.culRecord = (eLean == GetRecordFormat()) ? COnDiskPropertyRecord::FixedOverheadLean() : COnDiskPropertyRecord::FixedOverheadNormal(); _cRecPerPage = COMMON_PAGE_SIZE / (_info.culRecord * sizeof(ULONG)); ciDebugOut(( DEB_PROPSTORE, "PROPSTORE: %s Store.\n", (dwStoreLevel == PRIMARY_STORE) ? "Primary" : "Secondary")); ciDebugOut(( DEB_PROPSTORE, "PROPSTORE: Version = 0x%x\n", _info.Version )); ciDebugOut(( DEB_PROPSTORE, "PROPSTORE: Record size = %d bytes\n", _info.culRecord * sizeof(ULONG) )); ciDebugOut(( DEB_PROPSTORE, "PROPSTORE: Fixed record size = %d bytes\n", _info.culFixed * sizeof(ULONG) )); ciDebugOut(( DEB_PROPSTORE, "PROPSTORE: Store is %s\n", (_info.fDirty & fDirtyPropStore) ? "dirty" : "clean" )); ciDebugOut(( DEB_PROPSTORE, "PROPSTORE: Is NOT bacup mode: %d\n", 0 != (_info.fDirty & fNotBackedUp) )); ciDebugOut(( DEB_PROPSTORE, "PROPSTORE: %d properties stored\n", _info.cTotal )); ciDebugOut(( DEB_PROPSTORE, "PROPSTORE: %d fixed properties stored\n", _info.cFixed )); ciDebugOut(( DEB_PROPSTORE, "PROPSTORE: %s record format.\n", (eLean == GetRecordFormat()) ? "Lean" : "Normal (not-lean)")); ciDebugOut(( DEB_PROPSTORE, "PROPSTORE: Hash size = %u\n", _info.cHash )); ciDebugOut(( DEB_PROPSTORE, "PROPSTORE: Max workid = %u\n", _info.widMax )); ciDebugOut(( DEB_PROPSTORE, "PROPSTORE: %d records per %dK page\n", _cRecPerPage, COMMON_PAGE_SIZE / 1024 )); // // Load properties // _aProp.Init( _info.cHash ); CRcovStrmReadTrans xact( _xrsoPropStore.GetReference() ); CRcovStrmReadIter iter( xact, sizeof( CPropDesc ) ); CPropDesc temp; while ( !iter.AtEnd() ) { iter.GetRec( &temp ); if ( temp.IsFixedSize() ) ciDebugOut(( DEB_PROPSTORE, "PROPSTORE: pid = 0x%x, ordinal = %u, size = %u, offset = %u, fixed\n", temp.Pid(), temp.Ordinal(), temp.Size(), temp.Offset() )); else ciDebugOut(( DEB_PROPSTORE, "PROPSTORE: pid = 0x%x, ordinal = %u, cbMax = %d, vt = 0x%x variable\n", temp.Pid(), temp.Ordinal(), temp.Size(), temp.Type() )); _aProp[Lookup(temp.Pid())] = temp; } } //+--------------------------------------------------------------------------- // // Member: CPropStoreInfo::Commit // // Synopsis: Commits the transaction on disk and then in-memory. // // Arguments: [psi] - The new property store information. // [xact] - The persistent transaction to be committed. // // History: 3-26-96 srikants Created // // Notes: // //---------------------------------------------------------------------------- void CPropStoreInfo::Commit( CPropStoreInfo & psi, CRcovStrmWriteTrans & xact ) { // // Copy the in-memory structure to disk. // CRcovStorageHdr & hdr = _xrsoPropStore->GetHeader(); CRcovStrmWriteIter iter( xact, sizeof(CPropDesc) ); hdr.SetUserDataSize( hdr.GetBackup(), iter.UserDataSize( psi._info.cTotal ) ); unsigned iRec = 0; for ( unsigned i = 0; i < psi._aProp.Count(); i++ ) { ciDebugOut(( DEB_ITRACE, "_aProp[%d], Pid 0x%x Size %d Type 0x%x\n", i, psi._aProp[i].Pid(), psi._aProp[i].Size(), psi._aProp[i].Type() )); if ( !psi._aProp[i].IsFree() ) { psi._aProp[i].SetRecord( iRec ); iter.SetRec( &psi._aProp[i], iRec ); ciDebugOut(( DEB_ITRACE, "Pid 0x%x --> Slot %d\n", psi._aProp[i].Pid(), iRec )); iRec++; } } Win4Assert( iRec == psi._info.cTotal ); struct CRcovUserHdr data; RtlCopyMemory( &data._abHdr, &psi._info, sizeof(_info) ); hdr.SetCount(hdr.GetBackup(), psi._info.cTotal ); hdr.SetUserHdr( hdr.GetBackup(), data ); // // First commit the on-disk transaction. // xact.Commit(); // // NO FAILURES AFTER THIS // // // Copy the data from the new property store info. // _cRecPerPage = psi._cRecPerPage; _info = psi._info; Win4Assert( psi._xrsoPropStore.IsNull() ); Win4Assert( _fOwned && !psi._fOwned ); // // Update the property array. // delete [] _aProp.Acquire(); unsigned count = psi._aProp.Count(); _aProp.Set( count, psi._aProp.Acquire() ); } //+--------------------------------------------------------------------------- // // Member: CPropStoreInfo::NextWorkId // // Synopsis: Changes the workid for the stream to be the next logical // one. // // Returns: The new work id. // // History: 3-26-96 srikants Created // //---------------------------------------------------------------------------- WORKID CPropStoreInfo::NextWorkId(CiStorage & storage) { _info.Version++; _info.widStream = storage.CreateObjectId( CIndexId( _info.Version % (1 << 16), 0 ), PStorage::eNonSparseIndex ); return _info.widStream; } //+--------------------------------------------------------------------------- // // Member: CPropStoreInfo::InitWorkId // // Synopsis: Initializes the workid from the version number. // // Arguments: [storage] - Destination storage // // Returns: The WorkId generated for the property store stream // // History: 3-28-97 srikants Created // // Notes: When we add a property, we have to bump up the version # but // in case we are creating a backup, we should use the same // version #. // //---------------------------------------------------------------------------- WORKID CPropStoreInfo::InitWorkId(CiStorage & storage) { _info.widStream = storage.CreateObjectId( CIndexId( _info.Version % (1 << 16), 0 ), PStorage::eNonSparseIndex ); return _info.widStream; } //+--------------------------------------------------------------------------- // // Member: CPropStoreInfo::Add, public // // Synopsis: Add a new property to set of cached properties. // // Arguments: [pid] -- Propid // [vt] -- Datatype // [cbMaxLen] -- Soft-maximum length. Used to compute default // size of record. // [fCanBeModified] // -- Indicates if the property can be modified once set. // [storage] -- Storage object (for object creation). // // Returns: TRUE if a change was made to metadata // // History: 27-Dec-95 KyleP Created. // 05-Jun-96 KyleP Moved on-disk transaction to higher level // 13-Nov-97 KrishnaN Modifiable? // 19-Dec-97 KrishnaN Lean record support. // //---------------------------------------------------------------------------- BOOL CPropStoreInfo::Add( PROPID pid, ULONG vt, unsigned cbMaxLen, BOOL fCanBeModified, CiStorage & storage ) { // // Check for exact duplicate. // CPropDesc const * pdesc = GetDescription( pid ); if ( 0 != pdesc ) { if ( !pdesc->Modifiable() ) { ciDebugOut(( DEB_ITRACE, "Pid 0x%x: Exists in cache and marked UNModifiable!\n", pid )); return FALSE; } if ( vt == pdesc->Type() && cbMaxLen == pdesc->Size() ) { ciDebugOut(( DEB_ITRACE, "Pid 0x%x: Type %d, Size %d already in cache.\n", pid, vt, cbMaxLen )); return FALSE; } ciDebugOut(( DEB_ITRACE, "Pid 0x%x: Type (%d, %d), Size (%d, %d)\n", pid, pdesc->Type(), vt, pdesc->Size(), cbMaxLen )); } else ciDebugOut(( DEB_ITRACE, "Can't find pid 0x%x in cache.\n", pid )); // // Compute size and position (ordinal) of property. // BOOL fFixed; switch ( vt ) { case VT_I1: case VT_UI1: fFixed = TRUE; cbMaxLen = 1; break; case VT_I2: case VT_UI2: case VT_BOOL: fFixed = TRUE; cbMaxLen = 2; break; case VT_I4: case VT_UI4: case VT_R4: case VT_ERROR: fFixed = TRUE; cbMaxLen = 4; break; case VT_I8: case VT_UI8: case VT_R8: case VT_CY: case VT_DATE: case VT_FILETIME: fFixed = TRUE; cbMaxLen = 8; break; case VT_CLSID: fFixed = TRUE; cbMaxLen = sizeof(GUID); break; default: fFixed = FALSE; break; } // Ensure we don't exceed the max possible size of a single record. // The trick to the check is the assumption that the maximum impact // on the size of the record is the size of the largest fixed prop. // Currently it is sizeof(GUID), so we just check that there is // enough space for it. And if there is, we are fine! // Fix for bug 119508. // If this assert doesn't hold, then change it to whatever size is the // maximum fixed size and change the (_info.culFixed*4 + sizeof(GUID)) // portion of the following if statement to reflect that! Win4Assert(!fFixed || (fFixed && cbMaxLen <= sizeof(GUID))); if ((_info.culFixed*4 + sizeof(GUID)) >= COMMON_PAGE_SIZE) { ciDebugOut(( DEB_ITRACE, "Total fixed size (in bytes) %d exceeds" " max record size of %d\n", _info.culFixed*4 + cbMaxLen, COMMON_PAGE_SIZE )); return FALSE; } BOOL fDidDeletion = Delete( pid, storage ); // // Hash table size will have to change if: // a) pid > MAX_DIRECT and we're in direct hash mode, or // b) hash table is over 3/4 full, or // if ( (pid > MAX_DIRECT && _info.cHash == MAX_DIRECT) || // (hdr.GetCount(hdr.GetPrimary()) * 3 + 1 > _info.cHash * sizeof(ULONG)) ) (_info.cTotal * sizeof(ULONG) + 1 > _info.cHash * 3) ) { XArray xold( _aProp ); _info.cHash *= 2; if ( 0 == _info.cHash ) _info.cHash = MAX_DIRECT; ciDebugOut(( DEB_PROPSTORE, "growing _aProp size from %d to %d\n", xold.Count(), _info.cHash )); _aProp.Init( _info.cHash ); for ( unsigned i = 0; i < xold.Count(); i++ ) { if ( !xold[i].IsFree() ) { ciDebugOut(( DEB_PROPSTORE, "re-adding pid %d from [%d] to [%d]\n", i, LookupNew(xold[i].Pid()) )); _aProp[LookupNew(xold[i].Pid())] = xold[i]; } } } // // Ordinal and starting offset are computed differently for fixed and // variable properties. A new fixed property goes at the end of the // fixed section, and a new variable property goes at the end. // DWORD oStart; DWORD ordinal; if ( fFixed ) { ordinal = _info.cFixed; oStart = _info.culFixed; _info.cFixed++; _info.culFixed += (cbMaxLen - 1) / sizeof(ULONG) + 1; // // Since we just added an ordinal in the middle, we now need to go through and // find the first variable length property and move it to the end. // for ( unsigned i = 0; i < _aProp.Count(); i++ ) { if ( !_aProp[i].IsFree() && _aProp[i].Ordinal() == ordinal ) { #if DBG == 1 || CIDBG == 1 ULONG ordinal2 = _aProp[i].Ordinal(); #endif _aProp[i].SetOrdinal( _info.cTotal ); ciDebugOut(( DEB_PROPSTORE, "PROPSTORE: pid = 0x%x moved from ordinal = %u to ordinal %u\n", _aProp[i].Pid(), ordinal2, _aProp[i].Ordinal() )); break; } } } else { ordinal = _info.cTotal; oStart = 0xFFFFFFFF; } // // Add new record to in-memory and on-disk structures. // ciDebugOut(( DEB_PROPSTORE, "PROPSTORE: pid = 0x%x added at _aProp[%d], vt: 0x%x, old pid: 0x%x\n", pid, LookupNew(pid), vt, _aProp[LookupNew(pid)].Pid() )); _aProp[LookupNew(pid)].Init( pid, // Propid vt, // Data type oStart, // Starting offset cbMaxLen, // Length estimate ordinal, // Ordinal (position) of property 0, // Record number (filled in later) fCanBeModified); // Can this metadata be modified later? _info.cTotal++; // // Adjust size. We preallocated _aul[PREV], _aul[NEXT] and _aul[FREEBLOCKSIZE] to serve as // free list ptrs. So we should avoid allocating these ULONGs again. Watch the // allocation of dword for existence bits and space for first property. Since // all properties are allocated on sizeof(ULONG)-byte boundaries, we can monitor the allocation // of space for the first property and be sure that aul[1]'s preallocation gets // accounted for. // // The first time a property is added, it will cause a _aul[PREV] to be allocated // for existence bits. Since we preallocated that, we won't increase the // record length for the first set of 16 existence bits. if ( _info.cTotal > 1 && (_info.cTotal % 16) == 1 ) _info.culRecord += 1; // New dword of existence bits _info.culRecord += (cbMaxLen-1) / sizeof(ULONG) + 1; // The property itself // account for the preallocation of _aul[NEXT] (when space for first prop is allocated) // and _aul[FREEBLOCKSIZE] (when space for second prop is allocated). If we only have one prop allocated, // we will run with an overhead of 1 DWORD, but we know that we have many more than 2 properties! if ( _info.cTotal <= 2 ) _info.culRecord -= 1; if ( !fFixed ) _info.culRecord += 1; // Variable size dword _cRecPerPage = COMMON_PAGE_SIZE / (_info.culRecord * sizeof(ULONG)); ciDebugOut(( DEB_PROPSTORE, "New record size: %d bytes. %d records per %dK page\n", _info.culRecord * sizeof(ULONG), _cRecPerPage, COMMON_PAGE_SIZE / 1024 )); Win4Assert( _cRecPerPage > 0 ); if ( _cRecPerPage == 0 ) { ciDebugOut(( DEB_ERROR, "Record size > %u bytes!\n", COMMON_PAGE_SIZE )); THROW( CException( STATUS_INVALID_PARAMETER ) ); } #if CIDBG == 1 for ( unsigned i = 0; i < _aProp.Count(); i++ ) { ciDebugOut(( DEB_PROPSTORE, "_aProp[%d].pid: 0x%x\n", i, _aProp[i].Pid() )); } #endif // CIDBG == 1 return TRUE; } //+--------------------------------------------------------------------------- // // Member: CPropStoreInfo::DetectFormat, public // // Synopsis: Detect the type of records to be used and change according to that. // // History: 31-Dec-97 KrishnaN Created. // //---------------------------------------------------------------------------- void CPropStoreInfo::DetectFormat() { // In CPropStoreInfo::Init() we initialized record length based on the // assumption that primary is lean and secondary is normal. That could have // changed, so account for that. LONG lOldOverhead = (eLean == GetRecordFormat()) ? COnDiskPropertyRecord::FixedOverheadLean() : COnDiskPropertyRecord::FixedOverheadNormal(); // Determine the format of the records in the target property store. SetRecordFormat( CountFixedProps() == CountProps() ? eLean : eNormal); LONG lNewOverhead = (eLean == GetRecordFormat()) ? COnDiskPropertyRecord::FixedOverheadLean() : COnDiskPropertyRecord::FixedOverheadNormal(); // Adjust the overhead _info.culRecord += (lNewOverhead - lOldOverhead); _cRecPerPage = COMMON_PAGE_SIZE / (_info.culRecord * sizeof(ULONG)); ciDebugOut(( DEB_PROPSTORE, "Incorporated props from registry. " "New record size: %d bytes. %d records per %dK page\n", _info.culRecord * sizeof(ULONG), _cRecPerPage, COMMON_PAGE_SIZE / 1024 )); } //+--------------------------------------------------------------------------- // // Member: CPropStoreInfo::Delete, public // // Synopsis: Deletes a property from set of cached properties. // // Arguments: [pid] -- Propid // [storage] -- Storage object (for object creation). // // Returns: TRUE if a change was made to metadata // // History: 27-Dec-95 KyleP Created. // 05-Jun-96 KyleP Moved on-disk transaction to higher level // //---------------------------------------------------------------------------- BOOL CPropStoreInfo::Delete( PROPID pid, CiStorage & storage ) { // // Is there anything to get rid of? // CPropDesc const * pdesc = GetDescription( pid ); if ( 0 == pdesc ) return FALSE; if (!pdesc->Modifiable()) { ciDebugOut(( DEB_ITRACE, "Pid 0x%x: Cannot be deleted. Marked UNModifiable!\n", pid )); return FALSE; } if ( pdesc->IsFixedSize() ) { _info.cFixed--; _info.culFixed -= (pdesc->Size() - 1) / sizeof(ULONG) + 1; } for ( unsigned i = 0; i < _aProp.Count(); i++ ) { if ( _aProp[i].Pid() != pidInvalid && _aProp[i].Ordinal() > pdesc->Ordinal() ) { if ( pdesc->IsFixedSize() && _aProp[i].IsFixedSize() ) _aProp[i].SetOffset( _aProp[i].Offset() - ((pdesc->Size() - 1) / sizeof(ULONG) + 1) ); _aProp[i].SetOrdinal( _aProp[i].Ordinal() - 1 ); } } // // Global bookeeping // _info.cTotal--; // // Adjust size // _info.culRecord -= (pdesc->Size()-1) / sizeof(ULONG) + 1; // The property itself if ( !pdesc->IsFixedSize() ) _info.culRecord -= 1; // Variable size dword if ( ((_info.cTotal) % 16) == 0 ) _info.culRecord -= 1; // New dword of existence bits _cRecPerPage = COMMON_PAGE_SIZE / (_info.culRecord * sizeof(ULONG)); ciDebugOut(( DEB_PROPSTORE, "New record size: %d bytes. %d records per %dK page\n", _info.culRecord * sizeof(ULONG), _cRecPerPage, COMMON_PAGE_SIZE / 1024 )); Win4Assert( _cRecPerPage > 0 ); if ( _cRecPerPage == 0 ) { ciDebugOut(( DEB_ERROR, "Record size > %u bytes!\n", COMMON_PAGE_SIZE )); THROW( CException( STATUS_INVALID_PARAMETER ) ); } // Ensure that we have space for _aul[PREV], _aul[NEXT], and _aul[FREEBLOCKSIZE] We will have that as // long as we have at least two properties. ULONG ulOverhead = (eLean == GetRecordFormat()) ? COnDiskPropertyRecord::FixedOverheadLean() : COnDiskPropertyRecord::FixedOverheadNormal(); if ( _info.culRecord < ulOverhead ) { // As long as we have 2 or more properties, we wouldn't go under the // fixed overhead. Assert that! Win4Assert(_info.cTotal <= 1); _info.culRecord = ulOverhead; } // // Free record. // _aProp[Lookup(pid)].Free(); return TRUE; } //+--------------------------------------------------------------------------- // // Member: CPropStoreInfo::ChangeDirty, public // // Synopsis: Persistently change state of dirty bitfield // // Arguments: [fDirty] -- New state for dirty bitfield. // // History: 16-Jan-96 KyleP Created. // //---------------------------------------------------------------------------- void CPropStoreInfo::ChangeDirty( int fDirty ) { // In some error cases this can be null. if ( _xrsoPropStore.IsNull() ) return; _info.fDirty = fDirty; // // Atomically write dirty bit. // CRcovStorageHdr & hdr = _xrsoPropStore->GetHeader(); CRcovStrmWriteTrans xact( _xrsoPropStore.GetReference() ); struct CRcovUserHdr data; RtlCopyMemory( &data._abHdr, &_info, sizeof(_info) ); hdr.SetCount(hdr.GetBackup(), hdr.GetCount(hdr.GetPrimary()) ); hdr.SetUserHdr( hdr.GetBackup(), data ); xact.Commit(); } //+--------------------------------------------------------------------------- // // Member: CPropStoreInfo::Lookup, public // // Synopsis: Looks up pid in hash table. // // Arguments: [pid] -- Propid // // Returns: Index into hash table (_aProp) of pid, or first unused // entry on chain if pid doesn't exist. // // History: 27-Dec-95 KyleP Created. // //---------------------------------------------------------------------------- unsigned CPropStoreInfo::Lookup( PROPID pid ) { unsigned hash = pid % _info.cHash; // short-path for common case if ( pid == _aProp[hash].Pid() && _aProp[hash].IsInUse() ) return hash; unsigned start = hash; unsigned probe = hash; while ( pid != _aProp[hash].Pid() && _aProp[hash].IsInUse() ) { //ciDebugOut(( DEB_ERROR, "Hash: %u, Probe: %u, pid 0x%x != table 0x%x\n", // hash, probe, pid, _aProp[hash].Pid() )); hash = (hash + probe) % _info.cHash; if ( start == hash ) { Win4Assert( probe != 1 ); probe = 1; hash = (hash + probe) % _info.cHash; } } return hash; } //+--------------------------------------------------------------------------- // // Member: CPropStoreInfo::LookupNew, public // // Synopsis: Looks up pid in hash table, treats nulled entries as empty. // // Arguments: [pid] -- Propid // // Returns: Index into hash table (_aProp) of pid, or first unused // entry on chain if pid doesn't exist. // // History: 27-Dec-95 KyleP Created. // //---------------------------------------------------------------------------- unsigned CPropStoreInfo::LookupNew( PROPID pid ) { unsigned hash = pid % _info.cHash; unsigned start = hash; unsigned probe = hash; while ( pid != _aProp[hash].Pid() && !_aProp[hash].IsFree() ) { hash = (hash + probe) % _info.cHash; if ( start == hash ) { Win4Assert( probe != 1 ); probe = 1; hash = (hash + probe) % _info.cHash; } } return hash; } void CPhysPropertyStore::ReOpenStream() { Win4Assert( _stream.IsNull() ); Win4Assert( !"Don't call CPhysPropertyStore::ReOpenStream" ); //_stream = _storage.QueryExistingPropStream ( _obj, PStorage::eOpenForWrite ); } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::CPropertyStore, public // // Synopsis: Required for C++ EH. // // History: 27-Dec-95 KyleP Created. // //---------------------------------------------------------------------------- CPropertyStore::CPropertyStore(CPropStoreManager& propStoreMgr, DWORD dwStoreLevel) : _pStorage( 0 ), _aFreeBlocks( 0 ), _fAbort(FALSE), _fIsConsistent(TRUE), _ppsNew( 0 ), _fNew( FALSE ), _ulBackupSizeInPages( CI_PROPERTY_STORE_BACKUP_SIZE_DEFAULT ), _ulPSMappedCache( CI_PROPERTY_STORE_MAPPED_CACHE_DEFAULT ), _PropStoreInfo( dwStoreLevel ), _propStoreMgr( propStoreMgr ), _dwStoreLevel( dwStoreLevel ) { #if CIDBG == 1 _sigPSDebug = 0x2047554245445350i64; // PSDEBUG _tidReadSet = _tidReadReset = _tidWriteSet = _tidWriteReset = 0xFFFFFFFF; _xPerThreadReadCounts.Init( cTrackThreads ); _xPerThreadWriteCounts.Init( cTrackThreads ); RtlZeroMemory( _xPerThreadReadCounts.GetPointer(), cTrackThreads * sizeof(_xPerThreadReadCounts[0]) ); RtlZeroMemory( _xPerThreadWriteCounts.GetPointer(), cTrackThreads * sizeof(_xPerThreadWriteCounts[0]) ); #endif Win4Assert(PRIMARY_STORE == dwStoreLevel || SECONDARY_STORE == dwStoreLevel); #if CIDBG == 1 // Allocate an array to track what records are currently locked. // To be used to acquire all write locks. _pbRecordLockTracker = new BYTE[LockMgr().UniqueRecordCount()]; RtlZeroMemory(_pbRecordLockTracker, sizeof(BYTE)*LockMgr().UniqueRecordCount()); #endif // CIDBG } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::~CPropertyStore, public // // Synopsis: Closes/flushes property cache. // // History: 27-Dec-95 KyleP Created. // //---------------------------------------------------------------------------- CPropertyStore::~CPropertyStore() { delete _ppsNew; delete _aFreeBlocks; #if CIDBG == 1 delete [] _pbRecordLockTracker; #endif } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::Empty // // Synopsis: Empties out the intitialized members and prepares for a // re-init. // // History: 3-18-96 srikants Created // //---------------------------------------------------------------------------- void CPropertyStore::Empty() { _PropStoreInfo.Empty(); delete _xPhysStore.Acquire(); _pStorage = 0; } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::FastInit, public // // Synopsis: Initialize property store (two-phase construction) // // Arguments: [pStorage] -- Storage object. // // History: 27-Dec-95 KyleP Created. // 06-Mar-96 SrikantS Split into FastInit and LongInit // 23-Feb-98 KitmanH Code added to deal with read-only // catalogs // //---------------------------------------------------------------------------- void CPropertyStore::FastInit( CiStorage * pStorage) { Win4Assert( 0 != _ulPSMappedCache ); _pStorage = pStorage; XPtr xObj( _pStorage->QueryPropStore( 0, _dwStoreLevel ) ); _PropStoreInfo.Init( xObj, _dwStoreLevel ); Win4Assert(GetStoreLevel() == _dwStoreLevel); WORKID wid = _PropStoreInfo.WorkId(); if ( widInvalid != wid ) { SStorageObject xobj( _pStorage->QueryObject( wid ) ); PStorage::EOpenMode mode = _pStorage->IsReadOnly() ? PStorage::eOpenForRead : PStorage::eOpenForWrite; XPtr xmmstrm ( _pStorage->QueryExistingPropStream( xobj.GetObj(), mode, GetStoreLevel() )); Win4Assert( !xmmstrm.IsNull() ); if ( !xmmstrm->Ok() ) { ciDebugOut(( DEB_ERROR, "Open of index %08x failed\n", wid )); NTSTATUS status = xmmstrm->GetStatus(); if ( STATUS_DISK_FULL == status || HRESULT_FROM_WIN32(ERROR_DISK_FULL) == status || STATUS_INSUFFICIENT_RESOURCES == status || CI_E_CONFIG_DISK_FULL == status ) { CEventLog eventLog( NULL, wcsCiEventSource ); CEventItem item( EVENTLOG_WARNING_TYPE, CI_SERVICE_CATEGORY, MSG_CI_LOW_DISK_SPACE, 2 ); item.AddArg( _pStorage->GetVolumeName() ); item.AddArg( lowDiskWaterMark ); eventLog.ReportEvent( item ); THROW( CException( CI_E_CONFIG_DISK_FULL ) ); } else if ( xmmstrm->FStatusFileNotFound() ) { // // We don't have code to handle such failures, hence mark // catalog as corrupt; otherwise throw e_fail // ciDebugOut(( DEB_ERROR, "Stream %08x not found\n", wid )); Win4Assert( !"Stream not found\n" ); _pStorage->ReportCorruptComponent( L"PhysStorage2" ); THROW( CException( CI_CORRUPT_DATABASE )); } __int64 sizeRemaining, sizeTotal; _pStorage->GetDiskSpace ( sizeTotal, sizeRemaining ); if ( sizeRemaining < lowDiskWaterMark ) { CEventLog eventLog( NULL, wcsCiEventSource ); CEventItem item( EVENTLOG_WARNING_TYPE, CI_SERVICE_CATEGORY, MSG_CI_LOW_DISK_SPACE, 2 ); item.AddArg( _pStorage->GetVolumeName() ); item.AddArg( lowDiskWaterMark ); eventLog.ReportEvent( item ); THROW( CException( CI_E_CONFIG_DISK_FULL ) ); } else THROW( CException( status )); } // // mmstrm ownership is transferred regardless of whether the // constructor succeeds. // _xPhysStore.Set( new CPhysPropertyStore( *_pStorage, xobj.GetObj(), wid, xmmstrm.Acquire(), mode, _ulPSMappedCache ) ); // grow the file 2 meg at a time _xPhysStore->SetPageGrowth( 32 * COMMON_PAGE_SIZE / CI_PAGE_SIZE ); } } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::LongInit // // Synopsis: If the propstore was dirty when shut down, run the recovery // operation. // // Arguments: [fWasDirty] -- dirty flag is returned here // [cInconsistencies] -- returns number of inconsistencies found // [pfnUpdateCallback]-- Callback to be called to update docs during // recovery. the prop store has no knowledge of // doc store, so this callback is needed. // [pUserData] -- will be echoed back through callback. // // Returns: // // History: 3-06-96 srikants Created // // Notes: The propstore is locked for write during recovery, but // reads are still permitted. // //---------------------------------------------------------------------------- void CPropertyStore::LongInit( BOOL & fWasDirty, ULONG & cInconsistencies, T_UpdateDoc pfnUpdateCallback, void const *pUserData ) { // // Close the existing prop store backup stream // _xPSBkpStrm.Free(); // // Recover from dirty shutdown. // if ( _PropStoreInfo.IsDirty() ) { ciDebugOut(( DEB_WARN, "Property store shut down dirty. Restoring...\n" )); fWasDirty = TRUE; CPropertyStoreRecovery recover(*this, pfnUpdateCallback, pUserData); recover.DoRecovery(); cInconsistencies = recover.GetInconsistencyCount(); } else { fWasDirty = FALSE; cInconsistencies = 0; InitFreeList(); } WORKID wid = _PropStoreInfo.WorkId(); Win4Assert( widInvalid != wid ); SStorageObject xobj( _pStorage->QueryObject( wid ) ); // // At this point we have no use for any existing property store backup file. // Create a new backup file so it will be initialized correctly based on the // volume's sector size and architecture's page size and user specified number // of pages to be backed up. // _xPSBkpStrm.Set(_pStorage->QueryNewPSBkpStream( xobj.GetObj(), _ulBackupSizeInPages, GetStoreLevel() )); } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::BeginTransaction, public // // Synopsis: Begins a schema transaction. Any existing transaction will be // aborted. // // Returns: Token representing transaction. // // History: 27-Dec-95 KyleP Created. // //---------------------------------------------------------------------------- ULONG_PTR CPropertyStore::BeginTransaction() { // // Do we already have pending changes? // if ( 0 != _ppsNew ) EndTransaction( (ULONG_PTR)_ppsNew, FALSE, pidSecurity ); _fNew = FALSE; _ppsNew = new CPropertyStore( *this, _pStorage ); return (ULONG_PTR)_ppsNew; } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::Setup, public // // Synopsis: Setup a property description. Property may already exist // in the cache. // // Arguments: [pid] -- Propid // [vt] -- Datatype of property. VT_VARIANT if unknown. // [cbMaxLen] -- Soft-maximum length for variable length // properties. This much space is pre-allocated // in original record. // [ulToken] -- Token of transaction // [fCanBeModified] - Can the prop meta info be modified once set? // // Returns: TRUE if meta info has changed. FALSE otherwise. // // History: 27-Dec-95 KyleP Created. // //---------------------------------------------------------------------------- void CPropertyStore::Setup( PROPID pid, ULONG vt, DWORD cbMaxLen, ULONG_PTR ulToken, BOOL fCanBeModified ) { if ( ulToken != (ULONG_PTR)_ppsNew ) { ciDebugOut(( DEB_ERROR, "Transaction mismatch: 0x%x vs. 0x%x\n", ulToken, _ppsNew )); THROW( CException( STATUS_TRANSACTION_NO_MATCH ) ); } TRY { // // Make the change. NOTE: "|| _fNew" must be after call, or Add/Delete may not be called. // if ( 0 == cbMaxLen ) _fNew = _ppsNew->_PropStoreInfo.Delete( pid, *_pStorage ) || _fNew; else _fNew = _ppsNew->_PropStoreInfo.Add( pid, vt, cbMaxLen, fCanBeModified, *_pStorage ) || _fNew; } CATCH( CException, e ) { ciDebugOut(( DEB_ERROR, "Error 0x%X while setting up property 0x%X\n", e.GetErrorCode(), pid )); delete _ppsNew; _ppsNew = 0; _fNew = FALSE; RETHROW(); } END_CATCH } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::EndTransaction, public // // Synopsis: End property transaction, and maybe commit changes. // // Arguments: [ulToken] -- Token of transaction // [fCommit] -- TRUE --> Commit transaction // [pidFixed] -- Every workid with this pid will move to the // same workid in the new property cache. // Usually pidPath. // // History: 27-Dec-95 KyleP Created. // //---------------------------------------------------------------------------- void CPropertyStore::EndTransaction( ULONG_PTR ulToken, BOOL fCommit, PROPID pidFixed ) { if ( ulToken != (ULONG_PTR)_ppsNew ) { ciDebugOut(( DEB_ERROR, "PropertyStore: Transaction mismatch: 0x%x vs. 0x%x\n", ulToken, _ppsNew )); THROW( CException( STATUS_TRANSACTION_NO_MATCH ) ); } // // Squirrel away previous store. // WORKID widOld = _PropStoreInfo.WorkId(); WORKID widNew = widInvalid; TRY { if ( fCommit && _fNew ) { ciDebugOut(( DEB_ITRACE, "Committing changes to property metadata.\n" )); CRcovStrmWriteTrans xact( *_PropStoreInfo.GetRcovObj() ); _ppsNew->_PropStoreInfo.DetectFormat(); _ppsNew->CreateStorage( widInvalid ); // use next workid _ppsNew->InitFreeList(); widNew = _ppsNew->_PropStoreInfo.WorkId(); // Transfer existing data to new. BOOL fAbort = FALSE; if ( widOld != widInvalid ) Transfer( *_ppsNew, pidFixed, fAbort ); // // Prevent any readers from coming in until we commit the transaction. // Lock down the source so no readers will be able to read and no writers // will be able to write. // CWriteAccess writeLock( _rwAccess ); CLockAllRecordsForWrite lockAll(*this); _PropStoreInfo.Commit( _ppsNew->_PropStoreInfo, xact ); // // Transfer the property store information from the new to current // delete _xPhysStore.Acquire(); _xPhysStore.Set( _ppsNew->_xPhysStore.Acquire() ); delete _aFreeBlocks; _aFreeBlocks = _ppsNew->_aFreeBlocks; _ppsNew->_aFreeBlocks = 0; _PropStoreInfo.MarkClean(); } else ciDebugOut(( DEB_ITRACE, "No changes to property metadata. Rolling back transaction.\n" )); delete _ppsNew; _ppsNew = 0; _fNew = FALSE; } CATCH( CException, e ) { ciDebugOut(( DEB_ERROR, "Error 0x%X while commiting transaction.\n", e.GetErrorCode() )); delete _ppsNew; _ppsNew = 0; _fNew = FALSE; // // Delete the newly created property store from disk // if ( widInvalid != widNew ) _pStorage->DeleteObject( widNew ); RETHROW(); } END_CATCH if ( widOld != widInvalid ) _pStorage->DeleteObject( widOld ); } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::MakeBackupCopy // // Synopsis: Makes a backup copy of the property storage. It makes a // full copy if the pIEnumWorkids is NULL. Otherwise, it makes // a copy of only the changed workids. // // Arguments: [pIProgressEnum] - Progress indication // [pfAbort] - Caller initiated abort flag // [dstStorage] - Destination storage to use // [pIEnumWorkids] - List of workids to copy. If null, all the // workids are copied. // [pidFixed] - Which is the fixed pid ? // [ppFileList] - List of propstore files copied. // // History: 3-26-97 srikants Created // // Notes: Incremental not implemented yet // //---------------------------------------------------------------------------- void CPropertyStore::MakeBackupCopy( IProgressNotify * pIProgressEnum, BOOL & fAbort, CiStorage & dstStorage, ICiEnumWorkids * pIEnumWorkids, IEnumString **ppFileList ) { #if CIDBG == 1 if (pIEnumWorkids) { Win4Assert(!"For secondary level store, are you translating wids? Look in CPropStoreManager::MakeBackupCopy."); } #endif // CIDBG // // Create a backup copy of the property store. // // // For a FULL backup, it is possible to just make a copy of the streams // but if there are any problems with the data in this property store, they // will get carried over. Also, doing a "Transfer" may defrag the target // property store. // // // Delete any existing PropertyStore meta data info. // dstStorage.RemovePropStore(0, GetStoreLevel()); TRY { // // Make a backup copy of the PropStoreInfo. // XPtr xObj( dstStorage.QueryPropStore( 0, GetStoreLevel() ) ); CCopyRcovObject copyRcov( xObj.GetReference(), *_PropStoreInfo.GetRcovObj() ); copyRcov.DoIt(); XPtr xPropStore( new CPropertyStore( *this, &dstStorage ) ); xPropStore->_PropStoreInfo.InitWorkId( dstStorage ); xPropStore->CreateStorage( _PropStoreInfo.WorkId() ); // use same workid xPropStore->InitFreeList(); xPropStore->_PropStoreInfo.Accept( xObj ); FastTransfer( xPropStore.GetReference(), fAbort, pIProgressEnum ); xPropStore->_PropStoreInfo.FastTransfer( _PropStoreInfo ); // // return a list of file names only on demand // _propStoreMgr.Flush(); if (0 != ppFileList) { Win4Assert( 0 != _pStorage ); CEnumString * pEnumString = new CEnumString(); XInterface xEnumStr(pEnumString); dstStorage.ListPropStoreFileNames( *pEnumString, _PropStoreInfo.WorkId(), GetStoreLevel() ); *ppFileList = xEnumStr.Acquire(); } } CATCH( CException, e ) { dstStorage.RemovePropStore(0, GetStoreLevel()); dstStorage.DeleteObject( _PropStoreInfo.WorkId() ); RETHROW(); } END_CATCH } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::WriteProperty, public // // Synopsis: Write a property to the cache. // // Arguments: [PropRecord] -- Previously opened property record. // [pid] -- Propid // [var] -- Value // [fBackup] -- Backup? // // Returns: S_OK if everything went well. // S_FALSE if specified pid is not in store. // Error code if an error occurred. // // History: 27-Dec-95 KyleP Created. // 30-Dec-97 KrishnaN Improved error handling/reporting. // 27-Jan-2000 KLam Extended assert to handle out of // memory condition. // //---------------------------------------------------------------------------- SCODE CPropertyStore::WriteProperty( CPropRecordForWrites &PropRecord, PROPID pid, CStorageVariant const & var, BOOL fBackup) { Win4Assert( sizeof(CPropRecordForWrites) <= sizeof_CPropRecord ); WORKID wid = PropRecord._wid; ciDebugOut(( DEB_PROPSTORE, "WRITE: wid = 0x%x, pid = 0x%x, type = %d\n", wid, pid, var.Type() )); CPropDesc const * pdesc = _PropStoreInfo.GetDescription( pid ); if ( 0 != pdesc ) { COnDiskPropertyRecord * prec = PropRecord._prec; // If CPropRecord was passed in widInvalid, prec would be 0. So Write should fail! if (0 == prec) return E_INVALIDARG; if ( !prec->IsTopLevel() ) { ciDebugOut(( DEB_IWARN, "Trying to write to non-toplevel wid 0x%X\n", wid )); return E_INVALIDARG; } SCODE sc = S_OK; TRY { if (fBackup) { CBackupWid backupTopLevel(this, wid, prec->CountRecords()); _PropStoreInfo.MarkDirty(); } _PropStoreInfo.MarkDirty(); if ( pdesc->IsFixedSize() ) { #if CIDBG == 1 if ( ( pidSize == pid ) && ( VT_I8 == var.vt ) ) Win4Assert( 0xdddddddddddddddd != var.hVal.QuadPart ); if ( ( pidAttrib == pid ) && ( VT_UI4 == var.vt ) ) Win4Assert( 0xdddddddd != var.ulVal ); #endif // CIDBG == 1 prec->WriteFixed( pdesc->Ordinal(), pdesc->Mask(), pdesc->Offset(), pdesc->Type(), _PropStoreInfo.CountProps(), var ); } else { Win4Assert(!prec->IsLeanRecord()); Win4Assert( 0 != _pStorage ); BOOL fOk = prec->WriteVariable( pdesc->Ordinal(), pdesc->Mask(), _PropStoreInfo.FixedRecordSize(), _PropStoreInfo.CountProps(), _PropStoreInfo.CountFixedProps(), _PropStoreInfo.RecordSize(), var, *_pStorage ); // // Did we fit? // CBorrowed BorrowedOverflow( _xPhysStore.GetReference(), _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); while ( !fOk ) { // // Check for existing overflow block. // WORKID widOverflow = prec->OverflowBlock(); BOOL fNewBlock = FALSE; ULONG cWid = 1; // // Need new overflow block. // if ( 0 == widOverflow ) { Win4Assert(!prec->IsLeanRecord()); fNewBlock = TRUE; cWid = COnDiskPropertyRecord::CountNormalRecordsToStore( _PropStoreInfo.CountProps() - _PropStoreInfo.CountFixedProps(), _PropStoreInfo.RecordSize(), var ); // We cannot have a single record greater than COMMON_PAGE_SIZE. // Throw if we get into that situation. Fix for bug 119508. if (cWid > RecordsPerPage()) THROW(CException(CI_E_PROPERTY_TOOLARGE)); widOverflow = LokNewWorkId( cWid, FALSE, fBackup ); prec->SetOverflowBlock( widOverflow ); PropRecord._prec->IncrementOverflowChainLength(); } BorrowedOverflow.Release(); BorrowedOverflow.Set( widOverflow ); prec = BorrowedOverflow.Get(); if ( fNewBlock ) { # if CIDBG == 1 if ( prec->HasProperties( _PropStoreInfo.CountProps() ) ) { ciDebugOut(( DEB_ERROR, "New long record at %d, size = %d, p = 0x%x has stored properties!\n", widOverflow, cWid, prec )); } if ( !prec->IsNormalEmpty( _PropStoreInfo.RecordSize() ) ) { ciDebugOut(( DEB_ERROR, "New long record at %d, size = %d, p = 0x%x is not empty!\n", widOverflow, cWid, prec )); } // Win4Assert( !prec->HasProperties(_PropStoreInfo.CountProps()) && // prec->IsEmpty( _PropStoreInfo.RecordSize() ) ); # endif // CIDBG == 1 ciDebugOut(( DEB_PROPSTORE, "New long record at %d, size = %d\n", widOverflow, cWid )); prec->MakeLongRecord( cWid ); prec->SetOverflowBlock( 0 ); prec->SetToplevelBlock( wid ); } else { // // Every record in the chain gets backed up because of // this. That is the way it should be because we are // writing to every record in the chain as part of // overflow handling. If there is not sufficient space // in the overflow records, we will be creating a new // record and chaining it to the last one in the // existing chain. The backed up records will have no // evidence of the link made to the new record and that // is the way it should be. // if (fBackup) { CBackupWid backupOverflow(this, widOverflow, prec->CountRecords()); _PropStoreInfo.MarkDirty(); } // // NTRAID#DB-NTBUG9-84451-2000/07/31-dlee Indexing Service Property Store doesn't handle values in records that grow out of the record // Consider the case where a property was *in* a long record. // and then no longer fit in that long record... // Win4Assert( prec->ToplevelBlock() == wid ); } ULONG Ordinal = pdesc->Ordinal() - _PropStoreInfo.CountFixedProps(); DWORD Mask = (1 << ((Ordinal % 16) * 2)); Win4Assert( 0 != _pStorage ); fOk = prec->WriteVariable( Ordinal, // Ordinal (assuming 0 fixed) Mask, // Mask (assuming 0 fixed) 0, // Fixed properties _PropStoreInfo.CountProps() - _PropStoreInfo.CountFixedProps(), 0, // Count of fixed properties _PropStoreInfo.RecordSize(), var, *_pStorage ); Win4Assert( fOk || !fNewBlock ); // Property *must* fit in a fresh block! } } #if CIDBG == 1 // Assert that we have a dirty property store and that something // was written to the backup file (if fBackup is enabled) Win4Assert(_PropStoreInfo.IsDirty()); if (fBackup) { Win4Assert(BackupStream()->Pages() > 0); } #endif // CIDBG } CATCH( CException, e ) { sc = e.GetErrorCode(); ciDebugOut(( DEB_ERROR, "Exception 0x%x caught writing pid %d in wid %d. prec = 0x%x\n", sc, pid, wid, prec )); } END_CATCH return sc; } return S_FALSE; } //WriteProperty //+--------------------------------------------------------------------------- // // Member: CPropertyStore::WriteProperty, public // // Synopsis: Write a property to the cache. // // Arguments: [wid] -- Workid // [pid] -- Propid // [var] -- Value // [fBackup] -- Backup? // // Returns: S_OK if everything went well. // S_FALSE if specified pid is not in store. // Error code if an error occurred. // // History: 27-Dec-95 KyleP Created. // 30-Dec-97 KrishnaN Improved error handling/reporting. // //---------------------------------------------------------------------------- SCODE CPropertyStore::WriteProperty( WORKID wid, PROPID pid, CStorageVariant const & var, BOOL fBackup ) { CPropRecordForWrites PropRecord( wid, *this ); return WriteProperty( PropRecord, pid, var, fBackup ); } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::ReadProperty, public // // Synopsis: Read a property from the cache. Version which uses property // record. // // Arguments: [PropRec] -- Pre-opened property record // [pid] -- Propid // [pbData] -- Place to return the value // [pcb] -- On input, the maximum number of bytes to // write at pbData. On output, the number of // bytes written if the call was successful, // else the number of bytes required. // // History: 03-Apr-96 KyleP Created. // //---------------------------------------------------------------------------- BOOL CPropertyStore::ReadProperty( CPropRecordNoLock & PropRec, PROPID pid, PROPVARIANT * pbData, unsigned * pcb ) { *pcb -= sizeof(PROPVARIANT); BOOL fOk = ReadProperty( PropRec, pid, *pbData, (BYTE *)(pbData + 1), pcb ); *pcb += sizeof(PROPVARIANT); return fOk; } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::ReadProperty, public // // Synopsis: Read a property from the cache. Triggers CoTaskMemAlloc // // Arguments: [wid] -- Workid // [pid] -- Propid // [var] -- Place to return the value // // History: 27-Dec-95 KyleP Created. // //---------------------------------------------------------------------------- BOOL CPropertyStore::ReadProperty( WORKID wid, PROPID pid, PROPVARIANT & var ) { unsigned cb = 0xFFFFFFFF; return ReadProperty( wid, pid, var, 0, &cb ); } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::ReadProperty, public // // Synopsis: Read a property from the cache. Separate variable buffer. // // Arguments: [wid] -- Workid // [pid] -- Propid // [var] -- Variant written here // [pbExtra] -- Place to store additional pointer(s). // [pcbExtra] -- On input, the maximum number of bytes to // write at pbExtra. On output, the number of // bytes written if the call was successful, // else the number of bytes required. // // History: 27-Dec-95 KyleP Created. // //---------------------------------------------------------------------------- BOOL CPropertyStore::ReadProperty( WORKID wid, PROPID pid, PROPVARIANT & var, BYTE * pbExtra, unsigned * pcbExtra ) { CPropRecord PropRecord( wid, *this ); return ReadProperty( PropRecord, pid, var, pbExtra, pcbExtra ); } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::ReadProperty, public // // Synopsis: Read a property from the cache. Separate variable buffer. // Uses pre-opened property record. // // Arguments: [PropRec] -- Pre-opened property record. // [pid] -- Propid // [var] -- Variant written here // [pbExtra] -- Place to store additional pointer(s). // [pcbExtra] -- On input, the maximum number of bytes to // write at pbExtra. On output, the number of // bytes written if the call was successful, // else the number of bytes required. // // History: 03-Apr-96 KyleP Created. // //---------------------------------------------------------------------------- BOOL CPropertyStore::ReadProperty( CPropRecordNoLock & PropRecord, PROPID pid, PROPVARIANT & var, BYTE * pbExtra, unsigned * pcbExtra ) { ciDebugOut(( DEB_PROPSTORE, "READ: PropRec = 0x%x, pid = 0x%x\n", &PropRecord, pid )); if (!PropRecord.IsValid()) return FALSE; COnDiskPropertyRecord * prec = PropRecord._prec; return ReadProperty(prec, pid, var, pbExtra, pcbExtra); } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::ReadProperty, public // // Synopsis: Read a property from the cache. Separate variable buffer. // Uses pre-opened property record. // // Arguments: [prec] -- Ptr to preopened property record. // [pid] -- Propid // [var] -- Variant written here // [pbExtra] -- Place to store additional pointer(s). // [pcbExtra] -- On input, the maximum number of bytes to // write at pbExtra. On output, the number of // bytes written if the call was successful, // else the number of bytes required. // // History: 17-Mar-1998 KrishnaN Created. // 15-Mar-2000 KLam Add STATUS_INSUFFICIENT_RESOURCES to assert // //---------------------------------------------------------------------------- BOOL CPropertyStore::ReadProperty( COnDiskPropertyRecord *prec, PROPID pid, PROPVARIANT & var, BYTE * pbExtra, unsigned * pcbExtra ) { CPropDesc const * pdesc = _PropStoreInfo.GetDescription( pid ); // // Is the property cached? // if ( 0 == pdesc ) return FALSE; // If CPropRecord was passed in widInvalid, prec would be 0. So Read should fail! if (0 == prec) return FALSE; if ( !prec->IsInUse() ) { ciDebugOut(( DEB_IWARN, "Trying to read from a deleted wid in prec = 0x%X\n", prec )); return FALSE; } if ( !prec->IsTopLevel() ) { ciDebugOut(( DEB_IWARN, "Trying to start read from a non-toplevel prec = 0x%X\n", prec )); return FALSE; } TRY { if ( pdesc->IsFixedSize() ) { Win4Assert( 0 != _pStorage ); prec->ReadFixed( pdesc->Ordinal(), pdesc->Mask(), pdesc->Offset(), _PropStoreInfo.CountProps(), pdesc->Type(), var, pbExtra, pcbExtra, *_pStorage ); } else { BOOL fOk = prec->ReadVariable( pdesc->Ordinal(), pdesc->Mask(), _PropStoreInfo.FixedRecordSize(), _PropStoreInfo.CountProps(), _PropStoreInfo.CountFixedProps(), var, pbExtra, pcbExtra ); if (! fOk ) { CBorrowed BorrowedOverflow( _xPhysStore.GetReference(), _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); do { // // Check for existing overflow block. // WORKID widOverflow = prec->OverflowBlock(); // // Need new overflow block. // if ( 0 == widOverflow ) return FALSE; Win4Assert( _xPhysStore->PageSize() * CI_PAGE_SIZE >= COnDiskPropertyRecord::MinStreamSize( widOverflow, _PropStoreInfo.RecordSize() ) ); BorrowedOverflow.Release(); BorrowedOverflow.Set( widOverflow, FALSE ); prec = BorrowedOverflow.Get(); ULONG Ordinal = pdesc->Ordinal() - _PropStoreInfo.CountFixedProps(); DWORD Mask = (1 << ((Ordinal % 16) * 2) ); fOk = prec->ReadVariable( Ordinal, // Ordinal (assuming 0 fixed) Mask, // Mask (assuming 0 fixed) 0, // Fixed properties _PropStoreInfo.CountProps() - _PropStoreInfo.CountFixedProps(), 0, // Count of fixed properties var, pbExtra, pcbExtra ); } while ( !fOk ); } } } CATCH( CException, e ) { ciDebugOut(( DEB_ERROR, "Exception 0x%x caught reading pid %d in prec = 0x%x\n", e.GetErrorCode(), pid, prec )); // assert if the error is other than out of memory Win4Assert( ( e.GetErrorCode() == E_OUTOFMEMORY || e.GetErrorCode() == HRESULT_FROM_WIN32(STATUS_SHARING_VIOLATION) || e.GetErrorCode() == HRESULT_FROM_WIN32(STATUS_INSUFFICIENT_RESOURCES) ) && "Exception reading property" ); RETHROW(); } END_CATCH return TRUE; } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::DeleteRecord, public // // Synopsis: Free a record and any records chained off it. // // Arguments: [wid] -- Workid // // History: 27-Dec-95 KyleP Created. // //---------------------------------------------------------------------------- void CPropertyStore::DeleteRecord( WORKID wid, BOOL fBackup ) { Win4Assert(wid != widInvalid && wid != 0); ciDebugOut(( DEB_PROPSTORE, "DELETE: wid = 0x%x\n", wid )); ULONG cbStream = COnDiskPropertyRecord::MinStreamSize( wid, _PropStoreInfo.RecordSize() ); if ( _xPhysStore->PageSize() * CI_PAGE_SIZE < cbStream ) return; CBorrowed BorrowedTopLevel( _xPhysStore.GetReference(), wid, _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); CLockRecordForWrite wlock( *this, wid ); WORKID widStart = wid; COnDiskPropertyRecord * pRecTopLevel = BorrowedTopLevel.Get(); // Return if we have nothing to delete. if (0 == pRecTopLevel) return; BOOL fIsConsistent = TRUE; if ( !pRecTopLevel->IsTopLevel() ) { ciDebugOut(( DEB_ERROR, "Delete wid (0x%X) prec (0x%X) is not top level\n", wid, pRecTopLevel )); Win4Assert( !"Corruption detected in PropertyStore" ); fIsConsistent = FALSE; } ULONG cRemainingBlocks = 1; // won't change for a lean record if (eNormal == GetRecordFormat()) cRemainingBlocks = pRecTopLevel->GetOverflowChainLength()+1; while ( wid != 0 && wid <= _PropStoreInfo.MaxWorkId() ) { if ( 0 == cRemainingBlocks ) { // // We are either in some kind of corruption or loop. In either, // case, we have freed up as many as are probably safe. Just // get out of here. // ciDebugOut(( DEB_ERROR, "Delete wid (0x%X) overflow chain is corrupt\n", wid )); Win4Assert( !"Corruption detected in PropertyStore" ); fIsConsistent = FALSE; break; } CBorrowed Borrowed( _xPhysStore.GetReference(), wid, _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); COnDiskPropertyRecord * prec = Borrowed.Get(); WORKID widNext = 0; // causes loop termination for lean records if (eNormal == GetRecordFormat()) { widNext = prec->OverflowBlock(); if ( (wid != widStart) && !prec->IsOverflow() ) { ciDebugOut(( DEB_ERROR, "Wid (0x%x) - prec (0x%x). Not in use record to be deleted\n", wid, prec )); Win4Assert( !"Corruption detected in PropertyStore" ); fIsConsistent = FALSE; break; } } LokFreeRecord( wid, prec->CountRecords(), prec, fBackup ); wid = widNext; cRemainingBlocks--; } _PropStoreInfo.DecRecordsInUse(); if ( !fIsConsistent ) { _fIsConsistent = FALSE; THROW( CException( CI_PROPSTORE_INCONSISTENCY ) ); } #if CIDBG == 1 // Assert that we have a dirty property store and that something was written to // the backup file (if fBackup is enabled) Win4Assert(_PropStoreInfo.IsDirty()); if (fBackup) { Win4Assert(BackupStream()->Pages() > 0); } #endif // CIDBG } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::InitFreeList // // Synopsis: Initialize the free list block pointer array. // // Returns: Nothing // // History: 07 May 96 AlanW Created // // Notes: The free list block pointer array speeds allocation and // deallocation of records by storing the starting record number // of free records of a particular size. The free list is // sorted by the size of the record. // //---------------------------------------------------------------------------- void CPropertyStore::InitFreeList( ) { if ( 0 != _aFreeBlocks ) { delete _aFreeBlocks; _aFreeBlocks = 0; } _aFreeBlocks = new WORKID[ _PropStoreInfo.RecordsPerPage() + 1 ]; RtlZeroMemory( _aFreeBlocks, (_PropStoreInfo.RecordsPerPage()+1) * sizeof (WORKID) ); WORKID wid = _PropStoreInfo.FreeListHead(); ciDebugOut(( DEB_PROPSTORE, "Scanning free list starting with wid %u\n", wid )); if (0 != wid) { CBorrowed Borrowed( _xPhysStore.GetReference(), wid, _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); COnDiskPropertyRecord * prec = Borrowed.Get(); if ( prec->GetNextFreeRecord() != 0) { CBorrowed BorrowedNext( _xPhysStore.GetReference(), prec->GetNextFreeRecord(), _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); COnDiskPropertyRecord * precNext = BorrowedNext.Get(); if ( precNext->GetPreviousFreeRecord() != wid || precNext->CountRecords() != prec->GetNextFreeSize() ) { // Old-style free list Win4Assert( FALSE ); return; } } if ( prec->GetPreviousFreeRecord() != 0 ) { // // Minor inconsistency in the first free record. Fix it. // ciDebugOut(( DEB_WARN, "PROPSTORE: Repair free list previous pointer(0x%X)\n", wid )); prec->SetPreviousFreeRecord( 0 ); } if ( prec->GetNextFreeRecord() == 0 && _PropStoreInfo.FreeListTail( ) != wid ) { // // Minor inconsistency in a single free record. Fix it. // ciDebugOut(( DEB_WARN, "PROPSTORE: Repair free list tail pointer(0x%X)\n", wid )); prec->SetNextFree( 0, 0 ); _PropStoreInfo.SetFreeListTail( wid ); } } else if ( _PropStoreInfo.FreeListTail() != 0 ) { // // Free list tail not set correctly for an empty list. Fix it. // _PropStoreInfo.SetFreeListTail( 0 ); } CBorrowed Borrowed( _xPhysStore.GetReference(), _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); #if CIDBG == 1 WORKID widPrev = 0; ULONG cRecPrev = 0; #endif // CIDBG == 1 while ( 0 != wid ) { Borrowed.Set(wid); COnDiskPropertyRecord * prec = Borrowed.Get(); Win4Assert( prec->IsFreeRecord() && prec->GetPreviousFreeRecord() == widPrev ); Win4Assert( cRecPrev == 0 || prec->CountRecords() == cRecPrev ); if (_aFreeBlocks[prec->CountRecords()] == 0) { _aFreeBlocks[prec->CountRecords()] = wid; ciDebugOut(( DEB_PROPSTORE, " _aFreeBlocks[%03d] = 0x%X\n", prec->CountRecords(), wid )); } if (prec->CountRecords() == 1) break; #if CIDBG == 1 widPrev = wid; cRecPrev = prec->GetNextFreeSize(); #endif // CIDBG == 1 wid = prec->GetNextFreeRecord(); Borrowed.Release(); } } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::LokFreeRecord, private // // Synopsis: Add a free record to the free list. // // Arguments: [widFree] -- Workid of record to free // [cFree] -- Number of records in freed chunk // [precFree] -- On-disk Property record // [fBackup] -- Backup record? // // Notes: The free list is maintained in decreasing order of free // block size. The _aFreeBlocks array is updated. // // History: 01 May 96 AlanW Created. // //---------------------------------------------------------------------------- void CPropertyStore::LokFreeRecord( WORKID widFree, ULONG cFree, COnDiskPropertyRecord * precFree, BOOL fBackup ) { CImpersonateSystem impersonate; if (fBackup) { CBackupWid backup(this, widFree, cFree); _PropStoreInfo.MarkDirty(); } WORKID widListHead = _PropStoreInfo.FreeListHead(); ciDebugOut(( DEB_PROPSTORE, " free wid 0x%X, size = %d\n", widFree, cFree )); _PropStoreInfo.MarkDirty(); for (unsigned i = cFree; i <= _PropStoreInfo.RecordsPerPage(); i++) if (_aFreeBlocks[i] != 0) break; if ( 0 == widListHead || i > _PropStoreInfo.RecordsPerPage() || (i == cFree && widListHead == _aFreeBlocks[i]) ) { // // The block will go at the head of the list // WORKID widNext = widListHead; ULONG cFreeNext = 0; if ( 0 != widNext ) { CBorrowed BorrowedNext( _xPhysStore.GetReference(), widNext, _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); COnDiskPropertyRecord * precNext = BorrowedNext.Get(); if (fBackup) { CBackupWid backup(this, widNext, 1); _PropStoreInfo.MarkDirty(); } precNext->SetPreviousFreeRecord( widFree ); cFreeNext = precNext->CountRecords(); } else { _PropStoreInfo.SetFreeListTail( widFree ); } if (precFree->IsLeanRecord()) precFree->MakeLeanFreeRecord( cFree, widNext, cFreeNext, _PropStoreInfo.RecordSize() ); else precFree->MakeNormalFreeRecord( cFree, widNext, cFreeNext, _PropStoreInfo.RecordSize() ); precFree->SetPreviousFreeRecord( 0 ); Win4Assert( _aFreeBlocks[ cFree ] == 0 || i == cFree ); _aFreeBlocks[cFree] = widFree; _PropStoreInfo.SetFreeListHead( widFree ); return; } if ( i != cFree ) { // // A block of this size doesn't exist; find the next smaller // size and insert before it. // for ( i = cFree; i > 0; i-- ) if (_aFreeBlocks[i] != 0) break; } if ( i > 0 ) { // // Insert the block into the list // WORKID widNext = _aFreeBlocks[i]; ULONG cFreeNext = i; CBorrowed BorrowedNext( _xPhysStore.GetReference(), widNext, _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); COnDiskPropertyRecord * precNext = BorrowedNext.Get(); WORKID widPrev = precNext->GetPreviousFreeRecord(); precNext->SetPreviousFreeRecord( widFree ); Win4Assert( cFreeNext == precNext->CountRecords() ); if (precFree->IsLeanRecord()) precFree->MakeLeanFreeRecord( cFree, widNext, cFreeNext, _PropStoreInfo.RecordSize() ); else precFree->MakeNormalFreeRecord( cFree, widNext, cFreeNext, _PropStoreInfo.RecordSize() ); precFree->SetPreviousFreeRecord( widPrev ); // Insertion at list head is handled above... Win4Assert( widPrev != 0 ); if (widPrev != 0) { CBorrowed BorrowedPrev( _xPhysStore.GetReference(), widPrev, _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); COnDiskPropertyRecord * precPrev = BorrowedPrev.Get(); precPrev->SetNextFree( widFree, cFree ); } _aFreeBlocks[cFree] = widFree; return; } // // No blocks of this size or smaller found. Append to list // WORKID widPrev = _PropStoreInfo.FreeListTail(); Win4Assert( widPrev != 0 ); CBorrowed BorrowedPrev( _xPhysStore.GetReference(), widPrev, _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); COnDiskPropertyRecord * precPrev = BorrowedPrev.Get(); precPrev->SetNextFree( widFree, cFree ); if (precFree->IsLeanRecord()) precFree->MakeLeanFreeRecord( cFree, 0, 0, _PropStoreInfo.RecordSize() ); else precFree->MakeNormalFreeRecord( cFree, 0, 0, _PropStoreInfo.RecordSize() ); precFree->SetPreviousFreeRecord( widPrev ); _PropStoreInfo.SetFreeListTail( widFree ); Win4Assert( _aFreeBlocks[cFree] == 0 ); _aFreeBlocks[cFree] = widFree; return; } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::LokAllocRecord, private // // Synopsis: Allocate a record from the free list // // Arguments: [cFree] -- Number of contiguous records required // // Returns: WORKID - the work ID of the record allocated. 0 if none of // sufficient size could be found. // // History: 09 May 96 AlanW Created. // //---------------------------------------------------------------------------- WORKID CPropertyStore::LokAllocRecord( ULONG cFree ) { CImpersonateSystem impersonate; _PropStoreInfo.MarkDirty(); for (unsigned i = cFree; i <= _PropStoreInfo.RecordsPerPage(); i++) if (_aFreeBlocks[i] != 0) break; if ( i > _PropStoreInfo.RecordsPerPage() ) return 0; WORKID widFree = _aFreeBlocks[i]; CBorrowed Borrowed( _xPhysStore.GetReference(), widFree, _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); COnDiskPropertyRecord * prec = Borrowed.Get(); WORKID widNext = prec->GetNextFreeRecord(); WORKID widPrev = prec->GetPreviousFreeRecord(); if ( prec->CountRecords() != prec->GetNextFreeSize() ) _aFreeBlocks[i] = 0; else _aFreeBlocks[i] = prec->GetNextFreeRecord(); if ( widNext != 0 ) { CBorrowed BorrowedNext( _xPhysStore.GetReference(), widNext, _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); COnDiskPropertyRecord * precNext = BorrowedNext.Get(); Win4Assert( precNext->IsFreeRecord() ); Win4Assert( prec->GetNextFreeSize() == precNext->CountRecords() ); precNext->SetPreviousFreeRecord( widPrev ); } else { Win4Assert( _PropStoreInfo.FreeListTail() == widFree ); _PropStoreInfo.SetFreeListTail( widPrev ); } if ( widPrev != 0 ) { CBorrowed BorrowedPrev( _xPhysStore.GetReference(), widPrev, _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); COnDiskPropertyRecord * precPrev = BorrowedPrev.Get(); Win4Assert( precPrev->IsFreeRecord() ); precPrev->SetNextFree( widNext, prec->GetNextFreeSize() ); } else { Win4Assert( _PropStoreInfo.FreeListHead() == widFree ); _PropStoreInfo.SetFreeListHead( widNext ); } ciDebugOut(( DEB_PROPSTORE, " alloc wid 0x%X, size = %d\n", widFree, cFree )); return widFree; } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::CPropertyStore, private // // Synopsis: Copy constructor // // Arguments: [rhs] -- Source metadata // // History: 03-Jan-96 KyleP Created. // 26-Mar-96 SrikantS Modifed for better recovery. // //---------------------------------------------------------------------------- CPropertyStore::CPropertyStore( CPropertyStore & rhs, CiStorage * pStorage ) : _pStorage(0), _PropStoreInfo( rhs._PropStoreInfo ), _aFreeBlocks(0), _fAbort(FALSE), _ppsNew( 0 ), _fNew( FALSE ), _ulBackupSizeInPages( rhs._ulBackupSizeInPages ), _ulPSMappedCache( rhs._ulPSMappedCache ), _propStoreMgr( rhs._propStoreMgr ) { #if CIDBG == 1 _sigPSDebug = 0x2047554245445350i64; // PSDEBUG _tidReadSet = _tidReadReset = _tidWriteSet = _tidWriteReset = 0xFFFFFFFF; _xPerThreadReadCounts.Init( cTrackThreads ); _xPerThreadWriteCounts.Init( cTrackThreads ); RtlZeroMemory( _xPerThreadReadCounts.GetPointer(), cTrackThreads * sizeof(_xPerThreadReadCounts[0]) ); RtlZeroMemory( _xPerThreadWriteCounts.GetPointer(), cTrackThreads * sizeof(_xPerThreadWriteCounts[0]) ); #endif _pStorage = pStorage; #if CIDBG == 1 // Allocate an array to track what records are currently locked. // To be used to acquire all write locks. _pbRecordLockTracker = new BYTE[LockMgr().UniqueRecordCount()]; RtlZeroMemory(_pbRecordLockTracker, sizeof(BYTE)*LockMgr().UniqueRecordCount()); #endif // CIDBG } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::CreateStorage, private // // Synopsis: Creates property store storage. // // Arguments: [rhs] -- Source metadata // // Returns: WorkId of new storage // // History: 03-Jun-96 KyleP Created. // //---------------------------------------------------------------------------- WORKID CPropertyStore::CreateStorage( WORKID widGiven ) { WORKID wid = widInvalid == widGiven ? _PropStoreInfo.NextWorkId(*_pStorage) : widGiven; SStorageObject xobj( _pStorage->QueryObject( wid ) ); XPtr xmmstrm ( _pStorage->QueryNewPropStream( xobj.GetObj(), _PropStoreInfo.GetStoreLevel() )); _xPhysStore.Set( new CPhysPropertyStore( *_pStorage, xobj.GetObj(), _PropStoreInfo.WorkId(), xmmstrm.Acquire(), PStorage::eOpenForWrite, _ulPSMappedCache ) ); // grow the file 2 meg at a time _xPhysStore->SetPageGrowth( 32 * COMMON_PAGE_SIZE / CI_PAGE_SIZE ); // create a prop store backup stream _xPSBkpStrm.Set(_pStorage->QueryNewPSBkpStream( xobj.GetObj(), _ulBackupSizeInPages, GetStoreLevel() )); return wid; } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::WritePropertyInSpecificNewRecord, private // // Synopsis: Write a property to the cache. Allocate specified wid // for property. // // Arguments: [wid] -- Workid. Must be > MaxWorkId. // [pid] -- Propid // [var] -- Value // [fBackup] -- Backup? // // History: 27-Dec-95 KyleP Created. // //---------------------------------------------------------------------------- void CPropertyStore::WritePropertyInSpecificNewRecord( WORKID wid, PROPID pid, CStorageVariant const & var, BOOL fBackup) { // // Note: We don't need to lock here, because this method is used before // a property store comes on-line. // // // Since wid must be larger than current max, then we need to adjust the // maximum and store the records in-between on the free list. // Win4Assert( wid > _PropStoreInfo.MaxWorkId() ); WORKID widFree = _PropStoreInfo.MaxWorkId() + 1; while ( widFree < wid ) { WORKID widInRec = widFree % _PropStoreInfo.RecordsPerPage(); // // Build as long a record as possible for the in-between free records. // Don't span large page boundaries. // ULONG cWid = wid - widFree; if ( widInRec + cWid - 1 >= _PropStoreInfo.RecordsPerPage() ) cWid = _PropStoreInfo.RecordsPerPage() - widInRec; CBorrowed Borrowed( _xPhysStore.GetReference(), _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); Borrowed.SetMaybeNew( widFree ); COnDiskPropertyRecord * prec = Borrowed.Get(); ciDebugOut(( DEB_PROPSTORE, "Putting records 0x%x - 0x%x on free list.\n", widFree, widFree + cWid - 1 )); // Backup the records before freeing them. Back them up as one long // record because that is how they are being added to the free list. // For recovery purposes it doesn't matter if they are backed up // one by one or all together, but we prefer the latter for efficiency. // The free block is at most one large page. Assert that the backup is large // enough to hold the max size of the free block. Win4Assert(COMMON_PAGE_SIZE <= _PropStoreInfo.OSPageSize()*_xPSBkpStrm->MaxPages()); LokFreeRecord( widFree, cWid, prec, fBackup ); widFree += cWid; } // // Are we on a fresh large page? // CBorrowed Borrowed( _xPhysStore.GetReference(), _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); Borrowed.SetMaybeNew( wid ); COnDiskPropertyRecord * prec = Borrowed.Get(); // Tracking assert for bug 125604. Ensure that what we are overwriting // is indeed a free or a virgin record. Win4Assert(prec->IsFreeOrVirginRecord()); // IMPORTANT: This is a new block, so it is going to overwrite an // existing free record. Write the toplevel wid in the TopLevel field // of the record to be written over when the page is written to backup. // Note that if we are backing up a "lean record", we don't need to remember // the top-level of the displacing wid in the displaced wid. Because all // "in use" lean records will always be only "one-record" long, we know that // the displaced record was displaced by the same wid. if (eLean == GetRecordFormat()) { Win4Assert(1 == prec->CountRecords()); // Save the "to be displaced" record before actually displacing it. if (fBackup) { CBackupWid backupNewWid(this, wid, prec->CountRecords()); _PropStoreInfo.MarkDirty(); } prec->MakeNewLeanTopLevel(); } else { Win4Assert(eNormal == GetRecordFormat()); // Save the "to be displaced" record before actually displacing it. if (fBackup) { CBackupWid backupNewWid(this, wid, prec->CountRecords(), eTopLevelField, (ULONG)wid, prec); _PropStoreInfo.MarkDirty(); } prec->MakeNewNormalTopLevel(); } _PropStoreInfo.SetMaxWorkId( wid ); SCODE scWrite = WriteProperty( wid, pid, var, fBackup ); if (FAILED(scWrite)) THROW(CException(scWrite)); _PropStoreInfo.IncRecordsInUse(); #if CIDBG == 1 // Assert that we have a dirty property store and that something was written to // the backup file (if fBackup is enabled) Win4Assert(_PropStoreInfo.IsDirty()); if (fBackup) { Win4Assert(BackupStream()->Pages() > 0); } #endif // CIDBG } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::InitNewRecord, private inline // // Synopsis: Initializes a new property record // // Arguments: [wid] -- WORKID of the new wid // [cWid] -- Count of contiguous workids (records) needed. // [prec] -- pointer to the on-disk record // [fTopLevel] -- true if the new wid is a top-level wid // // History: 27-Feb-96 dlee Created from code in LokNewWorkId // 13-Jun-97 KrishnaN Backup support. // //---------------------------------------------------------------------------- inline void CPropertyStore::InitNewRecord( WORKID wid, ULONG cWid, COnDiskPropertyRecord * prec, BOOL fTopLevel, BOOL fBackup) { // Tracking assert for bug 125604. Ensure that what we are overwriting // is indeed a free or a virgin record. Win4Assert(prec->IsFreeOrVirginRecord()); // This is the only exception to the rule "backup before touching". // As a result of this exception, the backup file contains a free or virgin // record that is cWid long. During restore from backup, this length field // helps to identify the entire block as a free block, so processing can be // a little more efficient. Otherwise, we will have to work with cWid individual // free or virgin records. prec->MakeLongRecord(cWid); // Backup the record before touching it. if ( fTopLevel ) { // Record the top-level wid of the occupying record // in the backup as part of the occupied free record. // Note that if we are backing up a "lean record", we don't need to remember // the top-level of the displacing wid in the displaced wid. Because all // "in use" lean records will always be only "one-record" long, we know that // the displaced record was displaced by the same wid. if (eLean == GetRecordFormat()) { Win4Assert(1 == prec->CountRecords()); // Save the "to be displaced" record before actually displacing it. if (fBackup) { CBackupWid backupNewWid(this, wid, prec->CountRecords()); _PropStoreInfo.MarkDirty(); } prec->MakeNewLeanTopLevel(); } else { Win4Assert(eNormal == GetRecordFormat()); // Save the "to be displaced" record before actually displacing it. if (fBackup) { CBackupWid backupNewWid(this, wid, prec->CountRecords(), eTopLevelField, (ULONG)wid, prec); _PropStoreInfo.MarkDirty(); } prec->MakeNewNormalTopLevel(); } } else { Win4Assert(!prec->IsLeanRecord()); Win4Assert(eNormal == GetRecordFormat()); if (fBackup) { CBackupWid backupWid(this, wid, prec->CountRecords()); _PropStoreInfo.MarkDirty(); } prec->MakeNewOverflow(); } } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::LokNewWorkId, private // // Synopsis: Find next available workid // // Arguments: [cWid] -- Count of contiguous workids (records) needed. // [fTopLevel] -- true if the new wid is a top-level wid // // Returns: Next available workid. // // History: 03-Jan-96 KyleP Created. // //---------------------------------------------------------------------------- WORKID CPropertyStore::LokNewWorkId( ULONG cWid, BOOL fTopLevel, BOOL fBackup ) { // // First, try to find a free block of the appropriate size. // Search for the best-fit, scanning the list until an exact // match or the first smaller block is found. // COnDiskPropertyRecord * prec = 0; COnDiskPropertyRecord * precPrev = 0; CBorrowed Borrowed( _xPhysStore.GetReference(), _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); ciDebugOut(( DEB_PROPSTORE, "Looking for free record of size %u\n", cWid )); WORKID wid = LokAllocRecord( cWid ); if (wid != 0) { Borrowed.Set( wid ); prec = Borrowed.Get(); ciDebugOut(( DEB_PROPSTORE, " Free wid 0x%x, size = %u\n", wid, prec->CountRecords() )); // // Is it big enough? // Win4Assert ( prec->CountRecords() >= cWid ); // // Adjust size, and put extra back on free list. // if ( prec->CountRecords() > cWid ) { CBorrowed BorrowedRemainder( _xPhysStore.GetReference(), wid + cWid, _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); COnDiskPropertyRecord * precRemainder = BorrowedRemainder.Get(); LokFreeRecord( wid+cWid, prec->CountRecords() - cWid, precRemainder, fBackup ); } InitNewRecord( wid, cWid, prec, fTopLevel, fBackup ); } else { Win4Assert( cWid <= _PropStoreInfo.RecordsPerPage() ); wid = _PropStoreInfo.MaxWorkId() + 1; // // Do we need a fresh page? // WORKID widInRec = wid % _PropStoreInfo.RecordsPerPage(); if ( widInRec + cWid - 1 >= _PropStoreInfo.RecordsPerPage() ) { ciDebugOut(( DEB_PROPSTORE, "Aligning for Multi-workid request...\n" )); CBorrowed Borrowed( _xPhysStore.GetReference(), wid, _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); COnDiskPropertyRecord * prec = Borrowed.Get(); #if CIDBG == 1 if (eLean == GetRecordFormat()) Win4Assert( prec->IsLeanEmpty(_PropStoreInfo.RecordSize()) ); else Win4Assert( prec->IsNormalEmpty( _PropStoreInfo.RecordSize() ) ); #endif // CIDBG ULONG cFree = _PropStoreInfo.RecordsPerPage() - widInRec; LokFreeRecord( wid, cFree, prec, fBackup ); wid += cFree; Win4Assert( (wid % _PropStoreInfo.RecordsPerPage() ) == 0 ); } if ( cWid > 1 ) { ciDebugOut(( DEB_PROPSTORE, "New max workid = %d\n", _PropStoreInfo.MaxWorkId() )); } CBorrowed Borrowed( _xPhysStore.GetReference(), _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); Borrowed.SetMaybeNew( wid ); COnDiskPropertyRecord * prec = Borrowed.Get(); _PropStoreInfo.SetMaxWorkId( wid + cWid - 1 ); #if CIDBG==1 if ( prec->IsInUse() ) { ciDebugOut(( DEB_ERROR, "Wid (0x%X) pRec (0x%X) is in use.\n", wid, prec )); // Win4Assert( !"InUse bit must not be set here" ); } if ( prec->IsTopLevel() ) { ciDebugOut(( DEB_ERROR, "Wid (0x%X) pRec (0x%X) is top level record.\n", wid, prec )); // Win4Assert( !prec->IsTopLevel() ); } #endif // CIDBG==1 InitNewRecord( wid, cWid, prec, fTopLevel, fBackup ); } return wid; } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::Transfer, private // // Synopsis: Transfer complete contents to new store. // // Arguments: [Target] -- Target property store. // [pid] -- Property to transfer first. Must be on every // record. // [fAbort] -- Set this variable to TRUE to abort transfer. // [pProgress] -- Progress reported here. // // History: 16-Jan-96 KyleP Created. // 19-Sep-97 KrishnaN Disabled record transfer during backup. // // Notes: For progress notification, we are assuming that copying the // top level takes about 50% of the time and the remaining 50% // is for the properties other than the first property. // //---------------------------------------------------------------------------- void CPropertyStore::Transfer( CPropertyStore & Target, PROPID pid, BOOL & fAbort, IProgressNotify * pProgress ) { // // Make Sure that the pid specified is a valid pid and is of fixed // length. If it is of variable length, that property may overflow // the top level records. If that happens, we cannot guarantee retaining // the same top level wid number in the new propstore. // CPropDesc const * pdesc = _PropStoreInfo.GetDescription( pid ); if ( !pdesc || !pdesc->IsFixedSize() ) { ciDebugOut(( DEB_ERROR, "PropId 0x%X is not of fixed size.\n", pid )); THROW( CException( E_INVALIDARG ) ); } // // Copy data from old to new. // CPropertyStoreWids iter( *this ); PROPVARIANT var; XArray abExtra( COMMON_PAGE_SIZE ); // // Transfer special property first. This gives same top-level workids as // previous situation. Assumption: Property will *not* overflow record. // ULONG iRec = 0; const ULONG cTotal = _PropStoreInfo.CountRecordsInUse(); const cUpdInterval = 500; // every 500 records for ( WORKID wid = iter.WorkId(); wid != widInvalid; wid = iter.LokNextWorkId() ) { if ( _fAbort || fAbort ) { ciDebugOut(( DEB_WARN,"Stopping Transfer because of abort\n" )); THROW( CException(STATUS_TOO_LATE) ); } unsigned cb = abExtra.Count(); BOOL fOk = ReadProperty( wid, pid, var, abExtra.GetPointer(), &cb ); Win4Assert( fOk ); // Win4Assert( var.vt != VT_EMPTY ); if ( fOk ) { Target.WritePropertyInSpecificNewRecord( wid, pid, *(CStorageVariant *)(ULONG_PTR)&var, FALSE); } iRec++; if ( pProgress ) { if ( (iRec % cUpdInterval) == 0 ) { pProgress->OnProgress( (DWORD) iRec, (DWORD) 2*cTotal, // We are copying only the top level now FALSE, // Not accurate FALSE // Ownership of Blocking Behavior ); } } } Win4Assert( iRec == cTotal ); // // Transfer remaining properties. // CPropertyStoreWids iter2( *this ); iRec = 0; for ( wid = iter2.WorkId(); wid != widInvalid; wid = iter2.LokNextWorkId() ) { if ( _fAbort || fAbort ) { ciDebugOut(( DEB_WARN,"Stopping Transfer2 because of abort\n" )); THROW( CException(STATUS_TOO_LATE) ); } for ( unsigned i = 0; i < _PropStoreInfo.CountProps(); i++ ) { CPropDesc const * pdesc = _PropStoreInfo.GetDescriptionByOrdinal( i ); if ( 0 != pdesc && pdesc->Pid() != pid ) { unsigned cb = abExtra.Count(); if ( ReadProperty( wid, pdesc->Pid(), var, abExtra.GetPointer(), &cb ) && var.vt != VT_EMPTY ) { Target.WriteProperty( wid, pdesc->Pid(), *(CStorageVariant *)(ULONG_PTR)&var, FALSE); } } } iRec++; if ( pProgress ) { if ( (iRec++ % cUpdInterval) == 0 ) { pProgress->OnProgress( (DWORD) (iRec+cTotal), (DWORD) 2*cTotal, // We are copying only the top level now FALSE, // Not accurate FALSE // Ownership of Blocking Behavior ); } } } } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::FastTransfer, private // // Synopsis: Transfer complete contents to new store, without changes. // // Arguments: [Target] -- Target property store. // [fAbort] -- Set this variable to TRUE to abort transfer. // [pProgress] -- Progress reported here. // // History: 16-Oct-97 KyleP Created (based on ::Transfer) // //---------------------------------------------------------------------------- void CPropertyStore::FastTransfer( CPropertyStore & Target, BOOL & fAbort, IProgressNotify * pProgress ) { // // Just transfer the storage in bulk. There's no modification to the // property store here, thus no reason to iterate by record. // CProgressTracker Tracker; Tracker.LokStartTracking( pProgress, &fAbort ); _xPhysStore->MakeBackupCopy( Target._xPhysStore.GetReference(), Tracker ); Tracker.LokStopTracking(); } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::Flush // // Synopsis: Flushes the data in the property store and marks it clean. // // Returns: TRUE if the store is clean. FALSE otherwise. // // History: 3-20-96 srikants Created // //---------------------------------------------------------------------------- BOOL CPropertyStore::Flush() { CImpersonateSystem impersonate; // // Don't reset the backup stream here. // That will happen in the manager. // if ( _xPhysStore.GetPointer() ) { if ( !_pStorage->IsReadOnly() ) _xPhysStore->Flush(); if ( _fIsConsistent ) _PropStoreInfo.MarkClean(); else _PropStoreInfo.MarkDirty(); } return !IsDirty(); } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::AcquireRead, private // // Synopsis: Acquires read lock for a record // // Arguments: [record] -- Record // // History: 19-Jan-96 KyleP Created. // //---------------------------------------------------------------------------- void CPropertyStore::AcquireRead( CReadWriteLockRecord & record ) { #if CIDBG == 1 DWORD tid = GetCurrentThreadId(); if ( tid < cTrackThreads ) { Win4Assert( _xPerThreadReadCounts[tid] == 0 ); // Double-read Win4Assert( _xPerThreadWriteCounts[tid] == 0 ); // Write before Read } #endif do { if ( record.isBeingWritten() ) SyncRead( record ); record.AddReader(); if ( !record.isBeingWritten() ) break; else SyncReadDecrement( record ); } while ( TRUE ); #if CIDBG == 1 if ( tid < cTrackThreads ) _xPerThreadReadCounts[tid]++; #endif } //AcquireRead //+--------------------------------------------------------------------------- // // Member: CPropertyStore::SyncRead, private // // Synopsis: Helper for AcquireRead // // Arguments: [record] -- Record // // History: 19-Jan-96 KyleP Created. // //---------------------------------------------------------------------------- void CPropertyStore::SyncRead( CReadWriteLockRecord & record ) { do { BOOL fNeedToWait = FALSE; { CLock lock( _mtxRW ); if ( record.isBeingWritten() ) { ciDebugOut(( DEB_PROPSTORE, "READ.RESET\n" )); _ReSetReadTid(); _evtRead.Reset(); fNeedToWait = TRUE; } } if ( fNeedToWait ) { ciDebugOut(( DEB_PROPSTORE, "READ.WAIT\n" )); _evtRead.Wait(); } } while ( record.isBeingWritten() ); } //SyncRead //+--------------------------------------------------------------------------- // // Member: CPropertyStore::SyncReadDecrement, private // // Synopsis: Helper for AcquireRead // // Arguments: [record] -- Record // // History: 19-Jan-96 KyleP Created. // 21-Feb-97 dlee Rearranged to be more like // readwrit.hxx, for consistency // // Notes: The *very* important invariant of this method is that the // read count will be decremented once and only once. // The lack of this invarariant may very // well be the last Tripoli 1.0 bug. // //---------------------------------------------------------------------------- void CPropertyStore::SyncReadDecrement( CReadWriteLockRecord & record ) { BOOL fDecrementRead = TRUE; do { BOOL fNeedToWait = FALSE; { CLock lock( _mtxRW ); if ( record.isBeingWritten() ) { if ( fDecrementRead ) { record.RemoveReader(); if ( !record.isBeingRead() ) { ciDebugOut(( DEB_PROPSTORE, "WRITE.SET\n" )); _SetWriteTid(); _evtWrite.Set(); } } ciDebugOut(( DEB_PROPSTORE, "READ.RESET\n" )); _ReSetReadTid(); _evtRead.Reset(); fNeedToWait = TRUE; } else { if ( fDecrementRead ) record.RemoveReader(); } fDecrementRead = FALSE; } if ( fNeedToWait ) { ciDebugOut(( DEB_PROPSTORE, "READ.WAIT\n" )); _evtRead.Wait(); } } while ( record.isBeingWritten() ); Win4Assert( !fDecrementRead ); } //SyncReadDecrement //+--------------------------------------------------------------------------- // // Member: CPropertyStore::ReleaseRead, private // // Synopsis: Releases read lock for a record // // Arguments: [record] -- Record // // History: 19-Jan-96 KyleP Created. // //---------------------------------------------------------------------------- void CPropertyStore::ReleaseRead( CReadWriteLockRecord & record ) { if ( record.isBeingWritten() ) { CLock lock( _mtxRW ); record.RemoveReader(); if ( !record.isBeingRead() ) { ciDebugOut(( DEB_PROPSTORE, "WRITE.SET\n" )); _SetWriteTid(); _evtWrite.Set(); } } else { record.RemoveReader(); if ( record.isBeingWritten() ) { CLock lock( _mtxRW ); if ( !record.isBeingRead() ) { ciDebugOut(( DEB_PROPSTORE, "WRITE.SET\n" )); _SetWriteTid(); _evtWrite.Set(); } } } #if CIDBG == 1 DWORD tid = GetCurrentThreadId(); if ( tid < cTrackThreads ) _xPerThreadReadCounts[tid]--; #endif } //ReleaseRead //+--------------------------------------------------------------------------- // // Member: CPropertyStore::AcquireWrite, private // // Synopsis: Acquires write lock for a record // // Arguments: [record] -- Record // // History: 19-Jan-96 KyleP Created. // 20-Nov-97 KrishnaN Code reorg, but no functionality change. // //---------------------------------------------------------------------------- void CPropertyStore::AcquireWrite( CReadWriteLockRecord & record ) { #if CIDBG == 1 DWORD tid = GetCurrentThreadId(); if ( tid < cTrackThreads ) { Win4Assert( _xPerThreadReadCounts[tid] == 0 ); // Read before Write Win4Assert( _xPerThreadWriteCounts[tid] == 0 ); // Double-write } #endif AcquireWrite2(record); #if CIDBG == 1 if ( tid < cTrackThreads ) _xPerThreadWriteCounts[tid]++; #endif } //AcquireWrite //+--------------------------------------------------------------------------- // // Member: CPropertyStore::AcquireWrite2, private // // Synopsis: Acquires write lock for a record. There are no per thread // checks to ensure that a thread is only writing once. The // caller will check that. // // Arguments: [record] -- Record // // History: 20-Nov-97 KrishnaN Created. // //---------------------------------------------------------------------------- void CPropertyStore::AcquireWrite2( CReadWriteLockRecord & record ) { record.AddWriter(); BOOL fNeedToWait = FALSE; { CLock lock( _mtxRW ); Win4Assert( !record.LokIsBeingWrittenTwice() ); if ( record.isBeingRead() ) { ciDebugOut(( DEB_PROPSTORE, "WRITE.RESET\n" )); _ReSetWriteTid(); _evtWrite.Reset(); fNeedToWait = TRUE; } } if ( fNeedToWait ) { ciDebugOut(( DEB_PROPSTORE, "WRITE.WAIT\n" )); _evtWrite.Wait(); } } //AcquireWrite2 //+--------------------------------------------------------------------------- // // Member: CPropertyStore::ReleaseWrite, private // // Synopsis: Release write lock for a record // // Arguments: [record] -- Record // // History: 19-Jan-96 KyleP Created. // //---------------------------------------------------------------------------- void CPropertyStore::ReleaseWrite( CReadWriteLockRecord & record ) { ReleaseWrite2(record); #if CIDBG == 1 DWORD tid = GetCurrentThreadId(); if ( tid < cTrackThreads ) _xPerThreadWriteCounts[tid]--; #endif } //ReleaseWrite //+--------------------------------------------------------------------------- // // Member: CPropertyStore::ReleaseWrite2, private // // Synopsis: Release write lock for a record. No per thread tracking. The // caller will do that. // // Arguments: [record] -- Record // // History: 20-Nov-97 KrishnaN Created. // //---------------------------------------------------------------------------- void CPropertyStore::ReleaseWrite2( CReadWriteLockRecord & record ) { CLock lock( _mtxRW ); record.RemoveWriter(); ciDebugOut(( DEB_PROPSTORE, "READ.SET\n" )); _SetReadTid(); _evtRead.Set(); } //ReleaseWrite2 //+--------------------------------------------------------------------------- // // Member: CPropertyStore::AcquireWriteOnAllRecords, private // // Synopsis: Lock out all readers. // // History: 18-Nov-97 KrishnaN Created. // //---------------------------------------------------------------------------- void CPropertyStore::AcquireWriteOnAllRecords() { ULONG cRecs = LockMgr().UniqueRecordCount(); // lock down each record, one by one for (ULONG i = 0; i < cRecs; i++) { Win4Assert(0 == _pbRecordLockTracker[i]); AcquireWrite2( LockMgr().GetRecord( (WORKID) i) ); #if CIDBG == 1 _pbRecordLockTracker[i]++; #endif // CIDBG } } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::ReleaseWriteOnAllRecords, private // // Synopsis: Release all write locked records. // // History: 18-Nov-97 KrishnaN Created. // //---------------------------------------------------------------------------- void CPropertyStore::ReleaseWriteOnAllRecords() { ULONG cRecs = LockMgr().UniqueRecordCount(); for (ULONG i = 0; i < cRecs; i++) { Win4Assert( 1 == _pbRecordLockTracker[i] ); ReleaseWrite2(LockMgr().GetRecord((WORKID)i)); } #if CIDBG == 1 RtlZeroMemory(_pbRecordLockTracker, sizeof(BYTE)*LockMgr().UniqueRecordCount()); #endif // CIDBG } //+--------------------------------------------------------------------------- // // Member: CPropertyStore::GetTotalSizeInKB, public // // Synopsis: Compute the size of the property store, including the backup file. // // Return: The size in KiloBytes. // // History: 19-Jan-96 KyleP Created. // //---------------------------------------------------------------------------- ULONG CPropertyStore::GetTotalSizeInKB() { // primary store size ULONG cSizeInKB = 0; if ( !_xPhysStore.IsNull() ) cSizeInKB += CI_PAGE_SIZE*_xPhysStore->PageSize()/1024; if (!_xPSBkpStrm.IsNull()) { // backup store size cSizeInKB += roundup(_xPSBkpStrm->GetSizeInBytes(), 1024); } return cSizeInKB; } //GetTotalSizeInKB //+------------------------------------------------------------------------- // // Member: CiCat::ClearNonStorageProperties, public // // Synopsis: write VT_EMPTY into the properties in the PropertyStore ( // except the non-modifiable ones) // // Arguments: [rec] -- Property record for writes // // History: 06-Oct-2000 KitmanH Created // //-------------------------------------------------------------------------- void CPropertyStore::ClearNonStorageProperties( CCompositePropRecordForWrites & rec ) { CPropDesc const * pDesc = 0; CStorageVariant var; var.SetEMPTY(); for ( unsigned i = 0; i < _PropStoreInfo.CountDescription(); i++ ) { pDesc = _PropStoreInfo.GetDescription(i); if ( 0 != pDesc && pDesc->Modifiable() ) { ciDebugOut(( DEB_ITRACE, "ClearNonStorageProperties: CPropertyStore %d: about to overwrite pid %d\n", GetStoreLevel(), pDesc->Pid() )); _propStoreMgr.WriteProperty( GetStoreLevel(), rec, pDesc->Pid(), var ); } #if CIDBG else { if ( 0 != pDesc ) ciDebugOut(( DEB_ITRACE, "ClearNonStorageProperties: CPropertyStore %d: pid %d is !MODIFIABLE\n", GetStoreLevel(), pDesc->Pid() )); else ciDebugOut(( DEB_ITRACE, "CPropertyStore::ClearNonStorageProperties: pDesc is 0\n" )); } #endif } } // CBackupWid methods //+--------------------------------------------------------------------------- // // Function: CBackupWid::BackupWid method // // Synopsis: Backup the page(s) containing the wid. If necessary, write // the top-level wid in the free record. // // Arguments: [wid] -- Wid to backup. // [cRecsInWid] -- Length of the wid in physical records. // [FieldToCommit] -- Field to commit to disk. // [ulValue] -- When valid, this is the field value to // be written as part of the wid in the backup's // copy. // [pRec] -- Address of the record being modified. // // Returns: Nothing. // // History: 09-June-97 KrishnaN Created // 27-Jan-2000 KLam Removed bogus fBackedUp asserts // //---------------------------------------------------------------------------- void CBackupWid::BackupWid(WORKID wid, ULONG cRecsInWid, EField FieldToCommit, ULONG ulValue, COnDiskPropertyRecord const *pRec) { Win4Assert( _pPropStor ); Win4Assert( _pPropStor->BackupStream() ); ULONG cPages = TryDescribeWidInAPage(wid, cRecsInWid); cPages++; // actual number of pages ULONG dSlot = _ulFirstPage; ciDebugOut(( DEB_PSBACKUP, "BackupWid: wid = %d (0x%x), %d recs long, FieldToCommit = %d, " "Value to commit = 0x%x, pRec = 0x%x, pages needed = %d\n", wid, wid, cRecsInWid, FieldToCommit, ulValue, pRec, cPages)); // // If we need more than one page to describe this wid, allocate // space for the data structures and describe them. // BOOL fBackedUp = FALSE; ULONG ulOffset = 0xFFFFFFFF; if (1 == cPages) { fBackedUp = BackupPages(1, &_ulFirstPage, (void **)&_pbFirstPage); if (eFieldNone != FieldToCommit) DescribeField(FieldToCommit, pRec, 1, (void **)&_pbFirstPage, ulOffset); } else { CDynArrayInPlace adSlots(cPages); CDynArrayInPlace aPagePtrs(cPages); void const * const* paPagePtrs = aPagePtrs.GetPointer(); DescribeWid(wid, cRecsInWid, adSlots, aPagePtrs); fBackedUp = BackupPages(cPages, adSlots.GetPointer(), paPagePtrs); // reuse dSlot if (eFieldNone != FieldToCommit) dSlot = adSlots[DescribeField(FieldToCommit, pRec, cPages, paPagePtrs, ulOffset)]; } // // commit the widTopLevel to top-level field of free record to disk // At this point the page has already been commited to backup, so it should be there. // if (fBackedUp && eFieldNone != FieldToCommit) fBackedUp = _pPropStor->BackupStream()->CommitField(dSlot, ulOffset, sizeof(WORKID), &ulValue); // Throw the error that caused file i/o to fail! if (!fBackedUp) { ciDebugOut(( DEB_ERROR, "Unexpected error writing a page to the backup file!")); THROW( CException() ); } } //+--------------------------------------------------------------------------- // // Member: CBackupWid::TryDescribeWidInAPage, public // // Synopsis: Determine the OS pages containing the wids and the location of // the first wid in each OS page. // // Arguments: [wid] -- Wid to describe. // [cRecsInWid] -- Number of records in wid. // // Returns: The number of additional OS pages needed for the wid. The caller // should use DescribeWids if the return value is > 0. // // History: 09-Jun-97 KrishnaN Created. // //---------------------------------------------------------------------------- inline ULONG CBackupWid::TryDescribeWidInAPage(WORKID wid, ULONG cRecsInWid) { Win4Assert(widInvalid != wid ); // Determine the first and last OS pages, relative to start of property store file, which contain wid ComputeOSPageLocations(wid, cRecsInWid, &_ulFirstPage, &_ulLastPage); _nLargePage = _ulFirstPage/_pPropStor->OSPagesPerPage(); // this wid should be within a large page. Assert that. Win4Assert(_nLargePage == _ulLastPage/_pPropStor->OSPagesPerPage()); _pbFirstPage = GetOSPagePointer(_ulFirstPage); return (_ulLastPage - _ulFirstPage); } //+--------------------------------------------------------------------------- // // Member: CBackupWid::DescribeWids, public // // Synopsis: Determine the OS pages containing the wids. // // Arguments: [Wid] -- Wid to describe. // [cRecsInWid] -- Count of records in wid. // [pcPages] -- Number of pages. // [pulPages] -- Dynamic array of page descriptors. // [ppvPages] -- Dynamic array of page pointers to be backedup. // // Returns: Index into hash table (_aProp) of pid, or first unused // entry on chain if pid doesn't exist. // // Notes: Using this requires at least two dynamic allocations on the heap. // If that is deemed expensive, we can attempt to describe only one // wid at a time using TryDescribeWibInAPage. // // History: 09-Jun-97 KrishnaN Created. // //---------------------------------------------------------------------------- inline void CBackupWid::DescribeWid( WORKID wid, ULONG cRecsInWid, CDynArrayInPlace &pulPages, CDynArrayInPlace &ppvPages) { Win4Assert(widInvalid != wid ); // First and last page and the first page ptr should have already // been computed by calling TryDescribeWidInAPage Win4Assert(_ulFirstPage != 0xFFFFFFFF && _ulLastPage != 0xFFFFFFFF); Win4Assert(_pbFirstPage); Win4Assert(_ulLastPage > _ulFirstPage); ULONG cPages, page; for (page = _ulFirstPage, cPages = 0; page <= _ulLastPage; page++, cPages++) { pulPages[cPages] = page; ppvPages[cPages] = _pbFirstPage + cPages*_pPropStor->OSPageSize(); } } //+--------------------------------------------------------------------------- // // Function: CBackupWid::BackupPages, public // // Synopsis: Backup pages. If space is not available for backup, // // Arguments: [cPages] -- Number of pages to backup. // [pSlots] -- Array of page descriptors. // [ppbPages] -- Array of page pointers to backup. // // Returns: TRUE if the pages were successfully backed up. // FALSE otherwise. // // History: 09-June-97 KrishnaN Created // //---------------------------------------------------------------------------- BOOL CBackupWid::BackupPages(ULONG cPages, ULONG const *pulPages, void const * const * ppvPages) { Win4Assert(cPages && ppvPages && pulPages); Win4Assert( _pPropStor->BackupStream() ); ciDebugOut(( DEB_PSBACKUP, "BackupPages: About to backup %2d pages: ", cPages)); #if CIDBG // We should have no more than 16 pages to backup because that is the max // a property can straddle (a prop is limited to a 64K common page size). On // alpha the max is only 8. Win4Assert(cPages <= 16); WCHAR szBuff[2048]; szBuff[0] = 0; for (ULONG j = 0; j < cPages; j++) { WCHAR szPage[100]; swprintf(szPage, L"%10d, 0x%x; ", pulPages[j], ppvPages[j]); wcscat(szBuff, szPage); } ciDebugOut((DEB_PSBACKUP, "%ws\n", szBuff)); #endif // CIDBG // Commit pages. BOOL fOK; if (1 == cPages) fOK = _pPropStor->BackupStream()->CommitPage(pulPages[0], ppvPages[0]); else fOK = _pPropStor->BackupStream()->CommitPages(cPages, pulPages, ppvPages); if (fOK) return TRUE; // Not enough space to commit pages. Flush the property store // and try again. ciDebugOut((DEB_PSBACKUP, "Flushing the property store to make space for pages.\n")); _pPropStor->_propStoreMgr.Flush(); if (1 == cPages) return _pPropStor->BackupStream()->CommitPage(pulPages[0], ppvPages[0]); else return _pPropStor->BackupStream()->CommitPages(cPages, pulPages, ppvPages); } //+--------------------------------------------------------------------------- // // Function: CBackupWid::GetOSPagePointer, private // // Synopsis: Get the OS page pointer for the given page. // // Arguments: [ulPage] -- i-th OS page (0-based index) of prop store. // // Returns: A memory pointer to the specified page. // // History: 09-June-97 KrishnaN Created // // Notes: This will be returning read-only pages. // //---------------------------------------------------------------------------- inline BYTE * CBackupWid::GetOSPagePointer(ULONG ulPage) { PBYTE pLargePage = (PBYTE)_pPropStor->PhysStore()->BorrowLargeBuffer( ulPage/_pPropStor->OSPagesPerPage(), FALSE, FALSE ); return (pLargePage + _pPropStor->OSPageSize()*(ulPage%_pPropStor->OSPagesPerPage())); } //+--------------------------------------------------------------------------- // // Function: CBackupWid::ComputeOSPageLocations, private // // Synopsis: Compute the os pages containing the given wid.. // // Arguments: [wid] -- wid in question // [cRecsInWid] -- # of records in wid // [pFirstPage] -- Ptr where first page's loc should be stored // [pLastLage] -- Ptr where last page's loc should be stored // // Returns: A memory pointer to the specified page. // // History: 09-June-97 KrishnaN Created // // Notes: This will be returning read-only pages. // //---------------------------------------------------------------------------- inline void CBackupWid::ComputeOSPageLocations(WORKID wid, ULONG cRecsInWid, ULONG *pFirstPage, ULONG *pLastPage) { // Determine the i-th large page and the position of wid in the large page ULONG nLargePage = wid / _pPropStor->RecordsPerPage(); ULONG widInLargePage = wid % _pPropStor->RecordsPerPage(); *pFirstPage = nLargePage*_pPropStor->OSPagesPerPage() + (widInLargePage * _pPropStor->RecordSize() * sizeof(ULONG))/_pPropStor->OSPageSize(); *pLastPage = nLargePage*_pPropStor->OSPagesPerPage() + ((widInLargePage+cRecsInWid)*_pPropStor->RecordSize()*4 - 1)/_pPropStor->OSPageSize(); } //+--------------------------------------------------------------------------- // // Function: CBackupWid::DescribeField, private // // Synopsis: Obtain the location of the top-level field in the list of pages. // // Arguments: [FieldToCommit] -- Field to commit. Should not be eFieldNone. // [pRec] -- Address of the record being modified. // [cPages] -- Number of pages in the list of pages. // [ppvPages] -- List of pages. // [pulOffset] -- Ptr to location containing offset of field. // // Returns: i-th page in the ppvPages array that has the field. // // History: 09-June-97 KrishnaN Created // //---------------------------------------------------------------------------- ULONG CBackupWid::DescribeField( EField FieldToCommit, COnDiskPropertyRecord const *pRec, ULONG cPages, void const * const* ppvPages, ULONG &ulOffset) { Win4Assert(eFieldNone != FieldToCommit); Win4Assert(!pRec->IsLeanRecord()); // no need to do this for lean records! ulOffset = 0xFFFFFFFF; void const *pvFieldLoc = 0; switch (FieldToCommit) { case eTopLevelField: pvFieldLoc = pRec->GetTopLevelFieldAddress(); break; case eFieldNone: default: Win4Assert(!"Invalid field to describe!"); return 0xFFFFFFFF; } // We have the pointer to the wid field. Now identify the page it belongs to, for (ULONG i = 0; i < cPages; i++) if (pvFieldLoc >= ppvPages[i] && pvFieldLoc < (PVOID)(PBYTE(ppvPages[i]) + _pPropStor->OSPageSize())) break; Win4Assert( i < cPages ); ulOffset = (ULONG) ((PBYTE)pvFieldLoc - (PBYTE)ppvPages[i]); return i; } //+--------------------------------------------------------------------------- // // Member: Constructor for the CPropertyStoreRecovery method // // Arguments: [propStore] - // // History: 4-10-96 srikants Created // //---------------------------------------------------------------------------- CPropertyStoreRecovery::CPropertyStoreRecovery( CPropertyStore & propStore, T_UpdateDoc pfnUpdateCallback, void const *pUserData) : _propStore(propStore), _PropStoreInfo(propStore._PropStoreInfo), _wid(1), _pRec(0), _cRec(0), _widMax(0), _cTopLevel(0), _cInconsistencies(0), _cForceFreed(0), _pageTable( 10 ), _fnUpdateCallback( pfnUpdateCallback ), _pUserData( pUserData ) { _pPhysStore = propStore._xPhysStore.GetPointer(); _cRecPerPage = _PropStoreInfo.RecordsPerPage(); _aFreeBlocks = new WORKID[ _PropStoreInfo.RecordsPerPage() + 1 ]; RtlZeroMemory( _aFreeBlocks, (_PropStoreInfo.RecordsPerPage()+1) * sizeof (WORKID) ); // Open the backup stream in read mode for recovery WORKID wid = _PropStoreInfo.WorkId(); if (widInvalid != wid) { SStorageObject xobj( _propStore.GetStorage().QueryObject( wid ) ); _xPSBkpStrm.Set(_propStore.GetCiStorage().OpenExistingPSBkpStreamForRecovery( xobj.GetObj(), propStore.GetStoreLevel() )); } } //+--------------------------------------------------------------------------- // // Member: CPropertyStoreRecovery::~CPropertyStoreRecovery, public // // Synopsis: Destructor of the CPropertyStoreRecovery. // // Notes: In a successful recovery, the _aFreeBlocks will be transferred // to the CPropertyStore class. // // History: 10 May 96 AlanW Created. // //---------------------------------------------------------------------------- CPropertyStoreRecovery::~CPropertyStoreRecovery() { delete _aFreeBlocks; } //+--------------------------------------------------------------------------- // // Member: CPropertyStoreRecovery::AddToFreeList, private // // Synopsis: Add a free record to a free list. // // Arguments: [widFree] -- Workid of record to free // [cFree] -- Number of records in freed chunk // [precFree] -- On-disk Property record // [widListHead] -- Start of free list // // Returns: WORKID - new start of list // // Notes: All blocks in the free list passed are assumed to be of // the same length. // // History: 01 May 96 AlanW Created. // //---------------------------------------------------------------------------- WORKID CPropertyStoreRecovery::AddToFreeList( WORKID widFree, ULONG cFree, COnDiskPropertyRecord * precFree, WORKID widListHead ) { if ( widListHead != 0 ) { CBorrowed Borrowed( *_pPhysStore, widListHead, _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); COnDiskPropertyRecord * prec = Borrowed.Get(); Win4Assert( prec->CountRecords() == cFree ); Win4Assert( prec->GetNextFreeRecord() == 0 || prec->GetNextFreeSize() == cFree ); prec->SetPreviousFreeRecord( widFree ); } // // Insert new record at beginning of list // if (eLean == _PropStoreInfo.GetRecordFormat()) precFree->MakeLeanFreeRecord( cFree, widListHead, widListHead==0? 0 : cFree, _PropStoreInfo.RecordSize() ); else precFree->MakeNormalFreeRecord( cFree, widListHead, widListHead==0? 0 : cFree, _PropStoreInfo.RecordSize() ); return widFree; } //+--------------------------------------------------------------------------- // // Member: CPropertyStoreRecovery::SetFree // // Synopsis: Marks the current record as free for _cRec length of // records. // // History: 4-10-96 srikants Created // //---------------------------------------------------------------------------- void CPropertyStoreRecovery::SetFree() { Win4Assert( _pRec ); Win4Assert( _cRec <= _PropStoreInfo.RecordsPerPage() ); _aFreeBlocks[_cRec] = AddToFreeList( _wid, _cRec, _pRec, _aFreeBlocks[_cRec] ); } //+--------------------------------------------------------------------------- // // Member: CPropertyStoreRecovery::CheckOverflowChain // // Synopsis: Verify and fix the forward length links. // // History: 4-10-96 srikants Created // //---------------------------------------------------------------------------- BOOL CPropertyStoreRecovery::CheckOverflowChain() { Win4Assert( _pRec->IsNormalTopLevel() ); CBorrowed Borrowed( *_pPhysStore, _wid, _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); COnDiskPropertyRecord * prec = Borrowed.Get(); ULONG cOverflowBlocks = 0; BOOL fIsConsistent = TRUE; for ( WORKID widOvfl = prec->OverflowBlock(); 0 != widOvfl; widOvfl = prec->OverflowBlock() ) { if ( widOvfl > _widMax ) { ciDebugOut(( DEB_ERROR, "widOvfl (0x%X) > widMax (0x%X)\n", widOvfl, _widMax )); fIsConsistent = FALSE; break; } Borrowed.Release(); Borrowed.Set( widOvfl ); prec = Borrowed.Get(); // // If this is not marked as an overflow record, what should we do? // if ( !prec->IsOverflow() ) { ciDebugOut(( DEB_WARN, "Wid (0x%X) should be an overflow record, but not!\n", widOvfl )); fIsConsistent = FALSE; break; } // // Check the validity of the toplevel pointers. // if ( prec->ToplevelBlock() != _wid ) { ciDebugOut(( DEB_WARN, "Changing the toplevel wid of (0x%X) from (0x%X) to (0x%X)\n", widOvfl, prec->ToplevelBlock(), _wid )); fIsConsistent = FALSE; break; } cOverflowBlocks++; } if ( fIsConsistent ) { fIsConsistent = cOverflowBlocks == _pRec->GetOverflowChainLength(); if (!fIsConsistent) { ciDebugOut(( DEB_WARN, "Overflow length for toplevel wid %d(0x%x) is %d. Expected %d\n", _wid, _wid, cOverflowBlocks, _pRec->GetOverflowChainLength() )); } } return fIsConsistent; } //+--------------------------------------------------------------------------- // // Member: CPropertyStoreRecovery::FreeChain, private // // Synopsis: Free an overflow record chain to the free list // // History: 02 May 96 AlanW Added header // //---------------------------------------------------------------------------- void CPropertyStoreRecovery::FreeChain( ) { Win4Assert(!_pRec->IsLeanRecord()); if ( !_pRec->IsTopLevel() ) { ciDebugOut(( DEB_WARN, "Ignore chain with non-top-level wid (0x%X)\n", _wid )); return; } ciDebugOut(( DEB_WARN, "Freeing chain with wid (0x%X)\n", _wid )); BOOL fOverflow = FALSE; WORKID wid = _wid; CBorrowed Borrowed( *_pPhysStore, _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); while ( 0 != wid && wid <= _widMax ) { Borrowed.Release(); Borrowed.Set(wid); COnDiskPropertyRecord * prec = Borrowed.Get(); if ( fOverflow ) { if ( !prec->IsOverflow() ) { ciDebugOut(( DEB_WARN, "Ignore chain with non-overflow wid (0x%X)\n", _wid )); break; } } else fOverflow = TRUE; ciDebugOut(( DEB_PROPSTORE, "Force freeing up wid (0x%X) prec (0x%X)\n", wid, prec )); WORKID widNext = prec->OverflowBlock(); ULONG cRec = prec->CountRecords(); if ( ! prec->IsValidLength( wid, _cRecPerPage ) ) { ciDebugOut(( DEB_WARN, "Ignore chain with invalid block size (0x%X)\n", _wid )); cRec = 1; widNext = 0; } _aFreeBlocks[cRec] = AddToFreeList( wid, cRec, prec, _aFreeBlocks[cRec] ); _cForceFreed++; wid = widNext; } _cTopLevel--; } //+--------------------------------------------------------------------------- // // Member: CPropertyStoreRecovery::Pass0 // // Synopsis: Restore sections of the property store from the backup file. // // History: 12-Jun-97 KrishnaN Created // //---------------------------------------------------------------------------- void CPropertyStoreRecovery::Pass0() { if (!_xPSBkpStrm->IsOpenForRecovery()) { ciDebugOut((DEB_WARN, "PROPSTORE: Backup file is not available for recovery.\n")); return; } ULONG cPages = _xPSBkpStrm->Pages(); _pageTable.Reset(cPages); // Remember that the property store could have been moved from a different architecture. ULONG cPageSize = _xPSBkpStrm->PageSize(); if (0 != COMMON_PAGE_SIZE%cPageSize) { ciDebugOut((DEB_WARN, "PROPSTORE: Backup file's page size (%d) is not compatible with " "the large page size (%d) used by prop store.\n", cPageSize, COMMON_PAGE_SIZE)); return; } ULONG cCustomPagesPerLargePage = COMMON_PAGE_SIZE/cPageSize; // Read each page from the backup and graft those pages at the appropriate location // in the property store. for (ULONG i = 0; i < cPages; i++) { ULONG ulLoc = _xPSBkpStrm->GetPageLocation(i); if (invalidPage == ulLoc) { Win4Assert(!"How did we get an invalid page in backup!"); ciDebugOut((DEB_PSBACKUP, "Page %d in backup is invalid.\n", i)); continue; } _pageTable.AddEntry(ulLoc); // takes care of borrowing/returning large page and actual copy of page // from backup to primary CGraftPage graftPage(i, ulLoc, cPageSize, cCustomPagesPerLargePage, _pPhysStore, _xPSBkpStrm.GetPointer()); } // Close it now. We will open it again later, most likely for backup. _xPSBkpStrm->Close(); } //+--------------------------------------------------------------------------- // // Member: CPropertyStoreRecovery::Pass1 // // Synopsis: Perform the pass1 recovery operation. // // History: 4-10-96 srikants Created // 6-16-97 KrishnaN Modified to enumerate wids in backed // up pages as part of the pass. // //---------------------------------------------------------------------------- void CPropertyStoreRecovery::Pass1() { Win4Assert(_fnUpdateCallback); CPageHashTable widsRefiltered(_xPSBkpStrm->Pages()+1); // // Extract the pidLastSeenTime info. // CPropDesc const * pFTDesc = _PropStoreInfo.GetDescription( pidLastSeenTime ); FILETIME ftLastSeen; RtlZeroMemory( &ftLastSeen, sizeof(ftLastSeen) ); CStorageVariant varFtLast( ftLastSeen ); if ( _pPhysStore->PageSize() > 0 ) { ULONG cCustomPagesPerLargePage = COMMON_PAGE_SIZE/_xPSBkpStrm->PageSize(); ULONG cLargePages = (_PropStoreInfo.MaxWorkId() / _PropStoreInfo.RecordsPerPage()) + 1; Win4Assert( cLargePages <= _pPhysStore->PageSize() / (COMMON_PAGE_SIZE / CI_PAGE_SIZE) ); // // Loop through and rebuild free list and max workid. // CBorrowed Borrowed( *_pPhysStore, _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); while ( TRUE ) { if ( _propStore._fAbort ) { ciDebugOut(( DEB_WARN, "Stopping Restore because of abort\n" )); THROW( CException(STATUS_TOO_LATE) ); } // // End of file? // ULONG nLargePage = _wid / _PropStoreInfo.RecordsPerPage(); if ( nLargePage >= cLargePages ) break; // // Valid record. // Borrowed.Release(); Borrowed.Set( _wid ); CLockRecordForWrite recLock( _propStore, _wid ); _pRec = Borrowed.Get(); // // If this wid falls in one of the backed up pages we should // enumerate it. Wids in backup should be scheduled for // re-filtering and if necessary, deleted. // // compute i-th custom page based on other params ULONG nCustomPage = nLargePage*cCustomPagesPerLargePage + (_PropStoreInfo.RecordSize()*sizeof(ULONG)*(_wid%_PropStoreInfo.RecordsPerPage())) / _xPSBkpStrm->PageSize(); if ( _pRec->IsValidInUseRecord(_wid, _cRecPerPage) ) { WORKID widToplevel; _cRec = _pRec->CountRecords(); // // If this is a topLevel record, set the pidLastSeenTime to 0. // if ( _pRec->IsTopLevel() ) { widToplevel = _wid; // // Set the pidLastSeenTime to 0 // if ( pFTDesc ) { // We should be doing this only in the store containing pidLastSeenTime. Win4Assert(PRIMARY_STORE == _PropStoreInfo.GetStoreLevel()); _pRec->WriteFixed( pFTDesc->Ordinal(), pFTDesc->Mask(), pFTDesc->Offset(), pFTDesc->Type(), _PropStoreInfo.CountProps(), varFtLast ); } _cTopLevel++; } else { Win4Assert( eNormal == _PropStoreInfo.GetRecordFormat() ); widToplevel = _pRec->ToplevelBlock(); Win4Assert( _pRec->IsOverflow() ); ciDebugOut(( DEB_PROPSTORE, "PROPSTORE: Overflow Record 0x%X\n", _wid )); } _widMax = _wid+_cRec-1; // If this wid is in a backed up page we should // schedule the top-level wid for re-filtering. if (_pageTable.LookUp(nCustomPage)) { ULONG ul; // Refilter it if it has not already been if (!widsRefiltered.LookUp((ULONG)widToplevel, ul)) { _fnUpdateCallback(widToplevel, FALSE, _pUserData); ciDebugOut((DEB_PSBACKUP, "Wid %d (0x%x) in custom page %d scheduled for re-filtering.\n", widToplevel, widToplevel, nCustomPage)); widsRefiltered.AddEntry((ULONG)widToplevel, 0); } } } else { // This assert could go off in case of file corruption. Win4Assert(!_pRec->IsInUse()); // For the Normal Format records: // If this wid is in a backed up page and points to the wid // occupying its place in the primary, we should delete the // occupying wid. // // For the Lean Format records: // We do not store the top-level wid of the occupying wid for lean records. // Each lean record is only "one record" long. So every displaced // free record was displaced by a newly indexed document. Since we // delete newly indexed docs as part of restore, we need to delete // _wid if it is found in the backup. // if (_pageTable.LookUp(nCustomPage)) { if (eNormal == _PropStoreInfo.GetRecordFormat() && _pRec->ToplevelBlock() == _wid) { _pRec->ClearToplevelField(); ciDebugOut((DEB_PSBACKUP, "Wid %d (0x%x) in page %d scheduled for deletion\n", _wid, _wid, nCustomPage)); _fnUpdateCallback(_wid, TRUE, _pUserData); } else if (eLean == _PropStoreInfo.GetRecordFormat()) { ciDebugOut((DEB_PSBACKUP, "Wid %d (0x%x) in page %d scheduled for deletion\n", _wid, _wid, nCustomPage)); _fnUpdateCallback(_wid, TRUE, _pUserData); } } if ( _pRec->IsFreeRecord() && _pRec->IsValidLength(_wid, _cRecPerPage) ) { _cRec = _pRec->CountRecords(); } else { _cRec = 1; } // // coalesce any adjacent free blocks. // CBorrowed BorrowedNext( *_pPhysStore, _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); WORKID widNext = _wid + _cRec; BOOL fCoalesced = FALSE; #if CIDBG WCHAR wszDeletedWids[4096]; wszDeletedWids[0] = 0; short cWritten = 0; #endif // CIDBG while ( widNext % _cRecPerPage != 0 ) { BorrowedNext.Set( widNext ); COnDiskPropertyRecord * precNext = BorrowedNext.Get(); // Schedule the wid for deletion or re-filtering as appropriate if (precNext->IsInUse()) break; // compute i-th custom page based on other params nCustomPage = nLargePage*cCustomPagesPerLargePage + (_PropStoreInfo.RecordSize()*sizeof(DWORD)*(widNext%_PropStoreInfo.RecordsPerPage())) / _xPSBkpStrm->PageSize(); if (_pageTable.LookUp(nCustomPage)) { #if CIDBG == 1 BOOL fDump = TRUE; #endif // CIDBG if (eNormal == _PropStoreInfo.GetRecordFormat() && precNext->ToplevelBlock() == widNext) { precNext->ClearToplevelField(); _fnUpdateCallback(widNext, TRUE, _pUserData); } else if (eLean == _PropStoreInfo.GetRecordFormat()) { _fnUpdateCallback(widNext, TRUE, _pUserData); } #if CIDBG == 1 else fDump = FALSE; if (fDump) { // Writing individual wids to debugout is // overwhelming it. So batch up a few at a time. WCHAR szbuff[12]; swprintf(szbuff, L"%d,", widNext); wcscat(wszDeletedWids, szbuff); cWritten++; if (cWritten % 32 == 0) { ciDebugOut((DEB_PSBACKUP, "Scheduled the following %d wids for deletion\n", cWritten)); ciDebugOut((DEB_PSBACKUP, "%ws\n", wszDeletedWids)); cWritten = 0; wszDeletedWids[0] = 0; } } #endif // CIDBG } if ( precNext->IsFreeRecord() && precNext->IsValidLength(_wid, _cRecPerPage) ) _cRec += precNext->CountRecords(); else if ( !precNext->IsInUse() ) _cRec += 1; else break; fCoalesced = TRUE; widNext = _wid + _cRec; BorrowedNext.Release(); } #if CIDBG if (cWritten > 0 && cWritten < 32) { ciDebugOut((DEB_PSBACKUP, "Scheduled the following %d wids for deletion\n", cWritten)); ciDebugOut((DEB_PSBACKUP, "%ws\n", wszDeletedWids)); cWritten = 0; wszDeletedWids[0] = 0; } #endif // CIDBG if (fCoalesced) { ciDebugOut(( DEB_PROPSTORE, "PROPSTORE: Coalesced free records 0x%X(%d)\n", _wid, _cRec )); } SetFree(); } _wid += _cRec; } } } //+--------------------------------------------------------------------------- // // Member: CPropertyStoreRecovery::Pass2 // // Synopsis: Pass2 of the recovery phase. // // History: 4-10-96 srikants Created // // NTRAID#DB-NTBUG9-84467-2000/07/31-dlee Indexing Service property store doesn't checked for orphaned overflow chains // //---------------------------------------------------------------------------- void CPropertyStoreRecovery::Pass2() { ciDebugOut(( DEB_WARN, "PROPSTORE: Recovery Pass2\n" )); if ( _pPhysStore->PageSize() > 0 ) { ULONG cLargePages = _pPhysStore->PageSize() / (COMMON_PAGE_SIZE / CI_PAGE_SIZE); // // Check each top-level record for consistency // CBorrowed Borrowed( *_pPhysStore, _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); _wid = 1; while ( TRUE ) { if ( _propStore._fAbort ) { ciDebugOut(( DEB_WARN, "Stopping Restore because of abort\n" )); THROW( CException(STATUS_TOO_LATE) ); } // // End of file? // ULONG nLargePage = _wid / _PropStoreInfo.RecordsPerPage(); if ( nLargePage >= cLargePages ) break; // // Valid record. // Borrowed.Release(); Borrowed.Set( _wid ); CLockRecordForWrite recLock( _propStore, _wid ); _pRec = Borrowed.Get(); _cRec = _pRec->CountRecords(); // There is no overflow chaining involved in a property store // made up of lean records, so check that only for normal records. if ( _pRec->IsNormalTopLevel() ) { if ( !CheckOverflowChain() ) { // // Free up this workid. // _cInconsistencies++; FreeChain(); } } _wid += _cRec; } } } //+--------------------------------------------------------------------------- // // Member: CPropertyStoreRecovery::Complete // // Synopsis: Complete the recovery operation. // // History: 4-10-96 srikants Created // //---------------------------------------------------------------------------- void CPropertyStoreRecovery::Complete() { // // Now chain all the free lists together and remove entries that // are beyond _widMax. // WORKID widFreeListHead = 0; WORKID widFreeListTail = 0; COnDiskPropertyRecord * prec = 0; COnDiskPropertyRecord * precPrev = 0; CBorrowed Borrowed( *_pPhysStore, _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); CBorrowed BorrowedPrev( *_pPhysStore, _PropStoreInfo.RecordsPerPage(), _PropStoreInfo.RecordSize() ); for (unsigned cSize = _PropStoreInfo.RecordsPerPage(); cSize >= 1; cSize-- ) { WORKID wid = _aFreeBlocks[ cSize ]; // Skip to the first valid block in the free list while ( wid != 0 && wid > _widMax ) { Borrowed.Set( wid ); prec = Borrowed.Get(); ciDebugOut(( DEB_PROPSTORE, " Tossing free entry %x(%d)\n", wid, prec->CountRecords() )); wid = prec->GetNextFreeRecord(); prec->ClearAll( _PropStoreInfo.RecordSize() ); Borrowed.Release(); } _aFreeBlocks[ cSize ] = wid; if ( wid == 0 ) continue; Borrowed.Set( wid ); prec = Borrowed.Get(); // // Found a valid wid. Splice this list to the previous list // if (widFreeListHead == 0) widFreeListHead = wid; else { Win4Assert( widFreeListTail != 0 && precPrev != 0 ); prec->SetPreviousFreeRecord( widFreeListTail ); precPrev->SetNextFree( wid, cSize ); } WORKID widPrev = widFreeListTail; widFreeListTail = wid; while (wid != 0) { BorrowedPrev.Release(); BorrowedPrev = Borrowed; precPrev = prec; Borrowed.Set( wid ); prec = Borrowed.Get(); while ( wid != 0 && wid > _widMax ) { // // Remove an entry > widMax from the list // ciDebugOut(( DEB_PROPSTORE, " Tossing free entry %x(%d)\n", wid, prec->CountRecords() )); wid = prec->GetNextFreeRecord(); precPrev->SetNextFree( wid, prec->GetNextFreeSize() ); prec->ClearAll( _PropStoreInfo.RecordSize() ); Borrowed.Release(); if (wid != 0) { Borrowed.Set( wid ); prec = Borrowed.Get(); } } if (wid == 0) continue; prec->SetPreviousFreeRecord( widPrev ); widFreeListTail = wid; widPrev = wid; wid = prec->GetNextFreeRecord(); } BorrowedPrev.Release(); BorrowedPrev = Borrowed; precPrev = prec; } ciDebugOut(( DEB_WARN, "PROPSTORE: 0x%x top level, widFreeList = 0x%x : 0x%x, widMax = 0x%x\n", _cTopLevel, widFreeListHead, widFreeListTail, _widMax )); if ( _cInconsistencies ) { ciDebugOut(( DEB_WARN, "PROPSTORE: Inconsistencies= 0x%X; Records forcefully freed= 0x%X\n", _cInconsistencies, _cForceFreed )); } _PropStoreInfo.SetRecordsInUse( _cTopLevel ); _PropStoreInfo.SetMaxWorkId( _widMax ); _PropStoreInfo.SetFreeListHead( widFreeListHead ); _PropStoreInfo.SetFreeListTail( widFreeListTail ); _propStore._aFreeBlocks = _aFreeBlocks; _aFreeBlocks = 0; // Don't flush here. We will flush both the prop stores from the manager // after LongInit has been performed on both. } //+--------------------------------------------------------------------------- // // Member: CPropertyStoreRecovery::DoRecovery // // Synopsis: Do the recovery operation // // History: 4-10-96 srikants Created // 6-13-97 KrishnaN Added pass 0 to repair propstore from bkp. // //---------------------------------------------------------------------------- void CPropertyStoreRecovery::DoRecovery() { NTSTATUS status = STATUS_SUCCESS; TRY { CLock mtxLock( _propStore._mtxWrite ); Pass0(); Pass1(); Pass2(); Complete(); } CATCH( CException, e ) { ciDebugOut(( DEB_ERROR, "Error 0x%X doing PropertyStore restore\n", e.GetErrorCode() )); status = e.GetErrorCode(); } END_CATCH if ( STATUS_SUCCESS != status ) { if ( STATUS_ACCESS_VIOLATION == status ) { Win4Assert ( 0 != _propStore._pStorage ); _propStore._pStorage->ReportCorruptComponent( L"PropertyCache1" ); status = CI_CORRUPT_CATALOG; } THROW( CException( status ) ); } }