569 lines
16 KiB
C++
569 lines
16 KiB
C++
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
/* File: userbat.cpp
|
||
|
|
||
|
Description: Contains member function definitions for class
|
||
|
DiskQuotaUserBatch.
|
||
|
The DiskQuotaUserBatch object represents a batch update mechanism for
|
||
|
rapid update of multiple-user-object quota information. This class
|
||
|
takes advantage of the batching capabilities built into the NTIOAPI.
|
||
|
A user batch object is obtained through
|
||
|
IDiskQuotaControl::CreateUserBatch().
|
||
|
|
||
|
Revision History:
|
||
|
|
||
|
Date Description Programmer
|
||
|
-------- --------------------------------------------------- ----------
|
||
|
06/06/96 Initial creation. BrianAu
|
||
|
*/
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
#include "pch.h" // PCH
|
||
|
#pragma hdrstop
|
||
|
|
||
|
#include "userbat.h"
|
||
|
|
||
|
//
|
||
|
// Verify that build is UNICODE.
|
||
|
//
|
||
|
#if !defined(UNICODE)
|
||
|
# error This module must be compiled UNICODE.
|
||
|
#endif
|
||
|
|
||
|
//
|
||
|
// The NTFS quota write function can only handle a max of 64K of data
|
||
|
// in any one write operation. IssacHe recommended 60K as a comfortable
|
||
|
// limit.
|
||
|
//
|
||
|
const INT MAX_BATCH_BUFFER_BYTES = (1 << 10) * 60;
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
/* Function: DiskQuotaUserBatch::DiskQuotaUserBatch
|
||
|
|
||
|
Description: Constructor.
|
||
|
|
||
|
Arguments:
|
||
|
pFSObject - Address of File System object to be utilized by the
|
||
|
batching operations.
|
||
|
|
||
|
Returns: Nothing.
|
||
|
|
||
|
Revision History:
|
||
|
|
||
|
Date Description Programmer
|
||
|
-------- --------------------------------------------------- ----------
|
||
|
09/03/96 Initial creation. BrianAu
|
||
|
*/
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
DiskQuotaUserBatch::DiskQuotaUserBatch(
|
||
|
FSObject *pFSObject
|
||
|
) : m_cRef(0),
|
||
|
m_pFSObject(pFSObject)
|
||
|
{
|
||
|
DBGASSERT((NULL != m_pFSObject));
|
||
|
|
||
|
m_pFSObject->AddRef();
|
||
|
}
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
/* Function: DiskQuotaUserBatch::~DiskQuotaUserBatch
|
||
|
|
||
|
Description: Destructor.
|
||
|
|
||
|
Arguments: Destroys the batch object.
|
||
|
|
||
|
Returns: Nothing.
|
||
|
|
||
|
Revision History:
|
||
|
|
||
|
Date Description Programmer
|
||
|
-------- --------------------------------------------------- ----------
|
||
|
07/26/96 Initial creation. BrianAu
|
||
|
*/
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
DiskQuotaUserBatch::~DiskQuotaUserBatch(
|
||
|
VOID
|
||
|
)
|
||
|
{
|
||
|
Destroy();
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
/* Function: DiskQuotaUserBatch::QueryInterface
|
||
|
|
||
|
Description: Returns an interface pointer to the object's IUnknown or
|
||
|
IDiskQuotaUserBatch interface. Only IID_IUnknown and
|
||
|
IID_IDiskQuotaUserBatch are recognized. The object referenced by the
|
||
|
returned interface pointer is uninitialized. The recipient of the
|
||
|
pointer must call Initialize() before the object is usable.
|
||
|
|
||
|
Arguments:
|
||
|
riid - Reference to requested interface ID.
|
||
|
|
||
|
ppvOut - Address of interface pointer variable to accept interface ptr.
|
||
|
|
||
|
Returns:
|
||
|
NOERROR - Success.
|
||
|
E_NOINTERFACE - Requested interface not supported.
|
||
|
E_INVALIDARG - ppvOut argument was NULL.
|
||
|
|
||
|
Revision History:
|
||
|
|
||
|
Date Description Programmer
|
||
|
-------- --------------------------------------------------- ----------
|
||
|
06/06/96 Initial creation. BrianAu
|
||
|
*/
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
STDMETHODIMP
|
||
|
DiskQuotaUserBatch::QueryInterface(
|
||
|
REFIID riid,
|
||
|
LPVOID *ppvOut
|
||
|
)
|
||
|
{
|
||
|
HRESULT hResult = E_NOINTERFACE;
|
||
|
|
||
|
if (NULL == ppvOut)
|
||
|
return E_INVALIDARG;
|
||
|
|
||
|
*ppvOut = NULL;
|
||
|
|
||
|
if (IID_IUnknown == riid || IID_IDiskQuotaUserBatch == riid)
|
||
|
{
|
||
|
*ppvOut = this;
|
||
|
((LPUNKNOWN)*ppvOut)->AddRef();
|
||
|
hResult = NOERROR;
|
||
|
}
|
||
|
|
||
|
return hResult;
|
||
|
}
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
/* Function: DiskQuotaUserBatch::AddRef
|
||
|
|
||
|
Description: Increments object reference count.
|
||
|
|
||
|
Arguments: None.
|
||
|
|
||
|
Returns: New reference count value.
|
||
|
|
||
|
Revision History:
|
||
|
|
||
|
Date Description Programmer
|
||
|
-------- --------------------------------------------------- ----------
|
||
|
06/06/96 Initial creation. BrianAu
|
||
|
*/
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
STDMETHODIMP_(ULONG)
|
||
|
DiskQuotaUserBatch::AddRef(
|
||
|
VOID
|
||
|
)
|
||
|
{
|
||
|
ULONG ulReturn = m_cRef + 1;
|
||
|
|
||
|
DBGPRINT((DM_COM, DL_HIGH, TEXT("DiskQuotaUserBatch::AddRef, 0x%08X %d -> %d\n"),
|
||
|
this, ulReturn - 1, ulReturn));
|
||
|
|
||
|
InterlockedIncrement(&m_cRef);
|
||
|
|
||
|
return ulReturn;
|
||
|
}
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
/* Function: DiskQuotaUserBatch::Release
|
||
|
|
||
|
Description: Decrements object reference count. If count drops to 0,
|
||
|
object is deleted.
|
||
|
|
||
|
Arguments: None.
|
||
|
|
||
|
Returns: New reference count value.
|
||
|
|
||
|
Revision History:
|
||
|
|
||
|
Date Description Programmer
|
||
|
-------- --------------------------------------------------- ----------
|
||
|
06/06/96 Initial creation. BrianAu
|
||
|
*/
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
STDMETHODIMP_(ULONG)
|
||
|
DiskQuotaUserBatch::Release(
|
||
|
VOID
|
||
|
)
|
||
|
{
|
||
|
ULONG ulReturn = m_cRef - 1;
|
||
|
|
||
|
DBGPRINT((DM_COM, DL_HIGH, TEXT("DiskQuotaUserBatch::Release, 0x%08X %d -> %d\n"),
|
||
|
this, ulReturn + 1, ulReturn));
|
||
|
|
||
|
if (InterlockedDecrement(&m_cRef) == 0)
|
||
|
{
|
||
|
delete this;
|
||
|
ulReturn = 0;
|
||
|
}
|
||
|
return ulReturn;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
/* Function: DiskQuotaUserBatch::Destroy
|
||
|
|
||
|
Description: Destroys the contents of a user batch object and releases
|
||
|
its FSObject pointer.
|
||
|
|
||
|
Arguments: None.
|
||
|
|
||
|
Returns: Nothing.
|
||
|
|
||
|
Revision History:
|
||
|
|
||
|
Date Description Programmer
|
||
|
-------- --------------------------------------------------- ----------
|
||
|
06/06/96 Initial creation. BrianAu
|
||
|
*/
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
VOID
|
||
|
DiskQuotaUserBatch::Destroy(VOID)
|
||
|
{
|
||
|
//
|
||
|
// Remove and release all user object pointers from the batch list.
|
||
|
//
|
||
|
RemoveAll();
|
||
|
|
||
|
if (NULL != m_pFSObject)
|
||
|
{
|
||
|
//
|
||
|
// Release hold on File System object.
|
||
|
//
|
||
|
m_pFSObject->Release();
|
||
|
m_pFSObject = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
/* Function: DiskQuotaUserBatch::Add
|
||
|
|
||
|
Description: Adds an IDiskQuotaUser interface pointer to the batch list.
|
||
|
|
||
|
Arguments:
|
||
|
pUser - Address of IDiskQuotaUser interface.
|
||
|
|
||
|
Returns:
|
||
|
NOERROR - Success.
|
||
|
E_INVALIDARG - pUser arg is NULL.
|
||
|
E_OUTOFMEMORY - Couldn't create new node in batch list.
|
||
|
E_UNEXPECTED - Unexpected exception.
|
||
|
|
||
|
Revision History:
|
||
|
|
||
|
Date Description Programmer
|
||
|
-------- --------------------------------------------------- ----------
|
||
|
06/06/96 Initial creation. BrianAu
|
||
|
09/03/96 Add exception handling. BrianAu
|
||
|
*/
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
STDMETHODIMP
|
||
|
DiskQuotaUserBatch::Add(
|
||
|
PDISKQUOTA_USER pUser
|
||
|
)
|
||
|
{
|
||
|
HRESULT hResult = NOERROR;
|
||
|
|
||
|
if (NULL == pUser)
|
||
|
return E_INVALIDARG;
|
||
|
|
||
|
try
|
||
|
{
|
||
|
m_UserList.Append(pUser);
|
||
|
//
|
||
|
// Success. Increment ref count on object.
|
||
|
//
|
||
|
pUser->AddRef();
|
||
|
}
|
||
|
catch(CAllocException& e)
|
||
|
{
|
||
|
hResult = E_OUTOFMEMORY;
|
||
|
}
|
||
|
return hResult;
|
||
|
}
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
/* Function: DiskQuotaUserBatch::Remove
|
||
|
|
||
|
Description: Removes a user pointer from the batch queue.
|
||
|
|
||
|
Arguments:
|
||
|
pUser - Address of IDiskQuotaUser interface for the user object to
|
||
|
be removed.
|
||
|
|
||
|
Returns:
|
||
|
S_OK - Success.
|
||
|
S_FALSE - User not found in batch object.
|
||
|
E_INVALIDARG - pUser argument is NULL.
|
||
|
E_UNEXPECTED - Unexpected exception.
|
||
|
|
||
|
Revision History:
|
||
|
|
||
|
Date Description Programmer
|
||
|
-------- --------------------------------------------------- ----------
|
||
|
06/06/96 Initial creation. BrianAu
|
||
|
09/03/96 Add exception handling. BrianAu
|
||
|
*/
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
HRESULT
|
||
|
DiskQuotaUserBatch::Remove(
|
||
|
PDISKQUOTA_USER pUser
|
||
|
)
|
||
|
{
|
||
|
HRESULT hResult = S_FALSE; // Assume user not present.
|
||
|
PDISKQUOTA_USER pRemoved = NULL;
|
||
|
|
||
|
if (NULL == pUser)
|
||
|
return E_INVALIDARG;
|
||
|
|
||
|
m_UserList.Lock();
|
||
|
INT iUser = m_UserList.Find(pUser);
|
||
|
if (-1 != iUser)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
DBGASSERT((NULL != m_UserList[iUser]));
|
||
|
m_UserList[iUser]->Release();
|
||
|
m_UserList.Delete(iUser);
|
||
|
hResult = S_OK;
|
||
|
}
|
||
|
catch(CAllocException& e)
|
||
|
{
|
||
|
hResult = E_OUTOFMEMORY;
|
||
|
}
|
||
|
}
|
||
|
m_UserList.ReleaseLock();
|
||
|
|
||
|
return hResult;
|
||
|
}
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
/* Function: DiskQuotaUserBatch::RemoveAll
|
||
|
|
||
|
Description: Removes all user pointers from the batch
|
||
|
list and calling Release() through the removed pointer.
|
||
|
|
||
|
Arguments: None.
|
||
|
|
||
|
Returns:
|
||
|
NOERROR - Success.
|
||
|
E_UNEXPECTED - Unexpected exception.
|
||
|
|
||
|
Revision History:
|
||
|
|
||
|
Date Description Programmer
|
||
|
-------- --------------------------------------------------- ----------
|
||
|
06/06/96 Initial creation. BrianAu
|
||
|
09/03/96 Add exception handling. BrianAu
|
||
|
*/
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
STDMETHODIMP
|
||
|
DiskQuotaUserBatch::RemoveAll(
|
||
|
VOID
|
||
|
)
|
||
|
{
|
||
|
HRESULT hResult = NOERROR;
|
||
|
|
||
|
m_UserList.Lock();
|
||
|
INT cUsers = m_UserList.Count();
|
||
|
for (INT i = 0; i < cUsers; i++)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
DBGASSERT((NULL != m_UserList[i]));
|
||
|
m_UserList[i]->Release();
|
||
|
}
|
||
|
catch(CAllocException& e)
|
||
|
{
|
||
|
hResult = E_OUTOFMEMORY;
|
||
|
}
|
||
|
}
|
||
|
m_UserList.Clear();
|
||
|
m_UserList.ReleaseLock();
|
||
|
|
||
|
return hResult;
|
||
|
}
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
/* Function: DiskQuotaUserBatch::FlushToDisk
|
||
|
|
||
|
Description: Writes data for all batched user objects to disk in a single
|
||
|
NTIOAPI call. This is the real worker function for the batch object.
|
||
|
|
||
|
Arguments: None.
|
||
|
|
||
|
Returns:
|
||
|
NOERROR - Success.
|
||
|
E_OUTOFMEMORY - Insufficient memory.
|
||
|
E_UNEXPECTED - Unexpected exception.
|
||
|
|
||
|
Revision History:
|
||
|
|
||
|
Date Description Programmer
|
||
|
-------- --------------------------------------------------- ----------
|
||
|
06/06/96 Initial creation. BrianAu
|
||
|
09/03/96 Add exception handling. BrianAu
|
||
|
02/27/97 Divided NTFS writes into max 60KB pieces. BrianAu
|
||
|
The quota code in NTFS couldn't handle larger
|
||
|
buffers. It got into an infinite loop condition
|
||
|
due to filling of the log.
|
||
|
07/01/97 Replaced use of PointerList with CArray<>. BrianAu
|
||
|
Now use indexes instead of iterators.
|
||
|
*/
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
STDMETHODIMP
|
||
|
DiskQuotaUserBatch::FlushToDisk(
|
||
|
VOID
|
||
|
)
|
||
|
{
|
||
|
HRESULT hResult = NOERROR;
|
||
|
PFILE_QUOTA_INFORMATION pUserInfo = NULL;
|
||
|
PDISKQUOTA_USER pUser = NULL;
|
||
|
PBYTE pbBatchBuffer = NULL;
|
||
|
DWORD cbMinimumSid = FIELD_OFFSET(SID, SubAuthority) + sizeof(LONG);
|
||
|
INT iOuter = 0;
|
||
|
|
||
|
//
|
||
|
// Do nothing if the batch object is empty.
|
||
|
//
|
||
|
if (0 == m_UserList.Count())
|
||
|
return NOERROR;
|
||
|
|
||
|
m_UserList.Lock();
|
||
|
|
||
|
try
|
||
|
{
|
||
|
//
|
||
|
// Process the data in 60K chunks using a nested loop.
|
||
|
//
|
||
|
while(iOuter < m_UserList.UpperBound())
|
||
|
{
|
||
|
//
|
||
|
// Clone the outer iterator so we can process the next 60K of data.
|
||
|
// Need two new iterators. One for counting the bytes and
|
||
|
// one for transferring data to the write buffer. They're very small
|
||
|
// objects and cheap to create.
|
||
|
//
|
||
|
INT iCount = iOuter;
|
||
|
INT iTransfer = iOuter;
|
||
|
|
||
|
DWORD cbBatchBuffer = 0;
|
||
|
DWORD cItemsThisBatch = 0;
|
||
|
|
||
|
while(cbBatchBuffer < MAX_BATCH_BUFFER_BYTES &&
|
||
|
iCount <= m_UserList.UpperBound())
|
||
|
|
||
|
{
|
||
|
DWORD cbSid = 0;
|
||
|
pUser = m_UserList[iCount++];
|
||
|
pUser->GetSidLength(&cbSid);
|
||
|
|
||
|
//
|
||
|
// Total size required for user records.
|
||
|
//
|
||
|
cbBatchBuffer += FIELD_OFFSET(FILE_QUOTA_INFORMATION, Sid) + cbSid;
|
||
|
|
||
|
//
|
||
|
// Ensure it's quad-word aligned.
|
||
|
//
|
||
|
if (cbBatchBuffer & 0x00000007)
|
||
|
cbBatchBuffer = (cbBatchBuffer & 0xFFFFFFF8) + 8;
|
||
|
|
||
|
cItemsThisBatch++;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Allocate the buffer.
|
||
|
//
|
||
|
pbBatchBuffer = new BYTE[cbBatchBuffer];
|
||
|
|
||
|
PBYTE pbBatchBufferItem = pbBatchBuffer;
|
||
|
DWORD cbNextEntryOffset = 0;
|
||
|
//
|
||
|
// Now fill in the batch transaction buffer with data from
|
||
|
// all of the users in the batch list.
|
||
|
//
|
||
|
while(0 != cItemsThisBatch-- &&
|
||
|
iTransfer <= m_UserList.UpperBound())
|
||
|
{
|
||
|
pUser = m_UserList[iTransfer++];
|
||
|
pUserInfo = (PFILE_QUOTA_INFORMATION)pbBatchBufferItem;
|
||
|
|
||
|
pUser->GetSidLength(&pUserInfo->SidLength);
|
||
|
|
||
|
cbNextEntryOffset = FIELD_OFFSET(FILE_QUOTA_INFORMATION, Sid) + pUserInfo->SidLength;
|
||
|
//
|
||
|
// Ensure quad-word alignment.
|
||
|
//
|
||
|
if (cbNextEntryOffset & 0x00000007)
|
||
|
cbNextEntryOffset = (cbNextEntryOffset & 0xFFFFFFF8) + 8;
|
||
|
|
||
|
pUserInfo->NextEntryOffset = cbNextEntryOffset;
|
||
|
|
||
|
pUser->GetQuotaThreshold(&pUserInfo->QuotaThreshold.QuadPart);
|
||
|
pUser->GetQuotaLimit(&pUserInfo->QuotaLimit.QuadPart);
|
||
|
pUser->GetSid((PBYTE)&pUserInfo->Sid, pUserInfo->SidLength);
|
||
|
|
||
|
//
|
||
|
// These two don't get set but let's provide a known value anyway.
|
||
|
//
|
||
|
pUserInfo->ChangeTime.QuadPart = 0;
|
||
|
pUserInfo->QuotaUsed.QuadPart = 0;
|
||
|
|
||
|
pbBatchBufferItem += cbNextEntryOffset;
|
||
|
}
|
||
|
pUserInfo->NextEntryOffset = 0; // Last entry needs a 0 here.
|
||
|
//
|
||
|
// Submit the batch to the NTIOAPI for update.
|
||
|
//
|
||
|
hResult = m_pFSObject->SetUserQuotaInformation(pbBatchBuffer, cbBatchBuffer);
|
||
|
|
||
|
//
|
||
|
// Delete the data buffer.
|
||
|
//
|
||
|
delete[] pbBatchBuffer;
|
||
|
pbBatchBuffer = NULL;
|
||
|
|
||
|
//
|
||
|
// Advance the outer iterator to where the transfer iterator left off.
|
||
|
//
|
||
|
iOuter = iTransfer;
|
||
|
}
|
||
|
}
|
||
|
catch(CAllocException& e)
|
||
|
{
|
||
|
hResult = E_OUTOFMEMORY;
|
||
|
}
|
||
|
|
||
|
if (FAILED(hResult))
|
||
|
{
|
||
|
//
|
||
|
// Something failed. Invalid data cached in user objects.
|
||
|
// Next request for user data will have to read from disk.
|
||
|
//
|
||
|
iOuter = 0;
|
||
|
|
||
|
while(iOuter <= m_UserList.UpperBound())
|
||
|
{
|
||
|
pUser = m_UserList[iOuter++];
|
||
|
pUser->Invalidate();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
m_UserList.ReleaseLock();
|
||
|
|
||
|
delete[] pbBatchBuffer;
|
||
|
|
||
|
return hResult;
|
||
|
}
|