[NTFS] - Add some minor fixes and improvements:
[reactos.git] / drivers / filesystems / ntfs / attrib.c
index 61f2d6f..90e20d3 100644 (file)
 
 /* FUNCTIONS ****************************************************************/
 
+/**
+* @name AddData
+* @implemented
+*
+* Adds a $DATA attribute to a given FileRecord.
+*
+* @param FileRecord
+* Pointer to a complete file record to add the attribute to. Caller is responsible for
+* ensuring FileRecord is large enough to contain $DATA.
+*
+* @param AttributeAddress
+* Pointer to the region of memory that will receive the $DATA attribute.
+* This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
+*
+* @return
+* STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
+* of the given file record.
+*
+* @remarks
+* Only adding the attribute to the end of the file record is supported; AttributeAddress must
+* be of type AttributeEnd.
+* As it's implemented, this function is only intended to assist in creating new file records. It
+* could be made more general-purpose by considering file records with an $ATTRIBUTE_LIST.
+* It's the caller's responsibility to ensure the given file record has enough memory allocated
+* for the attribute.
+*/
+NTSTATUS
+AddData(PFILE_RECORD_HEADER FileRecord,
+        PNTFS_ATTR_RECORD AttributeAddress)
+{
+    ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR);
+    ULONG FileRecordEnd = AttributeAddress->Length;
+
+    if (AttributeAddress->Type != AttributeEnd)
+    {
+        DPRINT1("FIXME: Can only add $DATA attribute to the end of a file record.\n");
+        return STATUS_NOT_IMPLEMENTED;
+    }
+
+    AttributeAddress->Type = AttributeData;
+    AttributeAddress->Length = ResidentHeaderLength;
+    AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, 8);
+    AttributeAddress->Resident.ValueLength = 0;
+    AttributeAddress->Resident.ValueOffset = ResidentHeaderLength;
+
+    // for unnamed $DATA attributes, NameOffset equals header length
+    AttributeAddress->NameOffset = ResidentHeaderLength;
+    AttributeAddress->Instance = FileRecord->NextAttributeNumber++;
+
+    // move the attribute-end and file-record-end markers to the end of the file record
+    AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length);
+    SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd);
+
+    return STATUS_SUCCESS;
+}
+
+/**
+* @name AddFileName
+* @implemented
+*
+* Adds a $FILE_NAME attribute to a given FileRecord.
+*
+* @param FileRecord
+* Pointer to a complete file record to add the attribute to. Caller is responsible for
+* ensuring FileRecord is large enough to contain $FILE_NAME.
+*
+* @param AttributeAddress
+* Pointer to the region of memory that will receive the $FILE_NAME attribute.
+* This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
+*
+* @param DeviceExt
+* Points to the target disk's DEVICE_EXTENSION.
+*
+* @param FileObject
+* Pointer to the FILE_OBJECT which represents the new name.
+* This parameter is used to determine the filename and parent directory.
+*
+* @param CaseSensitive
+* Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
+* if an application opened the file with the FILE_FLAG_POSIX_SEMANTICS flag.
+*
+* @param ParentMftIndex
+* Pointer to a ULONGLONG which will receive the index of the parent directory.
+*
+* @return
+* STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
+* of the given file record.
+*
+* @remarks
+* Only adding the attribute to the end of the file record is supported; AttributeAddress must
+* be of type AttributeEnd.
+* As it's implemented, this function is only intended to assist in creating new file records. It
+* could be made more general-purpose by considering file records with an $ATTRIBUTE_LIST.
+* It's the caller's responsibility to ensure the given file record has enough memory allocated
+* for the attribute.
+*/
+NTSTATUS
+AddFileName(PFILE_RECORD_HEADER FileRecord,
+            PNTFS_ATTR_RECORD AttributeAddress,
+            PDEVICE_EXTENSION DeviceExt,
+            PFILE_OBJECT FileObject,
+            BOOLEAN CaseSensitive,
+            PULONGLONG ParentMftIndex)
+{
+    ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR);
+    PFILENAME_ATTRIBUTE FileNameAttribute;
+    LARGE_INTEGER SystemTime;
+    ULONG FileRecordEnd = AttributeAddress->Length;
+    ULONGLONG CurrentMFTIndex = NTFS_FILE_ROOT;
+    UNICODE_STRING Current, Remaining, FilenameNoPath;
+    NTSTATUS Status = STATUS_SUCCESS;
+    ULONG FirstEntry = 0;
+    WCHAR Buffer[MAX_PATH];
+
+    if (AttributeAddress->Type != AttributeEnd)
+    {
+        DPRINT1("FIXME: Can only add $FILE_NAME attribute to the end of a file record.\n");
+        return STATUS_NOT_IMPLEMENTED;
+    }
+
+    AttributeAddress->Type = AttributeFileName;
+    AttributeAddress->Instance = FileRecord->NextAttributeNumber++;
+
+    FileNameAttribute = (PFILENAME_ATTRIBUTE)((LONG_PTR)AttributeAddress + ResidentHeaderLength);
+
+    // set timestamps
+    KeQuerySystemTime(&SystemTime);
+    FileNameAttribute->CreationTime = SystemTime.QuadPart;
+    FileNameAttribute->ChangeTime = SystemTime.QuadPart;
+    FileNameAttribute->LastWriteTime = SystemTime.QuadPart;
+    FileNameAttribute->LastAccessTime = SystemTime.QuadPart;
+
+    FileNameAttribute->FileAttributes = NTFS_FILE_TYPE_ARCHIVE;
+
+    // we need to extract the filename from the path
+    DPRINT1("Pathname: %wZ\n", &FileObject->FileName);
+
+    RtlInitEmptyUnicodeString(&FilenameNoPath, Buffer, MAX_PATH);
+
+    FsRtlDissectName(FileObject->FileName, &Current, &Remaining);
+
+    while (Current.Length != 0)
+    {
+        DPRINT1("Current: %wZ\n", &Current);
+
+        if(Remaining.Length != 0)
+            RtlCopyUnicodeString(&FilenameNoPath, &Remaining);
+
+        Status = NtfsFindMftRecord(DeviceExt,
+                                   CurrentMFTIndex,
+                                   &Current,
+                                   &FirstEntry,
+                                   FALSE,
+                                   CaseSensitive,
+                                   &CurrentMFTIndex);
+        if (!NT_SUCCESS(Status))
+            break;
+
+        if (Remaining.Length == 0 )
+        {
+            if(Current.Length != 0)
+                RtlCopyUnicodeString(&FilenameNoPath, &Current);
+            break;
+        }
+
+        FsRtlDissectName(Current, &Current, &Remaining);
+    }
+
+    DPRINT1("MFT Index of parent: %I64u\n", CurrentMFTIndex);
+
+    // set reference to parent directory
+    FileNameAttribute->DirectoryFileReferenceNumber = CurrentMFTIndex;
+    *ParentMftIndex = CurrentMFTIndex;
+
+    DPRINT1("SequenceNumber: 0x%02x\n", FileRecord->SequenceNumber);
+
+    // The highest 2 bytes should be the sequence number, unless the parent happens to be root
+    if (CurrentMFTIndex == NTFS_FILE_ROOT)
+        FileNameAttribute->DirectoryFileReferenceNumber |= (ULONGLONG)NTFS_FILE_ROOT << 48;
+    else
+        FileNameAttribute->DirectoryFileReferenceNumber |= (ULONGLONG)FileRecord->SequenceNumber << 48;
+
+    DPRINT1("FileNameAttribute->DirectoryFileReferenceNumber: 0x%016I64x\n", FileNameAttribute->DirectoryFileReferenceNumber);
+
+    FileNameAttribute->NameLength = FilenameNoPath.Length / sizeof(WCHAR);
+    RtlCopyMemory(FileNameAttribute->Name, FilenameNoPath.Buffer, FilenameNoPath.Length);
+
+    // For now, we're emulating the way Windows behaves when 8.3 name generation is disabled
+    // TODO: add DOS Filename as needed
+    if (RtlIsNameLegalDOS8Dot3(&FilenameNoPath, NULL, NULL))
+        FileNameAttribute->NameType = NTFS_FILE_NAME_WIN32_AND_DOS;
+    else
+        FileNameAttribute->NameType = NTFS_FILE_NAME_POSIX;
+    
+    FileRecord->LinkCount++;
+
+    AttributeAddress->Length = ResidentHeaderLength +
+        FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + FilenameNoPath.Length;
+    AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, 8);
+
+    AttributeAddress->Resident.ValueLength = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + FilenameNoPath.Length;
+    AttributeAddress->Resident.ValueOffset = ResidentHeaderLength;
+    AttributeAddress->Resident.Flags = 1; // indexed
+
+    // move the attribute-end and file-record-end markers to the end of the file record
+    AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length);
+    SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd);
+
+    return Status;
+}
+
 /**
 * @name AddRun
 * @implemented
 *
 * @return
 * STATUS_SUCCESS on success. STATUS_INVALID_PARAMETER if AttrContext describes a resident attribute.
-* STATUS_INSUFFICIENT_RESOURCES if ConvertDataRunsToLargeMCB() fails.
+* STATUS_INSUFFICIENT_RESOURCES if ConvertDataRunsToLargeMCB() fails or if we fail to allocate a 
+* buffer for the new data runs.
+* STATUS_INSUFFICIENT_RESOURCES or STATUS_UNSUCCESSFUL if FsRtlAddLargeMcbEntry() fails.
 * STATUS_BUFFER_TOO_SMALL if ConvertLargeMCBToDataRuns() fails.
 * STATUS_NOT_IMPLEMENTED if we need to migrate the attribute to an attribute list (TODO).
 *
@@ -80,113 +293,158 @@ AddRun(PNTFS_VCB Vcb,
        ULONG RunLength)
 {
     NTSTATUS Status;
-    PUCHAR DataRun = (PUCHAR)&AttrContext->Record + AttrContext->Record.NonResident.MappingPairsOffset;
     int DataRunMaxLength;
     PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
-    LARGE_MCB DataRunsMCB;
     ULONG NextAttributeOffset = AttrOffset + AttrContext->Record.Length;
-    ULONGLONG NextVBN = AttrContext->Record.NonResident.LowestVCN;
+    ULONGLONG NextVBN = 0;
 
-    // Allocate some memory for the RunBuffer
     PUCHAR RunBuffer;
-    ULONG RunBufferOffset = 0;
+    ULONG RunBufferSize;
 
     if (!AttrContext->Record.IsNonResident)
         return STATUS_INVALID_PARAMETER;
 
-    RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
-
-    // Convert the data runs to a map control block
-    Status = ConvertDataRunsToLargeMCB(DataRun, &DataRunsMCB, &NextVBN);
-    if (!NT_SUCCESS(Status))
-    {
-        DPRINT1("Unable to convert data runs to MCB (probably ran out of memory)!\n");
-        ExFreePoolWithTag(RunBuffer, TAG_NTFS);
-        return Status;
-    }
+    if (AttrContext->Record.NonResident.AllocatedSize != 0)
+        NextVBN = AttrContext->Record.NonResident.HighestVCN + 1;
 
     // Add newly-assigned clusters to mcb
     _SEH2_TRY{
-        if (!FsRtlAddLargeMcbEntry(&DataRunsMCB,
+        if (!FsRtlAddLargeMcbEntry(&AttrContext->DataRunsMCB,
                                    NextVBN,
                                    NextAssignedCluster,
                                    RunLength))
         {
-            FsRtlUninitializeLargeMcb(&DataRunsMCB);
-            ExFreePoolWithTag(RunBuffer, TAG_NTFS);
-            return STATUS_INSUFFICIENT_RESOURCES;
+            ExRaiseStatus(STATUS_UNSUCCESSFUL);
         }
     } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
-        FsRtlUninitializeLargeMcb(&DataRunsMCB);
-        ExFreePoolWithTag(RunBuffer, TAG_NTFS);
-        _SEH2_YIELD(return STATUS_INSUFFICIENT_RESOURCES);
+        _SEH2_YIELD(_SEH2_GetExceptionCode());
     } _SEH2_END;
 
+    RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
+    if (!RunBuffer)
+    {
+        DPRINT1("ERROR: Couldn't allocate memory for data runs!\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
 
     // Convert the map control block back to encoded data runs
-    ConvertLargeMCBToDataRuns(&DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferOffset);
+    ConvertLargeMCBToDataRuns(&AttrContext->DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferSize);
 
     // Get the amount of free space between the start of the of the first data run and the attribute end
     DataRunMaxLength = AttrContext->Record.Length - AttrContext->Record.NonResident.MappingPairsOffset;
 
     // Do we need to extend the attribute (or convert to attribute list)?
-    if (DataRunMaxLength < RunBufferOffset)
+    if (DataRunMaxLength < RunBufferSize)
     {
         PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset);
         DataRunMaxLength += Vcb->NtfsInfo.BytesPerFileRecord - NextAttributeOffset - (sizeof(ULONG) * 2);
 
         // Can we move the end of the attribute?
-        if (NextAttribute->Type != AttributeEnd || DataRunMaxLength < RunBufferOffset - 1)
+        if (NextAttribute->Type != AttributeEnd || DataRunMaxLength < RunBufferSize - 1)
         {
             DPRINT1("FIXME: Need to create attribute list! Max Data Run Length available: %d\n", DataRunMaxLength);
             if (NextAttribute->Type != AttributeEnd)
                 DPRINT1("There's another attribute after this one with type %0xlx\n", NextAttribute->Type);
             ExFreePoolWithTag(RunBuffer, TAG_NTFS);
-            FsRtlUninitializeLargeMcb(&DataRunsMCB);
             return STATUS_NOT_IMPLEMENTED;
         }
 
         // calculate position of end markers
-        NextAttributeOffset = AttrOffset + AttrContext->Record.NonResident.MappingPairsOffset + RunBufferOffset;
+        NextAttributeOffset = AttrOffset + AttrContext->Record.NonResident.MappingPairsOffset + RunBufferSize;
         NextAttributeOffset = ALIGN_UP_BY(NextAttributeOffset, 8);
 
-        // Write the end markers
-        NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset);
-        NextAttribute->Type = AttributeEnd;
-        NextAttribute->Length = FILE_RECORD_END;
-
         // Update the length
         DestinationAttribute->Length = NextAttributeOffset - AttrOffset;
         AttrContext->Record.Length = DestinationAttribute->Length;
 
-        // We need to increase the FileRecord size
-        FileRecord->BytesInUse = NextAttributeOffset + (sizeof(ULONG) * 2);
+        // End the file record
+        NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset);
+        SetFileRecordEnd(FileRecord, NextAttribute, FILE_RECORD_END);
     }
 
-    // NOTE: from this point on the original attribute record will contain invalid data in it's runbuffer
-    // TODO: Elegant fix? Could we free the old Record and allocate a new one without issue?
-
     // Update HighestVCN
     DestinationAttribute->NonResident.HighestVCN =
-    AttrContext->Record.NonResident.HighestVCN = max(NextVBN - 1 + RunLength, 
+    AttrContext->Record.NonResident.HighestVCN = max(NextVBN - 1 + RunLength,
                                                      AttrContext->Record.NonResident.HighestVCN);
 
     // Write data runs to destination attribute
     RtlCopyMemory((PVOID)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), 
                   RunBuffer, 
-                  RunBufferOffset);
+                  RunBufferSize);
 
     // Update the file record
     Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord);
 
     ExFreePoolWithTag(RunBuffer, TAG_NTFS);
-    FsRtlUninitializeLargeMcb(&DataRunsMCB);
 
     NtfsDumpDataRuns((PUCHAR)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), 0);
 
     return Status;
 }
 
+/**
+* @name AddStandardInformation
+* @implemented
+*
+* Adds a $STANDARD_INFORMATION attribute to a given FileRecord.
+*
+* @param FileRecord
+* Pointer to a complete file record to add the attribute to. Caller is responsible for
+* ensuring FileRecord is large enough to contain $STANDARD_INFORMATION.
+*
+* @param AttributeAddress
+* Pointer to the region of memory that will receive the $STANDARD_INFORMATION attribute.
+* This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
+*
+* @return
+* STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
+* of the given file record.
+*
+* @remarks
+* Only adding the attribute to the end of the file record is supported; AttributeAddress must
+* be of type AttributeEnd.
+* As it's implemented, this function is only intended to assist in creating new file records. It
+* could be made more general-purpose by considering file records with an $ATTRIBUTE_LIST.
+* It's the caller's responsibility to ensure the given file record has enough memory allocated
+* for the attribute.
+*/
+NTSTATUS
+AddStandardInformation(PFILE_RECORD_HEADER FileRecord,
+                       PNTFS_ATTR_RECORD AttributeAddress)
+{
+    ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR);
+    PSTANDARD_INFORMATION StandardInfo = (PSTANDARD_INFORMATION)((LONG_PTR)AttributeAddress + ResidentHeaderLength);
+    LARGE_INTEGER SystemTime;
+    ULONG FileRecordEnd = AttributeAddress->Length;
+
+    if (AttributeAddress->Type != AttributeEnd)
+    {
+        DPRINT1("FIXME: Can only add $STANDARD_INFORMATION attribute to the end of a file record.\n");
+        return STATUS_NOT_IMPLEMENTED;
+    }
+
+    AttributeAddress->Type = AttributeStandardInformation;
+    AttributeAddress->Length = sizeof(STANDARD_INFORMATION) + ResidentHeaderLength;
+    AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, 8);
+    AttributeAddress->Resident.ValueLength = sizeof(STANDARD_INFORMATION);
+    AttributeAddress->Resident.ValueOffset = ResidentHeaderLength;
+    AttributeAddress->Instance = FileRecord->NextAttributeNumber++;
+
+    // set dates and times
+    KeQuerySystemTime(&SystemTime);
+    StandardInfo->CreationTime = SystemTime.QuadPart;
+    StandardInfo->ChangeTime = SystemTime.QuadPart;
+    StandardInfo->LastWriteTime = SystemTime.QuadPart;
+    StandardInfo->LastAccessTime = SystemTime.QuadPart;
+    StandardInfo->FileAttribute = NTFS_FILE_TYPE_ARCHIVE;
+
+    // move the attribute-end and file-record-end markers to the end of the file record
+    AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length);
+    SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd);
+
+    return STATUS_SUCCESS;
+}
+
 /**
 * @name ConvertDataRunsToLargeMCB
 * @implemented
@@ -200,7 +458,7 @@ AddRun(PNTFS_VCB Vcb,
 * Pointer to an unitialized LARGE_MCB structure.
 *
 * @return
-* STATUS_SUCCESS on success, STATUS_INSUFFICIENT_RESOURCES if we fail to
+* STATUS_SUCCESS on success, STATUS_INSUFFICIENT_RESOURCES or STATUS_UNSUCCESSFUL if we fail to
 * initialize the mcb or add an entry.
 *
 * @remarks
@@ -223,7 +481,7 @@ ConvertDataRunsToLargeMCB(PUCHAR DataRun,
     _SEH2_TRY{
         FsRtlInitializeLargeMcb(DataRunsMCB, NonPagedPool);
     } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
-        _SEH2_YIELD(return STATUS_INSUFFICIENT_RESOURCES);
+        _SEH2_YIELD(return _SEH2_GetExceptionCode());
     } _SEH2_END;
 
     while (*DataRun != 0)
@@ -242,12 +500,11 @@ ConvertDataRunsToLargeMCB(PUCHAR DataRun,
                                            DataRunStartLCN,
                                            DataRunLength))
                 {
-                    FsRtlUninitializeLargeMcb(DataRunsMCB);
-                    return STATUS_INSUFFICIENT_RESOURCES;
+                    ExRaiseStatus(STATUS_UNSUCCESSFUL);
                 }
             } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
                 FsRtlUninitializeLargeMcb(DataRunsMCB);
-                _SEH2_YIELD(return STATUS_INSUFFICIENT_RESOURCES);
+                _SEH2_YIELD(return _SEH2_GetExceptionCode());
             } _SEH2_END;
 
         }
@@ -292,7 +549,7 @@ ConvertLargeMCBToDataRuns(PLARGE_MCB DataRunsMCB,
     LONGLONG  DataRunOffset;
     ULONGLONG LastLCN = 0;
     LONGLONG Vbn, Lbn, Count;
-    int i;
+    ULONG i;
 
 
     DPRINT("\t[Vbn, Lbn, Count]\n");
@@ -321,7 +578,7 @@ ConvertLargeMCBToDataRuns(PLARGE_MCB DataRunsMCB,
         DataRunLengthSize = GetPackedByteCount(Count, TRUE);
         DPRINT("%d bytes needed.\n", DataRunLengthSize);
 
-        // ensure the next data run + end marker would be > Max buffer size
+        // ensure the next data run + end marker would be <= Max buffer size
         if (RunBufferOffset + 2 + DataRunLengthSize + DataRunOffsetSize > MaxBufferSize)
         {
             Status = STATUS_BUFFER_TOO_SMALL;
@@ -436,7 +693,8 @@ FindRun(PNTFS_ATTR_RECORD NresAttr,
 * @return
 * STATUS_SUCCESS on success. STATUS_INVALID_PARAMETER if AttrContext describes a resident attribute,
 * or if the caller requested more clusters be freed than the attribute has been allocated.
-* STATUS_INSUFFICIENT_RESOURCES if ConvertDataRunsToLargeMCB() fails.
+* STATUS_INSUFFICIENT_RESOURCES if allocating a buffer for the data runs fails or
+* if ConvertDataRunsToLargeMCB() fails.
 * STATUS_BUFFER_TOO_SMALL if ConvertLargeMCBToDataRuns() fails.
 *
 *
@@ -451,17 +709,12 @@ FreeClusters(PNTFS_VCB Vcb,
     NTSTATUS Status = STATUS_SUCCESS;
     ULONG ClustersLeftToFree = ClustersToFree;
 
-    // convert data runs to mcb
-    PUCHAR DataRun = (PUCHAR)&AttrContext->Record + AttrContext->Record.NonResident.MappingPairsOffset;
     PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
-    LARGE_MCB DataRunsMCB;
     ULONG NextAttributeOffset = AttrOffset + AttrContext->Record.Length;
     PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset);
-    ULONGLONG NextVBN = AttrContext->Record.NonResident.LowestVCN;
 
-    // Allocate some memory for the RunBuffer
-    PUCHAR RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
-    ULONG RunBufferOffset = 0;
+    PUCHAR RunBuffer;
+    ULONG RunBufferSize = 0;
 
     PFILE_RECORD_HEADER BitmapRecord;
     PNTFS_ATTR_CONTEXT DataContext;
@@ -472,27 +725,16 @@ FreeClusters(PNTFS_VCB Vcb,
 
     if (!AttrContext->Record.IsNonResident)
     {
-        ExFreePoolWithTag(RunBuffer, TAG_NTFS);
         return STATUS_INVALID_PARAMETER;
     }
 
-    // Convert the data runs to a map control block
-    Status = ConvertDataRunsToLargeMCB(DataRun, &DataRunsMCB, &NextVBN);
-    if (!NT_SUCCESS(Status))
-    {
-        DPRINT1("Unable to convert data runs to MCB (probably ran out of memory)!\n");
-        ExFreePoolWithTag(RunBuffer, TAG_NTFS);
-        return Status;
-    }
-
+    // Read the $Bitmap file
     BitmapRecord = ExAllocatePoolWithTag(NonPagedPool,
                                          Vcb->NtfsInfo.BytesPerFileRecord,
                                          TAG_NTFS);
     if (BitmapRecord == NULL)
     {
         DPRINT1("Error: Unable to allocate memory for bitmap file record!\n");
-        FsRtlUninitializeLargeMcb(&DataRunsMCB);
-        ExFreePoolWithTag(RunBuffer, TAG_NTFS);
         return STATUS_NO_MEMORY;
     }
 
@@ -500,9 +742,7 @@ FreeClusters(PNTFS_VCB Vcb,
     if (!NT_SUCCESS(Status))
     {
         DPRINT1("Error: Unable to read file record for bitmap!\n");
-        FsRtlUninitializeLargeMcb(&DataRunsMCB);
         ExFreePoolWithTag(BitmapRecord, TAG_NTFS);
-        ExFreePoolWithTag(RunBuffer, TAG_NTFS);
         return 0;
     }
 
@@ -510,9 +750,7 @@ FreeClusters(PNTFS_VCB Vcb,
     if (!NT_SUCCESS(Status))
     {
         DPRINT1("Error: Unable to find data attribute for bitmap file!\n");
-        FsRtlUninitializeLargeMcb(&DataRunsMCB);
         ExFreePoolWithTag(BitmapRecord, TAG_NTFS);
-        ExFreePoolWithTag(RunBuffer, TAG_NTFS);
         return 0;
     }
 
@@ -524,9 +762,7 @@ FreeClusters(PNTFS_VCB Vcb,
     {
         DPRINT1("Error: Unable to allocate memory for bitmap file data!\n");
         ReleaseAttributeContext(DataContext);
-        FsRtlUninitializeLargeMcb(&DataRunsMCB);
         ExFreePoolWithTag(BitmapRecord, TAG_NTFS);
-        ExFreePoolWithTag(RunBuffer, TAG_NTFS);
         return 0;
     }
 
@@ -539,7 +775,7 @@ FreeClusters(PNTFS_VCB Vcb,
     {
         LONGLONG LargeVbn, LargeLbn;
 
-        if (!FsRtlLookupLastLargeMcbEntry(&DataRunsMCB, &LargeVbn, &LargeLbn))
+        if (!FsRtlLookupLastLargeMcbEntry(&AttrContext->DataRunsMCB, &LargeVbn, &LargeLbn))
         {
             Status = STATUS_INVALID_PARAMETER;
             DPRINT1("DRIVER ERROR: FreeClusters called to free %lu clusters, which is %lu more clusters than are assigned to attribute!",
@@ -553,7 +789,9 @@ FreeClusters(PNTFS_VCB Vcb,
             // deallocate this cluster
             RtlClearBits(&Bitmap, LargeLbn, 1);
         }
-        FsRtlTruncateLargeMcb(&DataRunsMCB, AttrContext->Record.NonResident.HighestVCN);
+        FsRtlTruncateLargeMcb(&AttrContext->DataRunsMCB, AttrContext->Record.NonResident.HighestVCN);
+
+        // decrement HighestVCN, but don't let it go below 0
         AttrContext->Record.NonResident.HighestVCN = min(AttrContext->Record.NonResident.HighestVCN, AttrContext->Record.NonResident.HighestVCN - 1);
         ClustersLeftToFree--;
     }
@@ -563,19 +801,27 @@ FreeClusters(PNTFS_VCB Vcb,
     if (!NT_SUCCESS(Status))
     {
         ReleaseAttributeContext(DataContext);
-        FsRtlUninitializeLargeMcb(&DataRunsMCB);
         ExFreePoolWithTag(BitmapData, TAG_NTFS);
         ExFreePoolWithTag(BitmapRecord, TAG_NTFS);
-        ExFreePoolWithTag(RunBuffer, TAG_NTFS);
         return Status;
     }
 
     ReleaseAttributeContext(DataContext);
     ExFreePoolWithTag(BitmapData, TAG_NTFS);
     ExFreePoolWithTag(BitmapRecord, TAG_NTFS);    
+    
+    // Save updated data runs to file record
+
+    // Allocate some memory for a new RunBuffer
+    RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
+    if (!RunBuffer)
+    {
+        DPRINT1("ERROR: Couldn't allocate memory for data runs!\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
 
     // Convert the map control block back to encoded data runs
-    ConvertLargeMCBToDataRuns(&DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferOffset);
+    ConvertLargeMCBToDataRuns(&AttrContext->DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferSize);
 
     // Update HighestVCN
     DestinationAttribute->NonResident.HighestVCN = AttrContext->Record.NonResident.HighestVCN;
@@ -583,27 +829,23 @@ FreeClusters(PNTFS_VCB Vcb,
     // Write data runs to destination attribute
     RtlCopyMemory((PVOID)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset),
                   RunBuffer,
-                  RunBufferOffset);
+                  RunBufferSize);
 
+    // Is DestinationAttribute the last attribute in the file record?
     if (NextAttribute->Type == AttributeEnd)
     {
         // update attribute length
-        AttrContext->Record.Length = ALIGN_UP_BY(AttrContext->Record.NonResident.MappingPairsOffset + RunBufferOffset, 8);
+        AttrContext->Record.Length = ALIGN_UP_BY(AttrContext->Record.NonResident.MappingPairsOffset + RunBufferSize, 8);
         DestinationAttribute->Length = AttrContext->Record.Length;
 
         // write end markers
         NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)DestinationAttribute + DestinationAttribute->Length);
-        NextAttribute->Type = AttributeEnd;
-        NextAttribute->Length = FILE_RECORD_END;
-
-        // update file record length
-        FileRecord->BytesInUse = AttrOffset + DestinationAttribute->Length + (sizeof(ULONG) * 2);
+        SetFileRecordEnd(FileRecord, NextAttribute, FILE_RECORD_END);
     }
 
     // Update the file record
     Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord);
 
-    FsRtlUninitializeLargeMcb(&DataRunsMCB);
     ExFreePoolWithTag(RunBuffer, TAG_NTFS);
 
     NtfsDumpDataRuns((PUCHAR)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), 0);
@@ -844,6 +1086,7 @@ NtfsDumpFileNameAttribute(PNTFS_ATTR_RECORD Attribute)
     DbgPrint(" (%x) '%.*S' ", FileNameAttr->NameType, FileNameAttr->NameLength, FileNameAttr->Name);
     DbgPrint(" '%x' \n", FileNameAttr->FileAttributes);
     DbgPrint(" AllocatedSize: %I64u\nDataSize: %I64u\n", FileNameAttr->AllocatedSize, FileNameAttr->DataSize);
+    DbgPrint(" File reference: 0x%016I64x\n", FileNameAttr->DirectoryFileReferenceNumber);
 }
 
 
@@ -900,23 +1143,73 @@ VOID
 NtfsDumpIndexRootAttribute(PNTFS_ATTR_RECORD Attribute)
 {
     PINDEX_ROOT_ATTRIBUTE IndexRootAttr;
+    ULONG currentOffset;
+    ULONG currentNode;
 
     IndexRootAttr = (PINDEX_ROOT_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
 
     if (IndexRootAttr->AttributeType == AttributeFileName)
         ASSERT(IndexRootAttr->CollationRule == COLLATION_FILE_NAME);
 
-    DbgPrint("  $INDEX_ROOT (%uB, %u) ", IndexRootAttr->SizeOfEntry, IndexRootAttr->ClustersPerIndexRecord);
+    DbgPrint("  $INDEX_ROOT (%u bytes per index record, %u clusters) ", IndexRootAttr->SizeOfEntry, IndexRootAttr->ClustersPerIndexRecord);
 
     if (IndexRootAttr->Header.Flags == INDEX_ROOT_SMALL)
     {
-        DbgPrint(" (small) ");
+        DbgPrint(" (small)\n");
     }
     else
     {
         ASSERT(IndexRootAttr->Header.Flags == INDEX_ROOT_LARGE);
-        DbgPrint(" (large) ");
+        DbgPrint(" (large)\n");
+    }
+
+    DbgPrint("   Offset to first index: 0x%lx\n   Total size of index entries: 0x%lx\n   Allocated size of node: 0x%lx\n",
+             IndexRootAttr->Header.FirstEntryOffset,
+             IndexRootAttr->Header.TotalSizeOfEntries,
+             IndexRootAttr->Header.AllocatedSize);
+    currentOffset = IndexRootAttr->Header.FirstEntryOffset;
+    currentNode = 0;
+    // print details of every node in the index
+    while (currentOffset < IndexRootAttr->Header.TotalSizeOfEntries)
+    {
+        PINDEX_ENTRY_ATTRIBUTE currentIndexExtry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexRootAttr + 0x10 + currentOffset);
+        DbgPrint("   Index Node Entry %lu", currentNode++);
+        if (currentIndexExtry->Flags & NTFS_INDEX_ENTRY_NODE)
+            DbgPrint(" (Branch)");
+        else
+            DbgPrint(" (Leaf)");
+        if((currentIndexExtry->Flags & NTFS_INDEX_ENTRY_END))
+        {
+            DbgPrint(" (Dummy Key)");
+        }
+        DbgPrint("\n    File Reference: 0x%016I64x\n", currentIndexExtry->Data.Directory.IndexedFile);
+        DbgPrint("    Index Entry Length: 0x%x\n", currentIndexExtry->Length);
+        DbgPrint("    Index Key Length: 0x%x\n", currentIndexExtry->KeyLength);
+
+        // if this isn't the final (dummy) node, print info about the key (Filename attribute)
+        if (!(currentIndexExtry->Flags & NTFS_INDEX_ENTRY_END))
+        {
+            UNICODE_STRING Name;
+            DbgPrint("     Parent File Reference: 0x%016I64x\n", currentIndexExtry->FileName.DirectoryFileReferenceNumber);
+            DbgPrint("     $FILENAME indexed: ");
+            Name.Length = currentIndexExtry->FileName.NameLength * sizeof(WCHAR);
+            Name.MaximumLength = Name.Length;
+            Name.Buffer = currentIndexExtry->FileName.Name;
+            DbgPrint("'%wZ'\n", &Name);
+        }
+
+        // if this node has a sub-node beneath it
+        if (currentIndexExtry->Flags & NTFS_INDEX_ENTRY_NODE)
+        {
+            // Print the VCN of the sub-node
+            PULONGLONG SubNodeVCN = (PULONGLONG)((ULONG_PTR)currentIndexExtry + currentIndexExtry->Length - 8);
+            DbgPrint("    VCN of sub-node: 0x%llx\n", *SubNodeVCN);
+        }
+        
+        currentOffset += currentIndexExtry->Length;
+        ASSERT(currentIndexExtry->Length);
     }
+
 }