/*++ 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