windows-nt/Source/XPSP1/NT/inetsrv/query/ntciutil/propstor.cxx

4935 lines
158 KiB
C++
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
//+---------------------------------------------------------------------------
//
// 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 <pch.cxx>
#pragma hdrstop
#include <cistore.hxx>
#include <rcstrmit.hxx>
#include <proprec.hxx>
#include <propstor.hxx>
#include <propiter.hxx>
#include <borrow.hxx>
#include <propobj.hxx>
#include <eventlog.hxx>
#include <psavtrak.hxx>
#include <catalog.hxx>
#include <imprsnat.hxx>
#include <mmstrm.hxx>
#include <cievtmsg.h>
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<PRcovStorageObj> & 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<CPropDesc> 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<PRcovStorageObj> 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<PMmStream> 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<PRcovStorageObj> xObj( dstStorage.QueryPropStore( 0, GetStoreLevel() ) );
CCopyRcovObject copyRcov( xObj.GetReference(), *_PropStoreInfo.GetRcovObj() );
copyRcov.DoIt();
XPtr<CPropertyStore> 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<IEnumString> 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<PMmStream> 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<BYTE> 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<ULONG> adSlots(cPages);
CDynArrayInPlace<void *> 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<ULONG> &pulPages,
CDynArrayInPlace<void *> &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 ) );
}
}