4935 lines
158 KiB
C++
4935 lines
158 KiB
C++
//+---------------------------------------------------------------------------
|
|
//
|
|
// 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 ) );
|
|
}
|
|
|
|
}
|