/*++ Copyright (c) 1991 Microsoft Corporation Module Name: AttrSup.c Abstract: This module implements the attribute management routines for Ntfs Author: David Goebel [DavidGoe] 25-June-1991 Tom Miller [TomM] 9-November-1991 Revision History: --*/ #include "NtfsProc.h" // // The Bug check file id for this module // #define BugCheckFileId (NTFS_BUG_CHECK_ATTRSUP) // // Local debug trace level // #define Dbg (DEBUG_TRACE_ATTRSUP) // // Define a tag for general pool allocations from this module // #undef MODULE_POOL_TAG #define MODULE_POOL_TAG ('AFtN') // // // Internal support routines // BOOLEAN NtfsFindInFileRecord ( IN PIRP_CONTEXT IrpContext, IN PATTRIBUTE_RECORD_HEADER Attribute, OUT PATTRIBUTE_RECORD_HEADER *ReturnAttribute, IN ATTRIBUTE_TYPE_CODE QueriedTypeCode, IN PUNICODE_STRING QueriedName OPTIONAL, IN BOOLEAN IgnoreCase, IN PVOID QueriedValue OPTIONAL, IN ULONG QueriedValueLength ); // // Internal support routines for managing file record space // VOID NtfsCreateNonresidentWithValue ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN ATTRIBUTE_TYPE_CODE AttributeTypeCode, IN PUNICODE_STRING AttributeName OPTIONAL, IN PVOID Value OPTIONAL, IN ULONG ValueLength, IN USHORT AttributeFlags, IN BOOLEAN WriteClusters, IN PSCB ThisScb OPTIONAL, IN BOOLEAN LogIt, IN PATTRIBUTE_ENUMERATION_CONTEXT Context ); BOOLEAN NtfsGetSpaceForAttribute ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN ULONG Length, IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context ); VOID MakeRoomForAttribute ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN ULONG SizeNeeded, IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context ); VOID FindLargestAttributes ( IN PFILE_RECORD_SEGMENT_HEADER FileRecord, IN ULONG Number, OUT PATTRIBUTE_RECORD_HEADER *AttributeArray ); LONGLONG MoveAttributeToOwnRecord ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN PATTRIBUTE_RECORD_HEADER Attribute, IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context, OUT PBCB *NewBcb OPTIONAL, OUT PFILE_RECORD_SEGMENT_HEADER *NewFileRecord OPTIONAL ); VOID SplitFileRecord ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN ULONG SizeNeeded, IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context ); PFILE_RECORD_SEGMENT_HEADER NtfsCloneFileRecord ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN BOOLEAN MftData, OUT PBCB *Bcb, OUT PMFT_SEGMENT_REFERENCE FileReference ); ULONG GetSizeForAttributeList ( IN PFILE_RECORD_SEGMENT_HEADER FileRecord ); VOID CreateAttributeList ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN PFILE_RECORD_SEGMENT_HEADER FileRecord1, IN PFILE_RECORD_SEGMENT_HEADER FileRecord2 OPTIONAL, IN MFT_SEGMENT_REFERENCE SegmentReference2, IN PATTRIBUTE_RECORD_HEADER OldPosition OPTIONAL, IN ULONG SizeOfList, IN OUT PATTRIBUTE_ENUMERATION_CONTEXT ListContext ); VOID UpdateAttributeListEntry ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN PMFT_SEGMENT_REFERENCE OldFileReference, IN USHORT OldInstance, IN PMFT_SEGMENT_REFERENCE NewFileReference, IN USHORT NewInstance, IN OUT PATTRIBUTE_ENUMERATION_CONTEXT ListContext ); VOID NtfsAddNameToParent ( IN PIRP_CONTEXT IrpContext, IN PSCB ParentScb, IN PFCB ThisFcb, IN BOOLEAN IgnoreCase, IN PBOOLEAN LogIt, IN PFILE_NAME FileNameAttr, OUT PUCHAR FileNameFlags, OUT PQUICK_INDEX QuickIndex OPTIONAL, IN PNAME_PAIR NamePair OPTIONAL ); VOID NtfsAddDosOnlyName ( IN PIRP_CONTEXT IrpContext, IN PSCB ParentScb, IN PFCB ThisFcb, IN UNICODE_STRING FileName, IN BOOLEAN LogIt, IN PUNICODE_STRING SuggestedDosName OPTIONAL ); BOOLEAN NtfsAddTunneledNtfsOnlyName ( IN PIRP_CONTEXT IrpContext, IN PSCB ParentScb, IN PFCB ThisFcb, IN PUNICODE_STRING FileName, IN PBOOLEAN LogIt ); #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, CreateAttributeList) #pragma alloc_text(PAGE, FindLargestAttributes) #pragma alloc_text(PAGE, GetSizeForAttributeList) #pragma alloc_text(PAGE, MakeRoomForAttribute) #pragma alloc_text(PAGE, MoveAttributeToOwnRecord) #pragma alloc_text(PAGE, NtfsAddAttributeAllocation) #pragma alloc_text(PAGE, NtfsAddDosOnlyName) #pragma alloc_text(PAGE, NtfsAddLink) #pragma alloc_text(PAGE, NtfsAddNameToParent) #pragma alloc_text(PAGE, NtfsAddToAttributeList) #pragma alloc_text(PAGE, NtfsAddTunneledNtfsOnlyName) #pragma alloc_text(PAGE, NtfsChangeAttributeSize) #pragma alloc_text(PAGE, NtfsChangeAttributeValue) #pragma alloc_text(PAGE, NtfsCleanupAttributeContext) #pragma alloc_text(PAGE, NtfsCloneFileRecord) #pragma alloc_text(PAGE, NtfsConvertToNonresident) #pragma alloc_text(PAGE, NtfsCreateAttributeWithAllocation) #pragma alloc_text(PAGE, NtfsCreateAttributeWithValue) #pragma alloc_text(PAGE, NtfsCreateNonresidentWithValue) #pragma alloc_text(PAGE, NtfsDeleteAllocationFromRecord) #pragma alloc_text(PAGE, NtfsDeleteAttributeAllocation) #pragma alloc_text(PAGE, NtfsDeleteAttributeRecord) #pragma alloc_text(PAGE, NtfsDeleteFile) #pragma alloc_text(PAGE, NtfsDeleteFromAttributeList) #pragma alloc_text(PAGE, NtfsGetAttributeTypeCode) #pragma alloc_text(PAGE, NtfsGetSpaceForAttribute) #pragma alloc_text(PAGE, NtfsGrowStandardInformation) #pragma alloc_text(PAGE, NtfsIsFileDeleteable) #pragma alloc_text(PAGE, NtfsLookupEntry) #pragma alloc_text(PAGE, NtfsLookupExternalAttribute) #pragma alloc_text(PAGE, NtfsLookupInFileRecord) #pragma alloc_text(PAGE, NtfsMapAttributeValue) #pragma alloc_text(PAGE, NtfsPrepareForUpdateDuplicate) #pragma alloc_text(PAGE, NtfsRemoveLink) #pragma alloc_text(PAGE, NtfsRemoveLinkViaFlags) #pragma alloc_text(PAGE, NtfsRestartChangeAttributeSize) #pragma alloc_text(PAGE, NtfsRestartChangeMapping) #pragma alloc_text(PAGE, NtfsRestartChangeValue) #pragma alloc_text(PAGE, NtfsRestartInsertAttribute) #pragma alloc_text(PAGE, NtfsRestartRemoveAttribute) #pragma alloc_text(PAGE, NtfsRestartWriteEndOfFileRecord) #pragma alloc_text(PAGE, NtfsRewriteMftMapping) #pragma alloc_text(PAGE, NtfsSetTotalAllocatedField) #pragma alloc_text(PAGE, NtfsUpdateDuplicateInfo) #pragma alloc_text(PAGE, NtfsUpdateFcb) #pragma alloc_text(PAGE, NtfsUpdateFcbInfoFromDisk) #pragma alloc_text(PAGE, NtfsUpdateLcbDuplicateInfo) #pragma alloc_text(PAGE, NtfsUpdateScbFromAttribute) #pragma alloc_text(PAGE, NtfsUpdateStandardInformation) #pragma alloc_text(PAGE, NtfsWriteFileSizes) #pragma alloc_text(PAGE, SplitFileRecord) #pragma alloc_text(PAGE, UpdateAttributeListEntry) #endif ATTRIBUTE_TYPE_CODE NtfsGetAttributeTypeCode ( IN PVCB Vcb, IN UNICODE_STRING AttributeTypeName ) /*++ Routine Description: This routine returns the attribute type code for a given attribute name. Arguments: Vcb - Pointer to the Vcb from which to consult the attribute definitions. AttributeTypeName - A string containing the attribute type name to be looked up. Return Value: The attribute type code corresponding to the specified name, or 0 if the attribute type name does not exist. --*/ { PATTRIBUTE_DEFINITION_COLUMNS AttributeDef = Vcb->AttributeDefinitions; ATTRIBUTE_TYPE_CODE AttributeTypeCode = $UNUSED; UNICODE_STRING AttributeCodeName; PAGED_CODE(); // // Loop through all of the definitions looking for a name match. // while (AttributeDef->AttributeName[0] != 0) { RtlInitUnicodeString( &AttributeCodeName, AttributeDef->AttributeName ); // // The name lengths must match and the characters match exactly. // if ((AttributeCodeName.Length == AttributeTypeName.Length) && (RtlEqualMemory( AttributeTypeName.Buffer, AttributeDef->AttributeName, AttributeTypeName.Length ))) { AttributeTypeCode = AttributeDef->AttributeTypeCode; break; } // // Lets go to the next attribute column. // AttributeDef += 1; } return AttributeTypeCode; } VOID NtfsUpdateScbFromAttribute ( IN PIRP_CONTEXT IrpContext, IN OUT PSCB Scb, IN PATTRIBUTE_RECORD_HEADER AttrHeader OPTIONAL ) /*++ Routine Description: This routine fills in the header of an Scb with the information from the attribute for this Scb. Arguments: Scb - Supplies the SCB to update AttrHeader - Optionally provides the attribute to update from Return Value: None --*/ { ATTRIBUTE_ENUMERATION_CONTEXT AttrContext; BOOLEAN CleanupAttrContext = FALSE; PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsUpdateScbFromAttribute: Entered\n") ); // // If the attribute has been deleted, we can return immediately // claiming that the Scb has been initialized. // if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )) { SetFlag( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED ); DebugTrace( -1, Dbg, ("NtfsUpdateScbFromAttribute: Exit\n") ); return; } // // Use a try-finally to facilitate cleanup. // try { // // If we weren't given the attribute header, we look it up now. // if (!ARGUMENT_PRESENT( AttrHeader )) { NtfsInitializeAttributeContext( &AttrContext ); CleanupAttrContext = TRUE; NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &AttrContext ); AttrHeader = NtfsFoundAttribute( &AttrContext ); } // // Check whether this is resident or nonresident // if (NtfsIsAttributeResident( AttrHeader )) { Scb->Header.AllocationSize.QuadPart = AttrHeader->Form.Resident.ValueLength; if (!FlagOn(Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED)) { Scb->Header.ValidDataLength = Scb->Header.FileSize = Scb->Header.AllocationSize; SetFlag(Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED); } Scb->Header.AllocationSize.LowPart = QuadAlign( Scb->Header.AllocationSize.LowPart ); Scb->TotalAllocated = Scb->Header.AllocationSize.QuadPart; // // Set the resident flag in the Scb. // SetFlag( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT ); } else { VCN FileClusters; VCN AllocationClusters; if (!FlagOn(Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED)) { Scb->Header.ValidDataLength.QuadPart = AttrHeader->Form.Nonresident.ValidDataLength; Scb->Header.FileSize.QuadPart = AttrHeader->Form.Nonresident.FileSize; SetFlag(Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED); Scb->ValidDataToDisk = AttrHeader->Form.Nonresident.ValidDataLength; } Scb->Header.AllocationSize.QuadPart = AttrHeader->Form.Nonresident.AllocatedLength; Scb->TotalAllocated = Scb->Header.AllocationSize.QuadPart; if (FlagOn(AttrHeader->Flags, ATTRIBUTE_FLAG_COMPRESSION_MASK)) { Scb->TotalAllocated = AttrHeader->Form.Nonresident.TotalAllocated; if (Scb->TotalAllocated < 0) { Scb->TotalAllocated = 0; } else if (Scb->TotalAllocated > Scb->Header.AllocationSize.QuadPart) { Scb->TotalAllocated = Scb->Header.AllocationSize.QuadPart; } } ClearFlag( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT ); // // Get the size of the compression unit. // ASSERT((AttrHeader->Form.Nonresident.CompressionUnit == 0) || (AttrHeader->Form.Nonresident.CompressionUnit == NTFS_CLUSTERS_PER_COMPRESSION)); Scb->CompressionUnit = 0; Scb->CompressionUnitShift = 0; if ((AttrHeader->Form.Nonresident.CompressionUnit != 0) && (AttrHeader->Form.Nonresident.CompressionUnit < 31)) { Scb->CompressionUnit = BytesFromClusters( Scb->Vcb, 1 << AttrHeader->Form.Nonresident.CompressionUnit ); Scb->CompressionUnitShift = AttrHeader->Form.Nonresident.CompressionUnit; ASSERT( NtfsIsTypeCodeCompressible( Scb->AttributeTypeCode )); } // // Compute the clusters for the file and its allocation. // AllocationClusters = LlClustersFromBytes( Scb->Vcb, Scb->Header.AllocationSize.QuadPart ); if (Scb->CompressionUnit == 0) { FileClusters = LlClustersFromBytes(Scb->Vcb, Scb->Header.FileSize.QuadPart); } else { FileClusters = Scb->Header.FileSize.QuadPart + Scb->CompressionUnit - 1; FileClusters &= ~(Scb->CompressionUnit - 1); } // // If allocated clusters are greater than file clusters, mark // the Scb to truncate on close. // if (AllocationClusters > FileClusters) { SetFlag( Scb->ScbState, SCB_STATE_TRUNCATE_ON_CLOSE ); } } // // Update compression information if this is not an index // if (Scb->AttributeTypeCode != $INDEX_ALLOCATION) { Scb->AttributeFlags = AttrHeader->Flags; if (FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_SPARSE )) { NtfsRaiseStatus( IrpContext, STATUS_ACCESS_DENIED, NULL, NULL ); } if (FlagOn(AttrHeader->Flags, ATTRIBUTE_FLAG_COMPRESSION_MASK)) { // // Do not try to infer whether we are writing compressed or not // if we are actively changing the compression state. // if (!FlagOn(Scb->ScbState, SCB_STATE_REALLOCATE_ON_WRITE)) { SetFlag( Scb->ScbState, SCB_STATE_COMPRESSED ); } // // If the attribute is resident, then we will use our current // default. // if (Scb->CompressionUnit == 0) { Scb->CompressionUnit = BytesFromClusters( Scb->Vcb, 1 << NTFS_CLUSTERS_PER_COMPRESSION ); Scb->CompressionUnitShift = NTFS_CLUSTERS_PER_COMPRESSION; ASSERT( NtfsIsTypeCodeCompressible( Scb->AttributeTypeCode )); } } else { // // Do not try to infer whether we are writing compressed or not // if we are actively changing the compression state. // if (!FlagOn(Scb->ScbState, SCB_STATE_REALLOCATE_ON_WRITE)) { ClearFlag( Scb->ScbState, SCB_STATE_COMPRESSED ); } // // Make sure compression unit is 0 // Scb->CompressionUnit = 0; Scb->CompressionUnitShift = 0; } } // // If the compression unit is non-zero or this is a resident file // then set the flag in the common header for the Modified page writer. // NtfsAcquireFsrtlHeader( Scb ); if (NodeType( Scb ) == NTFS_NTC_SCB_DATA) { Scb->Header.IsFastIoPossible = NtfsIsFastIoPossible( Scb ); } else { Scb->Header.IsFastIoPossible = FastIoIsNotPossible; } NtfsReleaseFsrtlHeader( Scb ); // // Set the flag indicating this is the data attribute. // if (Scb->AttributeTypeCode == $DATA && Scb->AttributeName.Length == 0) { SetFlag( Scb->ScbState, SCB_STATE_UNNAMED_DATA ); } else { ClearFlag( Scb->ScbState, SCB_STATE_UNNAMED_DATA ); } SetFlag( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED ); if (NtfsIsExclusiveScb(Scb)) { NtfsSnapshotScb( IrpContext, Scb ); } } finally { DebugUnwind( NtfsUpdateScbFromAttribute ); // // Cleanup the attribute context. // if (CleanupAttrContext) { NtfsCleanupAttributeContext( &AttrContext ); } DebugTrace( -1, Dbg, ("NtfsUpdateScbFromAttribute: Exit\n") ); } return; } VOID NtfsUpdateFcbInfoFromDisk ( IN PIRP_CONTEXT IrpContext, IN BOOLEAN LoadSecurity, IN OUT PFCB Fcb, IN PFCB ParentFcb OPTIONAL, OUT POLD_SCB_SNAPSHOT UnnamedDataSizes OPTIONAL ) /*++ Routine Description: This routine is called to update an Fcb from the on-disk attributes for a file. We read the standard information and ea information. The first one must be present, we raise if not. The other does not have to exist. If this is not a directory, then we also need the size of the unnamed data attribute. Arguments: LoadSecurity - Indicates if we should load the security for this file if not already present. Fcb - This is the Fcb to update. ParentFcb - Optional pointer to parent of this Fcb. UnnamedDataSizes - If specified, then we store the details of the unnamed data attribute as we encounter it. Return Value: None - This routine will raise on error. --*/ { ATTRIBUTE_ENUMERATION_CONTEXT AttrContext; PATTRIBUTE_RECORD_HEADER AttributeHeader; BOOLEAN FoundEntry; BOOLEAN CorruptDisk = FALSE; PBCB Bcb = NULL; PDUPLICATED_INFORMATION Info; PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsUpdateFcbInfoFromDisk: Entered\n") ); NtfsInitializeAttributeContext( &AttrContext ); // // Use a try-finally to facilitate cleanup. // try { // // Look for standard information. This routine assumes it must be // the first attribute. // if (FoundEntry = NtfsLookupAttribute( IrpContext, Fcb, &Fcb->FileReference, &AttrContext )) { // // Verify that we found the standard information attribute. // AttributeHeader = NtfsFoundAttribute( &AttrContext ); if (AttributeHeader->TypeCode != $STANDARD_INFORMATION) { try_return( CorruptDisk = TRUE ); } } else { try_return( CorruptDisk = TRUE ); } Info = &Fcb->Info; // // Copy out the standard information values. // { PSTANDARD_INFORMATION StandardInformation; StandardInformation = (PSTANDARD_INFORMATION) NtfsAttributeValue( AttributeHeader ); Info->CreationTime = StandardInformation->CreationTime; Info->LastModificationTime = StandardInformation->LastModificationTime; Info->LastChangeTime = StandardInformation->LastChangeTime; Info->LastAccessTime = StandardInformation->LastAccessTime; Info->FileAttributes = StandardInformation->FileAttributes; #ifdef _CAIRO_ if (AttributeHeader->Form.Resident.ValueLength >= sizeof(STANDARD_INFORMATION)) { Fcb->ClassId = StandardInformation->ClassId; Fcb->OwnerId = StandardInformation->OwnerId; Fcb->SecurityId = StandardInformation->SecurityId; Fcb->Usn = StandardInformation->Usn; SetFlag(Fcb->FcbState, FCB_STATE_LARGE_STD_INFO); } #else _CAIRO_ if (AttributeHeader->Form.Resident.ValueLength > sizeof( STANDARD_INFORMATION )) { SetFlag(Fcb->FcbState, FCB_STATE_LARGE_STD_INFO); } #endif _CAIRO_ } Fcb->CurrentLastAccess = Info->LastAccessTime; // // We get the FILE_NAME_INDEX_PRESENT bit by reading the // file record. // if (FlagOn( NtfsContainingFileRecord( &AttrContext )->Flags, FILE_FILE_NAME_INDEX_PRESENT )) { SetFlag( Info->FileAttributes, DUP_FILE_NAME_INDEX_PRESENT ); } else { ClearFlag( Info->FileAttributes, DUP_FILE_NAME_INDEX_PRESENT ); } // // We now walk through all of the filename attributes, counting the // number of non-8dot3-only links. // Fcb->TotalLinks = Fcb->LinkCount = 0; FoundEntry = NtfsLookupNextAttributeByCode( IrpContext, Fcb, $FILE_NAME, &AttrContext ); while (FoundEntry) { PFILE_NAME FileName; AttributeHeader = NtfsFoundAttribute( &AttrContext ); if (AttributeHeader->TypeCode != $FILE_NAME) { break; } FileName = (PFILE_NAME) NtfsAttributeValue( AttributeHeader ); // // We increment the count as long as this is not a 8.3 link // only. // if (FileName->Flags != FILE_NAME_DOS) { Fcb->LinkCount += 1; Fcb->TotalLinks += 1; } // // Now look for the next link. // FoundEntry = NtfsLookupNextAttribute( IrpContext, Fcb, &AttrContext ); } // // There better be at least one. // if (Fcb->LinkCount == 0) { try_return( CorruptDisk = TRUE ); } // // If we are to load the security and it is not already present we // find the security attribute. // if (LoadSecurity && Fcb->SharedSecurity == NULL) { #ifdef _CAIRO_ // // We have two sources of security descriptors. First, we have // the SecurityId that is present in a large $STANDARD_INFORMATION. // The other case is where we don't have such a security Id and must // retrieve it from the $SECURITY_DESCRIPTOR attribute // // In the case where we have the Id, we load it from the volume // cache or index. // if (FlagOn( Fcb->FcbState, FCB_STATE_LARGE_STD_INFO ) && Fcb->SecurityId != SECURITY_ID_INVALID) { NtfsLoadSecurityDescriptorById( IrpContext, Fcb, ParentFcb ); } else { #endif // _CAIRO_ PSECURITY_DESCRIPTOR SecurityDescriptor; ULONG SecurityDescriptorLength; // // We may have to walk forward to the security descriptor. // while (FoundEntry) { AttributeHeader = NtfsFoundAttribute( &AttrContext ); if (AttributeHeader->TypeCode == $SECURITY_DESCRIPTOR) { NtfsMapAttributeValue( IrpContext, Fcb, (PVOID *)&SecurityDescriptor, &SecurityDescriptorLength, &Bcb, &AttrContext ); NtfsUpdateFcbSecurity( IrpContext, Fcb, ParentFcb, #ifdef _CAIRO_ SECURITY_ID_INVALID, #endif // _CAIRO_ SecurityDescriptor, SecurityDescriptorLength ); // // If the security descriptor was resident then the Bcb field // in the attribute context was stored in the returned Bcb and // the Bcb in the attribute context was cleared. In that case // the resumption of the attribute search will fail because // this module using the Bcb field to determine if this // is the initial enumeration. // if (NtfsIsAttributeResident( AttributeHeader )) { NtfsFoundBcb( &AttrContext ) = Bcb; Bcb = NULL; } } else if (AttributeHeader->TypeCode > $SECURITY_DESCRIPTOR) { break; } FoundEntry = NtfsLookupNextAttribute( IrpContext, Fcb, &AttrContext ); } #ifdef _CAIRO_ } #endif } // // If this is not a directory, we need the file size. // if (!IsDirectory( Info )) { BOOLEAN FoundData = FALSE; // // Look for the unnamed data attribute. // while (FoundEntry) { AttributeHeader = NtfsFoundAttribute( &AttrContext ); if (AttributeHeader->TypeCode > $DATA) { break; } if ((AttributeHeader->TypeCode == $DATA) && (AttributeHeader->NameLength == 0)) { // // This can vary depending whether the attribute is resident // or nonresident. // if (NtfsIsAttributeResident( AttributeHeader )) { Info->AllocatedLength = AttributeHeader->Form.Resident.ValueLength; Info->FileSize = Info->AllocatedLength; ((ULONG)Info->AllocatedLength) = QuadAlign( (ULONG)(Info->AllocatedLength) ); // // If the user passed in a ScbSnapshot, then copy the attribute // sizes to that. We use the trick of setting the low bit of the // attribute size to indicate a resident attribute. // if (ARGUMENT_PRESENT( UnnamedDataSizes )) { UnnamedDataSizes->TotalAllocated = UnnamedDataSizes->AllocationSize = Info->AllocatedLength; UnnamedDataSizes->FileSize = Info->FileSize; UnnamedDataSizes->ValidDataLength = Info->FileSize; UnnamedDataSizes->Resident = TRUE; UnnamedDataSizes->CompressionUnit = 0; UnnamedDataSizes->AttributeFlags = AttributeHeader->Flags; } FoundData = TRUE; } else if (AttributeHeader->Form.Nonresident.LowestVcn == 0) { Info->AllocatedLength = AttributeHeader->Form.Nonresident.AllocatedLength; Info->FileSize = AttributeHeader->Form.Nonresident.FileSize; if (ARGUMENT_PRESENT( UnnamedDataSizes )) { UnnamedDataSizes->TotalAllocated = UnnamedDataSizes->AllocationSize = Info->AllocatedLength; UnnamedDataSizes->FileSize = Info->FileSize; UnnamedDataSizes->ValidDataLength = AttributeHeader->Form.Nonresident.ValidDataLength; UnnamedDataSizes->Resident = FALSE; UnnamedDataSizes->CompressionUnit = AttributeHeader->Form.Nonresident.CompressionUnit; // // Remember if it is compressed. // UnnamedDataSizes->AttributeFlags = AttributeHeader->Flags; } if (FlagOn( AttributeHeader->Flags, ATTRIBUTE_FLAG_COMPRESSION_MASK )) { Info->AllocatedLength = AttributeHeader->Form.Nonresident.TotalAllocated; if (ARGUMENT_PRESENT( UnnamedDataSizes )) { UnnamedDataSizes->TotalAllocated = Info->AllocatedLength; if (UnnamedDataSizes->TotalAllocated < 0) { UnnamedDataSizes->TotalAllocated = 0; } else if (UnnamedDataSizes->TotalAllocated > Info->AllocatedLength) { UnnamedDataSizes->TotalAllocated = Info->AllocatedLength; } } } FoundData = TRUE; } break; } FoundEntry = NtfsLookupNextAttribute( IrpContext, Fcb, &AttrContext ); } if (!FoundData) { try_return( CorruptDisk = TRUE ); } } else { Info->AllocatedLength = 0; Info->FileSize = 0; } // // Now we look for an Ea information attribute. This one doesn't have to // be there. // Info->PackedEaSize = 0; while (FoundEntry) { PEA_INFORMATION EaInformation; AttributeHeader = NtfsFoundAttribute( &AttrContext ); if (AttributeHeader->TypeCode > $EA_INFORMATION) { break; } else if (AttributeHeader->TypeCode == $EA_INFORMATION) { EaInformation = (PEA_INFORMATION) NtfsAttributeValue( NtfsFoundAttribute( &AttrContext )); Info->PackedEaSize = EaInformation->PackedEaSize; break; } FoundEntry = NtfsLookupNextAttributeByCode( IrpContext, Fcb, $EA_INFORMATION, &AttrContext ); } // // Set the flag in the Fcb to indicate that we set these fields. // SetFlag( Fcb->FcbState, FCB_STATE_DUP_INITIALIZED ); try_exit: NOTHING; } finally { DebugUnwind( NtfsUpdateFcbInfoFromDisk ); NtfsCleanupAttributeContext( &AttrContext ); NtfsUnpinBcb( &Bcb ); DebugTrace( -1, Dbg, ("NtfsUpdateFcbInfoFromDisk: Exit\n") ); } // // If we encountered a corrupt disk, we generate a popup and raise the file // corrupt error. // if (CorruptDisk) { NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb ); } return; } VOID NtfsCleanupAttributeContext ( IN OUT PATTRIBUTE_ENUMERATION_CONTEXT AttributeContext ) /*++ Routine Description: This routine is called to free any resources claimed within an enumeration context and to unpin mapped or pinned data. Arguments: AttributeContext - Pointer to the enumeration context to perform cleanup on. Return Value: None. --*/ { PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsCleanupAttributeContext\n") ); // // TEMPCODE We need a call to cleanup any Scb's created. // // // Unpin any Bcb's pinned here. // NtfsUnpinBcb( &AttributeContext->FoundAttribute.Bcb ); NtfsUnpinBcb( &AttributeContext->AttributeList.Bcb ); NtfsUnpinBcb( &AttributeContext->AttributeList.NonresidentListBcb ); // // Originally, we zeroed the entire context at this point. This is // wildly inefficient since the context is either deallocated soon thereafter // or is initialized again. // // RtlZeroMemory( AttributeContext, sizeof(ATTRIBUTE_ENUMERATION_CONTEXT) ); // // BUGBUG - set entire contents to -1 (and reset Bcb's to NULL) to verify // that no one reuses this data structure #if DBG RtlFillMemory( AttributeContext, sizeof( *AttributeContext ), -1 ); AttributeContext->FoundAttribute.Bcb = NULL; AttributeContext->AttributeList.Bcb = NULL; AttributeContext->AttributeList.NonresidentListBcb = NULL; #endif DebugTrace( -1, Dbg, ("NtfsCleanupAttributeContext -> VOID\n") ); return; } VOID NtfsWriteFileSizes ( IN PIRP_CONTEXT IrpContext, IN PSCB Scb, IN PLONGLONG ValidDataLength, IN BOOLEAN AdvanceOnly, IN BOOLEAN LogIt ) /*++ Routine Description: This routine is called to modify the filesize and valid data size on the disk from the Scb. Arguments: Scb - Scb whose attribute is being modified. ValidDataLength - Supplies pointer to the new desired ValidDataLength AdvanceOnly - TRUE if the valid data length should be set only if greater than the current value on disk. FALSE if the valid data length should be set only if less than the current value on disk. LogIt - Indicates whether we should log this change. Return Value: None. --*/ { ATTRIBUTE_ENUMERATION_CONTEXT AttrContext; PATTRIBUTE_RECORD_HEADER AttributeHeader; PFILE_RECORD_SEGMENT_HEADER FileRecord; NEW_ATTRIBUTE_SIZES OldAttributeSizes; NEW_ATTRIBUTE_SIZES NewAttributeSizes; ULONG LogRecordSize = SIZEOF_PARTIAL_ATTRIBUTE_SIZES; BOOLEAN CompressedStream = FALSE; BOOLEAN UpdateMft = FALSE; PAGED_CODE(); // // Return immediately if the volume is locked. // if (FlagOn( Scb->Vcb->VcbState, VCB_STATE_LOCKED )) { return; } DebugTrace( +1, Dbg, ("NtfsWriteFileSizes: Entered\n") ); // // Use a try_finally to facilitate cleanup. // try { // // Find the attribute on the disk. // NtfsInitializeAttributeContext( &AttrContext ); NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &AttrContext ); // // Pull the pointers out of the attribute context. // FileRecord = NtfsContainingFileRecord( &AttrContext ); AttributeHeader = NtfsFoundAttribute( &AttrContext ); // // Check if this is a resident attribute, and if it is then we only // want to assert that the file sizes match and then return to // our caller // if (NtfsIsAttributeResident( AttributeHeader )) { try_return( NOTHING ); } // // Remember the existing values. // OldAttributeSizes.TotalAllocated = OldAttributeSizes.AllocationSize = AttributeHeader->Form.Nonresident.AllocatedLength; OldAttributeSizes.ValidDataLength = AttributeHeader->Form.Nonresident.ValidDataLength; OldAttributeSizes.FileSize = AttributeHeader->Form.Nonresident.FileSize; if (FlagOn( AttributeHeader->Flags, ATTRIBUTE_FLAG_COMPRESSION_MASK )) { CompressedStream = TRUE; OldAttributeSizes.TotalAllocated = AttributeHeader->Form.Nonresident.TotalAllocated; } // // Copy these values. // NewAttributeSizes = OldAttributeSizes; // // Check if we will be modifying the valid data length on // disk. Don't acquire this for the paging file in case the // current code block needs to be paged in. // if (!FlagOn( Scb->Fcb->FcbState, FCB_STATE_PAGING_FILE )) { NtfsAcquireFsrtlHeader(Scb); } if ((AdvanceOnly && (*ValidDataLength > OldAttributeSizes.ValidDataLength)) || (!AdvanceOnly && (*ValidDataLength < OldAttributeSizes.ValidDataLength))) { // // Copy the valid data length into the new size structure. // NewAttributeSizes.ValidDataLength = *ValidDataLength; UpdateMft = TRUE; } // // Now check if we're modifying the filesize. // if (Scb->Header.FileSize.QuadPart != OldAttributeSizes.FileSize) { NewAttributeSizes.FileSize = Scb->Header.FileSize.QuadPart; UpdateMft = TRUE; } if (!FlagOn( Scb->Fcb->FcbState, FCB_STATE_PAGING_FILE )) { NtfsReleaseFsrtlHeader(Scb); } // // If this is compressed then check if totally allocated has changed. // if (CompressedStream) { LogRecordSize = SIZEOF_FULL_ATTRIBUTE_SIZES; if (Scb->TotalAllocated != OldAttributeSizes.TotalAllocated) { NewAttributeSizes.TotalAllocated = Scb->TotalAllocated; UpdateMft = TRUE; } } // // Finally, update the allocated length from the Scb if it is different. // if (Scb->Header.AllocationSize.QuadPart != AttributeHeader->Form.Nonresident.AllocatedLength) { NewAttributeSizes.AllocationSize = Scb->Header.AllocationSize.QuadPart; UpdateMft = TRUE; } // // Continue on if we need to update the Mft. // if (UpdateMft) { // // Pin the attribute. // NtfsPinMappedAttribute( IrpContext, Scb->Vcb, &AttrContext ); AttributeHeader = NtfsFoundAttribute( &AttrContext ); if (NewAttributeSizes.ValidDataLength > NewAttributeSizes.FileSize) { // ASSERT(XxLeq(NewAttributeSizes.ValidDataLength,NewAttributeSizes.FileSize)); NewAttributeSizes.ValidDataLength = NewAttributeSizes.FileSize; } ASSERT(NewAttributeSizes.FileSize <= NewAttributeSizes.AllocationSize); ASSERT(NewAttributeSizes.ValidDataLength <= NewAttributeSizes.AllocationSize); // // Log this change to the attribute header. // if (LogIt) { FileRecord->Lsn = NtfsWriteLog( IrpContext, Scb->Vcb->MftScb, NtfsFoundBcb( &AttrContext ), SetNewAttributeSizes, &NewAttributeSizes, LogRecordSize, SetNewAttributeSizes, &OldAttributeSizes, LogRecordSize, NtfsMftOffset( &AttrContext ), PtrOffset( FileRecord, AttributeHeader ), 0, Scb->Vcb->BytesPerFileRecordSegment ); } else { CcSetDirtyPinnedData( NtfsFoundBcb( &AttrContext ), NULL ); } AttributeHeader->Form.Nonresident.AllocatedLength = NewAttributeSizes.AllocationSize; AttributeHeader->Form.Nonresident.FileSize = NewAttributeSizes.FileSize; AttributeHeader->Form.Nonresident.ValidDataLength = NewAttributeSizes.ValidDataLength; // // Don't modify the total allocated field unless there is an actual field for it. // if (CompressedStream && ((AttributeHeader->NameOffset >= SIZEOF_FULL_NONRES_ATTR_HEADER) || ((AttributeHeader->NameOffset == 0) && (AttributeHeader->Form.Nonresident.MappingPairsOffset >= SIZEOF_FULL_NONRES_ATTR_HEADER)))) { AttributeHeader->Form.Nonresident.TotalAllocated = NewAttributeSizes.TotalAllocated; } } try_exit: NOTHING; } finally { DebugUnwind( NtfsWriteFileSizes ); // // Cleanup the attribute context. // NtfsCleanupAttributeContext( &AttrContext ); DebugTrace( -1, Dbg, ("NtfsWriteFileSizes: Exit\n") ); } return; } VOID NtfsUpdateStandardInformation ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb ) /*++ Routine Description: This routine is called to update the standard information attribute for a file from the information in the Fcb. The fields being modified are the time fields and the file attributes. Arguments: Fcb - Fcb for the file to modify. Return Value: None --*/ { ATTRIBUTE_ENUMERATION_CONTEXT AttrContext; STANDARD_INFORMATION StandardInformation; ULONG Length; PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsUpdateStandardInformation: Entered\n") ); // // Use a try-finally to cleanup the attribute context. // try { // // Initialize the context structure. // NtfsInitializeAttributeContext( &AttrContext ); // // Locate the standard information, it must be there. // if (!NtfsLookupAttributeByCode( IrpContext, Fcb, &Fcb->FileReference, $STANDARD_INFORMATION, &AttrContext )) { DebugTrace( 0, Dbg, ("Can't find standard information\n") ); NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb ); } #ifdef _CAIRO_ Length = NtfsFoundAttribute( &AttrContext )->Form.Resident.ValueLength; // // Copy the existing standard information to our buffer. // RtlCopyMemory( &StandardInformation, NtfsAttributeValue( NtfsFoundAttribute( &AttrContext )), Length); #else // // Copy the existing standard information to our buffer. // RtlCopyMemory( &StandardInformation, NtfsAttributeValue( NtfsFoundAttribute( &AttrContext )), sizeof( STANDARD_INFORMATION )); #endif // // Since we are updating standard information, make sure the last // access time is up-to-date. // if (Fcb->Info.LastAccessTime != Fcb->CurrentLastAccess) { Fcb->Info.LastAccessTime = Fcb->CurrentLastAccess; SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_ACCESS ); } // // Change the relevant time fields. // StandardInformation.CreationTime = Fcb->Info.CreationTime; StandardInformation.LastModificationTime = Fcb->Info.LastModificationTime; StandardInformation.LastChangeTime = Fcb->Info.LastChangeTime; StandardInformation.LastAccessTime = Fcb->Info.LastAccessTime; StandardInformation.FileAttributes = Fcb->Info.FileAttributes; // // We clear the directory bit. // ClearFlag( StandardInformation.FileAttributes, DUP_FILE_NAME_INDEX_PRESENT ); #ifdef _CAIRO_ // Fill in the new fields if necessary. if (FlagOn(Fcb->FcbState, FCB_STATE_LARGE_STD_INFO)) { StandardInformation.ClassId = Fcb->ClassId; StandardInformation.OwnerId = Fcb->OwnerId; StandardInformation.SecurityId = Fcb->SecurityId; StandardInformation.Usn = Fcb->Usn; } // // Call to change the attribute value. // NtfsChangeAttributeValue( IrpContext, Fcb, 0, &StandardInformation, Length, FALSE, FALSE, FALSE, FALSE, &AttrContext ); #else // // Call to change the attribute value. // NtfsChangeAttributeValue( IrpContext, Fcb, 0, &StandardInformation, sizeof( STANDARD_INFORMATION ), FALSE, FALSE, FALSE, FALSE, &AttrContext ); #endif } finally { DebugUnwind( NtfsUpdateStandadInformation ); NtfsCleanupAttributeContext( &AttrContext ); DebugTrace( -1, Dbg, ("NtfsUpdateStandardInformation: Exit\n") ); } return; } #ifdef _CAIRO_ VOID NtfsGrowStandardInformation ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb ) /*++ Routine Description: This routine is called to grow and update the standard information attribute for a file from the information in the Fcb. Arguments: Fcb - Fcb for the file to modify. Return Value: None --*/ { ATTRIBUTE_ENUMERATION_CONTEXT AttrContext; STANDARD_INFORMATION StandardInformation; PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsGrowStandardInformation: Entered\n") ); // // Use a try-finally to cleanup the attribute context. // try { // // Initialize the context structure. // NtfsInitializeAttributeContext( &AttrContext ); // // Locate the standard information, it must be there. // if (!NtfsLookupAttributeByCode( IrpContext, Fcb, &Fcb->FileReference, $STANDARD_INFORMATION, &AttrContext )) { DebugTrace( 0, Dbg, ("Can't find standard information\n") ); NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb ); } if (NtfsFoundAttribute( &AttrContext )->Form.Resident.ValueLength == SIZEOF_OLD_STANDARD_INFORMATION) { // // Copy the existing standard information to our buffer. // RtlCopyMemory( &StandardInformation, NtfsAttributeValue( NtfsFoundAttribute( &AttrContext )), SIZEOF_OLD_STANDARD_INFORMATION); RtlZeroMemory((PCHAR) &StandardInformation + SIZEOF_OLD_STANDARD_INFORMATION, sizeof( STANDARD_INFORMATION) - SIZEOF_OLD_STANDARD_INFORMATION); } // // Since we are updating standard information, make sure the last // access time is up-to-date. // if (Fcb->Info.LastAccessTime != Fcb->CurrentLastAccess) { Fcb->Info.LastAccessTime = Fcb->CurrentLastAccess; SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_ACCESS ); } // // Change the relevant time fields. // StandardInformation.CreationTime = Fcb->Info.CreationTime; StandardInformation.LastModificationTime = Fcb->Info.LastModificationTime; StandardInformation.LastChangeTime = Fcb->Info.LastChangeTime; StandardInformation.LastAccessTime = Fcb->Info.LastAccessTime; StandardInformation.FileAttributes = Fcb->Info.FileAttributes; // // We clear the directory bit. // ClearFlag( StandardInformation.FileAttributes, DUP_FILE_NAME_INDEX_PRESENT ); // // Fill in the new fields. // StandardInformation.ClassId = Fcb->ClassId; StandardInformation.OwnerId = Fcb->OwnerId; StandardInformation.SecurityId = Fcb->SecurityId; StandardInformation.Usn = Fcb->Usn; // // Call to change the attribute value. // NtfsChangeAttributeValue( IrpContext, Fcb, 0, &StandardInformation, sizeof( STANDARD_INFORMATION), TRUE, FALSE, FALSE, FALSE, &AttrContext ); ClearFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO ); SetFlag( Fcb->FcbState, FCB_STATE_LARGE_STD_INFO ); } finally { DebugUnwind( NtfsGrowStandadInformation ); NtfsCleanupAttributeContext( &AttrContext ); DebugTrace( -1, Dbg, ("NtfsGrowStandardInformation: Exit\n") ); } return; } #endif BOOLEAN NtfsLookupEntry ( IN PIRP_CONTEXT IrpContext, IN PSCB ParentScb, IN BOOLEAN IgnoreCase, IN OUT PUNICODE_STRING Name, IN OUT PFILE_NAME *FileNameAttr, IN OUT PUSHORT FileNameAttrLength, OUT PQUICK_INDEX QuickIndex OPTIONAL, OUT PINDEX_ENTRY *IndexEntry, OUT PBCB *IndexEntryBcb ) /*++ Routine Description: This routine is called to look up a particular file name in a directory. It takes a single component name and a parent Scb to search in. To do the search, we need to construct a FILE_NAME attribute. We use a reusable buffer to do this, to avoid constantly allocating and deallocating pool. We try to keep this larger than we will ever need. When we find a match on disk, we copy over the name we were called with so we have a record of the actual case on the disk. In this way we can be case perserving. Arguments: ParentScb - This is the Scb for the parent directory. IgnoreCase - Indicates if we should ignore case while searching through the index. Name - This is the path component to search for. We will overwrite this in place if a match is found. FileNameAttr - Address of the buffer we will use to create the file name attribute. We will free this buffer and allocate a new buffer if needed. FileNameAttrLength - This is the length of the FileNameAttr buffer above. QuickIndex - If specified, supplies a pointer to a quik lookup structure to be updated by this routine. IndexEntry - Address to store the cache address of the matching entry. IndexEntryBcb - Address to store the Bcb for the IndexEntry above. Return Value: BOOLEAN - TRUE if a match was found, FALSE otherwise. --*/ { BOOLEAN FoundEntry; USHORT Size; PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsLookupEntry: Entered\n") ); // // We compute the size of the buffer needed to build the filename // attribute. If the current buffer is too small we deallocate it // and allocate a new one. We always allocate twice the size we // need in order to minimize the number of allocations. // Size = (USHORT)(sizeof( FILE_NAME ) + Name->Length - sizeof(WCHAR)); if (Size > *FileNameAttrLength) { if (*FileNameAttr != NULL) { DebugTrace( 0, Dbg, ("Deallocating previous file name attribute buffer\n") ); NtfsFreePool( *FileNameAttr ); *FileNameAttr = NULL; } *FileNameAttr = NtfsAllocatePool(PagedPool, Size << 1 ); *FileNameAttrLength = Size << 1; } // // We build the filename attribute. If this operation is ignore case, // we upcase the expression in the filename attribute. // NtfsBuildFileNameAttribute( IrpContext, &ParentScb->Fcb->FileReference, *Name, 0, *FileNameAttr ); // // Now we call the index routine to perform the search. // FoundEntry = NtfsFindIndexEntry( IrpContext, ParentScb, *FileNameAttr, IgnoreCase, QuickIndex, IndexEntryBcb, IndexEntry ); // // We always restore the name in the filename attribute to the original // name in case we upcased it in the lookup. // if (IgnoreCase) { RtlCopyMemory( (*FileNameAttr)->FileName, Name->Buffer, Name->Length ); } DebugTrace( +1, Dbg, ("NtfsLookupEntry: Exit -> %04x\n", FoundEntry) ); return FoundEntry; } VOID NtfsCreateAttributeWithValue ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN ATTRIBUTE_TYPE_CODE AttributeTypeCode, IN PUNICODE_STRING AttributeName OPTIONAL, IN PVOID Value OPTIONAL, IN ULONG ValueLength, IN USHORT AttributeFlags, IN PFILE_REFERENCE WhereIndexed OPTIONAL, IN BOOLEAN LogIt, OUT PATTRIBUTE_ENUMERATION_CONTEXT Context ) /*++ Routine Description: This routine creates the specified attribute with the specified value, and returns a description of it via the attribute context. If no value is specified, then the attribute is created with the specified number of zero bytes. On successful return, it is up to the caller to clean up the attribute context. Arguments: Fcb - Current file. AttributeTypeCode - Type code of the attribute to create. AttributeName - Optional name for attribute. Value - Pointer to the buffer containing the desired attribute value, or a NULL if zeros are desired. ValueLength - Length of value in bytes. AttributeFlags - Desired flags for the created attribute. WhereIndexed - Optionally supplies the file reference to the file where this attribute is indexed. 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. Context - A handle to the created attribute. This must be cleaned up upon return. Callers who may have made an attribute nonresident may not count on accessing the created attribute via this context upon return. Return Value: None. --*/ { UCHAR AttributeBuffer[SIZEOF_FULL_NONRES_ATTR_HEADER]; ULONG RecordOffset; PATTRIBUTE_RECORD_HEADER Attribute; PFILE_RECORD_SEGMENT_HEADER FileRecord; ULONG SizeNeeded; ULONG AttrSizeNeeded; PVCB Vcb; ULONG Passes = 0; ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_FCB( Fcb ); PAGED_CODE(); ASSERT( (AttributeFlags == 0) || (AttributeTypeCode == $INDEX_ROOT) || NtfsIsTypeCodeCompressible( AttributeTypeCode )); Vcb = Fcb->Vcb; DebugTrace( +1, Dbg, ("NtfsCreateAttributeWithValue\n") ); DebugTrace( 0, Dbg, ("Value = %08lx\n", Value) ); DebugTrace( 0, Dbg, ("ValueLength = %08lx\n", ValueLength) ); // // Clear out the invalid attribute flags for this volume. // AttributeFlags &= Vcb->AttributeFlagsMask; // // Calculate the size needed for this attribute // SizeNeeded = SIZEOF_RESIDENT_ATTRIBUTE_HEADER + QuadAlign( ValueLength ) + (ARGUMENT_PRESENT( AttributeName ) ? QuadAlign( AttributeName->Length ) : 0); // // Loop until we find all the space we need. // do { // // Reinitialize context if this is not the first pass. // if (Passes != 0) { NtfsCleanupAttributeContext( Context ); NtfsInitializeAttributeContext( Context ); } Passes += 1; ASSERT( Passes < 5 ); // // If the attribute is not indexed, then we will position to the // insertion point by type code and name. // if (!ARGUMENT_PRESENT( WhereIndexed )) { if (NtfsLookupAttributeByName( IrpContext, Fcb, &Fcb->FileReference, AttributeTypeCode, AttributeName, NULL, FALSE, Context )) { DebugTrace( 0, 0, ("Nonindexed attribute already exists, TypeCode = %08lx\n", AttributeTypeCode )); ASSERTMSG("Nonindexed attribute already exists, About to bugcheck ", FALSE); NtfsBugCheck( AttributeTypeCode, 0, 0 ); } // // Check here if the attribute needs to be nonresident and if so just // pass this off. // FileRecord = NtfsContainingFileRecord(Context); if ((SizeNeeded > (FileRecord->BytesAvailable - FileRecord->FirstFreeByte)) && (SizeNeeded >= Vcb->BigEnoughToMove) && !FlagOn(NtfsGetAttributeDefinition(Vcb, AttributeTypeCode)->Flags, ATTRIBUTE_DEF_MUST_BE_RESIDENT)) { NtfsCreateNonresidentWithValue( IrpContext, Fcb, AttributeTypeCode, AttributeName, Value, ValueLength, AttributeFlags, FALSE, NULL, LogIt, Context ); return; } // // Otherwise, if the attribute is indexed, then we position by the // attribute value. // } else { ASSERT(ARGUMENT_PRESENT(Value)); if (NtfsLookupAttributeByValue( IrpContext, Fcb, &Fcb->FileReference, AttributeTypeCode, Value, ValueLength, Context )) { DebugTrace( 0, 0, ("Indexed attribute already exists, TypeCode = %08lx\n", AttributeTypeCode )); ASSERTMSG("Indexed attribute already exists, About to bugcheck ", FALSE); NtfsBugCheck( AttributeTypeCode, 0, 0 ); } } // // If this attribute is being positioned in the base file record and // there is an attribute list then we need to ask for enough space // for the attribute list entry now. // FileRecord = NtfsContainingFileRecord( Context ); Attribute = NtfsFoundAttribute( Context ); AttrSizeNeeded = SizeNeeded; if (Context->AttributeList.Bcb != NULL && (ULONG) FileRecord <= (ULONG) Context->AttributeList.AttributeList && (ULONG) Attribute >= (ULONG) Context->AttributeList.AttributeList) { // // If the attribute list is non-resident then add a fudge factor of // 16 bytes for any new retrieval information. // if (NtfsIsAttributeResident( Context->AttributeList.AttributeList )) { AttrSizeNeeded += QuadAlign( FIELD_OFFSET( ATTRIBUTE_LIST_ENTRY, AttributeName ) + (ARGUMENT_PRESENT( AttributeName ) ? (ULONG) AttributeName->Length : sizeof( WCHAR ))); } else { AttrSizeNeeded += 0x10; } } // // Ask for the space we need. // } while (!NtfsGetSpaceForAttribute( IrpContext, Fcb, AttrSizeNeeded, Context )); // // Now point to the file record and calculate the record offset where // our attribute will go. And point to our local buffer. // RecordOffset = (PCHAR)NtfsFoundAttribute(Context) - (PCHAR)FileRecord; Attribute = (PATTRIBUTE_RECORD_HEADER)AttributeBuffer; RtlZeroMemory( Attribute, SIZEOF_RESIDENT_ATTRIBUTE_HEADER ); Attribute->TypeCode = AttributeTypeCode; Attribute->RecordLength = SizeNeeded; Attribute->FormCode = RESIDENT_FORM; if (ARGUMENT_PRESENT(AttributeName)) { ASSERT( AttributeName->Length <= 0x1FF ); Attribute->NameLength = (UCHAR)(AttributeName->Length / sizeof(WCHAR)); Attribute->NameOffset = (USHORT)SIZEOF_RESIDENT_ATTRIBUTE_HEADER; } Attribute->Flags = AttributeFlags; Attribute->Instance = FileRecord->NextAttributeInstance; Attribute->Form.Resident.ValueLength = ValueLength; Attribute->Form.Resident.ValueOffset = (USHORT)(SIZEOF_RESIDENT_ATTRIBUTE_HEADER + QuadAlign( Attribute->NameLength << 1) ); // // If this attribute is indexed, then we have to set the right flag // and update the file record reference count. // if (ARGUMENT_PRESENT(WhereIndexed)) { Attribute->Form.Resident.ResidentFlags = RESIDENT_FORM_INDEXED; } // // Now we will actually create the attribute in place, so that we // save copying everything twice, and can point to the final image // for the log write below. // NtfsRestartInsertAttribute( IrpContext, FileRecord, RecordOffset, Attribute, AttributeName, Value, ValueLength ); // // Finally, log the creation of this attribute // if (LogIt) { // // We have actually created the attribute above, but the write // log below could fail. The reason we did the create already // was to avoid having to allocate pool and copy everything // twice (header, name and value). Our normal error recovery // just recovers from the log file. But if we fail to write // the log, we have to remove this attribute by hand, and // raise the condition again. // try { FileRecord->Lsn = NtfsWriteLog( IrpContext, Vcb->MftScb, NtfsFoundBcb(Context), CreateAttribute, Add2Ptr(FileRecord, RecordOffset), Attribute->RecordLength, DeleteAttribute, NULL, 0, NtfsMftOffset( Context ), RecordOffset, 0, Vcb->BytesPerFileRecordSegment ); } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) { NtfsRestartRemoveAttribute( IrpContext, FileRecord, RecordOffset ); NtfsRaiseStatus( IrpContext, GetExceptionCode(), NULL, NULL ); } } // // Now add it to the attribute list if necessary // if (Context->AttributeList.Bcb != NULL) { MFT_SEGMENT_REFERENCE SegmentReference; *(PLONGLONG)&SegmentReference = LlFileRecordsFromBytes( Vcb, NtfsMftOffset( Context )); SegmentReference.SequenceNumber = FileRecord->SequenceNumber; NtfsAddToAttributeList( IrpContext, Fcb, SegmentReference, Context ); } DebugTrace( -1, Dbg, ("NtfsCreateAttributeWithValue -> VOID\n") ); return; } VOID NtfsCreateNonresidentWithValue ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN ATTRIBUTE_TYPE_CODE AttributeTypeCode, IN PUNICODE_STRING AttributeName OPTIONAL, IN PVOID Value OPTIONAL, IN ULONG ValueLength, IN USHORT AttributeFlags, IN BOOLEAN WriteClusters, IN PSCB ThisScb OPTIONAL, IN BOOLEAN LogIt, IN PATTRIBUTE_ENUMERATION_CONTEXT Context ) /*++ Routine Description: This routine creates the specified nonresident attribute with the specified value, and returns a description of it via the attribute context. If no value is specified, then the attribute is created with the specified number of zero bytes. On successful return, it is up to the caller to clean up the attribute context. Arguments: Fcb - Current file. AttributeTypeCode - Type code of the attribute to create. AttributeName - Optional name for attribute. Value - Pointer to the buffer containing the desired attribute value, or a NULL if zeros are desired. ValueLength - Length of value in bytes. AttributeFlags - Desired flags for the created attribute. WriteClusters - if supplied as TRUE, then we cannot write the data into the cache but must write the clusters directly to the disk. The value buffer in this case must be quad-aligned and a multiple of cluster size in size. If TRUE it also means we are being called during the NtfsConvertToNonresident path. We need to set a flag in the Scb in that case. ThisScb - If present, this is the Scb to use for the create. It also indicates that this call is from convert to non-resident. 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. Context - This is the location to create the new attribute. Return Value: None. --*/ { PSCB Scb; BOOLEAN ReturnedExistingScb; UNICODE_STRING LocalName; PVCB Vcb = Fcb->Vcb; BOOLEAN LogNonresidentToo; BOOLEAN AdvanceOnly; PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsCreateNonresidentWithValue\n") ); AdvanceOnly = LogNonresidentToo = BooleanFlagOn(NtfsGetAttributeDefinition(Vcb, AttributeTypeCode)->Flags, ATTRIBUTE_DEF_LOG_NONRESIDENT); ASSERT( (AttributeFlags == 0) || NtfsIsTypeCodeCompressible( AttributeTypeCode )); // // Clear out the invalid attribute flags for this volume. // AttributeFlags &= Vcb->AttributeFlagsMask; if (ARGUMENT_PRESENT(AttributeName)) { LocalName = *AttributeName; } else { LocalName.Length = LocalName.MaximumLength = 0; LocalName.Buffer = NULL; } if (ARGUMENT_PRESENT( ThisScb )) { Scb = ThisScb; ReturnedExistingScb = TRUE; } else { Scb = NtfsCreateScb( IrpContext, Fcb, AttributeTypeCode, &LocalName, FALSE, &ReturnedExistingScb ); // // An attribute has gone away but the Scb hasn't left yet. // Also mark the header as unitialized. // ClearFlag( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED | SCB_STATE_ATTRIBUTE_RESIDENT | SCB_STATE_FILE_SIZE_LOADED ); // // Set a flag in the Scb to indicate that we are converting to non-resident. // if (WriteClusters) { SetFlag( Scb->ScbState, SCB_STATE_CONVERT_UNDERWAY ); } } // // Allocate the record for the size we need. // NtfsAllocateAttribute( IrpContext, Scb, AttributeTypeCode, AttributeName, AttributeFlags, TRUE, LogIt, (LONGLONG)ValueLength, Context ); NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL ); SetFlag( Scb->ScbState, SCB_STATE_TRUNCATE_ON_CLOSE ); // // We need to be careful here, if this call is due to MM creating a // section, we don't want to call into the cache manager or we // will deadlock on the create section call. // if (!WriteClusters && !ARGUMENT_PRESENT( ThisScb )) { // // This call will initialize a stream for use below. // NtfsCreateInternalAttributeStream( IrpContext, Scb, TRUE ); } // // Now, write in the data. // Scb->Header.FileSize.QuadPart = ValueLength; if ((ARGUMENT_PRESENT( Value )) && (ValueLength != 0)) { if (LogNonresidentToo || !WriteClusters) { ULONG BytesThisPage; PVOID Buffer; PBCB Bcb = NULL; LONGLONG CurrentFileOffset = 0; ULONG RemainingBytes = ValueLength; PVOID CurrentValue = Value; // // While there is more to write, pin the next page and // write a log record. // try { CC_FILE_SIZES FileSizes; // // Call the Cache Manager to truncate and reestablish the FileSize, // so that we are guaranteed to get a valid data length call when // the data goes out. Otherwise he will likely think he does not // have to call us. // RtlCopyMemory( &FileSizes, &Scb->Header.AllocationSize, sizeof( CC_FILE_SIZES )); FileSizes.FileSize.QuadPart = 0; CcSetFileSizes( Scb->FileObject, &FileSizes ); CcSetFileSizes( Scb->FileObject, (PCC_FILE_SIZES)&Scb->Header.AllocationSize ); while (RemainingBytes) { BytesThisPage = (RemainingBytes < PAGE_SIZE ? RemainingBytes : PAGE_SIZE); NtfsUnpinBcb( &Bcb ); NtfsPinStream( IrpContext, Scb, CurrentFileOffset, BytesThisPage, &Bcb, &Buffer ); if (ARGUMENT_PRESENT(ThisScb)) { // // Set the address range modified so that the data will get // written to its new "home". // MmSetAddressRangeModified( Buffer, BytesThisPage ); } else { RtlCopyMemory( Buffer, CurrentValue, BytesThisPage ); } if (LogNonresidentToo) { (VOID) NtfsWriteLog( IrpContext, Scb, Bcb, UpdateNonresidentValue, Buffer, BytesThisPage, Noop, NULL, 0, CurrentFileOffset, 0, 0, BytesThisPage ); } else { CcSetDirtyPinnedData( Bcb, NULL ); } RemainingBytes -= BytesThisPage; CurrentValue = (PVOID) Add2Ptr( CurrentValue, BytesThisPage ); (ULONG)CurrentFileOffset += BytesThisPage; } } finally { NtfsUnpinBcb( &Bcb ); } } else { // // We are going to write the old data directly to disk. // NtfsWriteClusters( IrpContext, Vcb, Scb, (LONGLONG)0, Value, ClustersFromBytes( Vcb, ValueLength )); // // Be sure to note that the data is actually on disk. // AdvanceOnly = TRUE; } } // // We need to maintain the file size and valid data length in the // Scb and attribute record. For this attribute, the valid data // size and the file size are now the value length. // Scb->Header.ValidDataLength = Scb->Header.FileSize; NtfsWriteFileSizes( IrpContext, Scb, &Scb->Header.ValidDataLength.QuadPart, AdvanceOnly, LogIt ); if (!WriteClusters) { // // Let the cache manager know the new size for this attribute. // CcSetFileSizes( Scb->FileObject, (PCC_FILE_SIZES)&Scb->Header.AllocationSize ); } // // If this is the unnamed data attribute, we need to mark this // change in the Fcb. // if (FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) { Fcb->Info.AllocatedLength = Scb->TotalAllocated; Fcb->Info.FileSize = Scb->Header.FileSize.QuadPart; SetFlag( Fcb->InfoFlags, (FCB_INFO_CHANGED_ALLOC_SIZE | FCB_INFO_CHANGED_FILE_SIZE) ); } DebugTrace( -1, Dbg, ("NtfsCreateNonresidentWithValue -> VOID\n") ); } VOID NtfsMapAttributeValue ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, OUT PVOID *Buffer, OUT PULONG Length, OUT PBCB *Bcb, IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context ) /*++ Routine Description: This routine may be called to map an entire attribute value. It works whether the attribute is resident or nonresident. It is intended for general handling of system-defined attributes which are small to medium in size, i.e. 0-64KB. This routine will not work for attributes larger than the Cache Manager's virtual address granularity (currently 256KB), and this will be detected by the Cache Manager who will raise an error. Note that this routine only maps the data for read-only access. To modify the data, the caller must call NtfsChangeAttributeValue AFTER UNPINNING THE BCB (IF THE SIZE IS CHANGING) returned from this routine. Arguments: Fcb - Current file. Buffer - returns a pointer to the mapped attribute value. Length - returns the attribute value length in bytes. Bcb - Returns a Bcb which must be unpinned when done with the data, and before modifying the attribute value with a size change. Context - Attribute Context positioned at the attribute to change. Return Value: None. --*/ { PATTRIBUTE_RECORD_HEADER Attribute; PSCB Scb; UNICODE_STRING AttributeName; BOOLEAN ReturnedExistingScb; ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_FCB( Fcb ); PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsMapAttributeValue\n") ); DebugTrace( 0, Dbg, ("Fcb = %08lx\n", Fcb) ); DebugTrace( 0, Dbg, ("Context = %08lx\n", Context) ); Attribute = NtfsFoundAttribute(Context); // // For the resident case, everything we need is in the // attribute enumeration context. // if (NtfsIsAttributeResident(Attribute)) { *Buffer = NtfsAttributeValue(Attribute); *Length = Attribute->Form.Resident.ValueLength; *Bcb = NtfsFoundBcb(Context); NtfsFoundBcb(Context) = NULL; DebugTrace( 0, Dbg, ("Buffer < %08lx\n", *Buffer) ); DebugTrace( 0, Dbg, ("Length < %08lx\n", *Length) ); DebugTrace( 0, Dbg, ("Bcb < %08lx\n", *Bcb) ); DebugTrace( -1, Dbg, ("NtfsMapAttributeValue -> VOID\n") ); return; } // // Otherwise, this is a nonresident attribute. First create // the Scb and stream. Note we do not use any try-finally // around this because we currently expect cleanup to get // rid of these streams. // NtfsInitializeStringFromAttribute( &AttributeName, Attribute ); Scb = NtfsCreateScb( IrpContext, Fcb, Attribute->TypeCode, &AttributeName, FALSE, &ReturnedExistingScb ); if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) { NtfsUpdateScbFromAttribute( IrpContext, Scb, Attribute ); } NtfsCreateInternalAttributeStream( IrpContext, Scb, FALSE ); // // Now just try to map the whole thing. Count on the Cache Manager // to complain if the attribute is too big to map all at once. // NtfsMapStream( IrpContext, Scb, (LONGLONG)0, ((ULONG)Attribute->Form.Nonresident.FileSize), Bcb, Buffer ); *Length = ((ULONG)Attribute->Form.Nonresident.FileSize); DebugTrace( 0, Dbg, ("Buffer < %08lx\n", *Buffer) ); DebugTrace( 0, Dbg, ("Length < %08lx\n", *Length) ); DebugTrace( 0, Dbg, ("Bcb < %08lx\n", *Bcb) ); DebugTrace( -1, Dbg, ("NtfsMapAttributeValue -> VOID\n") ); } VOID NtfsChangeAttributeValue ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN ULONG ValueOffset, IN PVOID Value OPTIONAL, IN ULONG ValueLength, IN BOOLEAN SetNewLength, IN BOOLEAN LogNonresidentToo, IN BOOLEAN CreateSectionUnderway, IN BOOLEAN PreserveContext, IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context ) /*++ Routine Description: This routine changes the value of the specified attribute, optionally changing its size. The caller specifies the attribute to be changed via the attribute context, and must be prepared to clean up this context no matter how this routine returns. There are three byte ranges of interest for this routine. The first is existing bytes to be perserved at the beginning of the attribute. It begins a byte 0 and extends to the point where the attribute is being changed or the current end of the attribute, which ever is smaller. The second is the range of bytes which needs to be zeroed if the modified bytes begin past the current end of the file. This range will be of length 0 if the modified range begins within the current range of bytes for the attribute. The final range is the modified byte range. This is zeroed if no value pointer was specified. Ranges of zero bytes at the end of the attribute can be represented in non-resident attributes by a valid data length set to the beginning of what would be zero bytes. The following pictures illustrates these ranges when we writing data beyond the current end of the file. Current attribute ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ Value VVVVVVVVVVVVVVVVVVVVVVVV Byte range to save ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ Byte range to zero 0000 Resulting attribute ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ0000VVVVVVVVVVVVVVVVVVVVVVVV The following picture illustrates these ranges when we writing data which begins at or before the current end of the file. Current attribute ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ Value VVVVVVVVVVVVVVVVVVVVVVVV Byte range to save ZZZZZZZZZZZZZZZZZZZZZZZZZZZZ Byte range to zero (None) Resulting attribute ZZZZZZZZZZZZZZZZZZZZZZZZZZZZVVVVVVVVVVVVVVVVVVVVVVVV The following picture illustrates these ranges when we writing data totally within the current range of the file without setting a new size. Current attribute ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ Value VVVVVVVVVVVVVVVVVVVVVVVV Byte range to save (Save the whole range and then write over it) ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ Byte range to zero (None) Resulting attribute ZZZZVVVVVVVVVVVVVVVVVVVVVVVVZZZZ The following picture illustrates these ranges when we writing data totally within the current range of the file while setting a new size. Current attribute ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ Value VVVVVVVVVVVVVVVVVVVVVVVV Byte range to save (Only save the beginning) ZZZZ Byte range to zero (None) Resulting attribute ZZZZVVVVVVVVVVVVVVVVVVVVVVVV Any of the 'V' values above will be replaced by zeroes if the 'Value' parameter is not passed in. Arguments: Fcb - Current file. ValueOffset - Byte offset within the attribute at which the value change is to begin. Value - Pointer to the buffer containing the new value, if present. Otherwise zeroes are desired. ValueLength - Length of the value in the above buffer. SetNewLength - FALSE if the size of the value is not changing, or TRUE if the value length should be changed to ValueOffset + ValueLength. LogNonresidentToo - supplies TRUE if the update should be logged even if the attribute is nonresident (such as for the SECURITY_DESCRIPTOR). CreateSectionUnderway - if supplied as TRUE, then to the best of the caller's knowledge, an MM Create Section could be underway, which means that we cannot initiate caching on this attribute, as that could cause deadlock. The value buffer in this case must be quad-aligned and a multiple of cluster size in size. PreserveContext - Indicates if we need to lookup the attribute in case it might move. Context - Attribute Context positioned at the attribute to change. Return Value: None. --*/ { PATTRIBUTE_RECORD_HEADER Attribute; PFILE_RECORD_SEGMENT_HEADER FileRecord; ATTRIBUTE_TYPE_CODE AttributeTypeCode; UNICODE_STRING AttributeName; ULONG NewSize; PVCB Vcb; BOOLEAN ReturnedExistingScb; BOOLEAN GoToNonResident = FALSE; PVOID Buffer; ULONG CurrentLength; LONG SizeChange, QuadSizeChange; ULONG RecordOffset; ULONG ZeroLength = 0; ULONG UnchangedSize = 0; PBCB Bcb = NULL; PSCB Scb = NULL; PVOID SaveBuffer = NULL; PVOID CopyInputBuffer = NULL; WCHAR NameBuffer[8]; UNICODE_STRING SavedName; ATTRIBUTE_TYPE_CODE TypeCode; ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_FCB( Fcb ); Vcb = Fcb->Vcb; PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsChangeAttributeValue\n") ); DebugTrace( 0, Dbg, ("Fcb = %08lx\n", Fcb) ); DebugTrace( 0, Dbg, ("ValueOffset = %08lx\n", ValueOffset) ); DebugTrace( 0, Dbg, ("Value = %08lx\n", Value) ); DebugTrace( 0, Dbg, ("ValueLength = %08lx\n", ValueLength) ); DebugTrace( 0, Dbg, ("SetNewLength = %02lx\n", SetNewLength) ); DebugTrace( 0, Dbg, ("LogNonresidentToo = %02lx\n", LogNonresidentToo) ); DebugTrace( 0, Dbg, ("Context = %08lx\n", Context) ); // // Get the file record and attribute pointers. // FileRecord = NtfsContainingFileRecord(Context); Attribute = NtfsFoundAttribute(Context); TypeCode = Attribute->TypeCode; // // Set up a pointer to the name buffer in case we have to use it. // SavedName.Buffer = NameBuffer; // // Get the current attribute value length. // if (NtfsIsAttributeResident(Attribute)) { CurrentLength = Attribute->Form.Resident.ValueLength; } else { if (((PLARGE_INTEGER)&Attribute->Form.Nonresident.AllocatedLength)->HighPart != 0) { NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb ); } CurrentLength = ((ULONG)Attribute->Form.Nonresident.AllocatedLength); } ASSERT( SetNewLength || ((ValueOffset + ValueLength) <= CurrentLength) ); // // Calculate how much the file record is changing by, and its new // size. We also compute the size of the range of zero bytes. // if (SetNewLength) { NewSize = ValueOffset + ValueLength; SizeChange = NewSize - CurrentLength; QuadSizeChange = QuadAlign( NewSize ) - QuadAlign( CurrentLength ); // // If the new size is large enough, the size change may appear to be negative. // In this case we go directly to the non-resident path. // if (NewSize > Vcb->BytesPerFileRecordSegment) { GoToNonResident = TRUE; } } else { NewSize = CurrentLength; SizeChange = 0; QuadSizeChange = 0; } // // If we are zeroing a range in the file and it extends to the // end of the file or beyond then make this a single zeroed run. // if (!ARGUMENT_PRESENT( Value ) && ValueOffset >= CurrentLength) { ZeroLength = ValueOffset + ValueLength - CurrentLength; ValueOffset = ValueOffset + ValueLength; ValueLength = 0; // // If we are writing data starting beyond the end of the // file then we have a range of bytes to zero. // } else if (ValueOffset > CurrentLength) { ZeroLength = ValueOffset - CurrentLength; } // // At this point we know the following ranges: // // Range to save: Not needed unless going resident to non-resident // // Zero range: From Zero offset for length ZeroLength // // Modified range: From ValueOffset to NewSize, this length may // be zero. // // // If the attribute is resident, and it will stay resident, then we will // handle that case first, and return. // if (NtfsIsAttributeResident( Attribute ) && !GoToNonResident && ((QuadSizeChange <= (LONG)(FileRecord->BytesAvailable - FileRecord->FirstFreeByte)) || ((Attribute->RecordLength + SizeChange) < Vcb->BigEnoughToMove))) { PVOID UndoBuffer; ULONG UndoLength; ULONG AttributeOffset; // // If the attribute record is growing, then we have to get the new space // now. // if (QuadSizeChange > 0) { BOOLEAN FirstPass = TRUE; ASSERT( !FlagOn(Attribute->Form.Resident.ResidentFlags, RESIDENT_FORM_INDEXED) ); // // Save a description of the attribute in case we have to look it up // again. // SavedName.Length = SavedName.MaximumLength = (USHORT)(Attribute->NameLength * sizeof(WCHAR)); if (SavedName.Length > sizeof(NameBuffer)) { SavedName.Buffer = NtfsAllocatePool( NonPagedPool, SavedName.Length ); } // // Copy the name into the buffer. // if (SavedName.Length != 0) { RtlCopyMemory( SavedName.Buffer, Add2Ptr( Attribute, Attribute->NameOffset ), SavedName.Length ); } // // Make sure we deallocate the name buffer. // try { do { // // If not the first pass, we have to lookup the attribute // again. // if (!FirstPass) { BOOLEAN Found; NtfsCleanupAttributeContext( Context ); NtfsInitializeAttributeContext( Context ); Found = NtfsLookupAttributeByName( IrpContext, Fcb, &Fcb->FileReference, TypeCode, &SavedName, NULL, FALSE, Context ); ASSERT(Found); // // Now we have to reload our attribute pointer // Attribute = NtfsFoundAttribute(Context); } FirstPass = FALSE; // // If FALSE is returned, then the space was not allocated and // we have too loop back and try again. Second time must work. // } while (!NtfsChangeAttributeSize( IrpContext, Fcb, QuadAlign( Attribute->Form.Resident.ValueOffset + NewSize), Context )); } finally { if (SavedName.Buffer != NameBuffer) { NtfsFreePool(SavedName.Buffer); } } // // Now we have to reload our attribute pointer // FileRecord = NtfsContainingFileRecord(Context); Attribute = NtfsFoundAttribute(Context); } else { // // Make sure the buffer is pinned if we are not changing size, because // we begin to modify it below. // NtfsPinMappedAttribute( IrpContext, Vcb, Context ); // // We can eliminate some/all of the value if it has not changed. // if (ARGUMENT_PRESENT(Value)) { UnchangedSize = RtlCompareMemory( Add2Ptr(Attribute, Attribute->Form.Resident.ValueOffset + ValueOffset), Value, ValueLength ); Value = Add2Ptr(Value, UnchangedSize); ValueOffset += UnchangedSize; ValueLength -= UnchangedSize; } } RecordOffset = PtrOffset(FileRecord, Attribute); // // If there is a zero range of bytes, deal with it now. // If we are zeroing data then we must be growing the // file. // if (ZeroLength != 0) { // // We always start zeroing at the zeroing offset. // AttributeOffset = Attribute->Form.Resident.ValueOffset + CurrentLength; // // If we are starting at the end of the file the undo // buffer is NULL and the length is zero. // FileRecord->Lsn = NtfsWriteLog( IrpContext, Vcb->MftScb, NtfsFoundBcb(Context), UpdateResidentValue, NULL, ZeroLength, UpdateResidentValue, NULL, 0, NtfsMftOffset( Context ), RecordOffset, AttributeOffset, Vcb->BytesPerFileRecordSegment ); // // Now zero this data by calling the same routine as restart. // NtfsRestartChangeValue( IrpContext, FileRecord, RecordOffset, AttributeOffset, NULL, ZeroLength, TRUE ); } // // Now log the new data for the file. This range will always begin // within the current range of bytes for the file. Because of this // there is an undo action. // // Even if there is not a nonzero ValueLength, we still have to // execute this code if the attribute is being truncated. // The only exception is if we logged some zero data and have // nothing left to log. // if ((ValueLength != 0) || (ZeroLength == 0 && SizeChange != 0)) { // // The attribute offset is always at the value offset. // AttributeOffset = Attribute->Form.Resident.ValueOffset + ValueOffset; // // There are 3 possible cases for the undo action to // log. // // // If we are growing the file starting beyond the end of // the file then undo buffer is NULL and the length is // zero. This will still allow us to shrink the file // on abort. // if (ValueOffset >= CurrentLength) { UndoBuffer = NULL; UndoLength = 0; // // For the other cases the undo buffer begins at the // point of the change. // } else { UndoBuffer = Add2Ptr( Attribute, Attribute->Form.Resident.ValueOffset + ValueOffset ); // // If the size isn't changing then the undo length is the same as // the redo length. // if (SizeChange == 0) { UndoLength = ValueLength; // // Otherwise the length is the range between the end of the // file and the start of the new data. // } else { UndoLength = CurrentLength - ValueOffset; } } FileRecord->Lsn = NtfsWriteLog( IrpContext, Vcb->MftScb, NtfsFoundBcb(Context), UpdateResidentValue, Value, ValueLength, UpdateResidentValue, UndoBuffer, UndoLength, NtfsMftOffset( Context ), RecordOffset, AttributeOffset, Vcb->BytesPerFileRecordSegment ); // // Now update this data by calling the same routine as restart. // NtfsRestartChangeValue( IrpContext, FileRecord, RecordOffset, AttributeOffset, Value, ValueLength, (BOOLEAN)(SizeChange != 0) ); } DebugTrace( -1, Dbg, ("NtfsChangeAttributeValue -> VOID\n") ); return; } // // Nonresident case. Create the Scb and attributestream. // NtfsInitializeStringFromAttribute( &AttributeName, Attribute ); AttributeTypeCode = Attribute->TypeCode; Scb = NtfsCreateScb( IrpContext, Fcb, AttributeTypeCode, &AttributeName, FALSE, &ReturnedExistingScb ); // // Use try-finally for cleanup. // try { BOOLEAN AllocateBufferCopy = FALSE; BOOLEAN DeleteAllocation = FALSE; BOOLEAN LookupAttribute = FALSE; BOOLEAN AdvanceValidData = FALSE; LONGLONG NewValidDataLength; LONGLONG LargeValueOffset; LONGLONG LargeNewSize; if (SetNewLength && NewSize > Scb->Header.FileSize.LowPart && TypeCode == $ATTRIBUTE_LIST) { AllocateBufferCopy = TRUE; } LargeNewSize = NewSize; LargeValueOffset = ValueOffset; // // Well, the attribute is either changing to nonresident, or it is already // nonresident. First we will handle the conversion to nonresident case. // We can detect this case by whether or not the attribute is currently // resident. // if (NtfsIsAttributeResident(Attribute)) { NtfsConvertToNonresident( IrpContext, Fcb, Attribute, CreateSectionUnderway, Context ); // // Reload the attribute pointer from the context. // Attribute = NtfsFoundAttribute( Context ); // // The process of creating a non resident attribute will also create // and initialize a stream file for the Scb. If the file is already // non-resident we also need a stream file. // } else { NtfsCreateInternalAttributeStream( IrpContext, Scb, TRUE ); NtfsExpandQuotaToAllocationSize( IrpContext, Scb ); } // // If the attribute is already nonresident, make sure the allocation // is the right size. We grow it before we log the data to be sure // we have the space for the new data. We shrink it after we log the // new data so we have the old data available for the undo. // if (((PLARGE_INTEGER)&Attribute->Form.Nonresident.AllocatedLength)->HighPart != 0) { NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb ); } if (NewSize > ((ULONG)Attribute->Form.Nonresident.AllocatedLength)) { LONGLONG NewAllocation; if (PreserveContext) { // // Save a description of the attribute in case we have to look it up // again. // SavedName.Length = SavedName.MaximumLength = (USHORT)(Attribute->NameLength * sizeof(WCHAR)); if (SavedName.Length > sizeof(NameBuffer)) { SavedName.Buffer = NtfsAllocatePool( NonPagedPool, SavedName.Length ); } // // Copy the name into the buffer. // if (SavedName.Length != 0) { RtlCopyMemory( SavedName.Buffer, Add2Ptr( Attribute, Attribute->NameOffset ), SavedName.Length ); } LookupAttribute = TRUE; } // // If this is the attribute list then check if we want to allocate a larger block. // This way the attribute list doesn't get too fragmented. // NewAllocation = NewSize - ((ULONG)Attribute->Form.Nonresident.AllocatedLength); if (Scb->AttributeTypeCode == $ATTRIBUTE_LIST) { if ((ULONG) Attribute->Form.Nonresident.AllocatedLength > (4 * PAGE_SIZE)) { NewAllocation = (2 * PAGE_SIZE) + NewSize - ((ULONG)Attribute->Form.Nonresident.AllocatedLength); } else if ((ULONG) Attribute->Form.Nonresident.AllocatedLength > PAGE_SIZE) { NewAllocation = PAGE_SIZE + NewSize - ((ULONG)Attribute->Form.Nonresident.AllocatedLength); } } NtfsAddAllocation( IrpContext, Scb->FileObject, Scb, LlClustersFromBytes( Vcb, Attribute->Form.Nonresident.AllocatedLength ), LlClustersFromBytes( Vcb, NewAllocation ), FALSE); // // AddAllocation will adjust the sizes in the Scb and report // the new size to the cache manager. We need to remember if // we changed the sizes for the unnamed data attribute. // if (FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) { Fcb->Info.AllocatedLength = Scb->TotalAllocated; SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_ALLOC_SIZE ); } } else if (Vcb->BytesPerCluster <= ((ULONG)Attribute->Form.Nonresident.AllocatedLength) - NewSize) { if ((Scb->AttributeTypeCode != $ATTRIBUTE_LIST) || (NewSize * 2 < ((ULONG) Attribute->Form.Nonresident.AllocatedLength))) { DeleteAllocation = TRUE; } } // // Now, write in the data. // if ((ValueLength != 0 && ARGUMENT_PRESENT( Value )) || (LogNonresidentToo && SetNewLength)) { BOOLEAN BytesToUndo; // // We have to compute the amount of data to zero in a different // way than we did for the resident case. For the non-resident // case we need to zero the data between the old valid data // length and the offset in the file for the new data. // if (LargeValueOffset >= Scb->Header.ValidDataLength.QuadPart) { ZeroLength = (ULONG)(LargeValueOffset - Scb->Header.ValidDataLength.QuadPart); BytesToUndo = FALSE; } else { ZeroLength = 0; BytesToUndo = TRUE; } // // Update existing nonresident attribute. (We may have just created it // above.) // // If we are supposed to log it, then pin, log and do the update here. // if (LogNonresidentToo) { // // At this point the attribute is non-resident and contains // its previous value. If the new data lies beyond the // previous valid data, we need to zero this data. This // action won't require any undo. Otherwise the new data // lies within the existing data. In this case we need to // log the previous data for possible undo. Finally, the // tail of the new data may extend beyond the end of the // previous data. There is no undo requirement for these // bytes. // // We do the logging operation in three steps: // // 1 - We find the all the pages in the attribute that // we need to zero any bytes for. There is no // undo for these bytes. // // 2 - Find all pages where we have to perform undo and // log the changes to those pages. Note only // step 1 or step 2 will be performed as they // are mutually exclusive. // // 3 - Finally, we may have pages where the new data // extends beyond the current final page in the // attribute. We log the new data but there is // no undo. // // 4 - We may have pages where the old data extends // beyond the new data. We will log this old // data in the event that we grow and shrink // this attribute several times in the same // transaction (changes to the attribute list). // In this case there is redo but no undo. // LONGLONG CurrentPage; ULONG PageOffset; ULONG ByteCountToUndo; ULONG NewBytesRemaining; // // Find the starting page for this operation. It is the // ValidDataLength rounded down to a page boundary. // CurrentPage = Scb->Header.ValidDataLength.QuadPart; PageOffset = (ULONG)CurrentPage & (PAGE_SIZE - 1); (ULONG)CurrentPage = ((ULONG)CurrentPage & ~(PAGE_SIZE - 1)); // // Loop until there are no more bytes to zero. // while (ZeroLength != 0) { ULONG ZeroBytesThisPage; ZeroBytesThisPage = PAGE_SIZE - PageOffset; if (ZeroBytesThisPage > ZeroLength) { ZeroBytesThisPage = ZeroLength; } // // Pin the desired page and compute a buffer into the // page. Also compute how many bytes we we zero on // this page. // NtfsUnpinBcb( &Bcb ); NtfsPinStream( IrpContext, Scb, CurrentPage, ZeroBytesThisPage + PageOffset, &Bcb, &Buffer ); Buffer = Add2Ptr( Buffer, PageOffset ); // // Now write the zeros into the log. // (VOID) NtfsWriteLog( IrpContext, Scb, Bcb, UpdateNonresidentValue, NULL, ZeroBytesThisPage, Noop, NULL, 0, CurrentPage, PageOffset, 0, ZeroBytesThisPage + PageOffset ); // // Zero any data necessary. // RtlZeroMemory( Buffer, ZeroBytesThisPage ); // // Now move through the file. // ZeroLength -= ZeroBytesThisPage; CurrentPage = CurrentPage + PAGE_SIZE; PageOffset = 0; } // // Find the starting page for this operation. It is the // ValueOffset rounded down to a page boundary. // CurrentPage = LargeValueOffset; (ULONG)CurrentPage = ((ULONG)CurrentPage & ~(PAGE_SIZE - 1)); PageOffset = (ULONG)LargeValueOffset & (PAGE_SIZE - 1); // // Now loop until there are no more pages with undo // bytes to log. // NewBytesRemaining = ValueLength; if (BytesToUndo) { ByteCountToUndo = (ULONG)(Scb->Header.ValidDataLength.QuadPart - LargeValueOffset); // // If we are spanning pages, growing the file and the // input buffer points into the cache, we could lose // data as we cross a page boundary. In that case // we need to allocate a separate buffer. // if (AllocateBufferCopy && NewBytesRemaining + PageOffset > PAGE_SIZE) { CopyInputBuffer = NtfsAllocatePool(PagedPool, NewBytesRemaining ); RtlCopyMemory( CopyInputBuffer, Value, NewBytesRemaining ); Value = CopyInputBuffer; AllocateBufferCopy = FALSE; } // // If we aren't setting a new length then limit the // undo bytes to those being overwritten. // if (!SetNewLength && ByteCountToUndo > NewBytesRemaining) { ByteCountToUndo = NewBytesRemaining; } while (ByteCountToUndo != 0) { ULONG UndoBytesThisPage; ULONG RedoBytesThisPage; ULONG BytesThisPage; NTFS_LOG_OPERATION RedoOperation; PVOID RedoBuffer; // // Also compute the number of bytes of undo and // redo on this page. // RedoBytesThisPage = UndoBytesThisPage = PAGE_SIZE - PageOffset; if (RedoBytesThisPage > NewBytesRemaining) { RedoBytesThisPage = NewBytesRemaining; } if (UndoBytesThisPage >= ByteCountToUndo) { UndoBytesThisPage = ByteCountToUndo; } // // We pin enough bytes on this page to cover both the // redo and undo bytes. // if (UndoBytesThisPage > RedoBytesThisPage) { BytesThisPage = PageOffset + UndoBytesThisPage; } else { BytesThisPage = PageOffset + RedoBytesThisPage; } // // If there is no redo (we are shrinking the data), // then make the redo a noop. // if (RedoBytesThisPage == 0) { RedoOperation = Noop; RedoBuffer = NULL; } else { RedoOperation = UpdateNonresidentValue; RedoBuffer = Value; } // // Now we pin the page and calculate the beginning // buffer in the page. // NtfsUnpinBcb( &Bcb ); NtfsPinStream( IrpContext, Scb, CurrentPage, BytesThisPage, &Bcb, &Buffer ); Buffer = Add2Ptr( Buffer, PageOffset ); // // Now log the changes to this page. // (VOID) NtfsWriteLog( IrpContext, Scb, Bcb, RedoOperation, RedoBuffer, RedoBytesThisPage, UpdateNonresidentValue, Buffer, UndoBytesThisPage, CurrentPage, PageOffset, 0, BytesThisPage ); // // Move the data into place if we have new data. // if (RedoBytesThisPage != 0) { RtlMoveMemory( Buffer, Value, RedoBytesThisPage ); } // // Now decrement the counts and move through the // caller's buffer. // ByteCountToUndo -= UndoBytesThisPage; NewBytesRemaining -= RedoBytesThisPage; CurrentPage = PAGE_SIZE + CurrentPage; PageOffset = 0; Value = Add2Ptr( Value, RedoBytesThisPage ); } } // // Now loop until there are no more pages with new data // to log. // while (NewBytesRemaining != 0) { ULONG RedoBytesThisPage; // // Also compute the number of bytes of redo on this page. // RedoBytesThisPage = PAGE_SIZE - PageOffset; if (RedoBytesThisPage > NewBytesRemaining) { RedoBytesThisPage = NewBytesRemaining; } // // Now we pin the page and calculate the beginning // buffer in the page. // NtfsUnpinBcb( &Bcb ); NtfsPinStream( IrpContext, Scb, CurrentPage, RedoBytesThisPage, &Bcb, &Buffer ); Buffer = Add2Ptr( Buffer, PageOffset ); // // Now log the changes to this page. // (VOID) NtfsWriteLog( IrpContext, Scb, Bcb, UpdateNonresidentValue, Value, RedoBytesThisPage, Noop, NULL, 0, CurrentPage, PageOffset, 0, PageOffset + RedoBytesThisPage ); // // Move the data into place. // RtlMoveMemory( Buffer, Value, RedoBytesThisPage ); // // Now decrement the counts and move through the // caller's buffer. // NewBytesRemaining -= RedoBytesThisPage; CurrentPage = PAGE_SIZE + CurrentPage; PageOffset = 0; Value = Add2Ptr( Value, RedoBytesThisPage ); } // // If we have values to write, we write them to the cache now. // } else { // // If we have data to zero, we do no now. // if (ZeroLength != 0) { if (!NtfsZeroData( IrpContext, Scb, Scb->FileObject, Scb->Header.ValidDataLength.QuadPart, (LONGLONG)ZeroLength )) { NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL ); } } if (!CcCopyWrite( Scb->FileObject, (PLARGE_INTEGER)&LargeValueOffset, ValueLength, BooleanFlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT), Value )) { NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL ); } } // // We need to remember the new valid data length in the // Scb if it is greater than the existing. // NewValidDataLength = LargeValueOffset + ValueLength; if (NewValidDataLength > Scb->Header.ValidDataLength.QuadPart) { Scb->Header.ValidDataLength.QuadPart = NewValidDataLength; // // If we took the log non-resident path, then we // want to advance this on the disk as well. // if (LogNonresidentToo) { AdvanceValidData = TRUE; } SetFlag( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE ); } // // We need to maintain the file size in the Scb. If we grow the // file, we extend the cache file size. We always set the // valid data length in the Scb to the new file size. The // 'AdvanceValidData' boolean and the current size on the // disk will determine if it changes on disk. // if (SetNewLength) { Scb->Header.ValidDataLength.QuadPart = NewValidDataLength; } } if (SetNewLength) { Scb->Header.FileSize.QuadPart = LargeNewSize; if (LogNonresidentToo) { Scb->Header.ValidDataLength.QuadPart = LargeNewSize; } } if (Scb->Header.ValidDataLength.QuadPart < Scb->ValidDataToDisk) { Scb->ValidDataToDisk = Scb->Header.ValidDataLength.QuadPart; } // // If there is allocation to delete, we do so now. // if (DeleteAllocation ) { NtfsDeleteAllocation( IrpContext, Scb->FileObject, Scb, LlClustersFromBytes( Vcb, LargeNewSize ), MAXLONGLONG, TRUE, FALSE ); // // DeleteAllocation will adjust the sizes in the Scb and report // the new size to the cache manager. We need to remember if // we changed the sizes for the unnamed data attribute. // if (FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) { Fcb->Info.AllocatedLength = Scb->TotalAllocated; Fcb->Info.FileSize = Scb->Header.FileSize.QuadPart; SetFlag( Fcb->InfoFlags, (FCB_INFO_CHANGED_ALLOC_SIZE | FCB_INFO_CHANGED_FILE_SIZE) ); } if (AdvanceValidData) { NtfsWriteFileSizes( IrpContext, Scb, &Scb->Header.ValidDataLength.QuadPart, TRUE, TRUE ); } } else if (SetNewLength) { PFILE_OBJECT CacheFileObject = NULL; // // If there is no file object, we will create a stream file // now, // if (Scb->FileObject != NULL) { CacheFileObject = Scb->FileObject; } else if (!CreateSectionUnderway) { NtfsCreateInternalAttributeStream( IrpContext, Scb, FALSE ); CacheFileObject = Scb->FileObject; } else { PIO_STACK_LOCATION IrpSp; IrpSp = IoGetCurrentIrpStackLocation( IrpContext->OriginatingIrp ); if (IrpSp->FileObject->SectionObjectPointer == &Scb->NonpagedScb->SegmentObject) { CacheFileObject = IrpSp->FileObject; } } ASSERT( CacheFileObject != NULL ); CcSetFileSizes( CacheFileObject, (PCC_FILE_SIZES)&Scb->Header.AllocationSize ); // // If this is the unnamed data attribute, we need to mark this // change in the Fcb. // if (FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) { Fcb->Info.FileSize = Scb->Header.FileSize.QuadPart; SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_FILE_SIZE ); } // // Now update the sizes on the disk. // The new sizes will already be in the Scb. // NtfsWriteFileSizes( IrpContext, Scb, &Scb->Header.ValidDataLength.QuadPart, AdvanceValidData, TRUE ); } else if (AdvanceValidData) { NtfsWriteFileSizes( IrpContext, Scb, &Scb->Header.ValidDataLength.QuadPart, TRUE, TRUE ); } // // Look up the attribute again in case it moved. // if (LookupAttribute) { BOOLEAN Found; NtfsCleanupAttributeContext( Context ); NtfsInitializeAttributeContext( Context ); Found = NtfsLookupAttributeByName( IrpContext, Fcb, &Fcb->FileReference, TypeCode, &SavedName, NULL, FALSE, Context ); ASSERT(Found); } } finally { DebugUnwind( NtfsChangeAttributeValue ); if (CopyInputBuffer != NULL) { NtfsFreePool( CopyInputBuffer ); } if (SaveBuffer != NULL) { NtfsFreePool( SaveBuffer ); } NtfsUnpinBcb( &Bcb ); DebugTrace( -1, Dbg, ("NtfsChangeAttributeValue -> VOID\n") ); } } VOID NtfsConvertToNonresident ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN OUT PATTRIBUTE_RECORD_HEADER Attribute, IN BOOLEAN CreateSectionUnderway, IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context OPTIONAL ) /*++ Routine Description: This routine converts a resident attribute to nonresident. It does so by allocating a buffer and copying the data and attribute name away, deleting the attribute, allocating a new attribute of the right size, and then copying the data back out again. Arguments: Fcb - Requested file. Attribute - Supplies a pointer to the attribute to convert. CreateSectionUnderway - if supplied as TRUE, then to the best of the caller's knowledge, an MM Create Section could be underway, which means that we cannot initiate caching on this attribute, as that could cause deadlock. The value buffer in this case must be quad-aligned and a multiple of cluster size in size. Context - An attribute context to look up another attribute in the same file record. If supplied, we insure that the context is valid for converted attribute. Return Value: None --*/ { PVOID Buffer; PVOID AllocatedBuffer = NULL; ULONG AllocatedLength; ULONG AttributeNameOffset; ATTRIBUTE_ENUMERATION_CONTEXT LocalContext; BOOLEAN CleanupLocalContext = FALSE; ATTRIBUTE_TYPE_CODE AttributeTypeCode = Attribute->TypeCode; USHORT AttributeFlags = Attribute->Flags; PVOID AttributeValue = NULL; ULONG ValueLength; UNICODE_STRING AttributeName; WCHAR AttributeNameBuffer[16]; BOOLEAN WriteClusters = CreateSectionUnderway; PBCB ResidentBcb = NULL; PSCB Scb = NULL; #ifdef _CAIRO_ ULONG IrpContextFlags = FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE ); #endif // _CAIRO_ PAGED_CODE(); // // Use a try-finally to facilitate cleanup. // try { // // Build a temporary copy of the name out of the attribute. // AttributeName.MaximumLength = AttributeName.Length = Attribute->NameLength * sizeof( WCHAR ); AttributeName.Buffer = Add2Ptr( Attribute, Attribute->NameOffset ); // // If we don't have an attribute context for this attribute then look it // up now. // if (!ARGUMENT_PRESENT( Context )) { Context = &LocalContext; NtfsInitializeAttributeContext( Context ); CleanupLocalContext = TRUE; // // Lookup the first occurence of this attribute. // if (!NtfsLookupAttributeByName( IrpContext, Fcb, &Fcb->FileReference, AttributeTypeCode, &AttributeName, NULL, FALSE, Context )) { DebugTrace( 0, 0, ("Could not find attribute being converted\n") ); ASSERTMSG("Could not find attribute being converted, About to bugcheck ", FALSE); NtfsBugCheck( AttributeTypeCode, 0, 0 ); } } // // We need to figure out how much pool to allocate. If there is a mapped // view of this section or a section is being created we will allocate a buffer // and copy the data into the buffer. Otherwise we will pin the data in // the cache, mark it dirty and use that buffer to perform the conversion. // AllocatedLength = AttributeName.Length; if (CreateSectionUnderway) { #ifdef _CAIRO_ // // CAIROBUG: The following should be combined with the // code below when the cairo ifdefs are removed. // BOOLEAN ReturnedExistingScb; Scb = NtfsCreateScb( IrpContext, Fcb, AttributeTypeCode, &AttributeName, FALSE, &ReturnedExistingScb ); // // If the quota has been expanded already then the rigth // things will happen below. If the quota has not been // expanded then no quota changes are required since // file size is not changing. // ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE ); if (!FlagOn( Scb->ScbState, SCB_STATE_QUOTA_ENLARGED)) { SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE ); } Scb = NULL; #endif // _CAIRO_ AttributeNameOffset = ClusterAlign( Fcb->Vcb, Attribute->Form.Resident.ValueLength ); AllocatedLength += AttributeNameOffset; ValueLength = Attribute->Form.Resident.ValueLength; } else { BOOLEAN ReturnedExistingScb; Scb = NtfsCreateScb( IrpContext, Fcb, AttributeTypeCode, &AttributeName, FALSE, &ReturnedExistingScb ); // // Make sure the Scb is up-to-date. // NtfsUpdateScbFromAttribute( IrpContext, Scb, Attribute ); #ifdef _CAIRO_ // // If the quota has been expanded already then the rigth // things will happen below. If the quota has not been // expanded then no quota changes are required since // file size is not changing. // ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE ); if (!FlagOn( Scb->ScbState, SCB_STATE_QUOTA_ENLARGED)) { SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE ); } #endif // _CAIRO_ // // Set the flag in the Scb to indicate that we are converting this to // non resident. // SetFlag( Scb->ScbState, SCB_STATE_CONVERT_UNDERWAY ); // // Now check if the file is mapped by a user. // if (!MmCanFileBeTruncated( &Scb->NonpagedScb->SegmentObject, NULL )) { AttributeNameOffset = ClusterAlign( Fcb->Vcb, Attribute->Form.Resident.ValueLength ); AllocatedLength += AttributeNameOffset; ValueLength = Attribute->Form.Resident.ValueLength; Scb = NULL; WriteClusters = TRUE; } else { volatile UCHAR VolatileUchar; AttributeNameOffset = 0; NtfsCreateInternalAttributeStream( IrpContext, Scb, TRUE ); // // Make sure the cache is up-to-date. // CcSetFileSizes( Scb->FileObject, (PCC_FILE_SIZES)&Scb->Header.AllocationSize ); ValueLength = Scb->Header.ValidDataLength.LowPart; if (ValueLength != 0) { NtfsPinStream( IrpContext, Scb, (LONGLONG)0, ValueLength, &ResidentBcb, &AttributeValue ); // // Close the window where this page can leave memory before we // have the new attribute initialized. The result will be that // we may fault in this page again and read uninitialized data // out of the newly allocated sectors. // // Make the page dirty so that the cache manager will write it out // and update the valid data length. // VolatileUchar = *((PUCHAR) AttributeValue); *((PUCHAR) AttributeValue) = VolatileUchar; } } } if (AllocatedLength > 8) { Buffer = AllocatedBuffer = NtfsAllocatePool(PagedPool, AllocatedLength ); } else { Buffer = &AttributeNameBuffer; } // // Now update the attribute name in the buffer. // AttributeName.Buffer = Add2Ptr( Buffer, AttributeNameOffset ); RtlCopyMemory( AttributeName.Buffer, Add2Ptr( Attribute, Attribute->NameOffset ), AttributeName.Length ); // // If we are going to write the clusters directly to the disk then copy // the bytes into the buffer. // if (WriteClusters) { AttributeValue = Buffer; RtlCopyMemory( AttributeValue, NtfsAttributeValue( Attribute ), ValueLength ); } // // Now just delete the current record and create it nonresident. // Create nonresident with attribute does the right thing if we // are being called by MM. // NtfsDeleteAttributeRecord( IrpContext, Fcb, TRUE, TRUE, Context ); NtfsCreateNonresidentWithValue( IrpContext, Fcb, AttributeTypeCode, &AttributeName, AttributeValue, ValueLength, AttributeFlags, WriteClusters, Scb, TRUE, Context ); // // If we were passed an attribute context, then we want to // reload the context with the new location of the file. // if (!CleanupLocalContext) { NtfsCleanupAttributeContext( Context ); NtfsInitializeAttributeContext( Context ); if (!NtfsLookupAttributeByName( IrpContext, Fcb, &Fcb->FileReference, AttributeTypeCode, &AttributeName, NULL, FALSE, Context )) { DebugTrace( 0, 0, ("Could not find attribute being converted\n") ); ASSERTMSG("Could not find attribute being converted, About to raise corrupt ", FALSE); NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb ); } } } finally { DebugUnwind( NtfsConvertToNonresident ); if (AllocatedBuffer != NULL) { NtfsFreePool( AllocatedBuffer ); } if (CleanupLocalContext) { NtfsCleanupAttributeContext( Context ); } NtfsUnpinBcb( &ResidentBcb ); #ifdef _CAIRO_ // // Restore the value of the quota disable flag. // ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE ); SetFlag( IrpContext->Flags, IrpContextFlags ); #endif // _CAIRO_ } } VOID NtfsDeleteAttributeRecord ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN BOOLEAN LogIt, IN BOOLEAN PreserveFileRecord, IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context ) /*++ Routine Description: This routine deletes an existing attribute removing it from the file record. The caller specifies the attribute to be deleted via the attribute context, and must be prepared to clean up this context no matter how this routine returns. Note that currently this routine does not deallocate any clusters allocated to a nonresident attribute; it expects the caller to already have done so. Arguments: Fcb - Current file. LogIt - Most callers should specify TRUE, to have the change logged. However, we can specify FALSE if we are deleting an entire file record, and will be logging that. PreserveFileRecord - Indicates that we should save the file record because we will be re-inserting an attribute. Will only be TRUE for the convert to non-resident case. Context - Attribute Context positioned at the attribute to delete. Return Value: None. --*/ { PATTRIBUTE_RECORD_HEADER Attribute; PFILE_RECORD_SEGMENT_HEADER FileRecord; PVCB Vcb; ATTRIBUTE_TYPE_CODE AttributeTypeCode; ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_FCB( Fcb ); Vcb = Fcb->Vcb; PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsDeleteAttribute\n") ); DebugTrace( 0, Dbg, ("Fcb = %08lx\n", Fcb) ); DebugTrace( 0, Dbg, ("Context =%08lx\n", Context) ); // // Get the pointers we need. // Attribute = NtfsFoundAttribute(Context); AttributeTypeCode = Attribute->TypeCode; FileRecord = NtfsContainingFileRecord(Context); ASSERT( Attribute->RecordLength == QuadAlign( Attribute->RecordLength )); if ((Attribute->FormCode == NONRESIDENT_FORM) && !Context->FoundAttribute.AttributeAllocationDeleted) { NtfsDeleteAllocationFromRecord( IrpContext, Fcb, Context, TRUE ); // // Reload our local pointers. // Attribute = NtfsFoundAttribute(Context); FileRecord = NtfsContainingFileRecord(Context); } #ifdef _CAIRO_ // // If this is a resident stream then release the quota. Quota for // non-resident streams is handled by NtfsDeleteAllocaiton. // if ((Attribute->FormCode == RESIDENT_FORM) && NtfsIsTypeCodeSubjectToQuota( Attribute->TypeCode )) { LONGLONG Delta = -NtfsResidentStreamQuota( Vcb ); NtfsConditionallyUpdateQuota( IrpContext, Fcb, &Delta, LogIt, FALSE ); } #endif //_CAIRO // // Be sure the attribute is pinned. // NtfsPinMappedAttribute( IrpContext, Vcb, Context ); // // Log the change. // if (LogIt) { FileRecord->Lsn = NtfsWriteLog( IrpContext, Vcb->MftScb, NtfsFoundBcb(Context), DeleteAttribute, NULL, 0, CreateAttribute, Attribute, Attribute->RecordLength, NtfsMftOffset( Context ), (PCHAR)Attribute - (PCHAR)FileRecord, 0, Vcb->BytesPerFileRecordSegment ); } NtfsRestartRemoveAttribute( IrpContext, FileRecord, (PCHAR)Attribute - (PCHAR)FileRecord ); Context->FoundAttribute.AttributeDeleted = TRUE; if (LogIt && (Context->AttributeList.Bcb != NULL)) { // // Now delete the attribute list entry, if there is one. Do it // after freeing space above, because we assume the list has not moved. // Note we only do this if LogIt is TRUE, assuming that otherwise // the entire file is going away anyway, so there is no need to // fix up the list. // NtfsDeleteFromAttributeList( IrpContext, Fcb, Context ); } // // Delete the file record if it happened to go empty. (Note that // delete file does not call this routine and deletes its own file // records.) // if (!PreserveFileRecord && FileRecord->FirstFreeByte == ((ULONG)FileRecord->FirstAttributeOffset + QuadAlign( sizeof( ATTRIBUTE_TYPE_CODE )))) { ASSERT( NtfsFullSegmentNumber( &Fcb->FileReference ) == NtfsUnsafeSegmentNumber( &Fcb->FileReference ) ); NtfsDeallocateMftRecord( IrpContext, Vcb, (ULONG)Context->FoundAttribute.MftFileOffset >> Vcb->MftShift ); } DebugTrace( -1, Dbg, ("NtfsDeleteAttributeRecord -> VOID\n") ); return; } VOID NtfsDeleteAllocationFromRecord ( PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN PATTRIBUTE_ENUMERATION_CONTEXT Context, IN BOOLEAN BreakupAllowed ) /*++ Routine Description: This routine may be called to delete the allocation of an attribute from its attribute record. It does nothing to the attribute record itself - the caller must deal with that. Arguments: Fcb - Current file. Context - Attribute enumeration context positioned to the attribute whose allocation is to be deleted. 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 --*/ { PATTRIBUTE_RECORD_HEADER Attribute; PSCB Scb; UNICODE_STRING AttributeName; PFILE_OBJECT TempFileObject; BOOLEAN ScbExisted; BOOLEAN ScbAcquired = FALSE; BOOLEAN ReinitializeContext = FALSE; PAGED_CODE(); // // Point to the current attribute. // Attribute = NtfsFoundAttribute( Context ); // // If the attribute is nonresident, then delete its allocation. // ASSERT(Attribute->FormCode == NONRESIDENT_FORM); NtfsInitializeStringFromAttribute( &AttributeName, Attribute ); // // Decode the file object // Scb = NtfsCreateScb( IrpContext, Fcb, Attribute->TypeCode, &AttributeName, FALSE, &ScbExisted ); try { // // Acquire the Scb Exclusive // NtfsAcquireExclusiveScb( IrpContext, Scb ); ScbAcquired = TRUE; if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) { NtfsUpdateScbFromAttribute( IrpContext, Scb, Attribute ); if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE )) { NtfsExpandQuotaToAllocationSize( IrpContext, Scb ); } } // // If we created the Scb, then this is the only case where // it is legal for us to omit the File Object in the delete // allocation call, because there cannot possibly be a section. // if (!ScbExisted) { TempFileObject = NULL; // // Else, if there is already a stream file object, we can just // use it. // } else if (Scb->FileObject != NULL) { TempFileObject = Scb->FileObject; // // Else the Scb existed and we did not already have a stream, // so we have to create one and delete it on the way out. // } else { NtfsCreateInternalAttributeStream( IrpContext, Scb, TRUE ); TempFileObject = Scb->FileObject; } // // Before we make this call, we need to check if we will have to // reread the current attribute. This could be necessary if // we remove any records for this attribute in the delete case. // // We only do this under the following conditions. // // 1 - There is an attribute list present. // 2 - There is an entry following the current entry in // the attribute list. // 3 - The lowest Vcn for that following entry is non-zero. // if (Context->AttributeList.Bcb != NULL) { PATTRIBUTE_LIST_ENTRY NextEntry; NextEntry = (PATTRIBUTE_LIST_ENTRY) NtfsGetNextRecord( Context->AttributeList.Entry ); if (NextEntry < Context->AttributeList.BeyondFinalEntry) { if ( NextEntry->LowestVcn != 0) { ReinitializeContext = TRUE; } } } NtfsDeleteAllocation( IrpContext, TempFileObject, Scb, *(PVCN)&Li0, MAXLONGLONG, FALSE, BreakupAllowed ); // // Reread the attribute if we need to. // if (ReinitializeContext) { NtfsCleanupAttributeContext( Context ); NtfsInitializeAttributeContext( Context ); NtfsLookupAttributeForScb( IrpContext, Scb, NULL, Context ); } } finally { DebugUnwind( NtfsDeleteAllocationFromRecord ); if (ScbAcquired) { NtfsReleaseScb( IrpContext, Scb ); } } return; } // // This routine is intended for use by allocsup.c. Other callers should use // the routines in allocsup. // BOOLEAN NtfsCreateAttributeWithAllocation ( IN PIRP_CONTEXT IrpContext, IN PSCB Scb, IN ATTRIBUTE_TYPE_CODE AttributeTypeCode, IN PUNICODE_STRING AttributeName OPTIONAL, IN USHORT AttributeFlags, IN BOOLEAN LogIt, IN BOOLEAN UseContext, IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context ) /*++ Routine Description: This routine creates the specified attribute with allocation, and returns a description of it via the attribute context. If the amount of space being created is small enough, we do all of the work here. Otherwise we create the initial attribute and call NtfsAddAttributeAllocation to add the rest (in order to keep the more complex logic in one place). On successful return, it is up to the caller to clean up the attribute context. Arguments: Scb - Current stream. AttributeTypeCode - Type code of the attribute to create. AttributeName - Optional name for attribute. AttributeFlags - Desired flags for the created attribute. WhereIndexed - Optionally supplies the file reference to the file where this attribute is indexed. 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. UseContext - Indicates if the context is pointing at the location for the attribute. Context - A handle to the created attribute. This context is in a indeterminate state on return. Return Value: BOOLEAN - TRUE if we created the attribute with all the allocation. FALSE otherwise. We should only return FALSE if we are creating a file and don't want to log any of the changes to the file record. --*/ { UCHAR AttributeBuffer[SIZEOF_FULL_NONRES_ATTR_HEADER]; UCHAR MappingPairsBuffer[64]; ULONG RecordOffset; PATTRIBUTE_RECORD_HEADER Attribute; PFILE_RECORD_SEGMENT_HEADER FileRecord; ULONG SizeNeeded; ULONG AttrSizeNeeded; PCHAR MappingPairs; ULONG MappingPairsLength; LCN Lcn; VCN LastVcn; VCN HighestVcn; PVCB Vcb; ULONG Passes = 0; PFCB Fcb = Scb->Fcb; PNTFS_MCB Mcb = &Scb->Mcb; ULONG AttributeHeaderSize = SIZEOF_PARTIAL_NONRES_ATTR_HEADER; BOOLEAN AllocateAll = TRUE; ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_FCB( Fcb ); PAGED_CODE(); ASSERT( (AttributeFlags == 0) || NtfsIsTypeCodeCompressible( AttributeTypeCode )); Vcb = Fcb->Vcb; // // Clear out the invalid attribute flags for this volume. // AttributeFlags &= Vcb->AttributeFlagsMask; DebugTrace( +1, Dbg, ("NtfsCreateAttributeWithAllocation\n") ); DebugTrace( 0, Dbg, ("Mcb = %08lx\n", Mcb) ); // // Calculate the size needed for this attribute. (We say we have // Vcb->BigEnoughToMove bytes available as a short cut, since we // will extend later as required anyway. It should be extremely // unusual that we would really have to extend.) // MappingPairsLength = QuadAlign( NtfsGetSizeForMappingPairs( Mcb, Vcb->BigEnoughToMove, (LONGLONG)0, NULL, &LastVcn )); // // Remember the size of the attribute header needed for this file. // if (FlagOn( AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK )) { AttributeHeaderSize = SIZEOF_FULL_NONRES_ATTR_HEADER; } SizeNeeded = AttributeHeaderSize + MappingPairsLength + (ARGUMENT_PRESENT(AttributeName) ? QuadAlign( AttributeName->Length ) : 0); AttrSizeNeeded = SizeNeeded; // // Loop until we find all the space we need. // do { // // Reinitialize context if this is not the first pass. // if (Passes != 0) { NtfsCleanupAttributeContext( Context ); NtfsInitializeAttributeContext( Context ); } Passes += 1; ASSERT( Passes < 5 ); // // If the attribute is not indexed, then we will position to the // insertion point by type code and name. // if (!UseContext && NtfsLookupAttributeByName( IrpContext, Fcb, &Fcb->FileReference, AttributeTypeCode, AttributeName, NULL, FALSE, Context )) { DebugTrace( 0, 0, ("Nonresident attribute already exists, TypeCode = %08lx\n", AttributeTypeCode) ); ASSERTMSG("Nonresident attribute already exists, About to bugcheck ", FALSE); NtfsBugCheck( AttributeTypeCode, 0, 0 ); } // // If this attribute is being positioned in the base file record and // there is an attribute list then we need to ask for enough space // for the attribute list entry now. // FileRecord = NtfsContainingFileRecord( Context ); Attribute = NtfsFoundAttribute( Context ); AttrSizeNeeded = SizeNeeded; if (Context->AttributeList.Bcb != NULL && (ULONG) FileRecord <= (ULONG) Context->AttributeList.AttributeList && (ULONG) Attribute >= (ULONG) Context->AttributeList.AttributeList) { // // If the attribute list is non-resident then add a fudge factor of // 16 bytes for any new retrieval information. // if (NtfsIsAttributeResident( Context->AttributeList.AttributeList )) { AttrSizeNeeded += QuadAlign( FIELD_OFFSET( ATTRIBUTE_LIST_ENTRY, AttributeName ) + (ARGUMENT_PRESENT( AttributeName ) ? (ULONG) AttributeName->Length : sizeof( WCHAR ))); } else { AttrSizeNeeded += 0x10; } } UseContext = FALSE; // // Ask for the space we need. // } while (!NtfsGetSpaceForAttribute( IrpContext, Fcb, AttrSizeNeeded, Context )); // // Now get the attribute pointer and fill it in. // FileRecord = NtfsContainingFileRecord(Context); RecordOffset = (PCHAR)NtfsFoundAttribute(Context) - (PCHAR)FileRecord; Attribute = (PATTRIBUTE_RECORD_HEADER)AttributeBuffer; RtlZeroMemory( Attribute, SIZEOF_FULL_NONRES_ATTR_HEADER ); Attribute->TypeCode = AttributeTypeCode; Attribute->RecordLength = SizeNeeded; Attribute->FormCode = NONRESIDENT_FORM; // // Assume no attribute name, and calculate where the Mapping Pairs // will go. (Update below if we are wrong.) // MappingPairs = Add2Ptr( Attribute, AttributeHeaderSize ); // // If the attribute has a name, take care of that now. // if (ARGUMENT_PRESENT(AttributeName) && AttributeName->Length != 0) { ASSERT( AttributeName->Length <= 0x1FF ); Attribute->NameLength = (UCHAR)(AttributeName->Length / sizeof(WCHAR)); Attribute->NameOffset = (USHORT)AttributeHeaderSize; MappingPairs += QuadAlign( AttributeName->Length ); } Attribute->Flags = AttributeFlags; Attribute->Instance = FileRecord->NextAttributeInstance; // // We always need the mapping pairs offset. // Attribute->Form.Nonresident.MappingPairsOffset = (USHORT)(MappingPairs - (PCHAR)Attribute); // // Set up the compression unit size. // if (FlagOn(AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK)) { Attribute->Form.Nonresident.CompressionUnit = NTFS_CLUSTERS_PER_COMPRESSION; } // // Now we need to point to the real place to build the mapping pairs buffer. // If they will not be too big we can use our internal buffer. // MappingPairs = MappingPairsBuffer; if (MappingPairsLength > 64) { MappingPairs = NtfsAllocatePool( NonPagedPool, MappingPairsLength ); } *MappingPairs = 0; // // Find how much space is allocated by finding the last Mcb entry and // looking it up. If there are no entries, all of the subsequent // fields are already zeroed. // Attribute->Form.Nonresident.HighestVcn = HighestVcn = -1; if (NtfsLookupLastNtfsMcbEntry( Mcb, &HighestVcn, &Lcn )) { ASSERT_LCN_RANGE_CHECKING( Vcb, Lcn ); // // Now build the mapping pairs in place. // NtfsBuildMappingPairs( Mcb, 0, &LastVcn, MappingPairs ); Attribute->Form.Nonresident.HighestVcn = LastVcn; // // Fill in the nonresident-specific fields. We set the allocation // size to only include the Vcn's we included in the mapping pairs. // Attribute->Form.Nonresident.AllocatedLength = Int64ShllMod32((LastVcn + 1 ), Vcb->ClusterShift); // // The totally allocated field in the Scb will contain the current allocated // value for this stream. // if (FlagOn( AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK )) { ASSERT( Scb->Header.NodeTypeCode == NTFS_NTC_SCB_DATA ); Attribute->Form.Nonresident.TotalAllocated = Scb->TotalAllocated; } // // We are creating a attribute with zero allocation. Make the Vcn sizes match // so we don't make the call below to AddAttributeAllocation. // } else { LastVcn = HighestVcn; } // // Now we will actually create the attribute in place, so that we // save copying everything twice, and can point to the final image // for the log write below. // NtfsRestartInsertAttribute( IrpContext, FileRecord, RecordOffset, Attribute, AttributeName, MappingPairs, MappingPairsLength ); // // Finally, log the creation of this attribute // if (LogIt) { // // We have actually created the attribute above, but the write // log below could fail. The reason we did the create already // was to avoid having to allocate pool and copy everything // twice (header, name and value). Our normal error recovery // just recovers from the log file. But if we fail to write // the log, we have to remove this attribute by hand, and // raise the condition again. // try { FileRecord->Lsn = NtfsWriteLog( IrpContext, Vcb->MftScb, NtfsFoundBcb(Context), CreateAttribute, Add2Ptr(FileRecord, RecordOffset), Attribute->RecordLength, DeleteAttribute, NULL, 0, NtfsMftOffset( Context ), RecordOffset, 0, Vcb->BytesPerFileRecordSegment ); } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) { NtfsRestartRemoveAttribute( IrpContext, FileRecord, RecordOffset ); if (MappingPairs != MappingPairsBuffer) { NtfsFreePool( MappingPairs ); } NtfsRaiseStatus( IrpContext, GetExceptionCode(), NULL, NULL ); } } // // Free the mapping pairs buffer if we allocated one. // if (MappingPairs != MappingPairsBuffer) { NtfsFreePool( MappingPairs ); } // // Now add it to the attribute list if necessary // if (Context->AttributeList.Bcb != NULL) { MFT_SEGMENT_REFERENCE SegmentReference; *(PLONGLONG)&SegmentReference = LlFileRecordsFromBytes( Vcb, NtfsMftOffset( Context )); SegmentReference.SequenceNumber = FileRecord->SequenceNumber; NtfsAddToAttributeList( IrpContext, Fcb, SegmentReference, Context ); } // // We couldn't create all of the mapping for the allocation above. If // this is a create then we want to truncate the allocation to what we // have already allocated. Otherwise we want to call // NtfsAddAttributeAllocation to map the remaining allocation. // if (LastVcn != HighestVcn) { if (LogIt || !NtfsIsTypeCodeUserData( AttributeTypeCode ) || IrpContext->MajorFunction != IRP_MJ_CREATE) { NtfsAddAttributeAllocation( IrpContext, Scb, Context, NULL, NULL ); } else { // // Truncate away the clusters beyond the last Vcn and set the // flag in the IrpContext indicating there is more allocation // to do. // NtfsDeallocateClusters( IrpContext, Fcb->Vcb, &Scb->Mcb, LastVcn + 1, MAXLONGLONG, NULL ); NtfsUnloadNtfsMcbRange( &Scb->Mcb, LastVcn + 1, MAXLONGLONG, TRUE, FALSE ); #ifdef _CAIRO_ if (FlagOn( Scb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA )) { LONGLONG Delta = LlBytesFromClusters(Fcb->Vcb, LastVcn - HighestVcn); ASSERT( NtfsIsTypeCodeSubjectToQuota( AttributeTypeCode )); ASSERT( NtfsIsTypeCodeSubjectToQuota( Scb->AttributeTypeCode )); // // Return any quota charged. // NtfsConditionallyUpdateQuota( IrpContext, Fcb, &Delta, LogIt, TRUE ); } #endif // _CAIRO_ AllocateAll = FALSE; } } DebugTrace( -1, Dbg, ("NtfsCreateAttributeWithAllocation -> VOID\n") ); return AllocateAll; } // // This routine is intended for use by allocsup.c. Other callers should use // the routines in allocsup. // VOID NtfsAddAttributeAllocation ( IN PIRP_CONTEXT IrpContext, IN PSCB Scb, IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context, IN PVCN StartingVcn OPTIONAL, IN PVCN ClusterCount OPTIONAL ) /*++ Routine Description: This routine adds space to an existing nonresident attribute. The caller specifies the attribute to be changed via the attribute context, and must be prepared to clean up this context no matter how this routine returns. This routine procedes in the following steps, whose numbers correspond to the numbers in comments below: 1. Save a description of the current attribute. 2. Figure out how big the attribute would have to be to store all of the new run information. 3. Find the last occurrence of the attribute, to which the new allocation is to be appended. 4. If the attribute is getting very large and will not fit, then move it to its own file record. In any case grow the attribute enough to fit either all of the new allocation, or as much as possible. 5. Construct the new mapping pairs in place, and log the change. 6. If there is still more allocation to describe, then loop to create new file records and initialize them to describe additional allocation until all of the allocation is described. Arguments: Scb - Current stream. Context - Attribute Context positioned at the attribute to change. Note that unlike other routines, this parameter is left in an indeterminate state upon return. The caller should plan on doing nothing other than cleaning it up. StartingVcn - Supplies Vcn to start on, if not the new highest vcn ClusterCount - Supplies count of clusters being added, if not the new highest vcn Return Value: None. --*/ { PATTRIBUTE_RECORD_HEADER Attribute; PFILE_RECORD_SEGMENT_HEADER FileRecord; ULONG NewSize, MappingPairsSize; LONG SizeChange; PCHAR MappingPairs; ULONG SizeAvailable; USHORT AttributeFlags; PVCB Vcb; WCHAR NameBuffer[8]; UNICODE_STRING AttributeName; ATTRIBUTE_TYPE_CODE AttributeTypeCode; VCN LowestVcnRemapped; VCN LocalClusterCount; VCN OldHighestVcn; VCN NewHighestVcn; VCN LastVcn; BOOLEAN IsHotFixScb; PBCB NewBcb = NULL; LONGLONG MftReferenceNumber; PFCB Fcb = Scb->Fcb; PNTFS_MCB Mcb = &Scb->Mcb; ULONG AttributeHeaderSize; ASSERT_IRP_CONTEXT( IrpContext ); PAGED_CODE(); Vcb = Fcb->Vcb; DebugTrace( +1, Dbg, ("NtfsAddAttributeAllocation\n") ); DebugTrace( 0, Dbg, ("Fcb = %08lx\n", Fcb) ); DebugTrace( 0, Dbg, ("Mcb = %08lx\n", Mcb) ); DebugTrace( 0, Dbg, ("Context = %08lx\n", Context) ); // // Make a local copy of cluster count, if given. We will use this local // copy to determine the shrinking range if we move to a previous file // record on a second pass through this loop. // if (ARGUMENT_PRESENT( ClusterCount )) { LocalClusterCount = *ClusterCount; } while (TRUE) { // // Make sure the buffer is pinned. // NtfsPinMappedAttribute( IrpContext, Vcb, Context ); // // Make sure we cleanup on the way out // try { // // Step 1. // // Save a description of the attribute to help us look it up // again, and to make clones if necessary. // Attribute = NtfsFoundAttribute(Context); ASSERT( Attribute->RecordLength == QuadAlign( Attribute->RecordLength )); AttributeTypeCode = Attribute->TypeCode; AttributeFlags = Attribute->Flags; AttributeName.Length = AttributeName.MaximumLength = (USHORT)Attribute->NameLength << 1; AttributeName.Buffer = NameBuffer; if (AttributeName.Length > sizeof(NameBuffer)) { AttributeName.Buffer = NtfsAllocatePool( NonPagedPool, AttributeName.Length ); } RtlCopyMemory( AttributeName.Buffer, Add2Ptr( Attribute, Attribute->NameOffset ), AttributeName.Length ); ASSERT(Attribute->Form.Nonresident.LowestVcn == 0); OldHighestVcn = LlClustersFromBytes(Vcb, Attribute->Form.Nonresident.AllocatedLength) - 1; // // Get the file record pointer. // FileRecord = NtfsContainingFileRecord(Context); // // Step 2. // // Come up with the Vcn we will stop on. If a StartingVcn and ClusterCount // were specified, then use them to calculate where we will stop. Otherwise // lookup the largest Vcn in this Mcb, so that we will know when we are done. // We will also write the new allocation size here. // { LCN TempLcn; BOOLEAN UpdateFileSizes = FALSE; NewHighestVcn = -1; // // If a StartingVcn and ClusterCount were specified, then use them. // if (ARGUMENT_PRESENT(StartingVcn)) { ASSERT(ARGUMENT_PRESENT(ClusterCount)); NewHighestVcn = (*StartingVcn + LocalClusterCount) - 1; // // If there are no entries in the file record then we have no new // sizes to report. // } else if (NtfsLookupLastNtfsMcbEntry(Mcb, &NewHighestVcn, &TempLcn)) { // // For compressed files, make sure we are not shrinking allocation // size (OldHighestVcn) due to a compression unit that was all zeros // and has no allocation. Note, truncates are done in // NtfsDeleteAttributeAllocation, so we should not be shrinking the // file here. // // If this is an attribute being written compressed, then always // insure that we keep the allocation size on a compression unit // boundary, by pushing NewHighestVcn to a boundary - 1. // if (Scb->CompressionUnit != 0) { // // Don't shrink the file on this path. // if (OldHighestVcn > NewHighestVcn) { NewHighestVcn = OldHighestVcn; } ((PLARGE_INTEGER) &NewHighestVcn)->LowPart |= ClustersFromBytes(Vcb, Scb->CompressionUnit) - 1; // // Make sure we didn't push a hole into the next compression // unit. If so then truncate to the current NewHighestVcn. We // know this will be on a compression unit boundary. // if (NewHighestVcn < Scb->Mcb.NtfsMcbArray[Scb->Mcb.NtfsMcbArraySizeInUse - 1].EndingVcn) { NtfsUnloadNtfsMcbRange( &Scb->Mcb, NewHighestVcn + 1, MAXLONGLONG, TRUE, FALSE ); } } } // // Copy the new allocation size into our size structure and // update the attribute. // if (NewHighestVcn > OldHighestVcn) { Scb->Header.AllocationSize.QuadPart = LlBytesFromClusters(Fcb->Vcb, NewHighestVcn + 1); UpdateFileSizes = TRUE; } // // If we moved the allocation size up or the totally allocated does // not match the value on the disk (only for compressed files, // then update the file sizes. // if (UpdateFileSizes || (FlagOn( Attribute->Flags, ATTRIBUTE_FLAG_COMPRESSION_MASK ) && (Attribute->Form.Nonresident.TotalAllocated != Scb->TotalAllocated))) { NtfsWriteFileSizes( IrpContext, Scb, &Scb->Header.ValidDataLength.QuadPart, FALSE, TRUE ); } } // // Step 3. // // Lookup the attribute record at which the change begins, if it is not // the first file record that we are looking at. // if ((Attribute->Form.Nonresident.HighestVcn != OldHighestVcn) && (NewHighestVcn > Attribute->Form.Nonresident.HighestVcn)) { NtfsCleanupAttributeContext( Context ); NtfsInitializeAttributeContext( Context ); NtfsLookupAttributeForScb( IrpContext, Scb, &NewHighestVcn, Context ); Attribute = NtfsFoundAttribute(Context); ASSERT( Attribute->RecordLength == QuadAlign( Attribute->RecordLength )); FileRecord = NtfsContainingFileRecord(Context); } // // Make sure we nuke this range if we get an error, by expanding // the error recovery range. // if (Scb->Mcb.PoolType == PagedPool) { if (Scb->ScbSnapshot != NULL) { if (Attribute->Form.Nonresident.LowestVcn < Scb->ScbSnapshot->LowestModifiedVcn) { Scb->ScbSnapshot->LowestModifiedVcn = Attribute->Form.Nonresident.LowestVcn; } if (NewHighestVcn > Scb->ScbSnapshot->HighestModifiedVcn) { Scb->ScbSnapshot->HighestModifiedVcn = NewHighestVcn; } if (Attribute->Form.Nonresident.HighestVcn > Scb->ScbSnapshot->HighestModifiedVcn) { Scb->ScbSnapshot->HighestModifiedVcn = Attribute->Form.Nonresident.HighestVcn; } } } // // Remember the last Vcn we will need to create mapping pairs // for. We use either NewHighestVcn or the highest Vcn in this // file record in the case that we are just inserting a run into // an existing record. // if (ARGUMENT_PRESENT(StartingVcn)) { if (Attribute->Form.Nonresident.HighestVcn > NewHighestVcn) { NewHighestVcn = Attribute->Form.Nonresident.HighestVcn; } // // Otherwise we know we have looked up the last file record to regenerate // that. Let's make sure that the range lines up so we do not end up // generating a hole of one or a few clusters in the next file record. // } else { if (Attribute->Form.Nonresident.HighestVcn < NewHighestVcn) { NtfsDefineNtfsMcbRange( &Scb->Mcb, Attribute->Form.Nonresident.LowestVcn, NewHighestVcn, FALSE ); } } // // Remember the lowest Vcn for this attribute. We will use this to // decide whether to loop back and look for an earlier file record. // LowestVcnRemapped = Attribute->Form.Nonresident.LowestVcn; // // Remember the header size for this attribute. This will be the // mapping pairs offset except for attributes with names. // AttributeHeaderSize = Attribute->Form.Nonresident.MappingPairsOffset; if (Attribute->NameOffset != 0) { AttributeHeaderSize = Attribute->NameOffset; } // // If we are making space for a totally allocated field then we // want to add space to the non-resident header for these entries. // To detect this we know that a starting Vcn was specified and // we specified exactly the entire file record. Also the major // and minor Irp codes are exactly that for a compression operation. // if ((IrpContext->MajorFunction == IRP_MJ_FILE_SYSTEM_CONTROL) && (IrpContext->MinorFunction == IRP_MN_USER_FS_REQUEST) && (IoGetCurrentIrpStackLocation( IrpContext->OriginatingIrp)->Parameters.FileSystemControl.FsControlCode == FSCTL_SET_COMPRESSION) && ARGUMENT_PRESENT( StartingVcn ) && (*StartingVcn == 0) && (LocalClusterCount == Attribute->Form.Nonresident.HighestVcn + 1)) { AttributeHeaderSize += sizeof( LONGLONG ); } // // Now we must make sure that we never ask for more than can fit in // one file record with our attribute and a $END record. // SizeAvailable = NtfsMaximumAttributeSize(Vcb->BytesPerFileRecordSegment) - AttributeHeaderSize - QuadAlign( AttributeName.Length ); // // For the Mft, we will leave a "fudge factor" of 1/8th a file record // free to make sure that possible hot fixes do not cause us to // break the bootstrap process to finding the mapping for the Mft. // Only take this action if we already have an attribute list for // the Mft, otherwise we may not detect when we need to move to own // record. // IsHotFixScb = NtfsIsTopLevelHotFixScb( Scb ); if ((Scb == Vcb->MftScb) && (Context->AttributeList.Bcb != NULL) && !IsHotFixScb && !ARGUMENT_PRESENT( StartingVcn )) { SizeAvailable -= Vcb->MftCushion; } // // Calculate how much space is actually needed, independent of whether it will // fit. // MappingPairsSize = QuadAlign( NtfsGetSizeForMappingPairs( Mcb, SizeAvailable, Attribute->Form.Nonresident.LowestVcn, &NewHighestVcn, &LastVcn )); NewSize = AttributeHeaderSize + QuadAlign( AttributeName.Length ) + MappingPairsSize; SizeChange = (LONG)NewSize - (LONG)Attribute->RecordLength; // // Step 4. // // Here we decide if we need to move the attribute to its own record, // or whether there is enough room to grow it in place. // { VCN LowestVcn; ULONG Pass = 0; // // It is important to note that at this point, if we will need an // attribute list attribute, then we will already have it. This is // because we calculated the size needed for the attribute, and moved // to a our own record if we were not going to fit and we were not // already in a separate record. Later on we assume that the attribute // list exists, and just add to it as required. If we didn't move to // own record because this is the Mft and this is not file record 0, // then we already have an attribute list from a previous split. // do { // // If not the first pass, we have to lookup the attribute // again. (It looks terrible to have to refind an attribute // record other than the first one, but this should never // happen, since subsequent attributes should always be in // their own record.) // if (Pass != 0) { BOOLEAN Found; NtfsCleanupAttributeContext( Context ); NtfsInitializeAttributeContext( Context ); Found = NtfsLookupAttributeByName( IrpContext, Fcb, &Fcb->FileReference, AttributeTypeCode, &AttributeName, &LowestVcn, FALSE, Context ); ASSERT(Found); } Pass += 1; // // Now we have to reload our pointers // Attribute = NtfsFoundAttribute(Context); FileRecord = NtfsContainingFileRecord(Context); // // If the attribute doesn't fit, and it is not alone in this file // record, and the attribute is big enough to move, then we will // have to take some special action. Note that if we do not already // have an attribute list, then we will only do the move if we are // currently big enough to move, otherwise there may not be enough // space in MoveAttributeToOwnRecord to create the attribute list, // and that could cause us to recursively try to create the attribute // list in Create Attribute With Value. // // We won't make this move if we are dealing with the Mft and it // is not file record 0. // // Also we never move an attribute list to its own record. // if ((Attribute->TypeCode != $ATTRIBUTE_LIST) && (SizeChange > (LONG)(FileRecord->BytesAvailable - FileRecord->FirstFreeByte)) && ((NtfsFirstAttribute(FileRecord) != Attribute) || (((PATTRIBUTE_RECORD_HEADER)NtfsGetNextRecord(Attribute))->TypeCode != $END)) && (((NewSize >= Vcb->BigEnoughToMove) && (Context->AttributeList.Bcb != NULL)) || (Attribute->RecordLength >= Vcb->BigEnoughToMove)) && ((Scb != Vcb->MftScb) || (*(PLONGLONG)&FileRecord->BaseFileRecordSegment == 0))) { // // If we are moving the Mft $DATA out of the base file record, the // attribute context will point to the split portion on return. // The attribute will only contain previously existing mapping, none // of the additional clusters which exist in the Mcb. // MftReferenceNumber = MoveAttributeToOwnRecord( IrpContext, Fcb, Attribute, Context, &NewBcb, &FileRecord ); Attribute = NtfsFirstAttribute(FileRecord); ASSERT( Attribute->RecordLength == QuadAlign( Attribute->RecordLength )); FileRecord = NtfsContainingFileRecord(Context); } // // Remember the lowest Vcn so that we can find this record again // if we have to. We capture the value now, after the move attribute // in case this is the Mft doing a split and the entire attribute // didn't move. We depend on MoveAttributeToOwnRecord to return // the new file record for the Mft split. // LowestVcn = Attribute->Form.Nonresident.LowestVcn; // // If FALSE is returned, then the space was not allocated and // we have to loop back and try again. Second time must work. // } while (!NtfsChangeAttributeSize( IrpContext, Fcb, NewSize, Context )); // // Now we have to reload our pointers // Attribute = NtfsFoundAttribute(Context); FileRecord = NtfsContainingFileRecord(Context); } // // Step 5. // // Get pointer to mapping pairs // { ULONG AttributeOffset; ULONG MappingPairsOffset; CHAR MappingPairsBuffer[64]; ULONG RecordOffset = PtrOffset(FileRecord, Attribute); // // See if it is the case that all mapping pairs will not fit into // the current file record, as we may wish to split in the middle // rather than at the end as we are currently set up to do. // if (LastVcn < NewHighestVcn) { if (ARGUMENT_PRESENT(StartingVcn) && (Scb != Vcb->MftScb)) { // // In this case we have run out of room for mapping pairs via // an overwrite somewhere in the middle of the file. To avoid // shoving a couple mapping pairs off the end over and over, we // will arbitrarily split this attribute in the middle. We do // so by looking up the lowest and highest Vcns that we are working // with and get their indices, then split in the middle. // LCN TempLcn; LONGLONG TempCount; PVOID RangeLow, RangeHigh; ULONG IndexLow, IndexHigh; BOOLEAN Found; // // Get the low and high Mcb indices for these runs. // Found = NtfsLookupNtfsMcbEntry( Mcb, Attribute->Form.Nonresident.LowestVcn, NULL, NULL, NULL, NULL, &RangeLow, &IndexLow ); ASSERT(Found); // // Point to the last Vcn we know is actually in the Mcb... // LastVcn = LastVcn - 1; Found = NtfsLookupNtfsMcbEntry( Mcb, LastVcn, NULL, NULL, NULL, NULL, &RangeHigh, &IndexHigh ); ASSERT(Found); ASSERT(RangeLow == RangeHigh); // // Calculate the index in the middle. // IndexLow += (IndexHigh - IndexLow) /2; // // If we are inserting past he ValidDataToDisk (SplitMcb case), // then the allocation behind us may be relatively static, so // let's just move with our preallocated space to the new buffer. // if (*StartingVcn >= LlClustersFromBytes(Vcb, Scb->ValidDataToDisk)) { // // Calculate the index at about 7/8 the way. Hopefully this will // move over all of the unallocated piece, while still leaving // some small amount of expansion space behind for overwrites. // IndexLow += (IndexHigh - IndexLow) /2; IndexLow += (IndexHigh - IndexLow) /2; } // // Lookup the middle run and use the Last Vcn in that run. // Found = NtfsGetNextNtfsMcbEntry( Mcb, &RangeLow, IndexLow, &LastVcn, &TempLcn, &TempCount ); ASSERT(Found); LastVcn = (LastVcn + TempCount) - 1; // // Calculate how much space is now needed given our new LastVcn. // MappingPairsSize = QuadAlign( NtfsGetSizeForMappingPairs( Mcb, SizeAvailable, Attribute->Form.Nonresident.LowestVcn, &LastVcn, &LastVcn )); } } // // If we are growing this range, then we need to make sure we fix // its definition. // if ((LastVcn - 1) > Attribute->Form.Nonresident.HighestVcn) { NtfsDefineNtfsMcbRange( &Scb->Mcb, Attribute->Form.Nonresident.LowestVcn, LastVcn - 1, FALSE ); } // // Point to our local mapping pairs buffer, or allocate one if it is not // big enough. // MappingPairs = MappingPairsBuffer; if (MappingPairsSize > 64) { MappingPairs = NtfsAllocatePool( NonPagedPool, MappingPairsSize + 8 ); } // // Use try-finally to insure we free any pool on the way out. // try { DebugDoit( VCN TempVcn; TempVcn = LastVcn - 1; ASSERT(MappingPairsSize == QuadAlign( NtfsGetSizeForMappingPairs( Mcb, SizeAvailable, Attribute->Form.Nonresident.LowestVcn, &TempVcn, &LastVcn ))); ); // // Now add the space in the file record. // *MappingPairs = 0; NtfsBuildMappingPairs( Mcb, Attribute->Form.Nonresident.LowestVcn, &LastVcn, MappingPairs ); // // Now find the first different byte. (Most of the time the // cost to do this is probably more than paid for by less // logging.) // AttributeOffset = Attribute->Form.Nonresident.MappingPairsOffset; MappingPairsOffset = RtlCompareMemory( MappingPairs, Add2Ptr(Attribute, AttributeOffset), ((Attribute->RecordLength - AttributeOffset) > MappingPairsSize ? MappingPairsSize : (Attribute->RecordLength - AttributeOffset))); AttributeOffset += MappingPairsOffset; // // Log the change. // { LONGLONG LogOffset; if (NewBcb != NULL) { // // We know the file record number of the new file // record. Convert it to a file offset. // LogOffset = LlBytesFromFileRecords( Vcb, MftReferenceNumber ); } else { LogOffset = NtfsMftOffset( Context ); } FileRecord->Lsn = NtfsWriteLog( IrpContext, Vcb->MftScb, NewBcb != NULL ? NewBcb : NtfsFoundBcb(Context), UpdateMappingPairs, Add2Ptr(MappingPairs, MappingPairsOffset), MappingPairsSize - MappingPairsOffset, UpdateMappingPairs, Add2Ptr(Attribute, AttributeOffset), Attribute->RecordLength - AttributeOffset, LogOffset, RecordOffset, AttributeOffset, Vcb->BytesPerFileRecordSegment ); } // // Now do the mapping pairs update by calling the same // routine called at restart. // NtfsRestartChangeMapping( IrpContext, Vcb, FileRecord, RecordOffset, AttributeOffset, Add2Ptr(MappingPairs, MappingPairsOffset), MappingPairsSize - MappingPairsOffset ); } finally { if (MappingPairs != MappingPairsBuffer) { NtfsFreePool(MappingPairs); } } } ASSERT( Attribute->Form.Nonresident.HighestVcn == LastVcn ); // // Check if have spilled into the reserved area of an Mft file record. // if ((Scb == Vcb->MftScb) && (Context->AttributeList.Bcb != NULL)) { if (FileRecord->BytesAvailable - FileRecord->FirstFreeByte < Vcb->MftReserved && (*(PLONGLONG)&FileRecord->BaseFileRecordSegment != 0)) { NtfsAcquireCheckpoint( IrpContext, Vcb ); SetFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_EXCESS_MAP | VCB_MFT_DEFRAG_ENABLED ); NtfsReleaseCheckpoint( IrpContext, Vcb ); } } // // Step 6. // // Now loop to create new file records if we have more allocation to // describe. We use the highest Vcn of the file record we began with // as our stopping point or the last Vcn we are adding. // while (LastVcn < NewHighestVcn) { MFT_SEGMENT_REFERENCE Reference; LONGLONG FileRecordNumber; PATTRIBUTE_TYPE_CODE NewEnd; // // If we get here as the result of a hot fix in the Mft, bail // out. We could cause a disconnect in the Mft. // if (IsHotFixScb && (Scb == Vcb->MftScb)) { ExRaiseStatus( STATUS_INTERNAL_ERROR ); } // // Clone our current file record, and point to our new attribute. // NtfsUnpinBcb( &NewBcb ); FileRecord = NtfsCloneFileRecord( IrpContext, Fcb, (BOOLEAN)(Scb == Vcb->MftScb), &NewBcb, &Reference ); Attribute = Add2Ptr( FileRecord, FileRecord->FirstAttributeOffset ); // // Next LowestVcn is the LastVcn + 1 // LastVcn = LastVcn + 1; Attribute->Form.Nonresident.LowestVcn = LastVcn; // // Calculate the size of the attribute record we will need. // NewSize = SIZEOF_PARTIAL_NONRES_ATTR_HEADER + QuadAlign( AttributeName.Length ) + QuadAlign( NtfsGetSizeForMappingPairs( Mcb, SizeAvailable, LastVcn, &NewHighestVcn, &LastVcn )); // // Define the new range. // NtfsDefineNtfsMcbRange( &Scb->Mcb, Attribute->Form.Nonresident.LowestVcn, LastVcn - 1, FALSE ); // // Initialize the new attribute from the old one. // Attribute->TypeCode = AttributeTypeCode; Attribute->RecordLength = NewSize; Attribute->FormCode = NONRESIDENT_FORM; // // Assume no attribute name, and calculate where the Mapping Pairs // will go. (Update below if we are wrong.) // MappingPairs = (PCHAR)Attribute + SIZEOF_PARTIAL_NONRES_ATTR_HEADER; // // If the attribute has a name, take care of that now. // if (AttributeName.Length != 0) { Attribute->NameLength = (UCHAR)(AttributeName.Length / sizeof(WCHAR)); Attribute->NameOffset = (USHORT)PtrOffset(Attribute, MappingPairs); RtlCopyMemory( MappingPairs, AttributeName.Buffer, AttributeName.Length ); MappingPairs += QuadAlign( AttributeName.Length ); } Attribute->Flags = AttributeFlags; Attribute->Instance = FileRecord->NextAttributeInstance++; // // We always need the mapping pairs offset. // Attribute->Form.Nonresident.MappingPairsOffset = (USHORT)(MappingPairs - (PCHAR)Attribute); NewEnd = Add2Ptr( Attribute, Attribute->RecordLength ); *NewEnd = $END; FileRecord->FirstFreeByte = PtrOffset( FileRecord, NewEnd ) + QuadAlign( sizeof(ATTRIBUTE_TYPE_CODE )); // // Now add the space in the file record. // *MappingPairs = 0; NtfsBuildMappingPairs( Mcb, Attribute->Form.Nonresident.LowestVcn, &LastVcn, MappingPairs ); Attribute->Form.Nonresident.HighestVcn = LastVcn; // // Now log these changes and fix up the first file record. // FileRecordNumber = NtfsFullSegmentNumber(&Reference); // // Now log these changes and fix up the first file record. // FileRecord->Lsn = NtfsWriteLog( IrpContext, Vcb->MftScb, NewBcb, InitializeFileRecordSegment, FileRecord, FileRecord->FirstFreeByte, Noop, NULL, 0, LlBytesFromFileRecords( Vcb, FileRecordNumber ), 0, 0, Vcb->BytesPerFileRecordSegment ); // // Finally, we have to add the entry to the attribute list. // The routine we have to do this gets most of its inputs // out of an attribute context. Our context at this point // does not have quite the right information, so we have to // update it here before calling AddToAttributeList. (OK // this interface ain't pretty, but any normal person would // have fallen asleep before getting to this comment!) // Context->FoundAttribute.FileRecord = FileRecord; Context->FoundAttribute.Attribute = Attribute; Context->AttributeList.Entry = NtfsGetNextRecord(Context->AttributeList.Entry); NtfsAddToAttributeList( IrpContext, Fcb, Reference, Context ); } } finally { NtfsUnpinBcb( &NewBcb ); if (AttributeName.Buffer != NameBuffer) { NtfsFreePool(AttributeName.Buffer); } } if (!ARGUMENT_PRESENT( StartingVcn) || (LowestVcnRemapped <= *StartingVcn)) { break; } // // Move the range to be remapped down. // LocalClusterCount = LowestVcnRemapped - *StartingVcn; NtfsCleanupAttributeContext( Context ); NtfsInitializeAttributeContext( Context ); NtfsLookupAttributeForScb( IrpContext, Scb, NULL, Context ); } DebugTrace( -1, Dbg, ("NtfsAddAttributeAllocation -> VOID\n") ); } // // This routine is intended for use by allocsup.c. Other callers should use // the routines in allocsup. // VOID NtfsDeleteAttributeAllocation ( IN PIRP_CONTEXT IrpContext, IN PSCB Scb, IN BOOLEAN LogIt, IN PVCN StopOnVcn, IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context, IN BOOLEAN TruncateToVcn ) /*++ Routine Description: This routine deletes an existing nonresident attribute, removing the deleted clusters only from the allocation description in the file record. The caller specifies the attribute to be changed via the attribute context, and must be prepared to clean up this context no matter how this routine returns. The Scb must already have deleted the clusters in question. Arguments: Scb - Current attribute, with the clusters in question already deleted from the Mcb. LogIt - Most callers should specify TRUE, to have the change logged. However, we can specify FALSE if we are deleting an entire file record, and will be logging that. StopOnVcn - Vcn to stop on for regerating mapping Context - Attribute Context positioned at the attribute to change. TruncateToVcn - Truncate file sizes as appropriate to the Vcn Return Value: None. --*/ { ULONG AttributeOffset; ULONG MappingPairsOffset, MappingPairsSize; CHAR MappingPairsBuffer[64]; ULONG RecordOffset; PATTRIBUTE_RECORD_HEADER Attribute; PFILE_RECORD_SEGMENT_HEADER FileRecord; PCHAR MappingPairs; VCN LastVcn; ULONG NewSize; PVCB Vcb; ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_SCB( Scb ); PAGED_CODE(); Vcb = Scb->Vcb; // // For now we only support truncation. // DebugTrace( +1, Dbg, ("NtfsDeleteAttributeAllocation\n") ); DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) ); DebugTrace( 0, Dbg, ("Context = %08lx\n", Context) ); // // Make sure the buffer is pinned. // NtfsPinMappedAttribute( IrpContext, Vcb, Context ); Attribute = NtfsFoundAttribute(Context); ASSERT( Attribute->RecordLength == QuadAlign( Attribute->RecordLength )); // // Get the file record pointer. // FileRecord = NtfsContainingFileRecord(Context); RecordOffset = PtrOffset(FileRecord, Attribute); // // Calculate how much space is actually needed. // MappingPairsSize = QuadAlign(NtfsGetSizeForMappingPairs( &Scb->Mcb, MAXULONG, Attribute->Form.Nonresident.LowestVcn, StopOnVcn, &LastVcn )); // // Don't assume we understand everything about the size of the current header. // Find the offset of the name or the mapping pairs to use as the size // of the header. // NewSize = Attribute->Form.Nonresident.MappingPairsOffset; if (Attribute->NameLength != 0) { NewSize = Attribute->NameOffset + QuadAlign( Attribute->NameLength << 1 ); } NewSize += MappingPairsSize; // // If the record could somehow grow by deleting allocation, then // NtfsChangeAttributeSize could fail and we would have to copy the // loop from NtfsAddAttributeAllocation. // ASSERT( NewSize <= Attribute->RecordLength ); MappingPairs = MappingPairsBuffer; if (MappingPairsSize > 64) { MappingPairs = NtfsAllocatePool( NonPagedPool, MappingPairsSize + 8 ); } // // Use try-finally to insure we free any pool on the way out. // try { // // Now build up the mapping pairs in the buffer. // *MappingPairs = 0; NtfsBuildMappingPairs( &Scb->Mcb, Attribute->Form.Nonresident.LowestVcn, &LastVcn, MappingPairs ); // // Now find the first different byte. (Most of the time the // cost to do this is probably more than paid for by less // logging.) // AttributeOffset = Attribute->Form.Nonresident.MappingPairsOffset; MappingPairsOffset = RtlCompareMemory( MappingPairs, Add2Ptr(Attribute, AttributeOffset), MappingPairsSize ); AttributeOffset += MappingPairsOffset; // // Log the change. // if (LogIt) { FileRecord->Lsn = NtfsWriteLog( IrpContext, Vcb->MftScb, NtfsFoundBcb(Context), UpdateMappingPairs, Add2Ptr(MappingPairs, MappingPairsOffset), MappingPairsSize - MappingPairsOffset, UpdateMappingPairs, Add2Ptr(Attribute, AttributeOffset), Attribute->RecordLength - AttributeOffset, NtfsMftOffset( Context ), RecordOffset, AttributeOffset, Vcb->BytesPerFileRecordSegment ); } // // Now do the mapping pairs update by calling the same // routine called at restart. // NtfsRestartChangeMapping( IrpContext, Vcb, FileRecord, RecordOffset, AttributeOffset, Add2Ptr(MappingPairs, MappingPairsOffset), MappingPairsSize - MappingPairsOffset ); // // If we were asked to stop on a Vcn, then the caller does not wish // us to modify the Scb. (Currently this is only done one time when // the Mft Data attribute no longer fits in the first file record.) // if (TruncateToVcn) { LONGLONG Size; // // We add one cluster to calculate the allocation size. // LastVcn = LastVcn + 1; Size = LlBytesFromClusters( Vcb, LastVcn ); Scb->Header.AllocationSize.QuadPart = Size; if (Scb->Header.ValidDataLength.QuadPart > Size) { Scb->Header.ValidDataLength.QuadPart = Size; } if (Scb->Header.FileSize.QuadPart > Size) { Scb->Header.FileSize.QuadPart = Size; } // // Possibly update ValidDataToDisk // if (Size < Scb->ValidDataToDisk) { Scb->ValidDataToDisk = Size; } } } finally { if (MappingPairs != MappingPairsBuffer) { NtfsFreePool(MappingPairs); } } DebugTrace( -1, Dbg, ("NtfsDeleteAttributeAllocation -> VOID\n") ); } BOOLEAN NtfsIsFileDeleteable ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, OUT PBOOLEAN NonEmptyIndex ) /*++ Routine Description: This look checks if a file may be deleted by examing all of the index attributes to check that they have no children. Note that once a file is marked for delete, we must insure that none of the conditions checked by this routine are allowed to change. For example, once the file is marked for delete, no links may be added, and no files may be created in any indices of this file. Arguments: Fcb - Fcb for the file. NonEmptyIndex - Address to store TRUE if the file is not deleteable because it contains an non-empty indexed attribute. Return Value: FALSE - If it is not ok to delete the specified file. TRUE - If it is ok to delete the specified file. --*/ { ATTRIBUTE_ENUMERATION_CONTEXT Context; PATTRIBUTE_RECORD_HEADER Attribute; BOOLEAN MoreToGo; PAGED_CODE(); NtfsInitializeAttributeContext( &Context ); try { // // Enumerate all of the attributes to check whether they may be deleted. // MoreToGo = NtfsLookupAttributeByCode( IrpContext, Fcb, &Fcb->FileReference, $INDEX_ROOT, &Context ); while (MoreToGo) { // // Point to the current attribute. // Attribute = NtfsFoundAttribute( &Context ); // // If the attribute is an index, then it must be empty. // if (!NtfsIsIndexEmpty( IrpContext, Attribute )) { *NonEmptyIndex = TRUE; break; } // // Go to the next attribute. // MoreToGo = NtfsLookupNextAttributeByCode( IrpContext, Fcb, $INDEX_ROOT, &Context ); } } finally { DebugUnwind( NtfsIsFileDeleteable ); NtfsCleanupAttributeContext( &Context ); } // // The File is deleteable if scanned the entire file record // and found no reasons we could not delete the file. // return (BOOLEAN)(!MoreToGo); } VOID NtfsDeleteFile ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN PSCB ParentScb OPTIONAL, IN OUT PNAME_PAIR NamePair OPTIONAL ) /*++ Routine Description: This routine may be called to see if it is the specified file may be deleted from the specified parent (i.e., if the specified parent were to be acquired exclusive). This routine should be called from fileinfo, to see whether it is ok to mark an open file for delete. NamePair will capture the names of the file being deleted if supplied. Note that once a file is marked for delete, none of we must insure that none of the conditions checked by this routine are allowed to change. For example, once the file is marked for delete, no links may be added, and no files may be created in any indices of this file. NOTE: The caller must have the Fcb and ParentScb exclusive to call this routine, Arguments: Fcb - Fcb for the file. ParentScb - Parent Scb via which the file was opened, and which would be acquired exclusive to perform the delete. There may be no parent Fcb for a file whose links have already been removed. NamePair - optionally provided to capture the name pair of a file Return Value: None --*/ { ATTRIBUTE_ENUMERATION_CONTEXT Context; PATTRIBUTE_RECORD_HEADER Attribute; PFILE_RECORD_SEGMENT_HEADER FileRecord; PVCB Vcb; BOOLEAN MoreToGo; ULONG RecordNumber; NUKEM LocalNuke; ULONG Pass; ULONG i; PNUKEM TempNukem; PNUKEM Nukem = &LocalNuke; ULONG NukemIndex = 0; BOOLEAN NonresidentAttributeList = FALSE; PAGED_CODE(); ASSERT_EXCLUSIVE_FCB( Fcb ); if (ARGUMENT_PRESENT( ParentScb )) { ASSERT_EXCLUSIVE_SCB( ParentScb ); } RtlZeroMemory( &LocalNuke, sizeof(NUKEM) ); Vcb = Fcb->Vcb; SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE ); try { // // We perform the delete in two passes. On the first pass we delete // all of the allocation for non-resident attributes except for // a security descriptor and for a non-resident attribute list (very rare). // // The second pass will delete remove the file names from their parent // indexes and add the file records to a Neukem array. // for (Pass = 0; Pass < 2; Pass += 1) { NtfsInitializeAttributeContext( &Context ); // // Enumerate all of the attributes to check whether they may be deleted. // MoreToGo = NtfsLookupAttribute( IrpContext, Fcb, &Fcb->FileReference, &Context ); while (MoreToGo) { // // Point to the current attribute. // Attribute = NtfsFoundAttribute( &Context ); // // All indices must be empty. // ASSERT( (Attribute->TypeCode != $INDEX_ROOT) || NtfsIsIndexEmpty( IrpContext, Attribute )); // // If the attribute is nonresident, then delete its allocation. // We only need to make the NtfsDeleteAllocation from record for // the attribute with lowest Vcn of zero. This will deallocate // all of the clusters for the file. // if (Attribute->FormCode == NONRESIDENT_FORM) { if (Pass == 0) { if (Attribute->TypeCode != $SECURITY_DESCRIPTOR && Attribute->Form.Nonresident.LowestVcn == 0) { NtfsDeleteAllocationFromRecord( IrpContext, Fcb, &Context, TRUE ); // // Reload the attribute pointer in the event it // was remapped. // Attribute = NtfsFoundAttribute( &Context ); } } else { if (Attribute->TypeCode == $SECURITY_DESCRIPTOR && Attribute->Form.Nonresident.LowestVcn == 0) { NtfsDeleteAllocationFromRecord( IrpContext, Fcb, &Context, FALSE ); // // Reload the attribute pointer in the event it // was remapped. // Attribute = NtfsFoundAttribute( &Context ); } } } else { #ifdef _CAIRO_ if (Pass == 0 && Attribute->TypeCode == $STANDARD_INFORMATION && Attribute->Form.Resident.ValueLength == sizeof( STANDARD_INFORMATION )) { ASSERT( !FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_ENABLED ) || NtfsPerformQuotaOperation( Fcb ) || ((PSTANDARD_INFORMATION) NtfsAttributeValue( Attribute ))->QuotaCharged == 0); // // Return all of the user's quota for this file. // if (NtfsPerformQuotaOperation( Fcb )) { LONGLONG Delta = -(LONGLONG) ((PSTANDARD_INFORMATION) NtfsAttributeValue( Attribute ))-> QuotaCharged; NtfsUpdateFileQuota( IrpContext, Fcb, &Delta, TRUE, FALSE ); } } #endif // _CAIRO_ } if (Pass == 1) { // // If the attribute is a file name, then it must be from our // caller's parent directory, or else we cannot delete. // if (Attribute->TypeCode == $FILE_NAME) { PFILE_NAME FileName; FileName = (PFILE_NAME)NtfsAttributeValue( Attribute ); ASSERT( ARGUMENT_PRESENT( ParentScb )); ASSERT(NtfsEqualMftRef(&FileName->ParentDirectory, &ParentScb->Fcb->FileReference)); if (ARGUMENT_PRESENT(NamePair)) { // // Squirrel away names // NtfsCopyNameToNamePair( NamePair, FileName->FileName, FileName->FileNameLength, FileName->Flags ); } NtfsDeleteIndexEntry( IrpContext, ParentScb, (PVOID)FileName, &Fcb->FileReference ); } // // If this file record is not already deleted, then do it now. // Note, we are counting on its contents not to change. // FileRecord = NtfsContainingFileRecord( &Context ); // // See if this is the same as the last one we remembered, else remember it. // if (Context.AttributeList.Bcb != NULL) { RecordNumber = NtfsUnsafeSegmentNumber( &Context.AttributeList.Entry->SegmentReference ); } else { RecordNumber = NtfsUnsafeSegmentNumber( &Fcb->FileReference ); } // // Now loop to see if we already remembered this record. // This reduces our pool allocation and also prevents us // from deleting file records twice. // TempNukem = Nukem; while (TempNukem != NULL) { for (i = 0; i < 4; i++) { if (TempNukem->RecordNumbers[i] == RecordNumber) { RecordNumber = 0; break; } } TempNukem = TempNukem->Next; } if (RecordNumber != 0) { // // Is the list full? If so allocate and initialize a new one. // if (NukemIndex > 3) { TempNukem = (PNUKEM)ExAllocateFromPagedLookasideList( &NtfsNukemLookasideList ); RtlZeroMemory( TempNukem, sizeof(NUKEM) ); TempNukem->Next = Nukem; Nukem = TempNukem; NukemIndex = 0; } // // Remember to delete this guy. (Note we can possibly list someone // more than once, but NtfsDeleteFileRecord handles that.) // Nukem->RecordNumbers[NukemIndex] = RecordNumber; NukemIndex += 1; } // // When we have the first attribute, check for the existance of // a non-resident attribute list. // } else if ((Attribute->TypeCode == $STANDARD_INFORMATION) && (Context.AttributeList.Bcb != NULL) && (!NtfsIsAttributeResident( Context.AttributeList.AttributeList ))) { NonresidentAttributeList = TRUE; } // // Go to the next attribute. // MoreToGo = NtfsLookupNextAttribute( IrpContext, Fcb, &Context ); } NtfsCleanupAttributeContext( &Context ); } // // Handle the unusual nonresident attribute list case // if (NonresidentAttributeList) { NtfsInitializeAttributeContext( &Context ); NtfsLookupAttributeByCode( IrpContext, Fcb, &Fcb->FileReference, $ATTRIBUTE_LIST, &Context ); NtfsDeleteAllocationFromRecord( IrpContext, Fcb, &Context, FALSE ); NtfsCleanupAttributeContext( &Context ); } // // Now loop to delete the file records. // while (Nukem != NULL) { for (i = 0; i < 4; i++) { if (Nukem->RecordNumbers[i] != 0) { NtfsDeallocateMftRecord( IrpContext, Vcb, Nukem->RecordNumbers[i] ); } } TempNukem = Nukem->Next; if (Nukem != &LocalNuke) { ExFreeToPagedLookasideList( &NtfsNukemLookasideList, Nukem ); } Nukem = TempNukem; } } finally { DebugUnwind( NtfsDeleteFile ); NtfsCleanupAttributeContext( &Context ); ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE ); } return; } VOID NtfsPrepareForUpdateDuplicate ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN OUT PLCB *Lcb, IN OUT PSCB *ParentScb, IN BOOLEAN AcquireShared ) /*++ Routine Description: This routine is called to prepare for updating the duplicate information. At the conclusion of this routine we will have the Lcb and Scb for the update along with the Scb acquired. This routine will look at the existing values for the input parameters in deciding what actions need to be done. Arguments: Fcb - Fcb for the file. The file must already be acquired exclusively. Lcb - This is the address to store the link to update. This may already have a value. ParentScb - This is the address to store the parent Scb for the update. This may already point to a valid Scb. AcquireShared - Indicates how to acquire the parent Scb. Return Value: None --*/ { PLIST_ENTRY Links; PLCB ThisLcb; PAGED_CODE(); // // Start by trying to guarantee we have an Lcb for the update. // if (*Lcb == NULL) { Links = Fcb->LcbQueue.Flink; while (Links != &Fcb->LcbQueue) { ThisLcb = CONTAINING_RECORD( Links, LCB, FcbLinks ); // // We can use this link if it is still present on the // disk and if we were passed a parent Scb, it matches // the one for this Lcb. // if (!FlagOn( ThisLcb->LcbState, LCB_STATE_LINK_IS_GONE ) && ((*ParentScb == NULL) || (*ParentScb == ThisLcb->Scb) || ((ThisLcb == Fcb->Vcb->RootLcb) && (*ParentScb == Fcb->Vcb->RootIndexScb)))) { *Lcb = ThisLcb; break; } Links = Links->Flink; } } // // If we have an Lcb, try to find the correct Scb. // if ((*Lcb != NULL) && (*ParentScb == NULL)) { if (*Lcb == Fcb->Vcb->RootLcb) { *ParentScb = Fcb->Vcb->RootIndexScb; } else { *ParentScb = (*Lcb)->Scb; } } // // Acquire the parent Scb and put it in the transaction queue in the // IrpContext. // if (*ParentScb != NULL) { if (AcquireShared) { NtfsAcquireSharedScbForTransaction( IrpContext, *ParentScb ); } else { NtfsAcquireExclusiveScb( IrpContext, *ParentScb ); } } return; } VOID NtfsUpdateDuplicateInfo ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN PLCB Lcb OPTIONAL, IN PSCB ParentScb OPTIONAL ) /*++ Routine Description: This routine is called to update the duplicate information for a file in the duplicated information of its parent. If the Lcb is specified then this parent is the parent to update. If the link is either an NTFS or DOS only link then we must update the complementary link as well. If no Lcb is specified then this open was by file id or the original link has been deleted. In that case we will try to find a different link to update. Arguments: Fcb - Fcb for the file. Lcb - This is the link to update. Specified only if this is not an open by Id operation. ParentScb - This is the parent directory for the Lcb link if specified. Return Value: None --*/ { PQUICK_INDEX QuickIndex = NULL; UCHAR Buffer[sizeof( FILE_NAME ) + 11 * sizeof( WCHAR )]; PFILE_NAME FileNameAttr; BOOLEAN AcquiredFcbTable = FALSE; BOOLEAN ReturnedExistingFcb = TRUE; BOOLEAN Found; UCHAR FileNameFlags; ATTRIBUTE_ENUMERATION_CONTEXT Context; PVCB Vcb = Fcb->Vcb; PFCB ParentFcb = NULL; PAGED_CODE(); ASSERT_EXCLUSIVE_FCB( Fcb ); // // Return immediately if the volume is locked. // if (FlagOn( Fcb->Vcb->VcbState, VCB_STATE_LOCKED )) { return; } NtfsInitializeAttributeContext( &Context ); try { // // If we are updating the entry for the root then we know the // file name attribute to build. // if (Fcb == Fcb->Vcb->RootIndexScb->Fcb) { Lcb = Fcb->Vcb->RootLcb; ParentScb = Fcb->Vcb->RootIndexScb; QuickIndex = &Fcb->Vcb->RootLcb->QuickIndex; FileNameAttr = (PFILE_NAME) &Buffer; RtlZeroMemory( FileNameAttr, sizeof( FILE_NAME )); NtfsBuildFileNameAttribute( IrpContext, &Fcb->FileReference, NtfsRootIndexString, FILE_NAME_DOS | FILE_NAME_NTFS, FileNameAttr ); // // If we have and Lcb then it is either present or we noop this update. // } else if (ARGUMENT_PRESENT( Lcb )) { if (!FlagOn( Lcb->LcbState, LCB_STATE_LINK_IS_GONE )) { QuickIndex = &Lcb->QuickIndex; FileNameAttr = Lcb->FileNameAttr; } else { leave; } // // If there is no Lcb then lookup the first filename attribute // and update its index entry. If there is a parent Scb then we // must find a file name attribute for the same parent or we could // get into a deadlock situation. // } else { // // We now have a name link to update. We will now need // an Scb for the parent index. Remember that we may // have to teardown the Scb. If we already have a ParentScb // then we must find a link to the same parent or to the root. // Otherwise we could hit a deadlock. // Found = NtfsLookupAttributeByCode( IrpContext, Fcb, &Fcb->FileReference, $FILE_NAME, &Context ); if (!Found) { NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb ); } // // Loop until we find a suitable link or there are no more on the file. // do { FileNameAttr = (PFILE_NAME) NtfsAttributeValue( NtfsFoundAttribute( &Context )); // // If there is a parent and this attribute has the same parent we are // done. Our caller will always have acquired the ParentScb. // if (ARGUMENT_PRESENT( ParentScb )) { if (NtfsEqualMftRef( &FileNameAttr->ParentDirectory, &ParentScb->Fcb->FileReference )) { ASSERT_SHARED_SCB( ParentScb ); break; } // // If this is the parent of this link is the root then // acquire the root directory. // } else if (NtfsEqualMftRef( &FileNameAttr->ParentDirectory, &Vcb->RootIndexScb->Fcb->FileReference )) { ParentScb = Vcb->RootIndexScb; NtfsAcquireSharedScbForTransaction( IrpContext, ParentScb ); break; // // We have a link for this file. If we weren't given a parent // Scb then create one here. // } else if (!ARGUMENT_PRESENT( ParentScb )) { NtfsAcquireFcbTable( IrpContext, Vcb ); AcquiredFcbTable = TRUE; ParentFcb = NtfsCreateFcb( IrpContext, Vcb, FileNameAttr->ParentDirectory, FALSE, TRUE, &ReturnedExistingFcb ); ParentFcb->ReferenceCount += 1; if (!NtfsAcquireExclusiveFcb( IrpContext, ParentFcb, NULL, TRUE, TRUE )) { NtfsReleaseFcbTable( IrpContext, Vcb ); NtfsAcquireExclusiveFcb( IrpContext, ParentFcb, NULL, TRUE, FALSE ); NtfsAcquireFcbTable( IrpContext, Vcb ); } ParentFcb->ReferenceCount -= 1; NtfsReleaseFcbTable( IrpContext, Vcb ); AcquiredFcbTable = FALSE; ParentScb = NtfsCreateScb( IrpContext, ParentFcb, $INDEX_ALLOCATION, &NtfsFileNameIndex, FALSE, NULL ); NtfsAcquireExclusiveScb( IrpContext, ParentScb ); break; } } while (Found = NtfsLookupNextAttributeByCode( IrpContext, Fcb, $FILE_NAME, &Context )); // // If we didn't find anything then return. // if (!Found) { leave; } } // // Now update the filename in the parent index. // NtfsUpdateFileNameInIndex( IrpContext, ParentScb, FileNameAttr, &Fcb->Info, QuickIndex ); // // If this filename is either NTFS-ONLY or DOS-ONLY then // we need to find the other link. // if ((FileNameAttr->Flags == FILE_NAME_NTFS) || (FileNameAttr->Flags == FILE_NAME_DOS)) { // // Find out which flag we should be looking for. // if (FlagOn( FileNameAttr->Flags, FILE_NAME_NTFS )) { FileNameFlags = FILE_NAME_DOS; } else { FileNameFlags = FILE_NAME_NTFS; } if (!ARGUMENT_PRESENT( Lcb )) { NtfsCleanupAttributeContext( &Context ); NtfsInitializeAttributeContext( &Context ); } // // Now scan for the filename attribute we need. // Found = NtfsLookupAttributeByCode( IrpContext, Fcb, &Fcb->FileReference, $FILE_NAME, &Context ); while (Found) { FileNameAttr = (PFILE_NAME) NtfsAttributeValue( NtfsFoundAttribute( &Context )); if (FileNameAttr->Flags == FileNameFlags) { break; } Found = NtfsLookupNextAttributeByCode( IrpContext, Fcb, $FILE_NAME, &Context ); } // // We should have found the entry. // if (!Found) { NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb ); } NtfsUpdateFileNameInIndex( IrpContext, ParentScb, FileNameAttr, &Fcb->Info, NULL ); } } finally { DebugUnwind( NtfsUpdateDuplicateInfo ); if (AcquiredFcbTable) { NtfsReleaseFcbTable( IrpContext, Vcb ); } // // Cleanup the attribute context for this attribute search. // NtfsCleanupAttributeContext( &Context ); // // If we created the ParentFcb here then release it and // call teardown on it. // if (!ReturnedExistingFcb) { NtfsTeardownStructures( IrpContext, ParentFcb, NULL, FALSE, FALSE, NULL ); } } return; } VOID NtfsUpdateLcbDuplicateInfo ( IN PFCB Fcb, IN PLCB Lcb ) /*++ Routine Description: This routine is called after updating duplicate information via an Lcb. We want to clear the info flags for this Lcb and any complementary Lcb it may be part of. We also want to OR in the Info flags in the Fcb with any other Lcb's attached to the Fcb so we will update those in a timely fashion as well. Arguments: Fcb - Fcb for the file. Lcb - Lcb used to update duplicate information. It may not be present but that would be a rare case and we will perform that test here. Return Value: None --*/ { UCHAR FileNameFlags; PLCB NextLcb; PLIST_ENTRY Links; PAGED_CODE(); // // No work to do unless we were passed an Lcb. // if (Lcb != NULL) { // // Check if this is an NTFS only or DOS only link. // if (Lcb->FileNameAttr->Flags == FILE_NAME_NTFS) { FileNameFlags = FILE_NAME_DOS; } else if (Lcb->FileNameAttr->Flags == FILE_NAME_DOS) { FileNameFlags = FILE_NAME_NTFS; } else { FileNameFlags = (UCHAR) -1; } Lcb->InfoFlags = 0; Links = Fcb->LcbQueue.Flink; do { NextLcb = CONTAINING_RECORD( Links, LCB, FcbLinks ); if (NextLcb != Lcb) { if (NextLcb->FileNameAttr->Flags == FileNameFlags) { NextLcb->InfoFlags = 0; } else { SetFlag( NextLcb->InfoFlags, Fcb->InfoFlags ); } } Links = Links->Flink; } while (Links != &Fcb->LcbQueue); } return; } VOID NtfsUpdateFcb ( IN PFCB Fcb ) /*++ Routine Description: This routine is called when a timestamp may be updated on an Fcb which may have no open handles. We always update the last change time to the current time as well as last access and last write. Arguments: Fcb - Fcb for the file. Return Value: None --*/ { PAGED_CODE(); // // We need to update the parent directory's time stamps // to reflect this change. // KeQuerySystemTime( (PLARGE_INTEGER)&Fcb->Info.LastChangeTime ); SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO ); SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_CHANGE | FCB_INFO_CHANGED_LAST_MOD ); Fcb->CurrentLastAccess = Fcb->Info.LastModificationTime = Fcb->Info.LastChangeTime; return; } VOID NtfsAddLink ( IN PIRP_CONTEXT IrpContext, IN BOOLEAN CreatePrimaryLink, IN PSCB ParentScb, IN PFCB Fcb, IN PFILE_NAME FileNameAttr, IN PBOOLEAN LogIt OPTIONAL, OUT PUCHAR FileNameFlags, OUT PQUICK_INDEX QuickIndex OPTIONAL, IN PNAME_PAIR NamePair OPTIONAL ) /*++ Routine Description: This routine adds a link to a file by adding the filename attribute for the filename to the file and inserting the name in the parent Scb index. If we are creating the primary link for the file and need to generate an auxilary name, we will do that here. Use the optional NamePair to suggest auxilary names if provided. Arguments: CreatePrimaryLink - Indicates if we are creating the main Ntfs name for the file. ParentScb - This is the Scb to add the index entry for this link to. Fcb - This is the file to add the hard link to. FileNameAttr - File name attribute which is guaranteed only to have the name in it. LogIt - Indicates whether we should log the creation of this name. If not specified then we always log the name creation. On exit we will update this to TRUE if we logged the name creation because it might cause a split. FileNameFlags - We return the file name flags we use to create the link. QuickIndex - If specified, supplies a pointer to a quik lookup structure to be updated by this routine. NamePair - If specified, supplies names that will be checked first as possible auxilary names Return Value: None --*/ { BOOLEAN LocalLogIt = TRUE; PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsAddLink: Entered\n") ); if (!ARGUMENT_PRESENT( LogIt )) { LogIt = &LocalLogIt; } *FileNameFlags = 0; // // Next add this entry to parent. It is possible that this is a link, // an Ntfs name, a DOS name or Ntfs/Dos name. We use the filename // attribute structure from earlier, but need to add more information. // FileNameAttr->ParentDirectory = ParentScb->Fcb->FileReference; RtlCopyMemory( &FileNameAttr->Info, &Fcb->Info, sizeof( DUPLICATED_INFORMATION )); FileNameAttr->Flags = 0; // // We will override the CreatePrimaryLink with the value in the // registry. // NtfsAddNameToParent( IrpContext, ParentScb, Fcb, (BOOLEAN)((FlagOn( NtfsData.Flags, NTFS_FLAGS_CREATE_8DOT3_NAMES ) && CreatePrimaryLink) ? TRUE : FALSE), LogIt, FileNameAttr, FileNameFlags, QuickIndex, NamePair ); // // If the name is Ntfs only, we need to generate the DOS name. // if (*FileNameFlags == FILE_NAME_NTFS) { UNICODE_STRING NtfsName; NtfsName.Length = (USHORT)(FileNameAttr->FileNameLength * sizeof(WCHAR)); NtfsName.Buffer = FileNameAttr->FileName; NtfsAddDosOnlyName( IrpContext, ParentScb, Fcb, NtfsName, *LogIt, (NamePair ? &NamePair->Short : NULL) ); } DebugTrace( -1, Dbg, ("NtfsAddLink: Exit\n") ); return; } VOID NtfsRemoveLink ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN PSCB ParentScb, IN UNICODE_STRING LinkName, IN OUT PNAME_PAIR NamePair OPTIONAL ) /*++ Routine Description: This routine removes a hard link to a file by removing the filename attribute for the filename from the file and removing the name from the parent Scb index. It will also remove the other half of a primary link pair. A name pair may be used to capture the names. Arguments: Fcb - This is the file to remove the hard link from ParentScb - This is the Scb to remove the index entry for this link from LinkName - This is the file name to remove. It will be exact case. NamePair - optional name pair for capture Return Value: None --*/ { ATTRIBUTE_ENUMERATION_CONTEXT AttrContext; PFILE_NAME FoundFileName; UCHAR FileNameFlags; PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsRemoveLink: Entered\n") ); NtfsInitializeAttributeContext( &AttrContext ); // // Use a try-finally to facilitate cleanup. // try { // // Now loop through the filenames and find a match. // We better find at least one. // if (!NtfsLookupAttributeByCode( IrpContext, Fcb, &Fcb->FileReference, $FILE_NAME, &AttrContext )) { DebugTrace( 0, Dbg, ("Can't find filename attribute Fcb @ %08lx\n", Fcb) ); NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb ); } // // Now keep looking until we find a match. // while (TRUE) { FoundFileName = (PFILE_NAME) NtfsAttributeValue( NtfsFoundAttribute( &AttrContext )); // // Do an exact memory comparison. // if ((*(PLONGLONG)&FoundFileName->ParentDirectory == *(PLONGLONG)&ParentScb->Fcb->FileReference ) && ((FoundFileName->FileNameLength * sizeof( WCHAR )) == (ULONG)LinkName.Length) && (RtlEqualMemory( LinkName.Buffer, FoundFileName->FileName, LinkName.Length ))) { break; } // // Get the next filename attribute. // if (!NtfsLookupNextAttributeByCode( IrpContext, Fcb, $FILE_NAME, &AttrContext )) { DebugTrace( 0, Dbg, ("Can't find filename attribute Fcb @ %08lx\n", Fcb) ); NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb ); } } // // Capture the name into caller's area // if (ARGUMENT_PRESENT(NamePair)) { NtfsCopyNameToNamePair( NamePair, FoundFileName->FileName, FoundFileName->FileNameLength, FoundFileName->Flags ); } // // Now delete the name from the parent Scb. // NtfsDeleteIndexEntry( IrpContext, ParentScb, FoundFileName, &Fcb->FileReference ); // // Remember the filename flags for this entry. // FileNameFlags = FoundFileName->Flags; // // Now delete the entry. // NtfsDeleteAttributeRecord( IrpContext, Fcb, TRUE, FALSE, &AttrContext ); // // If the link is a partial link, we need to remove the second // half of the link. // if (FlagOn( FileNameFlags, (FILE_NAME_NTFS | FILE_NAME_DOS) ) && (FileNameFlags != (FILE_NAME_NTFS | FILE_NAME_DOS))) { NtfsRemoveLinkViaFlags( IrpContext, Fcb, ParentScb, (UCHAR)(FlagOn( FileNameFlags, FILE_NAME_NTFS ) ? FILE_NAME_DOS : FILE_NAME_NTFS), NamePair ); } } finally { DebugUnwind( NtfsRemoveLink ); NtfsCleanupAttributeContext( &AttrContext ); DebugTrace( -1, Dbg, ("NtfsRemoveLink: Exit\n") ); } return; } VOID NtfsRemoveLinkViaFlags ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN PSCB Scb, IN UCHAR FileNameFlags, IN OUT PNAME_PAIR NamePair OPTIONAL ) /*++ Routine Description: This routine is called to remove only a Dos name or only an Ntfs name. We already must know that these will be described by separate filename attributes. A name pair may be used to capture the name. Arguments: Fcb - This is the file to remove the hard link from ParentScb - This is the Scb to remove the index entry for this link from FileNameFlags - This is the single name flag that we must match exactly. NamePair - optional name pair for capture Return Value: None --*/ { ATTRIBUTE_ENUMERATION_CONTEXT AttrContext; PFILE_NAME FileNameAttr; PFILE_NAME FoundFileName; PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsRemoveLinkViaFlags: Entered\n") ); NtfsInitializeAttributeContext( &AttrContext ); FileNameAttr = NULL; // // Use a try-finally to facilitate cleanup. // try { // // Now loop through the filenames and find a match. // We better find at least one. // if (!NtfsLookupAttributeByCode( IrpContext, Fcb, &Fcb->FileReference, $FILE_NAME, &AttrContext )) { DebugTrace( 0, Dbg, ("Can't find filename attribute Fcb @ %08lx\n", Fcb) ); NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb ); } // // Now keep looking until we find a match. // while (TRUE) { FoundFileName = (PFILE_NAME) NtfsAttributeValue( NtfsFoundAttribute( &AttrContext )); // // Check for an exact flag match. // if ((*(PLONGLONG)&FoundFileName->ParentDirectory == *(PLONGLONG)&Scb->Fcb->FileReference) && (FoundFileName->Flags == FileNameFlags)) { break; } // // Get the next filename attribute. // if (!NtfsLookupNextAttributeByCode( IrpContext, Fcb, $FILE_NAME, &AttrContext )) { DebugTrace( 0, Dbg, ("Can't find filename attribute Fcb@ %08lx\n", Fcb) ); NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb ); } } // // Capture the name into caller's area // if (ARGUMENT_PRESENT(NamePair)) { NtfsCopyNameToNamePair( NamePair, FoundFileName->FileName, FoundFileName->FileNameLength, FoundFileName->Flags ); } FileNameAttr = NtfsAllocatePool(PagedPool, sizeof( FILE_NAME ) + (FoundFileName->FileNameLength << 1)); // // We build the file name attribute for the search. // RtlCopyMemory( FileNameAttr, FoundFileName, NtfsFileNameSize( FoundFileName )); // // Now delete the entry. // NtfsDeleteAttributeRecord( IrpContext, Fcb, TRUE, FALSE, &AttrContext ); // // Now delete the name from the parent Scb. // NtfsDeleteIndexEntry( IrpContext, Scb, FileNameAttr, &Fcb->FileReference ); } finally { DebugUnwind( NtfsRemoveLinkViaFlags ); NtfsCleanupAttributeContext( &AttrContext ); NtfsFreePool( FileNameAttr ); DebugTrace( -1, Dbg, ("NtfsRemoveLinkViaFlags: Exit\n") ); } return; } // // This routine is intended only for RESTART. // VOID NtfsRestartInsertAttribute ( IN PIRP_CONTEXT IrpContext, IN PFILE_RECORD_SEGMENT_HEADER FileRecord, IN ULONG RecordOffset, IN PATTRIBUTE_RECORD_HEADER Attribute, IN PUNICODE_STRING AttributeName OPTIONAL, IN PVOID ValueOrMappingPairs OPTIONAL, IN ULONG Length ) /*++ Routine Description: This routine performs a simple insert of an attribute record into a file record, without worrying about Bcbs or logging. Arguments: FileRecord - File record into which the attribute is to be inserted. RecordOffset - ByteOffset within the file record at which insert is to occur. Attribute - The attribute record to be inserted. AttributeName - May pass an optional attribute name in the running system only. ValueOrMappingPairs - May pass a value or mapping pairs pointer in the running system only. Length - Length of the value or mapping pairs array in bytes - nonzero in the running system only. If nonzero and the above pointer is NULL, then a value is to be zeroed. Return Value: None --*/ { PVOID From, To; ULONG MoveLength; ULONG AttributeHeaderSize; ASSERT_IRP_CONTEXT( IrpContext ); ASSERT( Attribute->RecordLength == QuadAlign( Attribute->RecordLength )); PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsRestartInsertAttribute\n") ); DebugTrace( 0, Dbg, ("FileRecord = %08lx\n", FileRecord) ); DebugTrace( 0, Dbg, ("RecordOffset = %08lx\n", RecordOffset) ); DebugTrace( 0, Dbg, ("Attribute = %08lx\n", Attribute) ); // // First make room for the attribute // From = (PCHAR)FileRecord + RecordOffset; To = (PCHAR)From + Attribute->RecordLength; MoveLength = FileRecord->FirstFreeByte - RecordOffset; RtlMoveMemory( To, From, MoveLength ); // // If there is either an attribute name or Length is nonzero, then // we are in the running system, and we are to assemble the attribute // in place. // if ((Length != 0) || ARGUMENT_PRESENT(AttributeName)) { // // First move the attribute header in. // if (Attribute->FormCode == RESIDENT_FORM) { AttributeHeaderSize = SIZEOF_RESIDENT_ATTRIBUTE_HEADER; } else if (Attribute->NameOffset != 0) { AttributeHeaderSize = Attribute->NameOffset; } else { AttributeHeaderSize = Attribute->Form.Nonresident.MappingPairsOffset; } RtlCopyMemory( From, Attribute, AttributeHeaderSize ); if (ARGUMENT_PRESENT(AttributeName)) { RtlCopyMemory( (PCHAR)From + Attribute->NameOffset, AttributeName->Buffer, AttributeName->Length ); } // // If a value was specified, move it in. Else the caller just wants us // to clear for that much. // if (ARGUMENT_PRESENT(ValueOrMappingPairs)) { RtlCopyMemory( (PCHAR)From + ((Attribute->FormCode == RESIDENT_FORM) ? Attribute->Form.Resident.ValueOffset : Attribute->Form.Nonresident.MappingPairsOffset), ValueOrMappingPairs, Length ); // // Only the resident form will pass a NULL pointer. // } else { RtlZeroMemory( (PCHAR)From + Attribute->Form.Resident.ValueOffset, Length ); } // // For the restart case, we really only have to insert the attribute. // (Note we can also hit this case in the running system when a resident // attribute is being created with no name and a null value.) // } else { // // Now move the attribute in. // RtlCopyMemory( From, Attribute, Attribute->RecordLength ); } // // Update the file record. // FileRecord->FirstFreeByte += Attribute->RecordLength; // // We only need to do this if we would be incrementing the instance // number. In the abort or restart case, we don't need to do this. // if (FileRecord->NextAttributeInstance <= Attribute->Instance) { FileRecord->NextAttributeInstance = Attribute->Instance + 1; } // // Remember to increment the reference count if this attribute is indexed. // if (FlagOn(Attribute->Form.Resident.ResidentFlags, RESIDENT_FORM_INDEXED)) { FileRecord->ReferenceCount += 1; } DebugTrace( -1, Dbg, ("NtfsRestartInsertAttribute -> VOID\n") ); return; } // // This routine is intended only for RESTART. // VOID NtfsRestartRemoveAttribute ( IN PIRP_CONTEXT IrpContext, IN PFILE_RECORD_SEGMENT_HEADER FileRecord, IN ULONG RecordOffset ) /*++ Routine Description: This routine performs a simple remove of an attribute record from a file record, without worrying about Bcbs or logging. Arguments: FileRecord - File record from which the attribute is to be removed. RecordOffset - ByteOffset within the file record at which remove is to occur. Return Value: None --*/ { PATTRIBUTE_RECORD_HEADER Attribute; ASSERT_IRP_CONTEXT( IrpContext ); PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsRestartRemoveAttribute\n") ); DebugTrace( 0, Dbg, ("FileRecord = %08lx\n", FileRecord) ); DebugTrace( 0, Dbg, ("RecordOffset = %08lx\n", RecordOffset) ); // // Calculate the address of the attribute we are removing. // Attribute = (PATTRIBUTE_RECORD_HEADER)((PCHAR)FileRecord + RecordOffset); ASSERT( Attribute->RecordLength == QuadAlign( Attribute->RecordLength )); // // Reduce first free byte by the amount we removed. // FileRecord->FirstFreeByte -= Attribute->RecordLength; // // Remember to decrement the reference count if this attribute is indexed. // if (FlagOn(Attribute->Form.Resident.ResidentFlags, RESIDENT_FORM_INDEXED)) { FileRecord->ReferenceCount -= 1; } // // Remove the attribute by moving the rest of the record down. // RtlMoveMemory( Attribute, (PCHAR)Attribute + Attribute->RecordLength, FileRecord->FirstFreeByte - RecordOffset ); DebugTrace( -1, Dbg, ("NtfsRestartRemoveAttribute -> VOID\n") ); return; } // // This routine is intended only for RESTART. // VOID NtfsRestartChangeAttributeSize ( IN PIRP_CONTEXT IrpContext, IN PFILE_RECORD_SEGMENT_HEADER FileRecord, IN PATTRIBUTE_RECORD_HEADER Attribute, IN ULONG NewRecordLength ) /*++ Routine Description: This routine changes the size of an attribute, and makes the related changes in the attribute record. Arguments: FileRecord - Pointer to the file record in which the attribute resides. Attribute - Pointer to the attribute whose size is changing. NewRecordLength - New attribute record length. Return Value: None. --*/ { LONG SizeChange = NewRecordLength - Attribute->RecordLength; PVOID AttributeEnd = Add2Ptr(Attribute, Attribute->RecordLength); UNREFERENCED_PARAMETER( IrpContext ); PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsRestartChangeAttributeSize\n") ); DebugTrace( 0, Dbg, ("FileRecord = %08lx\n", FileRecord) ); DebugTrace( 0, Dbg, ("Attribute = %08lx\n", Attribute) ); DebugTrace( 0, Dbg, ("NewRecordLength = %08lx\n", NewRecordLength) ); // // First move the end of the file record after the attribute we are changing. // RtlMoveMemory( Add2Ptr(Attribute, NewRecordLength), AttributeEnd, FileRecord->FirstFreeByte - PtrOffset(FileRecord, AttributeEnd) ); // // Now update the file and attribute records. // FileRecord->FirstFreeByte += SizeChange; Attribute->RecordLength = NewRecordLength; DebugTrace( -1, Dbg, ("NtfsRestartChangeAttributeSize -> VOID\n") ); } // // This routine is intended only for RESTART. // VOID NtfsRestartChangeValue ( IN PIRP_CONTEXT IrpContext, IN PFILE_RECORD_SEGMENT_HEADER FileRecord, IN ULONG RecordOffset, IN ULONG AttributeOffset, IN PVOID Data, IN ULONG Length, IN BOOLEAN SetNewLength ) /*++ Routine Description: This routine performs a simple change of an attribute value in a file record, without worrying about Bcbs or logging. Arguments: FileRecord - File record in which the attribute is to be changed. RecordOffset - ByteOffset within the file record at which the attribute starts. AttributeOffset - Offset within the attribute record at which data is to be changed. Data - Pointer to the new data. Length - Length of the new data. SetNewLength - TRUE if the attribute length should be changed. Return Value: None --*/ { PATTRIBUTE_RECORD_HEADER Attribute; BOOLEAN AlreadyMoved = FALSE; BOOLEAN DataInFileRecord = FALSE; ASSERT_IRP_CONTEXT( IrpContext ); PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsRestartChangeValue\n") ); DebugTrace( 0, Dbg, ("FileRecord = %08lx\n", FileRecord) ); DebugTrace( 0, Dbg, ("RecordOffset = %08lx\n", RecordOffset) ); DebugTrace( 0, Dbg, ("AttributeOffset = %08lx\n", AttributeOffset) ); DebugTrace( 0, Dbg, ("Data = %08lx\n", Data) ); DebugTrace( 0, Dbg, ("Length = %08lx\n", Length) ); DebugTrace( 0, Dbg, ("SetNewLength = %02lx\n", SetNewLength) ); // // Calculate the address of the attribute being changed. // Attribute = (PATTRIBUTE_RECORD_HEADER)((PCHAR)FileRecord + RecordOffset); ASSERT( Attribute->RecordLength == QuadAlign( Attribute->RecordLength )); ASSERT( RecordOffset == QuadAlign( RecordOffset )); // // First, if we are setting a new length, then move the data after the // attribute record and change FirstFreeByte accordingly. // if (SetNewLength) { ULONG NewLength = QuadAlign( AttributeOffset + Length ); // // If we are shrinking the attribute, we need to move the data // first to support caller's who are shifting data down in the // attribute value, like DeleteFromAttributeList. If we were // to shrink the record first in this case, we would clobber some // of the data to be moved down. // if (NewLength < Attribute->RecordLength) { // // Now move the new data in and remember we moved it. // AlreadyMoved = TRUE; // // If there is data to modify do so now. // if (Length != 0) { if (ARGUMENT_PRESENT(Data)) { RtlMoveMemory( (PCHAR)Attribute + AttributeOffset, Data, Length ); } else { RtlZeroMemory( (PCHAR)Attribute + AttributeOffset, Length ); } } } // // First move the tail of the file record to make/eliminate room. // RtlMoveMemory( Add2Ptr( Attribute, NewLength ), Add2Ptr( Attribute, Attribute->RecordLength ), FileRecord->FirstFreeByte - RecordOffset - Attribute->RecordLength ); // // Now update fields to reflect the change. // FileRecord->FirstFreeByte += (NewLength - Attribute->RecordLength); Attribute->RecordLength = NewLength; Attribute->Form.Resident.ValueLength = (USHORT)(AttributeOffset + Length - (ULONG)Attribute->Form.Resident.ValueOffset); } // // Now move the new data in. // if (!AlreadyMoved) { if (ARGUMENT_PRESENT(Data)) { RtlMoveMemory( Add2Ptr( Attribute, AttributeOffset ), Data, Length ); } else { RtlZeroMemory( Add2Ptr( Attribute, AttributeOffset ), Length ); } } DebugTrace( -1, Dbg, ("NtfsRestartChangeValue -> VOID\n") ); return; } // // This routine is intended only for RESTART. // VOID NtfsRestartChangeMapping ( IN PIRP_CONTEXT IrpContext, IN PVCB Vcb, IN PFILE_RECORD_SEGMENT_HEADER FileRecord, IN ULONG RecordOffset, IN ULONG AttributeOffset, IN PVOID Data, IN ULONG Length ) /*++ Routine Description: This routine performs a simple change of an attribute's mapping pairs in a file record, without worrying about Bcbs or logging. Arguments: Vcb - Vcb for volume FileRecord - File record in which the attribute is to be changed. RecordOffset - ByteOffset within the file record at which the attribute starts. AttributeOffset - Offset within the attribute record at which mapping is to be changed. Data - Pointer to the new mapping. Length - Length of the new mapping. Return Value: None --*/ { PATTRIBUTE_RECORD_HEADER Attribute; VCN HighestVcn; PCHAR MappingPairs; ULONG NewLength = QuadAlign( AttributeOffset + Length ); ASSERT_IRP_CONTEXT( IrpContext ); UNREFERENCED_PARAMETER( Vcb ); PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsRestartChangeMapping\n") ); DebugTrace( 0, Dbg, ("FileRecord = %08lx\n", FileRecord) ); DebugTrace( 0, Dbg, ("RecordOffset = %08lx\n", RecordOffset) ); DebugTrace( 0, Dbg, ("AttributeOffset = %08lx\n", AttributeOffset) ); DebugTrace( 0, Dbg, ("Data = %08lx\n", Data) ); DebugTrace( 0, Dbg, ("Length = %08lx\n", Length) ); // // Calculate the address of the attribute being changed. // Attribute = (PATTRIBUTE_RECORD_HEADER)((PCHAR)FileRecord + RecordOffset); ASSERT( Attribute->RecordLength == QuadAlign( Attribute->RecordLength )); ASSERT( RecordOffset == QuadAlign( RecordOffset )); // // First, if we are setting a new length, then move the data after the // attribute record and change FirstFreeByte accordingly. // // // First move the tail of the file record to make/eliminate room. // RtlMoveMemory( (PCHAR)Attribute + NewLength, (PCHAR)Attribute + Attribute->RecordLength, FileRecord->FirstFreeByte - RecordOffset - Attribute->RecordLength ); // // Now update fields to reflect the change. // FileRecord->FirstFreeByte += NewLength - Attribute->RecordLength; Attribute->RecordLength = NewLength; // // Now move the new data in. // RtlCopyMemory( (PCHAR)Attribute + AttributeOffset, Data, Length ); // // Finally update HighestVcn and (optionally) AllocatedLength fields. // MappingPairs = (PCHAR)Attribute + (ULONG)Attribute->Form.Nonresident.MappingPairsOffset; HighestVcn = NtfsGetHighestVcn( IrpContext, Attribute->Form.Nonresident.LowestVcn, MappingPairs ); Attribute->Form.Nonresident.HighestVcn = HighestVcn; DebugTrace( -1, Dbg, ("NtfsRestartChangeMapping -> VOID\n") ); return; } VOID NtfsAddToAttributeList ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN MFT_SEGMENT_REFERENCE SegmentReference, IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context ) /*++ Routine Description: This routine adds an attribute list entry for a newly inserted attribute. It is assumed that the context variable is pointing to the attribute record in the file record where it has been inserted, and also to the place in the attribute list where the new attribute list entry is to be inserted. Arguments: Fcb - Requested file. SegmentReference - Segment reference of the file record the new attribute is in. Context - Describes the current attribute. Return Value: None --*/ { // // Allocate an attribute list entry which hopefully has enough space // for the name. // struct { ATTRIBUTE_LIST_ENTRY EntryBuffer; WCHAR Name[10]; } NewEntry; ATTRIBUTE_ENUMERATION_CONTEXT ListContext; BOOLEAN FoundListContext; ULONG EntrySize; PFILE_RECORD_SEGMENT_HEADER FileRecord; PATTRIBUTE_RECORD_HEADER Attribute; PATTRIBUTE_LIST_ENTRY ListEntry = &NewEntry.EntryBuffer; BOOLEAN SetNewLength = TRUE; ULONG EntryOffset; ULONG BeyondEntryOffset; PAGED_CODE(); // // First construct the attribute list entry. // FileRecord = NtfsContainingFileRecord( Context ); Attribute = NtfsFoundAttribute( Context ); EntrySize = QuadAlign( FIELD_OFFSET( ATTRIBUTE_LIST_ENTRY, AttributeName ) + ((ULONG) Attribute->NameLength << 1)); // // Allocate the list entry if the one we have is not big enough. // if (EntrySize > sizeof(NewEntry)) { ListEntry = (PATTRIBUTE_LIST_ENTRY)NtfsAllocatePool( NonPagedPool, EntrySize ); } RtlZeroMemory( ListEntry, EntrySize ); NtfsInitializeAttributeContext( &ListContext ); // // Use try-finally to insure cleanup. // try { ULONG OldQuadAttrListSize; PATTRIBUTE_RECORD_HEADER ListAttribute; PFILE_RECORD_SEGMENT_HEADER ListFileRecord; // // Now fill in the list entry. // ListEntry->AttributeTypeCode = Attribute->TypeCode; ListEntry->RecordLength = (USHORT)EntrySize; ListEntry->AttributeNameLength = Attribute->NameLength; ListEntry->Instance = Attribute->Instance; ListEntry->AttributeNameOffset = (UCHAR)PtrOffset( ListEntry, &ListEntry->AttributeName[0] ); if (Attribute->FormCode == NONRESIDENT_FORM) { ListEntry->LowestVcn = Attribute->Form.Nonresident.LowestVcn; } ListEntry->SegmentReference = SegmentReference; if (Attribute->NameLength != 0) { RtlCopyMemory( &ListEntry->AttributeName[0], Add2Ptr(Attribute, Attribute->NameOffset), Attribute->NameLength << 1 ); } // // Lookup the list context so that we can modify the attribute list. // FoundListContext = NtfsLookupAttributeByCode( IrpContext, Fcb, &Fcb->FileReference, $ATTRIBUTE_LIST, &ListContext ); ASSERT( FoundListContext ); ListAttribute = NtfsFoundAttribute( &ListContext ); ListFileRecord = NtfsContainingFileRecord( &ListContext ); OldQuadAttrListSize = ListAttribute->RecordLength; // // Remember the relative offsets of list entries. // EntryOffset = (ULONG) PtrOffset( Context->AttributeList.FirstEntry, Context->AttributeList.Entry ); BeyondEntryOffset = (ULONG) PtrOffset( Context->AttributeList.FirstEntry, Context->AttributeList.BeyondFinalEntry ); // // If this operation is possibly going to make the attribute list go // non-resident, or else move other attributes around, then we will // reserve the space first in the attribute list and then map the // value. Note that some of the entries we need to shift up may // be modified as a side effect of making space! // if (NtfsIsAttributeResident( ListAttribute ) && (ListFileRecord->BytesAvailable - ListFileRecord->FirstFreeByte) < EntrySize) { ULONG Length; // // Add enough zeros to the end of the attribute to accommodate // the new attribute list entry. // NtfsChangeAttributeValue( IrpContext, Fcb, BeyondEntryOffset, NULL, EntrySize, TRUE, TRUE, FALSE, TRUE, &ListContext ); // // We now don't have to set the new length. // SetNewLength = FALSE; // // In case the attribute list went non-resident on this call, then we // need to update both list entry pointers in the found attribute. // (We do this "just in case" all the time to avoid a rare code path.) // // // Map the non-resident attribute list. // NtfsMapAttributeValue( IrpContext, Fcb, (PVOID *) &Context->AttributeList.FirstEntry, &Length, &Context->AttributeList.NonresidentListBcb, &ListContext ); // // If the list is still resident then unpin the current Bcb in // the original context to keep our pin counts in sync. // if (Context->AttributeList.Bcb == Context->AttributeList.NonresidentListBcb) { NtfsUnpinBcb( &Context->AttributeList.NonresidentListBcb ); } Context->AttributeList.Entry = Add2Ptr( Context->AttributeList.FirstEntry, EntryOffset ); Context->AttributeList.BeyondFinalEntry = Add2Ptr( Context->AttributeList.FirstEntry, BeyondEntryOffset ); } // // Check for adding duplicate entries... // ASSERT( ((EntryOffset == 0) || (!RtlEqualMemory((PVOID)((PCHAR)Context->AttributeList.Entry - EntrySize), ListEntry, EntrySize))) && ((BeyondEntryOffset == EntryOffset) || (!RtlEqualMemory(Context->AttributeList.Entry, ListEntry, EntrySize))) ); // // Now shift the old contents up to make room for our new entry. We don't let // the attribute list grow larger than a cache view however. // if (EntrySize + BeyondEntryOffset > VACB_MAPPING_GRANULARITY) { NtfsRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES, NULL, NULL ); } NtfsChangeAttributeValue( IrpContext, Fcb, EntryOffset + EntrySize, Context->AttributeList.Entry, BeyondEntryOffset - EntryOffset, SetNewLength, TRUE, FALSE, TRUE, &ListContext ); // // Now write in the new entry. // NtfsChangeAttributeValue( IrpContext, Fcb, EntryOffset, (PVOID)ListEntry, EntrySize, FALSE, TRUE, FALSE, FALSE, &ListContext ); // // Reload the attribute list values from the list context. // ListAttribute = NtfsFoundAttribute( &ListContext ); // // Now fix up the context for return // if (*(PLONGLONG)&FileRecord->BaseFileRecordSegment == 0) { // // We need to update the attribute pointer for the target attribute // by the amount of the change in the attribute list attribute. // Context->FoundAttribute.Attribute = Add2Ptr( Context->FoundAttribute.Attribute, ListAttribute->RecordLength - OldQuadAttrListSize ); } Context->AttributeList.BeyondFinalEntry = Add2Ptr( Context->AttributeList.BeyondFinalEntry, EntrySize ); #if DBG { PATTRIBUTE_LIST_ENTRY LastEntry, Entry; for (LastEntry = Context->AttributeList.FirstEntry, Entry = NtfsGetNextRecord(LastEntry); Entry < Context->AttributeList.BeyondFinalEntry; LastEntry = Entry, Entry = NtfsGetNextRecord(LastEntry)) { ASSERT( (LastEntry->RecordLength != Entry->RecordLength) || (!RtlEqualMemory(LastEntry, Entry, Entry->RecordLength)) ); } } #endif } finally { // // If we had to allocate a list entry buffer, deallocate it. // if (ListEntry != &NewEntry.EntryBuffer) { NtfsFreePool(ListEntry); } // // Cleanup the enumeration context for the list entry. // NtfsCleanupAttributeContext( &ListContext); } } VOID NtfsDeleteFromAttributeList ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context ) /*++ Routine Description: This routine deletes an attribute list entry for a recently deleted attribute. It is assumed that the context variable is pointing to the place in the attribute list where the attribute list entry is to be deleted. Arguments: Fcb - Requested file. Context - Describes the current attribute. Return Value: None --*/ { ATTRIBUTE_ENUMERATION_CONTEXT ListContext; BOOLEAN FoundListContext; PFILE_RECORD_SEGMENT_HEADER FileRecord; PATTRIBUTE_LIST_ENTRY ListEntry, NextListEntry; ULONG EntrySize; ULONG SavedListSize; PAGED_CODE(); FileRecord = NtfsContainingFileRecord( Context ); // // Lookup the list context so that we can modify the attribute list. // NtfsInitializeAttributeContext( &ListContext ); FoundListContext = NtfsLookupAttributeByCode( IrpContext, Fcb, &Fcb->FileReference, $ATTRIBUTE_LIST, &ListContext ); ASSERT(FoundListContext); // // Use try-finally to insure cleanup. // try { SavedListSize = NtfsFoundAttribute(&ListContext)->RecordLength; // // Now shift the old contents down to make room for our new entry. // ListEntry = Context->AttributeList.Entry; EntrySize = ListEntry->RecordLength; NextListEntry = Add2Ptr(ListEntry, EntrySize); NtfsChangeAttributeValue( IrpContext, Fcb, PtrOffset( Context->AttributeList.FirstEntry, Context->AttributeList.Entry ), NextListEntry, PtrOffset( NextListEntry, Context->AttributeList.BeyondFinalEntry ), TRUE, TRUE, FALSE, TRUE, &ListContext ); // // Now fix up the context for return // if (*(PLONGLONG)&FileRecord->BaseFileRecordSegment == 0) { SavedListSize -= NtfsFoundAttribute(&ListContext)->RecordLength; Context->FoundAttribute.Attribute = Add2Ptr( Context->FoundAttribute.Attribute, -(LONG)SavedListSize ); } Context->AttributeList.BeyondFinalEntry = Add2Ptr( Context->AttributeList.BeyondFinalEntry, -(LONG)EntrySize ); } finally { // // Cleanup the enumeration context for the list entry. // NtfsCleanupAttributeContext(&ListContext); } } BOOLEAN NtfsRewriteMftMapping ( IN PIRP_CONTEXT IrpContext, IN PVCB Vcb ) /*++ Routine Description: This routine is called to rewrite the mapping for the Mft file. This is done in the case where either hot-fixing or Mft defragging has caused us to spill into the reserved area of a file record. This routine will rewrite the mapping from the beginning, using the reserved record if necessary. On return it will indicate whether any work was done and if there is more work to do. Arguments: Vcb - This is the Vcb for the volume to defrag. ExcessMapping - Address to store whether there is still excess mapping in the file. Return Value: BOOLEAN - TRUE if we made any changes to the file. FALSE if we found no work to do. --*/ { ATTRIBUTE_ENUMERATION_CONTEXT AttrContext; PUCHAR MappingPairs = NULL; PBCB FileRecordBcb = NULL; BOOLEAN MadeChanges = FALSE; BOOLEAN ExcessMapping = FALSE; BOOLEAN LastFileRecord = FALSE; BOOLEAN SkipLookup = FALSE; PAGED_CODE(); NtfsInitializeAttributeContext( &AttrContext ); // // Use a try-finally to facilitate cleanup. // try { VCN CurrentVcn; // Starting Vcn for the next file record VCN MinimumVcn; // This Vcn must be in the current mapping VCN LastVcn; // Last Vcn in the current mapping VCN LastMftVcn; // Last Vcn in the file VCN NextVcn; // First Vcn past the end of the mapping ULONG ReservedIndex; // Reserved index in Mft ULONG NextIndex; // Next file record available for Mft mapping PFILE_RECORD_SEGMENT_HEADER FileRecord; MFT_SEGMENT_REFERENCE FileRecordReference; ULONG RecordOffset; PATTRIBUTE_RECORD_HEADER Attribute; ULONG AttributeOffset; ULONG MappingSizeAvailable; ULONG MappingPairsSize; // // Find the initial file record for the Mft. // NtfsLookupAttributeForScb( IrpContext, Vcb->MftScb, NULL, &AttrContext ); // // Compute some initial values. If this is the only file record // for the file then we are done. // ReservedIndex = Vcb->MftScb->ScbType.Mft.ReservedIndex; Attribute = NtfsFoundAttribute( &AttrContext ); LastMftVcn = Int64ShraMod32(Vcb->MftScb->Header.AllocationSize.QuadPart, Vcb->ClusterShift) - 1; CurrentVcn = Attribute->Form.Nonresident.HighestVcn + 1; if (CurrentVcn >= LastMftVcn) { try_return( NOTHING ); } // // Loop while there are more file records. We will insert any // additional file records needed within the loop so that this // call should succeed until the remapping is done. // while (SkipLookup || NtfsLookupNextAttributeForScb( IrpContext, Vcb->MftScb, &AttrContext )) { BOOLEAN ReplaceFileRecord; BOOLEAN ReplaceAttributeListEntry; ReplaceAttributeListEntry = FALSE; // // If we just looked up this entry then pin the current // attribute. // if (!SkipLookup) { // // Always pin the current attribute. // NtfsPinMappedAttribute( IrpContext, Vcb, &AttrContext ); } // // Extract some pointers from the current file record. // Remember if this was the last record. // ReplaceFileRecord = FALSE; FileRecord = NtfsContainingFileRecord( &AttrContext ); FileRecordReference = AttrContext.AttributeList.Entry->SegmentReference; Attribute = NtfsFoundAttribute( &AttrContext ); AttributeOffset = Attribute->Form.Nonresident.MappingPairsOffset; RecordOffset = PtrOffset( FileRecord, Attribute ); // // Remember if we are at the last attribute. // if (Attribute->Form.Nonresident.HighestVcn == LastMftVcn) { LastFileRecord = TRUE; } // // If we have already remapped this entire file record then // remove the attribute and it list entry. // if (!SkipLookup && (CurrentVcn > LastMftVcn)) { PATTRIBUTE_LIST_ENTRY ListEntry; ULONG Count; Count = 0; // // We want to remove this entry and all subsequent entries. // ListEntry = AttrContext.AttributeList.Entry; while ((ListEntry != AttrContext.AttributeList.BeyondFinalEntry) && (ListEntry->AttributeTypeCode == $DATA) && (ListEntry->AttributeNameLength == 0)) { Count += 1; NtfsDeallocateMftRecord( IrpContext, Vcb, NtfsUnsafeSegmentNumber( &ListEntry->SegmentReference ) ); NtfsDeleteFromAttributeList( IrpContext, Vcb->MftScb->Fcb, &AttrContext ); ListEntry = AttrContext.AttributeList.Entry; } // // Clear out the reserved index in case one of these // will do. // NtfsAcquireCheckpoint( IrpContext, Vcb ); ClearFlag( IrpContext->Flags, IRP_CONTEXT_MFT_RECORD_RESERVED ); ClearFlag( Vcb->MftReserveFlags, VCB_MFT_RECORD_RESERVED ); NtfsReleaseCheckpoint( IrpContext, Vcb ); Vcb->MftScb->ScbType.Mft.ReservedIndex = 0; try_return( NOTHING ); } // // Check if we are going to replace this file record with // the reserved record. // if (ReservedIndex < NtfsSegmentNumber( &FileRecordReference )) { PATTRIBUTE_RECORD_HEADER NewAttribute; PATTRIBUTE_TYPE_CODE NewEnd; // // Remember this index for our computation for the Minimum mapped // Vcn. // NextIndex = NtfsUnsafeSegmentNumber( &FileRecordReference ); FileRecord = NtfsCloneFileRecord( IrpContext, Vcb->MftScb->Fcb, TRUE, &FileRecordBcb, &FileRecordReference ); ReservedIndex = MAXULONG; // // Now lets create an attribute in the new file record. // NewAttribute = Add2Ptr( FileRecord, FileRecord->FirstFreeByte - QuadAlign( sizeof( ATTRIBUTE_TYPE_CODE ))); NewAttribute->TypeCode = Attribute->TypeCode; NewAttribute->RecordLength = SIZEOF_PARTIAL_NONRES_ATTR_HEADER; NewAttribute->FormCode = NONRESIDENT_FORM; NewAttribute->Flags = Attribute->Flags; NewAttribute->Instance = FileRecord->NextAttributeInstance++; NewAttribute->Form.Nonresident.LowestVcn = CurrentVcn; NewAttribute->Form.Nonresident.HighestVcn = 0; NewAttribute->Form.Nonresident.MappingPairsOffset = (USHORT) NewAttribute->RecordLength; NewEnd = Add2Ptr( NewAttribute, NewAttribute->RecordLength ); *NewEnd = $END; // // Now fix up the file record with this new data. // FileRecord->FirstFreeByte = PtrOffset( FileRecord, NewEnd ) + QuadAlign( sizeof( ATTRIBUTE_TYPE_CODE )); FileRecord->SequenceNumber += 1; if (FileRecord->SequenceNumber == 0) { FileRecord->SequenceNumber = 1; } FileRecordReference.SequenceNumber = FileRecord->SequenceNumber; // // Now switch this new file record into the attribute context. // NtfsUnpinBcb( &NtfsFoundBcb( &AttrContext )); NtfsFoundBcb( &AttrContext ) = FileRecordBcb; AttrContext.FoundAttribute.MftFileOffset = LlBytesFromFileRecords( Vcb, NextIndex ); AttrContext.FoundAttribute.Attribute = NewAttribute; AttrContext.FoundAttribute.FileRecord = FileRecord; FileRecordBcb = NULL; // // Now add an attribute list entry for this entry. // NtfsAddToAttributeList( IrpContext, Vcb->MftScb->Fcb, FileRecordReference, &AttrContext ); // // Reload our pointers for this file record. // Attribute = NewAttribute; AttributeOffset = SIZEOF_PARTIAL_NONRES_ATTR_HEADER; RecordOffset = PtrOffset( FileRecord, Attribute ); // // We must include either the last Vcn of the file or // the Vcn for the next file record to use for the Mft. // At this point MinimumVcn is the first Vcn that doesn't // have to be in the current mapping. // if (Vcb->FileRecordsPerCluster == 0) { MinimumVcn = (NextIndex + 1) << Vcb->MftToClusterShift; } else { MinimumVcn = (NextIndex + Vcb->FileRecordsPerCluster - 1) << Vcb->MftToClusterShift; } ReplaceFileRecord = TRUE; // // We will be using the current attribute. // } else { // // The mapping we write into this page must go // to the current end of the page or to the reserved // or spare file record, whichever is earlier. // If we are adding the reserved record to the end then // we know the final Vcn already. // if (SkipLookup) { NextVcn = LastMftVcn; } else { NextVcn = Attribute->Form.Nonresident.HighestVcn; } if (Vcb->FileRecordsPerCluster == 0) { NextIndex = (ULONG)Int64ShraMod32((NextVcn + 1), Vcb->MftToClusterShift); } else { NextIndex = (ULONG)Int64ShllMod32((NextVcn + 1), Vcb->MftToClusterShift); } if (ReservedIndex < NextIndex) { NextIndex = ReservedIndex + 1; ReplaceFileRecord = TRUE; } // // If we can use this file record unchanged then continue on. // Start by checking that it starts on the same Vcn boundary. // if (!SkipLookup) { // // If it starts on the same boundary then we check if we // can do any work with this. // if (CurrentVcn == Attribute->Form.Nonresident.LowestVcn) { ULONG RemainingFileRecordBytes; RemainingFileRecordBytes = FileRecord->BytesAvailable - FileRecord->FirstFreeByte; // // Check if we have less than the desired cushion // left. // if (RemainingFileRecordBytes < Vcb->MftCushion) { // // If we have no more file records there is no // remapping we can do. // if (!ReplaceFileRecord) { // // Remember if we used part of the reserved // portion of the file record. // if (RemainingFileRecordBytes < Vcb->MftReserved) { ExcessMapping = TRUE; } CurrentVcn = Attribute->Form.Nonresident.HighestVcn + 1; continue; } // // We have more than our cushion left. If this // is the last file record we will skip this. // } else if (Attribute->Form.Nonresident.HighestVcn == LastMftVcn) { CurrentVcn = Attribute->Form.Nonresident.HighestVcn + 1; continue; } // // If it doesn't start on the same boundary then we have to // delete and reinsert the attribute list entry. // } else { ReplaceAttributeListEntry = TRUE; } } ReplaceFileRecord = FALSE; // // Log the beginning state of this file record. // NtfsLogMftFileRecord( IrpContext, Vcb, FileRecord, LlBytesFromFileRecords( Vcb, NtfsSegmentNumber( &FileRecordReference ) ), NtfsFoundBcb( &AttrContext ), FALSE ); // // Compute the Vcn for the file record past the one we will use // next. At this point this is the first Vcn that doesn't have // to be in the current mapping. // if (Vcb->FileRecordsPerCluster == 0) { MinimumVcn = NextIndex << Vcb->MftToClusterShift; } else { MinimumVcn = (NextIndex + Vcb->FileRecordsPerCluster - 1) << Vcb->MftToClusterShift; } } // // Move back one vcn to adhere to the mapping pairs interface. // This is now the last Vcn which MUST appear in the current // mapping. // MinimumVcn = MinimumVcn - 1; // // Get the available size for the mapping pairs. We won't // include the cushion here. // MappingSizeAvailable = FileRecord->BytesAvailable + Attribute->RecordLength - FileRecord->FirstFreeByte - SIZEOF_PARTIAL_NONRES_ATTR_HEADER; // // We know the range of Vcn's the mapping must cover. // Compute the mapping pair size. If they won't fit and // leave our desired cushion then use whatever space is // needed. The NextVcn value is the first Vcn (or xxMax) // for the run after the last run in the current mapping. // MappingPairsSize = NtfsGetSizeForMappingPairs( &Vcb->MftScb->Mcb, MappingSizeAvailable - Vcb->MftCushion, CurrentVcn, NULL, &NextVcn ); // // If this mapping doesn't include the file record we will // be using next then extend the mapping to include it. // if (NextVcn <= MinimumVcn) { // // Compute the mapping pairs again. This must fit // since it already fits. // MappingPairsSize = NtfsGetSizeForMappingPairs( &Vcb->MftScb->Mcb, MappingSizeAvailable, CurrentVcn, &MinimumVcn, &NextVcn ); // // Remember if we still have excess mapping. // if (MappingSizeAvailable - MappingPairsSize < Vcb->MftReserved) { ExcessMapping = TRUE; } } // // Remember the last Vcn for the current run. If the NextVcn // is xxMax then we are at the end of the file. // if (NextVcn == MAXLONGLONG) { LastVcn = LastMftVcn; // // Otherwise it is one less than the next vcn value. // } else { LastVcn = NextVcn - 1; } // // Check if we have to rewrite this attribute. We will write the // new mapping if any of the following are true. // // We are replacing a file record // The attribute's LowestVcn doesn't match // The attributes's HighestVcn doesn't match. // if (ReplaceFileRecord || (CurrentVcn != Attribute->Form.Nonresident.LowestVcn) || (LastVcn != Attribute->Form.Nonresident.HighestVcn )) { Attribute->Form.Nonresident.LowestVcn = CurrentVcn; // // Replace the attribute list entry at this point if needed. // if (ReplaceAttributeListEntry) { NtfsDeleteFromAttributeList( IrpContext, Vcb->MftScb->Fcb, &AttrContext ); NtfsAddToAttributeList( IrpContext, Vcb->MftScb->Fcb, FileRecordReference, &AttrContext ); } // // Allocate a buffer for the mapping pairs if we haven't // done so. // if (MappingPairs == NULL) { MappingPairs = NtfsAllocatePool(PagedPool, NtfsMaximumAttributeSize( Vcb->BytesPerFileRecordSegment )); } NtfsBuildMappingPairs( &Vcb->MftScb->Mcb, CurrentVcn, &NextVcn, MappingPairs ); Attribute->Form.Nonresident.HighestVcn = NextVcn; NtfsRestartChangeMapping( IrpContext, Vcb, FileRecord, RecordOffset, AttributeOffset, MappingPairs, MappingPairsSize ); // // Log the changes to this page. // NtfsLogMftFileRecord( IrpContext, Vcb, FileRecord, LlBytesFromFileRecords( Vcb, NtfsSegmentNumber( &FileRecordReference ) ), NtfsFoundBcb( &AttrContext ), TRUE ); MadeChanges = TRUE; } // // Move to the first Vcn of the following record. // CurrentVcn = Attribute->Form.Nonresident.HighestVcn + 1; // // If we reached the last file record and have more mapping to do // then use the reserved record. It must be available or we would // have written out the entire mapping. // if (LastFileRecord && (CurrentVcn < LastMftVcn)) { PATTRIBUTE_RECORD_HEADER NewAttribute; PATTRIBUTE_TYPE_CODE NewEnd; // // Start by moving to the next file record. It better not be // there or the file is corrupt. This will position us to // insert the new record. // if (NtfsLookupNextAttributeForScb( IrpContext, Vcb->MftScb, &AttrContext )) { NtfsAcquireCheckpoint( IrpContext, Vcb ); ClearFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_PERMITTED ); NtfsReleaseCheckpoint( IrpContext, Vcb ); NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Vcb->MftScb->Fcb ); } FileRecord = NtfsCloneFileRecord( IrpContext, Vcb->MftScb->Fcb, TRUE, &FileRecordBcb, &FileRecordReference ); ReservedIndex = MAXULONG; // // Now lets create an attribute in the new file record. // NewAttribute = Add2Ptr( FileRecord, FileRecord->FirstFreeByte - QuadAlign( sizeof( ATTRIBUTE_TYPE_CODE ))); NewAttribute->TypeCode = Attribute->TypeCode; NewAttribute->RecordLength = SIZEOF_PARTIAL_NONRES_ATTR_HEADER; NewAttribute->FormCode = NONRESIDENT_FORM; NewAttribute->Flags = Attribute->Flags; NewAttribute->Instance = FileRecord->NextAttributeInstance++; NewAttribute->Form.Nonresident.LowestVcn = CurrentVcn; NewAttribute->Form.Nonresident.HighestVcn = 0; NewAttribute->Form.Nonresident.MappingPairsOffset = (USHORT) NewAttribute->RecordLength; NewEnd = Add2Ptr( NewAttribute, NewAttribute->RecordLength ); *NewEnd = $END; // // Now fix up the file record with this new data. // FileRecord->FirstFreeByte = PtrOffset( FileRecord, NewEnd ) + QuadAlign( sizeof( ATTRIBUTE_TYPE_CODE )); FileRecord->SequenceNumber += 1; if (FileRecord->SequenceNumber == 0) { FileRecord->SequenceNumber = 1; } FileRecordReference.SequenceNumber = FileRecord->SequenceNumber; // // Now switch this new file record into the attribute context. // NtfsUnpinBcb( &NtfsFoundBcb( &AttrContext )); NtfsFoundBcb( &AttrContext ) = FileRecordBcb; AttrContext.FoundAttribute.MftFileOffset = LlBytesFromFileRecords( Vcb, NtfsSegmentNumber( &FileRecordReference ) ); AttrContext.FoundAttribute.Attribute = NewAttribute; AttrContext.FoundAttribute.FileRecord = FileRecord; FileRecordBcb = NULL; // // Now add an attribute list entry for this entry. // NtfsAddToAttributeList( IrpContext, Vcb->MftScb->Fcb, FileRecordReference, &AttrContext ); SkipLookup = TRUE; LastFileRecord = FALSE; } else { SkipLookup = FALSE; } } // End while more file records // // If we didn't rewrite all of the mapping then there is some error. // if (CurrentVcn <= LastMftVcn) { NtfsAcquireCheckpoint( IrpContext, Vcb ); ClearFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_PERMITTED ); NtfsReleaseCheckpoint( IrpContext, Vcb ); NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Vcb->MftScb->Fcb ); } try_exit: NOTHING; // // Clear the excess mapping flag if no changes were made. // if (!ExcessMapping) { NtfsAcquireCheckpoint( IrpContext, Vcb ); ClearFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_EXCESS_MAP ); NtfsReleaseCheckpoint( IrpContext, Vcb ); } } finally { DebugUnwind( NtfsRewriteMftMapping ); NtfsCleanupAttributeContext( &AttrContext ); NtfsUnpinBcb( &FileRecordBcb ); if (MappingPairs != NULL) { NtfsFreePool( MappingPairs ); } } return MadeChanges; } // // Local Support Routine // VOID NtfsSetTotalAllocatedField ( IN PIRP_CONTEXT IrpContext, IN PSCB Scb, IN USHORT CompressionState ) /*++ Routine Description: This routine is called to insure that first attribute of a stream has the correct size attribute header based on the compression state of the file. Compressed streams will have a field for the total allocated space in the file in the nonresident header. This routine will see if the header is in a valid state and make space if necessary. Then it will rewrite any of the attribute data after the header. Arguments: Scb - Scb for affected stream CompressionState - 0 for no compression or nonzero for Rtl compression code - 1 Return Value: None. --*/ { ATTRIBUTE_ENUMERATION_CONTEXT AttrContext; PFILE_RECORD_SEGMENT_HEADER FileRecord; PATTRIBUTE_RECORD_HEADER Attribute; PATTRIBUTE_RECORD_HEADER NewAttribute = NULL; PUNICODE_STRING NewAttributeName = NULL; ULONG OldHeaderSize; ULONG NewHeaderSize; LONG SizeChange; PAGED_CODE(); // // This must be a non-resident user data file. // if (!NtfsIsTypeCodeUserData( Scb->AttributeTypeCode ) || FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) { return; } NtfsInitializeAttributeContext( &AttrContext ); // // Use a try-finally to facilitate cleanup. // try { while (TRUE) { // // Find the current and the new size for the attribute. // NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &AttrContext ); FileRecord = NtfsContainingFileRecord( &AttrContext ); Attribute = NtfsFoundAttribute( &AttrContext ); OldHeaderSize = Attribute->Form.Nonresident.MappingPairsOffset; if (Attribute->NameOffset != 0) { OldHeaderSize = Attribute->NameOffset; } if (CompressionState == 0) { NewHeaderSize = SIZEOF_PARTIAL_NONRES_ATTR_HEADER; } else { NewHeaderSize = SIZEOF_FULL_NONRES_ATTR_HEADER; } SizeChange = NewHeaderSize - OldHeaderSize; // // Make space if we need to do so. Lookup the attribute again // if necessary. // if (SizeChange > 0) { VCN StartingVcn; VCN ClusterCount; // // If the attribute is alone in the file record and there isn't // enough space available then the call to ChangeAttributeSize // can't make any space available. In that case we call // NtfsChangeAttributeAllocation and let that routine rewrite // the mapping to make space available. // if ((FileRecord->BytesAvailable - FileRecord->FirstFreeByte < (ULONG) SizeChange) && (NtfsFirstAttribute( FileRecord ) == Attribute) && (((PATTRIBUTE_RECORD_HEADER) NtfsGetNextRecord( Attribute ))->TypeCode == $END)) { NtfsLookupAllocation( IrpContext, Scb, Attribute->Form.Nonresident.HighestVcn, &StartingVcn, &ClusterCount, NULL, NULL ); StartingVcn = 0; ClusterCount = Attribute->Form.Nonresident.HighestVcn + 1; NtfsAddAttributeAllocation( IrpContext, Scb, &AttrContext, &StartingVcn, &ClusterCount ); } else if (NtfsChangeAttributeSize( IrpContext, Scb->Fcb, Attribute->RecordLength + SizeChange, &AttrContext)) { break; } NtfsCleanupAttributeContext( &AttrContext ); NtfsInitializeAttributeContext( &AttrContext ); continue; } break; } NtfsPinMappedAttribute( IrpContext, Scb->Vcb, &AttrContext ); // // Make a copy of the existing attribute and modify the total allocated field // if necessary. // NewAttribute = NtfsAllocatePool(PagedPool, Attribute->RecordLength + SizeChange ); RtlCopyMemory( NewAttribute, Attribute, SIZEOF_PARTIAL_NONRES_ATTR_HEADER ); if (Attribute->NameOffset != 0) { NewAttribute->NameOffset += (USHORT) SizeChange; NewAttributeName = &Scb->AttributeName; } NewAttribute->Form.Nonresident.MappingPairsOffset += (USHORT) SizeChange; NewAttribute->RecordLength += SizeChange; RtlCopyMemory( Add2Ptr( NewAttribute, NewAttribute->Form.Nonresident.MappingPairsOffset ), Add2Ptr( Attribute, Attribute->Form.Nonresident.MappingPairsOffset ), Attribute->RecordLength - Attribute->Form.Nonresident.MappingPairsOffset ); if (CompressionState != 0) { NewAttribute->Form.Nonresident.TotalAllocated = Scb->TotalAllocated; } // // We now have the before and after image to log. // FileRecord->Lsn = NtfsWriteLog( IrpContext, Scb->Vcb->MftScb, NtfsFoundBcb( &AttrContext ), DeleteAttribute, NULL, 0, CreateAttribute, Attribute, Attribute->RecordLength, NtfsMftOffset( &AttrContext ), PtrOffset( FileRecord, Attribute ), 0, Scb->Vcb->BytesPerFileRecordSegment ); NtfsRestartRemoveAttribute( IrpContext, FileRecord, PtrOffset( FileRecord, Attribute )); FileRecord->Lsn = NtfsWriteLog( IrpContext, Scb->Vcb->MftScb, NtfsFoundBcb( &AttrContext ), CreateAttribute, NewAttribute, NewAttribute->RecordLength, DeleteAttribute, NULL, 0, NtfsMftOffset( &AttrContext ), PtrOffset( FileRecord, Attribute ), 0, Scb->Vcb->BytesPerFileRecordSegment ); NtfsRestartInsertAttribute( IrpContext, FileRecord, PtrOffset( FileRecord, Attribute ), NewAttribute, NewAttributeName, Add2Ptr( NewAttribute, NewAttribute->Form.Nonresident.MappingPairsOffset ), NewAttribute->RecordLength - NewAttribute->Form.Nonresident.MappingPairsOffset ); } finally { DebugUnwind( NtfsSetTotalAllocatedField ); if (NewAttribute != NULL) { NtfsFreePool( NewAttribute ); } NtfsCleanupAttributeContext( &AttrContext ); } return; } // // Internal support routine // BOOLEAN NtfsLookupInFileRecord ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN PFILE_REFERENCE BaseFileReference OPTIONAL, IN ATTRIBUTE_TYPE_CODE QueriedTypeCode, IN PUNICODE_STRING QueriedName OPTIONAL, IN PVCN Vcn OPTIONAL, IN BOOLEAN IgnoreCase, IN PVOID QueriedValue OPTIONAL, IN ULONG QueriedValueLength, OUT PATTRIBUTE_ENUMERATION_CONTEXT Context ) /*++ Routine Description: This routine attempts to find the fist occurrence of an attribute with the specified AttributeTypeCode and the specified QueriedName in the specified BaseFileReference. If we find one, its attribute record is pinned and returned. Arguments: Fcb - Requested file. BaseFileReference - The base entry for this file in the MFT. Only needed on initial invocation. QueriedTypeCode - The attribute code to search for, if present. QueriedName - The attribute name to search for, if present. Vcn - Search for the nonresident attribute instance that has this Vcn IgnoreCase - Ignore case while comparing names. Ignored if QueriedName not present. QueriedValue - The actual attribute value to search for, if present. QueriedValueLength - The length of the attribute value to search for. Ignored if QueriedValue is not present. Context - Describes the prior found attribute on invocation (if this was not the initial enumeration), and contains the next found attribute on return. Return Value: BOOLEAN - True if we found an attribute, false otherwise. --*/ { PATTRIBUTE_RECORD_HEADER Attribute; PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsLookupInFileRecord\n") ); DebugTrace( 0, Dbg, ("Fcb = %08lx\n", Fcb) ); DebugTrace( 0, Dbg, ("BaseFileReference = %08I64x\n", ARGUMENT_PRESENT(BaseFileReference) ? NtfsFullSegmentNumber( BaseFileReference ) : 0xFFFFFFFFFFFF) ); DebugTrace( 0, Dbg, ("QueriedTypeCode = %08lx\n", QueriedTypeCode) ); DebugTrace( 0, Dbg, ("QueriedName = %08lx\n", QueriedName) ); DebugTrace( 0, Dbg, ("IgnoreCase = %02lx\n", IgnoreCase) ); DebugTrace( 0, Dbg, ("QueriedValue = %08lx\n", QueriedValue) ); DebugTrace( 0, Dbg, ("QueriedValueLength = %08lx\n", QueriedValueLength) ); DebugTrace( 0, Dbg, ("Context = %08lx\n", Context) ); // // Is this the initial enumeration? If so start at the beginning. // if (Context->FoundAttribute.Bcb == NULL) { PBCB Bcb; PFILE_RECORD_SEGMENT_HEADER FileRecord; PATTRIBUTE_RECORD_HEADER TempAttribute; ASSERT(!ARGUMENT_PRESENT(QueriedName) || !ARGUMENT_PRESENT(QueriedValue)); NtfsReadFileRecord( IrpContext, Fcb->Vcb, BaseFileReference, &Bcb, &FileRecord, &TempAttribute, &Context->FoundAttribute.MftFileOffset ); Attribute = TempAttribute; // // Initialize the found attribute context // Context->FoundAttribute.Bcb = Bcb; Context->FoundAttribute.FileRecord = FileRecord; // // And show that we have neither found nor used the External // Attributes List attribute. // Context->AttributeList.Bcb = NULL; Context->AttributeList.AttributeList = NULL; // // Scan to see if there is an attribute list, and if so, defer // immediately to NtfsLookupExternalAttribute - we must guide the // enumeration by the attribute list. // while (TempAttribute->TypeCode <= $ATTRIBUTE_LIST) { if (TempAttribute->RecordLength == 0) { NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb ); } if (TempAttribute->TypeCode == $ATTRIBUTE_LIST) { ULONG AttributeListLength; PATTRIBUTE_LIST_CONTEXT Ex = &Context->AttributeList; Context->FoundAttribute.Attribute = TempAttribute; if (QueriedTypeCode != $UNUSED && (QueriedTypeCode == $ATTRIBUTE_LIST)) { // // We found it. Return it in the enumeration context. // DebugTrace( 0, Dbg, ("Context->FoundAttribute.Attribute < %08lx\n", TempAttribute) ); DebugTrace( -1, Dbg, ("NtfsLookupInFileRecord -> TRUE (attribute list)\n") ); return TRUE; } // // Build up the context for the attribute list by hand here // for efficiency, so that we can call NtfsMapAttributeValue. // Ex->AttributeList = TempAttribute; NtfsMapAttributeValue( IrpContext, Fcb, (PVOID *)&Ex->FirstEntry, &AttributeListLength, &Ex->Bcb, Context ); Ex->Entry = Ex->FirstEntry; Ex->BeyondFinalEntry = Add2Ptr( Ex->FirstEntry, AttributeListLength ); // // If the list is non-resident then remember the correct Bcb for // the list. // if (!NtfsIsAttributeResident( TempAttribute )) { Ex->NonresidentListBcb = Ex->Bcb; Ex->Bcb = Context->FoundAttribute.Bcb; Context->FoundAttribute.Bcb = NULL; // // Otherwise unpin the Bcb for the current attribute. // } else { NtfsUnpinBcb( &Context->FoundAttribute.Bcb ); } // // We are now ready to itterate through the external attributes. // The Context->FoundAttribute.Bcb being NULL signals // NtfsLookupExternalAttribute that is should start at // Context->External.Entry instead of the entry immediately following. // return NtfsLookupExternalAttribute( IrpContext, Fcb, QueriedTypeCode, QueriedName, Vcn, IgnoreCase, QueriedValue, QueriedValueLength, Context ); } TempAttribute = NtfsGetNextRecord( TempAttribute ); NtfsCheckRecordBound( TempAttribute, FileRecord, Fcb->Vcb->BytesPerFileRecordSegment ); } if (QueriedTypeCode == $UNUSED || ((QueriedTypeCode == $STANDARD_INFORMATION) && (Attribute->TypeCode == $STANDARD_INFORMATION))) { // // We found it. Return it in the enumeration context. // Context->FoundAttribute.Attribute = Attribute; DebugTrace( 0, Dbg, ("Context->FoundAttribute.Attribute < %08lx\n", Attribute )); DebugTrace( -1, Dbg, ("NtfsLookupInFileRecord -> TRUE (No code or SI)\n") ); return TRUE; } } else { // // Special case if the prior found attribute was $END, this is // because we cannot search for the next entry after $END. // Attribute = Context->FoundAttribute.Attribute; if (!Context->FoundAttribute.AttributeDeleted) { Attribute = NtfsGetNextRecord( Attribute ); } NtfsCheckRecordBound( Attribute, Context->FoundAttribute.FileRecord, Fcb->Vcb->BytesPerFileRecordSegment ); Context->FoundAttribute.AttributeDeleted = FALSE; if (Attribute->TypeCode == $END) { DebugTrace( -1, Dbg, ("NtfsLookupInFileRecord -> FALSE ($END)\n") ); return FALSE; } if (Attribute->RecordLength == 0) { NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb ); } if (QueriedTypeCode == $UNUSED) { // // We found it. Return it in the enumeration context. // Context->FoundAttribute.Attribute = Attribute; DebugTrace( 0, Dbg, ("Context->FoundAttribute.Attribute < %08lx\n", Attribute) ); DebugTrace( -1, Dbg, ("NtfsLookupInFileRecord -> TRUE (No code)\n") ); return TRUE; } } DebugTrace( -1, Dbg, ("NtfsLookupInFileRecord ->\n") ); return NtfsFindInFileRecord( IrpContext, Attribute, &Context->FoundAttribute.Attribute, QueriedTypeCode, QueriedName, IgnoreCase, QueriedValue, QueriedValueLength ); } // // Internal support routine // BOOLEAN NtfsFindInFileRecord ( IN PIRP_CONTEXT IrpContext, IN PATTRIBUTE_RECORD_HEADER Attribute, OUT PATTRIBUTE_RECORD_HEADER *ReturnAttribute, IN ATTRIBUTE_TYPE_CODE QueriedTypeCode, IN PUNICODE_STRING QueriedName OPTIONAL, IN BOOLEAN IgnoreCase, IN PVOID QueriedValue OPTIONAL, IN ULONG QueriedValueLength ) /*++ Routine Description: This routine looks up an attribute in a file record. It returns TRUE if the attribute was found, or FALSE if not found. If FALSE is returned, the return attribute pointer points to the spot where the described attribute should be inserted. Thus this routine determines how attributes are collated within file records. Arguments: Attribute - The attribute within the file record at which the search should begin. ReturnAttribute - Pointer to the found attribute if returning TRUE, or to the position to insert the attribute if returning FALSE. QueriedTypeCode - The attribute code to search for, if present. QueriedName - The attribute name to search for, if present. IgnoreCase - Ignore case while comparing names. Ignored if QueriedName not present. QueriedValue - The actual attribute value to search for, if present. QueriedValueLength - The length of the attribute value to search for. Ignored if QueriedValue is not present. Return Value: BOOLEAN - True if we found an attribute, false otherwise. --*/ { PWCH UpcaseTable = IrpContext->Vcb->UpcaseTable; ULONG UpcaseTableSize = IrpContext->Vcb->UpcaseTableSize; // // Now walk through the base file record looking for the atttribute. If // the query is "exhausted", i.e., if a type code, attribute name, or // value is encountered which is greater than the one we are querying for, // then we return FALSE immediately out of this loop. If an exact match // is seen, we break, and return the match at the end of this routine. // Otherwise we keep looping while the query is not exhausted. // // IMPORTANT NOTE: // // The exact semantics of this loop are important, as they determine the // exact details of attribute ordering within the file record. A change // in the order of the tests within this loop CHANGES THE FILE STRUCTURE, // and possibly makes older NTFS volumes unreadable. // while ( TRUE ) { // // Mark this attribute position, since we may be returning TRUE // or FALSE below. // *ReturnAttribute = Attribute; // // Leave with the correct current position intact, if we hit the // end or a greater attribute type code. // // COLLATION RULE: // // Attributes are ordered by increasing attribute type code. // if (QueriedTypeCode < Attribute->TypeCode) { DebugTrace( -1, Dbg, ("NtfsLookupInFileRecord->FALSE (Type Code)\n") ); return FALSE; } if (Attribute->RecordLength == 0) { NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, NULL ); } // // If the attribute type code is a match, then need to check either // the name or the value or return a match. // // COLLATION RULE: // // Within equal attribute type codes, attribute names are ordered // by increasing lexigraphical order ignoring case. If two names // exist which are equal when case is ignored, they must not be // equal when compared with exact case, and within such equal // names they are ordered by increasing lexical value with exact // case. // if (QueriedTypeCode == Attribute->TypeCode) { // // Handle name-match case // if (ARGUMENT_PRESENT(QueriedName)) { UNICODE_STRING AttributeName; FSRTL_COMPARISON_RESULT Result; NtfsInitializeStringFromAttribute( &AttributeName, Attribute ); // // See if we have a name match. // if (NtfsAreNamesEqual( UpcaseTable, &AttributeName, QueriedName, IgnoreCase )) { break; } // // Compare the names ignoring case. // Result = NtfsCollateNames( UpcaseTable, UpcaseTableSize, QueriedName, &AttributeName, GreaterThan, TRUE); // // Break out if the result is LessThan, or if the result // is Equal to *and* the exact case compare yields LessThan. // if ((Result == LessThan) || ((Result == EqualTo) && (NtfsCollateNames( UpcaseTable, UpcaseTableSize, QueriedName, &AttributeName, GreaterThan, FALSE) == LessThan))) { return FALSE; } // // Handle value-match case // // COLLATION RULE: // // Values are collated by increasing values with unsigned-byte // compares. I.e., the first different byte is compared unsigned, // and the value with the highest byte comes second. If a shorter // value is exactly equal to the first part of a longer value, then // the shorter value comes first. // // Note that for values which are actually Unicode strings, the // collation is different from attribute name ordering above. However, // attribute ordering is visible outside the file system (you can // query "openable" attributes), whereas the ordering of indexed values // is not visible (for example you cannot query links). In any event, // the ordering of values must be considered up to the system, and // *must* be considered nondetermistic from the standpoint of a user. // } else if (ARGUMENT_PRESENT(QueriedValue)) { ULONG Diff, MinLength; // // Form the minimum of the ValueLength and the Attribute Value. // MinLength = Attribute->Form.Resident.ValueLength; if (QueriedValueLength < MinLength) { MinLength = QueriedValueLength; } // // Find the first different byte. // Diff = RtlCompareMemory( QueriedValue, NtfsGetValue(Attribute), MinLength ); // // The first substring was equal. // if (Diff == MinLength) { // // If the two lengths are equal, then we have an exact // match. // if (QueriedValueLength == Attribute->Form.Resident.ValueLength) { break; } // // Otherwise the shorter guy comes first; we can return // FALSE if the queried value is shorter. // if (QueriedValueLength < Attribute->Form.Resident.ValueLength) { return FALSE; } // // Otherwise some byte was different. Do an unsigned compare // of that byte to determine the ordering. Time to leave if // the queried value byte is less. // } else if (*((PUCHAR)QueriedValue + Diff) < *((PUCHAR)NtfsGetValue(Attribute) + Diff)) { return FALSE; } // // Otherwise we have a simple match on code // } else { break; } } Attribute = NtfsGetNextRecord( Attribute ); NtfsCheckRecordBound( Attribute, (ULONG)*ReturnAttribute & ~(IrpContext->Vcb->BytesPerFileRecordSegment - 1), IrpContext->Vcb->BytesPerFileRecordSegment ); } return TRUE; } // // Internal support routine // BOOLEAN NtfsLookupExternalAttribute ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN ATTRIBUTE_TYPE_CODE QueriedTypeCode, IN PUNICODE_STRING QueriedName OPTIONAL, IN PVCN Vcn OPTIONAL, IN BOOLEAN IgnoreCase, IN PVOID QueriedValue OPTIONAL, IN ULONG QueriedValueLength, OUT PATTRIBUTE_ENUMERATION_CONTEXT Context ) /*++ Routine Description: This routine attempts to find the first occurrence of an attribute with the specified AttributeTypeCode and the specified QueriedName and Value among the external attributes described by the Context. If we find one, its attribute record is pinned and returned. Arguments: Fcb - Requested file. QueriedTypeCode - The attribute code to search for, if present. QueriedName - The attribute name to search for, if present. Vcn - Lookup nonresident attribute instance with this Vcn IgnoreCase - Ignore case while comparing names. Ignored if QueriedName not present. QueriedValue - The actual attribute value to search for, if present. QueriedValueLength - The length of the attribute value to search for. Ignored if QueriedValue is not present. Context - Describes the prior found attribute on invocation (if this was not the initial enumeration), and contains the next found attribute on return. Return Value: BOOLEAN - True if we found an attribute, false otherwise. --*/ { PATTRIBUTE_LIST_ENTRY Entry, LastEntry; PWCH UpcaseTable = IrpContext->Vcb->UpcaseTable; ULONG UpcaseTableSize = IrpContext->Vcb->UpcaseTableSize; BOOLEAN Terminating = FALSE; BOOLEAN TerminateOnNext = FALSE; PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsLookupExternalAttribute\n") ); DebugTrace( 0, Dbg, ("Fcb = %08lx\n", Fcb) ); DebugTrace( 0, Dbg, ("QueriedTypeCode = %08lx\n", QueriedTypeCode) ); DebugTrace( 0, Dbg, ("QueriedName = %08lx\n", QueriedName) ); DebugTrace( 0, Dbg, ("IgnoreCase = %02lx\n", IgnoreCase) ); DebugTrace( 0, Dbg, ("QueriedValue = %08lx\n", QueriedValue) ); DebugTrace( 0, Dbg, ("QueriedValueLength = %08lx\n", QueriedValueLength) ); DebugTrace( 0, Dbg, ("Context = %08lx\n", Context) ); // // Check that our list is kosher. // if ((Context->AttributeList.Entry >= Context->AttributeList.BeyondFinalEntry) && !Context->FoundAttribute.AttributeDeleted) { NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb ); } // // Is this the initial enumeration? If so start at the beginning. // LastEntry = NULL; if (Context->FoundAttribute.Bcb == NULL) { Entry = Context->AttributeList.Entry; // // Else set Entry and LastEntry appropriately. // } else if (!Context->FoundAttribute.AttributeDeleted) { LastEntry = Context->AttributeList.Entry; Entry = NtfsGetNextRecord( LastEntry ); } else { Entry = Context->AttributeList.Entry; Context->FoundAttribute.AttributeDeleted = FALSE; // // If we are beyond the attribute list, we return false. This will // happen in the case where have removed an attribute record and // there are no entries left in the attribute list. // if (Context->AttributeList.Entry >= Context->AttributeList.BeyondFinalEntry) { // // In case the caller is doing an insert, we will position him at the end // of the first file record, an always try to insert new attributes there. // NtfsUnpinBcb( &Context->FoundAttribute.Bcb ); if (QueriedTypeCode != $UNUSED) { NtfsReadFileRecord( IrpContext, Fcb->Vcb, &Fcb->FileReference, &Context->FoundAttribute.Bcb, &Context->FoundAttribute.FileRecord, &Context->FoundAttribute.Attribute, &Context->FoundAttribute.MftFileOffset ); // // If returning FALSE, then take the time to really find the // correct position in the file record for a subsequent insert. // NtfsFindInFileRecord( IrpContext, Context->FoundAttribute.Attribute, &Context->FoundAttribute.Attribute, QueriedTypeCode, QueriedName, IgnoreCase, QueriedValue, QueriedValueLength ); } DebugTrace( -1, Dbg, ("NtfsLookupExternalAttribute -> FALSE\n") ); return FALSE; } } // // Now walk through the entries looking for an atttribute. // while (TRUE) { PATTRIBUTE_RECORD_HEADER Attribute; UNICODE_STRING EntryName; UNICODE_STRING AttributeName; PATTRIBUTE_LIST_ENTRY NextEntry; BOOLEAN CorrespondingAttributeFound; // // Check to see if we are now pointing beyond the final entry // and if so fall in to the loop to terminate pointing just // after the last entry. // if (Entry >= Context->AttributeList.BeyondFinalEntry) { Terminating = TRUE; TerminateOnNext = TRUE; Entry = Context->AttributeList.Entry; } else { NtfsCheckRecordBound( Entry, Context->AttributeList.FirstEntry, PtrOffset(Context->AttributeList.FirstEntry, Context->AttributeList.BeyondFinalEntry) ); if (Entry->RecordLength == 0) { NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb ); } NextEntry = NtfsGetNextRecord(Entry); } Context->AttributeList.Entry = Entry; // // Compare the type codes. The external attribute entry list is // ordered by type code, so if the queried type code is less than // the entry type code we continue the while(), if it is // greater than we break out of the while() and return failure. // If equal, we move on to compare names. // if (QueriedTypeCode != $UNUSED && !Terminating && (QueriedTypeCode != Entry->AttributeTypeCode)) { if ( QueriedTypeCode > Entry->AttributeTypeCode ) { Entry = NextEntry; continue; // // Set up to terminate on seeing a higher type code. // } else { Terminating = TRUE; } } // // At this point we are OK by TypeCode, compare names. // EntryName.Length = EntryName.MaximumLength = Entry->AttributeNameLength * 2; EntryName.Buffer = Add2Ptr(Entry, Entry->AttributeNameOffset); if (ARGUMENT_PRESENT(QueriedName) && !Terminating) { FSRTL_COMPARISON_RESULT Result; // // See if we have a name match. // if (!NtfsAreNamesEqual( UpcaseTable, &EntryName, QueriedName, IgnoreCase )) { // // Compare the names ignoring case. // Result = NtfsCollateNames( UpcaseTable, UpcaseTableSize, QueriedName, &EntryName, GreaterThan, TRUE); // // Break out if the result is LessThan, or if the result // is Equal to *and* the exact case compare yields LessThan. // if ((Result == LessThan) || ((Result == EqualTo) && (NtfsCollateNames( UpcaseTable, UpcaseTableSize, QueriedName, &EntryName, GreaterThan, FALSE) == LessThan))) { Terminating = TRUE; } else { Entry = NextEntry; continue; } } } // // Now search for the right Vcn range, if specified. // if (ARGUMENT_PRESENT(Vcn) && !Terminating) { ASSERT(Entry->LowestVcn <= *Vcn); // // If there is a next entry for our attribute and its LowestVcn is still <= // to the one we are looking for, then we have to go on. // if ((NextEntry < Context->AttributeList.BeyondFinalEntry) && (NextEntry->LowestVcn <= *Vcn) && (NextEntry->AttributeTypeCode == Entry->AttributeTypeCode) && (NextEntry->AttributeNameLength == Entry->AttributeNameLength) && (RtlEqualMemory(Add2Ptr(NextEntry, NextEntry->AttributeNameOffset), Add2Ptr(Entry, Entry->AttributeNameOffset), Entry->AttributeNameLength * 2 ))) { Entry = NextEntry; continue; } } // // Now we are also OK by name and Vcn, so now go find the attribute and // compare against value, if specified. // if ((LastEntry == NULL) || (!NtfsEqualMftRef(&LastEntry->SegmentReference, &Entry->SegmentReference))) { PFILE_RECORD_SEGMENT_HEADER FileRecord; NtfsUnpinBcb( &Context->FoundAttribute.Bcb ); NtfsReadFileRecord( IrpContext, Fcb->Vcb, &Entry->SegmentReference, &Context->FoundAttribute.Bcb, &FileRecord, &Attribute, &Context->FoundAttribute.MftFileOffset ); Context->FoundAttribute.FileRecord = FileRecord; // // If we already have the right record pinned, reload this pointer. // } else { Attribute = NtfsFirstAttribute(Context->FoundAttribute.FileRecord); } // // Now quickly loop through looking for the correct attribute // instance. // CorrespondingAttributeFound = FALSE; while (TRUE) { // // Check that we can safely access this attribute. // NtfsCheckRecordBound( Attribute, Context->FoundAttribute.FileRecord, Fcb->Vcb->BytesPerFileRecordSegment ); // // Exit the loop if we have reached the $END record. // if (Attribute->TypeCode == $END) { break; } // // Check that the attribute has a non-zero length. // if (Attribute->RecordLength == 0) { NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb ); } if (Entry->Instance == Attribute->Instance) { // // Well, the attribute list saved us from having to compare // type code and name as we went through this file record, // however now that we have found our attribute by its // instance number, we will do a quick check to see that // we got the right one. Else the file is corrupt. // if (Entry->AttributeTypeCode != Attribute->TypeCode) { break; } if (ARGUMENT_PRESENT(QueriedName)) { NtfsInitializeStringFromAttribute( &AttributeName, Attribute ); if (!NtfsAreNamesEqual( UpcaseTable, &AttributeName, &EntryName, FALSE)) { break; } } // // Show that we correctly found the attribute described in // the attribute list. // CorrespondingAttributeFound = TRUE; Context->FoundAttribute.Attribute = Attribute; // // Now we may just be here because we are terminating the // scan on seeing the end, a higher attribute code, or a // higher name. If so, return FALSE here. // if (Terminating) { // // If we hit the end of the attribute list, then we // are supposed to terminate after advancing the // attribute list entry. // if (TerminateOnNext) { Context->AttributeList.Entry = NtfsGetNextRecord(Entry); } // // In case the caller is doing an insert, we will position him at the end // of the first file record, an always try to insert new attributes there. // NtfsUnpinBcb( &Context->FoundAttribute.Bcb ); if (QueriedTypeCode != $UNUSED) { NtfsReadFileRecord( IrpContext, Fcb->Vcb, &Fcb->FileReference, &Context->FoundAttribute.Bcb, &Context->FoundAttribute.FileRecord, &Context->FoundAttribute.Attribute, &Context->FoundAttribute.MftFileOffset ); // // If returning FALSE, then take the time to really find the // correct position in the file record for a subsequent insert. // NtfsFindInFileRecord( IrpContext, Context->FoundAttribute.Attribute, &Context->FoundAttribute.Attribute, QueriedTypeCode, QueriedName, IgnoreCase, QueriedValue, QueriedValueLength ); } DebugTrace( 0, Dbg, ("Context->FoundAttribute.Attribute < %08lx\n", Attribute) ); DebugTrace( -1, Dbg, ("NtfsLookupExternalAttribute -> FALSE\n") ); return FALSE; } // // Now compare the value, if so queried. // if ( !ARGUMENT_PRESENT( QueriedValue ) || NtfsEqualAttributeValue( Attribute, QueriedValue, QueriedValueLength ) ) { // // It matches. Return it in the enumeration context. // DebugTrace( 0, Dbg, ("Context->FoundAttribute.Attribute < %08lx\n", Attribute )); DebugTrace( -1, Dbg, ("NtfsLookupExternalAttribute -> TRUE\n") ); return TRUE; } } // // Get the next attribute, and continue. // Attribute = NtfsGetNextRecord( Attribute ); } // // Did we even find the attribute corresponding to the entry? // If not, something is messed up. Raise file corrupt error. // if ( !CorrespondingAttributeFound ) { // // For the moment, ASSERT this falsehood so that we may have // a chance to peek before raising. // ASSERT( CorrespondingAttributeFound ); NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb ); } Entry = NtfsGetNextRecord( Entry ); } } // // Internal support routine // BOOLEAN NtfsGetSpaceForAttribute ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN ULONG Length, IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context ) /*++ Routine Description: This routine gets space for a new attribute record at the position indicated in the Context structure. As required, it will move attributes around, allocate an additional record in the Mft, or convert some other existing attribute to nonresident form. The caller should already have checked if the new attribute he is inserting should be stored resident or nonresident. On return, it is invalid to continue to use any previously-retrieved pointers, Bcbs, or other position-dependent information retrieved from the Context structure, as any of these values are liable to change. The file record in which the space has been found will already be pinned. Note, this routine DOES NOT actually make space for the attribute, it only verifies that sufficient space is there. The caller may call NtfsRestartInsertAttribute to actually insert the attribute in place. Arguments: Fcb - Requested file. Length - Quad-aligned length required in bytes. Context - Describes the position for the new attribute, as returned from the enumeration which failed to find an existing occurrence of the attribute. This pointer will either be pointing to some other attribute in the record, or to the first free quad-aligned byte if the new attribute is to go at the end. Return Value: FALSE - if a major move was necessary, and the caller should look up its desired position again and call back. TRUE - if the space was created --*/ { PATTRIBUTE_RECORD_HEADER NextAttribute; PFILE_RECORD_SEGMENT_HEADER FileRecord; PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsGetSpaceForAttribute\n") ); DebugTrace( 0, Dbg, ("Fcb = %08lx\n", Fcb) ); DebugTrace( 0, Dbg, ("Length = %08lx\n", Length) ); DebugTrace( 0, Dbg, ("Context = %08lx\n", Context) ); ASSERT( Length == QuadAlign( Length )); NextAttribute = NtfsFoundAttribute( Context ); FileRecord = NtfsContainingFileRecord( Context ); // // Make sure the buffer is pinned. // NtfsPinMappedAttribute( IrpContext, Fcb->Vcb, Context ); // // If the space is not there now, then make room and return with FALSE // if ((FileRecord->BytesAvailable - FileRecord->FirstFreeByte) < Length ) { MakeRoomForAttribute( IrpContext, Fcb, Length, Context ); DebugTrace( -1, Dbg, ("NtfsGetSpaceForAttribute -> FALSE\n") ); return FALSE; } DebugTrace( -1, Dbg, ("NtfsGetSpaceForAttribute -> TRUE\n") ); return TRUE; } // // Internal support routine // BOOLEAN NtfsChangeAttributeSize ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN ULONG Length, IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context ) /*++ Routine Description: This routine adjustss the space occupied by the current attribute record in the Context structure. As required, it will move attributes around, allocate an additional record in the Mft, or convert some other existing attribute to nonresident form. The caller should already have checked if the current attribute he is inserting should rather be converted to nonresident. When done, this routine has updated any file records whose allocation was changed, and also the RecordLength field in the adjusted attribute. No other attribute fields are updated. On return, it is invalid to continue to use any previously-retrieved pointers, Bcbs, or other position-dependent information retrieved from the Context structure, as any of these values are liable to change. The file record in which the space has been found will already be pinned. Arguments: Fcb - Requested file. Length - New quad-aligned length of attribute record in bytes Context - Describes the current attribute. Return Value: FALSE - if a major move was necessary, and the caller should look up its desired position again and call back. TRUE - if the space was created --*/ { PATTRIBUTE_RECORD_HEADER Attribute; PFILE_RECORD_SEGMENT_HEADER FileRecord; LONG SizeChange; PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsChangeAttributeSize\n") ); DebugTrace( 0, Dbg, ("Fcb = %08lx\n", Fcb) ); DebugTrace( 0, Dbg, ("Length = %08lx\n", Length) ); DebugTrace( 0, Dbg, ("Context = %08lx\n", Context) ); ASSERT( Length == QuadAlign( Length )); Attribute = NtfsFoundAttribute( Context ); FileRecord = NtfsContainingFileRecord( Context ); // // Make sure the buffer is pinned. // NtfsPinMappedAttribute( IrpContext, Fcb->Vcb, Context ); // // Calculate the change in attribute record size. // ASSERT( Attribute->RecordLength == QuadAlign( Attribute->RecordLength )); SizeChange = Length - Attribute->RecordLength; // // If there is not currently enough space, then we have to make room // and return FALSE to our caller. // if ( (LONG)(FileRecord->BytesAvailable - FileRecord->FirstFreeByte) < SizeChange ) { MakeRoomForAttribute( IrpContext, Fcb, SizeChange, Context ); DebugTrace( -1, Dbg, ("NtfsChangeAttributeSize -> FALSE\n") ); return FALSE; } DebugTrace( -1, Dbg, ("NtfsChangeAttributeSize -> TRUE\n") ); return TRUE; } // // Internal support routine // VOID MakeRoomForAttribute ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN ULONG SizeNeeded, IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context ) /*++ Routine Description: This routine attempts to make additional room for a new attribute or a growing attribute in a file record. The algorithm is as follows. First continuously loop through the record looking at the largest n attributes, from the largest down, to see which one of these attributes is big enough to move, and which one qualifies for one of the following actions: 1. For an index root attribute, the indexing package may be called to "push" the index root, i.e., add another level to the BTree leaving only an end index record in the root. 2. For a resident attribute which is allowed to be made nonresident, the attribute is made nonresident, leaving only run information in the root. 3. If the attribute is already nonresident, then it can be moved to a separate file record. If none of the above operations can be performed, or not enough free space is recovered, then as a last resort the file record is split in two. This would typically indicate that the file record is populated with a large number of small attributes. The first time step 3 above or a split of the file record occurs, the attribute list must be created for the file. Arguments: Fcb - Requested file. SizeNeeded - Supplies the total amount of free space needed, in bytes. Context - Describes the insertion point for the attribute which does not fit. NOTE -- This context is not valid on return. Return Value: None --*/ { PATTRIBUTE_RECORD_HEADER LargestAttributes[MAX_MOVEABLE_ATTRIBUTES]; PATTRIBUTE_RECORD_HEADER Attribute; PFILE_RECORD_SEGMENT_HEADER FileRecord; ULONG i; PVCB Vcb = Fcb->Vcb; PAGED_CODE(); // // Here is the current threshhold at which a move of an attribute will // be considered. // FileRecord = NtfsContainingFileRecord( Context ); // // Find the largest attributes for this file record. // FindLargestAttributes( FileRecord, MAX_MOVEABLE_ATTRIBUTES, LargestAttributes ); // // Now loop from largest to smallest of the largest attributes, // and see if there is something we can do. // for (i = 0; i < MAX_MOVEABLE_ATTRIBUTES; i += 1) { Attribute = LargestAttributes[i]; // // Look to the next attribute if there is no attribute at this array // position. // if (Attribute == NULL) { continue; // // If this is the Mft then any attribute that is 'BigEnoughToMove' // except $DATA/$PROPERTY_SET attributes outside the base file record. // We need to keep those where they are in order to enforce the // boot-strap mapping. // } else if (Fcb == Vcb->MftScb->Fcb) { if (Attribute->TypeCode == $DATA && ((*(PLONGLONG) &FileRecord->BaseFileRecordSegment != 0) || (Attribute->RecordLength < Vcb->BigEnoughToMove))) { continue; } // // Any attribute in a non-Mft file which is 'BigEnoughToMove' can // be considered. We also accept an $ATTRIBUTE_LIST attribute // in a non-Mft file which must go non-resident in order for // the attribute name to fit. Otherwise we could be trying to // add an attribute with a large name into the base file record. // We will need space to store the name twice, once for the // attribute list entry and once in the attribute. This can take // up 1024 bytes by itself. We want to force the attribute list // non-resident first so that the new attribute will fit. We // look at whether the attribute list followed by just the new data // will fit in the file record. // } else if (Attribute->RecordLength < Vcb->BigEnoughToMove) { if ((Attribute->TypeCode != $ATTRIBUTE_LIST) || ((PtrOffset( FileRecord, Attribute ) + Attribute->RecordLength + SizeNeeded + sizeof( LONGLONG)) <= FileRecord->BytesAvailable)) { continue; } } // // If this attribute is an index root, then we can just call the // indexing support to allocate a new index buffer and push the // current resident contents down. // if (Attribute->TypeCode == $INDEX_ROOT) { PSCB IndexScb; UNICODE_STRING IndexName; IndexName.Length = IndexName.MaximumLength = (USHORT)Attribute->NameLength << 1; IndexName.Buffer = Add2Ptr( Attribute, Attribute->NameOffset ); IndexScb = NtfsCreateScb( IrpContext, Fcb, $INDEX_ALLOCATION, &IndexName, FALSE, NULL ); NtfsPushIndexRoot( IrpContext, IndexScb ); return; // // Otherwise, if this is a resident attribute which can go nonresident, // then make it nonresident now. // } else if ((Attribute->FormCode == RESIDENT_FORM) && !FlagOn(NtfsGetAttributeDefinition(Vcb, Attribute->TypeCode)->Flags, ATTRIBUTE_DEF_MUST_BE_RESIDENT)) { NtfsConvertToNonresident( IrpContext, Fcb, Attribute, FALSE, NULL ); return; // // Finally, if the attribute is nonresident already, move it to its // own record unless it is an attribute list. // } else if ((Attribute->FormCode == NONRESIDENT_FORM) && (Attribute->TypeCode != $ATTRIBUTE_LIST)) { LONGLONG MftFileOffset; MftFileOffset = Context->FoundAttribute.MftFileOffset; MoveAttributeToOwnRecord( IrpContext, Fcb, Attribute, Context, NULL, NULL ); return; } } // // If we get here, it is because we failed to find enough space above. // Our last resort is to split into two file records, and this has // to work. We should never reach this point for the Mft. // if (Fcb == Vcb->MftScb->Fcb) { NtfsRaiseStatus( IrpContext, STATUS_DISK_FULL, NULL, NULL ); } SplitFileRecord( IrpContext, Fcb, SizeNeeded, Context ); } // // Internal support routine // VOID FindLargestAttributes ( IN PFILE_RECORD_SEGMENT_HEADER FileRecord, IN ULONG Number, OUT PATTRIBUTE_RECORD_HEADER *AttributeArray ) /*++ Routine Description: This routine returns the n largest attributes from a file record in an array, ordered from largest to smallest. Arguments: FileRecord - Supplies file record to scan for largest attributes. Number - Supplies the number of entries in the array. AttributeArray - Supplies the array which is to receive pointers to the largest attributes. This array must be zeroed prior to calling this routine. Return Value: None --*/ { ULONG i, j; PATTRIBUTE_RECORD_HEADER Attribute; PAGED_CODE(); RtlZeroMemory( AttributeArray, Number * sizeof(PATTRIBUTE_RECORD_HEADER) ); Attribute = Add2Ptr( FileRecord, FileRecord->FirstAttributeOffset ); while (Attribute->TypeCode != $END) { for (i = 0; i < Number; i++) { if ((AttributeArray[i] == NULL) || (AttributeArray[i]->RecordLength < Attribute->RecordLength)) { for (j = Number - 1; j != i; j--) { AttributeArray[j] = AttributeArray[j-1]; } AttributeArray[i] = Attribute; break; } } Attribute = Add2Ptr( Attribute, Attribute->RecordLength ); } } // // Internal support routine // LONGLONG MoveAttributeToOwnRecord ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN PATTRIBUTE_RECORD_HEADER Attribute, IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context, OUT PBCB *NewBcb OPTIONAL, OUT PFILE_RECORD_SEGMENT_HEADER *NewFileRecord OPTIONAL ) /*++ Routine Description: This routine may be called to move a particular attribute to a separate file record. If the file does not already have an attribute list, then one is created (else it is updated). Arguments: Fcb - Requested file. Attribute - Supplies a pointer to the attribute which is to be moved. Context - Supplies a pointer to a context which was used to look up another attribute in the same file record. If this is an Mft $DATA split we will point to the part that was split out of the first file record on return. The call from NtfsAddAttributeAllocation depends on this. NewBcb - If supplied, returns the Bcb address for the file record that the attribute was moved to. NewBcb and NewFileRecord must either both be specified or neither specified. NewFileRecord - If supplied, returns a pointer to the file record that the attribute was moved to. The caller may assume that the moved attribute is the first one in the file record. NewBcb and NewFileRecord must either both be specified or neither specified. Return Value: LONGLONG - Segment reference number of new record without a sequence number. --*/ { ATTRIBUTE_ENUMERATION_CONTEXT ListContext; ATTRIBUTE_ENUMERATION_CONTEXT MoveContext; PFILE_RECORD_SEGMENT_HEADER FileRecord1, FileRecord2; PATTRIBUTE_RECORD_HEADER Attribute2; BOOLEAN FoundListContext; MFT_SEGMENT_REFERENCE Reference2; LONGLONG MftRecordNumber2; WCHAR NameBuffer[8]; UNICODE_STRING AttributeName; ATTRIBUTE_TYPE_CODE AttributeTypeCode; VCN LowestVcn; BOOLEAN Found; BOOLEAN IsNonresident = FALSE; PBCB Bcb = NULL; PATTRIBUTE_TYPE_CODE NewEnd; PVCB Vcb = Fcb->Vcb; ULONG NewListSize = 0; BOOLEAN MftData = FALSE; PATTRIBUTE_RECORD_HEADER OldPosition = NULL; PAGED_CODE(); // // Make sure the attribute is pinned. // NtfsPinMappedAttribute( IrpContext, Vcb, Context ); // // See if we are being asked to move the Mft Data. // if ((Fcb == Vcb->MftScb->Fcb) && (Attribute->TypeCode == $DATA)) { MftData = TRUE; } NtfsInitializeAttributeContext( &ListContext ); NtfsInitializeAttributeContext( &MoveContext ); FileRecord1 = NtfsContainingFileRecord(Context); // // Save a description of the attribute to help us look it up // again, and to make clones if necessary. // ASSERT( Attribute->RecordLength == QuadAlign( Attribute->RecordLength )); AttributeTypeCode = Attribute->TypeCode; AttributeName.Length = AttributeName.MaximumLength = (USHORT)Attribute->NameLength << 1; AttributeName.Buffer = NameBuffer; if (AttributeName.Length > sizeof(NameBuffer)) { AttributeName.Buffer = NtfsAllocatePool( NonPagedPool, AttributeName.Length ); } RtlCopyMemory( AttributeName.Buffer, Add2Ptr(Attribute, Attribute->NameOffset), AttributeName.Length ); if (Attribute->FormCode == NONRESIDENT_FORM) { IsNonresident = TRUE; LowestVcn = Attribute->Form.Nonresident.LowestVcn; } try { // // Lookup the list context so that we know where it is at. // FoundListContext = NtfsLookupAttributeByCode( IrpContext, Fcb, &Fcb->FileReference, $ATTRIBUTE_LIST, &ListContext ); // // If we do not already have an attribute list, then calculate // how big it must be. Note, there must only be one file record // at this point. // if (!FoundListContext) { ASSERT( FileRecord1 == NtfsContainingFileRecord(&ListContext) ); NewListSize = GetSizeForAttributeList( FileRecord1 ); // // Now if the attribute list already exists, we have to look up // the first one we are going to move in order to update the // attribute list later. // } else { Found = NtfsLookupAttributeByName( IrpContext, Fcb, &Fcb->FileReference, Attribute->TypeCode, &AttributeName, IsNonresident ? &LowestVcn : NULL, FALSE, &MoveContext ); ASSERT(Found); ASSERT(Attribute == NtfsFoundAttribute(&MoveContext)); } // // Allocate a new file record and move the attribute over. // FileRecord2 = NtfsCloneFileRecord( IrpContext, Fcb, MftData, &Bcb, &Reference2 ); // // Remember the file record number for the new file record. // MftRecordNumber2 = NtfsFullSegmentNumber( &Reference2 ); Attribute2 = Add2Ptr( FileRecord2, FileRecord2->FirstAttributeOffset ); RtlCopyMemory( Attribute2, Attribute, (ULONG)Attribute->RecordLength ); Attribute2->Instance = FileRecord2->NextAttributeInstance++; NewEnd = Add2Ptr( Attribute2, Attribute2->RecordLength ); *NewEnd = $END; FileRecord2->FirstFreeByte = PtrOffset(FileRecord2, NewEnd) + QuadAlign( sizeof( ATTRIBUTE_TYPE_CODE )); // // If this is the Mft Data attribute, we cannot really move it, we // have to move all but the first part of it. // if (MftData) { PCHAR MappingPairs; ULONG NewSize; VCN OriginalLastVcn; VCN LastVcn; LONGLONG SavedFileSize = Attribute->Form.Nonresident.FileSize; LONGLONG SavedValidDataLength = Attribute->Form.Nonresident.ValidDataLength; PNTFS_MCB Mcb = &Vcb->MftScb->Mcb; NtfsCleanupAttributeContext( Context ); NtfsInitializeAttributeContext( Context ); Found = NtfsLookupAttributeByCode( IrpContext, Fcb, &Fcb->FileReference, $DATA, Context ); ASSERT(Found); // // Calculate the number of clusters in the Mft up to (possibly past) the // first user file record, and decrement to get LastVcn to stay in first // file record. // LastVcn = LlClustersFromBytes( Vcb, FIRST_USER_FILE_NUMBER * Vcb->BytesPerFileRecordSegment ) - 1; OriginalLastVcn = Attribute->Form.Nonresident.HighestVcn; // // Now truncate the first Mft record. // NtfsDeleteAttributeAllocation( IrpContext, Vcb->MftScb, TRUE, &LastVcn, Context, FALSE ); // // Now get the first Lcn for the new file record. // LastVcn = Attribute->Form.Nonresident.HighestVcn + 1; Attribute2->Form.Nonresident.LowestVcn = LastVcn; // // Calculate the size of the attribute record we will need. // We only create mapping pairs through the highest Vcn on the // disk. We don't include any that are being added through the // Mcb yet. // NewSize = SIZEOF_PARTIAL_NONRES_ATTR_HEADER + QuadAlign( AttributeName.Length ) + QuadAlign( NtfsGetSizeForMappingPairs( Mcb, MAXULONG, LastVcn, &OriginalLastVcn, &LastVcn )); Attribute2->RecordLength = NewSize; // // Assume no attribute name, and calculate where the Mapping Pairs // will go. (Update below if we are wrong.) // MappingPairs = (PCHAR)Attribute2 + SIZEOF_PARTIAL_NONRES_ATTR_HEADER; // // If the attribute has a name, take care of that now. // if (AttributeName.Length != 0) { Attribute2->NameLength = (UCHAR)(AttributeName.Length / sizeof(WCHAR)); Attribute2->NameOffset = (USHORT)PtrOffset(Attribute2, MappingPairs); RtlCopyMemory( MappingPairs, AttributeName.Buffer, AttributeName.Length ); MappingPairs += QuadAlign( AttributeName.Length ); } // // We always need the mapping pairs offset. // Attribute2->Form.Nonresident.MappingPairsOffset = (USHORT)PtrOffset(Attribute2, MappingPairs); NewEnd = Add2Ptr( Attribute2, Attribute2->RecordLength ); *NewEnd = $END; FileRecord2->FirstFreeByte = PtrOffset(FileRecord2, NewEnd) + QuadAlign( sizeof( ATTRIBUTE_TYPE_CODE )); // // Now add the space in the file record. // *MappingPairs = 0; NtfsBuildMappingPairs( Mcb, Attribute2->Form.Nonresident.LowestVcn, &LastVcn, MappingPairs ); Attribute2->Form.Nonresident.HighestVcn = LastVcn; } else { // // Now log these changes and fix up the first file record. // FileRecord1->Lsn = NtfsWriteLog( IrpContext, Vcb->MftScb, NtfsFoundBcb(Context), DeleteAttribute, NULL, 0, CreateAttribute, Attribute, Attribute->RecordLength, NtfsMftOffset( Context ), (PCHAR)Attribute - (PCHAR)FileRecord1, 0, Vcb->BytesPerFileRecordSegment ); // // Remember the old position for the CreateAttributeList // OldPosition = Attribute; NtfsRestartRemoveAttribute( IrpContext, FileRecord1, (PCHAR)Attribute - (PCHAR)FileRecord1 ); } FileRecord2->Lsn = NtfsWriteLog( IrpContext, Vcb->MftScb, Bcb, InitializeFileRecordSegment, FileRecord2, FileRecord2->FirstFreeByte, Noop, NULL, 0, LlBytesFromFileRecords( Vcb, MftRecordNumber2 ), 0, 0, Vcb->BytesPerFileRecordSegment ); // // Finally, create the attribute list attribute if needed. // if (!FoundListContext) { NtfsCleanupAttributeContext( &ListContext ); NtfsInitializeAttributeContext( &ListContext ); CreateAttributeList( IrpContext, Fcb, FileRecord1, MftData ? NULL : FileRecord2, Reference2, OldPosition, NewListSize, &ListContext ); // // Otherwise we have to update the existing attribute list, but only // if this is not the Mft data. In that case the attribute list is // still correct since we haven't moved the attribute entirely. // } else if (!MftData) { UpdateAttributeListEntry( IrpContext, Fcb, &MoveContext.AttributeList.Entry->SegmentReference, MoveContext.AttributeList.Entry->Instance, &Reference2, Attribute2->Instance, &ListContext ); } NtfsCleanupAttributeContext( Context ); NtfsInitializeAttributeContext( Context ); Found = NtfsLookupAttributeByName( IrpContext, Fcb, &Fcb->FileReference, AttributeTypeCode, &AttributeName, IsNonresident ? &LowestVcn : NULL, FALSE, Context ); ASSERT(Found); ASSERT(!IsNonresident || (LowestVcn == NtfsFoundAttribute(Context)->Form.Nonresident.LowestVcn)); // // For the case of the Mft split, we now add the final entry. // if (MftData) { // // Finally, we have to add the entry to the attribute list. // The routine we have to do this gets most of its inputs // out of an attribute context. Our context at this point // does not have quite the right information, so we have to // update it here before calling AddToAttributeList. // Context->FoundAttribute.FileRecord = FileRecord2; Context->FoundAttribute.Attribute = Attribute2; Context->AttributeList.Entry = NtfsGetNextRecord(Context->AttributeList.Entry); NtfsAddToAttributeList( IrpContext, Fcb, Reference2, Context ); NtfsCleanupAttributeContext( Context ); NtfsInitializeAttributeContext( Context ); Found = NtfsLookupAttributeByCode( IrpContext, Fcb, &Fcb->FileReference, $DATA, Context ); ASSERT(Found); while (IsNonresident && (Attribute2->Form.Nonresident.LowestVcn != NtfsFoundAttribute(Context)->Form.Nonresident.LowestVcn)) { Found = NtfsLookupNextAttributeByCode( IrpContext, Fcb, $DATA, Context ); ASSERT(Found); } } } finally { if (AttributeName.Buffer != NameBuffer) { NtfsFreePool(AttributeName.Buffer); } if (ARGUMENT_PRESENT(NewBcb)) { ASSERT(ARGUMENT_PRESENT(NewFileRecord)); *NewBcb = Bcb; *NewFileRecord = FileRecord2; } else { ASSERT(!ARGUMENT_PRESENT(NewFileRecord)); NtfsUnpinBcb( &Bcb ); } NtfsCleanupAttributeContext( &ListContext ); NtfsCleanupAttributeContext( &MoveContext ); } return MftRecordNumber2; } // // Internal support routine // VOID SplitFileRecord ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN ULONG SizeNeeded, IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context ) /*++ Routine Description: This routine splits a file record in two, when it has been found that there is no room for a new attribute. If the file does not already have an attribute list attribute then one is created. Essentially this routine finds the midpoint in the current file record (accounting for a potential new attribute list and also the space needed). Then it copies the second half of the file record over and fixes up the first record. The attribute list is created at the end if required. Arguments: Fcb - Requested file. SizeNeeded - Supplies the additional size needed, which is causing the split to occur. Context - Supplies the attribute enumeration context pointing to the spot where the new attribute is to be inserted or grown. Return Value: None --*/ { ATTRIBUTE_ENUMERATION_CONTEXT ListContext; ATTRIBUTE_ENUMERATION_CONTEXT MoveContext; PFILE_RECORD_SEGMENT_HEADER FileRecord1, FileRecord2; PATTRIBUTE_RECORD_HEADER Attribute1, Attribute2, Attribute; ULONG NewListOffset = 0; ULONG NewListSize = 0; ULONG NewAttributeOffset; ULONG SizeToStay; ULONG CurrentOffset, FutureOffset; ULONG SizeToMove; BOOLEAN FoundListContext; MFT_SEGMENT_REFERENCE Reference1, Reference2; LONGLONG MftFileRecord2; PBCB Bcb = NULL; ATTRIBUTE_TYPE_CODE EndCode = $END; PVCB Vcb = Fcb->Vcb; ULONG AdjustedAvailBytes; PAGED_CODE(); // // Make sure the attribute is pinned. // NtfsPinMappedAttribute( IrpContext, Vcb, Context ); // // Something is broken if we decide to split an Mft record. // ASSERT(Fcb != Vcb->MftScb->Fcb); NtfsInitializeAttributeContext( &ListContext ); NtfsInitializeAttributeContext( &MoveContext ); FileRecord1 = NtfsContainingFileRecord(Context); Attribute1 = NtfsFoundAttribute(Context); try { // // Lookup the list context so that we know where it is at. // FoundListContext = NtfsLookupAttributeByCode( IrpContext, Fcb, &Fcb->FileReference, $ATTRIBUTE_LIST, &ListContext ); // // If we do not already have an attribute list, then calculate // where it will go and how big it must be. Note, there must // only be one file record at this point. // if (!FoundListContext) { ASSERT( FileRecord1 == NtfsContainingFileRecord(&ListContext) ); NewListOffset = PtrOffset( FileRecord1, NtfsFoundAttribute(&ListContext) ); NewListSize = GetSizeForAttributeList( FileRecord1 ) + SIZEOF_RESIDENT_ATTRIBUTE_HEADER; } // // Similarly describe where the new attribute is to go, and how // big it is (already in SizeNeeded). // NewAttributeOffset = PtrOffset( FileRecord1, Attribute1 ); // // Now calculate the approximate number of bytes that is to be split // across two file records, and divide it in two, and that should give // the amount that is to stay in the first record. // SizeToStay = (FileRecord1->FirstFreeByte + NewListSize + SizeNeeded + sizeof(FILE_RECORD_SEGMENT_HEADER)) / 2; // // We know that since we called this routine we need to split at // least one entry from this file record. We also base our // split logic by finding the first attribute which WILL lie beyond // the split point (after adding an attribute list and possibly // an intermediate attribute). We shrink the split point to the // position at the end of where the current last attribute will be // after adding the attribute list. If we also add space before // the last attribute then we know the last attribute will surely // be split out. // if (SizeToStay > (FileRecord1->FirstFreeByte - sizeof( LONGLONG ) + NewListSize)) { SizeToStay = FileRecord1->FirstFreeByte - sizeof( LONGLONG ) + NewListSize; } // // Now begin the loop through the attributes to find the splitting // point. We stop when we reach the end record or are past the attribute // which contains the split point. We will split at the current attribute // if the remaining bytes after this attribute won't allow us to add // the bytes we need for the caller or create an attribute list if // it doesn't exist. // // At this point the following variables indicate the following: // // FutureOffset - This the offset of the current attribute // after adding an attribute list and the attribute we // are making space for. // // CurrentOffset - Current position in the file record of // of attribute being examined now. // // NewListOffset - Offset to insert new attribute list into // file record (0 indicates the list already exists). // // NewAttributeOffset - Offset in the file record of the new // attribute. This refers to the file record as it exists // when this routine is called. // FutureOffset = CurrentOffset = (ULONG)FileRecord1->FirstAttributeOffset; Attribute1 = Add2Ptr( FileRecord1, CurrentOffset ); AdjustedAvailBytes = FileRecord1->BytesAvailable - QuadAlign( sizeof( ATTRIBUTE_TYPE_CODE )); while (Attribute1->TypeCode != $END) { // // See if the attribute list goes here. // if (CurrentOffset == NewListOffset) { // // This attribute and all later attributes will be moved // by the size of attribute list. // FutureOffset += NewListSize; } // // See if the new attribute goes here. // if (CurrentOffset == NewAttributeOffset) { // // This attribute and all later attributes will be moved // by the size of new attribute. // FutureOffset += SizeNeeded; } FutureOffset += Attribute1->RecordLength; // // Check if we are at the split point. We split at this point // if the end of the current attribute will be at or beyond the // split point after adjusting for adding either an attribute list // or new attribute. We make this test >= since these two values // will be equal if we reach the last attribute without finding // the split point. This way we guarantee a split will happen. // // Note that we will go to the next attribute if the current attribute // is the first attribute in the file record. This can happen if the // first attribute is resident and must stay resident but takes up // half the file record or more (i.e. large filename attribute). // We must make sure to split at least one attribute out of this // record. // // Never split when pointing at $STANDARD_INFORMATION or $ATTRIBUTE_LIST. // if ((Attribute1->TypeCode > $ATTRIBUTE_LIST) && (FutureOffset >= SizeToStay) && (CurrentOffset != FileRecord1->FirstAttributeOffset)) { break; } CurrentOffset += Attribute1->RecordLength; Attribute1 = Add2Ptr( Attribute1, Attribute1->RecordLength ); } SizeToMove = FileRecord1->FirstFreeByte - CurrentOffset; // // If we are pointing at the attribute list or at the end record // we don't do the split. // if ((Attribute1->TypeCode == $END) || (Attribute1->TypeCode <= $ATTRIBUTE_LIST)) { NtfsRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES, NULL, NULL ); } // // Now if the attribute list already exists, we have to look up // the first one we are going to move in order to update the // attribute list later. // if (FoundListContext) { UNICODE_STRING AttributeName; BOOLEAN FoundIt; AttributeName.Length = AttributeName.MaximumLength = (USHORT)Attribute1->NameLength << 1; AttributeName.Buffer = Add2Ptr( Attribute1, Attribute1->NameOffset ); FoundIt = NtfsLookupAttributeByName( IrpContext, Fcb, &Fcb->FileReference, Attribute1->TypeCode, &AttributeName, (Attribute1->FormCode == NONRESIDENT_FORM) ? &Attribute1->Form.Nonresident.LowestVcn : NULL, FALSE, &MoveContext ); // // If we are splitting the file record between multiple attributes with // the same name (i.e. FILE_NAME attributes) then we need to find the // correct attribute. Since this is an unusual case we will just scan // forwards from the current attribute until we find the correct attribute. // while (FoundIt && (Attribute1 != NtfsFoundAttribute( &MoveContext ))) { FoundIt = NtfsLookupNextAttributeByName( IrpContext, Fcb, Attribute1->TypeCode, &AttributeName, FALSE, &MoveContext ); } ASSERT(FoundIt); ASSERT(Attribute1 == NtfsFoundAttribute(&MoveContext)); } // // Now Attribute1 is pointing to the first attribute to move. // Allocate a new file record and move the rest of our attributes // over. // if (FoundListContext) { Reference1 = MoveContext.AttributeList.Entry->SegmentReference; } FileRecord2 = NtfsCloneFileRecord( IrpContext, Fcb, FALSE, &Bcb, &Reference2 ); // // Capture the file record number of the new file record. // MftFileRecord2 = NtfsFullSegmentNumber( &Reference2 ); Attribute2 = Add2Ptr( FileRecord2, FileRecord2->FirstAttributeOffset ); RtlCopyMemory( Attribute2, Attribute1, SizeToMove ); FileRecord2->FirstFreeByte = (ULONG)FileRecord2->FirstAttributeOffset + SizeToMove; // // Loop to update all of the attribute instance codes // for (Attribute = Attribute2; Attribute < (PATTRIBUTE_RECORD_HEADER)Add2Ptr(FileRecord2, FileRecord2->FirstFreeByte) && Attribute->TypeCode != $END; Attribute = NtfsGetNextRecord(Attribute)) { NtfsCheckRecordBound( Attribute, FileRecord2, Vcb->BytesPerFileRecordSegment ); if (FoundListContext) { UpdateAttributeListEntry( IrpContext, Fcb, &Reference1, Attribute->Instance, &Reference2, FileRecord2->NextAttributeInstance, &ListContext ); } Attribute->Instance = FileRecord2->NextAttributeInstance++; } // // Now log these changes and fix up the first file record. // FileRecord2->Lsn = NtfsWriteLog( IrpContext, Vcb->MftScb, Bcb, InitializeFileRecordSegment, FileRecord2, FileRecord2->FirstFreeByte, Noop, NULL, 0, LlBytesFromFileRecords( Vcb, MftFileRecord2 ), 0, 0, Vcb->BytesPerFileRecordSegment ); FileRecord1->Lsn = NtfsWriteLog( IrpContext, Vcb->MftScb, NtfsFoundBcb(Context), WriteEndOfFileRecordSegment, &EndCode, sizeof(ATTRIBUTE_TYPE_CODE), WriteEndOfFileRecordSegment, Attribute1, SizeToMove, NtfsMftOffset( Context ), CurrentOffset, 0, Vcb->BytesPerFileRecordSegment ); NtfsRestartWriteEndOfFileRecord( FileRecord1, Attribute1, (PATTRIBUTE_RECORD_HEADER)&EndCode, sizeof(ATTRIBUTE_TYPE_CODE) ); // // Finally, create the attribute list attribute if needed. // if (!FoundListContext) { NtfsCleanupAttributeContext( &ListContext ); NtfsInitializeAttributeContext( &ListContext ); CreateAttributeList( IrpContext, Fcb, FileRecord1, FileRecord2, Reference2, NULL, NewListSize - SIZEOF_RESIDENT_ATTRIBUTE_HEADER, &ListContext ); } } finally { NtfsUnpinBcb( &Bcb ); NtfsCleanupAttributeContext( &ListContext ); NtfsCleanupAttributeContext( &MoveContext ); } } VOID NtfsRestartWriteEndOfFileRecord ( IN PFILE_RECORD_SEGMENT_HEADER FileRecord, IN PATTRIBUTE_RECORD_HEADER OldAttribute, IN PATTRIBUTE_RECORD_HEADER NewAttributes, IN ULONG SizeOfNewAttributes ) /*++ Routine Description: This routine is called both in the running system and at restart to modify the end of a file record, such as after it was split in two. Arguments: FileRecord - Supplies the pointer to the file record. OldAttribute - Supplies a pointer to the first attribute to be overwritten. NewAttributes - Supplies a pointer to the new attribute(s) to be copied to the spot above. SizeOfNewAttributes - Supplies the size to be copied in bytes. Return Value: None. --*/ { PAGED_CODE(); RtlMoveMemory( OldAttribute, NewAttributes, SizeOfNewAttributes ); FileRecord->FirstFreeByte = PtrOffset(FileRecord, OldAttribute) + SizeOfNewAttributes; // // The size coming in may not be quad aligned. // FileRecord->FirstFreeByte = QuadAlign( FileRecord->FirstFreeByte ); } // // Internal support routine // PFILE_RECORD_SEGMENT_HEADER NtfsCloneFileRecord ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN BOOLEAN MftData, OUT PBCB *Bcb, OUT PMFT_SEGMENT_REFERENCE FileReference ) /*++ Routine Description: This routine allocates an additional file record for an already existing and open file, for the purpose of overflowing attributes to this record. Arguments: Fcb - Requested file. MftData - TRUE if the file record is being cloned to describe the $DATA attribute for the Mft. Bcb - Returns a pointer to the Bcb for the new file record. FileReference - returns the file reference for the new file record. Return Value: Pointer to the allocated file record. --*/ { LONGLONG FileRecordOffset; PFILE_RECORD_SEGMENT_HEADER FileRecord; PVCB Vcb = Fcb->Vcb; PAGED_CODE(); // // First allocate the record. // *FileReference = NtfsAllocateMftRecord( IrpContext, Vcb, MftData ); // // Read it in and pin it. // NtfsPinMftRecord( IrpContext, Vcb, FileReference, TRUE, Bcb, &FileRecord, &FileRecordOffset ); // // Initialize it. // NtfsInitializeMftRecord( IrpContext, Vcb, FileReference, FileRecord, *Bcb, BooleanIsDirectory( &Fcb->Info )); FileRecord->BaseFileRecordSegment = Fcb->FileReference; FileRecord->ReferenceCount = 0; FileReference->SequenceNumber = FileRecord->SequenceNumber; return FileRecord; } // // Internal support routine // ULONG GetSizeForAttributeList ( IN PFILE_RECORD_SEGMENT_HEADER FileRecord ) /*++ Routine Description: This routine is designed to calculate the size that will be required for an attribute list attribute, for a base file record which is just about to split into two file record segments. Arguments: FileRecord - Pointer to the file record which is just about to split. Return Value: Size in bytes of the attribute list attribute that will be required, not including the attribute header size. --*/ { PATTRIBUTE_RECORD_HEADER Attribute; ULONG Size = 0; PAGED_CODE(); // // Point to first attribute. // Attribute = Add2Ptr(FileRecord, FileRecord->FirstAttributeOffset); // // Loop to add up size of required attribute list entries. // while (Attribute->TypeCode != $END) { Size += QuadAlign( FIELD_OFFSET( ATTRIBUTE_LIST_ENTRY, AttributeName ) + ((ULONG) Attribute->NameLength << 1)); Attribute = Add2Ptr( Attribute, Attribute->RecordLength ); } return Size; } // // Internal support routine // VOID CreateAttributeList ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN PFILE_RECORD_SEGMENT_HEADER FileRecord1, IN PFILE_RECORD_SEGMENT_HEADER FileRecord2 OPTIONAL, IN MFT_SEGMENT_REFERENCE SegmentReference2, IN PATTRIBUTE_RECORD_HEADER OldPosition OPTIONAL, IN ULONG SizeOfList, IN OUT PATTRIBUTE_ENUMERATION_CONTEXT ListContext ) /*++ Routine Description: This routine is intended to be called to create the attribute list attribute the first time. The caller must have already calculated the size required for the list to pass into this routine. The caller must have already removed any attributes from the base file record (FileRecord1) which are not to remain there. He must then pass in a pointer to the base file record and optionally a pointer to a second file record from which the new attribute list is to be created. Arguments: Fcb - Requested file. FileRecord1 - Pointer to the base file record, currently holding only those attributes to be described there. FileRecord2 - Optionally points to a second file record from which the second half of the attribute list is to be constructed. SegmentReference2 - The Mft segment reference of the second file record, if one was supplied. OldPosition - Should only be specified if FileRecord2 is specified. In this case it must point to an attribute position in FileRecord1 from which a single attribute was moved to file record 2. It will be used as an indication of where the attribute list entry should be inserted. SizeOfList - Exact size of the attribute list which will be required. ListContext - Context resulting from an attempt to look up the attribute list attribute, which failed. Return Value: None --*/ { PFILE_RECORD_SEGMENT_HEADER FileRecord; PATTRIBUTE_RECORD_HEADER Attribute; PATTRIBUTE_LIST_ENTRY AttributeList, ListEntry; MFT_SEGMENT_REFERENCE SegmentReference; PAGED_CODE(); // // Allocate space to construct the attribute list. (The list // cannot be constructed in place, because that would destroy error // recovery.) // ListEntry = AttributeList = (PATTRIBUTE_LIST_ENTRY) NtfsAllocatePool(PagedPool, SizeOfList ); // // Use try-finally to deallocate on the way out. // try { // // Loop to fill in the attribute list from the two file records // for (FileRecord = FileRecord1, SegmentReference = Fcb->FileReference; FileRecord != NULL; FileRecord = ((FileRecord == FileRecord1) ? FileRecord2 : NULL), SegmentReference = SegmentReference2) { // // Point to first attribute. // Attribute = Add2Ptr( FileRecord, FileRecord->FirstAttributeOffset ); // // Loop to add up size of required attribute list entries. // while (Attribute->TypeCode != $END) { PATTRIBUTE_RECORD_HEADER NextAttribute; // // See if we are at the remembered position. If so: // // Save this attribute to be the next one. // Point to the single attribute in FileRecord2 instead // Clear FileRecord2, as we will "consume" it here. // Set the Segment reference in the ListEntry // if ((Attribute == OldPosition) && (FileRecord2 != NULL)) { NextAttribute = Attribute; Attribute = Add2Ptr(FileRecord2, FileRecord2->FirstAttributeOffset); FileRecord2 = NULL; ListEntry->SegmentReference = SegmentReference2; // // Otherwise, this is the normal loop case. So: // // Set the next attribute pointer accordingly. // Set the Segment reference from the loop control // } else { NextAttribute = Add2Ptr(Attribute, Attribute->RecordLength); ListEntry->SegmentReference = SegmentReference; } // // Now fill in the list entry. // ListEntry->AttributeTypeCode = Attribute->TypeCode; ListEntry->RecordLength = (USHORT) QuadAlign( FIELD_OFFSET( ATTRIBUTE_LIST_ENTRY, AttributeName ) + ((ULONG) Attribute->NameLength << 1)); ListEntry->AttributeNameLength = Attribute->NameLength; ListEntry->AttributeNameOffset = (UCHAR)PtrOffset( ListEntry, &ListEntry->AttributeName[0] ); ListEntry->Instance = Attribute->Instance; ListEntry->LowestVcn = 0; if (Attribute->FormCode == NONRESIDENT_FORM) { ListEntry->LowestVcn = Attribute->Form.Nonresident.LowestVcn; } if (Attribute->NameLength != 0) { RtlCopyMemory( &ListEntry->AttributeName[0], Add2Ptr(Attribute, Attribute->NameOffset), Attribute->NameLength << 1 ); } ListEntry = Add2Ptr(ListEntry, ListEntry->RecordLength); Attribute = NextAttribute; } } // // Now create the attribute list attribute. // NtfsCreateAttributeWithValue( IrpContext, Fcb, $ATTRIBUTE_LIST, NULL, AttributeList, SizeOfList, 0, NULL, TRUE, ListContext ); } finally { NtfsFreePool( AttributeList ); } } // // Internal support routine // VOID UpdateAttributeListEntry ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN PMFT_SEGMENT_REFERENCE OldFileReference, IN USHORT OldInstance, IN PMFT_SEGMENT_REFERENCE NewFileReference, IN USHORT NewInstance, IN OUT PATTRIBUTE_ENUMERATION_CONTEXT ListContext ) /*++ Routine Description: This routine may be called to update a range of the attribute list as required by the movement of a range of attributes to a second record. The caller must supply a pointer to the file record to which the attributes have moved, along with the segment reference of that record. Arguments: Fcb - Requested file. OldFileReference - Old File Reference for attribute OldInstance - Old Instance number for attribute NewFileReference - New File Reference for attribute NewInstance - New Instance number for attribute ListContext - The attribute enumeration context which was used to locate the attribute list. Return Value: None --*/ { PATTRIBUTE_LIST_ENTRY AttributeList, ListEntry, BeyondList; PBCB Bcb = NULL; ULONG SizeOfList; ATTRIBUTE_LIST_ENTRY NewEntry; PATTRIBUTE_RECORD_HEADER Attribute; PAGED_CODE(); // // Map the attribute list if the attribute is non-resident. Otherwise the // attribute is already mapped and we have a Bcb in the attribute context. // Attribute = NtfsFoundAttribute( ListContext ); if (!NtfsIsAttributeResident( Attribute )) { NtfsMapAttributeValue( IrpContext, Fcb, (PVOID *) &AttributeList, &SizeOfList, &Bcb, ListContext ); // // Don't call the Map attribute routine because it NULLs the Bcb in the // attribute list. This Bcb is needed for ChangeAttributeValue to mark // the page dirty. // } else { AttributeList = (PATTRIBUTE_LIST_ENTRY) NtfsAttributeValue( Attribute ); SizeOfList = Attribute->Form.Resident.ValueLength; } // // Make sure we unpin the list. // try { // // Point beyond the end of the list. // BeyondList = (PATTRIBUTE_LIST_ENTRY)Add2Ptr( AttributeList, SizeOfList ); // // Loop through all of the attribute list entries until we find the one // we need to change. // for (ListEntry = AttributeList; ListEntry < BeyondList; ListEntry = NtfsGetNextRecord(ListEntry)) { if ((ListEntry->Instance == OldInstance) && NtfsEqualMftRef(&ListEntry->SegmentReference, OldFileReference)) { break; } } // // We better have found it! // ASSERT(ListEntry < BeyondList); if (ListEntry >= BeyondList) { NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb ); } // // Make a copy of the fixed portion of the attribute list entry, // and update to describe the new attribute location. // RtlCopyMemory( &NewEntry, ListEntry, sizeof(ATTRIBUTE_LIST_ENTRY) ); NewEntry.SegmentReference = *NewFileReference; NewEntry.Instance = NewInstance; // // Update the attribute list entry. // NtfsChangeAttributeValue( IrpContext, Fcb, PtrOffset(AttributeList, ListEntry), &NewEntry, sizeof(ATTRIBUTE_LIST_ENTRY), FALSE, TRUE, FALSE, TRUE, ListContext ); } finally { NtfsUnpinBcb( &Bcb ); } } // // Local support routine // VOID NtfsAddNameToParent ( IN PIRP_CONTEXT IrpContext, IN PSCB ParentScb, IN PFCB ThisFcb, IN BOOLEAN IgnoreCase, IN PBOOLEAN LogIt, IN PFILE_NAME FileNameAttr, OUT PUCHAR FileNameFlags, OUT PQUICK_INDEX QuickIndex OPTIONAL, IN PNAME_PAIR NamePair OPTIONAL ) /*++ Routine Description: This routine will create the filename attribute with the given name. Depending on the IgnoreCase flag, this is either a link or an Ntfs name. If it is an Ntfs name, we check if it is also the Dos name. We build a file name attribute and then add it via ThisFcb, we then add this entry to the parent. If the name is a Dos name and we are given tunneling information on the long name, we will add the long name attribute as well. Arguments: ParentScb - This is the parent directory for the file. ThisFcb - This is the file to add the filename to. IgnoreCase - Indicates if this name is case insensitive. Only for Posix will this be FALSE. LogIt - Indicates if we should log this operation. If FALSE and this is a large name then log the file record and begin logging. FileNameAttr - This contains a file name attribute structure to use. FileNameFlags - We store a copy of the File name flags used in the file name attribute. QuickIndex - If specified, we store the information about the location of the index entry added. NamePair - If specified, we add the tunneled NTFS-only name if the name we are directly adding is DOS-only. Return Value: None - This routine will raise on error. --*/ { PFILE_RECORD_SEGMENT_HEADER FileRecord; ATTRIBUTE_ENUMERATION_CONTEXT AttrContext; PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsAddNameToParent: Entered\n") ); NtfsInitializeAttributeContext( &AttrContext ); // // Use a try-finally to facilitate cleanup. // try { // // Decide whether the name is a link, Ntfs-Only or Ntfs/8.3 combined name. // Update the filename attribute to reflect this. // if (!IgnoreCase) { *FileNameFlags = 0; } else { UNICODE_STRING FileName; FileName.Length = FileName.MaximumLength = (USHORT)(FileNameAttr->FileNameLength * sizeof(WCHAR)); FileName.Buffer = FileNameAttr->FileName; *FileNameFlags = FILE_NAME_NTFS; if (NtfsIsFatNameValid( &FileName, FALSE )) { *FileNameFlags |= FILE_NAME_DOS; } // // If the name is DOS and there was a tunneled NTFS name, add it first if both names // exist in the pair (there may only be one in the long side). Note that we // really need to do this first so we lay down the correct filename flags. // if (NamePair && (NamePair->Long.Length > 0) && (NamePair->Short.Length > 0) && (*FileNameFlags == (FILE_NAME_NTFS | FILE_NAME_DOS))) { if (NtfsAddTunneledNtfsOnlyName(IrpContext, ParentScb, ThisFcb, &NamePair->Long, LogIt )) { // // Name didn't conflict and was added, so fix up the FileNameFlags // *FileNameFlags = FILE_NAME_DOS; // // We also need to upcase the short DOS name since we don't know the // case of what the user handed us and all DOS names are upcase. Note // that prior to tunneling being supported it was not possible for a user // to specify a short name, so this is a new situation. // RtlUpcaseUnicodeString(&FileName, &FileName, FALSE); } } } // // Now update the file name attribute. // FileNameAttr->Flags = *FileNameFlags; // // If we haven't been logging and this is a large name then begin logging. // if (!(*LogIt) && (FileNameAttr->FileNameLength > 100)) { // // Look up the file record and log its current state. // if (!NtfsLookupAttributeByCode( IrpContext, ThisFcb, &ThisFcb->FileReference, $STANDARD_INFORMATION, &AttrContext )) { NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, ThisFcb ); } NtfsPinMappedAttribute( IrpContext, ThisFcb->Vcb, &AttrContext ); FileRecord = NtfsContainingFileRecord( &AttrContext ); // // Log the current state of the file record. // FileRecord->Lsn = NtfsWriteLog( IrpContext, ThisFcb->Vcb->MftScb, NtfsFoundBcb( &AttrContext ), InitializeFileRecordSegment, FileRecord, FileRecord->FirstFreeByte, Noop, NULL, 0, NtfsMftOffset( &AttrContext ), 0, 0, ThisFcb->Vcb->BytesPerFileRecordSegment ); *LogIt = TRUE; NtfsCleanupAttributeContext( &AttrContext ); NtfsInitializeAttributeContext( &AttrContext ); } // // Put it in the file record. // NtfsCreateAttributeWithValue( IrpContext, ThisFcb, $FILE_NAME, NULL, FileNameAttr, NtfsFileNameSize( FileNameAttr ), 0, &FileNameAttr->ParentDirectory, *LogIt, &AttrContext ); // // Now put it in the index entry. // NtfsAddIndexEntry( IrpContext, ParentScb, FileNameAttr, NtfsFileNameSize( FileNameAttr ), &ThisFcb->FileReference, QuickIndex ); } finally { DebugUnwind( NtfsAddNameToParent ); NtfsCleanupAttributeContext( &AttrContext ); DebugTrace( -1, Dbg, ("NtfsAddNameToParent: Exit\n") ); } return; } // // Local support routine // VOID NtfsAddDosOnlyName ( IN PIRP_CONTEXT IrpContext, IN PSCB ParentScb, IN PFCB ThisFcb, IN UNICODE_STRING FileName, IN BOOLEAN LogIt, IN PUNICODE_STRING SuggestedDosName OPTIONAL ) /*++ Routine Description: This routine is called to build a Dos only name attribute an put it in the file record and the parent index. We need to allocate pool large enough to hold the name (easy for 8.3) and then check that the generated names don't already exist in the parent. Use the suggested name first if possible. Arguments: ParentScb - This is the parent directory for the file. ThisFcb - This is the file to add the filename to. FileName - This is the file name to add. LogIt - Indicates if we should log this operation. SuggestedDosName - If supplied, a name to try to use before auto-generation Return Value: None - This routine will raise on error. --*/ { GENERATE_NAME_CONTEXT NameContext; PFILE_NAME FileNameAttr; UNICODE_STRING Name8dot3; PINDEX_ENTRY IndexEntry; PBCB IndexEntryBcb; UCHAR TrailingDotAdj; ATTRIBUTE_ENUMERATION_CONTEXT AttrContext; BOOLEAN TrySuggestedDosName = TRUE; PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsAddDosOnlyName: Entered\n") ); IndexEntryBcb = NULL; RtlZeroMemory( &NameContext, sizeof( GENERATE_NAME_CONTEXT )); if (SuggestedDosName == NULL || SuggestedDosName->Length == 0) { // // The SuggestedDosName can be zero length if we have a tunneled // link or a tunneled file which was created whilst short name // generation was disabled. It is a bad thing to drop down null // filenames ... // TrySuggestedDosName = FALSE; } // // The maximum length is 24 bytes, but 2 are already defined with the // FILE_NAME structure. // FileNameAttr = NtfsAllocatePool(PagedPool, sizeof( FILE_NAME ) + 22 ); // // Use a try-finally to facilitate cleanup. // try { NtfsInitializeAttributeContext( &AttrContext ); // // Set up the string to hold the generated name. It will be part // of the file name attribute structure. // Name8dot3.Buffer = FileNameAttr->FileName; Name8dot3.MaximumLength = 24; FileNameAttr->ParentDirectory = ParentScb->Fcb->FileReference; FileNameAttr->Flags = FILE_NAME_DOS; // // Copy the info values into the filename attribute. // RtlCopyMemory( &FileNameAttr->Info, &ThisFcb->Info, sizeof( DUPLICATED_INFORMATION )); // // We will loop indefinitely. We generate a name, look in the parent // for it. If found we continue generating. If not then we have the // name we need. Attempt to use the suggested name first. // while( TRUE ) { TrailingDotAdj = 0; if (TrySuggestedDosName) { Name8dot3.Length = SuggestedDosName->Length; RtlCopyMemory(Name8dot3.Buffer, SuggestedDosName->Buffer, SuggestedDosName->Length); Name8dot3.MaximumLength = SuggestedDosName->MaximumLength; } else { RtlGenerate8dot3Name( &FileName, BooleanFlagOn(NtfsData.Flags,NTFS_FLAGS_ALLOW_EXTENDED_CHAR), &NameContext, &Name8dot3 ); if ((Name8dot3.Buffer[(Name8dot3.Length / 2) - 1] == L'.') && (Name8dot3.Length > sizeof( WCHAR ))) { TrailingDotAdj = 1; } } FileNameAttr->FileNameLength = (UCHAR)(Name8dot3.Length / 2) - TrailingDotAdj; if (!NtfsFindIndexEntry( IrpContext, ParentScb, FileNameAttr, TRUE, NULL, &IndexEntryBcb, &IndexEntry )) { break; } NtfsUnpinBcb( &IndexEntryBcb ); if (TrySuggestedDosName) { // // Failed to use the suggested name, so fix up the 8.3 space // Name8dot3.Buffer = FileNameAttr->FileName; Name8dot3.MaximumLength = 24; TrySuggestedDosName = FALSE; } } // // We add this entry to the file record. // NtfsCreateAttributeWithValue( IrpContext, ThisFcb, $FILE_NAME, NULL, FileNameAttr, NtfsFileNameSize( FileNameAttr ), 0, &FileNameAttr->ParentDirectory, LogIt, &AttrContext ); // // We add this entry to the parent. // NtfsAddIndexEntry( IrpContext, ParentScb, FileNameAttr, NtfsFileNameSize( FileNameAttr ), &ThisFcb->FileReference, NULL ); } finally { DebugUnwind( NtfsAddDosOnlyName ); NtfsFreePool( FileNameAttr ); NtfsUnpinBcb( &IndexEntryBcb ); NtfsCleanupAttributeContext( &AttrContext ); DebugTrace( -1, Dbg, ("NtfsAddDosOnlyName: Exit -> %08lx\n") ); } return; } // // Local support routine // BOOLEAN NtfsAddTunneledNtfsOnlyName ( IN PIRP_CONTEXT IrpContext, IN PSCB ParentScb, IN PFCB ThisFcb, IN PUNICODE_STRING FileName, IN PBOOLEAN LogIt ) /*++ Routine Description: This routine is called to attempt to insert a tunneled NTFS-only name attribute and put it in the file record and the parent index. If the name collides with an existing name nothing occurs. Arguments: ParentScb - This is the parent directory for the file. ThisFcb - This is the file to add the filename to. FileName - This is the file name to add. LogIt - Indicates if we should log this operation. If FALSE and this is a large name then log the file record and begin logging. Return Value: Boolean true if the name is added, false otherwise --*/ { PFILE_NAME FileNameAttr; PFILE_RECORD_SEGMENT_HEADER FileRecord; PINDEX_ENTRY IndexEntry; PBCB IndexEntryBcb; BOOLEAN Added = FALSE; ATTRIBUTE_ENUMERATION_CONTEXT AttrContext; PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsAddTunneledNtfsOnlyName: Entered\n") ); IndexEntryBcb = NULL; // // One WCHAR is already defined with the FILE_NAME structure. It is unfortunate // that we need to go to pool to do this ... // FileNameAttr = NtfsAllocatePool(PagedPool, sizeof( FILE_NAME ) + FileName->Length - sizeof(WCHAR) ); // // Use a try-finally to facilitate cleanup. // try { NtfsInitializeAttributeContext( &AttrContext ); RtlCopyMemory( FileNameAttr->FileName, FileName->Buffer, FileName->Length ); FileNameAttr->FileNameLength = FileName->Length / sizeof(WCHAR); FileNameAttr->ParentDirectory = ParentScb->Fcb->FileReference; FileNameAttr->Flags = FILE_NAME_NTFS; // // Copy the info values into the filename attribute. // RtlCopyMemory( &FileNameAttr->Info, &ThisFcb->Info, sizeof( DUPLICATED_INFORMATION )); // // Try out the name // if (!NtfsFindIndexEntry( IrpContext, ParentScb, FileNameAttr, TRUE, NULL, &IndexEntryBcb, &IndexEntry )) { // // Restore the case of the tunneled name // RtlCopyMemory( FileNameAttr->FileName, FileName->Buffer, FileName->Length ); // // If we haven't been logging and this is a large name then begin logging. // if (!(*LogIt) && (FileName->Length > 200)) { // // Look up the file record and log its current state. // if (!NtfsLookupAttributeByCode( IrpContext, ThisFcb, &ThisFcb->FileReference, $STANDARD_INFORMATION, &AttrContext )) { NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, ThisFcb ); } NtfsPinMappedAttribute( IrpContext, ThisFcb->Vcb, &AttrContext ); FileRecord = NtfsContainingFileRecord( &AttrContext ); // // Log the current state of the file record. // FileRecord->Lsn = NtfsWriteLog( IrpContext, ThisFcb->Vcb->MftScb, NtfsFoundBcb( &AttrContext ), InitializeFileRecordSegment, FileRecord, FileRecord->FirstFreeByte, Noop, NULL, 0, NtfsMftOffset( &AttrContext ), 0, 0, ThisFcb->Vcb->BytesPerFileRecordSegment ); *LogIt = TRUE; NtfsCleanupAttributeContext( &AttrContext ); NtfsInitializeAttributeContext( &AttrContext ); } // // We add this entry to the file record. // NtfsCreateAttributeWithValue( IrpContext, ThisFcb, $FILE_NAME, NULL, FileNameAttr, NtfsFileNameSize( FileNameAttr ), 0, &FileNameAttr->ParentDirectory, *LogIt, &AttrContext ); // // We add this entry to the parent. // NtfsAddIndexEntry( IrpContext, ParentScb, FileNameAttr, NtfsFileNameSize( FileNameAttr ), &ThisFcb->FileReference, NULL ); // // Flag the addition // Added = TRUE; } } finally { DebugUnwind( NtfsAddTunneledNtfsOnlyName ); NtfsFreePool( FileNameAttr ); NtfsUnpinBcb( &IndexEntryBcb ); NtfsCleanupAttributeContext( &AttrContext ); DebugTrace( -1, Dbg, ("NtfsAddTunneledNtfsOnlyName: Exit -> %08lx\n", Added) ); } return Added; }