You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5249 lines
156 KiB
5249 lines
156 KiB
/*++
|
|
|
|
Copyright (c) 1991 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
AllocSup.c
|
|
|
|
Abstract:
|
|
|
|
This module implements the general file stream allocation & truncation
|
|
routines for Ntfs
|
|
|
|
Author:
|
|
|
|
Tom Miller [TomM] 15-Jul-1991
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "NtfsProc.h"
|
|
|
|
//
|
|
// Local debug trace level
|
|
//
|
|
|
|
#define Dbg (DEBUG_TRACE_ALLOCSUP)
|
|
|
|
//
|
|
// Define a tag for general pool allocations from this module
|
|
//
|
|
|
|
#undef MODULE_POOL_TAG
|
|
#define MODULE_POOL_TAG ('aFtN')
|
|
|
|
ULONG NtfsExtendFactor = 4;
|
|
|
|
//
|
|
// Internal support routines
|
|
//
|
|
|
|
VOID
|
|
NtfsDeleteAllocationInternal (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN OUT PSCB Scb,
|
|
IN VCN StartingVcn,
|
|
IN VCN EndingVcn,
|
|
IN BOOLEAN LogIt
|
|
);
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE, NtfsPreloadAllocation)
|
|
#pragma alloc_text(PAGE, NtfsAddAllocation)
|
|
#pragma alloc_text(PAGE, NtfsAddSparseAllocation)
|
|
#pragma alloc_text(PAGE, NtfsAllocateAttribute)
|
|
#pragma alloc_text(PAGE, NtfsBuildMappingPairs)
|
|
#pragma alloc_text(PAGE, NtfsCheckForReservedClusters)
|
|
#pragma alloc_text(PAGE, NtfsDeleteAllocation)
|
|
#pragma alloc_text(PAGE, NtfsDeleteAllocationInternal)
|
|
#pragma alloc_text(PAGE, NtfsDeleteReservedBitmap)
|
|
#pragma alloc_text(PAGE, NtfsGetHighestVcn)
|
|
#pragma alloc_text(PAGE, NtfsGetSizeForMappingPairs)
|
|
#pragma alloc_text(PAGE, NtfsIsRangeAllocated)
|
|
#pragma alloc_text(PAGE, NtfsReallocateRange)
|
|
#endif
|
|
|
|
|
|
ULONG
|
|
NtfsPreloadAllocation (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN OUT PSCB Scb,
|
|
IN VCN StartingVcn,
|
|
IN VCN EndingVcn
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine assures that all ranges of the Mcb are loaded in the specified
|
|
Vcn range
|
|
|
|
Arguments:
|
|
|
|
Scb - Specifies which Scb is to be preloaded
|
|
|
|
StartingVcn - Specifies the first Vcn to be loaded
|
|
|
|
EndingVcn - Specifies the last Vcn to be loaded
|
|
|
|
Return Value:
|
|
|
|
Number of ranges spanned by the load request.
|
|
|
|
--*/
|
|
|
|
{
|
|
VCN CurrentVcn, LastCurrentVcn;
|
|
LCN Lcn;
|
|
LONGLONG Count;
|
|
PVOID RangePtr;
|
|
ULONG RunIndex;
|
|
ULONG RangesLoaded = 0;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Start with starting Vcn
|
|
//
|
|
|
|
CurrentVcn = StartingVcn;
|
|
|
|
//
|
|
// Always load the nonpaged guys from the front, so we don't
|
|
// produce an Mcb with a "known hole".
|
|
//
|
|
|
|
if (FlagOn(Scb->Fcb->FcbState, FCB_STATE_NONPAGED)) {
|
|
CurrentVcn = 0;
|
|
}
|
|
|
|
//
|
|
// Loop until it's all loaded.
|
|
//
|
|
|
|
while (CurrentVcn <= EndingVcn) {
|
|
|
|
//
|
|
// Remember this CurrentVcn as a way to know when we have hit the end
|
|
// (stopped making progress).
|
|
//
|
|
|
|
LastCurrentVcn = CurrentVcn;
|
|
|
|
//
|
|
// Load range with CurrentVcn, and if it is not there, get out.
|
|
//
|
|
|
|
(VOID)NtfsLookupAllocation( IrpContext, Scb, CurrentVcn, &Lcn, &Count, &RangePtr, &RunIndex );
|
|
|
|
//
|
|
// If preloading the mft flush and purge it afterwards. This is to
|
|
// remove any partial pages we generated above if any mft record for
|
|
// the mft described others records in the same page after it
|
|
//
|
|
|
|
if (FlagOn( IrpContext->Vcb->VcbState, VCB_STATE_PRELOAD_MFT )) {
|
|
|
|
IO_STATUS_BLOCK IoStatus;
|
|
|
|
CcFlushCache( &Scb->NonpagedScb->SegmentObject,
|
|
NULL,
|
|
0,
|
|
&IoStatus );
|
|
|
|
if (!NT_SUCCESS( IoStatus.Status )) {
|
|
|
|
NtfsNormalizeAndRaiseStatus( IrpContext,
|
|
IoStatus.Status,
|
|
STATUS_UNEXPECTED_IO_ERROR );
|
|
}
|
|
|
|
CcPurgeCacheSection( &Scb->NonpagedScb->SegmentObject,
|
|
(PLARGE_INTEGER)NULL,
|
|
0,
|
|
FALSE );
|
|
}
|
|
|
|
//
|
|
// Find out how many runs there are in this range
|
|
//
|
|
|
|
if (!NtfsNumberOfRunsInRange(&Scb->Mcb, RangePtr, &RunIndex) || (RunIndex == 0)) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Get the highest run in this range and calculate the next Vcn beyond this range.
|
|
//
|
|
|
|
NtfsGetNextNtfsMcbEntry( &Scb->Mcb, &RangePtr, RunIndex - 1, &CurrentVcn, &Lcn, &Count );
|
|
|
|
CurrentVcn += Count;
|
|
|
|
//
|
|
// If we are making no progress, we must have hit the end of the allocation,
|
|
// and we are done.
|
|
//
|
|
|
|
if (CurrentVcn == LastCurrentVcn) {
|
|
break;
|
|
}
|
|
|
|
RangesLoaded += 1;
|
|
}
|
|
|
|
return RangesLoaded;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
NtfsLookupAllocation (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN OUT PSCB Scb,
|
|
IN VCN Vcn,
|
|
OUT PLCN Lcn,
|
|
OUT PLONGLONG ClusterCount,
|
|
OUT PVOID *RangePtr OPTIONAL,
|
|
OUT PULONG RunIndex OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine looks up the given Vcn for an Scb, and returns whether it
|
|
is allocated and how many contiguously allocated (or deallocated) Lcns
|
|
exist at that point.
|
|
|
|
Arguments:
|
|
|
|
Scb - Specifies which attribute the lookup is to occur on.
|
|
|
|
Vcn - Specifies the Vcn to be looked up.
|
|
|
|
Lcn - If returning TRUE, returns the Lcn that the specified Vcn is mapped
|
|
to. If returning FALSE, the return value is undefined.
|
|
|
|
ClusterCount - If returning TRUE, returns the number of contiguously allocated
|
|
Lcns exist beginning at the Lcn returned. If returning FALSE,
|
|
specifies the number of unallocated Vcns exist beginning with
|
|
the specified Vcn.
|
|
|
|
RangePtr - If specified, we return the range index for the start of the mapping.
|
|
|
|
RunIndex - If specified, we return the run index within the range for the start of the mapping.
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - TRUE if the input Vcn has a corresponding Lcn and
|
|
FALSE otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
ATTRIBUTE_ENUMERATION_CONTEXT Context;
|
|
PATTRIBUTE_RECORD_HEADER Attribute;
|
|
|
|
VCN HighestCandidate;
|
|
|
|
BOOLEAN Found;
|
|
BOOLEAN EntryAdded;
|
|
|
|
VCN CapturedLowestVcn;
|
|
VCN CapturedHighestVcn;
|
|
|
|
PVCB Vcb = Scb->Fcb->Vcb;
|
|
BOOLEAN McbMutexAcquired = FALSE;
|
|
LONGLONG AllocationClusters;
|
|
BOOLEAN MountInProgress;
|
|
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_SCB( Scb );
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsLookupAllocation\n") );
|
|
DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
|
|
DebugTrace( 0, Dbg, ("Vcn = %I64x\n", Vcn) );
|
|
|
|
MountInProgress = ((IrpContext->TopLevelIrpContext->MajorFunction == IRP_MJ_FILE_SYSTEM_CONTROL) &&
|
|
(IrpContext->TopLevelIrpContext->MinorFunction == IRP_MN_MOUNT_VOLUME));
|
|
|
|
//
|
|
// First try to look up the allocation in the mcb, and return the run
|
|
// from there if we can. Also, if we are doing restart, just return
|
|
// the answer straight from the Mcb, because we cannot read the disk.
|
|
// We also do this for the Mft if the volume has been mounted as the
|
|
// Mcb for the Mft should always represent the entire file.
|
|
//
|
|
|
|
HighestCandidate = MAXLONGLONG;
|
|
if ((Found = NtfsLookupNtfsMcbEntry( &Scb->Mcb, Vcn, Lcn, ClusterCount, NULL, NULL, RangePtr, RunIndex ))
|
|
|
|
||
|
|
|
|
(Scb == Vcb->MftScb
|
|
|
|
&&
|
|
|
|
((!MountInProgress) ||
|
|
|
|
//
|
|
// we will not try to load the mft hole during mount while preloading in any
|
|
// recursive faults
|
|
//
|
|
|
|
(FlagOn( Vcb->VcbState, VCB_STATE_PRELOAD_MFT) &&
|
|
(!NtfsIsTopLevelNtfs( IrpContext )))))
|
|
|
|
||
|
|
|
|
FlagOn( Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS )) {
|
|
|
|
//
|
|
// If not found (beyond the end of the Mcb), we will return the
|
|
// count to the largest representable Lcn.
|
|
//
|
|
|
|
if ( !Found ) {
|
|
|
|
*ClusterCount = MAXLONGLONG - Vcn;
|
|
|
|
//
|
|
// Test if we found a hole in the allocation. In this case
|
|
// Found will be TRUE and the Lcn will be the UNUSED_LCN.
|
|
// We only expect this case at restart.
|
|
//
|
|
|
|
} else if (*Lcn == UNUSED_LCN) {
|
|
|
|
//
|
|
// If the Mcb package returned UNUSED_LCN, because of a hole, then
|
|
// we turn this into FALSE.
|
|
//
|
|
|
|
Found = FALSE;
|
|
}
|
|
|
|
ASSERT( !Found ||
|
|
(*Lcn != 0) ||
|
|
(NtfsEqualMftRef( &Scb->Fcb->FileReference, &BootFileReference )) ||
|
|
(NtfsEqualMftRef( &Scb->Fcb->FileReference, &VolumeFileReference )));
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsLookupAllocation -> %02lx\n", Found) );
|
|
|
|
return Found;
|
|
}
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Prepare for looking up attribute records to get the retrieval
|
|
// information.
|
|
//
|
|
|
|
CapturedLowestVcn = MAXLONGLONG;
|
|
NtfsInitializeAttributeContext( &Context );
|
|
|
|
//
|
|
// Make sure we have the main resource acquired shared so that the
|
|
// attributes in the file record are not moving around. We blindly
|
|
// use Wait = TRUE. Most of the time when we go to the disk for I/O
|
|
// (and thus need mapping) we are synchronous, and otherwise, the Mcb
|
|
// is virtually always loaded anyway and we do not get here.
|
|
//
|
|
|
|
NtfsAcquireResourceShared( IrpContext, Scb, TRUE );
|
|
|
|
try {
|
|
|
|
//
|
|
// Lookup the attribute record for this Scb.
|
|
//
|
|
|
|
NtfsLookupAttributeForScb( IrpContext, Scb, &Vcn, &Context );
|
|
Attribute = NtfsFoundAttribute( &Context );
|
|
|
|
ASSERT( !NtfsIsAttributeResident(Attribute) );
|
|
|
|
if (FlagOn( Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED )) {
|
|
AllocationClusters = LlClustersFromBytesTruncate( Vcb, Scb->Header.AllocationSize.QuadPart );
|
|
} else {
|
|
ASSERT( Attribute->Form.Nonresident.LowestVcn == 0);
|
|
AllocationClusters = LlClustersFromBytesTruncate( Vcb, Attribute->Form.Nonresident.AllocatedLength );
|
|
}
|
|
|
|
//
|
|
// The desired Vcn is not currently in the Mcb. We will loop to lookup all
|
|
// the allocation, and we need to make sure we cleanup on the way out.
|
|
//
|
|
// It is important to note that if we ever optimize this lookup to do random
|
|
// access to the mapping pairs, rather than sequentially loading up the Mcb
|
|
// until we get the Vcn he asked for, then NtfsDeleteAllocation will have to
|
|
// be changed.
|
|
//
|
|
|
|
//
|
|
// Acquire exclusive access to the mcb to keep others from looking at
|
|
// it while it is not fully loaded. Otherwise they might see a hole
|
|
// while we're still filling up the mcb
|
|
//
|
|
|
|
if (!FlagOn(Scb->Fcb->FcbState, FCB_STATE_NONPAGED)) {
|
|
NtfsAcquireNtfsMcbMutex( &Scb->Mcb );
|
|
McbMutexAcquired = TRUE;
|
|
}
|
|
|
|
//
|
|
// Store run information in the Mcb until we hit the last Vcn we are
|
|
// interested in, or until we cannot find any more attribute records.
|
|
//
|
|
|
|
while(TRUE) {
|
|
|
|
VCN CurrentVcn;
|
|
LCN CurrentLcn;
|
|
LONGLONG Change;
|
|
PCHAR ch;
|
|
ULONG VcnBytes;
|
|
ULONG LcnBytes;
|
|
|
|
//
|
|
// If we raise here either there is some discrepancy between memory
|
|
// structures and on disk values or the on-disk value is completely corrupted
|
|
//
|
|
// We Check:
|
|
// 1) Verify the highest and lowest Vcn values on disk are valid.
|
|
// 2) our starting Vcn sits within this range.
|
|
// 3) the on-disk allocation matches the in memory value in the Scb
|
|
//
|
|
|
|
if ((Attribute->Form.Nonresident.LowestVcn < 0) ||
|
|
(Attribute->Form.Nonresident.LowestVcn - 1 > Attribute->Form.Nonresident.HighestVcn) ||
|
|
(Vcn < Attribute->Form.Nonresident.LowestVcn) ||
|
|
(Attribute->Form.Nonresident.HighestVcn >= AllocationClusters)) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
|
|
}
|
|
|
|
//
|
|
// Define the new range.
|
|
//
|
|
|
|
NtfsDefineNtfsMcbRange( &Scb->Mcb,
|
|
CapturedLowestVcn = Attribute->Form.Nonresident.LowestVcn,
|
|
CapturedHighestVcn = Attribute->Form.Nonresident.HighestVcn,
|
|
McbMutexAcquired );
|
|
|
|
//
|
|
// Implement the decompression algorithm, as defined in ntfs.h.
|
|
//
|
|
|
|
HighestCandidate = Attribute->Form.Nonresident.LowestVcn;
|
|
CurrentLcn = 0;
|
|
ch = (PCHAR)Attribute + Attribute->Form.Nonresident.MappingPairsOffset;
|
|
|
|
//
|
|
// Loop to process mapping pairs.
|
|
//
|
|
|
|
EntryAdded = FALSE;
|
|
while (!IsCharZero(*ch)) {
|
|
|
|
//
|
|
// Set Current Vcn from initial value or last pass through loop.
|
|
//
|
|
|
|
CurrentVcn = HighestCandidate;
|
|
|
|
//
|
|
// VCNs should never be negative.
|
|
//
|
|
|
|
if (CurrentVcn < 0) {
|
|
|
|
ASSERT( FALSE );
|
|
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
|
|
}
|
|
|
|
//
|
|
// Extract the counts from the two nibbles of this byte.
|
|
//
|
|
|
|
VcnBytes = *ch & 0xF;
|
|
LcnBytes = *ch++ >> 4;
|
|
|
|
//
|
|
// Extract the Vcn change (use of RtlCopyMemory works for little-Endian)
|
|
// and update HighestCandidate.
|
|
//
|
|
|
|
Change = 0;
|
|
|
|
//
|
|
// The file is corrupt if there are 0 or more than 8 Vcn change bytes,
|
|
// more than 8 Lcn change bytes, or if we would walk off the end of
|
|
// the record, or a Vcn change is negative.
|
|
//
|
|
|
|
if (((ULONG)(VcnBytes - 1) > 7) || (LcnBytes > 8) ||
|
|
((ch + VcnBytes + LcnBytes + 1) > (PCHAR)Add2Ptr(Attribute, Attribute->RecordLength)) ||
|
|
IsCharLtrZero(*(ch + VcnBytes - 1))) {
|
|
|
|
ASSERT( FALSE );
|
|
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
|
|
}
|
|
RtlCopyMemory( &Change, ch, VcnBytes );
|
|
ch += VcnBytes;
|
|
HighestCandidate = HighestCandidate + Change;
|
|
|
|
//
|
|
// Extract the Lcn change and update CurrentLcn.
|
|
//
|
|
|
|
if (LcnBytes != 0) {
|
|
|
|
Change = 0;
|
|
if (IsCharLtrZero(*(ch + LcnBytes - 1))) {
|
|
Change = Change - 1;
|
|
}
|
|
RtlCopyMemory( &Change, ch, LcnBytes );
|
|
ch += LcnBytes;
|
|
CurrentLcn = CurrentLcn + Change;
|
|
|
|
//
|
|
// Now add it in to the mcb.
|
|
//
|
|
|
|
if ((CurrentLcn >= 0) && (LcnBytes != 0)) {
|
|
|
|
LONGLONG ClustersToAdd;
|
|
ClustersToAdd = HighestCandidate - CurrentVcn;
|
|
|
|
//
|
|
// Now try to add the current run. We never expect this
|
|
// call to return false.
|
|
//
|
|
|
|
ASSERT( ((ULONG)CurrentLcn) != 0xffffffff );
|
|
|
|
#ifdef NTFS_CHECK_BITMAP
|
|
//
|
|
// Make sure these bits are allocated in our copy of the bitmap.
|
|
//
|
|
|
|
if ((Vcb->BitmapCopy != NULL) &&
|
|
!NtfsCheckBitmap( Vcb,
|
|
(ULONG) CurrentLcn,
|
|
(ULONG) ClustersToAdd,
|
|
TRUE )) {
|
|
|
|
NtfsBadBitmapCopy( IrpContext, (ULONG) CurrentLcn, (ULONG) ClustersToAdd );
|
|
}
|
|
#endif
|
|
if (!NtfsAddNtfsMcbEntry( &Scb->Mcb,
|
|
CurrentVcn,
|
|
CurrentLcn,
|
|
ClustersToAdd,
|
|
McbMutexAcquired )) {
|
|
|
|
ASSERTMSG( "Unable to add entry to Mcb\n", FALSE );
|
|
|
|
NtfsRaiseStatus( IrpContext,
|
|
STATUS_FILE_CORRUPT_ERROR,
|
|
NULL,
|
|
Scb->Fcb );
|
|
}
|
|
|
|
EntryAdded = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Make sure that at least the Mcb gets loaded.
|
|
//
|
|
|
|
if (!EntryAdded) {
|
|
NtfsAddNtfsMcbEntry( &Scb->Mcb,
|
|
CapturedLowestVcn,
|
|
UNUSED_LCN,
|
|
1,
|
|
McbMutexAcquired );
|
|
}
|
|
|
|
if ((Vcn < HighestCandidate) ||
|
|
(!NtfsLookupNextAttributeForScb( IrpContext, Scb, &Context ))) {
|
|
break;
|
|
} else {
|
|
Attribute = NtfsFoundAttribute( &Context );
|
|
ASSERT( !NtfsIsAttributeResident(Attribute) );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now free the mutex and lookup in the Mcb while we still own
|
|
// the resource.
|
|
//
|
|
|
|
if (McbMutexAcquired) {
|
|
NtfsReleaseNtfsMcbMutex( &Scb->Mcb );
|
|
McbMutexAcquired = FALSE;
|
|
}
|
|
|
|
if (NtfsLookupNtfsMcbEntry( &Scb->Mcb, Vcn, Lcn, ClusterCount, NULL, NULL, RangePtr, RunIndex )) {
|
|
|
|
Found = (BOOLEAN)(*Lcn != UNUSED_LCN);
|
|
|
|
if (Found) { ASSERT_LCN_RANGE_CHECKING( Vcb, (*Lcn + *ClusterCount) ); }
|
|
|
|
} else {
|
|
|
|
Found = FALSE;
|
|
|
|
//
|
|
// At the end of file, we pretend there is one large hole!
|
|
//
|
|
|
|
if (HighestCandidate >=
|
|
LlClustersFromBytes(Vcb, Scb->Header.AllocationSize.QuadPart)) {
|
|
HighestCandidate = MAXLONGLONG;
|
|
}
|
|
|
|
//
|
|
// If we're asked to lookup a vcn but the highest vcn we found on disk
|
|
// is less than it - than the file record is corrupt - even if there is
|
|
// a hole at the end it must be written in the mapping pairs on disk
|
|
//
|
|
|
|
if (HighestCandidate <= Vcn) {
|
|
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, &Scb->Fcb->FileReference, Scb->Fcb );
|
|
}
|
|
|
|
*ClusterCount = HighestCandidate - Vcn;
|
|
}
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsLookupAllocation );
|
|
|
|
//
|
|
// If this is an error case then we better unload what we've just
|
|
// loaded
|
|
//
|
|
|
|
if (AbnormalTermination() &&
|
|
(CapturedLowestVcn != MAXLONGLONG) ) {
|
|
|
|
NtfsUnloadNtfsMcbRange( &Scb->Mcb,
|
|
CapturedLowestVcn,
|
|
CapturedHighestVcn,
|
|
FALSE,
|
|
McbMutexAcquired );
|
|
}
|
|
|
|
//
|
|
// In all cases we free up the mcb that we locked before entering
|
|
// the try statement
|
|
//
|
|
|
|
if (McbMutexAcquired) {
|
|
NtfsReleaseNtfsMcbMutex( &Scb->Mcb );
|
|
}
|
|
|
|
NtfsReleaseResource( IrpContext, Scb );
|
|
|
|
//
|
|
// Cleanup the attribute context on the way out.
|
|
//
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &Context );
|
|
}
|
|
|
|
ASSERT( !Found ||
|
|
(*Lcn != 0) ||
|
|
(NtfsEqualMftRef( &Scb->Fcb->FileReference, &BootFileReference )) ||
|
|
(NtfsEqualMftRef( &Scb->Fcb->FileReference, &VolumeFileReference )));
|
|
|
|
DebugTrace( 0, Dbg, ("Lcn < %0I64x\n", *Lcn) );
|
|
DebugTrace( 0, Dbg, ("ClusterCount < %0I64x\n", *ClusterCount) );
|
|
DebugTrace( -1, Dbg, ("NtfsLookupAllocation -> %02lx\n", Found) );
|
|
|
|
return Found;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
NtfsIsRangeAllocated (
|
|
IN PSCB Scb,
|
|
IN VCN StartVcn,
|
|
IN VCN FinalCluster,
|
|
IN BOOLEAN RoundToSparseUnit,
|
|
OUT PLONGLONG ClusterCount
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called on a sparse file to test the status of a range of the
|
|
file. Ntfs will return whether the range is allocated and also a known value
|
|
for the length of the allocation. It is possible that the range extends
|
|
beyond this point but another call needs to be made to check it.
|
|
|
|
Our caller needs to verify that the Mcb is loaded in this range i.e precall NtfsPreLoadAllocation
|
|
|
|
Arguments:
|
|
|
|
Scb - Scb for the file to check. This should be a sparse file.
|
|
|
|
StartVcn - Vcn within the range to check first.
|
|
|
|
FinalCluster - Trim the clusters found so we don't go beyond this point.
|
|
|
|
RoundToSparseUnit - If TRUE the range is rounded up to VCB->SparseFileUnit == 0x10000
|
|
so you may get a range returned as allocated which contain
|
|
a partial sparse area depending on the compression unit.
|
|
|
|
ClusterCount - Address to store the count of clusters of a known state.
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - TRUE if the range is allocated, FALSE otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOLEAN AllocatedRange;
|
|
VCN ThisVcn;
|
|
VCN ThisLcn;
|
|
VCN ThisClusterCount;
|
|
PVOID RangePtr;
|
|
ULONG RunIndex;
|
|
|
|
ULONG VcnClusterOffset = 0;
|
|
VCN FoundClusterCount = 0;
|
|
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Assert that the file is sparse, non-resident and we are within file size.
|
|
//
|
|
|
|
ASSERT( FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_SPARSE ));
|
|
ASSERT( !FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT ));
|
|
|
|
//
|
|
// Move the starting point back to a sparse file boundary.
|
|
//
|
|
|
|
ThisVcn = StartVcn;
|
|
|
|
if (RoundToSparseUnit) {
|
|
VcnClusterOffset = ((PLARGE_INTEGER) &ThisVcn)->LowPart & (Scb->Vcb->SparseFileClusters - 1);
|
|
((PLARGE_INTEGER) &ThisVcn)->LowPart &= ~(Scb->Vcb->SparseFileClusters - 1);
|
|
}
|
|
|
|
//
|
|
// Lookup the allocation at that position.
|
|
//
|
|
|
|
AllocatedRange = NtfsLookupNtfsMcbEntry( &Scb->Mcb,
|
|
ThisVcn,
|
|
&ThisLcn,
|
|
&ThisClusterCount,
|
|
NULL,
|
|
NULL,
|
|
&RangePtr,
|
|
&RunIndex );
|
|
|
|
//
|
|
// If the range has no mapping then it is entirely sparse.
|
|
//
|
|
|
|
if (!AllocatedRange) {
|
|
|
|
ThisClusterCount = MAXLONGLONG;
|
|
|
|
//
|
|
// If the block is not allocated and the length of the run is not enough
|
|
// clusters for a sparse file unit then look to make sure the block
|
|
// is fully deallocated.
|
|
//
|
|
|
|
} else if (ThisLcn == UNUSED_LCN) {
|
|
|
|
AllocatedRange = FALSE;
|
|
|
|
while (TRUE) {
|
|
|
|
FoundClusterCount += ThisClusterCount;
|
|
ThisVcn += ThisClusterCount;
|
|
ThisClusterCount = 0;
|
|
|
|
//
|
|
// Check successive runs to extend the hole.
|
|
//
|
|
|
|
if (ThisVcn >= FinalCluster) {
|
|
|
|
break;
|
|
}
|
|
|
|
RunIndex += 1;
|
|
if (!NtfsGetSequentialMcbEntry( &Scb->Mcb,
|
|
&RangePtr,
|
|
RunIndex,
|
|
&ThisVcn,
|
|
&ThisLcn,
|
|
&ThisClusterCount )) {
|
|
|
|
//
|
|
// The file is deallocated from here to the end of the Mcb.
|
|
// Treat this as a large hole.
|
|
//
|
|
|
|
ThisClusterCount = MAXLONGLONG - FoundClusterCount;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// If the range is allocated and we haven't found a full sparse unit
|
|
// then mark the block as allocated. If we have at lease one sparse
|
|
// file unit then trim the hole back to the nearest sparse file
|
|
// unit boundary.
|
|
//
|
|
|
|
if (ThisLcn != UNUSED_LCN) {
|
|
|
|
if (RoundToSparseUnit) {
|
|
if (FoundClusterCount < Scb->Fcb->Vcb->SparseFileClusters) {
|
|
|
|
//
|
|
// Set our variables to indicate we are at the start of a fully
|
|
// allocated sparse block.
|
|
//
|
|
|
|
ThisVcn -= FoundClusterCount;
|
|
ThisClusterCount += FoundClusterCount;
|
|
FoundClusterCount = 0;
|
|
|
|
AllocatedRange = TRUE;
|
|
|
|
} else {
|
|
|
|
ThisClusterCount = 0;
|
|
((PLARGE_INTEGER) &FoundClusterCount)->LowPart &= ~(Scb->Vcb->SparseFileClusters - 1);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we have an allocated block then find all of the contiguous allocated
|
|
// blocks we can.
|
|
//
|
|
|
|
if (AllocatedRange) {
|
|
|
|
while (TRUE) {
|
|
|
|
if (RoundToSparseUnit) {
|
|
|
|
//
|
|
// Round the clusters found to a sparse file unit and update
|
|
// the next vcn and count of clusters found.
|
|
//
|
|
|
|
ThisClusterCount = BlockAlign( ThisClusterCount, (LONG)Scb->Fcb->Vcb->SparseFileClusters );
|
|
}
|
|
|
|
ThisVcn += ThisClusterCount;
|
|
FoundClusterCount += ThisClusterCount;
|
|
|
|
//
|
|
// Break out if we are past our final target or the beginning of the
|
|
// next range is not allocated.
|
|
//
|
|
|
|
if ((ThisVcn >= FinalCluster) ||
|
|
!NtfsLookupNtfsMcbEntry( &Scb->Mcb,
|
|
ThisVcn,
|
|
&ThisLcn,
|
|
&ThisClusterCount,
|
|
NULL,
|
|
NULL,
|
|
&RangePtr,
|
|
&RunIndex ) ||
|
|
(ThisLcn == UNUSED_LCN)) {
|
|
|
|
ThisClusterCount = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Trim the clusters found to either a sparse file unit or the input final
|
|
// cluster value.
|
|
//
|
|
|
|
*ClusterCount = ThisClusterCount + FoundClusterCount - (LONGLONG) VcnClusterOffset;
|
|
|
|
if ((FinalCluster - StartVcn) < *ClusterCount) {
|
|
|
|
*ClusterCount = FinalCluster - StartVcn;
|
|
}
|
|
|
|
return AllocatedRange;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
NtfsAllocateAttribute (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PSCB Scb,
|
|
IN ATTRIBUTE_TYPE_CODE AttributeTypeCode,
|
|
IN PUNICODE_STRING AttributeName OPTIONAL,
|
|
IN USHORT AttributeFlags,
|
|
IN BOOLEAN AllocateAll,
|
|
IN BOOLEAN LogIt,
|
|
IN LONGLONG Size,
|
|
IN PATTRIBUTE_ENUMERATION_CONTEXT NewLocation OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine creates a new attribute and allocates space for it, either in a
|
|
file record, or as a nonresident attribute.
|
|
|
|
Arguments:
|
|
|
|
Scb - Scb for the attribute.
|
|
|
|
AttributeTypeCode - Attribute type code to be created.
|
|
|
|
AttributeName - Optional name for the attribute.
|
|
|
|
AttributeFlags - Flags to be stored in the attribute record for this attribute.
|
|
|
|
AllocateAll - Specified as TRUE if all allocation should be allocated,
|
|
even if we have to break up the transaction.
|
|
|
|
LogIt - Most callers should specify TRUE, to have the change logged. However,
|
|
we can specify FALSE if we are creating a new file record, and
|
|
will be logging the entire new file record.
|
|
|
|
Size - Size in bytes to allocate for the attribute.
|
|
|
|
NewLocation - If specified, this is the location to store the attribute.
|
|
|
|
Return Value:
|
|
|
|
FALSE - if the attribute was created, but not all of the space was allocated
|
|
(this can only happen if Scb was not specified)
|
|
TRUE - if the space was allocated.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOLEAN UninitializeOnClose = FALSE;
|
|
BOOLEAN NewLocationSpecified;
|
|
ATTRIBUTE_ENUMERATION_CONTEXT Context;
|
|
LONGLONG ClusterCount, SavedClusterCount;
|
|
BOOLEAN FullAllocation;
|
|
PFCB Fcb = Scb->Fcb;
|
|
LONGLONG Delta = NtfsResidentStreamQuota( Fcb->Vcb );
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Either there is no compression taking place or the attribute
|
|
// type code allows compression to be specified in the header.
|
|
// $INDEX_ROOT is a special hack to store the inherited-compression
|
|
// flag.
|
|
//
|
|
|
|
ASSERT( (AttributeFlags == 0) ||
|
|
(AttributeTypeCode == $INDEX_ROOT) ||
|
|
NtfsIsTypeCodeCompressible( AttributeTypeCode ));
|
|
|
|
//
|
|
// If the file is being created compressed, then we need to round its
|
|
// size to a compression unit boundary.
|
|
//
|
|
|
|
if ((Scb->CompressionUnit != 0) &&
|
|
(Scb->Header.NodeTypeCode == NTFS_NTC_SCB_DATA)) {
|
|
|
|
Size = BlockAlign( Size, (LONG)Scb->CompressionUnit );
|
|
}
|
|
|
|
//
|
|
// Prepare for looking up attribute records to get the retrieval
|
|
// information.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT( NewLocation )) {
|
|
|
|
NewLocationSpecified = TRUE;
|
|
|
|
} else {
|
|
|
|
NtfsInitializeAttributeContext( &Context );
|
|
NewLocationSpecified = FALSE;
|
|
NewLocation = &Context;
|
|
}
|
|
|
|
try {
|
|
|
|
//
|
|
// If the FILE_SIZE_LOADED flag is not set, then this Scb is for
|
|
// an attribute that does not yet exist on disk. We will put zero
|
|
// into all of the sizes fields and set the flags indicating that
|
|
// Scb is valid. NOTE - This routine expects both FILE_SIZE_LOADED
|
|
// and HEADER_INITIALIZED to be both set or both clear.
|
|
//
|
|
|
|
ASSERT( BooleanFlagOn( Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED )
|
|
== BooleanFlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED ));
|
|
|
|
if (!FlagOn( Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED )) {
|
|
|
|
Scb->ValidDataToDisk =
|
|
Scb->Header.AllocationSize.QuadPart =
|
|
Scb->Header.FileSize.QuadPart =
|
|
Scb->Header.ValidDataLength.QuadPart = 0;
|
|
|
|
SetFlag( Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED |
|
|
SCB_STATE_HEADER_INITIALIZED |
|
|
SCB_STATE_UNINITIALIZE_ON_RESTORE );
|
|
|
|
UninitializeOnClose = TRUE;
|
|
}
|
|
|
|
//
|
|
// Now snapshot this Scb. We use a try-finally so we can uninitialize
|
|
// the scb if neccessary.
|
|
//
|
|
|
|
NtfsSnapshotScb( IrpContext, Scb );
|
|
|
|
UninitializeOnClose = FALSE;
|
|
|
|
//
|
|
// First allocate the space he wants.
|
|
//
|
|
|
|
SavedClusterCount =
|
|
ClusterCount = LlClustersFromBytes(Fcb->Vcb, Size);
|
|
|
|
Scb->TotalAllocated = 0;
|
|
|
|
if (Size != 0) {
|
|
|
|
ASSERT( NtfsIsExclusiveScb( Scb ));
|
|
|
|
Scb->ScbSnapshot->LowestModifiedVcn = 0;
|
|
Scb->ScbSnapshot->HighestModifiedVcn = MAXLONGLONG;
|
|
|
|
NtfsAllocateClusters( IrpContext,
|
|
Fcb->Vcb,
|
|
Scb,
|
|
(LONGLONG)0,
|
|
(BOOLEAN)!NtfsIsTypeCodeUserData( AttributeTypeCode ),
|
|
ClusterCount,
|
|
NULL,
|
|
&ClusterCount );
|
|
|
|
//
|
|
// Account for any new clusters in the allocation.
|
|
//
|
|
|
|
Delta += LlBytesFromClusters( Fcb->Vcb, ClusterCount );
|
|
}
|
|
|
|
//
|
|
// Make sure the owner is allowed to have this space.
|
|
//
|
|
|
|
if (FlagOn( Scb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA )) {
|
|
|
|
ASSERT( NtfsIsTypeCodeSubjectToQuota( Scb->AttributeTypeCode ));
|
|
|
|
NtfsConditionallyUpdateQuota( IrpContext,
|
|
Fcb,
|
|
&Delta,
|
|
LogIt,
|
|
TRUE );
|
|
}
|
|
|
|
//
|
|
// Now create the attribute. Remember if this routine
|
|
// cut the allocation because of logging problems.
|
|
//
|
|
|
|
FullAllocation = NtfsCreateAttributeWithAllocation( IrpContext,
|
|
Scb,
|
|
AttributeTypeCode,
|
|
AttributeName,
|
|
AttributeFlags,
|
|
LogIt,
|
|
NewLocationSpecified,
|
|
NewLocation );
|
|
|
|
if (AllocateAll &&
|
|
(!FullAllocation ||
|
|
(ClusterCount < SavedClusterCount))) {
|
|
|
|
//
|
|
// If we are creating the attribute, then we only need to pass a
|
|
// file object below if we already cached it ourselves, such as
|
|
// in the case of ConvertToNonresident.
|
|
//
|
|
|
|
NtfsAddAllocation( IrpContext,
|
|
Scb->FileObject,
|
|
Scb,
|
|
ClusterCount,
|
|
(SavedClusterCount - ClusterCount),
|
|
FALSE,
|
|
NULL );
|
|
|
|
//
|
|
// Show that we allocated all of the space.
|
|
//
|
|
|
|
ClusterCount = SavedClusterCount;
|
|
FullAllocation = TRUE;
|
|
}
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsAllocateAttribute );
|
|
|
|
//
|
|
// Cleanup the attribute context on the way out.
|
|
//
|
|
|
|
if (!NewLocationSpecified) {
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &Context );
|
|
}
|
|
|
|
//
|
|
// Clear out the Scb if it was uninitialized to begin with.
|
|
//
|
|
|
|
if (UninitializeOnClose) {
|
|
|
|
ClearFlag( Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED |
|
|
SCB_STATE_HEADER_INITIALIZED |
|
|
SCB_STATE_UNINITIALIZE_ON_RESTORE );
|
|
}
|
|
}
|
|
|
|
return (FullAllocation && (SavedClusterCount <= ClusterCount));
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsAddAllocation (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject OPTIONAL,
|
|
IN OUT PSCB Scb,
|
|
IN VCN StartingVcn,
|
|
IN LONGLONG ClusterCount,
|
|
IN LOGICAL AskForMore,
|
|
IN OUT PCCB CcbForWriteExtend OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine adds allocation to an existing nonresident attribute. None of
|
|
the allocation is allowed to already exist, as this would make error recovery
|
|
too difficult. The caller must insure that he only asks for space not already
|
|
allocated.
|
|
|
|
Arguments:
|
|
|
|
FileObject - FileObject for the Scb
|
|
|
|
Scb - Scb for the attribute needing allocation
|
|
|
|
StartingVcn - First Vcn to be allocated.
|
|
|
|
ClusterCount - Number of clusters to allocate.
|
|
|
|
AskForMore - Indicates if we want to ask for extra allocation.
|
|
|
|
CcbForWriteExtend - Use the WriteExtendCount in this Ccb to determine the number of times
|
|
this file has been write extended. Use this in combination with AskForMore to
|
|
determine how much more to ask for.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
LONGLONG DesiredClusterCount;
|
|
|
|
ATTRIBUTE_ENUMERATION_CONTEXT Context;
|
|
BOOLEAN Extending;
|
|
BOOLEAN AllocateAll;
|
|
|
|
|
|
PVCB Vcb = IrpContext->Vcb;
|
|
|
|
LONGLONG LlTemp1;
|
|
LONGLONG LlTemp2;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_SCB( Scb );
|
|
ASSERT_EXCLUSIVE_SCB( Scb );
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsAddAllocation\n") );
|
|
|
|
//
|
|
// Determine if we must allocate in one shot or if partial results are allowed.
|
|
//
|
|
|
|
if (NtfsIsTypeCodeUserData( Scb->AttributeTypeCode ) &&
|
|
NtfsSegmentNumber( &Scb->Fcb->FileReference ) >= FIRST_USER_FILE_NUMBER) {
|
|
|
|
AllocateAll = FALSE;
|
|
|
|
} else {
|
|
|
|
AllocateAll = TRUE;
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// We cannot add space in this high level routine during restart.
|
|
// Everything we can use is in the Mcb.
|
|
//
|
|
|
|
if (FlagOn(Scb->Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS)) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsAddAllocation (Nooped for Restart) -> VOID\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// We limit the user to 32 bits for the clusters unless the file is
|
|
// sparse. For sparse files we limit ourselves to 63 bits for the file size.
|
|
//
|
|
|
|
LlTemp1 = ClusterCount + StartingVcn;
|
|
|
|
if (!FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_SPARSE )) {
|
|
|
|
if ((((PLARGE_INTEGER)&ClusterCount)->HighPart != 0)
|
|
|| (((PLARGE_INTEGER)&StartingVcn)->HighPart != 0)
|
|
|| (((PLARGE_INTEGER)&LlTemp1)->HighPart != 0)) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_DISK_FULL, NULL, NULL );
|
|
}
|
|
}
|
|
|
|
//
|
|
// First make sure the Mcb is loaded.
|
|
//
|
|
|
|
NtfsPreloadAllocation( IrpContext, Scb, StartingVcn, StartingVcn + ClusterCount - 1 );
|
|
|
|
//
|
|
// Now make the call to add the new allocation, and get out if we do
|
|
// not actually have to allocate anything. Before we do the allocation
|
|
// call check if we need to compute a new desired cluster count for
|
|
// extending a data attribute. We never allocate more than the requested
|
|
// clusters for the Mft.
|
|
//
|
|
|
|
Extending = (BOOLEAN)((LONGLONG)LlBytesFromClusters(Vcb, (StartingVcn + ClusterCount)) >
|
|
Scb->Header.AllocationSize.QuadPart);
|
|
|
|
//
|
|
// Check if we need to modified the base Vcn value stored in the snapshot for
|
|
// the abort case.
|
|
//
|
|
|
|
ASSERT( NtfsIsExclusiveScb( Scb ));
|
|
|
|
NtfsSnapshotScb( IrpContext, Scb );
|
|
|
|
if (Scb->ScbSnapshot != NULL) {
|
|
|
|
if (StartingVcn < Scb->ScbSnapshot->LowestModifiedVcn) {
|
|
|
|
Scb->ScbSnapshot->LowestModifiedVcn = StartingVcn;
|
|
}
|
|
|
|
LlTemp1 -= 1;
|
|
if (LlTemp1 > Scb->ScbSnapshot->HighestModifiedVcn) {
|
|
|
|
if (Extending) {
|
|
Scb->ScbSnapshot->HighestModifiedVcn = MAXLONGLONG;
|
|
} else {
|
|
Scb->ScbSnapshot->HighestModifiedVcn = LlTemp1;
|
|
}
|
|
}
|
|
}
|
|
|
|
ASSERT( (Scb->ScbSnapshot != NULL) ||
|
|
!NtfsIsTypeCodeUserData( Scb->AttributeTypeCode ) ||
|
|
(Scb == Vcb->BitmapScb) );
|
|
|
|
if (AskForMore) {
|
|
|
|
LONGLONG MaxFreeClusters;
|
|
|
|
//
|
|
// Assume these are the same.
|
|
//
|
|
|
|
DesiredClusterCount = ClusterCount;
|
|
|
|
//
|
|
// If there is a Ccb with a WriteExtend count less than 4 then use it.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT( CcbForWriteExtend )) {
|
|
|
|
//
|
|
// We want to be slightly smart about the rounding factor. The key thing is to keep
|
|
// the user's data contiguous within likely IO boundaries (MM flush regions, etc).
|
|
// We will progressively round to higher even cluster values based on the number of times the
|
|
// user has extended the file.
|
|
//
|
|
|
|
if (CcbForWriteExtend->WriteExtendCount != 0) {
|
|
|
|
//
|
|
// Initialize the rounding mask to 2 clusters and higher multiples of 2.
|
|
//
|
|
|
|
ULONG RoundingMask = (1 << CcbForWriteExtend->WriteExtendCount);
|
|
|
|
//
|
|
// Next perform the basic shift based on the size of this allocation.
|
|
//
|
|
|
|
DesiredClusterCount = Int64ShllMod32( ClusterCount, CcbForWriteExtend->WriteExtendCount );
|
|
|
|
//
|
|
// Now bias this by the StartingVcn and round this to the selected boundary.
|
|
//
|
|
|
|
DesiredClusterCount = BlockAlign( DesiredClusterCount + StartingVcn, (LONG)RoundingMask );
|
|
|
|
//
|
|
// Remove the StartingVcn bias and see if there is anything left.
|
|
// Note: the 2nd test is for a longlong rollover
|
|
//
|
|
|
|
if ((DesiredClusterCount - StartingVcn < ClusterCount) ||
|
|
(DesiredClusterCount < StartingVcn)) {
|
|
|
|
DesiredClusterCount = ClusterCount;
|
|
|
|
} else {
|
|
DesiredClusterCount -= StartingVcn;
|
|
}
|
|
|
|
//
|
|
// Don't use more than 2^32 clusters.
|
|
//
|
|
|
|
if (StartingVcn + DesiredClusterCount > MAX_CLUSTERS_PER_RANGE) {
|
|
|
|
DesiredClusterCount = ClusterCount;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Increment the extend count.
|
|
//
|
|
|
|
if (CcbForWriteExtend->WriteExtendCount < NtfsExtendFactor) {
|
|
|
|
CcbForWriteExtend->WriteExtendCount += 1;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Make sure we don't exceed our maximum file size.
|
|
// Also don't swallow up too much of the remaining disk space.
|
|
//
|
|
|
|
MaxFreeClusters = Int64ShraMod32( Vcb->FreeClusters, 10 ) + ClusterCount;
|
|
|
|
if (Vcb->MaxClusterCount - StartingVcn < MaxFreeClusters) {
|
|
|
|
MaxFreeClusters = Vcb->MaxClusterCount - StartingVcn;
|
|
|
|
ASSERT( MaxFreeClusters >= ClusterCount );
|
|
}
|
|
|
|
if (DesiredClusterCount > MaxFreeClusters) {
|
|
|
|
DesiredClusterCount = MaxFreeClusters;
|
|
}
|
|
|
|
if (NtfsPerformQuotaOperation(Scb->Fcb)) {
|
|
|
|
NtfsGetRemainingQuota( IrpContext,
|
|
Scb->Fcb->OwnerId,
|
|
&LlTemp1,
|
|
&LlTemp2,
|
|
&Scb->Fcb->QuotaControl->QuickIndexHint );
|
|
|
|
//
|
|
// Do not use LlClustersFromBytesTruncate it is signed and this must be
|
|
// an unsigned operation.
|
|
//
|
|
|
|
LlTemp1 = Int64ShrlMod32( LlTemp1, Vcb->ClusterShift );
|
|
|
|
if (DesiredClusterCount > LlTemp1) {
|
|
|
|
//
|
|
// The owner is near their quota limit. Do not grow the
|
|
// file past the requested amount. Note we do not bother
|
|
// calculating a desired amount based on the remaining quota.
|
|
// This keeps us from using up a bunch of quota that we may
|
|
// not need when the user is near the limit.
|
|
//
|
|
|
|
DesiredClusterCount = ClusterCount;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
DesiredClusterCount = ClusterCount;
|
|
}
|
|
|
|
//
|
|
// All allocation adds for compressed / sparse files should start on a compression unit boundary
|
|
//
|
|
|
|
ASSERT( (Scb->CompressionUnit == 0) ||
|
|
!FlagOn( StartingVcn, ClustersFromBytes( Scb->Vcb, Scb->CompressionUnit ) - 1) );
|
|
|
|
//
|
|
// Prepare for looking up attribute records to get the retrieval
|
|
// information.
|
|
//
|
|
|
|
NtfsInitializeAttributeContext( &Context );
|
|
|
|
if (Extending &&
|
|
FlagOn( Scb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA ) &&
|
|
NtfsPerformQuotaOperation( Scb->Fcb )) {
|
|
|
|
ASSERT( NtfsIsTypeCodeSubjectToQuota( Scb->AttributeTypeCode ));
|
|
|
|
//
|
|
// The quota index must be acquired before the mft scb is acquired.
|
|
//
|
|
|
|
ASSERT( !NtfsIsExclusiveScb( Vcb->MftScb ) || NtfsIsSharedFcb( Vcb->QuotaTableScb->Fcb ) );
|
|
|
|
NtfsAcquireQuotaControl( IrpContext, Scb->Fcb->QuotaControl );
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
while (TRUE) {
|
|
|
|
// Toplevel action is currently incompatible with our error recovery.
|
|
// It also costs in performance.
|
|
//
|
|
// //
|
|
// // Start the top-level action by remembering the current UndoNextLsn.
|
|
// //
|
|
//
|
|
// if (IrpContext->TransactionId != 0) {
|
|
//
|
|
// PTRANSACTION_ENTRY TransactionEntry;
|
|
//
|
|
// NtfsAcquireSharedRestartTable( &Vcb->TransactionTable, TRUE );
|
|
//
|
|
// TransactionEntry = (PTRANSACTION_ENTRY)GetRestartEntryFromIndex(
|
|
// &Vcb->TransactionTable, IrpContext->TransactionId );
|
|
//
|
|
// StartLsn = TransactionEntry->UndoNextLsn;
|
|
// SavedUndoRecords = TransactionEntry->UndoRecords;
|
|
// SavedUndoBytes = TransactionEntry->UndoBytes;
|
|
// NtfsReleaseRestartTable( &Vcb->TransactionTable );
|
|
//
|
|
// } else {
|
|
//
|
|
// StartLsn = *(PLSN)&Li0;
|
|
// SavedUndoRecords = 0;
|
|
// SavedUndoBytes = 0;
|
|
// }
|
|
//
|
|
|
|
//
|
|
// Remember that the clusters are only in the Scb now.
|
|
//
|
|
|
|
if (NtfsAllocateClusters( IrpContext,
|
|
Scb->Vcb,
|
|
Scb,
|
|
StartingVcn,
|
|
AllocateAll,
|
|
ClusterCount,
|
|
NULL,
|
|
&DesiredClusterCount )) {
|
|
|
|
|
|
//
|
|
// We defer looking up the attribute to make the "already-allocated"
|
|
// case faster.
|
|
//
|
|
|
|
NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &Context );
|
|
|
|
//
|
|
// Now add the space to the file record, if any was allocated.
|
|
//
|
|
|
|
if (Extending) {
|
|
|
|
LlTemp1 = Scb->Header.AllocationSize.QuadPart;
|
|
|
|
NtfsAddAttributeAllocation( IrpContext,
|
|
Scb,
|
|
&Context,
|
|
NULL,
|
|
NULL );
|
|
|
|
//
|
|
// Make sure the owner is allowed to have these
|
|
// clusters.
|
|
//
|
|
|
|
if (FlagOn( Scb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA )) {
|
|
|
|
//
|
|
// Note the allocated clusters cannot be used
|
|
// here because StartingVcn may be greater
|
|
// then allocation size.
|
|
//
|
|
|
|
LlTemp1 = Scb->Header.AllocationSize.QuadPart - LlTemp1;
|
|
|
|
NtfsConditionallyUpdateQuota( IrpContext,
|
|
Scb->Fcb,
|
|
&LlTemp1,
|
|
TRUE,
|
|
TRUE );
|
|
}
|
|
} else {
|
|
|
|
NtfsAddAttributeAllocation( IrpContext,
|
|
Scb,
|
|
&Context,
|
|
&StartingVcn,
|
|
&ClusterCount );
|
|
}
|
|
|
|
//
|
|
// If he did not allocate anything, make sure we get out below.
|
|
//
|
|
|
|
} else {
|
|
DesiredClusterCount = ClusterCount;
|
|
}
|
|
|
|
//
|
|
// Call the Cache Manager to extend the section, now that we have
|
|
// succeeded.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT( FileObject) && Extending) {
|
|
|
|
NtfsSetBothCacheSizes( FileObject,
|
|
(PCC_FILE_SIZES)&Scb->Header.AllocationSize,
|
|
Scb );
|
|
}
|
|
|
|
//
|
|
// Set up to truncate on close.
|
|
//
|
|
|
|
SetFlag( Scb->ScbState, SCB_STATE_TRUNCATE_ON_CLOSE );
|
|
|
|
//
|
|
// See if we need to loop back.
|
|
//
|
|
|
|
if (DesiredClusterCount < ClusterCount) {
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &Context );
|
|
|
|
//
|
|
// Commit the current transaction if we have one.
|
|
//
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
|
|
//
|
|
// Adjust our parameters and reinitialize the context
|
|
// for the loop back.
|
|
//
|
|
|
|
StartingVcn = StartingVcn + DesiredClusterCount;
|
|
ClusterCount = ClusterCount - DesiredClusterCount;
|
|
DesiredClusterCount = ClusterCount;
|
|
NtfsInitializeAttributeContext( &Context );
|
|
|
|
//
|
|
// Else we are done.
|
|
//
|
|
|
|
} else {
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsAddAllocation );
|
|
|
|
//
|
|
// Cleanup the attribute context on the way out.
|
|
//
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &Context );
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsAddAllocation -> VOID\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsAddSparseAllocation (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject OPTIONAL,
|
|
IN OUT PSCB Scb,
|
|
IN LONGLONG StartingOffset,
|
|
IN LONGLONG ByteCount
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to add a hole to the end of a sparse file. We need to
|
|
force NtfsAddAttributeAllocation to extend a file via a hole. We do this be
|
|
adding a new range to the end of the Mcb and force it to have a LargeMcb.
|
|
NtfsAddAttributeAllocation recognizes this and will write the file record.
|
|
Otherwise that routine will truncate the hole at the end of a file.
|
|
|
|
Arguments:
|
|
|
|
FileObject - FileObject for the Scb
|
|
|
|
Scb - Scb for the attribute needing allocation
|
|
|
|
StartingOffset - File offset which contains the first compression unit to add.
|
|
|
|
ByteCount - Number of bytes to allocate from the StartingOffset.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
LONGLONG Range;
|
|
VCN StartingVcn = LlClustersFromBytesTruncate( Scb->Vcb,
|
|
Scb->Header.AllocationSize.LowPart );
|
|
BOOLEAN UnloadMcb = TRUE;
|
|
|
|
ATTRIBUTE_ENUMERATION_CONTEXT Context;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_SCB( Scb );
|
|
ASSERT_EXCLUSIVE_SCB( Scb );
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsAddSparseAllocation\n") );
|
|
|
|
//
|
|
// Do a sanity check on the following.
|
|
//
|
|
// - This is not restart.
|
|
// - This is a sparse file.
|
|
// - The StartingOffset is beyond the end of the file.
|
|
//
|
|
|
|
ASSERT( !FlagOn( Scb->Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS ) &&
|
|
FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_SPARSE ) &&
|
|
(StartingOffset >= Scb->Header.AllocationSize.QuadPart) );
|
|
|
|
//
|
|
// Check if we need to modified the base Vcn value stored in the snapshot for
|
|
// the abort case.
|
|
//
|
|
|
|
NtfsSnapshotScb( IrpContext, Scb );
|
|
|
|
if (Scb->ScbSnapshot != NULL) {
|
|
|
|
if (StartingVcn < Scb->ScbSnapshot->LowestModifiedVcn) {
|
|
|
|
Scb->ScbSnapshot->LowestModifiedVcn = StartingVcn;
|
|
}
|
|
|
|
Scb->ScbSnapshot->HighestModifiedVcn = MAXLONGLONG;
|
|
}
|
|
|
|
ASSERT( Scb->ScbSnapshot != NULL );
|
|
|
|
//
|
|
// Round the end of the allocation upto a compression unit boundary.
|
|
//
|
|
|
|
Range = BlockAlign( StartingOffset + ByteCount, (LONG)Scb->CompressionUnit );
|
|
|
|
ASSERT( Range <= MAXFILESIZE );
|
|
|
|
//
|
|
// Convert from bytes to clusters.
|
|
//
|
|
|
|
StartingVcn = LlClustersFromBytesTruncate( Scb->Vcb, Scb->Header.AllocationSize.QuadPart );
|
|
Range = LlClustersFromBytesTruncate( Scb->Vcb, Range );
|
|
|
|
//
|
|
// Initialize the lookup context.
|
|
//
|
|
|
|
NtfsInitializeAttributeContext( &Context );
|
|
|
|
try {
|
|
|
|
//
|
|
// Load the allocation for the range ahead of us.
|
|
//
|
|
|
|
if (StartingOffset != 0) {
|
|
|
|
NtfsPreloadAllocation( IrpContext,
|
|
Scb,
|
|
StartingVcn - 1,
|
|
StartingVcn - 1 );
|
|
}
|
|
|
|
//
|
|
// Define a range past the current end of the file.
|
|
//
|
|
|
|
NtfsDefineNtfsMcbRange( &Scb->Mcb,
|
|
StartingVcn,
|
|
Range - 1,
|
|
FALSE );
|
|
|
|
//
|
|
// Now add a single hole so that there is an Mcb entry.
|
|
//
|
|
|
|
NtfsAddNtfsMcbEntry( &Scb->Mcb,
|
|
StartingVcn,
|
|
UNUSED_LCN,
|
|
Range - StartingVcn,
|
|
FALSE );
|
|
|
|
//
|
|
// Lookup the first file record for this Scb.
|
|
//
|
|
|
|
NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &Context );
|
|
|
|
if (FlagOn( Scb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA ) &&
|
|
NtfsPerformQuotaOperation( Scb->Fcb )) {
|
|
|
|
ASSERT( NtfsIsTypeCodeSubjectToQuota( Scb->AttributeTypeCode ));
|
|
|
|
//
|
|
// The quota index must be acquired before the mft scb is acquired.
|
|
//
|
|
|
|
ASSERT( !NtfsIsExclusiveScb( Scb->Vcb->MftScb ) ||
|
|
NtfsIsSharedScb( Scb->Fcb->Vcb->QuotaTableScb ) );
|
|
|
|
NtfsAcquireQuotaControl( IrpContext, Scb->Fcb->QuotaControl );
|
|
}
|
|
|
|
//
|
|
// Now add the space to the file record, if any was allocated.
|
|
//
|
|
|
|
Range = Scb->Header.AllocationSize.QuadPart;
|
|
|
|
NtfsAddAttributeAllocation( IrpContext,
|
|
Scb,
|
|
&Context,
|
|
NULL,
|
|
NULL );
|
|
|
|
//
|
|
// Make sure the owner is allowed to have these
|
|
// clusters.
|
|
//
|
|
|
|
if (FlagOn( Scb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA )) {
|
|
|
|
//
|
|
// Note the allocated clusters cannot be used
|
|
// here because StartingVcn may be greater
|
|
// then allocation size.
|
|
//
|
|
|
|
Range = Scb->Header.AllocationSize.QuadPart - Range;
|
|
|
|
NtfsConditionallyUpdateQuota( IrpContext,
|
|
Scb->Fcb,
|
|
&Range,
|
|
TRUE,
|
|
TRUE );
|
|
}
|
|
|
|
//
|
|
// Call the Cache Manager to extend the section, now that we have
|
|
// succeeded.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT( FileObject)) {
|
|
|
|
NtfsSetBothCacheSizes( FileObject,
|
|
(PCC_FILE_SIZES)&Scb->Header.AllocationSize,
|
|
Scb );
|
|
}
|
|
|
|
//
|
|
// Set up to truncate on close.
|
|
//
|
|
|
|
SetFlag( Scb->ScbState, SCB_STATE_TRUNCATE_ON_CLOSE );
|
|
UnloadMcb = FALSE;
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsAddSparseAllocation );
|
|
|
|
//
|
|
// Manually unload the Mcb in the event of an error. There may not be a
|
|
// transaction underway.
|
|
//
|
|
|
|
if (UnloadMcb) {
|
|
|
|
NtfsUnloadNtfsMcbRange( &Scb->Mcb,
|
|
StartingVcn,
|
|
MAXLONGLONG,
|
|
FALSE,
|
|
FALSE );
|
|
}
|
|
|
|
//
|
|
// Cleanup the attribute context on the way out.
|
|
//
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &Context );
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsAddSparseAllocation -> VOID\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsDeleteAllocation (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject OPTIONAL,
|
|
IN OUT PSCB Scb,
|
|
IN VCN StartingVcn,
|
|
IN VCN EndingVcn,
|
|
IN BOOLEAN LogIt,
|
|
IN BOOLEAN BreakupAllowed
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine deletes allocation from an existing nonresident attribute. If all
|
|
or part of the allocation does not exist, the effect is benign, and only the
|
|
remaining allocation is deleted.
|
|
|
|
Arguments:
|
|
|
|
FileObject - FileObject for the Scb. This should always be specified if
|
|
possible, and must be specified if it is possible that MM has a
|
|
section created.
|
|
|
|
Scb - Scb for the attribute needing allocation
|
|
|
|
StartingVcn - First Vcn to be deallocated.
|
|
|
|
EndingVcn - Last Vcn to be deallocated, or xxMax to truncate at StartingVcn.
|
|
If EndingVcn is *not* xxMax, a sparse deallocation is performed,
|
|
and none of the stream sizes are changed.
|
|
|
|
LogIt - Most callers should specify TRUE, to have the change logged. However,
|
|
we can specify FALSE if we are deleting the file record, and
|
|
will be logging this delete.
|
|
|
|
BreakupAllowed - TRUE if the caller can tolerate breaking up the deletion of
|
|
allocation into multiple transactions, if there are a large
|
|
number of runs.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
VCN MyStartingVcn, MyEndingVcn;
|
|
VCN BlockStartingVcn = 0;
|
|
PVOID FirstRangePtr;
|
|
ULONG FirstRunIndex;
|
|
PVOID LastRangePtr;
|
|
ULONG LastRunIndex;
|
|
BOOLEAN BreakingUp = FALSE;
|
|
PVCB Vcb = Scb->Vcb;
|
|
|
|
LCN TempLcn;
|
|
LONGLONG TempCount;
|
|
ULONG CompressionUnitInClusters = 1;
|
|
|
|
PAGED_CODE();
|
|
|
|
if (Scb->CompressionUnit != 0) {
|
|
CompressionUnitInClusters = ClustersFromBytes( Vcb, Scb->CompressionUnit );
|
|
}
|
|
|
|
//
|
|
// If the file is compressed, make sure we round the allocation
|
|
// size to a compression unit boundary, so we correctly interpret
|
|
// the compression state of the data at the point we are
|
|
// truncating to. I.e., the danger is that we throw away one
|
|
// or more clusters at the end of compressed data! Note that this
|
|
// adjustment could cause us to noop the call.
|
|
//
|
|
|
|
if (Scb->CompressionUnit != 0) {
|
|
|
|
//
|
|
// Now check if we are truncating at the end of the file.
|
|
//
|
|
|
|
if (EndingVcn == MAXLONGLONG) {
|
|
StartingVcn = BlockAlign( StartingVcn, (LONG)CompressionUnitInClusters );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Make sure we have a snapshot and update it with the range of this deallocation.
|
|
//
|
|
|
|
ASSERT( NtfsIsExclusiveScb( Scb ));
|
|
|
|
NtfsSnapshotScb( IrpContext, Scb );
|
|
|
|
//
|
|
// Make sure update the VCN range in the snapshot. We need to
|
|
// do it each pass through the loop
|
|
//
|
|
|
|
if (Scb->ScbSnapshot != NULL) {
|
|
|
|
if (StartingVcn < Scb->ScbSnapshot->LowestModifiedVcn) {
|
|
|
|
Scb->ScbSnapshot->LowestModifiedVcn = StartingVcn;
|
|
}
|
|
|
|
if (EndingVcn > Scb->ScbSnapshot->HighestModifiedVcn) {
|
|
|
|
Scb->ScbSnapshot->HighestModifiedVcn = EndingVcn;
|
|
}
|
|
}
|
|
|
|
ASSERT( (Scb->ScbSnapshot != NULL) ||
|
|
!NtfsIsTypeCodeUserData( Scb->AttributeTypeCode ));
|
|
|
|
//
|
|
// We may not be able to preload the entire allocation for an
|
|
// extremely large fragmented file. The number of Mcb's may exhaust
|
|
// available pool. We will break the range to deallocate into smaller
|
|
// ranges when preloading the allocation.
|
|
//
|
|
|
|
do {
|
|
|
|
//
|
|
// If this is a large file and breakup is allowed then see if we
|
|
// want to break up the range of the deallocation.
|
|
//
|
|
|
|
if ((Scb->Header.AllocationSize.HighPart != 0) && BreakupAllowed) {
|
|
|
|
//
|
|
// If this is the first pass through then determine the starting point
|
|
// for this range.
|
|
//
|
|
|
|
if (BlockStartingVcn == 0) {
|
|
|
|
MyEndingVcn = EndingVcn;
|
|
|
|
if (EndingVcn == MAXLONGLONG) {
|
|
|
|
MyEndingVcn = LlClustersFromBytesTruncate( Vcb,
|
|
Scb->Header.AllocationSize.QuadPart ) - 1;
|
|
}
|
|
|
|
BlockStartingVcn = MyEndingVcn - Vcb->ClustersPer4Gig;
|
|
|
|
//
|
|
// Remember we are breaking up now, and that as a result
|
|
// we have to log everything.
|
|
//
|
|
|
|
BreakingUp = TRUE;
|
|
LogIt = TRUE;
|
|
|
|
} else {
|
|
|
|
//
|
|
// If we are truncating from the end of the file then raise CANT_WAIT. This will
|
|
// cause us to release our resources periodically when deleting a large file.
|
|
//
|
|
|
|
if (BreakingUp && (EndingVcn == MAXLONGLONG)) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
|
|
}
|
|
|
|
BlockStartingVcn -= Vcb->ClustersPer4Gig;
|
|
}
|
|
|
|
if (BlockStartingVcn < StartingVcn) {
|
|
|
|
BlockStartingVcn = StartingVcn;
|
|
|
|
} else if (Scb->CompressionUnit != 0) {
|
|
|
|
//
|
|
// Now check if we are truncating at the end of the file.
|
|
// Always truncate to a compression unit boundary.
|
|
//
|
|
|
|
if (EndingVcn == MAXLONGLONG) {
|
|
BlockStartingVcn = BlockAlign( BlockStartingVcn, (LONG)CompressionUnitInClusters );
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
BlockStartingVcn = StartingVcn;
|
|
}
|
|
|
|
//
|
|
// First make sure the Mcb is loaded. Note it is possible that
|
|
// we could need the previous range loaded if the delete starts
|
|
// at the beginning of a file record boundary, thus the -1.
|
|
//
|
|
|
|
NtfsPreloadAllocation( IrpContext, Scb, ((BlockStartingVcn != 0) ? (BlockStartingVcn - 1) : 0), EndingVcn );
|
|
|
|
//
|
|
// Loop to do one or more deallocate calls.
|
|
//
|
|
|
|
MyEndingVcn = EndingVcn;
|
|
do {
|
|
|
|
//
|
|
// Now lookup and get the indices for the first Vcn being deleted.
|
|
// If we are off the end, get out. We do this in the loop, because
|
|
// conceivably deleting space could change the range pointer and
|
|
// index of the first entry.
|
|
//
|
|
|
|
if (!NtfsLookupNtfsMcbEntry( &Scb->Mcb,
|
|
BlockStartingVcn,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&FirstRangePtr,
|
|
&FirstRunIndex )) {
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Now see if we can deallocate everything at once.
|
|
//
|
|
|
|
MyStartingVcn = BlockStartingVcn;
|
|
LastRunIndex = MAXULONG;
|
|
|
|
if (BreakupAllowed) {
|
|
|
|
//
|
|
// Now lookup and get the indices for the last Vcn being deleted.
|
|
// If we are off the end, get the last index.
|
|
//
|
|
|
|
if (!NtfsLookupNtfsMcbEntry( &Scb->Mcb,
|
|
MyEndingVcn,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&LastRangePtr,
|
|
&LastRunIndex )) {
|
|
|
|
NtfsNumberOfRunsInRange(&Scb->Mcb, LastRangePtr, &LastRunIndex);
|
|
}
|
|
|
|
//
|
|
// If the Vcns to delete span multiple ranges, or there
|
|
// are too many in the last range to delete, then we
|
|
// will calculate the index of a run to start with for
|
|
// this pass through the loop.
|
|
//
|
|
|
|
if ((FirstRangePtr != LastRangePtr) ||
|
|
((LastRunIndex - FirstRunIndex) > MAXIMUM_RUNS_AT_ONCE)) {
|
|
|
|
//
|
|
// Figure out where we can afford to truncate to.
|
|
//
|
|
|
|
if (LastRunIndex >= MAXIMUM_RUNS_AT_ONCE) {
|
|
LastRunIndex -= MAXIMUM_RUNS_AT_ONCE;
|
|
} else {
|
|
LastRunIndex = 0;
|
|
}
|
|
|
|
//
|
|
// Now lookup the first Vcn in this run.
|
|
//
|
|
|
|
NtfsGetNextNtfsMcbEntry( &Scb->Mcb,
|
|
&LastRangePtr,
|
|
LastRunIndex,
|
|
&MyStartingVcn,
|
|
&TempLcn,
|
|
&TempCount );
|
|
|
|
ASSERT(MyStartingVcn > BlockStartingVcn);
|
|
|
|
//
|
|
// If compressed, round down to a compression unit boundary.
|
|
//
|
|
|
|
MyStartingVcn = BlockAlignTruncate( MyStartingVcn, (LONG)CompressionUnitInClusters );
|
|
|
|
//
|
|
// Remember we are breaking up now, and that as a result
|
|
// we have to log everything.
|
|
//
|
|
|
|
BreakingUp = TRUE;
|
|
LogIt = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// CAIROBUG Consider optimizing this code when the cairo ifdef's
|
|
// are removed.
|
|
//
|
|
|
|
//
|
|
// If this is a user data stream and we are truncating to end the
|
|
// return the quota to the owner.
|
|
//
|
|
|
|
if (FlagOn( Scb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA ) &&
|
|
(EndingVcn == MAXLONGLONG)) {
|
|
|
|
//
|
|
// Calculate the amount that allocation size is being reduced.
|
|
//
|
|
|
|
TempCount = LlBytesFromClusters( Vcb, MyStartingVcn ) -
|
|
Scb->Header.AllocationSize.QuadPart;
|
|
|
|
NtfsConditionallyUpdateQuota( IrpContext,
|
|
Scb->Fcb,
|
|
&TempCount,
|
|
TRUE,
|
|
FALSE );
|
|
}
|
|
|
|
//
|
|
// Now deallocate a range of clusters
|
|
//
|
|
|
|
NtfsDeleteAllocationInternal( IrpContext,
|
|
Scb,
|
|
MyStartingVcn,
|
|
EndingVcn,
|
|
LogIt );
|
|
|
|
//
|
|
// Now, if we are breaking up this deallocation, then do some
|
|
// transaction cleanup.
|
|
//
|
|
|
|
if (BreakingUp) {
|
|
|
|
//
|
|
// Free the Mft Scb if we currently own it provided we are not
|
|
// truncating a stream in the Mft.
|
|
//
|
|
|
|
if ((NtfsSegmentNumber( &Scb->Fcb->FileReference ) != MASTER_FILE_TABLE_NUMBER) &&
|
|
(EndingVcn == MAXLONGLONG) &&
|
|
(Vcb->MftScb != NULL) &&
|
|
(Vcb->MftScb->Fcb->ExclusiveFcbLinks.Flink != NULL) &&
|
|
NtfsIsExclusiveScb( Vcb->MftScb )) {
|
|
|
|
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_RELEASE_MFT );
|
|
}
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
|
|
//
|
|
// Move the ending Vcn backwards in the file. This will
|
|
// let us move down to the next earlier file record if
|
|
// this case spans multiple file records.
|
|
//
|
|
|
|
MyEndingVcn = MyStartingVcn - 1;
|
|
}
|
|
|
|
//
|
|
// Call the Cache Manager to change allocation size for either
|
|
// truncate or SplitMcb case (where EndingVcn was set to xxMax!).
|
|
//
|
|
|
|
if ((EndingVcn == MAXLONGLONG) && ARGUMENT_PRESENT( FileObject )) {
|
|
|
|
NtfsSetBothCacheSizes( FileObject,
|
|
(PCC_FILE_SIZES)&Scb->Header.AllocationSize,
|
|
Scb );
|
|
}
|
|
|
|
} while (MyStartingVcn != BlockStartingVcn);
|
|
|
|
} while (BlockStartingVcn != StartingVcn);
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsReallocateRange (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN OUT PSCB Scb,
|
|
IN VCN DeleteVcn,
|
|
IN LONGLONG DeleteCount,
|
|
IN VCN AllocateVcn,
|
|
IN LONGLONG AllocateCount,
|
|
IN PLCN TargetLcn OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to reallocate a individual range within the existing
|
|
allocation of the file. Typically this might be used to reallocate a
|
|
compression unit or perform MoveFile. We can modify the Mcb and then
|
|
write a single log record to write the mapping information. This routine
|
|
doesn't make any attempt to split the Mcb. Also our caller must know
|
|
that the change of allocation occurs entirely within the existing virtual
|
|
allocation of the file.
|
|
|
|
We might expand this routine in the future to optimize the case where we
|
|
are reallocating a compression unit only because we believe it is fragmented
|
|
and there is a good chance to reduce fragmentation. We could check to see
|
|
if a single run is available and only reallocate if such a run exists.
|
|
|
|
Arguments:
|
|
|
|
Scb - Scb for the attribute needing a change of allocation.
|
|
|
|
DeleteVcn - Starting Vcn for the range to delete.
|
|
|
|
DeleteClusters - Count of clusters to delete. May be zero.
|
|
|
|
AllocateVcn - Starting Vcn for the range to allocate.
|
|
|
|
AllocateClusters - Count of clusters to allocate. May be zero.
|
|
|
|
TargetLcn - If specified reallocate to this particular LCN
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
VCN StartingVcn;
|
|
VCN EndingVcn;
|
|
|
|
ATTRIBUTE_ENUMERATION_CONTEXT Context;
|
|
ULONG CleanupContext = FALSE;
|
|
|
|
BOOLEAN ChangedAllocation = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsReallocateRange: Entered\n") );
|
|
|
|
//
|
|
// Let's make sure we are within the full allocation of the stream.
|
|
//
|
|
|
|
ASSERT( (DeleteCount == 0) ||
|
|
((DeleteVcn <= LlClustersFromBytesTruncate( IrpContext->Vcb, Scb->Header.AllocationSize.QuadPart )) &&
|
|
((DeleteVcn + DeleteCount) <= LlClustersFromBytesTruncate( IrpContext->Vcb, Scb->Header.AllocationSize.QuadPart ))));
|
|
|
|
|
|
ASSERT( (AllocateCount == 0) ||
|
|
((AllocateVcn <= LlClustersFromBytesTruncate( IrpContext->Vcb, Scb->Header.AllocationSize.QuadPart )) &&
|
|
((AllocateVcn + AllocateCount) <= LlClustersFromBytesTruncate( IrpContext->Vcb, Scb->Header.AllocationSize.QuadPart ))));
|
|
|
|
//
|
|
// Either one or both or our input counts may be zero. Make sure the zero-length
|
|
// ranges don't make us do extra work.
|
|
//
|
|
|
|
if (DeleteCount == 0) {
|
|
|
|
if (AllocateCount == 0) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsReallocateRange: Exit\n") );
|
|
return;
|
|
}
|
|
|
|
DeleteVcn = AllocateVcn;
|
|
|
|
//
|
|
// The range is set by the allocation clusters.
|
|
//
|
|
|
|
StartingVcn = AllocateVcn;
|
|
EndingVcn = AllocateVcn + AllocateCount;
|
|
|
|
} else if (AllocateCount == 0) {
|
|
|
|
AllocateVcn = DeleteVcn;
|
|
|
|
//
|
|
// The range is set by the deallocation clusters.
|
|
//
|
|
|
|
StartingVcn = DeleteVcn;
|
|
EndingVcn = DeleteVcn + DeleteCount;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Find the lowest starting point.
|
|
//
|
|
|
|
StartingVcn = DeleteVcn;
|
|
|
|
if (DeleteVcn > AllocateVcn) {
|
|
|
|
StartingVcn = AllocateVcn;
|
|
}
|
|
|
|
//
|
|
// Find the highest ending point.
|
|
//
|
|
|
|
EndingVcn = DeleteVcn + DeleteCount;
|
|
|
|
if (AllocateVcn + AllocateCount > EndingVcn) {
|
|
|
|
EndingVcn = AllocateVcn + AllocateCount;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Make sure we have a snapshot and update it with the range of this deallocation.
|
|
//
|
|
|
|
ASSERT( NtfsIsExclusiveScb( Scb ));
|
|
|
|
NtfsSnapshotScb( IrpContext, Scb );
|
|
|
|
//
|
|
// Make sure update the VCN range in the snapshot. We need to do this for both ranges.
|
|
//
|
|
|
|
if (Scb->ScbSnapshot != NULL) {
|
|
|
|
if (StartingVcn < Scb->ScbSnapshot->LowestModifiedVcn) {
|
|
|
|
Scb->ScbSnapshot->LowestModifiedVcn = StartingVcn;
|
|
}
|
|
|
|
if (EndingVcn > Scb->ScbSnapshot->HighestModifiedVcn) {
|
|
|
|
Scb->ScbSnapshot->HighestModifiedVcn = EndingVcn;
|
|
}
|
|
}
|
|
|
|
ASSERT( (Scb->ScbSnapshot != NULL) ||
|
|
!NtfsIsTypeCodeUserData( Scb->AttributeTypeCode ));
|
|
|
|
//
|
|
// First make sure the Mcb is loaded. Note it is possible that
|
|
// we could need the previous range loaded if the delete starts
|
|
// at the beginning of a file record boundary, thus the -1.
|
|
//
|
|
|
|
NtfsPreloadAllocation( IrpContext,
|
|
Scb,
|
|
((StartingVcn != 0) ? (StartingVcn - 1) : 0),
|
|
EndingVcn - 1 );
|
|
|
|
//
|
|
// Use a try-finally in case we need to unload the Mcb.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// Do the deallocate first.
|
|
//
|
|
|
|
if (DeleteCount != 0) {
|
|
|
|
ChangedAllocation = NtfsDeallocateClusters( IrpContext,
|
|
Scb->Vcb,
|
|
Scb,
|
|
DeleteVcn,
|
|
DeleteVcn + DeleteCount - 1,
|
|
&Scb->TotalAllocated );
|
|
}
|
|
|
|
//
|
|
// Now do the allocation.
|
|
//
|
|
|
|
if (AllocateCount != 0) {
|
|
|
|
//
|
|
// The allocate path is simpler. We don't worry about ranges.
|
|
// Remember if any bits are allocated though.
|
|
//
|
|
|
|
if (NtfsAllocateClusters( IrpContext,
|
|
Scb->Vcb,
|
|
Scb,
|
|
AllocateVcn,
|
|
TRUE,
|
|
AllocateCount,
|
|
TargetLcn,
|
|
&AllocateCount )) {
|
|
|
|
ChangedAllocation = TRUE;
|
|
}
|
|
}
|
|
|
|
if (ChangedAllocation) {
|
|
|
|
//
|
|
// Now rewrite the mapping for this range.
|
|
//
|
|
|
|
AllocateCount = EndingVcn - StartingVcn;
|
|
|
|
NtfsInitializeAttributeContext( &Context );
|
|
CleanupContext = TRUE;
|
|
|
|
NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &Context );
|
|
|
|
NtfsAddAttributeAllocation( IrpContext,
|
|
Scb,
|
|
&Context,
|
|
&StartingVcn,
|
|
&AllocateCount );
|
|
}
|
|
|
|
} finally {
|
|
|
|
if (AbnormalTermination()) {
|
|
|
|
//
|
|
// Unload the Mcb if we don't have a transaction. We need to do this
|
|
// in case we have already removed part of a range.
|
|
//
|
|
|
|
if (IrpContext->TransactionId == 0) {
|
|
|
|
NtfsUnloadNtfsMcbRange( &Scb->Mcb,
|
|
StartingVcn,
|
|
MAXLONGLONG,
|
|
FALSE,
|
|
FALSE );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Cleanup the context if needed.
|
|
//
|
|
|
|
if (CleanupContext) {
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &Context );
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsReallocateRange: Exit\n") );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal support routine
|
|
//
|
|
|
|
VOID
|
|
NtfsDeleteAllocationInternal (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN OUT PSCB Scb,
|
|
IN VCN StartingVcn,
|
|
IN VCN EndingVcn,
|
|
IN BOOLEAN LogIt
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine deletes allocation from an existing nonresident attribute. If all
|
|
or part of the allocation does not exist, the effect is benign, and only the
|
|
remaining allocation is deleted.
|
|
|
|
Arguments:
|
|
|
|
Scb - Scb for the attribute needing allocation
|
|
|
|
StartingVcn - First Vcn to be deallocated.
|
|
|
|
EndingVcn - Last Vcn to be deallocated, or xxMax to truncate at StartingVcn.
|
|
If EndingVcn is *not* xxMax, a sparse deallocation is performed,
|
|
and none of the stream sizes are changed.
|
|
|
|
LogIt - Most callers should specify TRUE, to have the change logged. However,
|
|
we can specify FALSE if we are deleting the file record, and
|
|
will be logging this delete.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
ATTRIBUTE_ENUMERATION_CONTEXT Context, TempContext;
|
|
PATTRIBUTE_RECORD_HEADER Attribute;
|
|
LONGLONG SizeInBytes, SizeInClusters;
|
|
VCN Vcn1;
|
|
PVCB Vcb = Scb->Vcb;
|
|
BOOLEAN AddSpaceBack = FALSE;
|
|
BOOLEAN SplitMcb = FALSE;
|
|
BOOLEAN UpdatedAllocationSize = FALSE;
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_SCB( Scb );
|
|
ASSERT_EXCLUSIVE_SCB( Scb );
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsDeleteAllocation\n") );
|
|
|
|
//
|
|
// Calculate new allocation size, assuming truncate.
|
|
//
|
|
|
|
SizeInBytes = LlBytesFromClusters( Vcb, StartingVcn );
|
|
|
|
ASSERT( (Scb->ScbSnapshot == NULL) ||
|
|
(Scb->ScbSnapshot->LowestModifiedVcn <= StartingVcn) );
|
|
|
|
//
|
|
// If this is a sparse deallocation, then we will have to call
|
|
// NtfsAddAttributeAllocation at the end to complete the fixup.
|
|
//
|
|
|
|
if (EndingVcn != MAXLONGLONG) {
|
|
|
|
AddSpaceBack = TRUE;
|
|
|
|
//
|
|
// If we have not written anything beyond the last Vcn to be
|
|
// deleted, then we can actually call FsRtlSplitLargeMcb to
|
|
// slide the allocated space up and keep the file contiguous!
|
|
//
|
|
// Ignore this if this is the Mft and we are creating a hole or
|
|
// if we are in the process of changing the compression state.
|
|
//
|
|
// If we were called from either SetEOF or SetAllocation for a
|
|
// compressed file then we can be doing a flush for the last
|
|
// page of the file as a result of a call to CcSetFileSizes.
|
|
// In this case we don't want to split the Mcb because we could
|
|
// reenter CcSetFileSizes and throw away the last page.
|
|
//
|
|
|
|
if (FlagOn( Scb->ScbState, SCB_STATE_WRITE_COMPRESSED ) &&
|
|
(EndingVcn >= LlClustersFromBytesTruncate( Vcb,
|
|
((Scb->ValidDataToDisk + Scb->CompressionUnit - 1) &
|
|
~((LONGLONG) (Scb->CompressionUnit - 1))))) &&
|
|
(Scb != Vcb->MftScb) &&
|
|
!FlagOn( Scb->ScbState, SCB_STATE_REALLOCATE_ON_WRITE ) &&
|
|
((IrpContext == IrpContext->TopLevelIrpContext) ||
|
|
(IrpContext->TopLevelIrpContext->MajorFunction != IRP_MJ_SET_INFORMATION))) {
|
|
|
|
ASSERT( Scb->CompressionUnit != 0 );
|
|
|
|
//
|
|
// If we are going to split the Mcb, then make sure it is fully loaded.
|
|
// Do not bother to split if there are multiple ranges involved, so we
|
|
// do not end up rewriting lots of file records.
|
|
//
|
|
|
|
if (NtfsPreloadAllocation(IrpContext, Scb, StartingVcn, MAXLONGLONG) <= 1) {
|
|
|
|
SizeInClusters = (EndingVcn - StartingVcn) + 1;
|
|
|
|
ASSERT( NtfsIsTypeCodeUserData( Scb->AttributeTypeCode ));
|
|
|
|
SplitMcb = NtfsSplitNtfsMcb( &Scb->Mcb, StartingVcn, SizeInClusters );
|
|
|
|
//
|
|
// If the delete is off the end, we can get out.
|
|
//
|
|
|
|
if (!SplitMcb) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// We must already have a snapshot to make sure the mcb is unloaded if
|
|
// something goes wrong
|
|
//
|
|
|
|
ASSERT( Scb->ScbSnapshot != NULL );
|
|
|
|
//
|
|
// We must protect the call below with a try-finally in
|
|
// order to unload the Split Mcb. If there is no transaction
|
|
// underway then a release of the Scb would cause the
|
|
// snapshot to go away.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// We are not properly synchronized to change AllocationSize,
|
|
// so we will delete any clusters that may have slid off the
|
|
// end. Since we are going to smash EndingVcn soon anyway,
|
|
// use it as a scratch to hold AllocationSize in Vcns...
|
|
//
|
|
|
|
EndingVcn = LlClustersFromBytes(Vcb, Scb->Header.AllocationSize.QuadPart);
|
|
|
|
NtfsDeallocateClusters( IrpContext,
|
|
Vcb,
|
|
Scb,
|
|
EndingVcn,
|
|
MAXLONGLONG,
|
|
&Scb->TotalAllocated );
|
|
|
|
} finally {
|
|
|
|
if (AbnormalTermination() && (IrpContext->TransactionId == 0)) {
|
|
|
|
NtfsUnloadNtfsMcbRange( &Scb->Mcb,
|
|
StartingVcn,
|
|
MAXLONGLONG,
|
|
FALSE,
|
|
FALSE );
|
|
}
|
|
}
|
|
|
|
NtfsUnloadNtfsMcbRange( &Scb->Mcb,
|
|
EndingVcn,
|
|
MAXLONGLONG,
|
|
TRUE,
|
|
FALSE );
|
|
|
|
//
|
|
// Since we did a split, jam highest modified all the way up.
|
|
//
|
|
|
|
Scb->ScbSnapshot->HighestModifiedVcn = MAXLONGLONG;
|
|
|
|
//
|
|
// We will have to redo all of the allocation to the end now.
|
|
//
|
|
|
|
EndingVcn = MAXLONGLONG;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now make the call to delete the allocation (if we did not just split
|
|
// the Mcb), and get out if we didn't have to do anything, because a
|
|
// hole is being created where there is already a hole.
|
|
//
|
|
|
|
if (!SplitMcb &&
|
|
!NtfsDeallocateClusters( IrpContext,
|
|
Vcb,
|
|
Scb,
|
|
StartingVcn,
|
|
EndingVcn,
|
|
&Scb->TotalAllocated ) &&
|
|
EndingVcn != MAXLONGLONG) {
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// On successful truncates, we nuke the entire range here.
|
|
//
|
|
|
|
if (!SplitMcb && (EndingVcn == MAXLONGLONG)) {
|
|
|
|
NtfsUnloadNtfsMcbRange( &Scb->Mcb, StartingVcn, MAXLONGLONG, TRUE, FALSE );
|
|
}
|
|
|
|
//
|
|
// Prepare for looking up attribute records to get the retrieval
|
|
// information.
|
|
//
|
|
|
|
NtfsInitializeAttributeContext( &Context );
|
|
NtfsInitializeAttributeContext( &TempContext );
|
|
|
|
try {
|
|
|
|
//
|
|
// Lookup the attribute record so we can ultimately delete space to it.
|
|
//
|
|
|
|
NtfsLookupAttributeForScb( IrpContext, Scb, &StartingVcn, &Context );
|
|
|
|
//
|
|
// Now loop to delete the space to the file record. Do not do this if LogIt
|
|
// is FALSE, as this is someone trying to delete the entire file
|
|
// record, so we do not have to clean up the attribute record.
|
|
//
|
|
|
|
if (LogIt) {
|
|
|
|
do {
|
|
|
|
Attribute = NtfsFoundAttribute(&Context);
|
|
|
|
//
|
|
// If there is no overlap, then continue.
|
|
//
|
|
|
|
if ((Attribute->Form.Nonresident.HighestVcn < StartingVcn) ||
|
|
(Attribute->Form.Nonresident.LowestVcn > EndingVcn)) {
|
|
|
|
continue;
|
|
|
|
//
|
|
// If all of the allocation is going away, then delete the entire
|
|
// record. We have to show that the allocation is already deleted
|
|
// to avoid being called back via NtfsDeleteAttributeRecord! We
|
|
// avoid this for the first instance of this attribute.
|
|
//
|
|
|
|
} else if ((Attribute->Form.Nonresident.LowestVcn >= StartingVcn) &&
|
|
(EndingVcn == MAXLONGLONG) &&
|
|
(Attribute->Form.Nonresident.LowestVcn != 0)) {
|
|
|
|
NtfsDeleteAttributeRecord( IrpContext,
|
|
Scb->Fcb,
|
|
(LogIt ? DELETE_LOG_OPERATION : 0) |
|
|
DELETE_RELEASE_FILE_RECORD,
|
|
&Context );
|
|
|
|
//
|
|
// If just part of the allocation is going away, then make the
|
|
// call here to reconstruct the mapping pairs array.
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// If this is the end of a sparse deallocation, then break out
|
|
// because we will rewrite this file record below anyway.
|
|
//
|
|
|
|
if (EndingVcn <= Attribute->Form.Nonresident.HighestVcn) {
|
|
break;
|
|
|
|
//
|
|
// If we split the Mcb, then make sure we only regenerate the
|
|
// mapping pairs once at the split point (but continue to
|
|
// scan for any entire records to delete).
|
|
//
|
|
|
|
} else if (SplitMcb) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// If this is a sparse deallocation, then we have to call to
|
|
// add the allocation, since it is possible that the file record
|
|
// must split.
|
|
//
|
|
|
|
if (EndingVcn != MAXLONGLONG) {
|
|
|
|
//
|
|
// Compute the last Vcn in the file, Then remember if it is smaller,
|
|
// because that is the last one we will delete to, in that case.
|
|
//
|
|
|
|
Vcn1 = Attribute->Form.Nonresident.HighestVcn;
|
|
|
|
SizeInClusters = (Vcn1 - Attribute->Form.Nonresident.LowestVcn) + 1;
|
|
Vcn1 = Attribute->Form.Nonresident.LowestVcn;
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &TempContext );
|
|
NtfsInitializeAttributeContext( &TempContext );
|
|
|
|
NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &TempContext );
|
|
|
|
NtfsAddAttributeAllocation( IrpContext,
|
|
Scb,
|
|
&TempContext,
|
|
&Vcn1,
|
|
&SizeInClusters );
|
|
|
|
//
|
|
// Since we used a temporary context we will need to
|
|
// restart the scan from the first file record. We update
|
|
// the range to deallocate by the last operation. In most
|
|
// cases we will only need to modify one file record and
|
|
// we can exit this loop.
|
|
//
|
|
|
|
StartingVcn = Vcn1 + SizeInClusters;
|
|
|
|
if (StartingVcn > EndingVcn) {
|
|
|
|
break;
|
|
}
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &Context );
|
|
NtfsInitializeAttributeContext( &Context );
|
|
|
|
NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &Context );
|
|
continue;
|
|
|
|
//
|
|
// Otherwise, we can simply delete the allocation, because
|
|
// we know the file record cannot grow.
|
|
//
|
|
|
|
} else {
|
|
|
|
Vcn1 = StartingVcn - 1;
|
|
|
|
NtfsDeleteAttributeAllocation( IrpContext,
|
|
Scb,
|
|
LogIt,
|
|
&Vcn1,
|
|
&Context,
|
|
TRUE );
|
|
|
|
//
|
|
// The call above will update the allocation size and
|
|
// set the new file sizes on disk.
|
|
//
|
|
|
|
UpdatedAllocationSize = TRUE;
|
|
}
|
|
}
|
|
|
|
} while (NtfsLookupNextAttributeForScb(IrpContext, Scb, &Context));
|
|
|
|
//
|
|
// If this deletion makes the file sparse, then we have to call
|
|
// NtfsAddAttributeAllocation to regenerate the mapping pairs.
|
|
// Note that potentially they may no longer fit, and we could actually
|
|
// have to add a file record.
|
|
//
|
|
|
|
if (AddSpaceBack) {
|
|
|
|
//
|
|
// If we did not just split the Mcb, we have to calculate the
|
|
// SizeInClusters parameter for NtfsAddAttributeAllocation.
|
|
//
|
|
|
|
if (!SplitMcb) {
|
|
|
|
//
|
|
// Compute the last Vcn in the file, Then remember if it is smaller,
|
|
// because that is the last one we will delete to, in that case.
|
|
//
|
|
|
|
Vcn1 = Attribute->Form.Nonresident.HighestVcn;
|
|
|
|
//
|
|
// Get out if there is nothing to delete.
|
|
//
|
|
|
|
if (Vcn1 < StartingVcn) {
|
|
try_return(NOTHING);
|
|
}
|
|
|
|
SizeInClusters = (Vcn1 - Attribute->Form.Nonresident.LowestVcn) + 1;
|
|
Vcn1 = Attribute->Form.Nonresident.LowestVcn;
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &Context );
|
|
NtfsInitializeAttributeContext( &Context );
|
|
|
|
NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &Context );
|
|
|
|
NtfsAddAttributeAllocation( IrpContext,
|
|
Scb,
|
|
&Context,
|
|
&Vcn1,
|
|
&SizeInClusters );
|
|
|
|
} else {
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &Context );
|
|
NtfsInitializeAttributeContext( &Context );
|
|
|
|
NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &Context );
|
|
|
|
NtfsAddAttributeAllocation( IrpContext,
|
|
Scb,
|
|
&Context,
|
|
NULL,
|
|
NULL );
|
|
|
|
}
|
|
|
|
//
|
|
// If we truncated the file by removing a file record but didn't update
|
|
// the new allocation size then do so now. We don't have to worry about
|
|
// this for the sparse deallocation path.
|
|
//
|
|
|
|
} else if (!UpdatedAllocationSize) {
|
|
|
|
Scb->Header.AllocationSize.QuadPart = SizeInBytes;
|
|
|
|
if (Scb->Header.ValidDataLength.QuadPart > SizeInBytes) {
|
|
Scb->Header.ValidDataLength.QuadPart = SizeInBytes;
|
|
}
|
|
|
|
if (Scb->Header.FileSize.QuadPart > SizeInBytes) {
|
|
Scb->Header.FileSize.QuadPart = SizeInBytes;
|
|
}
|
|
|
|
//
|
|
// Possibly update ValidDataToDisk
|
|
//
|
|
|
|
if (FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK ) &&
|
|
(SizeInBytes < Scb->ValidDataToDisk)) {
|
|
|
|
Scb->ValidDataToDisk = SizeInBytes;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If this was a sparse deallocation, it is time to get out once we
|
|
// have fixed up the allocation information.
|
|
//
|
|
|
|
if (SplitMcb || (EndingVcn != MAXLONGLONG)) {
|
|
try_return(NOTHING);
|
|
}
|
|
|
|
//
|
|
// We update the allocation size in the attribute, only for normal
|
|
// truncates (AddAttributeAllocation does this for SplitMcb case).
|
|
//
|
|
|
|
if (LogIt) {
|
|
|
|
#ifdef BENL_DBG
|
|
BOOLEAN WroteIt;
|
|
|
|
WroteIt =
|
|
#endif
|
|
|
|
NtfsWriteFileSizes( IrpContext,
|
|
Scb,
|
|
&Scb->Header.ValidDataLength.QuadPart,
|
|
FALSE,
|
|
TRUE,
|
|
TRUE );
|
|
#ifdef BENL_DBG
|
|
ASSERT( WroteIt );
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// Free any reserved clusters in the space freed.
|
|
//
|
|
|
|
if ((EndingVcn == MAXLONGLONG) && (Scb->CompressionUnit != 0)) {
|
|
|
|
NtfsFreeReservedClusters( Scb,
|
|
LlBytesFromClusters(Vcb, StartingVcn),
|
|
0 );
|
|
}
|
|
|
|
try_exit: NOTHING;
|
|
} finally {
|
|
|
|
//
|
|
// If we raised and have split the mcb and have not started a transaction
|
|
// throw out the modified range of the mcb. We do this because in this case the
|
|
// snapshot can then be discarded by a caller releasing the fcb involved see NtfsZeroData
|
|
// so that the normal truncation that occurs in ProcessException will be skipped
|
|
//
|
|
|
|
if (AbnormalTermination() && SplitMcb && (IrpContext->TransactionId == 0)) {
|
|
|
|
ASSERT( Scb->ScbSnapshot );
|
|
|
|
//
|
|
// Unload any modified ranges in the Mcb.
|
|
//
|
|
|
|
NtfsUnloadNtfsMcbRange( &Scb->Mcb, Scb->ScbSnapshot->LowestModifiedVcn, MAXLONGLONG, FALSE, FALSE );
|
|
}
|
|
|
|
DebugUnwind( NtfsDeleteAllocationInternal );
|
|
|
|
//
|
|
// Cleanup the attribute context on the way out.
|
|
//
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &Context );
|
|
NtfsCleanupAttributeContext( IrpContext, &TempContext );
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsDeleteAllocationInternal -> VOID\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
ULONG
|
|
NtfsGetSizeForMappingPairs (
|
|
IN PNTFS_MCB Mcb,
|
|
IN ULONG BytesAvailable,
|
|
IN VCN LowestVcn,
|
|
IN PVCN StopOnVcn OPTIONAL,
|
|
OUT PVCN StoppedOnVcn
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine calculates the size required to describe the given Mcb in
|
|
a mapping pairs array. The caller may specify how many bytes are available
|
|
for mapping pairs storage, for the event that the entire Mcb cannot be
|
|
be represented. In any case, StoppedOnVcn returns the Vcn to supply to
|
|
NtfsBuildMappingPairs in order to generate the specified number of bytes.
|
|
In the event that the entire Mcb could not be described in the bytes available,
|
|
StoppedOnVcn is also the correct value to specify to resume the building
|
|
of mapping pairs in a subsequent record.
|
|
|
|
Arguments:
|
|
|
|
Mcb - The Mcb describing new allocation.
|
|
|
|
BytesAvailable - Bytes available for storing mapping pairs. This routine
|
|
is guaranteed to stop before returning a count greater
|
|
than this.
|
|
|
|
LowestVcn - Lowest Vcn field applying to the mapping pairs array
|
|
|
|
StopOnVcn - If specified, calculating size at the first run starting with a Vcn
|
|
beyond the specified Vcn
|
|
|
|
StoppedOnVcn - Returns the Vcn on which a stop was necessary, or xxMax if
|
|
the entire Mcb could be stored. This Vcn should be
|
|
subsequently supplied to NtfsBuildMappingPairs to generate
|
|
the calculated number of bytes.
|
|
|
|
Return Value:
|
|
|
|
Size required required for entire new array in bytes.
|
|
|
|
--*/
|
|
|
|
{
|
|
VCN NextVcn, CurrentVcn, LimitVcn;
|
|
LCN CurrentLcn;
|
|
VCN RunVcn;
|
|
LCN RunLcn;
|
|
BOOLEAN Found;
|
|
LONGLONG RunCount;
|
|
VCN HighestVcn;
|
|
PVOID RangePtr;
|
|
ULONG RunIndex;
|
|
ULONG MSize = 0;
|
|
ULONG LastSize = 0;
|
|
BOOLEAN FoundRun = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
HighestVcn = MAXLONGLONG;
|
|
|
|
//
|
|
// Initialize CurrentLcn as it will be initialized for decode.
|
|
//
|
|
|
|
CurrentLcn = 0;
|
|
NextVcn = RunVcn = LowestVcn;
|
|
|
|
//
|
|
// Limit ourselves to less than 32 bits for each mapping pair range.
|
|
// We use -2 here because we point to the Vcn to stop on, the length
|
|
// is one greater.
|
|
//
|
|
|
|
LimitVcn = MAXLONGLONG - 1;
|
|
|
|
//
|
|
// Use the input stop point if smaller.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT( StopOnVcn )) {
|
|
|
|
LimitVcn = *StopOnVcn;
|
|
}
|
|
|
|
Found = NtfsLookupNtfsMcbEntry( Mcb, RunVcn, &RunLcn, &RunCount, NULL, NULL, &RangePtr, &RunIndex );
|
|
|
|
//
|
|
// Loop through the Mcb to calculate the size of the mapping array.
|
|
//
|
|
|
|
while (TRUE) {
|
|
|
|
LONGLONG Change;
|
|
PCHAR cp;
|
|
|
|
//
|
|
// See if there is another entry in the Mcb.
|
|
//
|
|
|
|
if (!Found) {
|
|
|
|
//
|
|
// If the caller did not specify StopOnVcn, then break out.
|
|
//
|
|
|
|
if (!ARGUMENT_PRESENT(StopOnVcn)) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Otherwise, describe the "hole" up to and including the
|
|
// Vcn we are stopping on.
|
|
//
|
|
|
|
RunVcn = NextVcn;
|
|
RunLcn = UNUSED_LCN;
|
|
|
|
RunCount = (LimitVcn - RunVcn) + 1;
|
|
RunIndex = MAXULONG - 1;
|
|
|
|
//
|
|
// If this is the first non-hole then we need to enforce a cluster
|
|
// per range limit.
|
|
//
|
|
|
|
} else if (!FoundRun &&
|
|
(RunLcn != UNUSED_LCN)) {
|
|
|
|
if ((LowestVcn + MAX_CLUSTERS_PER_RANGE) <= LimitVcn) {
|
|
|
|
//
|
|
// If we are already beyond the limit then set
|
|
// the limit back to just before the current run.
|
|
// We allow a hole which is larger than our limit.
|
|
//
|
|
|
|
if (RunVcn >= MAX_CLUSTERS_PER_RANGE) {
|
|
|
|
LimitVcn = RunVcn - 1;
|
|
|
|
} else {
|
|
|
|
LimitVcn = LowestVcn + MAX_CLUSTERS_PER_RANGE - 1;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Other checks in the system should prevent rollover.
|
|
//
|
|
|
|
ASSERT( (LimitVcn + 1) >= LowestVcn );
|
|
FoundRun = TRUE;
|
|
}
|
|
|
|
//
|
|
// If we were asked to stop after a certain Vcn, or we have
|
|
// exceeded our limit then stop now.
|
|
//
|
|
|
|
if (RunVcn > LimitVcn) {
|
|
|
|
if (HighestVcn == MAXLONGLONG) {
|
|
HighestVcn = LimitVcn + 1;
|
|
}
|
|
break;
|
|
|
|
//
|
|
// If this run extends beyond the current end of this attribute
|
|
// record, then we still need to stop where we are supposed to
|
|
// after outputting this run.
|
|
//
|
|
|
|
} else if ((RunVcn + RunCount) > LimitVcn) {
|
|
HighestVcn = LimitVcn + 1;
|
|
}
|
|
|
|
//
|
|
// Advance the RunIndex for the next call.
|
|
//
|
|
|
|
RunIndex += 1;
|
|
|
|
//
|
|
// Add in one for the count byte.
|
|
//
|
|
|
|
MSize += 1;
|
|
|
|
//
|
|
// NextVcn becomes current Vcn and we calculate the new NextVcn.
|
|
//
|
|
|
|
CurrentVcn = RunVcn;
|
|
NextVcn = RunVcn + RunCount;
|
|
|
|
//
|
|
// Calculate the Vcn change to store.
|
|
//
|
|
|
|
Change = NextVcn - CurrentVcn;
|
|
|
|
//
|
|
// Now calculate the first byte to actually output
|
|
//
|
|
|
|
if (Change < 0) {
|
|
|
|
GetNegativeByte( (PLARGE_INTEGER)&Change, &cp );
|
|
|
|
} else {
|
|
|
|
GetPositiveByte( (PLARGE_INTEGER)&Change, &cp );
|
|
}
|
|
|
|
//
|
|
// Now add in the number of Vcn change bytes.
|
|
//
|
|
|
|
MSize += (ULONG)(cp - (PCHAR)&Change + 1);
|
|
|
|
//
|
|
// Do not output any Lcn bytes if it is the unused Lcn.
|
|
//
|
|
|
|
if (RunLcn != UNUSED_LCN) {
|
|
|
|
//
|
|
// Calculate the Lcn change to store.
|
|
//
|
|
|
|
Change = RunLcn - CurrentLcn;
|
|
|
|
//
|
|
// Now calculate the first byte to actually output
|
|
//
|
|
|
|
if (Change < 0) {
|
|
|
|
GetNegativeByte( (PLARGE_INTEGER)&Change, &cp );
|
|
|
|
} else {
|
|
|
|
GetPositiveByte( (PLARGE_INTEGER)&Change, &cp );
|
|
}
|
|
|
|
//
|
|
// Now add in the number of Lcn change bytes.
|
|
//
|
|
|
|
MSize += (ULONG)(cp - (PCHAR)&Change + 1);
|
|
|
|
CurrentLcn = RunLcn;
|
|
|
|
//
|
|
// If this is the first run then enforce the 32 bit limit.
|
|
//
|
|
|
|
if (!FoundRun) {
|
|
|
|
if ((LowestVcn + MAX_CLUSTERS_PER_RANGE - 1) < LimitVcn) {
|
|
|
|
LimitVcn = LowestVcn + MAX_CLUSTERS_PER_RANGE - 1;
|
|
}
|
|
FoundRun = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now see if we can still store the required number of bytes,
|
|
// and get out if not.
|
|
//
|
|
|
|
if ((MSize + 1) > BytesAvailable) {
|
|
|
|
HighestVcn = RunVcn;
|
|
MSize = LastSize;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Now advance some locals before looping back.
|
|
//
|
|
|
|
LastSize = MSize;
|
|
|
|
Found = NtfsGetSequentialMcbEntry( Mcb, &RangePtr, RunIndex, &RunVcn, &RunLcn, &RunCount );
|
|
}
|
|
|
|
//
|
|
// The caller had sufficient bytes available to store at least one
|
|
// run, or that we were able to process the entire (empty) Mcb.
|
|
//
|
|
|
|
ASSERT( (MSize != 0) || (HighestVcn == LimitVcn + 1) );
|
|
|
|
//
|
|
// Return the Vcn we stopped on (or xxMax) and the size caculated,
|
|
// adding one for the terminating 0.
|
|
//
|
|
|
|
*StoppedOnVcn = HighestVcn;
|
|
|
|
return MSize + 1;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
NtfsBuildMappingPairs (
|
|
IN PNTFS_MCB Mcb,
|
|
IN VCN LowestVcn,
|
|
IN OUT PVCN HighestVcn,
|
|
OUT PCHAR MappingPairs
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine builds a new mapping pairs array or adds to an old one.
|
|
|
|
At this time, this routine only supports adding to the end of the
|
|
Mapping Pairs Array.
|
|
|
|
Arguments:
|
|
|
|
Mcb - The Mcb describing new allocation.
|
|
|
|
LowestVcn - Lowest Vcn field applying to the mapping pairs array
|
|
|
|
HighestVcn - On input supplies the highest Vcn, after which we are to stop.
|
|
On output, returns the actual Highest Vcn represented in the
|
|
MappingPairs array, or LlNeg1 if the array is empty.
|
|
|
|
MappingPairs - Points to the current mapping pairs array to be extended.
|
|
To build a new array, the byte pointed to must contain 0.
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - TRUE if this mapping pair only describes a hole, FALSE otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
VCN NextVcn, CurrentVcn;
|
|
LCN CurrentLcn;
|
|
VCN RunVcn;
|
|
LCN RunLcn;
|
|
BOOLEAN Found;
|
|
LONGLONG RunCount;
|
|
PVOID RangePtr;
|
|
ULONG RunIndex;
|
|
BOOLEAN SingleHole = TRUE;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Initialize NextVcn and CurrentLcn as they will be initialized for decode.
|
|
//
|
|
|
|
CurrentLcn = 0;
|
|
NextVcn = RunVcn = LowestVcn;
|
|
|
|
Found = NtfsLookupNtfsMcbEntry( Mcb, RunVcn, &RunLcn, &RunCount, NULL, NULL, &RangePtr, &RunIndex );
|
|
|
|
//
|
|
// Loop through the Mcb to calculate the size of the mapping array.
|
|
//
|
|
|
|
while (TRUE) {
|
|
|
|
LONGLONG ChangeV, ChangeL;
|
|
PCHAR cp;
|
|
ULONG SizeV;
|
|
ULONG SizeL;
|
|
|
|
//
|
|
// See if there is another entry in the Mcb.
|
|
//
|
|
|
|
if (!Found) {
|
|
|
|
//
|
|
// Break out in the normal case
|
|
//
|
|
|
|
if (*HighestVcn == MAXLONGLONG) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Otherwise, describe the "hole" up to and including the
|
|
// Vcn we are stopping on.
|
|
//
|
|
|
|
RunVcn = NextVcn;
|
|
RunLcn = UNUSED_LCN;
|
|
RunCount = *HighestVcn - NextVcn;
|
|
RunIndex = MAXULONG - 1;
|
|
}
|
|
|
|
//
|
|
// Advance the RunIndex for the next call.
|
|
//
|
|
|
|
RunIndex += 1;
|
|
|
|
//
|
|
// Exit loop if we hit the HighestVcn we are looking for.
|
|
//
|
|
|
|
if (RunVcn >= *HighestVcn) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// This run may go beyond the highest we are looking for, if so
|
|
// we need to shrink the count.
|
|
//
|
|
|
|
if ((RunVcn + RunCount) > *HighestVcn) {
|
|
RunCount = *HighestVcn - RunVcn;
|
|
}
|
|
|
|
//
|
|
// NextVcn becomes current Vcn and we calculate the new NextVcn.
|
|
//
|
|
|
|
CurrentVcn = RunVcn;
|
|
NextVcn = RunVcn + RunCount;
|
|
|
|
//
|
|
// Calculate the Vcn change to store.
|
|
//
|
|
|
|
ChangeV = NextVcn - CurrentVcn;
|
|
|
|
//
|
|
// Now calculate the first byte to actually output
|
|
//
|
|
|
|
if (ChangeV < 0) {
|
|
|
|
GetNegativeByte( (PLARGE_INTEGER)&ChangeV, &cp );
|
|
|
|
} else {
|
|
|
|
GetPositiveByte( (PLARGE_INTEGER)&ChangeV, &cp );
|
|
}
|
|
|
|
//
|
|
// Now add in the number of Vcn change bytes.
|
|
//
|
|
|
|
SizeV = (ULONG)(cp - (PCHAR)&ChangeV + 1);
|
|
|
|
//
|
|
// Do not output any Lcn bytes if it is the unused Lcn.
|
|
//
|
|
|
|
SizeL = 0;
|
|
if (RunLcn != UNUSED_LCN) {
|
|
|
|
//
|
|
// Calculate the Lcn change to store.
|
|
//
|
|
|
|
ChangeL = RunLcn - CurrentLcn;
|
|
|
|
//
|
|
// Now calculate the first byte to actually output
|
|
//
|
|
|
|
if (ChangeL < 0) {
|
|
|
|
GetNegativeByte( (PLARGE_INTEGER)&ChangeL, &cp );
|
|
|
|
} else {
|
|
|
|
GetPositiveByte( (PLARGE_INTEGER)&ChangeL, &cp );
|
|
}
|
|
|
|
//
|
|
// Now add in the number of Lcn change bytes.
|
|
//
|
|
|
|
SizeL = (ULONG)(cp - (PCHAR)&ChangeL) + 1;
|
|
|
|
//
|
|
// Now advance CurrentLcn before looping back.
|
|
//
|
|
|
|
CurrentLcn = RunLcn;
|
|
SingleHole = FALSE;
|
|
}
|
|
|
|
//
|
|
// Now we can produce our outputs to the MappingPairs array.
|
|
//
|
|
|
|
*MappingPairs++ = (CHAR)(SizeV + (SizeL * 16));
|
|
|
|
while (SizeV != 0) {
|
|
*MappingPairs++ = (CHAR)(((ULONG)ChangeV) & 0xFF);
|
|
ChangeV = ChangeV >> 8;
|
|
SizeV -= 1;
|
|
}
|
|
|
|
while (SizeL != 0) {
|
|
*MappingPairs++ = (CHAR)(((ULONG)ChangeL) & 0xFF);
|
|
ChangeL = ChangeL >> 8;
|
|
SizeL -= 1;
|
|
}
|
|
|
|
Found = NtfsGetSequentialMcbEntry( Mcb, &RangePtr, RunIndex, &RunVcn, &RunLcn, &RunCount );
|
|
}
|
|
|
|
//
|
|
// Terminate the size with a 0 byte.
|
|
//
|
|
|
|
*MappingPairs = 0;
|
|
|
|
//
|
|
// Also return the actual highest Vcn.
|
|
//
|
|
|
|
*HighestVcn = NextVcn - 1;
|
|
|
|
return SingleHole;
|
|
}
|
|
|
|
VCN
|
|
NtfsGetHighestVcn (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN VCN LowestVcn,
|
|
IN PCHAR EndOfMappingPairs,
|
|
IN PCHAR MappingPairs
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returns the highest Vcn from a mapping pairs array. This
|
|
routine is intended for restart, in order to update the HighestVcn field
|
|
and possibly AllocatedLength in an attribute record after updating the
|
|
MappingPairs array.
|
|
|
|
Arguments:
|
|
|
|
LowestVcn - Lowest Vcn field applying to the mapping pairs array
|
|
|
|
EndOfMappingPairs - Points to the byte RIGHT AFTER the mapping pairs array
|
|
|
|
MappingPairs - Points to the mapping pairs array from which the highest
|
|
Vcn is to be extracted.
|
|
|
|
Return Value:
|
|
|
|
The Highest Vcn represented by the MappingPairs array.
|
|
|
|
--*/
|
|
|
|
{
|
|
VCN CurrentVcn, NextVcn;
|
|
ULONG VcnBytes, LcnBytes;
|
|
LONGLONG Change;
|
|
PCHAR ch = MappingPairs;
|
|
PCHAR VcnStart;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Implement the decompression algorithm, as defined in ntfs.h.
|
|
//
|
|
|
|
NextVcn = LowestVcn;
|
|
ch = MappingPairs;
|
|
|
|
//
|
|
// Loop to process mapping pairs.
|
|
//
|
|
|
|
while ((ch < EndOfMappingPairs) && !IsCharZero(*ch)) {
|
|
|
|
//
|
|
// Set Current Vcn from initial value or last pass through loop.
|
|
//
|
|
|
|
CurrentVcn = NextVcn;
|
|
|
|
//
|
|
// Extract the counts from the two nibbles of this byte.
|
|
//
|
|
|
|
VcnBytes = *ch & 0xF;
|
|
LcnBytes = (*ch++ >> 4) & 0xF; // avoid sign extended case
|
|
|
|
VcnStart = ch;
|
|
|
|
//
|
|
// Calculate next mapping pair location first and check for buffer overrun
|
|
//
|
|
|
|
ch += VcnBytes + LcnBytes;
|
|
|
|
//
|
|
// Extract the Vcn change (use of RtlCopyMemory works for little-Endian)
|
|
// and update NextVcn.
|
|
//
|
|
|
|
Change = 0;
|
|
|
|
if ((ch > EndOfMappingPairs) ||
|
|
VcnBytes > 8 ||
|
|
LcnBytes > 8 ||
|
|
IsCharLtrZero(*(VcnStart + VcnBytes - 1))) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, NULL );
|
|
}
|
|
|
|
RtlCopyMemory( &Change, VcnStart, VcnBytes );
|
|
NextVcn = NextVcn + Change;
|
|
}
|
|
|
|
Change = NextVcn - 1;
|
|
return *(PVCN)&Change;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
NtfsReserveClusters (
|
|
IN PIRP_CONTEXT IrpContext OPTIONAL,
|
|
IN PSCB Scb,
|
|
IN LONGLONG FileOffset,
|
|
IN ULONG ByteCount
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine reserves all clusters that would be required to write
|
|
the full range of compression units covered by the described range
|
|
of Vcns. All clusters in the range are reserved, without regard to
|
|
how many clusters are already reserved in that range. Not paying
|
|
attention to how many clusters are already allocated in that range
|
|
is not only a simplification, but it is also necessary, since we
|
|
sometimes deallocate all existing clusters anyway, and make them
|
|
ineligible for reallocation in the same transaction. Thus in the
|
|
worst case you do always need an additional 16 clusters when a
|
|
compression unit is first modified. Note that although we could
|
|
specifically reserve (double-reserve, in fact) the entire allocation
|
|
size of the stream, when reserving from the volume, we never reserve
|
|
more than AllocationSize + MM_MAXIMUM_DISK_IO_SIZE - size actually
|
|
allocated, since the worst we could ever need to doubly allocate is
|
|
limited by the maximum flush size.
|
|
|
|
For user-mapped streams, we have no way of keeping track of dirty
|
|
pages, so we effectivel always reserve AllocationSize +
|
|
MM_MAXIMUM_DISK_IO_SIZE.
|
|
|
|
This routine is called from FastIo, and therefore has no IrpContext.
|
|
|
|
Arguments:
|
|
|
|
IrpContext - If IrpContext is not specified, then not all data is
|
|
available to determine if the clusters can be reserved,
|
|
and FALSE may be returned unnecessarily. This case
|
|
is intended for the fast I/O path, which will just
|
|
force us to take the long path to write.
|
|
|
|
Scb - Address of a compressed stream for which we are reserving space
|
|
|
|
FileOffset - Starting byte being modified by caller
|
|
|
|
ByteCount - Number of bytes being modified by caller
|
|
|
|
Return Value:
|
|
|
|
FALSE if not all clusters could be reserved
|
|
TRUE if all clusters were reserved
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG FirstBit, LastBit, CurrentLastBit;
|
|
ULONG FirstRange, LastRange;
|
|
PRESERVED_BITMAP_RANGE FreeBitmap, NextBitmap, CurrentBitmap;
|
|
ULONG CompressionShift;
|
|
PVCB Vcb = Scb->Vcb;
|
|
ULONG SizeTemp;
|
|
LONGLONG TempL;
|
|
PVOID NewBitmapBuffer;
|
|
BOOLEAN ReturnValue = FALSE;
|
|
ULONG MappedFile;
|
|
BOOLEAN FlippedBit = FALSE;
|
|
|
|
ASSERT( Scb->Header.NodeTypeCode == NTFS_NTC_SCB_DATA );
|
|
|
|
//
|
|
// Nothing to do if byte count is zero.
|
|
//
|
|
|
|
if (ByteCount == 0) { return TRUE; }
|
|
|
|
//
|
|
// Calculate first and last bits to reserve.
|
|
//
|
|
|
|
CompressionShift = Vcb->ClusterShift + (ULONG)Scb->CompressionUnitShift;
|
|
|
|
FirstBit = ((ULONG) Int64ShraMod32( FileOffset, (CompressionShift) )) & NTFS_BITMAP_RANGE_MASK;
|
|
FirstRange = (ULONG) Int64ShraMod32( FileOffset, CompressionShift + NTFS_BITMAP_RANGE_SHIFT );
|
|
|
|
LastBit = ((ULONG) Int64ShraMod32( FileOffset + ByteCount - 1, CompressionShift )) & NTFS_BITMAP_RANGE_MASK;
|
|
LastRange = (ULONG) Int64ShraMod32( FileOffset + ByteCount - 1,
|
|
CompressionShift + NTFS_BITMAP_RANGE_SHIFT );
|
|
MappedFile = FlagOn( Scb->Header.Flags, FSRTL_FLAG_USER_MAPPED_FILE );
|
|
|
|
//
|
|
// Make sure we started with numbers in range.
|
|
//
|
|
|
|
ASSERT( (((LONGLONG) FirstRange << (CompressionShift + NTFS_BITMAP_RANGE_SHIFT)) +
|
|
((LONGLONG)(FirstBit + 1) << CompressionShift)) > FileOffset );
|
|
|
|
ASSERT( (FirstRange < LastRange) || (LastBit >= FirstBit) );
|
|
ASSERT( FileOffset + ByteCount <= Scb->Header.AllocationSize.QuadPart );
|
|
|
|
//
|
|
// Purge the cache since getting the bitmap may be blocked behind the mft
|
|
// which needs to wait for the cache to purge
|
|
//
|
|
|
|
if (IrpContext) {
|
|
NtfsPurgeFileRecordCache( IrpContext );
|
|
}
|
|
|
|
NtfsAcquireResourceExclusive( IrpContext, Vcb->BitmapScb, TRUE );
|
|
NtfsAcquireReservedClusters( Vcb );
|
|
|
|
//
|
|
// Loop through all of the bitmap ranges for this request.
|
|
//
|
|
|
|
while (TRUE) {
|
|
|
|
CurrentBitmap = NULL;
|
|
|
|
//
|
|
// If we are at the last range then set the current last bit to
|
|
// our final last bit.
|
|
//
|
|
|
|
CurrentLastBit = LastBit;
|
|
if (FirstRange != LastRange) {
|
|
|
|
CurrentLastBit = NTFS_BITMAP_RANGE_MASK;
|
|
}
|
|
|
|
//
|
|
// If there is no bitmap then create the first entry in the list.
|
|
//
|
|
|
|
if (Scb->ScbType.Data.ReservedBitMap == NULL) {
|
|
|
|
//
|
|
// If we are at range zero and the bitcount is not
|
|
// too high then use the basic model.
|
|
//
|
|
|
|
if ((LastRange == 0) && (CurrentLastBit < NTFS_BITMAP_MAX_BASIC_SIZE)) {
|
|
|
|
SizeTemp = NtfsBasicBitmapSize( CurrentLastBit + 1 );
|
|
|
|
//
|
|
// Allocate a buffer for the basic bitmap.
|
|
//
|
|
|
|
CurrentBitmap = NtfsAllocatePoolNoRaise( PagedPool, SizeTemp );
|
|
|
|
//
|
|
// Initialize the data if there is no error.
|
|
//
|
|
|
|
if (CurrentBitmap == NULL) { goto AllocationFailure; }
|
|
|
|
//
|
|
// Initialize the new structure.
|
|
//
|
|
|
|
RtlZeroMemory( CurrentBitmap, SizeTemp );
|
|
RtlInitializeBitMap( &CurrentBitmap->Bitmap,
|
|
&CurrentBitmap->RangeOffset,
|
|
(SizeTemp - FIELD_OFFSET( RESERVED_BITMAP_RANGE, RangeOffset )) * 8);
|
|
|
|
//
|
|
// Allocate a link entry and create the bitmap. We will defer
|
|
// allocating the buffer for the bitmap until later.
|
|
//
|
|
|
|
} else {
|
|
|
|
CurrentBitmap = NtfsAllocatePoolNoRaise( PagedPool, sizeof( RESERVED_BITMAP_RANGE ));
|
|
|
|
if (CurrentBitmap == NULL) { goto AllocationFailure; }
|
|
|
|
RtlZeroMemory( CurrentBitmap, sizeof( RESERVED_BITMAP_RANGE ));
|
|
|
|
InitializeListHead( &CurrentBitmap->Links );
|
|
CurrentBitmap->RangeOffset = FirstRange;
|
|
}
|
|
|
|
//
|
|
// Update our pointer to the reserved bitmap.
|
|
//
|
|
|
|
Scb->ScbType.Data.ReservedBitMap = CurrentBitmap;
|
|
|
|
//
|
|
// Look through the existing ranges to find the range we are interested in.
|
|
// If we currently have the basic single bitmap structure
|
|
// then we can either use it or must convert it.
|
|
//
|
|
|
|
} else if (Scb->ScbType.Data.ReservedBitMap->Links.Flink == NULL) {
|
|
|
|
//
|
|
// If we are accessing range zero then grow the bitmap if necessary.
|
|
//
|
|
|
|
if ((FirstRange == 0) && (CurrentLastBit < NTFS_BITMAP_MAX_BASIC_SIZE)) {
|
|
|
|
//
|
|
// Remember this bitmap.
|
|
//
|
|
|
|
NextBitmap = Scb->ScbType.Data.ReservedBitMap;
|
|
if (CurrentLastBit >= NextBitmap->Bitmap.SizeOfBitMap) {
|
|
|
|
SizeTemp = NtfsBasicBitmapSize( CurrentLastBit + 1 );
|
|
CurrentBitmap = NtfsAllocatePoolNoRaise( PagedPool, SizeTemp );
|
|
|
|
if (CurrentBitmap == NULL) { goto AllocationFailure; }
|
|
|
|
RtlZeroMemory( CurrentBitmap, SizeTemp );
|
|
RtlInitializeBitMap( &CurrentBitmap->Bitmap,
|
|
&CurrentBitmap->RangeOffset,
|
|
(SizeTemp - FIELD_OFFSET( RESERVED_BITMAP_RANGE, RangeOffset )) * 8);
|
|
|
|
CurrentBitmap->BasicDirtyBits = NextBitmap->BasicDirtyBits;
|
|
|
|
RtlCopyMemory( CurrentBitmap->Bitmap.Buffer,
|
|
NextBitmap->Bitmap.Buffer,
|
|
NextBitmap->Bitmap.SizeOfBitMap / 8 );
|
|
|
|
//
|
|
// Now store this into the Scb.
|
|
//
|
|
|
|
Scb->ScbType.Data.ReservedBitMap = CurrentBitmap;
|
|
NtfsFreePool( NextBitmap );
|
|
|
|
} else {
|
|
|
|
CurrentBitmap = NextBitmap;
|
|
}
|
|
|
|
//
|
|
// Otherwise we want to convert to the linked list of bitmap ranges.
|
|
//
|
|
|
|
} else {
|
|
|
|
NextBitmap = NtfsAllocatePoolNoRaise( PagedPool, sizeof( RESERVED_BITMAP_RANGE ));
|
|
|
|
if (NextBitmap == NULL) { goto AllocationFailure; }
|
|
|
|
//
|
|
// Update the new structure.
|
|
//
|
|
|
|
RtlZeroMemory( NextBitmap, sizeof( RESERVED_BITMAP_RANGE ));
|
|
|
|
InitializeListHead( &NextBitmap->Links );
|
|
NextBitmap->DirtyBits = Scb->ScbType.Data.ReservedBitMap->BasicDirtyBits;
|
|
|
|
SizeTemp = Scb->ScbType.Data.ReservedBitMap->Bitmap.SizeOfBitMap / 8;
|
|
|
|
//
|
|
// We will use the existing bitmap as the buffer for the new bitmap.
|
|
// Move the bits to the start of the buffer and then zero
|
|
// the remaining bytes.
|
|
//
|
|
|
|
RtlMoveMemory( Scb->ScbType.Data.ReservedBitMap,
|
|
Scb->ScbType.Data.ReservedBitMap->Bitmap.Buffer,
|
|
SizeTemp );
|
|
|
|
RtlZeroMemory( Add2Ptr( Scb->ScbType.Data.ReservedBitMap, SizeTemp ),
|
|
sizeof( LIST_ENTRY ) + sizeof( RTL_BITMAP ));
|
|
|
|
//
|
|
// Limit ourselves to the maximum range size.
|
|
//
|
|
|
|
SizeTemp = (SizeTemp + sizeof( LIST_ENTRY ) + sizeof( RTL_BITMAP )) * 8;
|
|
if (SizeTemp > NTFS_BITMAP_RANGE_SIZE) {
|
|
|
|
SizeTemp = NTFS_BITMAP_RANGE_SIZE;
|
|
}
|
|
|
|
RtlInitializeBitMap( &NextBitmap->Bitmap,
|
|
(PULONG) Scb->ScbType.Data.ReservedBitMap,
|
|
SizeTemp );
|
|
|
|
//
|
|
// Now point to this new bitmap.
|
|
//
|
|
|
|
Scb->ScbType.Data.ReservedBitMap = NextBitmap;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we didn't find the correct bitmap above then scan the list looking
|
|
// for the entry.
|
|
//
|
|
|
|
if (CurrentBitmap == NULL) {
|
|
|
|
//
|
|
// Walk the list looking for a matching entry.
|
|
//
|
|
|
|
NextBitmap = Scb->ScbType.Data.ReservedBitMap;
|
|
FreeBitmap = NULL;
|
|
|
|
while (TRUE) {
|
|
|
|
//
|
|
// Exit if we found the correct range.
|
|
//
|
|
|
|
if (NextBitmap->RangeOffset == FirstRange) {
|
|
|
|
CurrentBitmap = NextBitmap;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Remember if this is a free range.
|
|
//
|
|
|
|
if (NextBitmap->DirtyBits == 0) {
|
|
|
|
FreeBitmap = NextBitmap;
|
|
}
|
|
|
|
//
|
|
// Exit if we are past our target and have a empty range then break out.
|
|
//
|
|
|
|
if ((NextBitmap->RangeOffset > FirstRange) &&
|
|
(FreeBitmap != NULL)) {
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Move to the next entry.
|
|
//
|
|
|
|
NextBitmap = CONTAINING_RECORD( NextBitmap->Links.Flink,
|
|
RESERVED_BITMAP_RANGE,
|
|
Links );
|
|
|
|
//
|
|
// Break out if we are back at the beginning of the list.
|
|
//
|
|
|
|
if (NextBitmap == Scb->ScbType.Data.ReservedBitMap) {
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we still don't have the bitmap then we can look to see if
|
|
// we found any available free bitmaps.
|
|
//
|
|
|
|
if (CurrentBitmap == NULL) {
|
|
|
|
//
|
|
// We lucked out and found a free bitmap. Let's use it for
|
|
// this new range.
|
|
//
|
|
|
|
if (FreeBitmap != NULL) {
|
|
|
|
CurrentBitmap = FreeBitmap;
|
|
|
|
//
|
|
// Go ahead and remove it from the list. Deal with the cases where
|
|
// we are the first entry and possibly the only entry.
|
|
//
|
|
|
|
if (Scb->ScbType.Data.ReservedBitMap == FreeBitmap) {
|
|
|
|
if (IsListEmpty( &FreeBitmap->Links )) {
|
|
|
|
Scb->ScbType.Data.ReservedBitMap = NULL;
|
|
|
|
} else {
|
|
|
|
Scb->ScbType.Data.ReservedBitMap = CONTAINING_RECORD( FreeBitmap->Links.Flink,
|
|
RESERVED_BITMAP_RANGE,
|
|
Links );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Remove this entry from the list.
|
|
//
|
|
|
|
RemoveEntryList( &FreeBitmap->Links );
|
|
|
|
//
|
|
// We need to allocate a new range and insert it
|
|
// in the correct location.
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// Allocate a new bitmap and remember we need to insert it into the list.
|
|
//
|
|
|
|
CurrentBitmap = NtfsAllocatePoolNoRaise( PagedPool, sizeof( RESERVED_BITMAP_RANGE ));
|
|
|
|
if (CurrentBitmap == NULL) { goto AllocationFailure; }
|
|
|
|
RtlZeroMemory( CurrentBitmap, sizeof( RESERVED_BITMAP_RANGE ));
|
|
}
|
|
|
|
//
|
|
// Set the correct range value in the new bitmap.
|
|
//
|
|
|
|
CurrentBitmap->RangeOffset = FirstRange;
|
|
|
|
//
|
|
// Now walk through and insert the new range into the list. Start by checking if
|
|
// we are the only entry in the list.
|
|
//
|
|
|
|
if (Scb->ScbType.Data.ReservedBitMap == NULL) {
|
|
|
|
InitializeListHead( &CurrentBitmap->Links );
|
|
Scb->ScbType.Data.ReservedBitMap = CurrentBitmap;
|
|
|
|
} else {
|
|
|
|
NextBitmap = Scb->ScbType.Data.ReservedBitMap;
|
|
|
|
//
|
|
// Walk through the list if we are not the new first element.
|
|
//
|
|
|
|
if (CurrentBitmap->RangeOffset > NextBitmap->RangeOffset) {
|
|
|
|
do {
|
|
|
|
//
|
|
// Move to the next entry.
|
|
//
|
|
|
|
NextBitmap = CONTAINING_RECORD( NextBitmap->Links.Flink,
|
|
RESERVED_BITMAP_RANGE,
|
|
Links );
|
|
|
|
ASSERT( NextBitmap->RangeOffset != CurrentBitmap->RangeOffset );
|
|
|
|
//
|
|
// Exit if we are at the last entry.
|
|
//
|
|
|
|
if (NextBitmap == Scb->ScbType.Data.ReservedBitMap ) {
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Continue until we find an entry larger than us.
|
|
//
|
|
|
|
} while (CurrentBitmap->RangeOffset > NextBitmap->RangeOffset);
|
|
|
|
//
|
|
// We are the new first element.
|
|
//
|
|
|
|
} else {
|
|
|
|
Scb->ScbType.Data.ReservedBitMap = CurrentBitmap;
|
|
}
|
|
|
|
//
|
|
// Insert the new entry ahead of the next entry we found.
|
|
//
|
|
|
|
InsertTailList( &NextBitmap->Links, &CurrentBitmap->Links );
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// We have a current bitmap. Make sure it is large enough for the current
|
|
// bit.
|
|
//
|
|
|
|
if (CurrentBitmap->Bitmap.SizeOfBitMap <= CurrentLastBit) {
|
|
|
|
//
|
|
// We should already have adjusted the sizes for the basic bitmap.
|
|
//
|
|
|
|
ASSERT( CurrentBitmap->Links.Flink != NULL );
|
|
|
|
SizeTemp = NtfsBitmapSize( CurrentLastBit + 1 );
|
|
|
|
//
|
|
// Allocate the new buffer and copy the previous bits over.
|
|
//
|
|
|
|
NewBitmapBuffer = NtfsAllocatePoolNoRaise( PagedPool, SizeTemp );
|
|
|
|
if (NewBitmapBuffer == NULL) { goto AllocationFailure; }
|
|
|
|
if (CurrentBitmap->Bitmap.SizeOfBitMap != 0) {
|
|
|
|
RtlCopyMemory( NewBitmapBuffer,
|
|
CurrentBitmap->Bitmap.Buffer,
|
|
CurrentBitmap->Bitmap.SizeOfBitMap / 8 );
|
|
|
|
NtfsFreePool( CurrentBitmap->Bitmap.Buffer );
|
|
}
|
|
|
|
RtlZeroMemory( Add2Ptr( NewBitmapBuffer, CurrentBitmap->Bitmap.SizeOfBitMap / 8 ),
|
|
SizeTemp - (CurrentBitmap->Bitmap.SizeOfBitMap / 8) );
|
|
|
|
//
|
|
// Limit the bitmap size by the max range size.
|
|
//
|
|
|
|
SizeTemp *= 8;
|
|
|
|
if (SizeTemp > NTFS_BITMAP_RANGE_SIZE) {
|
|
|
|
SizeTemp = NTFS_BITMAP_RANGE_SIZE;
|
|
}
|
|
|
|
RtlInitializeBitMap( &CurrentBitmap->Bitmap,
|
|
NewBitmapBuffer,
|
|
SizeTemp );
|
|
}
|
|
|
|
//
|
|
// Figure out the worst case reservation required for this Scb, in bytes.
|
|
//
|
|
|
|
TempL = NtfsCalculateNeededReservedSpace( Scb );
|
|
|
|
//
|
|
// Now loop to reserve the space, a compression unit at a time.
|
|
// We use the security fast mutex as a convenient end resource.
|
|
//
|
|
|
|
do {
|
|
|
|
//
|
|
// If this compression unit is not already reserved do it now.
|
|
//
|
|
|
|
FlippedBit = FALSE;
|
|
if (!RtlCheckBit( &CurrentBitmap->Bitmap, FirstBit )) {
|
|
|
|
//
|
|
// If there is not sufficient space on the volume, then
|
|
// we must see if this Scb is totally reserved anyway.
|
|
//
|
|
|
|
if (((Vcb->TotalReserved + (Int64ShraMod32( Vcb->TotalReserved, 8 )) +
|
|
(1 << Scb->CompressionUnitShift)) >= Vcb->FreeClusters) &&
|
|
(Scb->ScbType.Data.TotalReserved < TempL) &&
|
|
#ifdef BRIANDBG
|
|
!NtfsIgnoreReserved &&
|
|
#endif
|
|
(FlagOn(Scb->ScbState, SCB_STATE_WRITE_ACCESS_SEEN))) {
|
|
|
|
NtfsReleaseReservedClusters( Vcb );
|
|
NtfsReleaseResource( IrpContext, Vcb->BitmapScb );
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Reserve this compression unit and increase the number of dirty
|
|
// bits for this range.
|
|
//
|
|
|
|
SetFlag( CurrentBitmap->Bitmap.Buffer[FirstBit / 32], 1 << (FirstBit % 32) );
|
|
if (CurrentBitmap->Links.Flink != NULL) {
|
|
|
|
CurrentBitmap->DirtyBits += 1;
|
|
|
|
} else {
|
|
|
|
CurrentBitmap->BasicDirtyBits += 1;
|
|
}
|
|
|
|
FlippedBit = TRUE;
|
|
}
|
|
|
|
if (FlippedBit || (MappedFile && (Scb->ScbType.Data.TotalReserved <= TempL))) {
|
|
|
|
//
|
|
// Increased TotalReserved bytes in the Scb.
|
|
//
|
|
|
|
Scb->ScbType.Data.TotalReserved += Scb->CompressionUnit;
|
|
ASSERT( Scb->CompressionUnit != 0 );
|
|
ASSERT( (Scb->CompressionUnitShift != 0) ||
|
|
(Vcb->BytesPerCluster == 0x10000) );
|
|
|
|
//
|
|
// Increase total reserved clusters in the Vcb, if the user has
|
|
// write access. (Otherwise this must be a call from a read
|
|
// to a usermapped section.)
|
|
//
|
|
|
|
if (FlagOn(Scb->ScbState, SCB_STATE_WRITE_ACCESS_SEEN)) {
|
|
Vcb->TotalReserved += 1 << Scb->CompressionUnitShift;
|
|
}
|
|
|
|
TempL -= Scb->CompressionUnit;
|
|
TempL += Int64ShraMod32( Scb->CompressionUnit, 8 );
|
|
}
|
|
|
|
FirstBit += 1;
|
|
} while (FirstBit <= CurrentLastBit);
|
|
|
|
//
|
|
// Exit if we have reached the last range.
|
|
//
|
|
|
|
if (FirstRange == LastRange) { break; }
|
|
|
|
FirstRange += 1;
|
|
FirstBit = 0;
|
|
}
|
|
|
|
ReturnValue = TRUE;
|
|
|
|
AllocationFailure:
|
|
|
|
NtfsReleaseReservedClusters( Vcb );
|
|
NtfsReleaseResource( IrpContext, Vcb->BitmapScb );
|
|
|
|
//
|
|
// If we have an Irp Context then we can raise insufficient resources. Otherwise
|
|
// return FALSE.
|
|
//
|
|
|
|
if (!ReturnValue && ARGUMENT_PRESENT( IrpContext )) {
|
|
NtfsRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES, NULL, NULL );
|
|
}
|
|
|
|
return ReturnValue;
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
NtfsFreeReservedClusters (
|
|
IN PSCB Scb,
|
|
IN LONGLONG FileOffset,
|
|
IN ULONG ByteCount
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine frees any previously reserved clusters in the specified range.
|
|
|
|
Arguments:
|
|
|
|
Scb - Address of a compressed stream for which we are freeing reserved space
|
|
|
|
FileOffset - Starting byte being freed
|
|
|
|
ByteCount - Number of bytes being freed by caller, or 0 if to end of file
|
|
|
|
Return Value:
|
|
|
|
None (all errors simply raise)
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG FirstBit, LastBit, CurrentLastBit;
|
|
ULONG FirstRange, LastRange;
|
|
ULONG CompressionShift;
|
|
PRESERVED_BITMAP_RANGE CurrentBitmap = NULL;
|
|
PUSHORT DirtyBits;
|
|
PRESERVED_BITMAP_RANGE NextBitmap;
|
|
PVCB Vcb = Scb->Vcb;
|
|
LONGLONG TempL;
|
|
ULONG MappedFile;
|
|
|
|
NtfsAcquireReservedClusters( Vcb );
|
|
|
|
MappedFile = FlagOn( Scb->Header.Flags, FSRTL_FLAG_USER_MAPPED_FILE );
|
|
|
|
//
|
|
// If there is no bitmap for non mapped files or the reserved count is zero we
|
|
// can get out immediately.
|
|
//
|
|
|
|
if ((Scb->Header.NodeTypeCode != NTFS_NTC_SCB_DATA) ||
|
|
(NULL == Scb->ScbType.Data.ReservedBitMap) ||
|
|
(Scb->ScbType.Data.TotalReserved == 0)) {
|
|
NtfsReleaseReservedClusters( Vcb );
|
|
return;
|
|
}
|
|
|
|
TempL = NtfsCalculateNeededReservedSpace( Scb );
|
|
|
|
if (MappedFile) {
|
|
|
|
//
|
|
// Mapped files can only shrink reserved down to upper limit
|
|
//
|
|
|
|
if (Scb->ScbType.Data.TotalReserved <= TempL + Scb->CompressionUnit) {
|
|
NtfsReleaseReservedClusters( Vcb );
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Calculate first bit to free, and initialize LastBit
|
|
//
|
|
|
|
CompressionShift = Vcb->ClusterShift + (ULONG)Scb->CompressionUnitShift;
|
|
FirstBit = ((ULONG) Int64ShraMod32( FileOffset, CompressionShift )) & NTFS_BITMAP_RANGE_MASK;
|
|
FirstRange = (ULONG) Int64ShraMod32( FileOffset, CompressionShift + NTFS_BITMAP_RANGE_SHIFT );
|
|
LastRange = MAXULONG;
|
|
LastBit = MAXULONG;
|
|
|
|
//
|
|
// If ByteCount was specified, then calculate LastBit.
|
|
//
|
|
|
|
if (ByteCount != 0) {
|
|
LastBit = ((ULONG) Int64ShraMod32( FileOffset + ByteCount - 1, CompressionShift )) & NTFS_BITMAP_RANGE_MASK;
|
|
LastRange = (ULONG) Int64ShraMod32( FileOffset + ByteCount - 1,
|
|
CompressionShift + NTFS_BITMAP_RANGE_SHIFT );
|
|
}
|
|
|
|
//
|
|
// Make sure we started with numbers in range.
|
|
//
|
|
|
|
ASSERT( (((LONGLONG) FirstRange << (CompressionShift + NTFS_BITMAP_RANGE_SHIFT)) +
|
|
((LONGLONG)(FirstBit + 1) << CompressionShift)) > FileOffset );
|
|
|
|
ASSERT( (FirstRange < LastRange) || (LastBit >= FirstBit) );
|
|
|
|
//
|
|
// Look for the first range which lies within our input range.
|
|
//
|
|
|
|
NextBitmap = Scb->ScbType.Data.ReservedBitMap;
|
|
|
|
//
|
|
// If this is a basic bitmap range then our input should be range zero.
|
|
//
|
|
|
|
if (NextBitmap->Links.Flink == NULL) {
|
|
|
|
if (FirstRange == 0) {
|
|
|
|
CurrentBitmap = NextBitmap;
|
|
DirtyBits = &CurrentBitmap->BasicDirtyBits;
|
|
}
|
|
|
|
//
|
|
// Otherwise loop through the links.
|
|
//
|
|
|
|
} else {
|
|
|
|
do {
|
|
|
|
//
|
|
// Check if this bitmap is within the range being checked.
|
|
//
|
|
|
|
if (NextBitmap->RangeOffset >= FirstRange) {
|
|
|
|
if (NextBitmap->RangeOffset <= LastRange) {
|
|
|
|
CurrentBitmap = NextBitmap;
|
|
DirtyBits = &CurrentBitmap->DirtyBits;
|
|
|
|
if (NextBitmap->RangeOffset != FirstRange) {
|
|
|
|
FirstBit = 0;
|
|
FirstRange = NextBitmap->RangeOffset;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
NextBitmap = CONTAINING_RECORD( NextBitmap->Links.Flink,
|
|
RESERVED_BITMAP_RANGE,
|
|
Links );
|
|
|
|
} while (NextBitmap != Scb->ScbType.Data.ReservedBitMap);
|
|
}
|
|
|
|
//
|
|
// If we didn't find a match we can exit.
|
|
//
|
|
|
|
if (CurrentBitmap == NULL) {
|
|
|
|
NtfsReleaseReservedClusters( Vcb );
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Loop for each bitmap in the input range.
|
|
//
|
|
|
|
while (TRUE) {
|
|
|
|
//
|
|
// If we are at the last range then use the input last bit.
|
|
//
|
|
|
|
CurrentLastBit = LastBit;
|
|
if (FirstRange != LastRange) {
|
|
|
|
CurrentLastBit = NTFS_BITMAP_RANGE_MASK;
|
|
}
|
|
|
|
//
|
|
// Under no circumstances should we go off the end!
|
|
//
|
|
|
|
if (CurrentLastBit >= CurrentBitmap->Bitmap.SizeOfBitMap) {
|
|
CurrentLastBit = CurrentBitmap->Bitmap.SizeOfBitMap - 1;
|
|
}
|
|
|
|
//
|
|
// Now loop to free the space, a compression unit at a time.
|
|
// We use the security fast mutex as a convenient end resource.
|
|
//
|
|
|
|
if (MappedFile || (*DirtyBits != 0)) {
|
|
|
|
while (FirstBit <= CurrentLastBit) {
|
|
|
|
//
|
|
// If this compression unit is reserved, then free it.
|
|
//
|
|
|
|
if (MappedFile || RtlCheckBit( &CurrentBitmap->Bitmap, FirstBit )) {
|
|
|
|
//
|
|
// Free this compression unit and decrement the dirty bits
|
|
// for this bitmap if required.
|
|
//
|
|
|
|
if (!MappedFile) {
|
|
ClearFlag( CurrentBitmap->Bitmap.Buffer[FirstBit / 32], 1 << (FirstBit % 32) );
|
|
}
|
|
|
|
//
|
|
// Decrease TotalReserved bytes in the Scb.
|
|
//
|
|
|
|
ASSERT( Scb->ScbType.Data.TotalReserved >= Scb->CompressionUnit );
|
|
Scb->ScbType.Data.TotalReserved -= Scb->CompressionUnit;
|
|
ASSERT( Scb->CompressionUnit != 0 );
|
|
|
|
//
|
|
// Decrease total reserved clusters in the Vcb, if we are counting
|
|
// against the Vcb.
|
|
//
|
|
|
|
if (FlagOn(Scb->ScbState, SCB_STATE_WRITE_ACCESS_SEEN)) {
|
|
ASSERT(Vcb->TotalReserved >= (1 << Scb->CompressionUnitShift));
|
|
Vcb->TotalReserved -= 1 << Scb->CompressionUnitShift;
|
|
}
|
|
|
|
if (MappedFile) {
|
|
|
|
TempL += Scb->CompressionUnit;
|
|
TempL -= Int64ShraMod32( Scb->CompressionUnit, 8 );
|
|
|
|
if (Scb->ScbType.Data.TotalReserved <= TempL) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Go ahead and break out if the count of dirty bits goes to zero.
|
|
//
|
|
|
|
ASSERT( MappedFile || *DirtyBits != 0 );
|
|
|
|
if (!MappedFile) {
|
|
*DirtyBits -= 1;
|
|
|
|
if (*DirtyBits == 0) { break; }
|
|
}
|
|
}
|
|
FirstBit += 1;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Break out if we are last the last range or there is no next range
|
|
// or we're mapped and not at the limit
|
|
//
|
|
|
|
if ((NULL == CurrentBitmap->Links.Flink) ||
|
|
(FirstRange == LastRange) ||
|
|
(MappedFile &&
|
|
(Scb->ScbType.Data.TotalReserved <= TempL))) {
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Move to the next range.
|
|
//
|
|
|
|
CurrentBitmap = CONTAINING_RECORD( CurrentBitmap->Links.Flink,
|
|
RESERVED_BITMAP_RANGE,
|
|
Links );
|
|
|
|
//
|
|
// Exit if we did not find a new range within the user specified range.
|
|
//
|
|
|
|
if ((CurrentBitmap->RangeOffset > LastRange) ||
|
|
(CurrentBitmap->RangeOffset <= FirstRange)) {
|
|
|
|
break;
|
|
}
|
|
|
|
FirstRange = CurrentBitmap->RangeOffset;
|
|
DirtyBits = &CurrentBitmap->DirtyBits;
|
|
|
|
FirstBit = 0;
|
|
}
|
|
|
|
NtfsReleaseReservedClusters( Vcb );
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
NtfsCheckForReservedClusters (
|
|
IN PSCB Scb,
|
|
IN LONGLONG StartingVcn,
|
|
IN OUT PLONGLONG ClusterCount
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to determine if a range of a stream has reserved
|
|
clusters. It is used when the user queries for the allocated ranges. We
|
|
want to tell the user that a range which has reserved clusters is allocated.
|
|
Otherwise he may skip over this range when reading from the file for a
|
|
backup or copy operation.
|
|
|
|
Arguments:
|
|
|
|
Scb - Address of the Scb for a sparsestream for which we are checking for
|
|
reservation. Our caller should only call us for this type of stream.
|
|
|
|
StartingVcn - Starting offset of a potential zeroed range. This is guaranteed
|
|
to begin on a sparse range boundary.
|
|
|
|
ClusterCount - On input this is the length of the range to check. On output it
|
|
is the length of the deallocated range beginning at this offset. The length
|
|
will be zero if the first compression unit is reserved.
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - TRUE if a reserved unit is found in the range, FALSE otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG CompressionShift;
|
|
ULONG FirstBit, LastBit, CurrentLastBit, CurrentBits;
|
|
ULONG FirstRange, LastRange;
|
|
ULONG RemainingBits;
|
|
ULONG FoundBit;
|
|
PRESERVED_BITMAP_RANGE CurrentBitmap = NULL;
|
|
PRESERVED_BITMAP_RANGE NextBitmap;
|
|
PUSHORT DirtyBits;
|
|
PVCB Vcb = Scb->Vcb;
|
|
LONGLONG FoundBits = 0;
|
|
|
|
BOOLEAN FoundReserved = FALSE;
|
|
|
|
RTL_BITMAP LocalBitmap;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Check that the stream is really sparse and that the file offset is on a sparse
|
|
// boundary.
|
|
//
|
|
|
|
ASSERT( FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK | ATTRIBUTE_FLAG_SPARSE ));
|
|
ASSERT( (((ULONG) LlBytesFromClusters( Vcb, StartingVcn )) & (Scb->CompressionUnit - 1)) == 0 );
|
|
|
|
//
|
|
// If there is no bitmap, we can get out.
|
|
//
|
|
|
|
if ((Scb->ScbType.Data.ReservedBitMap == NULL) ||
|
|
(Scb->ScbType.Data.TotalReserved == 0)) {
|
|
return FoundReserved;
|
|
}
|
|
|
|
//
|
|
// Compute the range of bits that need to be checked. Trim this by the range of
|
|
// the bitmap.
|
|
//
|
|
|
|
CompressionShift = (ULONG) Scb->CompressionUnitShift;
|
|
FirstBit = ((ULONG) Int64ShraMod32( StartingVcn, CompressionShift )) & NTFS_BITMAP_RANGE_MASK;
|
|
FirstRange = (ULONG) Int64ShraMod32( StartingVcn, CompressionShift + NTFS_BITMAP_RANGE_SHIFT );
|
|
|
|
LastBit = ((ULONG) Int64ShraMod32( StartingVcn + *ClusterCount - 1, CompressionShift )) & NTFS_BITMAP_RANGE_MASK;
|
|
LastRange = (ULONG) Int64ShraMod32( StartingVcn + *ClusterCount - 1,
|
|
CompressionShift + NTFS_BITMAP_RANGE_SHIFT );
|
|
|
|
NtfsAcquireReservedClusters( Vcb );
|
|
|
|
//
|
|
// Look for the first range which lies within our input range.
|
|
//
|
|
|
|
NextBitmap = Scb->ScbType.Data.ReservedBitMap;
|
|
|
|
//
|
|
// If this is a basic bitmap range then our input should be range zero.
|
|
//
|
|
|
|
if (NextBitmap->Links.Flink == NULL) {
|
|
|
|
if (FirstRange == 0) {
|
|
|
|
CurrentBitmap = NextBitmap;
|
|
DirtyBits = &CurrentBitmap->BasicDirtyBits;
|
|
}
|
|
|
|
//
|
|
// Otherwise loop through the links.
|
|
//
|
|
|
|
} else {
|
|
|
|
do {
|
|
|
|
//
|
|
// Check if this bitmap is within the range being checked.
|
|
//
|
|
|
|
if (NextBitmap->RangeOffset >= FirstRange) {
|
|
|
|
if (NextBitmap->RangeOffset <= LastRange) {
|
|
|
|
CurrentBitmap = NextBitmap;
|
|
DirtyBits = &CurrentBitmap->DirtyBits;
|
|
|
|
//
|
|
// If we are skipping any ranges then remember how
|
|
// many bits are implicitly clear.
|
|
//
|
|
|
|
if (NextBitmap->RangeOffset != FirstRange) {
|
|
|
|
FoundBits = (NextBitmap->RangeOffset - FirstRange) * NTFS_BITMAP_RANGE_SIZE;
|
|
FoundBits -= FirstBit;
|
|
FirstBit = 0;
|
|
FirstRange = NextBitmap->RangeOffset;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
NextBitmap = CONTAINING_RECORD( NextBitmap->Links.Flink,
|
|
RESERVED_BITMAP_RANGE,
|
|
Links );
|
|
|
|
} while (NextBitmap != Scb->ScbType.Data.ReservedBitMap);
|
|
}
|
|
|
|
//
|
|
// If we didn't find a match we can exit.
|
|
//
|
|
|
|
if (CurrentBitmap == NULL) {
|
|
|
|
NtfsReleaseReservedClusters( Vcb );
|
|
return FoundReserved;
|
|
}
|
|
|
|
//
|
|
// Loop for each bitmap in the input range.
|
|
//
|
|
|
|
while (TRUE) {
|
|
|
|
//
|
|
// If we are at the last range then use the input last bit.
|
|
//
|
|
|
|
CurrentLastBit = LastBit;
|
|
if (FirstRange != LastRange) {
|
|
|
|
CurrentLastBit = NTFS_BITMAP_RANGE_MASK;
|
|
}
|
|
|
|
CurrentBits = CurrentLastBit - FirstBit + 1;
|
|
|
|
//
|
|
// Skip this range if there are no dirty bits.
|
|
//
|
|
|
|
if (*DirtyBits != 0) {
|
|
|
|
//
|
|
// Under no circumstances should we go off the end!
|
|
//
|
|
|
|
if (CurrentLastBit >= CurrentBitmap->Bitmap.SizeOfBitMap) {
|
|
CurrentLastBit = CurrentBitmap->Bitmap.SizeOfBitMap - 1;
|
|
}
|
|
|
|
//
|
|
// Check on the number of bits remaining in this bitmap.
|
|
//
|
|
|
|
if (FirstBit <= CurrentLastBit) {
|
|
|
|
RemainingBits = CurrentLastBit - FirstBit + 1;
|
|
ASSERT( RemainingBits != 0 );
|
|
|
|
//
|
|
// If the starting bit is set then there is nothing else to do.
|
|
// Otherwise find the length of the clear run.
|
|
//
|
|
|
|
if (RtlCheckBit( &CurrentBitmap->Bitmap, FirstBit )) {
|
|
|
|
FoundBit = FirstBit;
|
|
|
|
} else {
|
|
|
|
RtlInitializeBitMap( &LocalBitmap,
|
|
CurrentBitmap->Bitmap.Buffer,
|
|
CurrentLastBit + 1 );
|
|
|
|
FoundBit = RtlFindNextForwardRunClear( &LocalBitmap,
|
|
FirstBit,
|
|
&FirstBit );
|
|
|
|
if (FoundBit == RemainingBits) {
|
|
|
|
FoundBit = 0xffffffff;
|
|
|
|
} else {
|
|
|
|
FoundBit += FirstBit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If a bit was found then we need to compute where it lies in the
|
|
// requested range.
|
|
//
|
|
|
|
if (FoundBit != 0xffffffff) {
|
|
|
|
//
|
|
// Include any clear bits from this range in our total.
|
|
//
|
|
|
|
FoundBits += (FoundBit - FirstBit);
|
|
|
|
//
|
|
// Convert from compression units to clusters and trim to a compression
|
|
// unit boundary.
|
|
//
|
|
|
|
*ClusterCount = BlockAlignTruncate( Int64ShllMod32( FoundBits, CompressionShift ), (LONG)Vcb->SparseFileClusters );
|
|
|
|
//
|
|
// Now adjust the output cluster range value.
|
|
//
|
|
|
|
ASSERT( LlBytesFromClusters( Vcb, StartingVcn + *ClusterCount ) <= (ULONGLONG) Scb->Header.FileSize.QuadPart );
|
|
FoundReserved = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Break out if we are last the last range or there is no next range.
|
|
//
|
|
|
|
if ((CurrentBitmap->Links.Flink == NULL) ||
|
|
(FirstRange == LastRange)) {
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Move to the next range.
|
|
//
|
|
|
|
CurrentBitmap = CONTAINING_RECORD( CurrentBitmap->Links.Flink,
|
|
RESERVED_BITMAP_RANGE,
|
|
Links );
|
|
|
|
//
|
|
// Exit if we did not find a new range within the user specified range.
|
|
//
|
|
|
|
if ((CurrentBitmap->RangeOffset <= FirstRange) ||
|
|
(CurrentBitmap->RangeOffset > LastRange)) {
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Add in the bits for any ranges we skipped.
|
|
//
|
|
|
|
FoundBits += (CurrentBitmap->RangeOffset - FirstRange - 1) * NTFS_BITMAP_RANGE_SIZE;
|
|
FirstRange = CurrentBitmap->RangeOffset;
|
|
FirstBit = 0;
|
|
|
|
//
|
|
// Include the bits from the most recent range in our count of found bits.
|
|
//
|
|
|
|
FoundBits += CurrentBits;
|
|
|
|
//
|
|
// Remember where the dirty bits field is.
|
|
//
|
|
|
|
DirtyBits = &CurrentBitmap->DirtyBits;
|
|
}
|
|
|
|
NtfsReleaseReservedClusters( Vcb );
|
|
return FoundReserved;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsDeleteReservedBitmap (
|
|
IN PSCB Scb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to free all of the components of the reserved bitmap. We
|
|
free any remaining reserved clusters and deallocate all of the pool associated with
|
|
the bitmap.
|
|
|
|
Arguments:
|
|
|
|
Scb - Scb for the stream.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PRESERVED_BITMAP_RANGE FirstRange;
|
|
PRESERVED_BITMAP_RANGE CurrentRange;
|
|
|
|
PAGED_CODE();
|
|
|
|
FirstRange = Scb->ScbType.Data.ReservedBitMap;
|
|
|
|
ASSERT( FirstRange != NULL );
|
|
|
|
//
|
|
// Free any reserved clusters still present.
|
|
//
|
|
|
|
if ((Scb->ScbType.Data.TotalReserved != 0) && FlagOn( Scb->ScbState, SCB_STATE_WRITE_ACCESS_SEEN )) {
|
|
|
|
LONGLONG ClusterCount;
|
|
|
|
ClusterCount = LlClustersFromBytesTruncate( Scb->Vcb, Scb->ScbType.Data.TotalReserved );
|
|
|
|
//
|
|
// Use the security fast mutex as a convenient end resource.
|
|
//
|
|
|
|
NtfsAcquireReservedClusters( Scb->Vcb );
|
|
|
|
ASSERT(Scb->Vcb->TotalReserved >= ClusterCount);
|
|
Scb->Vcb->TotalReserved -= ClusterCount;
|
|
|
|
NtfsReleaseReservedClusters( Scb->Vcb );
|
|
}
|
|
|
|
Scb->ScbType.Data.TotalReserved = 0;
|
|
|
|
//
|
|
// The typical case is where the first range is the only range
|
|
// for a small file.
|
|
//
|
|
|
|
if (FirstRange->Links.Flink == NULL) {
|
|
|
|
NtfsFreePool( FirstRange );
|
|
|
|
//
|
|
// Otherwise we need to walk through the list of ranges.
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// Loop through the reserved bitmaps until we hit the first.
|
|
//
|
|
|
|
do {
|
|
|
|
CurrentRange = CONTAINING_RECORD( FirstRange->Links.Flink,
|
|
RESERVED_BITMAP_RANGE,
|
|
Links );
|
|
|
|
RemoveEntryList( &CurrentRange->Links );
|
|
|
|
if (CurrentRange->Bitmap.Buffer != NULL) {
|
|
|
|
NtfsFreePool( CurrentRange->Bitmap.Buffer );
|
|
}
|
|
|
|
NtfsFreePool( CurrentRange );
|
|
|
|
} while (CurrentRange != FirstRange);
|
|
}
|
|
|
|
//
|
|
// Show that the bitmap is gone.
|
|
//
|
|
|
|
Scb->ScbType.Data.ReservedBitMap = NULL;
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
#if (defined(NTFS_RWCMP_TRACE) || defined(SYSCACHE) || defined(NTFS_RWC_DEBUG) || defined(SYSCACHE_DEBUG))
|
|
|
|
BOOLEAN
|
|
FsRtlIsSyscacheFile (
|
|
IN PFILE_OBJECT FileObject
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returns to the caller whether or not the specified
|
|
file object is a file to be logged. Originally this was only used for
|
|
the syscache stress test (thus the name). The function understands minimal
|
|
wildcard patterns. To change which filename is logged against change the
|
|
variable MakName.
|
|
|
|
Arguments:
|
|
|
|
FileObject - supplies the FileObject to be tested (it must not be
|
|
cleaned up yet).
|
|
|
|
Return Value:
|
|
|
|
FALSE - if the file is not a Syscache file.
|
|
TRUE - if the file is a Syscache file.
|
|
|
|
--*/
|
|
{
|
|
ULONG iM = 0;
|
|
ULONG iF;
|
|
PWSTR MakName = L"cac*.tmp";
|
|
ULONG LenMakName = wcslen(MakName);
|
|
|
|
if (FileObject && NtfsSyscacheTrackingActive) {
|
|
iF = FileObject->FileName.Length / 2;
|
|
while ((iF != 0) && (FileObject->FileName.Buffer[iF - 1] != '\\')) {
|
|
iF--;
|
|
}
|
|
|
|
|
|
while (TRUE) {
|
|
|
|
//
|
|
// If we are past the end of the file object then we are done in any case.
|
|
//
|
|
|
|
if ((LONG)iF == FileObject->FileName.Length / 2) {
|
|
|
|
//
|
|
// Both strings exausted then we are done.
|
|
//
|
|
|
|
if (iM == LenMakName) {
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
break;
|
|
|
|
//
|
|
// Break if more input but the match string is exhausted.
|
|
//
|
|
|
|
} else if (iM == LenMakName) {
|
|
|
|
break;
|
|
|
|
//
|
|
// If we are at the '*' then match everything but skip to next character
|
|
// on a '.'
|
|
//
|
|
|
|
} else if (MakName[iM] == '*') {
|
|
|
|
//
|
|
// if we're at the last character move past wildchar in template
|
|
//
|
|
|
|
|
|
if ((FileObject->FileName.Buffer[iF] == L'.') && (LenMakName != iM + 1)) {
|
|
|
|
//
|
|
// Move past * and . in NakName
|
|
//
|
|
|
|
ASSERT(MakName[iM + 1] == L'.');
|
|
|
|
iM++; iM++;
|
|
|
|
} else if (((LONG)iF + 1 == FileObject->FileName.Length / 2)) {
|
|
iM++;
|
|
}
|
|
iF++;
|
|
|
|
} else if (MakName[iM] == (WCHAR)(FileObject->FileName.Buffer[iF] )) {
|
|
iM++; iF++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
VOID
|
|
FsRtlVerifySyscacheData (
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PVOID Buffer,
|
|
IN ULONG Length,
|
|
IN ULONG Offset
|
|
)
|
|
|
|
/*
|
|
|
|
Routine Description:
|
|
|
|
This routine scans a buffer to see if it is valid data for a syscache
|
|
file, and stops if it sees bad data.
|
|
|
|
HINT TO CALLERS: Make sure (Offset + Length) <= FileSize!
|
|
|
|
Arguments:
|
|
|
|
Buffer - Pointer to the buffer to be checked
|
|
|
|
Length - Length of the buffer to be checked in bytes
|
|
|
|
Offset - File offset at which this data starts (syscache files are currently
|
|
limited to 24 bits of file offset).
|
|
|
|
Return Value:
|
|
|
|
None (stops on error)
|
|
|
|
--*/
|
|
|
|
{
|
|
PULONG BufferEnd;
|
|
|
|
BufferEnd = (PULONG)((PCHAR)Buffer + (Length & ~3));
|
|
|
|
while ((PULONG)Buffer < BufferEnd) {
|
|
|
|
if ((*(PULONG)Buffer != 0) && (((*(PULONG)Buffer & 0xFFFFFF) ^ Offset) != 0xFFFFFF) &&
|
|
((Offset & 0x1FF) != 0)) {
|
|
|
|
DbgPrint("Bad Data, FileObject = %08lx, Offset = %08lx, Buffer = %08lx\n",
|
|
FileObject, Offset, (PULONG)Buffer );
|
|
DbgBreakPoint();
|
|
}
|
|
Offset += 4;
|
|
Buffer = (PVOID)((PULONG)Buffer + 1);
|
|
}
|
|
}
|
|
|
|
|
|
#endif
|