/*++ 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) // // Internal support routines // VOID NtfsDeleteAllocationInternal ( IN PIRP_CONTEXT IrpContext, IN PFILE_OBJECT FileObject OPTIONAL, 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, NtfsAllocateAttribute) #pragma alloc_text(PAGE, NtfsBuildMappingPairs) #pragma alloc_text(PAGE, NtfsDeleteAllocation) #pragma alloc_text(PAGE, NtfsDeleteAllocationInternal) #pragma alloc_text(PAGE, NtfsGetHighestVcn) #pragma alloc_text(PAGE, NtfsGetSizeForMappingPairs) #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); // // 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->Vcb; BOOLEAN McbMutexAcquired = FALSE; 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) ); // // 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 == Scb->Vcb->MftScb && FlagOn( Scb->Vcb->Vpb->Flags, VPB_MOUNTED )) || FlagOn( Scb->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. // ExAcquireResourceShared( Scb->Header.Resource, TRUE ); try { // // Lookup the attribute record for this Scb. // NtfsLookupAttributeForScb( IrpContext, Scb, &Vcn, &Context ); // // 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. // do { VCN CurrentVcn; LCN CurrentLcn; LONGLONG Change; PCHAR ch; ULONG VcnBytes; ULONG LcnBytes; Attribute = NtfsFoundAttribute( &Context ); ASSERT( !NtfsIsAttributeResident(Attribute) ); // // 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; // // 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; // // If we are adding a cluster which extends into the upper // 32 bits then the disk is corrupt. // ASSERT( ((PLARGE_INTEGER)&HighestCandidate)->HighPart == 0 ); if (((PLARGE_INTEGER)&HighestCandidate)->HighPart != 0) { NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb ); } // // 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 ); } } while (( Vcn >= HighestCandidate ) && NtfsLookupNextAttributeForScb( IrpContext, Scb, &Context )); // // 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( Scb->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; } *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 ); } ExReleaseResource( Scb->Header.Resource ); // // Cleanup the attribute context on the way out. // NtfsCleanupAttributeContext( &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 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; 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)) { ((ULONG)Size) |= Scb->CompressionUnit - 1; } // // 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 ); if (UninitializeOnClose && NtfsPerformQuotaOperation( Fcb ) && !FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE ) && FlagOn( Scb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA )) { ASSERT( NtfsIsTypeCodeSubjectToQuota( AttributeTypeCode )); // // This is a new stream with zero size indicate // the quota is based on allocation size. // SetFlag( Scb->ScbState, SCB_STATE_QUOTA_ENLARGED ); } 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, &ClusterCount ); #ifdef _CAIRO_ // // Make sure the owner is allowed to have these // clusters. // if (FlagOn( Scb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA )) { LONGLONG Delta = LlBytesFromClusters(Fcb->Vcb, ClusterCount); ASSERT( NtfsIsTypeCodeSubjectToQuota( Scb->AttributeTypeCode )); ASSERT( !NtfsPerformQuotaOperation( Fcb ) || FlagOn( Scb->ScbState, SCB_STATE_QUOTA_ENLARGED) || FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE )); NtfsConditionallyUpdateQuota( IrpContext, Fcb, &Delta, LogIt, TRUE ); } #endif // _CAIRO_ } // // 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 ); // // 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( &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 BOOLEAN AskForMore ) /*++ 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. Return Value: None. --*/ { LONGLONG DesiredClusterCount; ATTRIBUTE_ENUMERATION_CONTEXT Context; BOOLEAN Extending; PVCB Vcb = IrpContext->Vcb; LONGLONG LlTemp1; PAGED_CODE(); ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_SCB( Scb ); ASSERT_EXCLUSIVE_SCB( Scb ); DebugTrace( +1, Dbg, ("NtfsAddAllocation\n") ); // // 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; } // // If the user's request extends beyond 32 bits for the cluster number // raise a disk full error. // LlTemp1 = ClusterCount + StartingVcn; 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 )); if (Scb->ScbSnapshot == NULL) { 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 )); if (AskForMore) { ULONG TailClusters; // // Use a simpler, more aggressive allocation strategy. // // // ULONG RunsInMcb; // LARGE-INTEGER AllocatedClusterCount; // LARGE-INTEGER Temp; // // // // // For the desired run cluster allocation count we compute the following // // formula // // // // DesiredClusterCount = Max(ClusterCount, Min(AllocatedClusterCount, 2^RunsInMcb)) // // // // where we will not let the RunsInMcb go beyond 10 // // // // // // // First compute 2^RunsInMcb // // // // RunsInMcb = FsRtlNumberOfRunsInLargeMcb( &Scb->Mcb ); // Temp = XxFromUlong(1 << (RunsInMcb < 10 ? RunsInMcb : 10)); // // // // // Next compute Min(AllocatedClusterCount, 2^RunsInMcb) // // // // AllocatedClusterCount = XxClustersFromBytes( Scb->Vcb, Scb->Header.AllocationSize ); // Temp = (XxLtr(AllocatedClusterCount, Temp) ? AllocatedClusterCount : Temp); // // // // // Now compute the Max function // // // // DesiredClusterCount = (XxGtr(ClusterCount, Temp) ? ClusterCount : Temp); // DesiredClusterCount = ClusterCount << 5; #ifdef _CAIRO_ if (NtfsPerformQuotaOperation(Scb->Fcb)) { NtfsGetRemainingQuota( IrpContext, Scb->Fcb->OwnerId, &LlTemp1, &Scb->Fcb->QuotaControl->QuickIndexHint ); LlTemp1 = LlClustersFromBytesTruncate( Vcb, LlTemp1 ); 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; } } #endif _CAIRO_ // // Make sure we don't extend this request into more than 32 bits. // LlTemp1 = DesiredClusterCount + StartingVcn; if ((((PLARGE_INTEGER)&DesiredClusterCount)->HighPart != 0) || (((PLARGE_INTEGER)&LlTemp1)->HighPart != 0)) { DesiredClusterCount = MAXULONG - StartingVcn; } // // Round up the cluster count so we fall on a page boundary. // TailClusters = (((ULONG)StartingVcn) + (ULONG)ClusterCount) & (Vcb->ClustersPerPage - 1); if (TailClusters != 0) { ClusterCount = ClusterCount + (Vcb->ClustersPerPage - TailClusters); } } else { DesiredClusterCount = ClusterCount; } // // 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) && (StartingVcn < LlClustersFromBytes(Vcb, (Scb->ValidDataToDisk + Scb->CompressionUnit - 1) & ~(Scb->CompressionUnit - 1)))) { ULONG CompressionUnitDeficit; CompressionUnitDeficit = ClustersFromBytes( Scb->Vcb, Scb->CompressionUnit ); if (((ULONG)StartingVcn) & (CompressionUnitDeficit - 1)) { // // BUGBUG: It appears this code is never called. // ASSERT(FALSE); CompressionUnitDeficit -= ((ULONG)StartingVcn) & (CompressionUnitDeficit - 1); if (ClusterCount <= CompressionUnitDeficit) { if (DesiredClusterCount <= CompressionUnitDeficit) { return; } ClusterCount = 0; } else { ClusterCount = ClusterCount - CompressionUnitDeficit; } StartingVcn = StartingVcn + CompressionUnitDeficit; DesiredClusterCount = DesiredClusterCount - CompressionUnitDeficit; } } // // Prepare for looking up attribute records to get the retrieval // information. // NtfsInitializeAttributeContext( &Context ); #ifdef _CAIRO_ 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 ) || ExIsResourceAcquiredSharedLite( Vcb->QuotaTableScb->Fcb->Resource )); NtfsAcquireQuotaControl( IrpContext, Scb->Fcb->QuotaControl ); } #endif // _CAIRO_ 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, (BOOLEAN)!NtfsIsTypeCodeUserData( Scb->AttributeTypeCode ), ClusterCount, &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 ); #ifdef _CAIRO_ // // Make sure the owner is allowed to have these // clusters. // if (FlagOn( Scb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA )) { ASSERT( NtfsIsTypeCodeSubjectToQuota( Scb->AttributeTypeCode )); // // Note the allocated clusters cannot be used // here because StartingVcn may be greater // then allocation size. // LlTemp1 = Scb->Header.AllocationSize.QuadPart - LlTemp1; ASSERT( !NtfsPerformQuotaOperation( Scb->Fcb ) || FlagOn( Scb->ScbState, SCB_STATE_QUOTA_ENLARGED)); NtfsConditionallyUpdateQuota( IrpContext, Scb->Fcb, &LlTemp1, TRUE, TRUE ); } #endif } else { NtfsAddAttributeAllocation( IrpContext, Scb, &Context, &StartingVcn, &ClusterCount ); } // // If he did not allocate anything, make sure we get out below. // } else { DesiredClusterCount = ClusterCount; } // Toplevel action is currently incompatible with our error recovery. // // // // // Now we will end this routine as a top-level action so that // // anyone can use this extended space. // // // // ****If we find that we are always keeping the Scb exclusive anyway, // // we could eliminate this log call. // // // // (VOID)NtfsWriteLog( IrpContext, // Vcb->MftScb, // NULL, // EndTopLevelAction, // NULL, // 0, // CompensationLogRecord, // (PVOID)&StartLsn, // sizeof(LSN), // Li0, // 0, // 0, // 0 ); // // // // // Now reset the undo information for the top-level action. // // // // { // PTRANSACTION_ENTRY TransactionEntry; // // NtfsAcquireSharedRestartTable( &Vcb->TransactionTable, TRUE ); // // TransactionEntry = (PTRANSACTION_ENTRY)GetRestartEntryFromIndex( // &Vcb->TransactionTable, IrpContext->TransactionId ); // // ASSERT(TransactionEntry->UndoBytes >= SavedUndoBytes); // // LfsResetUndoTotal( Vcb->LogHandle, // TransactionEntry->UndoRecords - SavedUndoRecords, // -(TransactionEntry->UndoBytes - SavedUndoBytes) ); // // TransactionEntry->UndoRecords = SavedUndoRecords; // TransactionEntry->UndoBytes = SavedUndoBytes; // // // NtfsReleaseRestartTable( &Vcb->TransactionTable ); // } // // // Call the Cache Manager to extend the section, now that we have // succeeded. // if (ARGUMENT_PRESENT( FileObject) && Extending && CcIsFileCached(FileObject)) { CcSetFileSizes( FileObject, (PCC_FILE_SIZES)&Scb->Header.AllocationSize ); } // // 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( &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( &Context ); } DebugTrace( -1, Dbg, ("NtfsAddAllocation -> 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; LCN TempLcn; LONGLONG TempCount; ULONG CompressionUnitInClusters = 1; PAGED_CODE(); if (Scb->CompressionUnit != 0) { CompressionUnitInClusters = ClustersFromBytes( Scb->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 = StartingVcn + (CompressionUnitInClusters - 1); ((ULONG)StartingVcn) &= ~(CompressionUnitInClusters - 1); } } // // Make sure we have a snapshot and update it with the range of this deallocation. // ASSERT( NtfsIsExclusiveScb( Scb )); if (Scb->ScbSnapshot == NULL) { 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 { LONGLONG ClustersPer4Gig; // // 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) { ClustersPer4Gig = LlClustersFromBytesTruncate( Scb->Vcb, 0x0000000100000000 ); MyEndingVcn = EndingVcn; if (EndingVcn == MAXLONGLONG) { MyEndingVcn = LlClustersFromBytesTruncate( Scb->Vcb, Scb->Header.AllocationSize.QuadPart ) - 1; } BlockStartingVcn = MyEndingVcn - 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 -= 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 += (CompressionUnitInClusters - 1); ((ULONG)BlockStartingVcn) &= ~(CompressionUnitInClusters - 1); } } } 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. // ((ULONG)MyStartingVcn) &= ~(CompressionUnitInClusters - 1); // // Remember we are breaking up now, and that as a result // we have to log everything. // BreakingUp = TRUE; LogIt = TRUE; } } #ifdef _CAIRO_ // // 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) { ASSERT( NtfsIsTypeCodeSubjectToQuota( Scb->AttributeTypeCode )); ASSERT( !NtfsPerformQuotaOperation( Scb->Fcb ) || FlagOn( Scb->ScbState, SCB_STATE_QUOTA_ENLARGED) || FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE )); // // Calculate the amount that allocation size is being reduced. // TempCount = LlBytesFromClusters( Scb->Vcb, MyStartingVcn ) - Scb->Header.AllocationSize.QuadPart; NtfsConditionallyUpdateQuota( IrpContext, Scb->Fcb, &TempCount, TRUE, FALSE ); } #endif // _CAIRO_ // // Now deallocate a range of clusters // NtfsDeleteAllocationInternal( IrpContext, FileObject, Scb, MyStartingVcn, EndingVcn, LogIt ); // // Now, if we are breaking up this deallocation, then do some // transaction cleanup. // if (BreakingUp) { 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; } } while (MyStartingVcn != BlockStartingVcn); } while (BlockStartingVcn != StartingVcn); } // // Internal support routine // VOID NtfsDeleteAllocationInternal ( IN PIRP_CONTEXT IrpContext, IN PFILE_OBJECT FileObject OPTIONAL, 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: 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. 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 (Scb != Vcb->MftScb && !FlagOn( Scb->ScbState, SCB_STATE_REALLOCATE_ON_WRITE ) && (Scb->CompressionUnit != 0) && (EndingVcn >= LlClustersFromBytes(Vcb, (Scb->ValidDataToDisk + Scb->CompressionUnit - 1) & ~(Scb->CompressionUnit - 1))) && ((IrpContext == IrpContext->TopLevelIrpContext) || (IrpContext->TopLevelIrpContext->MajorFunction != IRP_MJ_SET_INFORMATION))) { ASSERT( FlagOn( Scb->ScbState, SCB_STATE_COMPRESSED )); // // 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 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->Mcb, EndingVcn, MAXLONGLONG, &Scb->TotalAllocated ); } finally { if (AbnormalTermination()) { 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->Mcb, 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)) { Context.FoundAttribute.AttributeAllocationDeleted = TRUE; NtfsDeleteAttributeRecord( IrpContext, Scb->Fcb, LogIt, FALSE, &Context ); Context.FoundAttribute.AttributeAllocationDeleted = FALSE; // // 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( &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( &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( &Context ); NtfsInitializeAttributeContext( &Context ); NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &Context ); NtfsAddAttributeAllocation( IrpContext, Scb, &Context, &Vcn1, &SizeInClusters ); } else { NtfsCleanupAttributeContext( &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 (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) { NtfsWriteFileSizes( IrpContext, Scb, &Scb->Header.ValidDataLength.QuadPart, FALSE, TRUE ); } // // Call the Cache Manager to change allocation size for either // truncate or SplitMcb case (where EndingVcn was set to xxMax!). // if (ARGUMENT_PRESENT(FileObject) && CcIsFileCached( FileObject )) { CcSetFileSizes( FileObject, (PCC_FILE_SIZES)&Scb->Header.AllocationSize ); } // // Free any reserved clusters in the space freed. // if ((EndingVcn == MAXLONGLONG) && FlagOn(Scb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK) && (Scb->Header.NodeTypeCode == NTFS_NTC_SCB_DATA)) { NtfsFreeReservedClusters( Scb, LlBytesFromClusters(Vcb, StartingVcn), 0 ); } try_exit: NOTHING; } finally { DebugUnwind( NtfsDeleteAllocation ); // // Cleanup the attribute context on the way out. // NtfsCleanupAttributeContext( &Context ); NtfsCleanupAttributeContext( &TempContext ); } DebugTrace( -1, Dbg, ("NtfsDeleteAllocation -> 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; LCN CurrentLcn; VCN RunVcn; LCN RunLcn; BOOLEAN Found; LONGLONG RunCount; VCN HighestVcn; PVOID RangePtr; ULONG RunIndex; ULONG MSize = 0; ULONG LastSize = 0; PAGED_CODE(); HighestVcn = MAXLONGLONG; // // Initialize CurrentLcn as it 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 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 = (*StopOnVcn - RunVcn) + 1; RunIndex = MAXULONG - 1; } // // If we were asked to stop after a certain Vcn, do it here. // if (ARGUMENT_PRESENT(StopOnVcn)) { // // If the next Vcn is beyond the one we are to stop on, then // set HighestVcn, if not already set below, and get out. // if (RunVcn > *StopOnVcn) { if (*StopOnVcn == MAXLONGLONG) { HighestVcn = RunVcn; } 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. // if ((RunVcn + RunCount) > *StopOnVcn) { HighestVcn = *StopOnVcn + 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 += 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 += cp - (PCHAR)&Change + 1; CurrentLcn = RunLcn; } // // 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 on // run, or that we were able to process the entire (empty) Mcb. // ASSERT( (MSize != 0) || (HighestVcn == MAXLONGLONG) ); // // Return the Vcn we stopped on (or xxMax) and the size caculated, // adding one for the terminating 0. // *StoppedOnVcn = HighestVcn; return MSize + 1; } VOID 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: None --*/ { VCN NextVcn, CurrentVcn; LCN CurrentLcn; VCN RunVcn; LCN RunLcn; BOOLEAN Found; LONGLONG RunCount; PVOID RangePtr; ULONG RunIndex; 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 = 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 = (cp - (PCHAR)&ChangeL) + 1; // // Now advance CurrentLcn before looping back. // CurrentLcn = RunLcn; } // // 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; } VCN NtfsGetHighestVcn ( IN PIRP_CONTEXT IrpContext, IN VCN LowestVcn, 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 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; PAGED_CODE(); // // Implement the decompression algorithm, as defined in ntfs.h. // NextVcn = LowestVcn; ch = MappingPairs; // // Loop to process mapping pairs. // while (!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; // // Extract the Vcn change (use of RtlCopyMemory works for little-Endian) // and update NextVcn. // Change = 0; if (IsCharLtrZero(*(ch + VcnBytes - 1))) { NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, NULL ); } RtlCopyMemory( &Change, ch, VcnBytes ); NextVcn = NextVcn + Change; // // Just skip over Lcn. // ch += VcnBytes + LcnBytes; } 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; PRTL_BITMAP NewBitMap; LONGLONG SizeOfNewBitMap; ULONG CompressionShift; PVCB Vcb = Scb->Vcb; ULONG SizeTemp = 0; LONGLONG TempL; 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)); LastBit = (ULONG)Int64ShraMod32((FileOffset + (LONGLONG)ByteCount - 1), (CompressionShift)); // // Make sure we started with numbers in range. // ASSERT( ((LONGLONG)(FirstBit + 1) << CompressionShift) > FileOffset ); ASSERT( LastBit >= FirstBit ); ExAcquireResourceExclusive( Vcb->BitmapScb->Header.Resource, TRUE ); NtfsAcquireReservedClusters( Vcb ); // // See if we have to allocate a new or bigger bitmap. // if ((Scb->ScbType.Data.ReservedBitMap == NULL) || ((SizeTemp = Scb->ScbType.Data.ReservedBitMap->SizeOfBitMap) <= LastBit)) { // // Round the size we need to the nearest quad word since we will // use that much anyway, and want to reduce the number of times // we grow the bitmap. Convert old size to bytes. // SizeOfNewBitMap = FileOffset + (LONGLONG)ByteCount; if (SizeOfNewBitMap < Scb->Header.AllocationSize.QuadPart) { SizeOfNewBitMap = Scb->Header.AllocationSize.QuadPart; } SizeOfNewBitMap = (ULONG)((Int64ShraMod32(SizeOfNewBitMap, CompressionShift) + 64) & ~63) / 8; SizeTemp /= 8; // // Allocate and initialize the new bitmap. // NewBitMap = ExAllocatePool( PagedPool, (ULONG)SizeOfNewBitMap + sizeof(RTL_BITMAP) ); // // Check for alloacation error // if (NewBitMap == NULL) { NtfsReleaseReservedClusters( Vcb ); ExReleaseResource( Vcb->BitmapScb->Header.Resource ); // // If we have an Irp Context then we can raise insufficient resources. Otherwise // return FALSE. // if (ARGUMENT_PRESENT( IrpContext )) { NtfsRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES, NULL, NULL ); } else { return FALSE; } } RtlInitializeBitMap( NewBitMap, Add2Ptr(NewBitMap, sizeof(RTL_BITMAP)), (ULONG)SizeOfNewBitMap * 8 ); // // Copy the old bitmap over and delete it. Zero the new part. // if (SizeTemp != 0) { RtlCopyMemory( Add2Ptr(NewBitMap, sizeof(RTL_BITMAP)), Add2Ptr(Scb->ScbType.Data.ReservedBitMap, sizeof(RTL_BITMAP)), SizeTemp ); NtfsFreePool( Scb->ScbType.Data.ReservedBitMap ); } RtlZeroMemory( Add2Ptr(NewBitMap, sizeof(RTL_BITMAP) + SizeTemp), (ULONG)SizeOfNewBitMap - SizeTemp ); Scb->ScbType.Data.ReservedBitMap = NewBitMap; } NewBitMap = Scb->ScbType.Data.ReservedBitMap; // // One problem with the reservation strategy, is that we cannot precisely reserve // for metadata. If we reserve too much, we will return premature disk full, if // we reserve too little, the Lazy Writer can get an error. As we add compression // units to a file, large files will eventually require additional File Records. // If each compression unit required 0x20 bytes of run information (fairly pessimistic) // then a 0x400 size file record would fill up with less than 0x20 runs requiring // (worst case) two additional clusters for another file record. So each 0x20 // compression units require 0x200 reserved clusters, and a separate 2 cluster // file record. 0x200/2 = 0x100. So the calculations below tack a 1/0x100 (about // .4% "surcharge" on the amount reserved both in the Scb and the Vcb, to solve // the Lazy Writer popups like the ones Alan Morris gets in the print lab. // // // Figure out the worst case reservation required for this Scb, in bytes. // TempL = Scb->Header.AllocationSize.QuadPart + MM_MAXIMUM_DISK_IO_SIZE + Scb->CompressionUnit - (FlagOn( Scb->Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS ) ? Scb->Header.AllocationSize.QuadPart : Scb->TotalAllocated) + (Scb->ScbType.Data.TotalReserved / 0x100); // // Now loop to reserve the space, a compression unit at a time. // We use the security fast mutex as a convenient end resource. // while (FirstBit <= LastBit) { // // If this compression unit is not already reserved, do it now. // if (!RtlCheckBit( NewBitMap, FirstBit )) { // // If there is not sufficient space on the volume, then // we must see if this Scb is totally reserved anyway. // if (((Vcb->TotalReserved + (Vcb->TotalReserved / 0x100) + (1 << Scb->CompressionUnitShift)) >= Vcb->FreeClusters) && (Scb->ScbType.Data.TotalReserved < TempL) && (FlagOn(Scb->ScbState, SCB_STATE_WRITE_ACCESS_SEEN))) { NtfsReleaseReservedClusters( Vcb ); ExReleaseResource( Vcb->BitmapScb->Header.Resource ); return FALSE; } // // Reserve this compression unit. // SetFlag( NewBitMap->Buffer[FirstBit / 32], 1 << (FirstBit % 32) ); // // Increased TotalReserved bytes in the Scb. // Scb->ScbType.Data.TotalReserved += Scb->CompressionUnit; ASSERT( Scb->CompressionUnit != 0 ); ASSERT( Scb->CompressionUnitShift != 0 ); // // 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; } } FirstBit += 1; } NtfsReleaseReservedClusters( Vcb ); ExReleaseResource( Vcb->BitmapScb->Header.Resource ); return TRUE; } 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; PRTL_BITMAP BitMap; ULONG CompressionShift; PVCB Vcb = Scb->Vcb; ASSERT(Scb->Header.NodeTypeCode == NTFS_NTC_SCB_DATA); NtfsAcquireReservedClusters( Vcb ); // // If there is no bitmap, we can get out. // CompressionShift = Vcb->ClusterShift + (ULONG)Scb->CompressionUnitShift; BitMap = Scb->ScbType.Data.ReservedBitMap; if (BitMap == NULL) { NtfsReleaseReservedClusters( Vcb ); return; } // // Calculate first bit to free, and initialize LastBit // FirstBit = (ULONG)Int64ShraMod32(FileOffset, (CompressionShift)); LastBit = MAXULONG; // // If ByteCount was specified, then calculate LastBit. // if (ByteCount != 0) { LastBit = (ULONG)Int64ShraMod32((FileOffset + (LONGLONG)ByteCount - 1), (CompressionShift)); } // // Make sure we started with numbers in range. // ASSERT( ((LONGLONG)(FirstBit + 1) << CompressionShift) > FileOffset ); ASSERT( LastBit >= FirstBit ); // // Under no circumstances should we go off the end! // if (LastBit >= Scb->ScbType.Data.ReservedBitMap->SizeOfBitMap) { LastBit = Scb->ScbType.Data.ReservedBitMap->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. // while (FirstBit <= LastBit) { // // If this compression unit is reserved, then free it. // if (RtlCheckBit( BitMap, FirstBit )) { // // Free this compression unit. // ClearFlag( 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 ); ASSERT( Scb->CompressionUnitShift != 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; } } FirstBit += 1; } NtfsReleaseReservedClusters( Vcb ); } VOID NtfsFreeFinalReservedClusters ( IN PVCB Vcb, IN LONGLONG ClusterCount ) /*++ Routine Description: This routine frees any previously reserved clusters in the specified range. Arguments: Vcb - Volume to which clusters are to be freed ClusterCount - Number of clusters being freed by caller Return Value: None (all errors simply raise) --*/ { // // Use the security fast mutex as a convenient end resource. // NtfsAcquireReservedClusters( Vcb ); ASSERT(Vcb->TotalReserved >= ClusterCount); Vcb->TotalReserved -= ClusterCount; NtfsReleaseReservedClusters( Vcb ); } #ifdef SYSCACHE BOOLEAN FsRtlIsSyscacheFile ( IN PFILE_OBJECT FileObject ) /*++ Routine Description: This routine returns to the caller whether or not the specified file object is a file for the Syscache stress test. It is considered a syscache file, if the last component of the file name in the FileObject matches "cac*.tmp", case insensitive. 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. --*/ { if ((FileObject != NULL) && (FileObject->FileName.Length >= 8*2)) { ULONG iM = 0; ULONG iF; PWSTR MakName = L"cac*.tmp"; iF = FileObject->FileName.Length / 2; while ((iF != 0) && (FileObject->FileName.Buffer[iF - 1] != '\\')) { iF--; } while (TRUE) { if ((iM == 8) && ((LONG)iF == FileObject->FileName.Length / 2)) { return TRUE; } else if (MakName[iM] == '*') { if (FileObject->FileName.Buffer[iF] == '.') { iM++; iM++; iF++; } else { iF++; if ((LONG)iF == FileObject->FileName.Length / 2) { break; } } } else if (MakName[iM] == (WCHAR)(FileObject->FileName.Buffer[iF] | ('a' - 'A'))) { 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