Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1074 lines
24 KiB

// FreeList.cpp -- Implementation for class CFreeList and interface IFreelist
#include "StdAfx.h"
HRESULT CFreeList::CreateFreeList
(IITFileSystem *pITFS, CULINT cbPreallocated, IITFreeList **ppITFreeList)
{
CFreeList *pfl = New CFreeList(NULL);
return FinishSetup(pfl? pfl->m_ImpIFreeList.InitNew(pITFS, cbPreallocated)
: STG_E_INSUFFICIENTMEMORY,
pfl, IID_IFreeListManager, (PPVOID)ppITFreeList
);
}
HRESULT CFreeList::CreateAndSizeFreeList
(IITFileSystem *pITFS, CULINT cbPreallocated, UINT cSlots, IITFreeList **ppITFreeList)
{
CFreeList *pfl = New CFreeList(NULL);
return FinishSetup(pfl? pfl->m_ImpIFreeList.InitNew(pITFS, cbPreallocated, cSlots)
: STG_E_INSUFFICIENTMEMORY,
pfl, IID_IFreeListManager, (PPVOID)ppITFreeList
);
}
HRESULT CFreeList::LoadFreeList(IITFileSystem *pITFS, IITFreeList **ppITFreeList)
{
CFreeList *pfl = New CFreeList(NULL);
return FinishSetup(pfl? pfl->m_ImpIFreeList.InitLoad(pITFS)
: STG_E_INSUFFICIENTMEMORY,
pfl, IID_IFreeListManager, (PPVOID) ppITFreeList
);
}
HRESULT CFreeList::AttachFreeList(IITFileSystem *pITFS, IITFreeList **ppITFreeList)
{
CFreeList *pfl = New CFreeList(NULL);
return FinishSetup(pfl? pfl->m_ImpIFreeList.LazyInitNew(pITFS)
: STG_E_INSUFFICIENTMEMORY,
pfl, IID_IFreeListManager, (PPVOID) ppITFreeList
);
}
CFreeList::CImpIFreeList::CImpIFreeList(CFreeList *pBackObj, IUnknown *punkOuter)
:IITFreeList(pBackObj, punkOuter)
{
m_fInitialed = FALSE;
m_fIsDirty = FALSE;
m_cBlocks = 0;
m_cBlocksMax = 0;
m_cbSpace = 0;
m_cbLost = 0;
m_paFreeItems = NULL;
m_pITFS = NULL;
DEBUGCODE(m_cbPreallocated = 0; )
}
CFreeList::CImpIFreeList::~CImpIFreeList(void)
{
if (!m_fInitialed)
{
RonM_ASSERT(!m_paFreeItems);
RonM_ASSERT(!m_cBlocksMax);
RonM_ASSERT(!m_cBlocks);
RonM_ASSERT(!m_cbSpace.NonZero());
RonM_ASSERT(!m_cbLost.NonZero());
RonM_ASSERT(!m_fIsDirty);
RonM_ASSERT(!m_pITFS);
return;
}
if (m_fIsDirty)
{
if (m_pITFS) RonM_ASSERT(m_paFreeItems);
else RonM_ASSERT(FALSE);
}
if (m_paFreeItems) delete [] m_paFreeItems;
if (m_pITFS) m_pITFS->Release();
}
// IPersist Method:
HRESULT STDMETHODCALLTYPE CFreeList::CImpIFreeList::GetClassID(
/* [out] */ CLSID __RPC_FAR *pClassID)
{
*pClassID = CLSID_IFreeListManager_1;
return NO_ERROR;
}
// IPersistStreamInit Methods:
HRESULT STDMETHODCALLTYPE CFreeList::CImpIFreeList::IsDirty(void)
{
return m_fIsDirty? S_OK : S_FALSE;
}
HRESULT STDMETHODCALLTYPE CFreeList::CImpIFreeList::Load(
/* [in] */ LPSTREAM pStm)
{
// This routine sets the initial state for the free list from
// a stream. It assumes that we've previously called
// Save on this stream at the current seek position.
// We assume we'll find:
//
// -- m_cBlocksMax
// -- m_cBlocks
// -- m_cbSpace
// -- m_cbLost
// -- The first m_cBlocks items from m_paFreeItems
//
// Note: This ordering is part of the interface spec!
RonM_ASSERT(!m_fInitialed);
if (m_fInitialed)
return E_UNEXPECTED;
ULONG cItemsMax, cItemsActual;
CULINT cbSpace, cbLost;
CSyncWith sw(m_cs);
HRESULT hr = ReadIn(pStm, &cItemsMax, sizeof(cItemsMax));
if (SUCCEEDED(hr) && !cItemsMax)
hr = STG_E_DOCFILECORRUPT;
if (SUCCEEDED(hr))
hr = ReadIn(pStm, &cItemsActual, sizeof(cItemsActual));
if (SUCCEEDED(hr))
hr = ReadIn(pStm, &cbSpace, sizeof(cbSpace));
if (SUCCEEDED(hr))
hr = ReadIn(pStm, &cbLost, sizeof(cbLost));
if (SUCCEEDED(hr))
{
RonM_ASSERT(!m_paFreeItems);
m_paFreeItems = New FREEITEM[cItemsMax];
if (!m_paFreeItems)
hr = E_OUTOFMEMORY;
else
if (cItemsActual)
{
UINT cbActual = sizeof(FREEITEM) * cItemsActual;
hr = ReadIn(pStm, m_paFreeItems, cbActual);
if (!SUCCEEDED(hr))
{
delete [] m_paFreeItems;
m_paFreeItems = NULL;
return hr;
}
}
}
if (SUCCEEDED(hr))
{
m_cBlocks = cItemsActual;
m_cBlocksMax = cItemsMax;
m_cbSpace = cbSpace;
m_cbLost = cbLost;
m_fIsDirty = FALSE;
m_fInitialed = TRUE;
}
return hr;
}
HRESULT CFreeList::CImpIFreeList::ReadIn(LPSTREAM pStm, PVOID pv, ULONG cb)
{
ULONG cbRead;
HRESULT hr = pStm->Read(pv, cb, &cbRead);
if (!SUCCEEDED(hr))
return hr;
// In the context of the ITSS files the following
// assert ought to be true.
RonM_ASSERT(cbRead == cb);
return (cbRead != cb)? STG_E_READFAULT : NO_ERROR;
}
HRESULT CFreeList::CImpIFreeList::WriteOut(LPSTREAM pStm, PVOID pv, ULONG cb)
{
ULONG cbWritten;
HRESULT hr = pStm->Write(pv, cb, &cbWritten);
if (!SUCCEEDED(hr))
return hr;
// In the context of the ITSS files the following
// assert ought to be true.
RonM_ASSERT(cbWritten == cb);
return (cbWritten != cb)? STG_E_WRITEFAULT : NO_ERROR;
}
HRESULT STDMETHODCALLTYPE CFreeList::CImpIFreeList::Save(
/* [in] */ LPSTREAM pStm,
/* [in] */ BOOL fClearDirty)
{
// This routine stores the state of the free list in a stream.
// Here's the order in which we write our state:
//
// -- m_cBlocksMax
// -- m_cBlocks
// -- m_cbSpace
// -- m_cbLost
// -- The first m_cBlocks items from m_paFreeItems
//
// Note: This ordering is part of the interface spec!
RonM_ASSERT(m_fInitialed);
RonM_ASSERT(!m_cBlocks || m_paFreeItems);
RonM_ASSERT(m_cBlocksMax);
RonM_ASSERT(m_fIsDirty);
CSyncWith sw(m_cs);
if (fClearDirty)
m_fIsDirty = FALSE;
HRESULT hr = WriteOut(pStm, &m_cBlocksMax, sizeof(m_cBlocksMax));
if (SUCCEEDED(hr))
hr = WriteOut(pStm, &m_cBlocks, sizeof(m_cBlocks));
if (SUCCEEDED(hr))
hr = WriteOut(pStm, &m_cbSpace, sizeof(m_cbSpace));
if (SUCCEEDED(hr))
hr = WriteOut(pStm, &m_cbLost, sizeof(m_cbLost));
if (SUCCEEDED(hr) && m_cBlocks)
{
UINT cbActual = sizeof(FREEITEM) * m_cBlocks;
hr = WriteOut(pStm, m_paFreeItems, cbActual);
}
if (FAILED(hr))
m_fIsDirty = TRUE;
return hr;
}
HRESULT STDMETHODCALLTYPE CFreeList::CImpIFreeList::GetSizeMax(
/* [out] */ ULARGE_INTEGER __RPC_FAR *pCbSize)
{
RonM_ASSERT(m_fInitialed);
if (!m_fInitialed)
return E_UNEXPECTED;
pCbSize->QuadPart = sizeof(m_cBlocksMax) + sizeof(m_cBlocks)
+ sizeof(m_cbSpace)
+ sizeof(m_cbLost)
+ sizeof(FREEITEM) * m_cBlocks;
return NO_ERROR;
}
HRESULT STDMETHODCALLTYPE CFreeList::CImpIFreeList::InitNew()
{
RonM_ASSERT(FALSE);
return E_NOTIMPL;
}
// IFreeList Methods:
HRESULT STDMETHODCALLTYPE CFreeList::CImpIFreeList::InitNew
(IITFileSystem *pITFS, CULINT cbPreallocated)
{
return InitNew(pITFS, cbPreallocated, DEFAULT_ENTRIES_MAX);
}
HRESULT STDMETHODCALLTYPE CFreeList::CImpIFreeList::InitNew
(IITFileSystem *pITFS, CULINT cbPreallocated, UINT cEntriesMax)
{
RonM_ASSERT(!m_fInitialed);
RonM_ASSERT(!m_paFreeItems);
RonM_ASSERT(pITFS);
m_pITFS = pITFS;
m_pITFS->AddRef();
if (m_fInitialed)
return E_UNEXPECTED;
if (!cEntriesMax)
return E_INVALIDARG;
m_paFreeItems = New FREEITEM[cEntriesMax];
if (!m_paFreeItems)
return E_OUTOFMEMORY;
RonM_ASSERT(!m_fIsDirty);
RonM_ASSERT(!m_cBlocks);
RonM_ASSERT(!m_cbLost.NonZero());
m_fInitialed = TRUE;
m_cBlocksMax = cEntriesMax;
m_cbSpace = cbPreallocated;
m_fIsDirty = TRUE;
DEBUGCODE( m_cbPreallocated = cbPreallocated; )
return NO_ERROR;
}
HRESULT STDMETHODCALLTYPE CFreeList::CImpIFreeList::InitLoad(IITFileSystem *pITFS)
{
m_pITFS = pITFS;
m_pITFS->AddRef();
return ConnectToStream();
}
UINT CFreeList::CImpIFreeList::HighestEntryLEQ(CULINT &ullBase)
{
// BugBug(Optimize): 97/01/30
//
// The code below is a close paraphrase of the original free list code from the
// MV file system. It's a lot more complicated than it needs to be. For now I've
// resisted the temptation to rewrite it completely because that might introduce
// bugs. However when the dust settles a bit, I'm going to clean up this code.
INT iLo=0;
INT iHi=m_cBlocks-1;
INT iSpan=iHi-iLo+1;
INT iFound=-1;
while (iSpan>0)
{
INT iMid=(iLo+iHi)/2;
if (ullBase > m_paFreeItems[iMid].ullOffset)
{
if (iSpan==1)
{
iFound=iLo;
break;
}
iLo=min(iHi,iMid+1);
}
else
{ if (iSpan==1)
{
iFound=iLo-1;
break;
}
iHi=max(iLo,iMid-1);
}
iSpan=iHi-iLo+1;
}
return iFound;
}
HRESULT STDMETHODCALLTYPE CFreeList::CImpIFreeList::PutFreeSpace
(CULINT ullBase, CULINT ullCB)
{
RonM_ASSERT(m_fInitialed);
if (!m_fInitialed)
return E_UNEXPECTED;
HRESULT hr = S_OK;
if (!m_paFreeItems && m_pITFS)
hr = ConnectToStream();
if (hr != S_OK) return hr;
RonM_ASSERT(m_paFreeItems);
if (!ullCB.NonZero())
return NO_ERROR;
RonM_ASSERT(ullBase >= m_cbPreallocated);
CSyncWith sw(m_cs);
RonM_ASSERT(ullCB <= m_cbSpace);
UINT cItems = m_cBlocks;
BOOL fFullList = cItems == m_cBlocksMax;
if (!cItems)
{
// The list is empty.
m_paFreeItems->ullOffset = ullBase;
m_paFreeItems->ullCB = ullCB;
m_cBlocks++;
m_fIsDirty = TRUE;
return NO_ERROR;
}
INT iFound = HighestEntryLEQ(ullBase);
cItems = m_cBlocks - iFound - 1;
// wFound == -1, insert at beginning,
// else insert _after_ wFound
PFREEITEM pfi = m_paFreeItems + (iFound+1);
PFREEITEM pfiPrev = NULL;
if (iFound!=-1)
pfiPrev = pfi - 1;
if ( pfiPrev
&& ullBase == (pfiPrev->ullOffset + pfiPrev->ullCB)
)
{ // Merge with previous
pfiPrev->ullCB += ullCB;
if (pfiPrev->ullOffset + pfiPrev->ullCB == pfi->ullOffset)
{
// it fills a hole, merge with next
pfiPrev->ullCB += pfi->ullCB;
// Scoot all next blocks back by one if any
if (cItems)
{
MoveMemory(pfi, pfi+1, sizeof(FREEITEM) * cItems);
m_cBlocks--;
}
}
m_fIsDirty = TRUE;
return NO_ERROR;
}
// Can we merge with the following block?
if (cItems && (ullBase + ullCB) == pfi->ullOffset)
{
pfi->ullOffset = ullBase;
pfi->ullCB += ullCB;
m_fIsDirty = TRUE;
return NO_ERROR;
}
// Can't merge with an existing block.
// So we have to insert this block into the list.
if (fFullList)
{
// The list is already full. So before we add a new
// entry, we've got to remove something. The space
// represented by the item we discard will be lost
// forever. So we first look for the smallest item
// in the list.
ULARGE_INTEGER uli;
uli. LowPart = 0xFFFFFFFF;
uli.HighPart = 0xFFFFFFFF;
CULINT ullSmallest = CULINT(uli);
WORD wSmallestBlock = (WORD)-1;
PFREEITEM pfiTemp = m_paFreeItems;
WORD wBlockTemp = 0;
for (wBlockTemp=0; wBlockTemp < m_cBlocks; wBlockTemp++, pfiTemp++)
{
if (ullSmallest > pfiTemp->ullCB)
{
ullSmallest = pfiTemp->ullCB;
wSmallestBlock=wBlockTemp;
}
}
// If our new block is smaller than the smallest block in the list,
// just discard the new block.
if (ullCB < ullSmallest)
{
m_cbLost += ullCB;
m_fIsDirty = TRUE;
return NO_ERROR;
}
m_cbLost += ullSmallest;
// Now we're going to remove the smallest block. If that
// block is at the end of the list, we just decrement the
// count of active blocks.
if (wSmallestBlock != m_cBlocksMax - 1)
{
// When it isn't at the end, we've got to slide subsequent
// list items down by one position.
MoveMemory(m_paFreeItems + wSmallestBlock,
m_paFreeItems + wSmallestBlock + 1,
sizeof(FREEITEM) * (m_cBlocksMax - wSmallestBlock - 1)
);
}
m_cBlocks--;
cItems--;
// If the block we removed was lower than our insertion position,
// we must adjust our insertion postion down by one.
if ((int)wSmallestBlock <= iFound)
{
pfi = pfiPrev;
cItems++;
}
}
// Insert Item
if (cItems)
MoveMemory(pfi + 1, pfi, sizeof(FREEITEM) * cItems);
pfi->ullOffset = ullBase;
pfi->ullCB = ullCB;
m_cBlocks++;
m_fIsDirty = TRUE;
return NO_ERROR;
}
HRESULT STDMETHODCALLTYPE CFreeList::CImpIFreeList::GetFreeSpace
(CULINT *pullBase, CULINT *pullcb)
{
CSyncWith sw(m_cs);
RonM_ASSERT(m_fInitialed);
HRESULT hr = S_OK;
if (!m_paFreeItems && m_pITFS)
hr = ConnectToStream();
if (hr != S_OK) return hr;
RonM_ASSERT(m_paFreeItems);
CULINT ullCB = *pullcb;
ULARGE_INTEGER uli;
uli. LowPart = DWORD(-1);
uli.HighPart = DWORD(-1);
CULINT ullMinDiff(uli);
UINT cBlocks = m_cBlocks;
PFREEITEM pfi = m_paFreeItems;
UINT iBlock;
UINT iBlockBest = UINT(-1);
// The loop below does a best-fit sweep through the
// blocks in the list.
// BugBug(Optimize): 97/01/30
//
// Best-fit space management tends toward maximum fragmentation. So we
// might do better with an different selection rule. Such a change would
// mean that the return value of *pullcb will sometimes be larger than its
// entry value. That means that all the clients of this interface must
// keep track of both the offset and the size returned!
for (iBlock= 0; iBlock < m_cBlocks; iBlock++, pfi++)
if (ullCB <= pfi->ullCB)
{
CULINT ullDiff;
ullDiff= pfi->ullCB - ullCB;
if (ullMinDiff > ullDiff)
{
ullMinDiff = ullDiff;
iBlockBest = iBlock;
}
}
if (iBlockBest == UINT(-1)) // Did we find any items that were big enough?
{
// If not, we'll try to allocate from the end of the active space.
CULINT ullSpaceMax;
ullSpaceMax = 0 - *pullcb;
if (ullSpaceMax < *pullcb)
return S_FALSE;
*pullBase = m_cbSpace;
m_cbSpace += *pullcb;
m_fIsDirty = TRUE;
return S_OK;
}
pfi= m_paFreeItems + iBlockBest;
*pullBase = pfi->ullOffset;
// Note that we don't overwrite *pullcb in this code. However that may change in
// a future implementation. For now we return exactly the space requested.
// That means we must record any trailing space left over from this item.
//
// The spec rule is that *pullcb's return value will be >= it's entry value.
pfi->ullOffset += ullCB;
pfi->ullCB -= ullCB;
if (!(pfi->ullCB.NonZero()))
{
// We matched the item size exactly.
if (iBlockBest < m_cBlocks - 1) // Was it the last entry?
{
// If not, we've got to slide down the entries which followed it.
MoveMemory(pfi, pfi+1, sizeof(FREEITEM) * (m_cBlocks - iBlockBest - 1));
}
m_cBlocks--;
}
m_fIsDirty = TRUE;
return NO_ERROR;
}
HRESULT STDMETHODCALLTYPE CFreeList::CImpIFreeList::GetFreeSpaceAt
(CULINT ullBase, CULINT *pullcb)
{
CSyncWith sw(m_cs);
HRESULT hr = S_OK;
if (!m_paFreeItems && m_pITFS)
hr = ConnectToStream();
if (hr != S_OK) return hr;
CULINT ullcbReq;
ullcbReq = *pullcb;
if (ullBase == m_cbSpace)
{
CULINT ullcbAvailable;
ullcbAvailable = 0 - m_cbSpace;
HRESULT hr = S_OK;
if (ullcbAvailable >= ullcbReq)
m_cbSpace += ullcbReq;
else hr = S_FALSE;
m_fIsDirty = TRUE;
return hr;
}
UINT iFound = HighestEntryLEQ(ullBase);
if (iFound == UINT(-1))
return S_FALSE;
PFREEITEM pfi= m_paFreeItems + iFound;
CULINT ullLimit;
ullLimit = pfi->ullOffset + pfi->ullCB;
if (ullLimit <= ullBase)
return S_FALSE;
CULINT ullChunk;
ullChunk = ullLimit - ullBase;
if (ullChunk < ullcbReq)
{
HRESULT hr = S_OK;
if (ullLimit == m_cbSpace)
{
CULINT ullcbAvailable;
ullcbAvailable = 0 - m_cbSpace;
if (ullcbAvailable >= ullcbReq - ullChunk)
m_cbSpace += ullcbReq - ullChunk;
else hr = S_FALSE;
}
else hr = S_FALSE;
if (hr == S_FALSE)
return S_FALSE;
}
pfi->ullCB = ullBase - pfi->ullOffset; // Record the prefix space.
if (!(pfi->ullCB).NonZero())
{
// We've used up the entire block.
// Now we need to remove it from the list.
if (iFound < m_cBlocks - 1)
MoveMemory(pfi, pfi + 1, sizeof(FREEITEM) * (m_cBlocks - iFound - 1));
m_cBlocks--;
}
if (ullChunk > ullcbReq) // Check for trailing space in the block
{
CULINT ullBaseTrailing, ullcbTrailing;
ullBaseTrailing = ullBase + ullcbReq;
ullcbTrailing = ullChunk - ullcbReq;
HRESULT hr =
PutFreeSpace(ullBaseTrailing, ullcbTrailing);
RonM_ASSERT(SUCCEEDED(hr));
}
m_fIsDirty = TRUE;
return NO_ERROR;
}
HRESULT STDMETHODCALLTYPE CFreeList::CImpIFreeList::GetEndingFreeSpace
(CULINT *pullBase, CULINT *pullcb)
{
HRESULT hr = S_OK;
if (!m_paFreeItems && m_pITFS)
hr = ConnectToStream();
if (hr != S_OK) return hr;
CULINT ullcbReq;
ullcbReq = *pullcb;
CSyncWith sw(m_cs);
if (m_cBlocks)
{
PFREEITEM pfi = m_paFreeItems + m_cBlocks - 1;
CULINT ullBase, ullcb;
ullBase = pfi->ullOffset;
ullcb = pfi->ullCB;
if (m_cbSpace == ullBase + ullcb)
{
if (ullcbReq > ullcb)
{
CULINT ullcbAvailable;
ullcbAvailable = 0 - ullBase;
if (ullcbAvailable < ullcbReq)
return S_FALSE;
m_cbSpace += ullcbReq - ullcb;
}
m_cBlocks--;
*pullBase = ullBase;
if (ullcb > ullcbReq)
{
CULINT ullBaseResidue, ullcbResidue;
ullBaseResidue = ullBase + ullcbReq;
ullcbResidue = ullcb - ullcbReq;
#ifdef _DEBUG
HRESULT hr =
#endif // _DEBUG
PutFreeSpace(ullBaseResidue, ullcbResidue);
RonM_ASSERT(SUCCEEDED(hr));
}
m_fIsDirty = TRUE;
return NO_ERROR;
}
}
CULINT ullcbAvailable;
ullcbAvailable = 0 - m_cbSpace;
if (ullcbAvailable < ullcbReq)
return S_FALSE;
*pullBase = m_cbSpace;
m_cbSpace += ullcbReq;
m_fIsDirty = TRUE;
return NO_ERROR;
}
HRESULT STDMETHODCALLTYPE CFreeList::CImpIFreeList::GetStatistics
(CULINT *pcbLost, CULINT *pcbSpace)
{
HRESULT hr = S_OK;
if (!m_paFreeItems && m_pITFS)
hr = ConnectToStream();
if (hr != S_OK) return hr;
*pcbLost = m_cbLost;
*pcbSpace = m_cbSpace;
return hr;
}
HRESULT STDMETHODCALLTYPE CFreeList::CImpIFreeList::SetFreeListEmpty()
{
RonM_ASSERT(m_fInitialed);
RonM_ASSERT(m_pITFS);
HRESULT hr = S_OK;
if (!m_cBlocksMax) hr = ConnectToStream();
if (SUCCEEDED(hr))
{
m_cBlocks = 0;
m_fIsDirty = TRUE;
}
return hr;
}
HRESULT STDMETHODCALLTYPE CFreeList::CImpIFreeList::SetSpaceSize(ULARGE_INTEGER uliCbSpace)
{
RonM_ASSERT(m_fInitialed);
RonM_ASSERT(m_pITFS);
m_cbSpace = CULINT(uliCbSpace);
m_fIsDirty = TRUE;
return S_OK;
}
HRESULT STDMETHODCALLTYPE CFreeList::CImpIFreeList::LazyInitNew(IITFileSystem *pITFS)
{
RonM_ASSERT(!m_fInitialed);
RonM_ASSERT(!m_paFreeItems);
RonM_ASSERT(pITFS);
pITFS->AddRef();
m_pITFS = pITFS;
m_fIsDirty = FALSE;
m_fInitialed = TRUE;
return S_OK;
}
HRESULT STDMETHODCALLTYPE CFreeList::CImpIFreeList::RecordFreeList()
{
RonM_ASSERT(m_fInitialed);
RonM_ASSERT(!m_cBlocks || m_paFreeItems);
RonM_ASSERT(m_pITFS);
if (!m_pITFS) return E_FAIL;
HRESULT hr = S_OK;
if (m_fIsDirty)
{
/*
One of the complications here is that the act of recording the free
list may alter the free list. That's because we may need to allocate
or reallocate space within the file system to do the write operations.
The best case, of course, is the situation where the Free List stream
is already exactly the right size to hold the free list information.
In the other possible situations the stream may be empty, or larger than
or smaller than the free list image. Those situations cause allocations
and thus affect the free list. We deal with those possibilities by
1. Only allowing the stream to grow from its initial size.
2. Allowing a few iterations to take care of cases where the allocation
operation may grow the free list or change some of its entries and
thus make it "dirty" again.
To catch flaws in this logic we trigger an assertion and return E_FAIL
if the iteration count goes beyond four. If the interactions between the
file system and the free list changes substantially, we may need to
change the iteration limit upward.
*/
CULINT ullSizeMax;
CULINT ullSizeCurrent;
ULARGE_INTEGER cbFreeList;
hr = GetSizeMax(&cbFreeList);
RonM_ASSERT(SUCCEEDED(hr));
ullSizeMax = cbFreeList;
ullSizeCurrent = 0;
IStreamITEx *pStrmFreeList = NULL;
hr = m_pITFS->OpenStream(NULL, L"F", STGM_READWRITE, &pStrmFreeList);
if (!SUCCEEDED(hr)) return hr;
UINT iterations = 0;
while (m_fIsDirty) // m_fIsDirty is set TRUE when the free list changes size or content.
// It is set FALSE by the Save call below.
{
hr = GetSizeMax(&cbFreeList);
RonM_ASSERT(SUCCEEDED(hr));
if (ullSizeMax < cbFreeList)
ullSizeMax = cbFreeList;
if (ullSizeCurrent < ullSizeMax)
{
ullSizeCurrent = ullSizeMax;
hr = pStrmFreeList->SetSize(ullSizeMax.Uli());
if (!SUCCEEDED(hr)) break;
}
LARGE_INTEGER uli;
uli.LowPart = 0;
uli.HighPart = 0;
hr = pStrmFreeList->Seek(uli, STREAM_SEEK_SET, NULL);
if (!SUCCEEDED(hr)) break;
hr = Save(pStrmFreeList, TRUE); // TRUE ==> Reset the dirty flag
if (!SUCCEEDED(hr)) break;
hr = GetSizeMax(&cbFreeList);
RonM_ASSERT(SUCCEEDED(hr));
if (ullSizeMax < cbFreeList)
ullSizeMax = cbFreeList;
hr = pStrmFreeList->Flush();
if (!SUCCEEDED(hr)) break;
hr = GetSizeMax(&cbFreeList);
RonM_ASSERT(SUCCEEDED(hr));
if (ullSizeMax < cbFreeList)
ullSizeMax = cbFreeList;
if (++iterations > 4)
{
RonM_ASSERT(FALSE); // This has become an infinite loop.
return E_FAIL;
}
}
pStrmFreeList->Release();
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE CFreeList::CImpIFreeList::ConnectToStream()
{
IStreamITEx *pStrmFreeList = NULL;
HRESULT hr = m_pITFS->OpenStream(NULL, L"F", STGM_READWRITE, &pStrmFreeList);
if (!SUCCEEDED(hr)) return hr;
LARGE_INTEGER uli;
uli.LowPart = 0;
uli.HighPart = 0;
hr = pStrmFreeList->Seek(uli, STREAM_SEEK_SET, NULL);
if (SUCCEEDED(hr))
{
m_fInitialed = FALSE; // To satisfy the Load routine...
hr = Load(pStrmFreeList);
}
pStrmFreeList->Release();
return hr;
}