5581 lines
146 KiB
C
5581 lines
146 KiB
C
/*++
|
||
|
||
Copyright (c) 1996 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
Quota.c
|
||
|
||
Abstract:
|
||
|
||
This module implements the quota support routines for Ntfs
|
||
|
||
Author:
|
||
|
||
Jeff Havens [JHavens] 29-Feb-1996
|
||
|
||
Revision History:
|
||
|
||
--*/
|
||
|
||
#include "NtfsProc.h"
|
||
|
||
#define Dbg DEBUG_TRACE_QUOTA
|
||
|
||
|
||
//
|
||
// Define a tag for general pool allocations from this module
|
||
//
|
||
|
||
#undef MODULE_POOL_TAG
|
||
#define MODULE_POOL_TAG ('QFtN')
|
||
|
||
#define MAXIMUM_SID_LENGTH \
|
||
(FIELD_OFFSET( SID, SubAuthority ) + sizeof( ULONG ) * SID_MAX_SUB_AUTHORITIES)
|
||
|
||
#define MAXIMUM_QUOTA_ROW (SIZEOF_QUOTA_USER_DATA + MAXIMUM_SID_LENGTH + sizeof( ULONG ))
|
||
|
||
//
|
||
// Local quota support routines.
|
||
//
|
||
|
||
VOID
|
||
NtfsClearAndVerifyQuotaIndex (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PVCB Vcb
|
||
);
|
||
|
||
NTSTATUS
|
||
NtfsClearPerFileQuota (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PFCB Fcb,
|
||
IN PVOID Context
|
||
);
|
||
|
||
VOID
|
||
NtfsDeleteUnsedIds (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PVCB Vcb
|
||
);
|
||
|
||
VOID
|
||
NtfsMarkUserLimit (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PVOID Context
|
||
);
|
||
|
||
NTSTATUS
|
||
NtfsPackQuotaInfo (
|
||
IN PSID Sid,
|
||
IN PQUOTA_USER_DATA QuotaUserData OPTIONAL,
|
||
IN PFILE_QUOTA_INFORMATION OutBuffer,
|
||
IN OUT PULONG OutBufferSize
|
||
);
|
||
|
||
VOID
|
||
NtfsPostUserLimit (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PVCB Vcb,
|
||
IN PQUOTA_CONTROL_BLOCK QuotaControl
|
||
);
|
||
|
||
NTSTATUS
|
||
NtfsPrepareForDelete (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PVCB Vcb,
|
||
IN PSID Sid
|
||
);
|
||
|
||
VOID
|
||
NtfsRepairQuotaIndex (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PVOID Context
|
||
);
|
||
|
||
NTSTATUS
|
||
NtfsRepairPerFileQuota (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PFCB Fcb,
|
||
IN PVOID Context
|
||
);
|
||
|
||
VOID
|
||
NtfsSaveQuotaFlags (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PVCB Vcb
|
||
);
|
||
|
||
VOID
|
||
NtfsSaveQuotaFlagsSafe (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PVCB Vcb
|
||
);
|
||
|
||
NTSTATUS
|
||
NtfsVerifyOwnerIndex (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PVCB Vcb
|
||
);
|
||
|
||
RTL_GENERIC_COMPARE_RESULTS
|
||
NtfsQuotaTableCompare (
|
||
IN PRTL_GENERIC_TABLE Table,
|
||
PVOID FirstStruct,
|
||
PVOID SecondStruct
|
||
);
|
||
|
||
PVOID
|
||
NtfsQuotaTableAllocate (
|
||
IN PRTL_GENERIC_TABLE Table,
|
||
CLONG ByteSize
|
||
);
|
||
|
||
VOID
|
||
NtfsQuotaTableFree (
|
||
IN PRTL_GENERIC_TABLE Table,
|
||
PVOID Buffer
|
||
);
|
||
|
||
#if (DBG || defined( NTFS_FREE_ASSERTS ) || defined( NTFSDBG ))
|
||
BOOLEAN NtfsAllowFixups = 1;
|
||
BOOLEAN NtfsCheckQuota = 0;
|
||
#endif // DBG
|
||
|
||
#ifdef ALLOC_PRAGMA
|
||
#pragma alloc_text(PAGE, NtfsAcquireQuotaControl)
|
||
#pragma alloc_text(PAGE, NtfsCalculateQuotaAdjustment)
|
||
#pragma alloc_text(PAGE, NtfsClearAndVerifyQuotaIndex)
|
||
#pragma alloc_text(PAGE, NtfsClearPerFileQuota)
|
||
#pragma alloc_text(PAGE, NtfsDeleteUnsedIds)
|
||
#pragma alloc_text(PAGE, NtfsDereferenceQuotaControlBlock)
|
||
#pragma alloc_text(PAGE, NtfsFixupQuota)
|
||
#pragma alloc_text(PAGE, NtfsFsQuotaQueryInfo)
|
||
#pragma alloc_text(PAGE, NtfsFsQuotaSetInfo)
|
||
#pragma alloc_text(PAGE, NtfsGetCallersUserId)
|
||
#pragma alloc_text(PAGE, NtfsGetOwnerId)
|
||
#pragma alloc_text(PAGE, NtfsGetRemainingQuota)
|
||
#pragma alloc_text(PAGE, NtfsInitializeQuotaControlBlock)
|
||
#pragma alloc_text(PAGE, NtfsInitializeQuotaIndex)
|
||
#pragma alloc_text(PAGE, NtfsMarkQuotaCorrupt)
|
||
#pragma alloc_text(PAGE, NtfsMarkUserLimit)
|
||
#pragma alloc_text(PAGE, NtfsMoveQuotaOwner)
|
||
#pragma alloc_text(PAGE, NtfsPackQuotaInfo)
|
||
#pragma alloc_text(PAGE, NtfsPostUserLimit)
|
||
#pragma alloc_text(PAGE, NtfsPostRepairQuotaIndex)
|
||
#pragma alloc_text(PAGE, NtfsPrepareForDelete)
|
||
#pragma alloc_text(PAGE, NtfsReleaseQuotaControl)
|
||
#pragma alloc_text(PAGE, NtfsRepairQuotaIndex)
|
||
#pragma alloc_text(PAGE, NtfsSaveQuotaFlags)
|
||
#pragma alloc_text(PAGE, NtfsSaveQuotaFlagsSafe)
|
||
#pragma alloc_text(PAGE, NtfsQueryQuotaUserSidList)
|
||
#pragma alloc_text(PAGE, NtfsQuotaTableCompare)
|
||
#pragma alloc_text(PAGE, NtfsQuotaTableAllocate)
|
||
#pragma alloc_text(PAGE, NtfsQuotaTableFree)
|
||
#pragma alloc_text(PAGE, NtfsUpdateFileQuota)
|
||
#pragma alloc_text(PAGE, NtfsUpdateQuotaDefaults)
|
||
#pragma alloc_text(PAGE, NtfsVerifyOwnerIndex)
|
||
#pragma alloc_text(PAGE, NtfsRepairPerFileQuota)
|
||
#endif
|
||
|
||
|
||
VOID
|
||
NtfsAcquireQuotaControl (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PQUOTA_CONTROL_BLOCK QuotaControl
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Acquire the quota control block and quota index for shared update. Multiple
|
||
transactions can update then index, but only one thread can update a
|
||
particular index.
|
||
|
||
Arguments:
|
||
|
||
QuotaControl - Quota control block to be acquired.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
PVOID *Position;
|
||
PVOID *ScbArray;
|
||
ULONG Count;
|
||
|
||
PAGED_CODE();
|
||
|
||
ASSERT( QuotaControl->ReferenceCount > 0 );
|
||
|
||
//
|
||
// Make sure we have a free spot in the Scb array in the IrpContext.
|
||
//
|
||
|
||
if (IrpContext->SharedScb == NULL) {
|
||
|
||
Position = &IrpContext->SharedScb;
|
||
IrpContext->SharedScbSize = 1;
|
||
|
||
//
|
||
// Too bad the first one is not available. If the current size is one then allocate a
|
||
// new block and copy the existing value to it.
|
||
//
|
||
|
||
} else if (IrpContext->SharedScbSize == 1) {
|
||
|
||
if (IrpContext->SharedScb == QuotaControl) {
|
||
|
||
//
|
||
// The quota block has already been aquired.
|
||
//
|
||
|
||
return;
|
||
}
|
||
|
||
ScbArray = NtfsAllocatePool( PagedPool, sizeof( PVOID ) * 4 );
|
||
RtlZeroMemory( ScbArray, sizeof( PVOID ) * 4 );
|
||
*ScbArray = IrpContext->SharedScb;
|
||
IrpContext->SharedScb = ScbArray;
|
||
IrpContext->SharedScbSize = 4;
|
||
Position = ScbArray + 1;
|
||
|
||
//
|
||
// Otherwise look through the existing array and look for a free spot. Allocate a larger
|
||
// array if we need to grow it.
|
||
//
|
||
|
||
} else {
|
||
|
||
Position = IrpContext->SharedScb;
|
||
Count = IrpContext->SharedScbSize;
|
||
|
||
do {
|
||
|
||
if (*Position == NULL) {
|
||
|
||
break;
|
||
}
|
||
|
||
if (*Position == QuotaControl) {
|
||
|
||
//
|
||
// The quota block has already been aquired.
|
||
//
|
||
|
||
return;
|
||
}
|
||
|
||
Count -= 1;
|
||
Position += 1;
|
||
|
||
} while (Count != 0);
|
||
|
||
//
|
||
// If we didn't find one then allocate a new structure.
|
||
//
|
||
|
||
if (Count == 0) {
|
||
|
||
ScbArray = NtfsAllocatePool( PagedPool, sizeof( PVOID ) * IrpContext->SharedScbSize * 2 );
|
||
RtlZeroMemory( ScbArray, sizeof( PVOID ) * IrpContext->SharedScbSize * 2 );
|
||
RtlCopyMemory( ScbArray,
|
||
IrpContext->SharedScb,
|
||
sizeof( PVOID ) * IrpContext->SharedScbSize );
|
||
|
||
NtfsFreePool( IrpContext->SharedScb );
|
||
IrpContext->SharedScb = ScbArray;
|
||
Position = ScbArray + IrpContext->SharedScbSize;
|
||
IrpContext->SharedScbSize *= 2;
|
||
}
|
||
}
|
||
|
||
//
|
||
// The following assert is bougus, but I want know if we hit the case
|
||
// where create is acquiring the scb stream shared.
|
||
// Then make sure that the resource is released in create.c
|
||
//
|
||
|
||
ASSERT( IrpContext->MajorFunction != IRP_MJ_CREATE || IrpContext->OriginatingIrp != NULL || NtfsIsExclusiveScb( IrpContext->Vcb->QuotaTableScb ));
|
||
|
||
//
|
||
// Increase the reference count so the quota control block is not deleted
|
||
// while it is in the shared list.
|
||
//
|
||
|
||
ASSERT( QuotaControl->ReferenceCount > 0 );
|
||
InterlockedIncrement( &QuotaControl->ReferenceCount );
|
||
|
||
//
|
||
// The quota index must be acquired before the mft scb is acquired.
|
||
//
|
||
|
||
ASSERT(!NtfsIsExclusiveScb( IrpContext->Vcb->MftScb ) ||
|
||
ExIsResourceAcquiredSharedLite( IrpContext->Vcb->QuotaTableScb->Header.Resource ));
|
||
|
||
NtfsAcquireResourceShared( IrpContext, IrpContext->Vcb->QuotaTableScb, TRUE );
|
||
ExAcquireFastMutexUnsafe( QuotaControl->QuotaControlLock );
|
||
|
||
*Position = QuotaControl;
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
VOID
|
||
NtfsCalculateQuotaAdjustment (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PFCB Fcb,
|
||
OUT PLONGLONG Delta
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine scans the user data streams in a file and determines
|
||
by how much the quota needs to be adjusted.
|
||
|
||
Arguments:
|
||
|
||
Fcb - Fcb whose quota usage is being modified.
|
||
|
||
Delta - Returns the amount of quota adjustment required for the file.
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
ATTRIBUTE_ENUMERATION_CONTEXT Context;
|
||
PATTRIBUTE_RECORD_HEADER Attribute;
|
||
VCN StartVcn = 0;
|
||
|
||
PAGED_CODE();
|
||
|
||
ASSERT_EXCLUSIVE_FCB( Fcb );
|
||
|
||
//
|
||
// There is nothing to do if the standard infor has not been
|
||
// expanded yet.
|
||
//
|
||
|
||
if (!FlagOn( Fcb->FcbState, FCB_STATE_LARGE_STD_INFO )) {
|
||
*Delta = 0;
|
||
return;
|
||
}
|
||
|
||
NtfsInitializeAttributeContext( &Context );
|
||
|
||
//
|
||
// Use a try-finally to cleanup the enumeration structure.
|
||
//
|
||
|
||
try {
|
||
|
||
//
|
||
// Start with the $STANDARD_INFORMATION. This must be the first one found.
|
||
//
|
||
|
||
if (!NtfsLookupAttribute( IrpContext, Fcb, &Fcb->FileReference, &Context )) {
|
||
|
||
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
|
||
}
|
||
|
||
Attribute = NtfsFoundAttribute( &Context );
|
||
|
||
if (Attribute->TypeCode != $STANDARD_INFORMATION) {
|
||
|
||
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
|
||
}
|
||
|
||
//
|
||
// Initialize quota amount to the value current in the standard information structure.
|
||
//
|
||
|
||
*Delta = -(LONGLONG) ((PSTANDARD_INFORMATION) NtfsAttributeValue( Attribute ))->QuotaCharged;
|
||
|
||
//
|
||
// Now continue while there are more attributes to find.
|
||
//
|
||
|
||
while (NtfsLookupNextAttributeByVcn( IrpContext, Fcb, &StartVcn, &Context )) {
|
||
|
||
//
|
||
// Point to the current attribute.
|
||
//
|
||
|
||
Attribute = NtfsFoundAttribute( &Context );
|
||
|
||
//
|
||
// For all user data streams charge for a file record plus any non-resident allocation.
|
||
// For index streams charge for a file record for the INDEX_ROOT.
|
||
//
|
||
// For user data look for a resident attribute or the first attribute of a non-resident stream.
|
||
// Otherwise look for a $I30 stream.
|
||
//
|
||
|
||
if (NtfsIsTypeCodeSubjectToQuota( Attribute->TypeCode ) ||
|
||
((Attribute->TypeCode == $INDEX_ROOT) &&
|
||
((Attribute->NameLength * sizeof( WCHAR )) == NtfsFileNameIndex.Length) &&
|
||
RtlEqualMemory( Add2Ptr( Attribute, Attribute->NameOffset ),
|
||
NtfsFileNameIndex.Buffer,
|
||
NtfsFileNameIndex.Length ))) {
|
||
|
||
//
|
||
// Always charge for at least one file record.
|
||
//
|
||
|
||
*Delta += NtfsResidentStreamQuota( Fcb->Vcb );
|
||
|
||
//
|
||
// Charge for the allocated length for non-resident.
|
||
//
|
||
|
||
if (!NtfsIsAttributeResident( Attribute )) {
|
||
|
||
*Delta += Attribute->Form.Nonresident.AllocatedLength;
|
||
}
|
||
}
|
||
}
|
||
|
||
} finally {
|
||
|
||
NtfsCleanupAttributeContext( IrpContext, &Context );
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
VOID
|
||
NtfsClearAndVerifyQuotaIndex (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PVCB Vcb
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine iterates over the quota user data index and verifies the back
|
||
pointer to the owner id index. It also zeros the quota used field for
|
||
each owner.
|
||
|
||
Arguments:
|
||
|
||
Vcb - Pointer to the volume control block whose index is to be operated
|
||
on.
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
INDEX_KEY IndexKey;
|
||
INDEX_ROW OwnerRow;
|
||
MAP_HANDLE MapHandle;
|
||
PQUOTA_USER_DATA UserData;
|
||
PINDEX_ROW QuotaRow;
|
||
PVOID RowBuffer;
|
||
NTSTATUS Status;
|
||
ULONG OwnerId;
|
||
ULONG Count;
|
||
ULONG i;
|
||
PSCB QuotaScb = Vcb->QuotaTableScb;
|
||
PSCB OwnerIdScb = Vcb->OwnerIdTableScb;
|
||
PINDEX_ROW IndexRow = NULL;
|
||
PREAD_CONTEXT ReadContext = NULL;
|
||
BOOLEAN IndexAcquired = FALSE;
|
||
|
||
NtOfsInitializeMapHandle( &MapHandle );
|
||
|
||
//
|
||
// Allocate a buffer lager enough for several rows.
|
||
//
|
||
|
||
RowBuffer = NtfsAllocatePool( PagedPool, PAGE_SIZE );
|
||
|
||
try {
|
||
|
||
//
|
||
// Allocate a bunch of index row entries.
|
||
//
|
||
|
||
Count = PAGE_SIZE / sizeof( QUOTA_USER_DATA );
|
||
|
||
IndexRow = NtfsAllocatePool( PagedPool,
|
||
Count * sizeof( INDEX_ROW ) );
|
||
|
||
//
|
||
// Iterate through the quota entries. Start where we left off.
|
||
//
|
||
|
||
OwnerId = Vcb->QuotaFileReference.SegmentNumberLowPart;
|
||
IndexKey.KeyLength = sizeof( OwnerId );
|
||
IndexKey.Key = &OwnerId;
|
||
|
||
Status = NtOfsReadRecords( IrpContext,
|
||
QuotaScb,
|
||
&ReadContext,
|
||
&IndexKey,
|
||
NtOfsMatchAll,
|
||
NULL,
|
||
&Count,
|
||
IndexRow,
|
||
PAGE_SIZE,
|
||
RowBuffer );
|
||
|
||
|
||
while (NT_SUCCESS( Status )) {
|
||
|
||
NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
|
||
|
||
//
|
||
// Acquire the VCB shared and check whether we should
|
||
// continue.
|
||
//
|
||
|
||
if (!NtfsIsVcbAvailable( Vcb )) {
|
||
|
||
//
|
||
// The volume is going away, bail out.
|
||
//
|
||
|
||
NtfsReleaseVcb( IrpContext, Vcb );
|
||
Status = STATUS_VOLUME_DISMOUNTED;
|
||
leave;
|
||
}
|
||
|
||
NtfsAcquireExclusiveScb( IrpContext, QuotaScb );
|
||
NtfsAcquireExclusiveScb( IrpContext, OwnerIdScb );
|
||
IndexAcquired = TRUE;
|
||
|
||
//
|
||
// The following assert must be done while the quota resource
|
||
// held; otherwise a lingering transaction may cause it to
|
||
//
|
||
|
||
ASSERT( RtlIsGenericTableEmpty( &Vcb->QuotaControlTable ));
|
||
|
||
QuotaRow = IndexRow;
|
||
|
||
for (i = 0; i < Count; i += 1, QuotaRow += 1) {
|
||
|
||
UserData = QuotaRow->DataPart.Data;
|
||
|
||
//
|
||
// Validate the record is long enough for the Sid.
|
||
//
|
||
|
||
IndexKey.KeyLength = RtlLengthSid( &UserData->QuotaSid );
|
||
|
||
if ((IndexKey.KeyLength + SIZEOF_QUOTA_USER_DATA > QuotaRow->DataPart.DataLength) ||
|
||
!RtlValidSid( &UserData->QuotaSid )) {
|
||
|
||
ASSERT( FALSE );
|
||
|
||
//
|
||
// The sid is bad delete the record.
|
||
//
|
||
|
||
NtOfsDeleteRecords( IrpContext,
|
||
QuotaScb,
|
||
1,
|
||
&QuotaRow->KeyPart );
|
||
|
||
continue;
|
||
}
|
||
|
||
IndexKey.Key = &UserData->QuotaSid;
|
||
|
||
//
|
||
// Look up the Sid is in the owner id index.
|
||
//
|
||
|
||
Status = NtOfsFindRecord( IrpContext,
|
||
OwnerIdScb,
|
||
&IndexKey,
|
||
&OwnerRow,
|
||
&MapHandle,
|
||
NULL );
|
||
|
||
ASSERT( NT_SUCCESS( Status ));
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
|
||
//
|
||
// The owner id entry is missing. Add one back in.
|
||
//
|
||
|
||
OwnerRow.KeyPart = IndexKey;
|
||
OwnerRow.DataPart.DataLength = QuotaRow->KeyPart.KeyLength;
|
||
OwnerRow.DataPart.Data = QuotaRow->KeyPart.Key;
|
||
|
||
NtOfsAddRecords( IrpContext,
|
||
OwnerIdScb,
|
||
1,
|
||
&OwnerRow,
|
||
FALSE );
|
||
|
||
|
||
} else {
|
||
|
||
//
|
||
// Verify that the owner id's match.
|
||
//
|
||
|
||
if (*((PULONG) QuotaRow->KeyPart.Key) != *((PULONG) OwnerRow.DataPart.Data)) {
|
||
|
||
ASSERT( FALSE );
|
||
|
||
//
|
||
// Keep the quota record with the lower
|
||
// quota id. Delete the one with the higher
|
||
// quota id. Note this is the simple approach
|
||
// and not best case of the lower id does not
|
||
// exist. In that case a user entry will be delete
|
||
// and be reassigned a default quota.
|
||
//
|
||
|
||
if (*((PULONG) QuotaRow->KeyPart.Key) < *((PULONG) OwnerRow.DataPart.Data)) {
|
||
|
||
//
|
||
// Make the ownid's match.
|
||
//
|
||
|
||
OwnerRow.KeyPart = IndexKey;
|
||
OwnerRow.DataPart.DataLength = QuotaRow->KeyPart.KeyLength;
|
||
OwnerRow.DataPart.Data = QuotaRow->KeyPart.Key;
|
||
|
||
NtOfsUpdateRecord( IrpContext,
|
||
OwnerIdScb,
|
||
1,
|
||
&OwnerRow,
|
||
NULL,
|
||
NULL );
|
||
|
||
} else {
|
||
|
||
//
|
||
// Delete this record and proceed.
|
||
//
|
||
|
||
|
||
NtOfsDeleteRecords( IrpContext,
|
||
QuotaScb,
|
||
1,
|
||
&QuotaRow->KeyPart );
|
||
|
||
NtOfsReleaseMap( IrpContext, &MapHandle );
|
||
continue;
|
||
}
|
||
}
|
||
|
||
NtOfsReleaseMap( IrpContext, &MapHandle );
|
||
}
|
||
|
||
//
|
||
// Set the quota used to zero.
|
||
//
|
||
|
||
UserData->QuotaUsed = 0;
|
||
QuotaRow->DataPart.DataLength = SIZEOF_QUOTA_USER_DATA;
|
||
|
||
NtOfsUpdateRecord( IrpContext,
|
||
QuotaScb,
|
||
1,
|
||
QuotaRow,
|
||
NULL,
|
||
NULL );
|
||
}
|
||
|
||
//
|
||
// Release the indexes and commit what has been done so far.
|
||
//
|
||
|
||
NtfsReleaseScb( IrpContext, QuotaScb );
|
||
NtfsReleaseScb( IrpContext, OwnerIdScb );
|
||
NtfsReleaseVcb( IrpContext, Vcb );
|
||
IndexAcquired = FALSE;
|
||
|
||
//
|
||
// Complete the request which commits the pending
|
||
// transaction if there is one and releases of the
|
||
// acquired resources. The IrpContext will not
|
||
// be deleted because the no delete flag is set.
|
||
//
|
||
|
||
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_DONT_DELETE | IRP_CONTEXT_FLAG_RETAIN_FLAGS );
|
||
NtfsCompleteRequest( IrpContext, NULL, STATUS_SUCCESS );
|
||
|
||
//
|
||
// Remember how far we got so we can restart correctly.
|
||
//
|
||
|
||
Vcb->QuotaFileReference.SegmentNumberLowPart = *((PULONG) IndexRow[Count - 1].KeyPart.Key);
|
||
|
||
//
|
||
// Make sure the next free id is beyond the current ids.
|
||
//
|
||
|
||
if (Vcb->QuotaOwnerId <= Vcb->QuotaFileReference.SegmentNumberLowPart) {
|
||
|
||
ASSERT( Vcb->QuotaOwnerId > Vcb->QuotaFileReference.SegmentNumberLowPart );
|
||
Vcb->QuotaOwnerId = Vcb->QuotaFileReference.SegmentNumberLowPart + 1;
|
||
}
|
||
|
||
//
|
||
// Look up the next set of entries in the quota index.
|
||
//
|
||
|
||
Count = PAGE_SIZE / sizeof( QUOTA_USER_DATA );
|
||
Status = NtOfsReadRecords( IrpContext,
|
||
QuotaScb,
|
||
&ReadContext,
|
||
NULL,
|
||
NtOfsMatchAll,
|
||
NULL,
|
||
&Count,
|
||
IndexRow,
|
||
PAGE_SIZE,
|
||
RowBuffer );
|
||
}
|
||
|
||
ASSERT( (Status == STATUS_NO_MORE_MATCHES) || (Status == STATUS_NO_MATCH) );
|
||
|
||
} finally {
|
||
|
||
NtfsFreePool( RowBuffer );
|
||
NtOfsReleaseMap( IrpContext, &MapHandle );
|
||
|
||
if (IndexAcquired) {
|
||
NtfsReleaseScb( IrpContext, QuotaScb );
|
||
NtfsReleaseScb( IrpContext, OwnerIdScb );
|
||
NtfsReleaseVcb( IrpContext, Vcb );
|
||
}
|
||
|
||
if (IndexRow != NULL) {
|
||
NtfsFreePool( IndexRow );
|
||
}
|
||
|
||
if (ReadContext != NULL) {
|
||
NtOfsFreeReadContext( ReadContext );
|
||
}
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NtfsClearPerFileQuota (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PFCB Fcb,
|
||
IN PVOID Context
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine clears the quota charged field in each file on the volume. The
|
||
Quata control block is also released in fcb.
|
||
|
||
Arguments:
|
||
|
||
Fcb - Fcb for the file to be processed.
|
||
|
||
Context - Unsed.
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS
|
||
|
||
--*/
|
||
{
|
||
ULONGLONG NewQuota;
|
||
ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
|
||
PSTANDARD_INFORMATION StandardInformation;
|
||
PQUOTA_CONTROL_BLOCK QuotaControl = Fcb->QuotaControl;
|
||
PVCB Vcb = Fcb->Vcb;
|
||
|
||
UNREFERENCED_PARAMETER( Context);
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// There is nothing to do if the standard info has not been
|
||
// expanded yet.
|
||
//
|
||
|
||
if (!FlagOn( Fcb->FcbState, FCB_STATE_LARGE_STD_INFO )) {
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
//
|
||
// Use a try-finally to cleanup the attribute context.
|
||
//
|
||
|
||
try {
|
||
|
||
//
|
||
// Initialize the context structure.
|
||
//
|
||
|
||
NtfsInitializeAttributeContext( &AttrContext );
|
||
|
||
//
|
||
// Locate the standard information, it must be there.
|
||
//
|
||
|
||
if (!NtfsLookupAttributeByCode( IrpContext,
|
||
Fcb,
|
||
&Fcb->FileReference,
|
||
$STANDARD_INFORMATION,
|
||
&AttrContext )) {
|
||
|
||
DebugTrace( 0, Dbg, ("Can't find standard information\n") );
|
||
|
||
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
|
||
}
|
||
|
||
StandardInformation = (PSTANDARD_INFORMATION) NtfsAttributeValue( NtfsFoundAttribute( &AttrContext ));
|
||
|
||
ASSERT( NtfsFoundAttribute( &AttrContext )->Form.Resident.ValueLength == sizeof( STANDARD_INFORMATION ));
|
||
|
||
NewQuota = 0;
|
||
|
||
//
|
||
// Call to change the attribute value.
|
||
//
|
||
|
||
NtfsChangeAttributeValue( IrpContext,
|
||
Fcb,
|
||
FIELD_OFFSET( STANDARD_INFORMATION, QuotaCharged ),
|
||
&NewQuota,
|
||
sizeof( StandardInformation->QuotaCharged ),
|
||
FALSE,
|
||
FALSE,
|
||
FALSE,
|
||
FALSE,
|
||
&AttrContext );
|
||
|
||
//
|
||
// Release the quota control block for this fcb.
|
||
//
|
||
|
||
if (QuotaControl != NULL) {
|
||
NtfsDereferenceQuotaControlBlock( Vcb, &Fcb->QuotaControl );
|
||
}
|
||
|
||
} finally {
|
||
|
||
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
||
}
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
VOID
|
||
NtfsDeleteUnsedIds (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PVCB Vcb
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine iterates over the quota user data index and removes any
|
||
entries still marked as deleted.
|
||
|
||
Arguments:
|
||
|
||
Vcb - Pointer to the volume control block whoes index is to be operated
|
||
on.
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
INDEX_KEY IndexKey;
|
||
PINDEX_KEY KeyPtr;
|
||
PQUOTA_USER_DATA UserData;
|
||
PINDEX_ROW QuotaRow;
|
||
PVOID RowBuffer;
|
||
NTSTATUS Status = STATUS_SUCCESS;
|
||
ULONG OwnerId;
|
||
ULONG Count;
|
||
ULONG i;
|
||
PSCB QuotaScb = Vcb->QuotaTableScb;
|
||
PSCB OwnerIdScb = Vcb->OwnerIdTableScb;
|
||
PINDEX_ROW IndexRow = NULL;
|
||
PREAD_CONTEXT ReadContext = NULL;
|
||
BOOLEAN IndexAcquired = FALSE;
|
||
|
||
//
|
||
// Allocate a buffer large enough for several rows.
|
||
//
|
||
|
||
RowBuffer = NtfsAllocatePool( PagedPool, PAGE_SIZE );
|
||
|
||
try {
|
||
|
||
//
|
||
// Allocate a bunch of index row entries.
|
||
//
|
||
|
||
Count = PAGE_SIZE / sizeof( QUOTA_USER_DATA );
|
||
|
||
IndexRow = NtfsAllocatePool( PagedPool,
|
||
Count * sizeof( INDEX_ROW ) );
|
||
|
||
//
|
||
// Iterate through the quota entries. Start where we left off.
|
||
//
|
||
|
||
OwnerId = Vcb->QuotaFileReference.SegmentNumberLowPart;
|
||
IndexKey.KeyLength = sizeof( OwnerId );
|
||
IndexKey.Key = &OwnerId;
|
||
KeyPtr = &IndexKey;
|
||
|
||
while (NT_SUCCESS( Status )) {
|
||
|
||
NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
|
||
|
||
//
|
||
// Acquire the VCB shared and check whether we should
|
||
// continue.
|
||
//
|
||
|
||
if (!NtfsIsVcbAvailable( Vcb )) {
|
||
|
||
//
|
||
// The volume is going away, bail out.
|
||
//
|
||
|
||
NtfsReleaseVcb( IrpContext, Vcb );
|
||
Status = STATUS_VOLUME_DISMOUNTED;
|
||
leave;
|
||
}
|
||
|
||
NtfsAcquireExclusiveScb( IrpContext, QuotaScb );
|
||
NtfsAcquireExclusiveScb( IrpContext, OwnerIdScb );
|
||
ExAcquireFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
IndexAcquired = TRUE;
|
||
|
||
//
|
||
// Make sure the delete secquence number has not changed since
|
||
// the scan was delete.
|
||
//
|
||
|
||
if (ULongToPtr( Vcb->QuotaDeleteSecquence ) != IrpContext->Union.NtfsIoContext) {
|
||
|
||
//
|
||
// The scan needs to be restarted. Set the state to posted
|
||
// and raise status can not wait which will cause us to retry.
|
||
//
|
||
|
||
ClearFlag( Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING );
|
||
SetFlag( Vcb->QuotaState, VCB_QUOTA_REPAIR_POSTED );
|
||
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
|
||
}
|
||
|
||
Status = NtOfsReadRecords( IrpContext,
|
||
QuotaScb,
|
||
&ReadContext,
|
||
KeyPtr,
|
||
NtOfsMatchAll,
|
||
NULL,
|
||
&Count,
|
||
IndexRow,
|
||
PAGE_SIZE,
|
||
RowBuffer );
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
break;
|
||
}
|
||
|
||
QuotaRow = IndexRow;
|
||
|
||
for (i = 0; i < Count; i += 1, QuotaRow += 1) {
|
||
|
||
PQUOTA_CONTROL_BLOCK QuotaControl;
|
||
|
||
UserData = QuotaRow->DataPart.Data;
|
||
|
||
if (!FlagOn( UserData->QuotaFlags, QUOTA_FLAG_ID_DELETED )) {
|
||
continue;
|
||
}
|
||
|
||
//
|
||
// Check to see if there is a quota control entry
|
||
// for this id.
|
||
//
|
||
|
||
ASSERT( FIELD_OFFSET( QUOTA_CONTROL_BLOCK, OwnerId ) <= FIELD_OFFSET( INDEX_ROW, KeyPart.Key ));
|
||
|
||
QuotaControl = RtlLookupElementGenericTable( &Vcb->QuotaControlTable,
|
||
CONTAINING_RECORD( &QuotaRow->KeyPart.Key,
|
||
QUOTA_CONTROL_BLOCK,
|
||
OwnerId ));
|
||
|
||
//
|
||
// If there is a quota control entry or there is now
|
||
// some quota charged, then clear the deleted flag
|
||
// and update the entry.
|
||
//
|
||
|
||
if ((QuotaControl != NULL) || (UserData->QuotaUsed != 0)) {
|
||
|
||
ASSERT( (QuotaControl == NULL) && (UserData->QuotaUsed == 0) );
|
||
|
||
ClearFlag( UserData->QuotaFlags, QUOTA_FLAG_ID_DELETED );
|
||
|
||
QuotaRow->DataPart.DataLength = SIZEOF_QUOTA_USER_DATA;
|
||
|
||
IndexKey.KeyLength = sizeof( OwnerId );
|
||
IndexKey.Key = &OwnerId;
|
||
NtOfsUpdateRecord( IrpContext,
|
||
QuotaScb,
|
||
1,
|
||
QuotaRow,
|
||
NULL,
|
||
NULL );
|
||
|
||
continue;
|
||
}
|
||
|
||
//
|
||
// Delete the user quota data record.
|
||
//
|
||
|
||
IndexKey.KeyLength = sizeof( OwnerId );
|
||
IndexKey.Key = &OwnerId;
|
||
NtOfsDeleteRecords( IrpContext,
|
||
QuotaScb,
|
||
1,
|
||
&QuotaRow->KeyPart );
|
||
|
||
//
|
||
// Delete the owner id record.
|
||
//
|
||
|
||
IndexKey.Key = &UserData->QuotaSid;
|
||
IndexKey.KeyLength = RtlLengthSid( &UserData->QuotaSid );
|
||
NtOfsDeleteRecords( IrpContext,
|
||
OwnerIdScb,
|
||
1,
|
||
&IndexKey );
|
||
}
|
||
|
||
//
|
||
// Release the indexes and commit what has been done so far.
|
||
//
|
||
|
||
ExReleaseFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
NtfsReleaseScb( IrpContext, QuotaScb );
|
||
NtfsReleaseScb( IrpContext, OwnerIdScb );
|
||
NtfsReleaseVcb( IrpContext, Vcb );
|
||
IndexAcquired = FALSE;
|
||
|
||
//
|
||
// Complete the request which commits the pending
|
||
// transaction if there is one and releases of the
|
||
// acquired resources. The IrpContext will not
|
||
// be deleted because the no delete flag is set.
|
||
//
|
||
|
||
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_DONT_DELETE | IRP_CONTEXT_FLAG_RETAIN_FLAGS );
|
||
NtfsCompleteRequest( IrpContext, NULL, STATUS_SUCCESS );
|
||
|
||
//
|
||
// Remember how far we got so we can restart correctly.
|
||
//
|
||
|
||
Vcb->QuotaFileReference.SegmentNumberLowPart = *((PULONG) IndexRow[Count - 1].KeyPart.Key);
|
||
|
||
KeyPtr = NULL;
|
||
}
|
||
|
||
ASSERT( (Status == STATUS_NO_MORE_MATCHES) || (Status == STATUS_NO_MATCH) );
|
||
|
||
} finally {
|
||
|
||
NtfsFreePool( RowBuffer );
|
||
|
||
if (IndexAcquired) {
|
||
ExReleaseFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
NtfsReleaseScb( IrpContext, QuotaScb );
|
||
NtfsReleaseScb( IrpContext, OwnerIdScb );
|
||
NtfsReleaseVcb( IrpContext, Vcb );
|
||
}
|
||
|
||
if (IndexRow != NULL) {
|
||
NtfsFreePool( IndexRow );
|
||
}
|
||
|
||
if (ReadContext != NULL) {
|
||
NtOfsFreeReadContext( ReadContext );
|
||
}
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
VOID
|
||
NtfsDereferenceQuotaControlBlock (
|
||
IN PVCB Vcb,
|
||
IN PQUOTA_CONTROL_BLOCK *QuotaControl
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine dereferences the quota control block.
|
||
If reference count is now zero the block will be deallocated.
|
||
|
||
Arguments:
|
||
|
||
Vcb - Vcb for the volume that own the quota contorl block.
|
||
|
||
QuotaControl - Quota control block to be derefernece.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
PQUOTA_CONTROL_BLOCK TempQuotaControl;
|
||
LONG ReferenceCount;
|
||
ULONG OwnerId;
|
||
ULONG QuotaControlDeleteCount;
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// Capture the owner id and delete count;
|
||
//
|
||
|
||
OwnerId = (*QuotaControl)->OwnerId;
|
||
QuotaControlDeleteCount = Vcb->QuotaControlDeleteCount;
|
||
|
||
//
|
||
// Update the reference count.
|
||
//
|
||
|
||
ReferenceCount = InterlockedDecrement( &(*QuotaControl)->ReferenceCount );
|
||
|
||
ASSERT( ReferenceCount >= 0 );
|
||
|
||
//
|
||
// If the reference count is not zero we are done.
|
||
//
|
||
|
||
if (ReferenceCount != 0) {
|
||
|
||
//
|
||
// Clear the pointer from the FCB and return.
|
||
//
|
||
|
||
*QuotaControl = NULL;
|
||
return;
|
||
}
|
||
|
||
//
|
||
// Lock the quota table.
|
||
//
|
||
|
||
ExAcquireFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
|
||
try {
|
||
|
||
//
|
||
// Now things get messy. Check the delete count.
|
||
//
|
||
|
||
if (QuotaControlDeleteCount != Vcb->QuotaControlDeleteCount) {
|
||
|
||
//
|
||
// This is a bogus assert, but I want to see if this ever occurs.
|
||
//
|
||
|
||
ASSERT( QuotaControlDeleteCount != Vcb->QuotaControlDeleteCount );
|
||
|
||
//
|
||
// Something has already been deleted, the old quota control
|
||
// block may have been deleted already. Look it up again.
|
||
//
|
||
|
||
TempQuotaControl = RtlLookupElementGenericTable( &Vcb->QuotaControlTable,
|
||
CONTAINING_RECORD( &OwnerId,
|
||
QUOTA_CONTROL_BLOCK,
|
||
OwnerId ));
|
||
|
||
//
|
||
// The block was already deleted we are done.
|
||
//
|
||
|
||
if (TempQuotaControl == NULL) {
|
||
leave;
|
||
}
|
||
|
||
} else {
|
||
|
||
TempQuotaControl = *QuotaControl;
|
||
ASSERT( TempQuotaControl == RtlLookupElementGenericTable( &Vcb->QuotaControlTable,
|
||
CONTAINING_RECORD( &OwnerId,
|
||
QUOTA_CONTROL_BLOCK,
|
||
OwnerId )));
|
||
}
|
||
|
||
//
|
||
// Verify the reference count is still zero. The reference count
|
||
// cannot transision from zero to one while the quota table lock is
|
||
// held.
|
||
//
|
||
|
||
if (TempQuotaControl->ReferenceCount != 0) {
|
||
leave;
|
||
}
|
||
|
||
//
|
||
// Increment the delete count.
|
||
//
|
||
|
||
InterlockedIncrement( &Vcb->QuotaControlDeleteCount );
|
||
|
||
NtfsFreePool( TempQuotaControl->QuotaControlLock );
|
||
RtlDeleteElementGenericTable( &Vcb->QuotaControlTable,
|
||
TempQuotaControl );
|
||
|
||
} finally {
|
||
|
||
ExReleaseFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
*QuotaControl = NULL;
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
VOID
|
||
NtfsFixupQuota (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PFCB Fcb
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine ensures that the charged field is correct in the
|
||
standard information attribute of a file. If there is a problem
|
||
the it is fixed.
|
||
|
||
Arguments:
|
||
|
||
Fcb - Pointer to the FCB of the file being opened.
|
||
|
||
Return Value:
|
||
|
||
NONE
|
||
|
||
--*/
|
||
|
||
{
|
||
LONGLONG Delta = 0;
|
||
|
||
PAGED_CODE();
|
||
|
||
ASSERT( FlagOn( Fcb->Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_ENABLED ));
|
||
ASSERT( NtfsIsExclusiveFcb( Fcb ));
|
||
|
||
if (Fcb->OwnerId != QUOTA_INVALID_ID) {
|
||
|
||
ASSERT( Fcb->QuotaControl == NULL );
|
||
|
||
Fcb->QuotaControl = NtfsInitializeQuotaControlBlock( Fcb->Vcb, Fcb->OwnerId );
|
||
}
|
||
|
||
if ((NtfsPerformQuotaOperation( Fcb )) && (!NtfsIsVolumeReadOnly( Fcb->Vcb ))) {
|
||
|
||
NtfsCalculateQuotaAdjustment( IrpContext, Fcb, &Delta );
|
||
|
||
ASSERT( NtfsAllowFixups || FlagOn( Fcb->Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING ) || (Delta == 0) );
|
||
|
||
if (Delta != 0) {
|
||
#if DBG
|
||
|
||
if (IrpContext->OriginatingIrp != NULL ) {
|
||
PFILE_OBJECT FileObject;
|
||
|
||
FileObject = IoGetCurrentIrpStackLocation(
|
||
IrpContext->OriginatingIrp )->FileObject;
|
||
|
||
if (FileObject != NULL && FileObject->FileName.Buffer != NULL) {
|
||
DebugTrace( 0, Dbg, ( "NtfsFixupQuota: Quota fix up required on %Z of %I64x bytes\n",
|
||
&FileObject->FileName,
|
||
Delta ));
|
||
}
|
||
}
|
||
#endif
|
||
|
||
NtfsUpdateFileQuota( IrpContext, Fcb, &Delta, TRUE, FALSE );
|
||
}
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NtfsFsQuotaQueryInfo (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PVCB Vcb,
|
||
IN ULONG StartingId,
|
||
IN BOOLEAN ReturnSingleEntry,
|
||
IN OUT PFILE_QUOTA_INFORMATION *QuotaInfoOutBuffer,
|
||
IN OUT PULONG Length,
|
||
IN OUT PCCB Ccb OPTIONAL
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine returns the quota information for the volume.
|
||
|
||
Arguments:
|
||
|
||
Vcb - Volume control block for the volume to be quered.
|
||
|
||
StartingId - Owner Id after which to start the listing.
|
||
|
||
ReturnSingleEntry - Indicates only one entry should be returned.
|
||
|
||
QuotaInfoOutBuffer - Buffer to return the data. On return, points at the
|
||
last good entry copied.
|
||
|
||
Length - In the size of the buffer. Out the amount of space remaining.
|
||
|
||
Ccb - Optional Ccb which is updated with the last returned owner id.
|
||
|
||
Return Value:
|
||
|
||
Returns the status of the operation.
|
||
|
||
--*/
|
||
|
||
{
|
||
INDEX_ROW IndexRow;
|
||
INDEX_KEY IndexKey;
|
||
PINDEX_KEY KeyPtr;
|
||
PQUOTA_USER_DATA UserData;
|
||
PVOID RowBuffer;
|
||
NTSTATUS Status;
|
||
ULONG OwnerId;
|
||
ULONG Count = 1;
|
||
PREAD_CONTEXT ReadContext = NULL;
|
||
ULONG UserBufferLength = *Length;
|
||
PFILE_QUOTA_INFORMATION OutBuffer = *QuotaInfoOutBuffer;
|
||
|
||
PAGED_CODE();
|
||
|
||
if (UserBufferLength < sizeof(FILE_QUOTA_INFORMATION)) {
|
||
|
||
//
|
||
// The user buffer is way too small.
|
||
//
|
||
|
||
return STATUS_BUFFER_TOO_SMALL;
|
||
}
|
||
|
||
//
|
||
// Return nothing if quotas are not enabled.
|
||
//
|
||
|
||
if (Vcb->QuotaTableScb == NULL) {
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
//
|
||
// Allocate a buffer large enough for the largest quota entry and key.
|
||
//
|
||
|
||
RowBuffer = NtfsAllocatePool( PagedPool, MAXIMUM_QUOTA_ROW );
|
||
|
||
//
|
||
// Look up each entry in the quota index start with the next
|
||
// requested owner id.
|
||
//
|
||
|
||
OwnerId = StartingId + 1;
|
||
|
||
if (OwnerId < QUOTA_FISRT_USER_ID) {
|
||
OwnerId = QUOTA_FISRT_USER_ID;
|
||
}
|
||
|
||
IndexKey.KeyLength = sizeof( OwnerId );
|
||
IndexKey.Key = &OwnerId;
|
||
KeyPtr = &IndexKey;
|
||
|
||
try {
|
||
|
||
while (NT_SUCCESS( Status = NtOfsReadRecords( IrpContext,
|
||
Vcb->QuotaTableScb,
|
||
&ReadContext,
|
||
KeyPtr,
|
||
NtOfsMatchAll,
|
||
NULL,
|
||
&Count,
|
||
&IndexRow,
|
||
MAXIMUM_QUOTA_ROW,
|
||
RowBuffer ))) {
|
||
|
||
ASSERT( Count == 1 );
|
||
|
||
KeyPtr = NULL;
|
||
UserData = IndexRow.DataPart.Data;
|
||
|
||
//
|
||
// Skip this entry if it has been deleted.
|
||
//
|
||
|
||
if (FlagOn( UserData->QuotaFlags, QUOTA_FLAG_ID_DELETED )) {
|
||
continue;
|
||
}
|
||
|
||
if (!NT_SUCCESS( Status = NtfsPackQuotaInfo(&UserData->QuotaSid,
|
||
UserData,
|
||
OutBuffer,
|
||
&UserBufferLength ))) {
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Remember the owner id of the last entry returned.
|
||
//
|
||
|
||
OwnerId = *((PULONG) IndexRow.KeyPart.Key);
|
||
|
||
if (ReturnSingleEntry) {
|
||
break;
|
||
}
|
||
|
||
*QuotaInfoOutBuffer = OutBuffer;
|
||
OutBuffer = Add2Ptr( OutBuffer, OutBuffer->NextEntryOffset );
|
||
}
|
||
|
||
//
|
||
// If we're returning at least one entry, it's a SUCCESS.
|
||
//
|
||
|
||
if (UserBufferLength != *Length) {
|
||
|
||
Status = STATUS_SUCCESS;
|
||
|
||
//
|
||
// Set the next entry offset to zero to
|
||
// indicate list termination. If we are only returning a
|
||
// single entry, it makes more sense to let the caller
|
||
// take care of it.
|
||
//
|
||
|
||
if (!ReturnSingleEntry) {
|
||
|
||
(*QuotaInfoOutBuffer)->NextEntryOffset = 0;
|
||
}
|
||
|
||
if (Ccb != NULL) {
|
||
Ccb->LastOwnerId = OwnerId;
|
||
}
|
||
|
||
//
|
||
// Return how much of the buffer was used up.
|
||
// QuotaInfoOutBuffer already points at the last good entry.
|
||
//
|
||
|
||
*Length = UserBufferLength;
|
||
|
||
} else if (Status != STATUS_BUFFER_OVERFLOW) {
|
||
|
||
//
|
||
// We return NO_MORE_ENTRIES if we aren't returning any
|
||
// entries (even when the buffer was large enough).
|
||
//
|
||
|
||
Status = STATUS_NO_MORE_ENTRIES;
|
||
}
|
||
|
||
} finally {
|
||
|
||
NtfsFreePool( RowBuffer );
|
||
|
||
if (ReadContext != NULL) {
|
||
NtOfsFreeReadContext( ReadContext );
|
||
}
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NtfsFsQuotaSetInfo (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PVCB Vcb,
|
||
IN PFILE_QUOTA_INFORMATION FileQuotaInfo,
|
||
IN ULONG Length
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine sets the quota information on the volume for the
|
||
owner pasted in from the user buffer.
|
||
|
||
Arguments:
|
||
|
||
Vcb - Volume control block for the volume to be changed.
|
||
|
||
FileQuotaInfo - Buffer to return the data.
|
||
|
||
Length - The size of the buffer in bytes.
|
||
|
||
Return Value:
|
||
|
||
Returns the status of the operation.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS Status = STATUS_SUCCESS;
|
||
ULONG LengthUsed = 0;
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// Return nothing if quotas are not enabled.
|
||
//
|
||
|
||
if (Vcb->QuotaTableScb == NULL) {
|
||
|
||
return STATUS_INVALID_DEVICE_REQUEST;
|
||
|
||
}
|
||
|
||
//
|
||
// Validate the entire buffer before doing any work.
|
||
//
|
||
|
||
Status = IoCheckQuotaBufferValidity( FileQuotaInfo,
|
||
Length,
|
||
&LengthUsed );
|
||
|
||
IrpContext->OriginatingIrp->IoStatus.Information = LengthUsed;
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
return Status;
|
||
}
|
||
|
||
LengthUsed = 0;
|
||
|
||
//
|
||
// Perform the requested updates.
|
||
//
|
||
|
||
while (TRUE) {
|
||
|
||
//
|
||
// Make sure that the administrator limit is not being changed.
|
||
//
|
||
|
||
if (RtlEqualSid( SeExports->SeAliasAdminsSid, &FileQuotaInfo->Sid ) &&
|
||
(FileQuotaInfo->QuotaLimit.QuadPart != -1)) {
|
||
|
||
//
|
||
// Reject the request with access denied.
|
||
//
|
||
|
||
NtfsRaiseStatus( IrpContext, STATUS_ACCESS_DENIED, NULL, NULL );
|
||
|
||
}
|
||
|
||
if (FileQuotaInfo->QuotaLimit.QuadPart == -2) {
|
||
|
||
Status = NtfsPrepareForDelete( IrpContext,
|
||
Vcb,
|
||
&FileQuotaInfo->Sid );
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
break;
|
||
}
|
||
|
||
} else {
|
||
|
||
NtfsGetOwnerId( IrpContext,
|
||
&FileQuotaInfo->Sid,
|
||
TRUE,
|
||
FileQuotaInfo );
|
||
}
|
||
|
||
if (FileQuotaInfo->NextEntryOffset == 0) {
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Advance to the next entry.
|
||
//
|
||
|
||
FileQuotaInfo = Add2Ptr( FileQuotaInfo, FileQuotaInfo->NextEntryOffset);
|
||
}
|
||
|
||
//
|
||
// If the quota tracking has been requested and the quotas need to be
|
||
// repaired then try to repair them now.
|
||
//
|
||
|
||
if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_REQUESTED ) &&
|
||
FlagOn( Vcb->QuotaFlags,
|
||
(QUOTA_FLAG_OUT_OF_DATE |
|
||
QUOTA_FLAG_CORRUPT |
|
||
QUOTA_FLAG_PENDING_DELETES) )) {
|
||
|
||
NtfsPostRepairQuotaIndex( IrpContext, Vcb );
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NtfsQueryQuotaUserSidList (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PVCB Vcb,
|
||
IN PFILE_GET_QUOTA_INFORMATION SidList,
|
||
IN OUT PFILE_QUOTA_INFORMATION QuotaInfoOutBuffer,
|
||
IN OUT PULONG BufferLength,
|
||
IN BOOLEAN ReturnSingleEntry
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine query for the quota data for each user specified in the
|
||
user provided sid list.
|
||
|
||
Arguments:
|
||
|
||
Vcb - Supplies a pointer to the volume control block.
|
||
|
||
SidList - Supplies a pointer to the Sid list. The list has already
|
||
been validated.
|
||
|
||
QuotaInfoOutBuffer - Indicates where the retrived query data should be placed.
|
||
|
||
BufferLength - Indicates that size of the buffer, and is updated with the
|
||
amount of data actually placed in the buffer.
|
||
|
||
ReturnSingleEntry - Indicates if just one entry should be returned.
|
||
|
||
Return Value:
|
||
|
||
Returns the status of the operation.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS Status = STATUS_SUCCESS;
|
||
ULONG BytesRemaining = *BufferLength;
|
||
PFILE_QUOTA_INFORMATION LastEntry = QuotaInfoOutBuffer;
|
||
ULONG OwnerId;
|
||
|
||
PAGED_CODE( );
|
||
|
||
//
|
||
// Loop through each of the entries.
|
||
//
|
||
|
||
while (TRUE) {
|
||
|
||
//
|
||
// Get the owner id.
|
||
//
|
||
|
||
OwnerId = NtfsGetOwnerId( IrpContext,
|
||
&SidList->Sid,
|
||
FALSE,
|
||
NULL );
|
||
|
||
if (OwnerId != QUOTA_INVALID_ID) {
|
||
|
||
//
|
||
// Send ownerid and ask for a single entry.
|
||
//
|
||
|
||
Status = NtfsFsQuotaQueryInfo( IrpContext,
|
||
Vcb,
|
||
OwnerId - 1,
|
||
TRUE,
|
||
&QuotaInfoOutBuffer,
|
||
&BytesRemaining,
|
||
NULL );
|
||
|
||
} else {
|
||
|
||
//
|
||
// Send back zeroed data alongwith the Sid.
|
||
//
|
||
|
||
Status = NtfsPackQuotaInfo( &SidList->Sid,
|
||
NULL,
|
||
QuotaInfoOutBuffer,
|
||
&BytesRemaining );
|
||
}
|
||
|
||
//
|
||
// Bail out if we got a real error.
|
||
//
|
||
|
||
if (!NT_SUCCESS( Status ) && (Status != STATUS_NO_MORE_ENTRIES)) {
|
||
|
||
break;
|
||
}
|
||
|
||
if (ReturnSingleEntry) {
|
||
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Make a note of the last entry filled in.
|
||
//
|
||
|
||
LastEntry = QuotaInfoOutBuffer;
|
||
|
||
//
|
||
// If we've exhausted the SidList, we're done
|
||
//
|
||
|
||
if (SidList->NextEntryOffset == 0) {
|
||
break;
|
||
}
|
||
|
||
SidList = Add2Ptr( SidList, SidList->NextEntryOffset );
|
||
|
||
ASSERT(QuotaInfoOutBuffer->NextEntryOffset > 0);
|
||
QuotaInfoOutBuffer = Add2Ptr( QuotaInfoOutBuffer,
|
||
QuotaInfoOutBuffer->NextEntryOffset );
|
||
}
|
||
|
||
//
|
||
// Set the next entry offset to zero to
|
||
// indicate list termination.
|
||
//
|
||
|
||
if (BytesRemaining != *BufferLength) {
|
||
|
||
LastEntry->NextEntryOffset = 0;
|
||
Status = STATUS_SUCCESS;
|
||
}
|
||
|
||
//
|
||
// Update the buffer length to reflect what's left.
|
||
// If we've copied anything at all, we must return SUCCESS.
|
||
//
|
||
|
||
ASSERT( (BytesRemaining == *BufferLength) || (Status == STATUS_SUCCESS ) );
|
||
*BufferLength = BytesRemaining;
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NtfsPackQuotaInfo (
|
||
IN PSID Sid,
|
||
IN PQUOTA_USER_DATA QuotaUserData OPTIONAL,
|
||
IN PFILE_QUOTA_INFORMATION OutBuffer,
|
||
IN OUT PULONG OutBufferSize
|
||
)
|
||
|
||
/*++
|
||
Routine Description:
|
||
|
||
This is an internal routine that fills a given FILE_QUOTA_INFORMATION
|
||
structure with information from a given QUOTA_USER_DATA structure.
|
||
|
||
Arguments:
|
||
|
||
Sid - SID to be copied. Same as the one embedded inside the USER_DATA struct.
|
||
This routine doesn't care if it's a valid sid.
|
||
|
||
QuotaUserData - Source of data
|
||
|
||
QuotaInfoBufferPtr - Buffer to have user data copied in to.
|
||
|
||
OutBufferSize - IN size of the buffer, OUT size of the remaining buffer.
|
||
--*/
|
||
|
||
{
|
||
ULONG SidLength;
|
||
ULONG NextOffset;
|
||
ULONG EntrySize;
|
||
|
||
SidLength = RtlLengthSid( Sid );
|
||
EntrySize = SidLength + FIELD_OFFSET( FILE_QUOTA_INFORMATION, Sid );
|
||
|
||
//
|
||
// Abort if this entry won't fit in the buffer.
|
||
//
|
||
|
||
if (*OutBufferSize < EntrySize) {
|
||
|
||
return STATUS_BUFFER_OVERFLOW;
|
||
}
|
||
|
||
if (ARGUMENT_PRESENT(QuotaUserData)) {
|
||
|
||
//
|
||
// Fill in the user buffer for this entry.
|
||
//
|
||
|
||
OutBuffer->ChangeTime.QuadPart = QuotaUserData->QuotaChangeTime;
|
||
OutBuffer->QuotaUsed.QuadPart = QuotaUserData->QuotaUsed;
|
||
OutBuffer->QuotaThreshold.QuadPart = QuotaUserData->QuotaThreshold;
|
||
OutBuffer->QuotaLimit.QuadPart = QuotaUserData->QuotaLimit;
|
||
|
||
} else {
|
||
|
||
//
|
||
// Return all zeros for the data, up until the Sid.
|
||
//
|
||
|
||
RtlZeroMemory( OutBuffer, FIELD_OFFSET(FILE_QUOTA_INFORMATION, Sid) );
|
||
}
|
||
|
||
OutBuffer->SidLength = SidLength;
|
||
RtlCopyMemory( &OutBuffer->Sid,
|
||
Sid,
|
||
SidLength );
|
||
|
||
//
|
||
// Calculate the next offset.
|
||
//
|
||
|
||
NextOffset = QuadAlign( EntrySize );
|
||
|
||
//
|
||
// Add the offset to the amount used.
|
||
// NextEntryOffset may be sligthly larger than Length due to
|
||
// rounding of the previous entry size to longlong.
|
||
//
|
||
|
||
if (*OutBufferSize > NextOffset) {
|
||
|
||
*OutBufferSize -= NextOffset;
|
||
OutBuffer->NextEntryOffset = NextOffset;
|
||
|
||
} else {
|
||
|
||
//
|
||
// We did have enough room for this entry, but quad-alignment made
|
||
// it look like we didn't. Return the last few bytes left
|
||
// (what we lost in rounding up) just for correctness, although
|
||
// those really won't be of much use. The NextEntryOffset will be
|
||
// zeroed subsequently by the caller.
|
||
// Note that the OutBuffer is pointing at the _beginning_ of the
|
||
// last entry returned in this case.
|
||
//
|
||
|
||
ASSERT( *OutBufferSize >= EntrySize );
|
||
*OutBufferSize -= EntrySize;
|
||
OutBuffer->NextEntryOffset = EntrySize;
|
||
}
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
ULONG
|
||
NtfsGetOwnerId (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PSID Sid,
|
||
IN BOOLEAN CreateNew,
|
||
IN PFILE_QUOTA_INFORMATION FileQuotaInfo OPTIONAL
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine determines the owner id for the requested SID. First the
|
||
Sid is looked up in the Owner Id index. If the entry exists, then that
|
||
owner id is returned. If the sid does not exist then new entry is
|
||
created in the owner id index.
|
||
|
||
Arguments:
|
||
|
||
Sid - Security id to determine the owner id.
|
||
|
||
CreateNew - Create a new id if necessary.
|
||
|
||
FileQuotaInfo - Optional quota data to update quota index with.
|
||
|
||
Return Value:
|
||
|
||
ULONG - Owner Id for the security id. QUOTA_INVALID_ID is returned if id
|
||
did not exist and CreateNew was FALSE.
|
||
|
||
--*/
|
||
|
||
{
|
||
ULONG OwnerId;
|
||
ULONG DefaultId;
|
||
ULONG SidLength;
|
||
NTSTATUS Status;
|
||
INDEX_ROW IndexRow;
|
||
INDEX_KEY IndexKey;
|
||
MAP_HANDLE MapHandle;
|
||
PQUOTA_USER_DATA NewQuotaData = NULL;
|
||
QUICK_INDEX_HINT QuickIndexHint;
|
||
PSCB QuotaScb;
|
||
PVCB Vcb = IrpContext->Vcb;
|
||
PSCB OwnerIdScb = Vcb->OwnerIdTableScb;
|
||
|
||
BOOLEAN ExistingRecord;
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// Determine the Sid length.
|
||
//
|
||
|
||
SidLength = RtlLengthSid( Sid );
|
||
|
||
IndexKey.KeyLength = SidLength;
|
||
IndexKey.Key = Sid;
|
||
|
||
//
|
||
// If there is quota information to update or there are pending deletes
|
||
// then long path must be taken where the user quota entry is found.
|
||
//
|
||
|
||
if (FileQuotaInfo == NULL) {
|
||
|
||
//
|
||
// Acquire the owner id index shared.
|
||
//
|
||
|
||
NtfsAcquireSharedScb( IrpContext, OwnerIdScb );
|
||
|
||
try {
|
||
|
||
//
|
||
// Assume the Sid is in the index.
|
||
//
|
||
|
||
Status = NtOfsFindRecord( IrpContext,
|
||
OwnerIdScb,
|
||
&IndexKey,
|
||
&IndexRow,
|
||
&MapHandle,
|
||
NULL );
|
||
|
||
//
|
||
// If the sid was found then capture is value.
|
||
//
|
||
|
||
if (NT_SUCCESS( Status )) {
|
||
|
||
ASSERT( IndexRow.DataPart.DataLength == sizeof( ULONG ));
|
||
OwnerId = *((PULONG) IndexRow.DataPart.Data);
|
||
|
||
//
|
||
// Release the index map handle.
|
||
//
|
||
|
||
NtOfsReleaseMap( IrpContext, &MapHandle );
|
||
}
|
||
|
||
} finally {
|
||
NtfsReleaseScb( IrpContext, OwnerIdScb );
|
||
}
|
||
|
||
//
|
||
// If the sid was found and there are no pending deletes, we are done.
|
||
//
|
||
|
||
if (NT_SUCCESS(Status)) {
|
||
|
||
if (!FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_PENDING_DELETES )) {
|
||
return OwnerId;
|
||
}
|
||
|
||
//
|
||
// Look up the actual record to see if it is deleted.
|
||
//
|
||
|
||
QuotaScb = Vcb->QuotaTableScb;
|
||
NtfsAcquireSharedScb( IrpContext, QuotaScb );
|
||
|
||
try {
|
||
|
||
IndexKey.KeyLength = sizeof(ULONG);
|
||
IndexKey.Key = &OwnerId;
|
||
|
||
Status = NtOfsFindRecord( IrpContext,
|
||
QuotaScb,
|
||
&IndexKey,
|
||
&IndexRow,
|
||
&MapHandle,
|
||
NULL );
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
|
||
ASSERT( NT_SUCCESS( Status ));
|
||
NtfsMarkQuotaCorrupt( IrpContext, Vcb );
|
||
OwnerId = QUOTA_INVALID_ID;
|
||
leave;
|
||
}
|
||
|
||
if (FlagOn( ((PQUOTA_USER_DATA) IndexRow.DataPart.Data)->QuotaFlags,
|
||
QUOTA_FLAG_ID_DELETED )) {
|
||
|
||
//
|
||
// Return invalid user.
|
||
//
|
||
|
||
OwnerId = QUOTA_INVALID_ID;
|
||
}
|
||
|
||
//
|
||
// Release the index map handle.
|
||
//
|
||
|
||
NtOfsReleaseMap( IrpContext, &MapHandle );
|
||
|
||
} finally {
|
||
|
||
NtfsReleaseScb( IrpContext, QuotaScb );
|
||
}
|
||
|
||
//
|
||
// If an active id was found or caller does not want a new
|
||
// created then return.
|
||
//
|
||
|
||
if ((OwnerId != QUOTA_INVALID_ID) || !CreateNew) {
|
||
return OwnerId;
|
||
}
|
||
|
||
} else if (!CreateNew) {
|
||
|
||
//
|
||
// Just return QUOTA_INVALID_ID.
|
||
//
|
||
|
||
return QUOTA_INVALID_ID;
|
||
}
|
||
}
|
||
|
||
//
|
||
// If we have the quotatable resource, we should have it exclusively.
|
||
//
|
||
|
||
ASSERT( CreateNew );
|
||
ASSERT( !ExIsResourceAcquiredSharedLite( Vcb->QuotaTableScb->Fcb->Resource ) ||
|
||
ExIsResourceAcquiredExclusiveLite( Vcb->QuotaTableScb->Fcb->Resource ));
|
||
|
||
//
|
||
// Acquire Owner id and quota index exclusive.
|
||
//
|
||
|
||
QuotaScb = Vcb->QuotaTableScb;
|
||
NtfsAcquireExclusiveScb( IrpContext, QuotaScb );
|
||
NtfsAcquireExclusiveScb( IrpContext, OwnerIdScb );
|
||
|
||
NtOfsInitializeMapHandle( &MapHandle );
|
||
|
||
try {
|
||
|
||
//
|
||
// Verify that the sid is still not in the index.
|
||
//
|
||
|
||
IndexKey.KeyLength = SidLength;
|
||
IndexKey.Key = Sid;
|
||
|
||
Status = NtOfsFindRecord( IrpContext,
|
||
OwnerIdScb,
|
||
&IndexKey,
|
||
&IndexRow,
|
||
&MapHandle,
|
||
NULL );
|
||
|
||
//
|
||
// If the sid was found then capture the owner id.
|
||
//
|
||
|
||
ExistingRecord = NT_SUCCESS(Status);
|
||
|
||
if (ExistingRecord) {
|
||
|
||
ASSERT( IndexRow.DataPart.DataLength == sizeof( ULONG ));
|
||
OwnerId = *((PULONG) IndexRow.DataPart.Data);
|
||
|
||
if ((FileQuotaInfo == NULL) &&
|
||
!FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_PENDING_DELETES )) {
|
||
|
||
leave;
|
||
}
|
||
|
||
//
|
||
// Release the index map handle.
|
||
//
|
||
|
||
NtOfsReleaseMap( IrpContext, &MapHandle );
|
||
|
||
} else {
|
||
|
||
//
|
||
// Allocate a new owner id and update the owner index.
|
||
//
|
||
|
||
OwnerId = Vcb->QuotaOwnerId;
|
||
Vcb->QuotaOwnerId += 1;
|
||
|
||
IndexRow.KeyPart.KeyLength = SidLength;
|
||
IndexRow.KeyPart.Key = Sid;
|
||
IndexRow.DataPart.Data = &OwnerId;
|
||
IndexRow.DataPart.DataLength = sizeof(OwnerId);
|
||
|
||
NtOfsAddRecords( IrpContext,
|
||
OwnerIdScb,
|
||
1,
|
||
&IndexRow,
|
||
FALSE );
|
||
}
|
||
|
||
//
|
||
// Allocate space for the new quota user data.
|
||
//
|
||
|
||
NewQuotaData = NtfsAllocatePool( PagedPool,
|
||
SIZEOF_QUOTA_USER_DATA + SidLength);
|
||
|
||
if (ExistingRecord) {
|
||
|
||
//
|
||
// Find the existing record and update it.
|
||
//
|
||
|
||
IndexKey.KeyLength = sizeof( ULONG );
|
||
IndexKey.Key = &OwnerId;
|
||
|
||
RtlZeroMemory( &QuickIndexHint, sizeof( QuickIndexHint ));
|
||
|
||
Status = NtOfsFindRecord( IrpContext,
|
||
QuotaScb,
|
||
&IndexKey,
|
||
&IndexRow,
|
||
&MapHandle,
|
||
&QuickIndexHint );
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
|
||
ASSERT( NT_SUCCESS( Status ));
|
||
NtfsMarkQuotaCorrupt( IrpContext, Vcb );
|
||
OwnerId = QUOTA_INVALID_ID;
|
||
leave;
|
||
}
|
||
|
||
ASSERT( IndexRow.DataPart.DataLength == SIZEOF_QUOTA_USER_DATA + SidLength );
|
||
|
||
RtlCopyMemory( NewQuotaData, IndexRow.DataPart.Data, IndexRow.DataPart.DataLength );
|
||
|
||
ASSERT( RtlEqualMemory( &NewQuotaData->QuotaSid, Sid, SidLength ));
|
||
|
||
//
|
||
// Update the changed fields in the record.
|
||
//
|
||
|
||
if (FileQuotaInfo != NULL) {
|
||
|
||
ClearFlag( NewQuotaData->QuotaFlags, QUOTA_FLAG_DEFAULT_LIMITS );
|
||
NewQuotaData->QuotaThreshold = FileQuotaInfo->QuotaThreshold.QuadPart;
|
||
NewQuotaData->QuotaLimit = FileQuotaInfo->QuotaLimit.QuadPart;
|
||
KeQuerySystemTime( (PLARGE_INTEGER) &NewQuotaData->QuotaChangeTime );
|
||
|
||
} else if (!FlagOn( NewQuotaData->QuotaFlags, QUOTA_FLAG_ID_DELETED )) {
|
||
|
||
//
|
||
// There is nothing to update just return.
|
||
//
|
||
|
||
leave;
|
||
}
|
||
|
||
//
|
||
// Always clear the deleted flag.
|
||
//
|
||
|
||
ClearFlag( NewQuotaData->QuotaFlags, QUOTA_FLAG_ID_DELETED );
|
||
ASSERT( (OwnerId != Vcb->AdministratorId) || (NewQuotaData->QuotaLimit == -1) );
|
||
|
||
//
|
||
// The key length does not change.
|
||
//
|
||
|
||
IndexRow.KeyPart.Key = &OwnerId;
|
||
ASSERT( IndexRow.KeyPart.KeyLength == sizeof( ULONG ));
|
||
IndexRow.DataPart.Data = NewQuotaData;
|
||
IndexRow.DataPart.DataLength = SIZEOF_QUOTA_USER_DATA;
|
||
|
||
NtOfsUpdateRecord( IrpContext,
|
||
QuotaScb,
|
||
1,
|
||
&IndexRow,
|
||
&QuickIndexHint,
|
||
&MapHandle );
|
||
|
||
leave;
|
||
}
|
||
|
||
if (FileQuotaInfo == NULL) {
|
||
|
||
//
|
||
// Look up the default quota limits.
|
||
//
|
||
|
||
DefaultId = QUOTA_DEFAULTS_ID;
|
||
IndexKey.KeyLength = sizeof( ULONG );
|
||
IndexKey.Key = &DefaultId;
|
||
|
||
Status = NtOfsFindRecord( IrpContext,
|
||
QuotaScb,
|
||
&IndexKey,
|
||
&IndexRow,
|
||
&MapHandle,
|
||
NULL );
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
|
||
ASSERT( NT_SUCCESS( Status ));
|
||
NtfsRaiseStatus( IrpContext,
|
||
STATUS_QUOTA_LIST_INCONSISTENT,
|
||
NULL,
|
||
Vcb->QuotaTableScb->Fcb );
|
||
}
|
||
|
||
ASSERT( IndexRow.DataPart.DataLength >= SIZEOF_QUOTA_USER_DATA );
|
||
|
||
//
|
||
// Initialize the new quota entry with the defaults.
|
||
//
|
||
|
||
RtlCopyMemory( NewQuotaData,
|
||
IndexRow.DataPart.Data,
|
||
SIZEOF_QUOTA_USER_DATA );
|
||
|
||
ClearFlag( NewQuotaData->QuotaFlags, ~QUOTA_FLAG_USER_MASK );
|
||
|
||
} else {
|
||
|
||
//
|
||
// Initialize the new record with the new data.
|
||
//
|
||
|
||
RtlZeroMemory( NewQuotaData, SIZEOF_QUOTA_USER_DATA );
|
||
|
||
NewQuotaData->QuotaVersion = QUOTA_USER_VERSION;
|
||
NewQuotaData->QuotaThreshold = FileQuotaInfo->QuotaThreshold.QuadPart;
|
||
NewQuotaData->QuotaLimit = FileQuotaInfo->QuotaLimit.QuadPart;
|
||
}
|
||
|
||
ASSERT( !RtlEqualSid( SeExports->SeAliasAdminsSid, Sid ) ||
|
||
(NewQuotaData->QuotaThreshold == -1) );
|
||
|
||
//
|
||
// Copy the Sid into the new record.
|
||
//
|
||
|
||
RtlCopyMemory( &NewQuotaData->QuotaSid, Sid, SidLength );
|
||
KeQuerySystemTime( (PLARGE_INTEGER) &NewQuotaData->QuotaChangeTime );
|
||
|
||
//
|
||
// Add the new quota data record to the index.
|
||
//
|
||
|
||
IndexRow.KeyPart.KeyLength = sizeof( ULONG );
|
||
IndexRow.KeyPart.Key = &OwnerId;
|
||
IndexRow.DataPart.Data = NewQuotaData;
|
||
IndexRow.DataPart.DataLength = SIZEOF_QUOTA_USER_DATA + SidLength;
|
||
|
||
NtOfsAddRecords( IrpContext,
|
||
QuotaScb,
|
||
1,
|
||
&IndexRow,
|
||
TRUE );
|
||
|
||
} finally {
|
||
|
||
if (NewQuotaData != NULL) {
|
||
NtfsFreePool( NewQuotaData );
|
||
}
|
||
|
||
//
|
||
// Release the index map handle and index resources.
|
||
//
|
||
|
||
NtOfsReleaseMap( IrpContext, &MapHandle );
|
||
NtfsReleaseScb( IrpContext, QuotaScb );
|
||
NtfsReleaseScb( IrpContext, OwnerIdScb );
|
||
}
|
||
|
||
return OwnerId;
|
||
}
|
||
|
||
|
||
VOID
|
||
NtfsGetRemainingQuota (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN ULONG OwnerId,
|
||
OUT PULONGLONG RemainingQuota,
|
||
OUT PULONGLONG TotalQuota,
|
||
IN OUT PQUICK_INDEX_HINT QuickIndexHint OPTIONAL
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine returns the remaining amount of quota a user has before a
|
||
the quota limit is reached.
|
||
|
||
Arguments:
|
||
|
||
Fcb - Fcb whose quota usage is being checked.
|
||
|
||
OwnerId - Supplies the owner id to look up.
|
||
|
||
RemainingQuota - Returns the remaining amount of quota in bytes.
|
||
|
||
TotalQuota - Returns the total amount of quota in bytes for the given sid.
|
||
|
||
QuickIndexHint - Supplies an optional hint where to look of the value.
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
PQUOTA_USER_DATA UserData;
|
||
INDEX_ROW IndexRow;
|
||
INDEX_KEY IndexKey;
|
||
MAP_HANDLE MapHandle;
|
||
NTSTATUS Status;
|
||
PVCB Vcb = IrpContext->Vcb;
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// Initialize the map handle.
|
||
//
|
||
|
||
NtOfsInitializeMapHandle( &MapHandle );
|
||
NtfsAcquireSharedScb( IrpContext, Vcb->QuotaTableScb );
|
||
|
||
try {
|
||
|
||
IndexKey.KeyLength = sizeof(ULONG);
|
||
IndexKey.Key = &OwnerId;
|
||
|
||
Status = NtOfsFindRecord( IrpContext,
|
||
Vcb->QuotaTableScb,
|
||
&IndexKey,
|
||
&IndexRow,
|
||
&MapHandle,
|
||
QuickIndexHint );
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
|
||
//
|
||
// This look up should not fail.
|
||
//
|
||
|
||
ASSERT( NT_SUCCESS( Status ));
|
||
|
||
//
|
||
// There is one case where this could occur. That is a
|
||
// owner id could be deleted while this ccb was in use.
|
||
//
|
||
|
||
*RemainingQuota = 0;
|
||
*TotalQuota = 0;
|
||
leave;
|
||
}
|
||
|
||
UserData = IndexRow.DataPart.Data;
|
||
|
||
if (UserData->QuotaUsed >= UserData->QuotaLimit) {
|
||
|
||
*RemainingQuota = 0;
|
||
|
||
} else {
|
||
|
||
*RemainingQuota = UserData->QuotaLimit - UserData->QuotaUsed;
|
||
}
|
||
|
||
*TotalQuota = UserData->QuotaLimit;
|
||
|
||
} finally {
|
||
|
||
NtOfsReleaseMap( IrpContext, &MapHandle );
|
||
NtfsReleaseScb( IrpContext, Vcb->QuotaTableScb );
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
PQUOTA_CONTROL_BLOCK
|
||
NtfsInitializeQuotaControlBlock (
|
||
IN PVCB Vcb,
|
||
IN ULONG OwnerId
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine returns the quota control block field specified owner. First
|
||
a lookup is done in the quota control table for an existing quota control
|
||
block. If there is no quota control block, then a new one is created.
|
||
|
||
Arguments:
|
||
|
||
Vcb - Supplies the volume control block.
|
||
|
||
OwnerId - Supplies the requested owner id.
|
||
|
||
Return Value:
|
||
|
||
Returns a quota control block for the owner.
|
||
|
||
--*/
|
||
|
||
{
|
||
PQUOTA_CONTROL_BLOCK QuotaControl;
|
||
BOOLEAN NewEntry;
|
||
PQUOTA_CONTROL_BLOCK InitQuotaControl;
|
||
PFAST_MUTEX Lock = NULL;
|
||
PVOID NodeOrParent;
|
||
TABLE_SEARCH_RESULT SearchResult;
|
||
|
||
PAGED_CODE();
|
||
|
||
ASSERT( OwnerId != 0 );
|
||
|
||
//
|
||
// Lock the quota table.
|
||
//
|
||
|
||
ExAcquireFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
|
||
try {
|
||
|
||
InitQuotaControl = Vcb->QuotaControlTemplate;
|
||
InitQuotaControl->OwnerId = OwnerId;
|
||
|
||
QuotaControl = RtlLookupElementGenericTableFull( &Vcb->QuotaControlTable,
|
||
InitQuotaControl,
|
||
&NodeOrParent,
|
||
&SearchResult );
|
||
|
||
if (QuotaControl == NULL) {
|
||
|
||
//
|
||
// Allocate and initialize the lock.
|
||
//
|
||
|
||
Lock = NtfsAllocatePoolWithTag( NonPagedPool,
|
||
sizeof( FAST_MUTEX ),
|
||
'QftN' );
|
||
|
||
ExInitializeFastMutex( Lock );
|
||
|
||
//
|
||
// Insert table element into table.
|
||
//
|
||
|
||
QuotaControl = RtlInsertElementGenericTableFull( &Vcb->QuotaControlTable,
|
||
InitQuotaControl,
|
||
sizeof( QUOTA_CONTROL_BLOCK ) + SIZEOF_QUOTA_USER_DATA,
|
||
&NewEntry,
|
||
NodeOrParent,
|
||
SearchResult );
|
||
|
||
ASSERT( IsQuadAligned( &QuotaControl->QuickIndexHint ));
|
||
|
||
QuotaControl->QuotaControlLock = Lock;
|
||
Lock = NULL;
|
||
}
|
||
|
||
//
|
||
// Update the reference count and add set the pointer in the Fcb.
|
||
//
|
||
|
||
InterlockedIncrement( &QuotaControl->ReferenceCount );
|
||
|
||
ASSERT( OwnerId == QuotaControl->OwnerId );
|
||
|
||
} finally {
|
||
|
||
//
|
||
// Clean up.
|
||
//
|
||
|
||
if (Lock != NULL) {
|
||
NtfsFreePool( Lock );
|
||
}
|
||
|
||
ExReleaseFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
|
||
}
|
||
|
||
return QuotaControl;
|
||
}
|
||
|
||
|
||
VOID
|
||
NtfsInitializeQuotaIndex (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PFCB Fcb,
|
||
IN PVCB Vcb
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine opens the quota index for the volume. If the index does not
|
||
exist it is created and initialized.
|
||
|
||
Arguments:
|
||
|
||
Fcb - Pointer to Fcb for the quota file.
|
||
|
||
Vcb - Volume control block for volume be mounted.
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
ULONG Key;
|
||
NTSTATUS Status;
|
||
INDEX_ROW IndexRow;
|
||
MAP_HANDLE MapHandle;
|
||
QUOTA_USER_DATA QuotaData;
|
||
UNICODE_STRING IndexName = CONSTANT_UNICODE_STRING( L"$Q" );
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// Initialize quota table and fast mutex.
|
||
//
|
||
|
||
ExInitializeFastMutex( &Vcb->QuotaControlLock );
|
||
|
||
RtlInitializeGenericTable( &Vcb->QuotaControlTable,
|
||
NtfsQuotaTableCompare,
|
||
NtfsQuotaTableAllocate,
|
||
NtfsQuotaTableFree,
|
||
NULL );
|
||
|
||
ReInitializeQuotaIndex:
|
||
|
||
NtOfsCreateIndex( IrpContext,
|
||
Fcb,
|
||
IndexName,
|
||
CREATE_OR_OPEN,
|
||
0,
|
||
COLLATION_NTOFS_ULONG,
|
||
NtOfsCollateUlong,
|
||
NULL,
|
||
&Vcb->QuotaTableScb );
|
||
|
||
IndexName.Buffer = L"$O";
|
||
|
||
NtOfsCreateIndex( IrpContext,
|
||
Fcb,
|
||
IndexName,
|
||
CREATE_OR_OPEN,
|
||
0,
|
||
COLLATION_NTOFS_SID,
|
||
NtOfsCollateSid,
|
||
NULL,
|
||
&Vcb->OwnerIdTableScb );
|
||
|
||
//
|
||
// Find the next owner id to allocate.
|
||
//
|
||
|
||
NtfsAcquireExclusiveScb( IrpContext, Vcb->QuotaTableScb );
|
||
|
||
try {
|
||
|
||
//
|
||
// Initialize quota delete secquence number.
|
||
//
|
||
|
||
Vcb->QuotaDeleteSecquence = 1;
|
||
|
||
//
|
||
// Load the quota flags.
|
||
//
|
||
|
||
Key = QUOTA_DEFAULTS_ID;
|
||
IndexRow.KeyPart.KeyLength = sizeof( ULONG );
|
||
IndexRow.KeyPart.Key = &Key;
|
||
|
||
Status = NtOfsFindRecord( IrpContext,
|
||
Vcb->QuotaTableScb,
|
||
&IndexRow.KeyPart,
|
||
&IndexRow,
|
||
&MapHandle,
|
||
NULL);
|
||
|
||
if (NT_SUCCESS( Status )) {
|
||
|
||
//
|
||
// Make sure this is the correct version.
|
||
//
|
||
|
||
if (((PQUOTA_USER_DATA) IndexRow.DataPart.Data)->QuotaVersion > QUOTA_USER_VERSION) {
|
||
|
||
//
|
||
// Release the index map handle.
|
||
//
|
||
|
||
NtOfsReleaseMap( IrpContext, &MapHandle );
|
||
|
||
//
|
||
// Wrong version close the quota index this will
|
||
// pervent use from doing anything with quotas.
|
||
//
|
||
|
||
NtOfsCloseIndex( IrpContext, Vcb->QuotaTableScb );
|
||
Vcb->QuotaTableScb = NULL;
|
||
|
||
leave;
|
||
}
|
||
|
||
//
|
||
// If this is an old version delete it.
|
||
//
|
||
|
||
if (((PQUOTA_USER_DATA) IndexRow.DataPart.Data)->QuotaVersion < QUOTA_USER_VERSION) {
|
||
|
||
DebugTrace( 0, Dbg, ( "NtfsInitializeQuotaIndex: Deleting version 1 quota index\n" ));
|
||
|
||
//
|
||
// Release the index map handle.
|
||
//
|
||
|
||
NtOfsReleaseMap( IrpContext, &MapHandle );
|
||
|
||
//
|
||
// Increment the cleanup count so the FCB does not
|
||
// go away.
|
||
//
|
||
|
||
Fcb->CleanupCount += 1;
|
||
|
||
//
|
||
// This is an old version of the quota file
|
||
// delete it the owner id index and start over again.
|
||
//
|
||
|
||
NtOfsDeleteIndex( IrpContext, Fcb, Vcb->QuotaTableScb );
|
||
|
||
NtOfsCloseIndex( IrpContext, Vcb->QuotaTableScb );
|
||
Vcb->QuotaTableScb = NULL;
|
||
|
||
//
|
||
// Delete the owner index too.
|
||
//
|
||
|
||
NtOfsDeleteIndex( IrpContext, Fcb, Vcb->OwnerIdTableScb );
|
||
|
||
NtOfsCloseIndex( IrpContext, Vcb->OwnerIdTableScb );
|
||
Vcb->OwnerIdTableScb = NULL;
|
||
|
||
NtfsCommitCurrentTransaction( IrpContext );
|
||
|
||
//
|
||
// Restore the cleanup count
|
||
//
|
||
|
||
Fcb->CleanupCount -= 1;
|
||
|
||
IndexName.Buffer = L"$Q";
|
||
|
||
goto ReInitializeQuotaIndex;
|
||
}
|
||
|
||
//
|
||
// The index already exists, just initialize the quota
|
||
// fields in the VCB.
|
||
//
|
||
|
||
Vcb->QuotaFlags = ((PQUOTA_USER_DATA) IndexRow.DataPart.Data)->QuotaFlags;
|
||
|
||
//
|
||
// Release the index map handle.
|
||
//
|
||
|
||
NtOfsReleaseMap( IrpContext, &MapHandle );
|
||
|
||
} else if (Status == STATUS_NO_MATCH) {
|
||
|
||
//
|
||
// The index was newly created.
|
||
// Create a default quota data row.
|
||
//
|
||
|
||
Key = QUOTA_DEFAULTS_ID;
|
||
|
||
RtlZeroMemory( &QuotaData, sizeof( QUOTA_USER_DATA ));
|
||
|
||
//
|
||
// Indicate that the quota needs to be rebuilt.
|
||
//
|
||
|
||
QuotaData.QuotaVersion = QUOTA_USER_VERSION;
|
||
|
||
QuotaData.QuotaFlags = QUOTA_FLAG_DEFAULT_LIMITS;
|
||
|
||
QuotaData.QuotaThreshold = MAXULONGLONG;
|
||
QuotaData.QuotaLimit = MAXULONGLONG;
|
||
KeQuerySystemTime( (PLARGE_INTEGER) &QuotaData.QuotaChangeTime );
|
||
|
||
IndexRow.KeyPart.KeyLength = sizeof( ULONG );
|
||
IndexRow.KeyPart.Key = &Key;
|
||
IndexRow.DataPart.DataLength = SIZEOF_QUOTA_USER_DATA;
|
||
IndexRow.DataPart.Data = &QuotaData;
|
||
|
||
NtOfsAddRecords( IrpContext,
|
||
Vcb->QuotaTableScb,
|
||
1,
|
||
&IndexRow,
|
||
TRUE );
|
||
|
||
Vcb->QuotaOwnerId = QUOTA_FISRT_USER_ID;
|
||
|
||
Vcb->QuotaFlags = QuotaData.QuotaFlags;
|
||
}
|
||
|
||
Key = MAXULONG;
|
||
IndexRow.KeyPart.KeyLength = sizeof( ULONG );
|
||
IndexRow.KeyPart.Key = &Key;
|
||
|
||
Status = NtOfsFindLastRecord( IrpContext,
|
||
Vcb->QuotaTableScb,
|
||
&IndexRow.KeyPart,
|
||
&IndexRow,
|
||
&MapHandle );
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
|
||
//
|
||
// This call should never fail.
|
||
//
|
||
|
||
ASSERT( NT_SUCCESS( Status) );
|
||
SetFlag( Vcb->QuotaFlags, QUOTA_FLAG_CORRUPT);
|
||
leave;
|
||
}
|
||
|
||
Key = *((PULONG) IndexRow.KeyPart.Key) + 1;
|
||
|
||
if (Key < QUOTA_FISRT_USER_ID) {
|
||
Key = QUOTA_FISRT_USER_ID;
|
||
}
|
||
|
||
Vcb->QuotaOwnerId = Key;
|
||
|
||
//
|
||
// Release the index map handle.
|
||
//
|
||
|
||
NtOfsReleaseMap( IrpContext, &MapHandle );
|
||
|
||
//
|
||
// Get the administrator ID so it can be protected from quota
|
||
// limits.
|
||
//
|
||
|
||
Vcb->AdministratorId = NtfsGetOwnerId( IrpContext,
|
||
SeExports->SeAliasAdminsSid,
|
||
TRUE,
|
||
NULL );
|
||
|
||
if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_REQUESTED )) {
|
||
|
||
//
|
||
// Allocate and initialize the template control block.
|
||
// Allocate enough space in the quota control block for the index
|
||
// data part. This is used as the new record when calling update
|
||
// record. This template is only allocated once and then it is
|
||
// saved in the vcb.
|
||
//
|
||
|
||
Vcb->QuotaControlTemplate = NtfsAllocatePoolWithTag( PagedPool,
|
||
sizeof( QUOTA_CONTROL_BLOCK ) + SIZEOF_QUOTA_USER_DATA,
|
||
'QftN' );
|
||
|
||
RtlZeroMemory( Vcb->QuotaControlTemplate,
|
||
sizeof( QUOTA_CONTROL_BLOCK ) +
|
||
SIZEOF_QUOTA_USER_DATA );
|
||
|
||
Vcb->QuotaControlTemplate->NodeTypeCode = NTFS_NTC_QUOTA_CONTROL;
|
||
Vcb->QuotaControlTemplate->NodeByteSize = sizeof( QUOTA_CONTROL_BLOCK ) + SIZEOF_QUOTA_USER_DATA;
|
||
}
|
||
|
||
//
|
||
// Fix up the quota on the root directory.
|
||
//
|
||
|
||
NtfsConditionallyFixupQuota( IrpContext, Vcb->RootIndexScb->Fcb );
|
||
|
||
} finally {
|
||
|
||
if (Vcb->QuotaTableScb != NULL) {
|
||
NtfsReleaseScb( IrpContext, Vcb->QuotaTableScb );
|
||
}
|
||
}
|
||
|
||
//
|
||
// If the quota tracking has been requested and the quotas need to be
|
||
// repaired then try to repair them now.
|
||
//
|
||
|
||
if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_REQUESTED) &&
|
||
FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_OUT_OF_DATE | QUOTA_FLAG_CORRUPT | QUOTA_FLAG_PENDING_DELETES )) {
|
||
NtfsPostRepairQuotaIndex( IrpContext, Vcb );
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
VOID
|
||
NtfsMarkUserLimit (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PVOID Context
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine marks a user's quota data entry to indicate that the user
|
||
has exceeded quota. The event is also logged.
|
||
|
||
Arguments:
|
||
|
||
Context - Supplies a pointer to the referenced quota control block.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
PQUOTA_CONTROL_BLOCK QuotaControl = Context;
|
||
PVCB Vcb = IrpContext->Vcb;
|
||
LARGE_INTEGER CurrentTime;
|
||
PQUOTA_USER_DATA UserData;
|
||
INDEX_ROW IndexRow;
|
||
INDEX_KEY IndexKey;
|
||
MAP_HANDLE MapHandle;
|
||
NTSTATUS Status;
|
||
BOOLEAN QuotaTableAcquired = FALSE;
|
||
|
||
PAGED_CODE();
|
||
|
||
DebugTrace( 0, Dbg, ( "NtfsMarkUserLimit: Quota limit called for owner id = %lx\n", QuotaControl->OwnerId ));
|
||
|
||
NtOfsInitializeMapHandle( &MapHandle );
|
||
|
||
//
|
||
// Acquire the VCB shared and check whether we should
|
||
// continue.
|
||
//
|
||
|
||
NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
|
||
|
||
try {
|
||
|
||
if (!NtfsIsVcbAvailable( Vcb )) {
|
||
|
||
//
|
||
// The volume is going away, bail out.
|
||
//
|
||
|
||
Status = STATUS_VOLUME_DISMOUNTED;
|
||
leave;
|
||
}
|
||
|
||
NtfsAcquireExclusiveScb( IrpContext, Vcb->QuotaTableScb );
|
||
QuotaTableAcquired = TRUE;
|
||
|
||
//
|
||
// Get the user's quota data entry.
|
||
//
|
||
|
||
IndexKey.KeyLength = sizeof( ULONG );
|
||
IndexKey.Key = &QuotaControl->OwnerId;
|
||
|
||
Status = NtOfsFindRecord( IrpContext,
|
||
Vcb->QuotaTableScb,
|
||
&IndexKey,
|
||
&IndexRow,
|
||
&MapHandle,
|
||
&QuotaControl->QuickIndexHint );
|
||
|
||
if (!NT_SUCCESS( Status ) ||
|
||
(IndexRow.DataPart.DataLength < SIZEOF_QUOTA_USER_DATA + FIELD_OFFSET( SID, SubAuthority )) ||
|
||
((ULONG) SeLengthSid( &(((PQUOTA_USER_DATA) (IndexRow.DataPart.Data))->QuotaSid)) + SIZEOF_QUOTA_USER_DATA !=
|
||
IndexRow.DataPart.DataLength)) {
|
||
|
||
//
|
||
// This look up should not fail.
|
||
//
|
||
|
||
ASSERT( NT_SUCCESS( Status ));
|
||
ASSERTMSG(( "NTFS: corrupt quotasid\n" ), FALSE);
|
||
|
||
NtfsMarkQuotaCorrupt( IrpContext, IrpContext->Vcb );
|
||
leave;
|
||
}
|
||
|
||
//
|
||
// Space is allocated for the new record after the quota control
|
||
// block.
|
||
//
|
||
|
||
UserData = (PQUOTA_USER_DATA) (QuotaControl + 1);
|
||
ASSERT( IndexRow.DataPart.DataLength >= SIZEOF_QUOTA_USER_DATA );
|
||
|
||
RtlCopyMemory( UserData,
|
||
IndexRow.DataPart.Data,
|
||
SIZEOF_QUOTA_USER_DATA );
|
||
|
||
KeQuerySystemTime( &CurrentTime );
|
||
UserData->QuotaChangeTime = CurrentTime.QuadPart;
|
||
|
||
//
|
||
// Indicate that user exceeded quota.
|
||
//
|
||
|
||
UserData->QuotaExceededTime = CurrentTime.QuadPart;
|
||
SetFlag( UserData->QuotaFlags, QUOTA_FLAG_LIMIT_REACHED );
|
||
|
||
//
|
||
// Log the limit event. If this fails then leave.
|
||
//
|
||
|
||
if (!NtfsLogEvent( IrpContext,
|
||
IndexRow.DataPart.Data,
|
||
IO_FILE_QUOTA_LIMIT,
|
||
STATUS_DISK_FULL )) {
|
||
leave;
|
||
}
|
||
|
||
//
|
||
// The key length does not change.
|
||
//
|
||
|
||
IndexRow.KeyPart.Key = &QuotaControl->OwnerId;
|
||
ASSERT( IndexRow.KeyPart.KeyLength == sizeof( ULONG ));
|
||
IndexRow.DataPart.Data = UserData;
|
||
IndexRow.DataPart.DataLength = SIZEOF_QUOTA_USER_DATA;
|
||
|
||
NtOfsUpdateRecord( IrpContext,
|
||
Vcb->QuotaTableScb,
|
||
1,
|
||
&IndexRow,
|
||
&QuotaControl->QuickIndexHint,
|
||
&MapHandle );
|
||
|
||
} except( NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
|
||
|
||
Status = IrpContext->TopLevelIrpContext->ExceptionStatus;
|
||
}
|
||
|
||
//
|
||
// The request will be retied if the status is can't wait or log file full.
|
||
//
|
||
|
||
if ((Status != STATUS_CANT_WAIT) && (Status != STATUS_LOG_FILE_FULL)) {
|
||
|
||
//
|
||
// If we will not be called back, then no matter what happened
|
||
// dereference the quota control block and clear the post flag.
|
||
//
|
||
|
||
ExAcquireFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
ASSERT( FlagOn( QuotaControl->Flags, QUOTA_FLAG_LIMIT_POSTED ));
|
||
ClearFlag( QuotaControl->Flags, QUOTA_FLAG_LIMIT_POSTED );
|
||
ExReleaseFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
|
||
NtfsDereferenceQuotaControlBlock( Vcb, &QuotaControl );
|
||
}
|
||
|
||
//
|
||
// Release the index map handle.
|
||
//
|
||
|
||
NtOfsReleaseMap( IrpContext, &MapHandle );
|
||
|
||
if (QuotaTableAcquired) {
|
||
|
||
NtfsReleaseScb( IrpContext, Vcb->QuotaTableScb );
|
||
}
|
||
|
||
NtfsReleaseVcb( IrpContext, Vcb );
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
|
||
NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
VOID
|
||
NtfsMoveQuotaOwner (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PFCB Fcb,
|
||
IN PSECURITY_DESCRIPTOR Security
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine changes the owner id and quota charged for a file when the
|
||
file owner is changed.
|
||
|
||
Arguments:
|
||
|
||
Fcb - Pointer to fcb being opened.
|
||
|
||
Security - Pointer to the new security descriptor
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
LONGLONG QuotaCharged;
|
||
ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
|
||
PSTANDARD_INFORMATION StandardInformation;
|
||
PSID Sid = NULL;
|
||
ULONG OwnerId;
|
||
NTSTATUS Status;
|
||
BOOLEAN OwnerDefaulted;
|
||
|
||
PAGED_CODE();
|
||
|
||
if (!NtfsPerformQuotaOperation(Fcb)) {
|
||
return;
|
||
}
|
||
|
||
//
|
||
// Extract the security id from the security descriptor.
|
||
//
|
||
|
||
Status = RtlGetOwnerSecurityDescriptor( Security,
|
||
&Sid,
|
||
&OwnerDefaulted );
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
NtfsRaiseStatus( IrpContext, Status, NULL, Fcb );
|
||
}
|
||
|
||
//
|
||
// If we didn't get a SID then we can't move the owner.
|
||
//
|
||
|
||
if (Sid == NULL) {
|
||
|
||
return;
|
||
}
|
||
|
||
//
|
||
// Generate a owner id for the Fcb.
|
||
//
|
||
|
||
OwnerId = NtfsGetOwnerId( IrpContext, Sid, TRUE, NULL );
|
||
|
||
if (OwnerId == Fcb->OwnerId) {
|
||
|
||
//
|
||
// The owner is not changing so just return.
|
||
//
|
||
|
||
return;
|
||
}
|
||
|
||
//
|
||
// Initialize the context structure and map handle.
|
||
//
|
||
|
||
NtfsInitializeAttributeContext( &AttrContext );
|
||
|
||
//
|
||
// Preacquire the quota index exclusive since an entry may need to
|
||
// be added.
|
||
//
|
||
|
||
NtfsAcquireExclusiveScb( IrpContext, Fcb->Vcb->QuotaTableScb );
|
||
|
||
try {
|
||
|
||
//
|
||
// Locate the standard information, it must be there.
|
||
//
|
||
|
||
if (!NtfsLookupAttributeByCode( IrpContext,
|
||
Fcb,
|
||
&Fcb->FileReference,
|
||
$STANDARD_INFORMATION,
|
||
&AttrContext )) {
|
||
|
||
DebugTrace( 0, Dbg, ("Can't find standard information\n") );
|
||
|
||
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
|
||
}
|
||
|
||
StandardInformation = (PSTANDARD_INFORMATION) NtfsAttributeValue( NtfsFoundAttribute( &AttrContext ));
|
||
|
||
QuotaCharged = -((LONGLONG) StandardInformation->QuotaCharged);
|
||
|
||
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
||
|
||
//
|
||
// Remove the quota from the old owner.
|
||
//
|
||
|
||
NtfsUpdateFileQuota( IrpContext,
|
||
Fcb,
|
||
&QuotaCharged,
|
||
TRUE,
|
||
FALSE );
|
||
|
||
//
|
||
// Set the new owner id.
|
||
//
|
||
|
||
Fcb->OwnerId = OwnerId;
|
||
|
||
//
|
||
// Note the old quota block is kept around until the operation is
|
||
// complete. This is so the recovery code does not have allocate
|
||
// a memory if the old quota block is needed. This is done in
|
||
// NtfsCommonSetSecurityInfo.
|
||
//
|
||
|
||
Fcb->QuotaControl = NtfsInitializeQuotaControlBlock( Fcb->Vcb, OwnerId );
|
||
|
||
QuotaCharged = -QuotaCharged;
|
||
|
||
//
|
||
// Try to charge the quota to the new owner.
|
||
//
|
||
|
||
NtfsUpdateFileQuota( IrpContext,
|
||
Fcb,
|
||
&QuotaCharged,
|
||
TRUE,
|
||
TRUE );
|
||
|
||
SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
|
||
|
||
} finally {
|
||
|
||
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
||
NtfsReleaseScb( IrpContext, Fcb->Vcb->QuotaTableScb );
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
VOID
|
||
NtfsMarkQuotaCorrupt (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PVCB Vcb
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine attempts to mark the quota index corrupt. It will
|
||
also attempt post a request to rebuild the quota index.
|
||
|
||
Arguments:
|
||
|
||
Vcb - Supplies a pointer the the volume who quota data is corrupt.
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
DebugTrace( 0, Dbg, ( "NtfsMarkQuotaCorrupt: Marking quota dirty on Vcb = %lx\n", Vcb));
|
||
|
||
if (!FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_CORRUPT )) {
|
||
|
||
//
|
||
// If the quota were not previous corrupt then log an event
|
||
// so others know this occured.
|
||
//
|
||
|
||
NtfsLogEvent( IrpContext,
|
||
NULL,
|
||
IO_FILE_QUOTA_CORRUPT,
|
||
STATUS_FILE_CORRUPT_ERROR );
|
||
}
|
||
|
||
ExAcquireFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
|
||
SetFlag( Vcb->QuotaFlags, QUOTA_FLAG_CORRUPT );
|
||
SetFlag( Vcb->QuotaState, VCB_QUOTA_SAVE_QUOTA_FLAGS );
|
||
|
||
//
|
||
// Since the index is corrupt there is no point in tracking the
|
||
// quota usage.
|
||
//
|
||
|
||
ClearFlag( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_ENABLED );
|
||
|
||
ExReleaseFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
|
||
//
|
||
// Do not save the flags here since the quota scb may be acquired
|
||
// shared. The repair will save the flags when it runs.
|
||
// Try to fix the problems.
|
||
//
|
||
|
||
NtfsPostRepairQuotaIndex( IrpContext, Vcb );
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
VOID
|
||
NtfsPostRepairQuotaIndex (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PVCB Vcb
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine posts a request to recalculate all of the user quota data.
|
||
|
||
Arguments:
|
||
|
||
Vcb - Volume control block for volume whos quota needs to be fixed.
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
PAGED_CODE();
|
||
|
||
try {
|
||
|
||
ExAcquireFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
|
||
if (FlagOn( Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING)) {
|
||
ExReleaseFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
leave;
|
||
}
|
||
|
||
if (Vcb->QuotaControlTemplate == NULL) {
|
||
|
||
//
|
||
// Allocate and initialize the template control block.
|
||
// Allocate enough space in the quota control block for the index
|
||
// data part. This is used as the new record when calling update
|
||
// record. This template is only allocated once and then it is
|
||
// saved in the vcb.
|
||
//
|
||
|
||
Vcb->QuotaControlTemplate = NtfsAllocatePoolWithTag( PagedPool,
|
||
sizeof( QUOTA_CONTROL_BLOCK ) + SIZEOF_QUOTA_USER_DATA,
|
||
'QftN' );
|
||
|
||
RtlZeroMemory( Vcb->QuotaControlTemplate,
|
||
sizeof( QUOTA_CONTROL_BLOCK ) + SIZEOF_QUOTA_USER_DATA );
|
||
|
||
Vcb->QuotaControlTemplate->NodeTypeCode = NTFS_NTC_QUOTA_CONTROL;
|
||
Vcb->QuotaControlTemplate->NodeByteSize = sizeof( QUOTA_CONTROL_BLOCK ) + SIZEOF_QUOTA_USER_DATA;
|
||
|
||
}
|
||
|
||
SetFlag( Vcb->QuotaState, VCB_QUOTA_REPAIR_POSTED );
|
||
ExReleaseFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
|
||
//
|
||
// Post this special request.
|
||
//
|
||
|
||
NtfsPostSpecial( IrpContext,
|
||
Vcb,
|
||
NtfsRepairQuotaIndex,
|
||
NULL );
|
||
|
||
|
||
} finally {
|
||
|
||
if (AbnormalTermination()) {
|
||
|
||
ExAcquireFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
ClearFlag( Vcb->QuotaState, VCB_QUOTA_REPAIR_POSTED);
|
||
ExReleaseFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
}
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
VOID
|
||
NtfsPostUserLimit (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PVCB Vcb,
|
||
IN PQUOTA_CONTROL_BLOCK QuotaControl
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine posts a request to save the fact that the user has exceeded
|
||
their limit.
|
||
|
||
Arguments:
|
||
|
||
Vcb - Volume control block for volume whos quota needs to be fixed.
|
||
|
||
QuotaControl - Quota control block for the user.
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
PAGED_CODE();
|
||
|
||
try {
|
||
|
||
ExAcquireFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
|
||
if (FlagOn( QuotaControl->Flags, QUOTA_FLAG_LIMIT_POSTED )) {
|
||
ExReleaseFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
leave;
|
||
}
|
||
|
||
SetFlag( QuotaControl->Flags, QUOTA_FLAG_LIMIT_POSTED );
|
||
|
||
//
|
||
// Reference the quota control block so it does not go away.
|
||
//
|
||
|
||
ASSERT( QuotaControl->ReferenceCount > 0 );
|
||
InterlockedIncrement( &QuotaControl->ReferenceCount );
|
||
|
||
ExReleaseFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
|
||
//
|
||
// Post this special request.
|
||
//
|
||
|
||
NtfsPostSpecial( IrpContext,
|
||
Vcb,
|
||
NtfsMarkUserLimit,
|
||
QuotaControl );
|
||
|
||
} finally {
|
||
|
||
if (AbnormalTermination()) {
|
||
|
||
ExAcquireFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
ClearFlag( QuotaControl->Flags, QUOTA_FLAG_LIMIT_POSTED );
|
||
ExReleaseFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
}
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NtfsPrepareForDelete (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PVCB Vcb,
|
||
IN PSID Sid
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine determines if an owner id is a candidate for deletion. If
|
||
the id appears deletable its user data is reset to the defaults and the
|
||
entry is marked as deleted. Later a worker thread will do the actual
|
||
deletion.
|
||
|
||
Arguments:
|
||
|
||
Vcb - Supplies a pointer to the volume containing the entry to be deleted.
|
||
|
||
Sid - Security id to to be deleted.
|
||
|
||
Return Value:
|
||
|
||
Returns a status indicating of the id was deletable at this time.
|
||
|
||
--*/
|
||
{
|
||
ULONG OwnerId;
|
||
ULONG DefaultOwnerId;
|
||
NTSTATUS Status = STATUS_SUCCESS;
|
||
INDEX_ROW IndexRow;
|
||
INDEX_ROW NewIndexRow;
|
||
INDEX_KEY IndexKey;
|
||
MAP_HANDLE MapHandle;
|
||
PQUOTA_CONTROL_BLOCK QuotaControl;
|
||
QUOTA_USER_DATA NewQuotaData;
|
||
PSCB QuotaScb = Vcb->QuotaTableScb;
|
||
PSCB OwnerIdScb = Vcb->OwnerIdTableScb;
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// Determine the Sid length.
|
||
//
|
||
|
||
IndexKey.KeyLength = RtlLengthSid( Sid );
|
||
IndexKey.Key = Sid;
|
||
|
||
//
|
||
// Acquire Owner id and quota index exclusive.
|
||
//
|
||
|
||
NtfsAcquireExclusiveScb( IrpContext, QuotaScb );
|
||
NtfsAcquireExclusiveScb( IrpContext, OwnerIdScb );
|
||
ExAcquireFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
|
||
NtOfsInitializeMapHandle( &MapHandle );
|
||
|
||
try {
|
||
|
||
//
|
||
// Look up the SID in the owner index.
|
||
//
|
||
|
||
Status = NtOfsFindRecord( IrpContext,
|
||
OwnerIdScb,
|
||
&IndexKey,
|
||
&IndexRow,
|
||
&MapHandle,
|
||
NULL );
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
leave;
|
||
}
|
||
|
||
//
|
||
// If the sid was found then capture the owner id.
|
||
//
|
||
|
||
ASSERT( IndexRow.DataPart.DataLength == sizeof( ULONG ));
|
||
OwnerId = *((PULONG) IndexRow.DataPart.Data);
|
||
|
||
//
|
||
// Release the index map handle.
|
||
//
|
||
|
||
NtOfsReleaseMap( IrpContext, &MapHandle );
|
||
|
||
//
|
||
// Find the existing record and update it.
|
||
//
|
||
|
||
IndexKey.KeyLength = sizeof( ULONG );
|
||
IndexKey.Key = &OwnerId;
|
||
|
||
Status = NtOfsFindRecord( IrpContext,
|
||
QuotaScb,
|
||
&IndexKey,
|
||
&IndexRow,
|
||
&MapHandle,
|
||
NULL );
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
|
||
ASSERT( NT_SUCCESS( Status ));
|
||
NtfsMarkQuotaCorrupt( IrpContext, Vcb );
|
||
leave;
|
||
}
|
||
|
||
RtlCopyMemory( &NewQuotaData, IndexRow.DataPart.Data, SIZEOF_QUOTA_USER_DATA );
|
||
|
||
//
|
||
// Check to see if there is a quota control entry
|
||
// for this id.
|
||
//
|
||
|
||
ASSERT( FIELD_OFFSET( QUOTA_CONTROL_BLOCK, OwnerId ) <= FIELD_OFFSET( INDEX_ROW, KeyPart.Key ));
|
||
|
||
QuotaControl = RtlLookupElementGenericTable( &Vcb->QuotaControlTable,
|
||
CONTAINING_RECORD( &IndexRow.KeyPart.Key,
|
||
QUOTA_CONTROL_BLOCK,
|
||
OwnerId ));
|
||
|
||
//
|
||
// If there is a quota control entry or there is now
|
||
// some quota charged, then the entry cannot be deleted.
|
||
//
|
||
|
||
if ((QuotaControl != NULL) || (NewQuotaData.QuotaUsed != 0)) {
|
||
|
||
Status = STATUS_CANNOT_DELETE;
|
||
leave;
|
||
}
|
||
|
||
//
|
||
// Find the default quota record.
|
||
//
|
||
|
||
DefaultOwnerId = QUOTA_DEFAULTS_ID;
|
||
IndexKey.KeyLength = sizeof( ULONG );
|
||
IndexKey.Key = &DefaultOwnerId;
|
||
|
||
NtOfsReleaseMap( IrpContext, &MapHandle );
|
||
|
||
Status = NtOfsFindRecord( IrpContext,
|
||
QuotaScb,
|
||
&IndexKey,
|
||
&IndexRow,
|
||
&MapHandle,
|
||
NULL );
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
NtfsRaiseStatus( IrpContext, STATUS_QUOTA_LIST_INCONSISTENT, NULL, QuotaScb->Fcb );
|
||
}
|
||
|
||
//
|
||
// Set the user entry to the current defaults. Then if the entry
|
||
// is really inuse it will appear that is came back after the delete.
|
||
//
|
||
|
||
RtlCopyMemory( &NewQuotaData,
|
||
IndexRow.DataPart.Data,
|
||
SIZEOF_QUOTA_USER_DATA );
|
||
|
||
ClearFlag( NewQuotaData.QuotaFlags, ~QUOTA_FLAG_USER_MASK );
|
||
|
||
//
|
||
// Set the deleted flag.
|
||
//
|
||
|
||
SetFlag( NewQuotaData.QuotaFlags, QUOTA_FLAG_ID_DELETED );
|
||
|
||
//
|
||
// The key length does not change.
|
||
//
|
||
|
||
NewIndexRow.KeyPart.Key = &OwnerId;
|
||
NewIndexRow.KeyPart.KeyLength = sizeof( ULONG );
|
||
NewIndexRow.DataPart.Data = &NewQuotaData;
|
||
NewIndexRow.DataPart.DataLength = SIZEOF_QUOTA_USER_DATA;
|
||
|
||
NtOfsUpdateRecord( IrpContext,
|
||
QuotaScb,
|
||
1,
|
||
&NewIndexRow,
|
||
NULL,
|
||
NULL );
|
||
|
||
//
|
||
// Update the delete secquence number this is used to indicate
|
||
// another id has been deleted. If the repair code is in the
|
||
// middle of its scan it must restart the scan.
|
||
//
|
||
|
||
Vcb->QuotaDeleteSecquence += 1;
|
||
|
||
//
|
||
// Indicate there are pending deletes.
|
||
//
|
||
|
||
if (!FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_PENDING_DELETES )) {
|
||
|
||
SetFlag( Vcb->QuotaFlags, QUOTA_FLAG_PENDING_DELETES );
|
||
|
||
ASSERT( IndexRow.DataPart.DataLength <= sizeof( QUOTA_USER_DATA ));
|
||
|
||
RtlCopyMemory( &NewQuotaData,
|
||
IndexRow.DataPart.Data,
|
||
IndexRow.DataPart.DataLength );
|
||
|
||
//
|
||
// Update the changed fields in the record.
|
||
//
|
||
|
||
NewQuotaData.QuotaFlags = Vcb->QuotaFlags;
|
||
|
||
//
|
||
// Note the sizes in the IndexRow stay the same.
|
||
//
|
||
|
||
IndexRow.KeyPart.Key = &DefaultOwnerId;
|
||
ASSERT( IndexRow.KeyPart.KeyLength == sizeof( ULONG ));
|
||
IndexRow.DataPart.Data = &NewQuotaData;
|
||
|
||
NtOfsUpdateRecord( IrpContext,
|
||
QuotaScb,
|
||
1,
|
||
&IndexRow,
|
||
NULL,
|
||
NULL );
|
||
}
|
||
|
||
} finally {
|
||
|
||
//
|
||
// Release the index map handle and index resources.
|
||
//
|
||
|
||
NtOfsReleaseMap( IrpContext, &MapHandle );
|
||
ExReleaseFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
NtfsReleaseScb( IrpContext, QuotaScb );
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
VOID
|
||
NtfsRepairQuotaIndex (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PVOID Context
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine is called by a worker thread to fix the quota indexes
|
||
and recalculate all of the quota values.
|
||
|
||
Arguments:
|
||
|
||
Context - Unused.
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
PVCB Vcb = IrpContext->Vcb;
|
||
ULONG State;
|
||
NTSTATUS Status;
|
||
ULONG RetryCount = 0;
|
||
|
||
PAGED_CODE();
|
||
|
||
UNREFERENCED_PARAMETER( Context );
|
||
|
||
try {
|
||
|
||
DebugTrace( 0, Dbg, ( "NtfsRepairQuotaIndex: Starting quota repair. Vcb = %lx\n", Vcb ));
|
||
|
||
//
|
||
// The volume could've gotten write-protected by now.
|
||
//
|
||
|
||
if (NtfsIsVolumeReadOnly( Vcb )) {
|
||
|
||
NtfsRaiseStatus( IrpContext, STATUS_MEDIA_WRITE_PROTECTED, NULL, NULL );
|
||
}
|
||
|
||
//
|
||
// Acquire the volume exclusive and the quota lock.
|
||
//
|
||
|
||
NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
|
||
ExAcquireFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
|
||
Status = STATUS_SUCCESS;
|
||
|
||
if (!FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_REQUESTED )) {
|
||
|
||
//
|
||
// There is no point in doing any of this work if tracking
|
||
// is not requested.
|
||
//
|
||
|
||
Status = STATUS_INVALID_PARAMETER;
|
||
|
||
} else if (FlagOn( Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING ) == VCB_QUOTA_REPAIR_POSTED) {
|
||
|
||
if (FlagOn( Vcb->QuotaFlags,
|
||
(QUOTA_FLAG_OUT_OF_DATE |
|
||
QUOTA_FLAG_CORRUPT |
|
||
QUOTA_FLAG_PENDING_DELETES) ) == QUOTA_FLAG_PENDING_DELETES) {
|
||
|
||
//
|
||
// Only the last to phases need to be run.
|
||
//
|
||
|
||
ClearFlag( Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING );
|
||
|
||
SetFlag( Vcb->QuotaState, VCB_QUOTA_RECALC_STARTED );
|
||
|
||
State = VCB_QUOTA_RECALC_STARTED;
|
||
|
||
//
|
||
// Capture the delete secquence number. If it changes
|
||
// before the actual deletes are done then we have to
|
||
// start over.
|
||
//
|
||
|
||
IrpContext->Union.NtfsIoContext = ULongToPtr( Vcb->QuotaDeleteSecquence );
|
||
|
||
} else {
|
||
|
||
//
|
||
// We are starting just starting. Clear the quota tracking
|
||
// flags and indicate the current state.
|
||
//
|
||
|
||
ClearFlag( Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING );
|
||
|
||
SetFlag( Vcb->QuotaState, VCB_QUOTA_CLEAR_RUNNING | VCB_QUOTA_SAVE_QUOTA_FLAGS);
|
||
|
||
ClearFlag( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_ENABLED );
|
||
|
||
SetFlag( Vcb->QuotaFlags, QUOTA_FLAG_OUT_OF_DATE );
|
||
|
||
State = VCB_QUOTA_CLEAR_RUNNING;
|
||
}
|
||
|
||
//
|
||
// Initialize the File reference to the root index.
|
||
//
|
||
|
||
NtfsSetSegmentNumber( &Vcb->QuotaFileReference,
|
||
0,
|
||
ROOT_FILE_NAME_INDEX_NUMBER );
|
||
|
||
Vcb->QuotaFileReference.SequenceNumber = 0;
|
||
|
||
NtfsLogEvent( IrpContext,
|
||
NULL,
|
||
IO_FILE_QUOTA_STARTED,
|
||
STATUS_SUCCESS );
|
||
|
||
} else {
|
||
|
||
State = FlagOn( Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING);
|
||
}
|
||
|
||
ExReleaseFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
NtfsReleaseVcb( IrpContext, Vcb );
|
||
|
||
if (FlagOn( Vcb->QuotaState, VCB_QUOTA_SAVE_QUOTA_FLAGS )) {
|
||
|
||
NtfsSaveQuotaFlagsSafe( IrpContext, Vcb );
|
||
}
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
|
||
}
|
||
|
||
//
|
||
// Determine the current state
|
||
//
|
||
|
||
switch (State) {
|
||
|
||
case VCB_QUOTA_CLEAR_RUNNING:
|
||
|
||
DebugTrace( 4, Dbg, ( "NtfsRepairQuotaIndex: Starting clear per file quota.\n" ));
|
||
|
||
//
|
||
// Clear the quota charged field in each file and clear
|
||
// all of the quota control blocks from the fcbs.
|
||
//
|
||
|
||
Status = NtfsIterateMft( IrpContext,
|
||
Vcb,
|
||
&Vcb->QuotaFileReference,
|
||
NtfsClearPerFileQuota,
|
||
NULL );
|
||
|
||
if (Status == STATUS_END_OF_FILE) {
|
||
Status = STATUS_SUCCESS;
|
||
}
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
|
||
}
|
||
|
||
RestartVerifyQuotaIndex:
|
||
|
||
//
|
||
// Update the state to the next phase.
|
||
//
|
||
|
||
ExAcquireFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
ClearFlag( Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING );
|
||
SetFlag( Vcb->QuotaState, VCB_QUOTA_INDEX_REPAIR);
|
||
ExReleaseFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
|
||
//
|
||
// NtfsClearAndVerifyQuotaIndex uses the low part of the
|
||
// file reference to store the current owner id.
|
||
// Intialize this to the first user id.
|
||
//
|
||
|
||
Vcb->QuotaFileReference.SegmentNumberLowPart = QUOTA_FISRT_USER_ID;
|
||
|
||
//
|
||
// Fall through.
|
||
//
|
||
|
||
case VCB_QUOTA_INDEX_REPAIR:
|
||
|
||
DebugTrace( 4, Dbg, ( "NtfsRepairQuotaIndex: Starting clear quota index.\n" ));
|
||
|
||
//
|
||
// Clear the quota used for each owner id.
|
||
//
|
||
|
||
NtfsClearAndVerifyQuotaIndex( IrpContext, Vcb );
|
||
|
||
//
|
||
// Update the state to the next phase.
|
||
//
|
||
|
||
ExAcquireFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
ClearFlag( Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING );
|
||
SetFlag( Vcb->QuotaState, VCB_QUOTA_OWNER_VERIFY);
|
||
ExReleaseFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
|
||
//
|
||
// Note NtfsVerifyOwnerIndex does not use any restart state,
|
||
// since it normally does not preform any transactions.
|
||
//
|
||
|
||
//
|
||
// Fall through.
|
||
//
|
||
|
||
case VCB_QUOTA_OWNER_VERIFY:
|
||
|
||
DebugTrace( 4, Dbg, ( "NtfsRepairQuotaIndex: Starting verify owner index.\n" ));
|
||
|
||
//
|
||
// Verify the owner's id points to quota user data.
|
||
//
|
||
|
||
Status = NtfsVerifyOwnerIndex( IrpContext, Vcb );
|
||
|
||
//
|
||
// Restart the rebuild with the quota index phase.
|
||
//
|
||
|
||
if (!NT_SUCCESS( Status ) ) {
|
||
|
||
if (RetryCount < 2) {
|
||
|
||
RetryCount += 1;
|
||
goto RestartVerifyQuotaIndex;
|
||
|
||
} else {
|
||
|
||
NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
|
||
}
|
||
}
|
||
|
||
//
|
||
// Update the state to the next phase.
|
||
// Start tracking quota and do enforcement as requested.
|
||
//
|
||
|
||
NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
|
||
ExAcquireFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
ClearFlag( Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING );
|
||
SetFlag( Vcb->QuotaState, VCB_QUOTA_RECALC_STARTED | VCB_QUOTA_SAVE_QUOTA_FLAGS);
|
||
|
||
if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_REQUESTED)) {
|
||
|
||
SetFlag( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_ENABLED);
|
||
Status = STATUS_SUCCESS;
|
||
|
||
} else {
|
||
|
||
//
|
||
// There is no point in doing any of this work if tracking
|
||
// is not requested.
|
||
//
|
||
|
||
Status = STATUS_INVALID_PARAMETER;
|
||
}
|
||
|
||
//
|
||
// Capture the delete secquence number. If it changes
|
||
// before the actual deletes are done then we have to
|
||
// start over.
|
||
//
|
||
|
||
IrpContext->Union.NtfsIoContext = ULongToPtr( Vcb->QuotaDeleteSecquence );
|
||
|
||
ExReleaseFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
NtfsReleaseVcb( IrpContext, Vcb );
|
||
|
||
if (FlagOn( Vcb->QuotaState, VCB_QUOTA_SAVE_QUOTA_FLAGS )) {
|
||
|
||
NtfsSaveQuotaFlagsSafe( IrpContext, Vcb );
|
||
}
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
|
||
}
|
||
|
||
//
|
||
// Initialize the File reference to the first user file.
|
||
//
|
||
|
||
NtfsSetSegmentNumber( &Vcb->QuotaFileReference,
|
||
0,
|
||
ROOT_FILE_NAME_INDEX_NUMBER );
|
||
Vcb->QuotaFileReference.SequenceNumber = 0;
|
||
|
||
//
|
||
// Fall through.
|
||
//
|
||
|
||
case VCB_QUOTA_RECALC_STARTED:
|
||
|
||
DebugTrace( 4, Dbg, ( "NtfsRepairQuotaIndex: Starting per file quota usage.\n" ));
|
||
|
||
//
|
||
// Fix the user files.
|
||
//
|
||
|
||
Status = NtfsIterateMft( IrpContext,
|
||
Vcb,
|
||
&Vcb->QuotaFileReference,
|
||
NtfsRepairPerFileQuota,
|
||
NULL );
|
||
|
||
if (Status == STATUS_END_OF_FILE) {
|
||
Status = STATUS_SUCCESS;
|
||
}
|
||
|
||
//
|
||
// Everything is done indicate we are up to date.
|
||
//
|
||
|
||
ExAcquireFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
ClearFlag( Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING );
|
||
SetFlag( Vcb->QuotaState, VCB_QUOTA_SAVE_QUOTA_FLAGS);
|
||
|
||
if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_PENDING_DELETES )) {
|
||
|
||
//
|
||
// Need to actually delete the ids.
|
||
//
|
||
|
||
SetFlag( Vcb->QuotaState, VCB_QUOTA_DELETEING_IDS );
|
||
State = VCB_QUOTA_DELETEING_IDS;
|
||
|
||
//
|
||
// NtfsDeleteUnsedIds uses the low part of the
|
||
// file reference to store the current owner id.
|
||
// Intialize this to the first user id.
|
||
//
|
||
|
||
Vcb->QuotaFileReference.SegmentNumberLowPart = QUOTA_FISRT_USER_ID;
|
||
|
||
}
|
||
|
||
ClearFlag( Vcb->QuotaFlags, QUOTA_FLAG_OUT_OF_DATE | QUOTA_FLAG_CORRUPT );
|
||
|
||
ExReleaseFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
|
||
if (FlagOn( Vcb->QuotaState, VCB_QUOTA_SAVE_QUOTA_FLAGS )) {
|
||
NtfsSaveQuotaFlagsSafe( IrpContext, Vcb );
|
||
}
|
||
|
||
if (State != VCB_QUOTA_DELETEING_IDS) {
|
||
break;
|
||
}
|
||
|
||
case VCB_QUOTA_DELETEING_IDS:
|
||
|
||
//
|
||
// Remove and ids which are marked for deletion.
|
||
//
|
||
|
||
NtfsDeleteUnsedIds( IrpContext, Vcb );
|
||
|
||
ExAcquireFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
|
||
ClearFlag( Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING );
|
||
SetFlag( Vcb->QuotaState, VCB_QUOTA_SAVE_QUOTA_FLAGS);
|
||
ClearFlag( Vcb->QuotaFlags, QUOTA_FLAG_PENDING_DELETES );
|
||
|
||
ExReleaseFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
|
||
if (FlagOn( Vcb->QuotaState, VCB_QUOTA_SAVE_QUOTA_FLAGS )) {
|
||
NtfsSaveQuotaFlagsSafe( IrpContext, Vcb );
|
||
}
|
||
|
||
break;
|
||
|
||
default:
|
||
|
||
ASSERT( FALSE );
|
||
Status = STATUS_INVALID_PARAMETER;
|
||
NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
|
||
}
|
||
|
||
if (NT_SUCCESS( Status )) {
|
||
NtfsLogEvent( IrpContext,
|
||
NULL,
|
||
IO_FILE_QUOTA_SUCCEEDED,
|
||
Status );
|
||
}
|
||
|
||
} except(NtfsExceptionFilter(IrpContext, GetExceptionInformation())) {
|
||
|
||
Status = IrpContext->TopLevelIrpContext->ExceptionStatus;
|
||
}
|
||
|
||
DebugTrace( 0, Dbg, ( "NtfsRepairQuotaIndex: Quota repair done. Status = %8lx Context = %lx\n", Status, (ULONG) NtfsSegmentNumber( &Vcb->QuotaFileReference )));
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
|
||
//
|
||
// If we will not be called back then clear the running state bits.
|
||
//
|
||
|
||
if ((Status != STATUS_CANT_WAIT) && (Status != STATUS_LOG_FILE_FULL)) {
|
||
|
||
ExAcquireFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
ClearFlag( Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING );
|
||
ExReleaseFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
|
||
//
|
||
// Only log if we attempted to do work - which is only the case
|
||
// if tracking is on
|
||
//
|
||
|
||
if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_REQUESTED)) {
|
||
NtfsLogEvent( IrpContext, NULL, IO_FILE_QUOTA_FAILED, Status );
|
||
}
|
||
|
||
}
|
||
|
||
NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
VOID
|
||
NtfsReleaseQuotaControl (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PQUOTA_CONTROL_BLOCK QuotaControl
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function is called by transcation control to release the quota control
|
||
block and quota index after a transcation has been completed.
|
||
|
||
Arguments:
|
||
|
||
QuotaControl - Quota control block to be released.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
PVCB Vcb = IrpContext->Vcb;
|
||
PAGED_CODE();
|
||
|
||
ExReleaseFastMutexUnsafe( QuotaControl->QuotaControlLock );
|
||
NtfsReleaseResource( IrpContext, Vcb->QuotaTableScb );
|
||
|
||
NtfsDereferenceQuotaControlBlock( Vcb, &QuotaControl );
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NtfsRepairPerFileQuota (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PFCB Fcb,
|
||
IN PVOID Context
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine calculate the quota used by a file and update the
|
||
update QuotaCharged field in the standard info as well as QuotaUsed
|
||
in the user's index structure. If the owner id is not set this is
|
||
also updated at this time.
|
||
|
||
Arguments:
|
||
|
||
Fcb - Fcb for the file to be processed.
|
||
|
||
Context - Unsed.
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS
|
||
|
||
--*/
|
||
|
||
{
|
||
LONGLONG Delta;
|
||
INDEX_KEY IndexKey;
|
||
INDEX_ROW IndexRow;
|
||
PREAD_CONTEXT ReadContext = NULL;
|
||
ULONG Count;
|
||
PSID Sid;
|
||
PVCB Vcb = Fcb->Vcb;
|
||
NTSTATUS Status;
|
||
BOOLEAN OwnerDefaulted;
|
||
BOOLEAN SetOwnerId = FALSE;
|
||
BOOLEAN StdInfoGrown = FALSE;
|
||
|
||
PAGED_CODE( );
|
||
|
||
UNREFERENCED_PARAMETER( Context);
|
||
|
||
//
|
||
// Preacquire the security stream and quota index in case the
|
||
// mft has to be grown.
|
||
//
|
||
|
||
ASSERT(!NtfsIsExclusiveScb( Vcb->MftScb ) || NtfsIsExclusiveScb( Vcb->QuotaTableScb ));
|
||
|
||
NtfsAcquireExclusiveScb( IrpContext, Vcb->QuotaTableScb );
|
||
|
||
try {
|
||
|
||
//
|
||
// Always clear the owner ID so that the SID is retrived from
|
||
// the security descriptor.
|
||
//
|
||
|
||
Fcb->OwnerId = QUOTA_INVALID_ID;
|
||
|
||
if (Fcb->QuotaControl != NULL) {
|
||
|
||
//
|
||
// If there is a quota control block it is now bougus
|
||
// Free it up a new one will be generated below.
|
||
//
|
||
|
||
NtfsDereferenceQuotaControlBlock( Vcb, &Fcb->QuotaControl );
|
||
}
|
||
|
||
if (Fcb->OwnerId != QUOTA_INVALID_ID) {
|
||
|
||
//
|
||
// Verify the id actually exists in the index.
|
||
//
|
||
|
||
Count = 0;
|
||
IndexKey.Key = &Fcb->OwnerId;
|
||
IndexKey.KeyLength = sizeof( Fcb->OwnerId );
|
||
Status = NtOfsReadRecords( IrpContext,
|
||
Vcb->QuotaTableScb,
|
||
&ReadContext,
|
||
&IndexKey,
|
||
NtOfsMatchUlongExact,
|
||
&IndexKey,
|
||
&Count,
|
||
&IndexRow,
|
||
0,
|
||
NULL );
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
|
||
ASSERT( NT_SUCCESS( Status ));
|
||
|
||
//
|
||
// There is no user quota data for this id assign a
|
||
// new one to the file.
|
||
//
|
||
|
||
Fcb->OwnerId = QUOTA_INVALID_ID;
|
||
|
||
if (Fcb->QuotaControl != NULL) {
|
||
|
||
//
|
||
// If there is a quota control block it is now bougus
|
||
// Free it up a new one will be generated below.
|
||
//
|
||
|
||
NtfsDereferenceQuotaControlBlock( Vcb, &Fcb->QuotaControl );
|
||
}
|
||
}
|
||
|
||
NtOfsFreeReadContext( ReadContext );
|
||
}
|
||
|
||
if (Fcb->OwnerId == QUOTA_INVALID_ID) {
|
||
|
||
if (Fcb->SharedSecurity == NULL) {
|
||
NtfsLoadSecurityDescriptor ( IrpContext, Fcb );
|
||
}
|
||
|
||
ASSERT( Fcb->SharedSecurity != NULL );
|
||
|
||
//
|
||
// Extract the security id from the security descriptor.
|
||
//
|
||
|
||
Status = RtlGetOwnerSecurityDescriptor( Fcb->SharedSecurity->SecurityDescriptor,
|
||
&Sid,
|
||
&OwnerDefaulted );
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
NtfsRaiseStatus( IrpContext, Status, NULL, Fcb);
|
||
}
|
||
|
||
//
|
||
// Generate a owner id for the Fcb.
|
||
//
|
||
|
||
Fcb->OwnerId = NtfsGetOwnerId( IrpContext,
|
||
Sid,
|
||
TRUE,
|
||
NULL );
|
||
|
||
SetOwnerId = TRUE;
|
||
|
||
SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
|
||
|
||
if (FlagOn( Fcb->FcbState, FCB_STATE_LARGE_STD_INFO )) {
|
||
|
||
NtfsUpdateStandardInformation( IrpContext, Fcb );
|
||
|
||
} else {
|
||
|
||
//
|
||
// Grow the standard information.
|
||
//
|
||
|
||
StdInfoGrown = TRUE;
|
||
NtfsGrowStandardInformation( IrpContext, Fcb );
|
||
}
|
||
}
|
||
|
||
//
|
||
// Initialize the quota control block.
|
||
//
|
||
|
||
if (Fcb->QuotaControl == NULL) {
|
||
Fcb->QuotaControl = NtfsInitializeQuotaControlBlock( Vcb, Fcb->OwnerId );
|
||
}
|
||
|
||
NtfsCalculateQuotaAdjustment( IrpContext, Fcb, &Delta );
|
||
|
||
ASSERT( NtfsAllowFixups || FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_OUT_OF_DATE ) || (Delta == 0));
|
||
|
||
if ((Delta != 0) || FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_PENDING_DELETES )) {
|
||
|
||
NtfsUpdateFileQuota( IrpContext, Fcb, &Delta, TRUE, FALSE );
|
||
}
|
||
|
||
if (SetOwnerId) {
|
||
|
||
//
|
||
// If the owner id was set then commit the transaction now.
|
||
// That way if a raise occurs the OwnerId can be cleared before
|
||
// the function returns. No resources are released.
|
||
//
|
||
|
||
NtfsCheckpointCurrentTransaction( IrpContext );
|
||
}
|
||
|
||
} finally {
|
||
|
||
//
|
||
// Clear any Fcb changes if the operation failed.
|
||
// This is so when a retry occurs the necessary
|
||
// operations are done.
|
||
//
|
||
|
||
if (AbnormalTermination()) {
|
||
|
||
if (StdInfoGrown) {
|
||
ClearFlag( Fcb->FcbState, FCB_STATE_LARGE_STD_INFO );
|
||
}
|
||
|
||
if (SetOwnerId) {
|
||
|
||
Fcb->OwnerId = QUOTA_INVALID_ID;
|
||
|
||
if (Fcb->QuotaControl != NULL) {
|
||
NtfsDereferenceQuotaControlBlock( Vcb, &Fcb->QuotaControl );
|
||
}
|
||
}
|
||
}
|
||
|
||
NtfsReleaseScb( IrpContext, Vcb->QuotaTableScb );
|
||
}
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
VOID
|
||
NtfsUpdateFileQuota (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PFCB Fcb,
|
||
IN PLONGLONG Delta,
|
||
IN LOGICAL LogIt,
|
||
IN LOGICAL CheckQuota
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine updates the quota amount for a file and owner by the
|
||
requested amount. If quota is being increated and the CheckQuota is true
|
||
than the new quota amount will be tested for quota violations. If the
|
||
hard limit is exceeded an error is raised. If the LogIt flags is not set
|
||
then changes to the standard information structure are not logged.
|
||
Changes to the user quota data are always logged.
|
||
|
||
Arguments:
|
||
|
||
Fcb - Fcb whose quota usage is being modified.
|
||
|
||
Delta - Supplies the signed amount to change the quota for the file.
|
||
|
||
LogIt - Indicates whether we should log this change.
|
||
|
||
CheckQuota - Indicates whether we should check for quota violations.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
ULONGLONG NewQuota;
|
||
LARGE_INTEGER CurrentTime;
|
||
ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
|
||
PSTANDARD_INFORMATION StandardInformation;
|
||
PQUOTA_USER_DATA UserData;
|
||
INDEX_ROW IndexRow;
|
||
INDEX_KEY IndexKey;
|
||
MAP_HANDLE MapHandle;
|
||
NTSTATUS Status;
|
||
PQUOTA_CONTROL_BLOCK QuotaControl = Fcb->QuotaControl;
|
||
PVCB Vcb = Fcb->Vcb;
|
||
ULONG Length;
|
||
|
||
PAGED_CODE();
|
||
|
||
DebugTrace( +1, Dbg, ("NtfsUpdateFileQuota: Entered\n") );
|
||
|
||
ASSERT( FlagOn( Fcb->FcbState, FCB_STATE_LARGE_STD_INFO ));
|
||
|
||
|
||
//
|
||
// Readonly volumes shouldn't proceed.
|
||
//
|
||
|
||
if (NtfsIsVolumeReadOnly( Vcb )) {
|
||
|
||
ASSERT( FALSE );
|
||
NtfsRaiseStatus( IrpContext, STATUS_MEDIA_WRITE_PROTECTED, NULL, NULL );
|
||
}
|
||
|
||
//
|
||
// Use a try-finally to cleanup the attribute context.
|
||
//
|
||
|
||
try {
|
||
|
||
//
|
||
// Initialize the context structure and map handle.
|
||
//
|
||
|
||
NtfsInitializeAttributeContext( &AttrContext );
|
||
NtOfsInitializeMapHandle( &MapHandle );
|
||
|
||
//
|
||
// Locate the standard information, it must be there.
|
||
//
|
||
|
||
if (!NtfsLookupAttributeByCode( IrpContext,
|
||
Fcb,
|
||
&Fcb->FileReference,
|
||
$STANDARD_INFORMATION,
|
||
&AttrContext )) {
|
||
|
||
DebugTrace( 0, Dbg, ("Can't find standard information\n") );
|
||
|
||
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
|
||
}
|
||
|
||
StandardInformation = (PSTANDARD_INFORMATION) NtfsAttributeValue( NtfsFoundAttribute( &AttrContext ));
|
||
|
||
ASSERT( NtfsFoundAttribute( &AttrContext )->Form.Resident.ValueLength == sizeof( STANDARD_INFORMATION ));
|
||
|
||
NewQuota = StandardInformation->QuotaCharged + *Delta;
|
||
|
||
SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
|
||
|
||
if ((LONGLONG) NewQuota < 0) {
|
||
|
||
//
|
||
// Do not let the quota data go negitive.
|
||
//
|
||
|
||
NewQuota = 0;
|
||
}
|
||
|
||
if (LogIt) {
|
||
|
||
//
|
||
// Call to change the attribute value.
|
||
//
|
||
|
||
NtfsChangeAttributeValue( IrpContext,
|
||
Fcb,
|
||
FIELD_OFFSET(STANDARD_INFORMATION, QuotaCharged),
|
||
&NewQuota,
|
||
sizeof( StandardInformation->QuotaCharged),
|
||
FALSE,
|
||
FALSE,
|
||
FALSE,
|
||
FALSE,
|
||
&AttrContext );
|
||
} else {
|
||
|
||
//
|
||
// Just update the value in the standard information
|
||
// it will be logged later.
|
||
//
|
||
|
||
StandardInformation->QuotaCharged = NewQuota;
|
||
}
|
||
|
||
//
|
||
// Update the quota information block.
|
||
//
|
||
|
||
NtfsAcquireQuotaControl( IrpContext, QuotaControl );
|
||
|
||
IndexKey.KeyLength = sizeof(ULONG);
|
||
IndexKey.Key = &QuotaControl->OwnerId;
|
||
|
||
Status = NtOfsFindRecord( IrpContext,
|
||
Vcb->QuotaTableScb,
|
||
&IndexKey,
|
||
&IndexRow,
|
||
&MapHandle,
|
||
&QuotaControl->QuickIndexHint );
|
||
|
||
if (!(NT_SUCCESS( Status )) ||
|
||
(IndexRow.DataPart.DataLength < SIZEOF_QUOTA_USER_DATA + FIELD_OFFSET( SID, SubAuthority )) ||
|
||
((ULONG)SeLengthSid( &(((PQUOTA_USER_DATA)(IndexRow.DataPart.Data))->QuotaSid)) + SIZEOF_QUOTA_USER_DATA !=
|
||
IndexRow.DataPart.DataLength)) {
|
||
|
||
//
|
||
// This look up should not fail.
|
||
//
|
||
|
||
ASSERT( NT_SUCCESS( Status ));
|
||
ASSERTMSG(( "NTFS: corrupt quotasid\n" ), FALSE);
|
||
|
||
NtfsMarkQuotaCorrupt( IrpContext, IrpContext->Vcb );
|
||
leave;
|
||
}
|
||
|
||
//
|
||
// Space is allocated for the new record after the quota control
|
||
// block.
|
||
//
|
||
|
||
UserData = (PQUOTA_USER_DATA) (QuotaControl + 1);
|
||
ASSERT( IndexRow.DataPart.DataLength >= SIZEOF_QUOTA_USER_DATA );
|
||
|
||
RtlCopyMemory( UserData,
|
||
IndexRow.DataPart.Data,
|
||
SIZEOF_QUOTA_USER_DATA );
|
||
|
||
ASSERT( (LONGLONG) UserData->QuotaUsed >= -*Delta );
|
||
|
||
UserData->QuotaUsed += *Delta;
|
||
|
||
if ((LONGLONG) UserData->QuotaUsed < 0) {
|
||
|
||
//
|
||
// Do not let the quota data go negative.
|
||
//
|
||
|
||
UserData->QuotaUsed = 0;
|
||
}
|
||
|
||
//
|
||
// Indicate only the quota used field has been set so far.
|
||
//
|
||
|
||
Length = FIELD_OFFSET( QUOTA_USER_DATA, QuotaChangeTime );
|
||
|
||
//
|
||
// Only update the quota modified time if this is the last cleanup
|
||
// for the owner.
|
||
//
|
||
|
||
if (IrpContext->MajorFunction == IRP_MJ_CLEANUP) {
|
||
|
||
KeQuerySystemTime( &CurrentTime );
|
||
UserData->QuotaChangeTime = CurrentTime.QuadPart;
|
||
|
||
ASSERT( Length <= FIELD_OFFSET( QUOTA_USER_DATA, QuotaThreshold ));
|
||
Length = FIELD_OFFSET( QUOTA_USER_DATA, QuotaThreshold );
|
||
}
|
||
|
||
if (CheckQuota && (*Delta > 0)) {
|
||
|
||
if ((UserData->QuotaUsed > UserData->QuotaLimit) &&
|
||
(UserData->QuotaUsed >= (UserData->QuotaLimit + Vcb->BytesPerCluster))) {
|
||
|
||
KeQuerySystemTime( &CurrentTime );
|
||
UserData->QuotaChangeTime = CurrentTime.QuadPart;
|
||
|
||
if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_LOG_LIMIT ) &&
|
||
(!FlagOn( UserData->QuotaFlags, QUOTA_FLAG_LIMIT_REACHED ) ||
|
||
((ULONGLONG) CurrentTime.QuadPart > UserData->QuotaExceededTime + NtfsMaxQuotaNotifyRate))) {
|
||
|
||
if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_ENFORCEMENT_ENABLED) &&
|
||
(Vcb->AdministratorId != QuotaControl->OwnerId)) {
|
||
|
||
//
|
||
// The operation to mark the user's quota data entry
|
||
// must be posted since any changes to the entry
|
||
// will be undone by the following raise.
|
||
//
|
||
|
||
NtfsPostUserLimit( IrpContext, Vcb, QuotaControl );
|
||
NtfsRaiseStatus( IrpContext, STATUS_DISK_FULL, NULL, Fcb );
|
||
|
||
} else {
|
||
|
||
//
|
||
// Log the fact that quota was exceeded.
|
||
//
|
||
|
||
if (NtfsLogEvent( IrpContext,
|
||
IndexRow.DataPart.Data,
|
||
IO_FILE_QUOTA_LIMIT,
|
||
STATUS_SUCCESS )) {
|
||
|
||
//
|
||
// The event was successfuly logged. Do not log
|
||
// another for a while.
|
||
//
|
||
|
||
DebugTrace( 0, Dbg, ("NtfsUpdateFileQuota: Quota Limit exceeded. OwnerId = %lx\n", QuotaControl->OwnerId));
|
||
|
||
UserData->QuotaExceededTime = CurrentTime.QuadPart;
|
||
SetFlag( UserData->QuotaFlags, QUOTA_FLAG_LIMIT_REACHED );
|
||
|
||
//
|
||
// Log all of the changed data.
|
||
//
|
||
|
||
Length = SIZEOF_QUOTA_USER_DATA;
|
||
}
|
||
}
|
||
|
||
} else if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_ENFORCEMENT_ENABLED) &&
|
||
(Vcb->AdministratorId != QuotaControl->OwnerId)) {
|
||
|
||
NtfsRaiseStatus( IrpContext, STATUS_DISK_FULL, NULL, Fcb );
|
||
}
|
||
|
||
}
|
||
|
||
if (UserData->QuotaUsed > UserData->QuotaThreshold) {
|
||
|
||
KeQuerySystemTime( &CurrentTime );
|
||
UserData->QuotaChangeTime = CurrentTime.QuadPart;
|
||
|
||
if ((ULONGLONG) CurrentTime.QuadPart >
|
||
(UserData->QuotaExceededTime + NtfsMaxQuotaNotifyRate)) {
|
||
|
||
if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_LOG_THRESHOLD)) {
|
||
|
||
if (NtfsLogEvent( IrpContext,
|
||
IndexRow.DataPart.Data,
|
||
IO_FILE_QUOTA_THRESHOLD,
|
||
STATUS_SUCCESS )) {
|
||
|
||
//
|
||
// The event was successfuly logged. Do not log
|
||
// another for a while.
|
||
//
|
||
|
||
DebugTrace( 0, Dbg, ("NtfsUpdateFileQuota: Quota threshold exceeded. OwnerId = %lx\n", QuotaControl->OwnerId));
|
||
|
||
UserData->QuotaExceededTime = CurrentTime.QuadPart;
|
||
|
||
//
|
||
// Log all of the changed data.
|
||
//
|
||
|
||
Length = SIZEOF_QUOTA_USER_DATA;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Now is a good time to clear the limit reached flag.
|
||
//
|
||
|
||
ClearFlag( UserData->QuotaFlags, QUOTA_FLAG_LIMIT_REACHED );
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Always clear the deleted flag.
|
||
//
|
||
|
||
ClearFlag( UserData->QuotaFlags, QUOTA_FLAG_ID_DELETED );
|
||
|
||
//
|
||
// Only log the part that changed.
|
||
//
|
||
|
||
IndexRow.KeyPart.Key = &QuotaControl->OwnerId;
|
||
ASSERT( IndexRow.KeyPart.KeyLength == sizeof(ULONG) );
|
||
IndexRow.DataPart.Data = UserData;
|
||
IndexRow.DataPart.DataLength = Length;
|
||
|
||
NtOfsUpdateRecord( IrpContext,
|
||
Vcb->QuotaTableScb,
|
||
1,
|
||
&IndexRow,
|
||
&QuotaControl->QuickIndexHint,
|
||
&MapHandle );
|
||
|
||
} finally {
|
||
|
||
DebugUnwind( NtfsUpdateFileQuota );
|
||
|
||
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
||
NtOfsReleaseMap( IrpContext, &MapHandle );
|
||
|
||
DebugTrace( -1, Dbg, ("NtfsUpdateFileQuota: Exit\n") );
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
VOID
|
||
NtfsSaveQuotaFlags (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PVCB Vcb
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine saves the quota flags in the defaults quota entry.
|
||
|
||
Arguments:
|
||
|
||
Vcb - Volume control block for volume be query.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
ULONG OwnerId;
|
||
NTSTATUS Status;
|
||
INDEX_ROW IndexRow;
|
||
INDEX_KEY IndexKey;
|
||
MAP_HANDLE MapHandle;
|
||
QUICK_INDEX_HINT QuickIndexHint;
|
||
QUOTA_USER_DATA NewQuotaData;
|
||
PSCB QuotaScb;
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// Acquire quota index exclusive.
|
||
//
|
||
|
||
QuotaScb = Vcb->QuotaTableScb;
|
||
NtfsAcquireExclusiveScb( IrpContext, QuotaScb );
|
||
NtOfsInitializeMapHandle( &MapHandle );
|
||
ExAcquireFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
|
||
try {
|
||
|
||
//
|
||
// Find the default quota record and update it.
|
||
//
|
||
|
||
OwnerId = QUOTA_DEFAULTS_ID;
|
||
IndexKey.KeyLength = sizeof(ULONG);
|
||
IndexKey.Key = &OwnerId;
|
||
|
||
RtlZeroMemory( &QuickIndexHint, sizeof( QuickIndexHint ));
|
||
|
||
Status = NtOfsFindRecord( IrpContext,
|
||
QuotaScb,
|
||
&IndexKey,
|
||
&IndexRow,
|
||
&MapHandle,
|
||
&QuickIndexHint );
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
NtfsRaiseStatus( IrpContext, STATUS_QUOTA_LIST_INCONSISTENT, NULL, QuotaScb->Fcb );
|
||
}
|
||
|
||
ASSERT( IndexRow.DataPart.DataLength <= sizeof( QUOTA_USER_DATA ));
|
||
|
||
RtlCopyMemory( &NewQuotaData,
|
||
IndexRow.DataPart.Data,
|
||
IndexRow.DataPart.DataLength );
|
||
|
||
//
|
||
// Update the changed fields in the record.
|
||
//
|
||
|
||
NewQuotaData.QuotaFlags = Vcb->QuotaFlags;
|
||
|
||
//
|
||
// Note the sizes in the IndexRow stay the same.
|
||
//
|
||
|
||
IndexRow.KeyPart.Key = &OwnerId;
|
||
ASSERT( IndexRow.KeyPart.KeyLength == sizeof(ULONG) );
|
||
IndexRow.DataPart.Data = &NewQuotaData;
|
||
|
||
NtOfsUpdateRecord( IrpContext,
|
||
QuotaScb,
|
||
1,
|
||
&IndexRow,
|
||
&QuickIndexHint,
|
||
&MapHandle );
|
||
|
||
ClearFlag( Vcb->QuotaState, VCB_QUOTA_SAVE_QUOTA_FLAGS);
|
||
|
||
} finally {
|
||
|
||
//
|
||
// Release the index map handle and scb.
|
||
//
|
||
|
||
ExReleaseFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
NtOfsReleaseMap( IrpContext, &MapHandle );
|
||
NtfsReleaseScb( IrpContext, QuotaScb );
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
VOID
|
||
NtfsSaveQuotaFlagsSafe (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PVCB Vcb
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine safely saves the quota flags in the defaults quota entry.
|
||
It acquires the volume shared, checks to see if it is ok to write,
|
||
updates the flags and finally commits the transaction.
|
||
|
||
Arguments:
|
||
|
||
Vcb - Volume control block for volume be query.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
PAGED_CODE();
|
||
|
||
ASSERT( IrpContext->TransactionId == 0);
|
||
|
||
NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
|
||
|
||
try {
|
||
|
||
//
|
||
// Acquire the VCB shared and check whether we should
|
||
// continue.
|
||
//
|
||
|
||
if (!NtfsIsVcbAvailable( Vcb )) {
|
||
|
||
//
|
||
// The volume is going away, bail out.
|
||
//
|
||
|
||
NtfsRaiseStatus( IrpContext, STATUS_VOLUME_DISMOUNTED, NULL, NULL );
|
||
}
|
||
|
||
//
|
||
// Do the work.
|
||
//
|
||
|
||
NtfsSaveQuotaFlags( IrpContext, Vcb );
|
||
|
||
//
|
||
// Set the irp context flags to indicate that we are in the
|
||
// fsp and that the irp context should not be deleted when
|
||
// complete request or process exception are called. The in
|
||
// fsp flag keeps us from raising in a few places. These
|
||
// flags must be set inside the loop since they are cleared
|
||
// under certain conditions.
|
||
//
|
||
|
||
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_DONT_DELETE | IRP_CONTEXT_FLAG_RETAIN_FLAGS );
|
||
SetFlag( IrpContext->State, IRP_CONTEXT_STATE_IN_FSP);
|
||
|
||
NtfsCompleteRequest( IrpContext, NULL, STATUS_SUCCESS );
|
||
|
||
} finally {
|
||
|
||
NtfsReleaseVcb( IrpContext, Vcb );
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
VOID
|
||
NtfsUpdateQuotaDefaults (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PVCB Vcb,
|
||
IN PFILE_FS_CONTROL_INFORMATION FileControlInfo
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function updates the default settings index entry for quotas.
|
||
|
||
Arguments:
|
||
|
||
Vcb - Volume control block for volume be query.
|
||
|
||
FileQuotaInfo - Optional quota data to update quota index with.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
ULONG OwnerId;
|
||
NTSTATUS Status;
|
||
INDEX_ROW IndexRow;
|
||
INDEX_KEY IndexKey;
|
||
MAP_HANDLE MapHandle;
|
||
QUOTA_USER_DATA NewQuotaData;
|
||
QUICK_INDEX_HINT QuickIndexHint;
|
||
ULONG Flags;
|
||
PSCB QuotaScb;
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// Acquire quota index exclusive.
|
||
//
|
||
|
||
QuotaScb = Vcb->QuotaTableScb;
|
||
NtfsAcquireExclusiveScb( IrpContext, QuotaScb );
|
||
NtOfsInitializeMapHandle( &MapHandle );
|
||
ExAcquireFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
|
||
try {
|
||
|
||
//
|
||
// Find the default quota record and update it.
|
||
//
|
||
|
||
OwnerId = QUOTA_DEFAULTS_ID;
|
||
IndexKey.KeyLength = sizeof( ULONG );
|
||
IndexKey.Key = &OwnerId;
|
||
|
||
RtlZeroMemory( &QuickIndexHint, sizeof( QuickIndexHint ));
|
||
|
||
Status = NtOfsFindRecord( IrpContext,
|
||
QuotaScb,
|
||
&IndexKey,
|
||
&IndexRow,
|
||
&MapHandle,
|
||
&QuickIndexHint );
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
NtfsRaiseStatus( IrpContext, STATUS_QUOTA_LIST_INCONSISTENT, NULL, QuotaScb->Fcb );
|
||
}
|
||
|
||
ASSERT( IndexRow.DataPart.DataLength == SIZEOF_QUOTA_USER_DATA );
|
||
|
||
RtlCopyMemory( &NewQuotaData,
|
||
IndexRow.DataPart.Data,
|
||
IndexRow.DataPart.DataLength );
|
||
|
||
//
|
||
// Update the changed fields in the record.
|
||
//
|
||
|
||
NewQuotaData.QuotaThreshold = FileControlInfo->DefaultQuotaThreshold.QuadPart;
|
||
NewQuotaData.QuotaLimit = FileControlInfo->DefaultQuotaLimit.QuadPart;
|
||
KeQuerySystemTime( (PLARGE_INTEGER) &NewQuotaData.QuotaChangeTime );
|
||
|
||
//
|
||
// Update the quota flags.
|
||
//
|
||
|
||
Flags = FlagOn( FileControlInfo->FileSystemControlFlags,
|
||
FILE_VC_QUOTA_MASK );
|
||
|
||
switch (Flags) {
|
||
|
||
case FILE_VC_QUOTA_NONE:
|
||
|
||
//
|
||
// Disable quotas
|
||
//
|
||
|
||
ClearFlag( Vcb->QuotaFlags,
|
||
(QUOTA_FLAG_TRACKING_ENABLED |
|
||
QUOTA_FLAG_ENFORCEMENT_ENABLED |
|
||
QUOTA_FLAG_TRACKING_REQUESTED) );
|
||
|
||
break;
|
||
|
||
case FILE_VC_QUOTA_TRACK:
|
||
|
||
//
|
||
// Clear the enforment flags.
|
||
//
|
||
|
||
ClearFlag( Vcb->QuotaFlags, QUOTA_FLAG_ENFORCEMENT_ENABLED );
|
||
|
||
//
|
||
// Request tracking be enabled.
|
||
//
|
||
|
||
SetFlag( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_REQUESTED );
|
||
break;
|
||
|
||
case FILE_VC_QUOTA_ENFORCE:
|
||
|
||
//
|
||
// Set the enforcement and tracking enabled flags.
|
||
//
|
||
|
||
SetFlag( Vcb->QuotaFlags,
|
||
QUOTA_FLAG_ENFORCEMENT_ENABLED | QUOTA_FLAG_TRACKING_REQUESTED);
|
||
|
||
break;
|
||
}
|
||
|
||
//
|
||
// If quota tracking is not now
|
||
// enabled then the quota data will need
|
||
// to be rebuild so indicate quotas are out of date.
|
||
// Note the out of date flags always set of quotas
|
||
// are disabled.
|
||
//
|
||
|
||
if (!FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_ENABLED )) {
|
||
SetFlag( Vcb->QuotaFlags, QUOTA_FLAG_OUT_OF_DATE );
|
||
}
|
||
|
||
//
|
||
// Track the logging flags.
|
||
//
|
||
|
||
ClearFlag( Vcb->QuotaFlags,
|
||
QUOTA_FLAG_LOG_THRESHOLD | QUOTA_FLAG_LOG_LIMIT );
|
||
|
||
if (FlagOn( FileControlInfo->FileSystemControlFlags, FILE_VC_LOG_QUOTA_THRESHOLD )) {
|
||
|
||
SetFlag( Vcb->QuotaFlags, QUOTA_FLAG_LOG_THRESHOLD );
|
||
}
|
||
|
||
if (FlagOn( FileControlInfo->FileSystemControlFlags, FILE_VC_LOG_QUOTA_LIMIT )) {
|
||
|
||
SetFlag( Vcb->QuotaFlags, QUOTA_FLAG_LOG_LIMIT );
|
||
}
|
||
|
||
SetFlag( Vcb->QuotaState, VCB_QUOTA_SAVE_QUOTA_FLAGS );
|
||
|
||
//
|
||
// Save the new flags in the new index entry.
|
||
//
|
||
|
||
NewQuotaData.QuotaFlags = Vcb->QuotaFlags;
|
||
|
||
//
|
||
// Note the sizes in the IndexRow stays the same.
|
||
//
|
||
|
||
IndexRow.KeyPart.Key = &OwnerId;
|
||
ASSERT( IndexRow.KeyPart.KeyLength == sizeof( ULONG ));
|
||
IndexRow.DataPart.Data = &NewQuotaData;
|
||
|
||
NtOfsUpdateRecord( IrpContext,
|
||
QuotaScb,
|
||
1,
|
||
&IndexRow,
|
||
&QuickIndexHint,
|
||
&MapHandle );
|
||
|
||
ClearFlag( Vcb->QuotaState, VCB_QUOTA_SAVE_QUOTA_FLAGS );
|
||
|
||
} finally {
|
||
|
||
//
|
||
// Release the index map handle and scb.
|
||
//
|
||
|
||
ExReleaseFastMutexUnsafe( &Vcb->QuotaControlLock );
|
||
NtOfsReleaseMap( IrpContext, &MapHandle );
|
||
NtfsReleaseScb( IrpContext, QuotaScb );
|
||
}
|
||
|
||
//
|
||
// If the quota tracking has been requested and the quotas need to be
|
||
// repaired then try to repair them now.
|
||
//
|
||
|
||
if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_REQUESTED ) &&
|
||
FlagOn( Vcb->QuotaFlags,
|
||
QUOTA_FLAG_OUT_OF_DATE | QUOTA_FLAG_CORRUPT | QUOTA_FLAG_PENDING_DELETES )) {
|
||
|
||
NtfsPostRepairQuotaIndex( IrpContext, Vcb );
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NtfsVerifyOwnerIndex (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PVCB Vcb
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine iterates over the owner id index and verifies the pointer
|
||
to the quota user data index.
|
||
|
||
Arguments:
|
||
|
||
Vcb - Pointer to the volume control block whoes index is to be operated
|
||
on.
|
||
|
||
Return Value:
|
||
|
||
Returns a status indicating if the owner index was ok.
|
||
|
||
--*/
|
||
|
||
{
|
||
INDEX_KEY IndexKey;
|
||
INDEX_ROW QuotaRow;
|
||
MAP_HANDLE MapHandle;
|
||
PQUOTA_USER_DATA UserData;
|
||
PINDEX_ROW OwnerRow;
|
||
PVOID RowBuffer;
|
||
NTSTATUS Status;
|
||
NTSTATUS ReturnStatus = STATUS_SUCCESS;
|
||
ULONG Count;
|
||
ULONG i;
|
||
PSCB QuotaScb = Vcb->QuotaTableScb;
|
||
PSCB OwnerIdScb = Vcb->OwnerIdTableScb;
|
||
PINDEX_ROW IndexRow = NULL;
|
||
PREAD_CONTEXT ReadContext = NULL;
|
||
BOOLEAN IndexAcquired = FALSE;
|
||
|
||
NtOfsInitializeMapHandle( &MapHandle );
|
||
|
||
//
|
||
// Allocate a buffer lager enough for several rows.
|
||
//
|
||
|
||
RowBuffer = NtfsAllocatePool( PagedPool, PAGE_SIZE );
|
||
|
||
try {
|
||
|
||
//
|
||
// Allocate a bunch of index row entries.
|
||
//
|
||
|
||
Count = PAGE_SIZE / sizeof( SID );
|
||
|
||
IndexRow = NtfsAllocatePool( PagedPool,
|
||
Count * sizeof( INDEX_ROW ));
|
||
|
||
//
|
||
// Iterate through the owner id entries. Start with a zero sid.
|
||
//
|
||
|
||
RtlZeroMemory( IndexRow, sizeof( SID ));
|
||
IndexKey.KeyLength = sizeof( SID );
|
||
IndexKey.Key = IndexRow;
|
||
|
||
Status = NtOfsReadRecords( IrpContext,
|
||
OwnerIdScb,
|
||
&ReadContext,
|
||
&IndexKey,
|
||
NtOfsMatchAll,
|
||
NULL,
|
||
&Count,
|
||
IndexRow,
|
||
PAGE_SIZE,
|
||
RowBuffer );
|
||
|
||
while (NT_SUCCESS( Status )) {
|
||
|
||
//
|
||
// Acquire the VCB shared and check whether we should
|
||
// continue.
|
||
//
|
||
|
||
NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
|
||
|
||
if (!NtfsIsVcbAvailable( Vcb )) {
|
||
|
||
//
|
||
// The volume is going away, bail out.
|
||
//
|
||
|
||
NtfsReleaseVcb( IrpContext, Vcb );
|
||
Status = STATUS_VOLUME_DISMOUNTED;
|
||
leave;
|
||
}
|
||
|
||
NtfsAcquireExclusiveScb( IrpContext, QuotaScb );
|
||
NtfsAcquireExclusiveScb( IrpContext, OwnerIdScb );
|
||
IndexAcquired = TRUE;
|
||
|
||
OwnerRow = IndexRow;
|
||
|
||
for (i = 0; i < Count; i += 1, OwnerRow += 1) {
|
||
|
||
IndexKey.KeyLength = OwnerRow->DataPart.DataLength;
|
||
IndexKey.Key = OwnerRow->DataPart.Data;
|
||
|
||
//
|
||
// Look up the Owner id in the quota index.
|
||
//
|
||
|
||
Status = NtOfsFindRecord( IrpContext,
|
||
QuotaScb,
|
||
&IndexKey,
|
||
&QuotaRow,
|
||
&MapHandle,
|
||
NULL );
|
||
|
||
|
||
ASSERT( NT_SUCCESS( Status ));
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
|
||
//
|
||
// The quota entry is missing just delete this row;
|
||
//
|
||
|
||
NtOfsDeleteRecords( IrpContext,
|
||
OwnerIdScb,
|
||
1,
|
||
&OwnerRow->KeyPart );
|
||
|
||
continue;
|
||
}
|
||
|
||
UserData = QuotaRow.DataPart.Data;
|
||
|
||
ASSERT( (OwnerRow->KeyPart.KeyLength == QuotaRow.DataPart.DataLength - SIZEOF_QUOTA_USER_DATA) &&
|
||
RtlEqualMemory( OwnerRow->KeyPart.Key, &UserData->QuotaSid, OwnerRow->KeyPart.KeyLength ));
|
||
|
||
if ((OwnerRow->KeyPart.KeyLength != QuotaRow.DataPart.DataLength - SIZEOF_QUOTA_USER_DATA) ||
|
||
!RtlEqualMemory( OwnerRow->KeyPart.Key,
|
||
&UserData->QuotaSid,
|
||
OwnerRow->KeyPart.KeyLength )) {
|
||
|
||
NtOfsReleaseMap( IrpContext, &MapHandle );
|
||
|
||
//
|
||
// The Sids do not match delete both of these records.
|
||
// This causes the user whatever their Sid is to get
|
||
// the defaults.
|
||
//
|
||
|
||
|
||
NtOfsDeleteRecords( IrpContext,
|
||
OwnerIdScb,
|
||
1,
|
||
&OwnerRow->KeyPart );
|
||
|
||
NtOfsDeleteRecords( IrpContext,
|
||
QuotaScb,
|
||
1,
|
||
&IndexKey );
|
||
|
||
ReturnStatus = STATUS_QUOTA_LIST_INCONSISTENT;
|
||
}
|
||
|
||
NtOfsReleaseMap( IrpContext, &MapHandle );
|
||
}
|
||
|
||
//
|
||
// Release the indexes and commit what has been done so far.
|
||
//
|
||
|
||
NtfsReleaseScb( IrpContext, QuotaScb );
|
||
NtfsReleaseScb( IrpContext, OwnerIdScb );
|
||
NtfsReleaseVcb( IrpContext, Vcb );
|
||
IndexAcquired = FALSE;
|
||
|
||
//
|
||
// Complete the request which commits the pending
|
||
// transaction if there is one and releases of the
|
||
// acquired resources. The IrpContext will not
|
||
// be deleted because the no delete flag is set.
|
||
//
|
||
|
||
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_DONT_DELETE | IRP_CONTEXT_FLAG_RETAIN_FLAGS );
|
||
NtfsCompleteRequest( IrpContext, NULL, STATUS_SUCCESS );
|
||
|
||
//
|
||
// Look up the next set of entries in the quota index.
|
||
//
|
||
|
||
Count = PAGE_SIZE / sizeof( SID );
|
||
Status = NtOfsReadRecords( IrpContext,
|
||
OwnerIdScb,
|
||
&ReadContext,
|
||
NULL,
|
||
NtOfsMatchAll,
|
||
NULL,
|
||
&Count,
|
||
IndexRow,
|
||
PAGE_SIZE,
|
||
RowBuffer );
|
||
}
|
||
|
||
ASSERT( (Status == STATUS_NO_MORE_MATCHES) || (Status == STATUS_NO_MATCH) );
|
||
|
||
} finally {
|
||
|
||
NtfsFreePool( RowBuffer );
|
||
NtOfsReleaseMap( IrpContext, &MapHandle );
|
||
|
||
if (IndexAcquired) {
|
||
NtfsReleaseScb( IrpContext, QuotaScb );
|
||
NtfsReleaseScb( IrpContext, OwnerIdScb );
|
||
NtfsReleaseVcb( IrpContext, Vcb );
|
||
}
|
||
|
||
if (IndexRow != NULL) {
|
||
NtfsFreePool( IndexRow );
|
||
}
|
||
|
||
if (ReadContext != NULL) {
|
||
NtOfsFreeReadContext( ReadContext );
|
||
}
|
||
}
|
||
|
||
return ReturnStatus;
|
||
}
|
||
|
||
|
||
RTL_GENERIC_COMPARE_RESULTS
|
||
NtfsQuotaTableCompare (
|
||
IN PRTL_GENERIC_TABLE Table,
|
||
PVOID FirstStruct,
|
||
PVOID SecondStruct
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This is a generic table support routine to compare two quota table elements
|
||
|
||
Arguments:
|
||
|
||
Table - Supplies the generic table being queried. Not used.
|
||
|
||
FirstStruct - Supplies the first quota table element to compare
|
||
|
||
SecondStruct - Supplies the second quota table element to compare
|
||
|
||
Return Value:
|
||
|
||
RTL_GENERIC_COMPARE_RESULTS - The results of comparing the two
|
||
input structures
|
||
|
||
--*/
|
||
{
|
||
ULONG Key1 = ((PQUOTA_CONTROL_BLOCK) FirstStruct)->OwnerId;
|
||
ULONG Key2 = ((PQUOTA_CONTROL_BLOCK) SecondStruct)->OwnerId;
|
||
|
||
PAGED_CODE();
|
||
|
||
if (Key1 < Key2) {
|
||
|
||
return GenericLessThan;
|
||
}
|
||
|
||
if (Key1 > Key2) {
|
||
|
||
return GenericGreaterThan;
|
||
}
|
||
|
||
return GenericEqual;
|
||
|
||
UNREFERENCED_PARAMETER( Table );
|
||
}
|
||
|
||
PVOID
|
||
NtfsQuotaTableAllocate (
|
||
IN PRTL_GENERIC_TABLE Table,
|
||
CLONG ByteSize
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This is a generic table support routine to allocate memory
|
||
|
||
Arguments:
|
||
|
||
Table - Supplies the generic table being used
|
||
|
||
ByteSize - Supplies the number of bytes to allocate
|
||
|
||
Return Value:
|
||
|
||
PVOID - Returns a pointer to the allocated data
|
||
|
||
--*/
|
||
|
||
{
|
||
PAGED_CODE();
|
||
|
||
return NtfsAllocatePoolWithTag( PagedPool, ByteSize, 'QftN' );
|
||
|
||
UNREFERENCED_PARAMETER( Table );
|
||
}
|
||
|
||
VOID
|
||
NtfsQuotaTableFree (
|
||
IN PRTL_GENERIC_TABLE Table,
|
||
IN PVOID Buffer
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This is a generic table support routine to free memory
|
||
|
||
Arguments:
|
||
|
||
Table - Supplies the generic table being used
|
||
|
||
Buffer - Supplies pointer to the buffer to be freed
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
PAGED_CODE();
|
||
|
||
NtfsFreePool( Buffer );
|
||
|
||
UNREFERENCED_PARAMETER( Table );
|
||
}
|
||
|
||
|
||
ULONG
|
||
NtfsGetCallersUserId (
|
||
IN PIRP_CONTEXT IrpContext
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine finds the calling thread's SID and translates it to an
|
||
owner id.
|
||
|
||
Arguments:
|
||
|
||
Return Value:
|
||
|
||
Returns the owner id.
|
||
|
||
--*/
|
||
|
||
{
|
||
SECURITY_SUBJECT_CONTEXT SubjectContext;
|
||
PACCESS_TOKEN Token;
|
||
PTOKEN_USER UserToken = NULL;
|
||
NTSTATUS Status;
|
||
ULONG OwnerId;
|
||
|
||
PAGED_CODE();
|
||
|
||
SeCaptureSubjectContext( &SubjectContext );
|
||
|
||
try {
|
||
|
||
Token = SeQuerySubjectContextToken( &SubjectContext );
|
||
|
||
Status = SeQueryInformationToken( Token, TokenOwner, &UserToken );
|
||
|
||
|
||
if (!NT_SUCCESS( Status )) {
|
||
NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
|
||
}
|
||
|
||
|
||
OwnerId = NtfsGetOwnerId( IrpContext, UserToken->User.Sid, FALSE, NULL );
|
||
|
||
if (OwnerId == QUOTA_INVALID_ID) {
|
||
|
||
//
|
||
// If the user does not currently have an id on this
|
||
// system just use the current defaults.
|
||
//
|
||
|
||
OwnerId = QUOTA_DEFAULTS_ID;
|
||
}
|
||
|
||
} finally {
|
||
|
||
if (UserToken != NULL) {
|
||
NtfsFreePool( UserToken);
|
||
}
|
||
|
||
SeReleaseSubjectContext( &SubjectContext );
|
||
}
|
||
|
||
return OwnerId;
|
||
}
|
||
|