[NTFS] Don't leak memory when reading or writing non-resident attributes
[reactos.git] / drivers / filesystems / ntfs / mft.c
index 1df31ae..cf4f084 100644 (file)
@@ -30,6 +30,7 @@
 /* INCLUDES *****************************************************************/
 
 #include "ntfs.h"
+#include <ntintsafe.h>
 
 #define NDEBUG
 #include <debug.h>
@@ -42,15 +43,32 @@ PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord)
     PNTFS_ATTR_CONTEXT Context;
 
     Context = ExAllocatePoolWithTag(NonPagedPool,
-                                    FIELD_OFFSET(NTFS_ATTR_CONTEXT, Record) + AttrRecord->Length,
+                                    sizeof(NTFS_ATTR_CONTEXT),
                                     TAG_NTFS);
-    RtlCopyMemory(&Context->Record, AttrRecord, AttrRecord->Length);
+    if(!Context)
+    {
+        DPRINT1("Error: Unable to allocate memory for context!\n");
+        return NULL;
+    }
+
+    // Allocate memory for a copy of the attribute
+    Context->pRecord = ExAllocatePoolWithTag(NonPagedPool, AttrRecord->Length, TAG_NTFS);
+    if(!Context->pRecord)
+    {
+        DPRINT1("Error: Unable to allocate memory for attribute record!\n");
+        ExFreePoolWithTag(Context, TAG_NTFS);
+        return NULL;
+    }
+
+    // Copy the attribute
+    RtlCopyMemory(Context->pRecord, AttrRecord, AttrRecord->Length);
+
     if (AttrRecord->IsNonResident)
     {
         LONGLONG DataRunOffset;
         ULONGLONG DataRunLength;
         ULONGLONG NextVBN = 0;
-        PUCHAR DataRun = (PUCHAR)&Context->Record + Context->Record.NonResident.MappingPairsOffset;
+        PUCHAR DataRun = (PUCHAR)((ULONG_PTR)Context->pRecord + Context->pRecord->NonResident.MappingPairsOffset);
 
         Context->CacheRun = DataRun;
         Context->CacheRunOffset = 0;
@@ -74,6 +92,7 @@ PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord)
         if (!NT_SUCCESS(ConvertDataRunsToLargeMCB(DataRun, &Context->DataRunsMCB, &NextVBN)))
         {
             DPRINT1("Unable to convert data runs to MCB!\n");
+            ExFreePoolWithTag(Context->pRecord, TAG_NTFS);
             ExFreePoolWithTag(Context, TAG_NTFS);
             return NULL;
         }
@@ -86,11 +105,14 @@ PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord)
 VOID
 ReleaseAttributeContext(PNTFS_ATTR_CONTEXT Context)
 {
-    if (Context->Record.IsNonResident)
+    if (Context->pRecord->IsNonResident)
     {
         FsRtlUninitializeLargeMcb(&Context->DataRunsMCB);
     }
 
+    if(Context->pRecord)
+        ExFreePoolWithTag(Context->pRecord, TAG_NTFS);
+
     ExFreePoolWithTag(Context, TAG_NTFS);
 }
 
@@ -173,7 +195,7 @@ AttributeAllocatedLength(PNTFS_ATTR_RECORD AttrRecord)
     if (AttrRecord->IsNonResident)
         return AttrRecord->NonResident.AllocatedSize;
     else
-        return AttrRecord->Resident.ValueLength;
+        return ALIGN_UP_BY(AttrRecord->Resident.ValueLength, ATTR_RECORD_ALIGNMENT);
 }
 
 
@@ -207,7 +229,7 @@ AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord)
 * STATUS_CANT_WAIT if CanWait was FALSE and the function could not get immediate, exclusive access to the MFT.
 *
 * @remarks
-* Increases the size of the Master File Table by 8 records. Bitmap entries for the new records are cleared,
+* Increases the size of the Master File Table by 64 records. Bitmap entries for the new records are cleared,
 * and the bitmap is also enlarged if needed. Mimicking Windows' behavior when enlarging the mft is still TODO.
 * This function will wait for exlusive access to the volume fcb.
 */
@@ -218,13 +240,17 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
     LARGE_INTEGER BitmapSize;
     LARGE_INTEGER DataSize;
     LONGLONG BitmapSizeDifference;
-    ULONG DataSizeDifference = Vcb->NtfsInfo.BytesPerFileRecord * 8;
+    ULONG NewRecords = ATTR_RECORD_ALIGNMENT * 8;  // Allocate one new record for every bit of every byte we'll be adding to the bitmap
+    ULONG DataSizeDifference = Vcb->NtfsInfo.BytesPerFileRecord * NewRecords;
     ULONG BitmapOffset;
     PUCHAR BitmapBuffer;
     ULONGLONG BitmapBytes;
     ULONGLONG NewBitmapSize;
+    ULONGLONG FirstNewMftIndex;
     ULONG BytesRead;
     ULONG LengthWritten;
+    PFILE_RECORD_HEADER BlankFileRecord;
+    ULONG i;
     NTSTATUS Status;
 
     DPRINT1("IncreaseMftSize(%p, %s)\n", Vcb, CanWait ? "TRUE" : "FALSE");
@@ -235,24 +261,44 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
         return STATUS_CANT_WAIT;
     }
 
+    // Create a blank file record that will be used later
+    BlankFileRecord = NtfsCreateEmptyFileRecord(Vcb);
+    if (!BlankFileRecord)
+    {
+        DPRINT1("Error: Unable to create empty file record!\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+
+    // Clear the flags (file record is not in use)
+    BlankFileRecord->Flags = 0;
+
     // Find the bitmap attribute of master file table
-    Status = FindAttribute(Vcb, Vcb->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, &BitmapOffset);
+    Status = FindAttribute(Vcb, Vcb->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL);
     if (!NT_SUCCESS(Status))
     {
         DPRINT1("ERROR: Couldn't find $BITMAP attribute of Mft!\n");
+        ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
         ExReleaseResourceLite(&(Vcb->DirResource));
         return Status;
     }
 
     // Get size of Bitmap Attribute
-    BitmapSize.QuadPart = AttributeDataLength(&BitmapContext->Record);
+    BitmapSize.QuadPart = AttributeDataLength(BitmapContext->pRecord);
 
     // Calculate the new mft size
-    DataSize.QuadPart = AttributeDataLength(&(Vcb->MFTContext->Record)) + DataSizeDifference;
+    DataSize.QuadPart = AttributeDataLength(Vcb->MFTContext->pRecord) + DataSizeDifference;
+
+    // Find the index of the first Mft entry that will be created
+    FirstNewMftIndex = AttributeDataLength(Vcb->MFTContext->pRecord) / Vcb->NtfsInfo.BytesPerFileRecord;
 
     // Determine how many bytes will make up the bitmap
     BitmapBytes = DataSize.QuadPart / Vcb->NtfsInfo.BytesPerFileRecord / 8;
-    
+    if ((DataSize.QuadPart / Vcb->NtfsInfo.BytesPerFileRecord) % 8 != 0)
+        BitmapBytes++;
+
+    // Windows will always keep the number of bytes in a bitmap as a multiple of 8, so no bytes are wasted on slack
+    BitmapBytes = ALIGN_UP_BY(BitmapBytes, ATTR_RECORD_ALIGNMENT);
+
     // Determine how much we need to adjust the bitmap size (it's possible we don't)
     BitmapSizeDifference = BitmapBytes - BitmapSize.QuadPart;
     NewBitmapSize = max(BitmapSize.QuadPart + BitmapSizeDifference, BitmapSize.QuadPart);
@@ -262,6 +308,7 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
     if (!BitmapBuffer)
     {
         DPRINT1("ERROR: Unable to allocate memory for bitmap attribute!\n");
+        ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
         ExReleaseResourceLite(&(Vcb->DirResource));
         ReleaseAttributeContext(BitmapContext);
         return STATUS_INSUFFICIENT_RESOURCES;
@@ -279,6 +326,7 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
     if (BytesRead != BitmapSize.LowPart)
     {
         DPRINT1("ERROR: Bytes read != Bitmap size!\n");
+        ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
         ExReleaseResourceLite(&(Vcb->DirResource));
         ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
         ReleaseAttributeContext(BitmapContext);
@@ -290,18 +338,30 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
     if (!NT_SUCCESS(Status))
     {
         DPRINT1("ERROR: Failed to set size of $MFT data attribute!\n");
+        ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
         ExReleaseResourceLite(&(Vcb->DirResource));
         ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
         ReleaseAttributeContext(BitmapContext);
         return Status;
     }
+    
+    // We'll need to find the bitmap again, because its offset will have changed after resizing the data attribute
+    ReleaseAttributeContext(BitmapContext);
+    Status = FindAttribute(Vcb, Vcb->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, &BitmapOffset);
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT1("ERROR: Couldn't find $BITMAP attribute of Mft!\n");
+        ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
+        ExReleaseResourceLite(&(Vcb->DirResource));
+        return Status;
+    }
 
     // If the bitmap grew
     if (BitmapSizeDifference > 0)
     {
         // Set the new bitmap size
-        BitmapSize.QuadPart += BitmapSizeDifference;
-        if (BitmapContext->Record.IsNonResident)
+        BitmapSize.QuadPart = NewBitmapSize;
+        if (BitmapContext->pRecord->IsNonResident)
             Status = SetNonResidentAttributeDataLength(Vcb, BitmapContext, BitmapOffset, Vcb->MasterFileTable, &BitmapSize);
         else
             Status = SetResidentAttributeDataLength(Vcb, BitmapContext, BitmapOffset, Vcb->MasterFileTable, &BitmapSize);
@@ -309,6 +369,7 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
         if (!NT_SUCCESS(Status))
         {
             DPRINT1("ERROR: Failed to set size of bitmap attribute!\n");
+            ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
             ExReleaseResourceLite(&(Vcb->DirResource));
             ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
             ReleaseAttributeContext(BitmapContext);
@@ -316,13 +377,14 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
         }
     }
 
-    //NtfsDumpFileAttributes(Vcb, FileRecord);
+    NtfsDumpFileAttributes(Vcb, Vcb->MasterFileTable);
 
     // Update the file record with the new attribute sizes
     Status = UpdateFileRecord(Vcb, Vcb->VolumeFcb->MFTIndex, Vcb->MasterFileTable);
     if (!NT_SUCCESS(Status))
     {
         DPRINT1("ERROR: Failed to update $MFT file record!\n");
+        ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
         ExReleaseResourceLite(&(Vcb->DirResource));
         ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
         ReleaseAttributeContext(BitmapContext);
@@ -330,9 +392,10 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
     }
 
     // Write out the new bitmap
-    Status = WriteAttribute(Vcb, BitmapContext, BitmapOffset, BitmapBuffer, BitmapSize.LowPart, &LengthWritten);
+    Status = WriteAttribute(Vcb, BitmapContext, 0, BitmapBuffer, NewBitmapSize, &LengthWritten, Vcb->MasterFileTable);
     if (!NT_SUCCESS(Status))
     {
+        ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
         ExReleaseResourceLite(&(Vcb->DirResource));
         ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
         ReleaseAttributeContext(BitmapContext);
@@ -340,48 +403,151 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
         return Status;
     }
 
+    // Create blank records for the new file record entries.
+    for (i = 0; i < NewRecords; i++)
+    {
+        Status = UpdateFileRecord(Vcb, FirstNewMftIndex + i, BlankFileRecord);
+        if (!NT_SUCCESS(Status))
+        {
+            DPRINT1("ERROR: Failed to write blank file record!\n");
+            ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
+            ExReleaseResourceLite(&(Vcb->DirResource));
+            ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
+            ReleaseAttributeContext(BitmapContext);
+            return Status;
+        }
+    }
+
+    // Update the mft mirror
+    Status = UpdateMftMirror(Vcb);
+
     // Cleanup
+    ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
     ExReleaseResourceLite(&(Vcb->DirResource));
     ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
     ReleaseAttributeContext(BitmapContext);
 
-    return STATUS_SUCCESS;
+    return Status;
 }
 
-VOID
-InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext,
+/**
+* @name MoveAttributes
+* @implemented
+*
+* Moves a block of attributes to a new location in the file Record. The attribute at FirstAttributeToMove
+* and every attribute after that will be moved to MoveTo.
+*
+* @param DeviceExt
+* Pointer to the DEVICE_EXTENSION (VCB) of the target volume.
+*
+* @param FirstAttributeToMove
+* Pointer to the first NTFS_ATTR_RECORD that needs to be moved. This pointer must reside within a file record.
+*
+* @param FirstAttributeOffset
+* Offset of FirstAttributeToMove relative to the beginning of the file record.
+*
+* @param MoveTo
+* ULONG_PTR with the memory location that will be the new location of the first attribute being moved.
+*
+* @return
+* The new location of the final attribute (i.e. AttributeEnd marker).
+*/
+PNTFS_ATTR_RECORD
+MoveAttributes(PDEVICE_EXTENSION DeviceExt,
+               PNTFS_ATTR_RECORD FirstAttributeToMove,
+               ULONG FirstAttributeOffset,
+               ULONG_PTR MoveTo)
+{
+    // Get the size of all attributes after this one
+    ULONG MemBlockSize = 0;
+    PNTFS_ATTR_RECORD CurrentAttribute = FirstAttributeToMove;
+    ULONG CurrentOffset = FirstAttributeOffset;
+    PNTFS_ATTR_RECORD FinalAttribute;
+
+    while (CurrentAttribute->Type != AttributeEnd && CurrentOffset < DeviceExt->NtfsInfo.BytesPerFileRecord)
+    {
+        CurrentOffset += CurrentAttribute->Length;
+        MemBlockSize += CurrentAttribute->Length;
+        CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length);
+    }
+
+    FinalAttribute = (PNTFS_ATTR_RECORD)(MoveTo + MemBlockSize);
+    MemBlockSize += sizeof(ULONG) * 2;  // Add the AttributeEnd and file record end
+
+    ASSERT(MemBlockSize % ATTR_RECORD_ALIGNMENT == 0);
+
+    // Move the attributes after this one
+    RtlMoveMemory((PCHAR)MoveTo, FirstAttributeToMove, MemBlockSize);
+
+    return FinalAttribute;
+}
+
+NTSTATUS
+InternalSetResidentAttributeLength(PDEVICE_EXTENSION DeviceExt,
+                                   PNTFS_ATTR_CONTEXT AttrContext,
                                    PFILE_RECORD_HEADER FileRecord,
                                    ULONG AttrOffset,
                                    ULONG DataSize)
 {
     PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
+    PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Destination + Destination->Length);
+    PNTFS_ATTR_RECORD FinalAttribute;
+    ULONG OldAttributeLength = Destination->Length;
     ULONG NextAttributeOffset;
 
-    DPRINT("InternalSetResidentAttributeLength( %p, %p, %lu, %lu )\n", AttrContext, FileRecord, AttrOffset, DataSize);
+    DPRINT1("InternalSetResidentAttributeLength( %p, %p, %p, %lu, %lu )\n", DeviceExt, AttrContext, FileRecord, AttrOffset, DataSize);
 
-    ASSERT(!AttrContext->Record.IsNonResident);
+    ASSERT(!AttrContext->pRecord->IsNonResident);
 
-    // update ValueLength Field
-    AttrContext->Record.Resident.ValueLength =
+    // Update ValueLength Field
     Destination->Resident.ValueLength = DataSize;
 
-    // calculate the record length and end marker offset
-    AttrContext->Record.Length =
-    Destination->Length = DataSize + AttrContext->Record.Resident.ValueOffset;
-    NextAttributeOffset = AttrOffset + AttrContext->Record.Length;
+    // Calculate the record length and end marker offset
+    Destination->Length = ALIGN_UP_BY(DataSize + AttrContext->pRecord->Resident.ValueOffset, ATTR_RECORD_ALIGNMENT);
+    NextAttributeOffset = AttrOffset + Destination->Length;
 
     // Ensure NextAttributeOffset is aligned to an 8-byte boundary
-    if (NextAttributeOffset % 8 != 0)
+    ASSERT(NextAttributeOffset % ATTR_RECORD_ALIGNMENT == 0);
+
+    // Will the new attribute be larger than the old one?
+    if (Destination->Length > OldAttributeLength)
+    {
+        // Free the old copy of the attribute in the context, as it will be the wrong length
+        ExFreePoolWithTag(AttrContext->pRecord, TAG_NTFS);
+
+        // Create a new copy of the attribute record for the context
+        AttrContext->pRecord = ExAllocatePoolWithTag(NonPagedPool, Destination->Length, TAG_NTFS);
+        if (!AttrContext->pRecord)
+        {
+            DPRINT1("Unable to allocate memory for attribute!\n");
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+        RtlZeroMemory((PVOID)((ULONG_PTR)AttrContext->pRecord + OldAttributeLength), Destination->Length - OldAttributeLength);
+        RtlCopyMemory(AttrContext->pRecord, Destination, OldAttributeLength);
+    }
+
+    // Are there attributes after this one that need to be moved?
+    if (NextAttribute->Type != AttributeEnd)
     {
-        USHORT Padding = ATTR_RECORD_ALIGNMENT - (NextAttributeOffset % ATTR_RECORD_ALIGNMENT);
-        NextAttributeOffset += Padding;
-        AttrContext->Record.Length += Padding;
-        Destination->Length += Padding;
+        // Move the attributes after this one
+        FinalAttribute = MoveAttributes(DeviceExt, NextAttribute, NextAttributeOffset, (ULONG_PTR)Destination + Destination->Length);
     }
-    
-    // advance Destination to the final "attribute" and set the file record end
-    Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)Destination + Destination->Length);
-    SetFileRecordEnd(FileRecord, Destination, FILE_RECORD_END);
+    else
+    {
+        // advance to the final "attribute," adjust for the changed length of the attribute we're resizing
+        FinalAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)NextAttribute - OldAttributeLength + Destination->Length);
+    }
+
+    // Update pRecord's length
+    AttrContext->pRecord->Length = Destination->Length;
+    AttrContext->pRecord->Resident.ValueLength = DataSize;
+
+    // set the file record end
+    SetFileRecordEnd(FileRecord, FinalAttribute, FILE_RECORD_END);
+
+    //NtfsDumpFileRecord(DeviceExt, FileRecord);
+
+    return STATUS_SUCCESS;
 }
 
 /**
@@ -399,7 +565,7 @@ SetAttributeDataLength(PFILE_OBJECT FileObject,
 {
     NTSTATUS Status = STATUS_SUCCESS;
 
-    DPRINT1("SetAttributeDataLenth(%p, %p, %p, %lu, %p, %I64u)\n",
+    DPRINT1("SetAttributeDataLength(%p, %p, %p, %lu, %p, %I64u)\n",
             FileObject,
             Fcb,
             AttrContext,
@@ -408,7 +574,7 @@ SetAttributeDataLength(PFILE_OBJECT FileObject,
             DataSize->QuadPart);
 
     // are we truncating the file?
-    if (DataSize->QuadPart < AttributeDataLength(&AttrContext->Record))
+    if (DataSize->QuadPart < AttributeDataLength(AttrContext->pRecord))
     {
         if (!MmCanFileBeTruncated(FileObject->SectionObjectPointer, DataSize))
         {
@@ -417,7 +583,7 @@ SetAttributeDataLength(PFILE_OBJECT FileObject,
         }
     }
 
-    if (AttrContext->Record.IsNonResident)
+    if (AttrContext->pRecord->IsNonResident)
     {
         Status = SetNonResidentAttributeDataLength(Fcb->Vcb,
                                                    AttrContext,
@@ -448,8 +614,8 @@ SetAttributeDataLength(PFILE_OBJECT FileObject,
 
     if (NT_SUCCESS(Status))
     {
-        if (AttrContext->Record.IsNonResident)
-            Fcb->RFCB.AllocationSize.QuadPart = AttrContext->Record.NonResident.AllocatedSize;
+        if (AttrContext->pRecord->IsNonResident)
+            Fcb->RFCB.AllocationSize.QuadPart = AttrContext->pRecord->NonResident.AllocatedSize;
         else
             Fcb->RFCB.AllocationSize = *DataSize;
         Fcb->RFCB.FileSize = *DataSize;
@@ -485,6 +651,9 @@ SetFileRecordEnd(PFILE_RECORD_HEADER FileRecord,
                  PNTFS_ATTR_RECORD AttrEnd,
                  ULONG EndMarker)
 {
+    // Ensure AttrEnd is aligned on an 8-byte boundary, relative to FileRecord
+    ASSERT(((ULONG_PTR)AttrEnd - (ULONG_PTR)FileRecord) % ATTR_RECORD_ALIGNMENT == 0);
+
     // mark the end of attributes
     AttrEnd->Type = AttributeEnd;
 
@@ -538,12 +707,12 @@ SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
     ULONG BytesPerCluster = Vcb->NtfsInfo.BytesPerCluster;
     ULONGLONG AllocationSize = ROUND_UP(DataSize->QuadPart, BytesPerCluster);
     PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
-    ULONG ExistingClusters = AttrContext->Record.NonResident.AllocatedSize / BytesPerCluster;
+    ULONG ExistingClusters = AttrContext->pRecord->NonResident.AllocatedSize / BytesPerCluster;
 
-    ASSERT(AttrContext->Record.IsNonResident);
+    ASSERT(AttrContext->pRecord->IsNonResident);
 
     // do we need to increase the allocation size?
-    if (AttrContext->Record.NonResident.AllocatedSize < AllocationSize)
+    if (AttrContext->pRecord->NonResident.AllocatedSize < AllocationSize)
     {
         ULONG ClustersNeeded = (AllocationSize / BytesPerCluster) - ExistingClusters;
         LARGE_INTEGER LastClusterInDataRun;
@@ -557,7 +726,7 @@ SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
         else
         {
             if (!FsRtlLookupLargeMcbEntry(&AttrContext->DataRunsMCB,
-                                          (LONGLONG)AttrContext->Record.NonResident.HighestVCN,
+                                          (LONGLONG)AttrContext->pRecord->NonResident.HighestVCN,
                                           (PLONGLONG)&LastClusterInDataRun.QuadPart,
                                           NULL,
                                           NULL,
@@ -567,13 +736,13 @@ SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
                 DPRINT1("Error looking up final large MCB entry!\n");
 
                 // Most likely, HighestVCN went above the largest mapping
-                DPRINT1("Highest VCN of record: %I64u\n", AttrContext->Record.NonResident.HighestVCN);
+                DPRINT1("Highest VCN of record: %I64u\n", AttrContext->pRecord->NonResident.HighestVCN);
                 return STATUS_INVALID_PARAMETER;
             }
         }
 
         DPRINT("LastClusterInDataRun: %I64u\n", LastClusterInDataRun.QuadPart);
-        DPRINT("Highest VCN of record: %I64u\n", AttrContext->Record.NonResident.HighestVCN);
+        DPRINT("Highest VCN of record: %I64u\n", AttrContext->pRecord->NonResident.HighestVCN);
 
         while (ClustersNeeded > 0)
         {
@@ -601,7 +770,7 @@ SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
             LastClusterInDataRun.LowPart = NextAssignedCluster + AssignedClusters - 1;
         }
     }
-    else if (AttrContext->Record.NonResident.AllocatedSize > AllocationSize)
+    else if (AttrContext->pRecord->NonResident.AllocatedSize > AllocationSize)
     {
         // shrink allocation size
         ULONG ClustersToFree = ExistingClusters - (AllocationSize / BytesPerCluster);
@@ -610,13 +779,18 @@ SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
 
     // TODO: is the file compressed, encrypted, or sparse?
 
-    AttrContext->Record.NonResident.AllocatedSize = AllocationSize;
-    AttrContext->Record.NonResident.DataSize = DataSize->QuadPart;
-    AttrContext->Record.NonResident.InitializedSize = DataSize->QuadPart;
+    AttrContext->pRecord->NonResident.AllocatedSize = AllocationSize;
+    AttrContext->pRecord->NonResident.DataSize = DataSize->QuadPart;
+    AttrContext->pRecord->NonResident.InitializedSize = DataSize->QuadPart;
 
     DestinationAttribute->NonResident.AllocatedSize = AllocationSize;
     DestinationAttribute->NonResident.DataSize = DataSize->QuadPart;
     DestinationAttribute->NonResident.InitializedSize = DataSize->QuadPart;
+    
+    // HighestVCN seems to be set incorrectly somewhere. Apply a hack-fix to reset it. 
+    // HACKHACK FIXME: Fix for sparse files; this math won't work in that case.
+    AttrContext->pRecord->NonResident.HighestVCN = ((ULONGLONG)AllocationSize / Vcb->NtfsInfo.BytesPerCluster) - 1;
+    DestinationAttribute->NonResident.HighestVCN = AttrContext->pRecord->NonResident.HighestVCN;
 
     DPRINT("Allocated Size: %I64u\n", DestinationAttribute->NonResident.AllocatedSize);
 
@@ -667,18 +841,18 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
     NTSTATUS Status;
 
     // find the next attribute
-    ULONG NextAttributeOffset = AttrOffset + AttrContext->Record.Length;
+    ULONG NextAttributeOffset = AttrOffset + AttrContext->pRecord->Length;
     PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((PCHAR)FileRecord + NextAttributeOffset);
 
-    ASSERT(!AttrContext->Record.IsNonResident);
+    ASSERT(!AttrContext->pRecord->IsNonResident);
 
     //NtfsDumpFileAttributes(Vcb, FileRecord);
 
     // Do we need to increase the data length?
-    if (DataSize->QuadPart > AttrContext->Record.Resident.ValueLength)
+    if (DataSize->QuadPart > AttrContext->pRecord->Resident.ValueLength)
     {
         // There's usually padding at the end of a record. Do we need to extend past it?
-        ULONG MaxValueLength = AttrContext->Record.Length - AttrContext->Record.Resident.ValueOffset;
+        ULONG MaxValueLength = AttrContext->pRecord->Length - AttrContext->pRecord->Resident.ValueOffset;
         if (MaxValueLength < DataSize->LowPart)
         {
             // If this is the last attribute, we could move the end marker to the very end of the file record
@@ -688,14 +862,16 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
             {
                 // convert attribute to non-resident
                 PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
+                PNTFS_ATTR_RECORD NewRecord;
                 LARGE_INTEGER AttribDataSize;
                 PVOID AttribData;
+                ULONG NewRecordLength;
                 ULONG EndAttributeOffset;
                 ULONG LengthWritten;
 
                 DPRINT1("Converting attribute to non-resident.\n");
 
-                AttribDataSize.QuadPart = AttrContext->Record.Resident.ValueLength;
+                AttribDataSize.QuadPart = AttrContext->pRecord->Resident.ValueLength;
 
                 // Is there existing data we need to back-up?
                 if (AttribDataSize.QuadPart > 0)
@@ -719,23 +895,43 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
 
                 // Start by turning this attribute into a 0-length, non-resident attribute, then enlarge it.
 
+                // The size of a 0-length, non-resident attribute will be 0x41 + the size of the attribute name, aligned to an 8-byte boundary
+                NewRecordLength = ALIGN_UP_BY(0x41 + (AttrContext->pRecord->NameLength * sizeof(WCHAR)), ATTR_RECORD_ALIGNMENT);
+
+                // Create a new attribute record that will store the 0-length, non-resident attribute
+                NewRecord = ExAllocatePoolWithTag(NonPagedPool, NewRecordLength, TAG_NTFS);
+
                 // Zero out the NonResident structure
-                RtlZeroMemory(&AttrContext->Record.NonResident.LowestVCN,
-                              FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize) - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.LowestVCN));
-                RtlZeroMemory(&Destination->NonResident.LowestVCN,
-                              FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize) - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.LowestVCN));
+                RtlZeroMemory(NewRecord, NewRecordLength);
 
-                // update the mapping pairs offset, which will be 0x40 + length in bytes of the name
-                AttrContext->Record.NonResident.MappingPairsOffset = Destination->NonResident.MappingPairsOffset = 0x40 + (Destination->NameLength * 2);
+                // Copy the data that's common to both non-resident and resident attributes
+                RtlCopyMemory(NewRecord, AttrContext->pRecord, FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.ValueLength));
+
+                // if there's a name
+                if (AttrContext->pRecord->NameLength != 0)
+                {
+                    // copy the name
+                    // An attribute name will be located at offset 0x18 for a resident attribute, 0x40 for non-resident
+                    RtlCopyMemory((PCHAR)((ULONG_PTR)NewRecord + 0x40),
+                                  (PCHAR)((ULONG_PTR)AttrContext->pRecord + 0x18),
+                                  AttrContext->pRecord->NameLength * sizeof(WCHAR));
+                }
+
+                // update the mapping pairs offset, which will be 0x40 (size of a non-resident header) + length in bytes of the name
+                NewRecord->NonResident.MappingPairsOffset = 0x40 + (AttrContext->pRecord->NameLength * sizeof(WCHAR));
 
                 // update the end of the file record
                 // calculate position of end markers (1 byte for empty data run)
-                EndAttributeOffset = AttrOffset + AttrContext->Record.NonResident.MappingPairsOffset + 1;
+                EndAttributeOffset = AttrOffset + NewRecord->NonResident.MappingPairsOffset + 1;
                 EndAttributeOffset = ALIGN_UP_BY(EndAttributeOffset, ATTR_RECORD_ALIGNMENT);
 
                 // Update the length
-                Destination->Length = EndAttributeOffset - AttrOffset;
-                AttrContext->Record.Length = Destination->Length;
+                NewRecord->Length = EndAttributeOffset - AttrOffset;
+
+                ASSERT(NewRecord->Length == NewRecordLength);
+
+                // Copy the new attribute record into the file record
+                RtlCopyMemory(Destination, NewRecord, NewRecord->Length);
 
                 // Update the file record end
                 SetFileRecordEnd(FileRecord,
@@ -756,7 +952,7 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
                 } _SEH2_END;
 
                 // Mark the attribute as non-resident (we wait until after we know the LargeMcb was initialized)
-                AttrContext->Record.IsNonResident = Destination->IsNonResident = 1;
+                NewRecord->IsNonResident = Destination->IsNonResident = 1;
 
                 // Update file record on disk
                 Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord);
@@ -768,6 +964,10 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
                     return Status;
                 }
 
+                // Now we need to free the old copy of the attribute record in the context and replace it with the new one
+                ExFreePoolWithTag(AttrContext->pRecord, TAG_NTFS);
+                AttrContext->pRecord = NewRecord;
+
                 // Now we can treat the attribute as non-resident and enlarge it normally
                 Status = SetNonResidentAttributeDataLength(Vcb, AttrContext, AttrOffset, FileRecord, DataSize);
                 if (!NT_SUCCESS(Status))
@@ -781,7 +981,7 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
                 // restore the back-up attribute, if we made one
                 if (AttribDataSize.QuadPart > 0)
                 {
-                    Status = WriteAttribute(Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart, &LengthWritten);
+                    Status = WriteAttribute(Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart, &LengthWritten, FileRecord);
                     if (!NT_SUCCESS(Status))
                     {
                         DPRINT1("ERROR: Unable to write attribute data to non-resident clusters during migration!\n");
@@ -795,19 +995,10 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
             }
         }
     }
-    else if (DataSize->LowPart < AttrContext->Record.Resident.ValueLength)
-    {
-        // we need to decrease the length
-        if (NextAttribute->Type != AttributeEnd)
-        {
-            DPRINT1("FIXME: Don't know how to decrease length of resident attribute unless it's the final attribute!\n");
-            return STATUS_NOT_IMPLEMENTED;
-        }
-    }
 
     // set the new length of the resident attribute (if we didn't migrate it)
-    if (!AttrContext->Record.IsNonResident)
-        InternalSetResidentAttributeLength(AttrContext, FileRecord, AttrOffset, DataSize->LowPart);
+    if (!AttrContext->pRecord->IsNonResident)
+        return InternalSetResidentAttributeLength(Vcb, AttrContext, FileRecord, AttrOffset, DataSize->LowPart);
 
     return STATUS_SUCCESS;
 }
@@ -832,13 +1023,19 @@ ReadAttribute(PDEVICE_EXTENSION Vcb,
     //TEMPTEMP
     PUCHAR TempBuffer;
 
-    if (!Context->Record.IsNonResident)
+    if (!Context->pRecord->IsNonResident)
     {
-        if (Offset > Context->Record.Resident.ValueLength)
+        // We need to truncate Offset to a ULONG for pointer arithmetic
+        // The check below should ensure that Offset is well within the range of 32 bits
+        ULONG LittleOffset = (ULONG)Offset;
+
+        // Ensure that offset isn't beyond the end of the attribute
+        if (Offset > Context->pRecord->Resident.ValueLength)
             return 0;
-        if (Offset + Length > Context->Record.Resident.ValueLength)
-            Length = (ULONG)(Context->Record.Resident.ValueLength - Offset);
-        RtlCopyMemory(Buffer, (PCHAR)&Context->Record + Context->Record.Resident.ValueOffset + Offset, Length);
+        if (Offset + Length > Context->pRecord->Resident.ValueLength)
+            Length = (ULONG)(Context->pRecord->Resident.ValueLength - Offset);
+
+        RtlCopyMemory(Buffer, (PVOID)((ULONG_PTR)Context->pRecord + Context->pRecord->Resident.ValueOffset + LittleOffset), Length);
         return Length;
     }
 
@@ -867,6 +1064,10 @@ ReadAttribute(PDEVICE_EXTENSION Vcb,
         //TEMPTEMP
         ULONG UsedBufferSize;
         TempBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
+        if (TempBuffer == NULL)
+        {
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
 
         LastLCN = 0;
         CurrentOffset = 0;
@@ -902,6 +1103,7 @@ ReadAttribute(PDEVICE_EXTENSION Vcb,
 
             if (*DataRun == 0)
             {
+                ExFreePoolWithTag(TempBuffer, TAG_NTFS);
                 return AlreadyRead;
             }
 
@@ -996,7 +1198,7 @@ ReadAttribute(PDEVICE_EXTENSION Vcb,
     } /* if Disk */
 
     // TEMPTEMP
-    if (Context->Record.IsNonResident)
+    if (Context->pRecord->IsNonResident)
         ExFreePoolWithTag(TempBuffer, TAG_NTFS);
 
     Context->CacheRun = DataRun;
@@ -1036,6 +1238,12 @@ ReadAttribute(PDEVICE_EXTENSION Vcb,
 * @param RealLengthWritten
 * Pointer to a ULONG which will receive how much data was written, in bytes
 *
+* @param FileRecord
+* Optional pointer to a FILE_RECORD_HEADER that contains a copy of the file record
+* being written to. Can be NULL, in which case the file record will be read from disk.
+* If not-null, WriteAttribute() will skip reading from disk, and FileRecord
+* will be updated with the newly-written attribute before the function returns.
+*
 * @return
 * STATUS_SUCCESS if successful, an error code otherwise. STATUS_NOT_IMPLEMENTED if
 * writing to a sparse file.
@@ -1051,7 +1259,8 @@ WriteAttribute(PDEVICE_EXTENSION Vcb,
                ULONGLONG Offset,
                const PUCHAR Buffer,
                ULONG Length,
-               PULONG RealLengthWritten)
+               PULONG RealLengthWritten,
+               PFILE_RECORD_HEADER FileRecord)
 {
     ULONGLONG LastLCN;
     PUCHAR DataRun;
@@ -1063,72 +1272,90 @@ WriteAttribute(PDEVICE_EXTENSION Vcb,
     NTSTATUS Status;
     PUCHAR SourceBuffer = Buffer;
     LONGLONG StartingOffset;
+    BOOLEAN FileRecordAllocated = FALSE;
     
     //TEMPTEMP
     PUCHAR TempBuffer;
         
 
-    DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p)\n", Vcb, Context, Offset, Buffer, Length, RealLengthWritten);
+    DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p, %p)\n", Vcb, Context, Offset, Buffer, Length, RealLengthWritten, FileRecord);
 
     *RealLengthWritten = 0;
 
     // is this a resident attribute?
-    if (!Context->Record.IsNonResident)
+    if (!Context->pRecord->IsNonResident)
     {
         ULONG AttributeOffset;
         PNTFS_ATTR_CONTEXT FoundContext;
-        PFILE_RECORD_HEADER FileRecord;
+        PNTFS_ATTR_RECORD Destination;
+
+        // Ensure requested data is within the bounds of the attribute
+        ASSERT(Offset + Length <= Context->pRecord->Resident.ValueLength);
 
-        if (Offset + Length > Context->Record.Resident.ValueLength)
+        if (Offset + Length > Context->pRecord->Resident.ValueLength)
         {
             DPRINT1("DRIVER ERROR: Attribute is too small!\n");
             return STATUS_INVALID_PARAMETER;
         }
 
-        FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
-
-        if (!FileRecord)
+        // Do we need to read the file record?
+        if (FileRecord == NULL)
         {
-            DPRINT1("Error: Couldn't allocate file record!\n");
-            return STATUS_NO_MEMORY;
-        }
+            FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
+            if (!FileRecord)
+            {
+                DPRINT1("Error: Couldn't allocate file record!\n");
+                return STATUS_NO_MEMORY;
+            }
+
+            FileRecordAllocated = TRUE;
 
-        // read the file record
-        ReadFileRecord(Vcb, Context->FileMFTIndex, FileRecord);
+            // read the file record
+            ReadFileRecord(Vcb, Context->FileMFTIndex, FileRecord);
+        }
 
         // find where to write the attribute data to
         Status = FindAttribute(Vcb, FileRecord,
-                               Context->Record.Type,
-                               (PCWSTR)((PCHAR)&Context->Record + Context->Record.NameOffset),
-                               Context->Record.NameLength,
+                               Context->pRecord->Type,
+                               (PCWSTR)((ULONG_PTR)Context->pRecord + Context->pRecord->NameOffset),
+                               Context->pRecord->NameLength,
                                &FoundContext,
                                &AttributeOffset);
 
         if (!NT_SUCCESS(Status))
         {
             DPRINT1("ERROR: Couldn't find matching attribute!\n");
-            ExFreePoolWithTag(FileRecord, TAG_NTFS);
+            if(FileRecordAllocated)
+                ExFreePoolWithTag(FileRecord, TAG_NTFS);
             return Status;
         }
 
-        DPRINT("Offset: %I64u, AttributeOffset: %u, ValueOffset: %u\n", Offset, AttributeOffset, Context->Record.Resident.ValueLength);
-        Offset += AttributeOffset + Context->Record.Resident.ValueOffset;
-        
-        if (Offset + Length > Vcb->NtfsInfo.BytesPerFileRecord)
+        Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttributeOffset);
+
+        DPRINT("Offset: %I64u, AttributeOffset: %u, ValueOffset: %u\n", Offset, AttributeOffset, Context->pRecord->Resident.ValueLength);
+
+        // Will we be writing past the end of the allocated file record?
+        if (Offset + Length + AttributeOffset + Context->pRecord->Resident.ValueOffset > Vcb->NtfsInfo.BytesPerFileRecord)
         {
             DPRINT1("DRIVER ERROR: Data being written extends past end of file record!\n");
             ReleaseAttributeContext(FoundContext);
-            ExFreePoolWithTag(FileRecord, TAG_NTFS);
+            if (FileRecordAllocated)
+                ExFreePoolWithTag(FileRecord, TAG_NTFS);
             return STATUS_INVALID_PARAMETER;
         }
 
-        // copy the data being written into the file record
-        RtlCopyMemory((PCHAR)FileRecord + Offset, Buffer, Length);
+        // copy the data being written into the file record. We cast Offset to ULONG, which is safe because it's range has been verified.
+        RtlCopyMemory((PCHAR)((ULONG_PTR)Destination + Context->pRecord->Resident.ValueOffset + (ULONG)Offset), Buffer, Length);
 
         Status = UpdateFileRecord(Vcb, Context->FileMFTIndex, FileRecord);
 
+        // Update the context's copy of the resident attribute
+        ASSERT(Context->pRecord->Length == Destination->Length);
+        RtlCopyMemory((PVOID)Context->pRecord, Destination, Context->pRecord->Length);
+
         ReleaseAttributeContext(FoundContext);
-        ExFreePoolWithTag(FileRecord, TAG_NTFS);
+        if (FileRecordAllocated)
+            ExFreePoolWithTag(FileRecord, TAG_NTFS);
 
         if (NT_SUCCESS(Status))
             *RealLengthWritten = Length;
@@ -1157,7 +1384,11 @@ WriteAttribute(PDEVICE_EXTENSION Vcb,
         CurrentOffset = 0;  
 
         // This will be rewritten in the next iteration to just use the DataRuns MCB directly
-        TempBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);        
+        TempBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
+        if (TempBuffer == NULL)
+        {
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
 
         ConvertLargeMCBToDataRuns(&Context->DataRunsMCB,
                                   TempBuffer,
@@ -1182,7 +1413,8 @@ WriteAttribute(PDEVICE_EXTENSION Vcb,
                 // (it may require increasing the allocation size).
                 DataRunStartLCN = -1;
                 DPRINT1("FIXME: Writing to sparse files is not supported yet!\n");
-                return STATUS_NOT_IMPLEMENTED;
+                Status = STATUS_NOT_IMPLEMENTED;
+                goto Cleanup;
             }
 
             // Have we reached the data run we're trying to write to?
@@ -1199,7 +1431,8 @@ WriteAttribute(PDEVICE_EXTENSION Vcb,
                 // (Presently, this code will rarely be reached, the write will usually have already failed by now)
                 // [We can reach here by creating a new file record when the MFT isn't large enough]
                 DPRINT1("FIXME: Master File Table needs to be enlarged.\n");
-                return STATUS_END_OF_FILE;
+                Status = STATUS_END_OF_FILE;
+                goto Cleanup;
             }
 
             CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
@@ -1233,7 +1466,7 @@ WriteAttribute(PDEVICE_EXTENSION Vcb,
         Context->CacheRunLastLCN = LastLCN;
         Context->CacheRunCurrentOffset = CurrentOffset;
 
-        return Status;
+        goto Cleanup;
     }
 
     Length -= WriteLength;
@@ -1266,7 +1499,8 @@ WriteAttribute(PDEVICE_EXTENSION Vcb,
         if (DataRunStartLCN == -1)
         {
             DPRINT1("FIXME: Don't know how to write to sparse files yet! (DataRunStartLCN == -1)\n");
-            return STATUS_NOT_IMPLEMENTED;
+            Status = STATUS_NOT_IMPLEMENTED;
+            goto Cleanup;
         }
         else
         {
@@ -1297,7 +1531,8 @@ WriteAttribute(PDEVICE_EXTENSION Vcb,
             {
                 // Failed sanity check.
                 DPRINT1("Encountered EOF before expected!\n");
-                return STATUS_END_OF_FILE;
+                Status = STATUS_END_OF_FILE;
+                goto Cleanup;
             }
 
             break;
@@ -1319,10 +1554,6 @@ WriteAttribute(PDEVICE_EXTENSION Vcb,
         }
     } // end while (Length > 0) [more data to write]
 
-    // TEMPTEMP
-    if (Context->Record.IsNonResident)
-        ExFreePoolWithTag(TempBuffer, TAG_NTFS);
-
     Context->CacheRun = DataRun;
     Context->CacheRunOffset = Offset + *RealLengthWritten;
     Context->CacheRunStartLCN = DataRunStartLCN;
@@ -1330,6 +1561,11 @@ WriteAttribute(PDEVICE_EXTENSION Vcb,
     Context->CacheRunLastLCN = LastLCN;
     Context->CacheRunCurrentOffset = CurrentOffset;
 
+Cleanup:
+    // TEMPTEMP
+    if (Context->pRecord->IsNonResident)
+        ExFreePoolWithTag(TempBuffer, TAG_NTFS);
+
     return Status;
 }
 
@@ -1419,7 +1655,7 @@ UpdateFileNameRecord(PDEVICE_EXTENSION Vcb,
         return STATUS_INSUFFICIENT_RESOURCES;
     }
 
-    Status = ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, AttributeDataLength(&IndexRootCtx->Record));
+    Status = ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, AttributeDataLength(IndexRootCtx->pRecord));
     if (!NT_SUCCESS(Status))
     {
         DPRINT1("ERROR: Failed to read Index Root!\n");
@@ -1453,7 +1689,7 @@ UpdateFileNameRecord(PDEVICE_EXTENSION Vcb,
     {
         // we need to write the index root attribute back to disk
         ULONG LengthWritten;
-        Status = WriteAttribute(Vcb, IndexRootCtx, 0, (PUCHAR)IndexRecord, AttributeDataLength(&IndexRootCtx->Record), &LengthWritten);
+        Status = WriteAttribute(Vcb, IndexRootCtx, 0, (PUCHAR)IndexRecord, AttributeDataLength(IndexRootCtx->pRecord), &LengthWritten, MftRecord);
         if (!NT_SUCCESS(Status))
         {
             DPRINT1("ERROR: Couldn't update Index Root!\n");
@@ -1551,7 +1787,7 @@ UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb,
         return Status;
     }
 
-    IndexAllocationSize = AttributeDataLength(&IndexAllocationCtx->Record);
+    IndexAllocationSize = AttributeDataLength(IndexAllocationCtx->pRecord);
     Status = STATUS_OBJECT_PATH_NOT_FOUND;
     for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize)
     {
@@ -1595,7 +1831,7 @@ UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb,
                 break;
             }
 
-            Status = WriteAttribute(Vcb, IndexAllocationCtx, RecordOffset, (const PUCHAR)IndexRecord, IndexBlockSize, &Written);
+            Status = WriteAttribute(Vcb, IndexAllocationCtx, RecordOffset, (const PUCHAR)IndexRecord, IndexBlockSize, &Written, MftRecord);
             if (!NT_SUCCESS(Status))
             {
                 DPRINT1("ERROR Performing write!\n");
@@ -1648,7 +1884,13 @@ UpdateFileRecord(PDEVICE_EXTENSION Vcb,
     AddFixupArray(Vcb, &FileRecord->Ntfs);
 
     // write the file record to the master file table
-    Status = WriteAttribute(Vcb, Vcb->MFTContext, MftIndex * Vcb->NtfsInfo.BytesPerFileRecord, (const PUCHAR)FileRecord, Vcb->NtfsInfo.BytesPerFileRecord, &BytesWritten);
+    Status = WriteAttribute(Vcb, 
+                            Vcb->MFTContext,
+                            MftIndex * Vcb->NtfsInfo.BytesPerFileRecord,
+                            (const PUCHAR)FileRecord,
+                            Vcb->NtfsInfo.BytesPerFileRecord,
+                            &BytesWritten,
+                            FileRecord);
 
     if (!NT_SUCCESS(Status))
     {
@@ -1731,6 +1973,7 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
     ULONGLONG BitmapDataSize;
     ULONGLONG AttrBytesRead;
     PUCHAR BitmapData;
+    PUCHAR BitmapBuffer;
     ULONG LengthWritten;
     PNTFS_ATTR_CONTEXT BitmapContext;
     LARGE_INTEGER BitmapBits;
@@ -1739,6 +1982,8 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
     DPRINT1("AddNewMftEntry(%p, %p, %p, %s)\n", FileRecord, DeviceExt, DestinationIndex, CanWait ? "TRUE" : "FALSE");
 
     // First, we have to read the mft's $Bitmap attribute
+
+    // Find the attribute
     Status = FindAttribute(DeviceExt, DeviceExt->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL);
     if (!NT_SUCCESS(Status))
     {
@@ -1746,22 +1991,29 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
         return Status;
     }
 
-    // allocate a buffer for the $Bitmap attribute
-    BitmapDataSize = AttributeDataLength(&BitmapContext->Record);
-    BitmapData = ExAllocatePoolWithTag(NonPagedPool, BitmapDataSize, TAG_NTFS);
-    if (!BitmapData)
+    // Get size of bitmap
+    BitmapDataSize = AttributeDataLength(BitmapContext->pRecord);
+
+    // RtlInitializeBitmap wants a ULONG-aligned pointer, and wants the memory passed to it to be a ULONG-multiple
+    // Allocate a buffer for the $Bitmap attribute plus enough to ensure we can get a ULONG-aligned pointer
+    BitmapBuffer = ExAllocatePoolWithTag(NonPagedPool, BitmapDataSize + sizeof(ULONG), TAG_NTFS);
+    if (!BitmapBuffer)
     {
         ReleaseAttributeContext(BitmapContext);
         return STATUS_INSUFFICIENT_RESOURCES;
     }
+    RtlZeroMemory(BitmapBuffer, BitmapDataSize + sizeof(ULONG));
+
+    // Get a ULONG-aligned pointer for the bitmap itself
+    BitmapData = (PUCHAR)ALIGN_UP_BY((ULONG_PTR)BitmapBuffer, sizeof(ULONG));
 
     // read $Bitmap attribute
     AttrBytesRead = ReadAttribute(DeviceExt, BitmapContext, 0, (PCHAR)BitmapData, BitmapDataSize);
 
-    if (AttrBytesRead == 0)
+    if (AttrBytesRead != BitmapDataSize)
     {
         DPRINT1("ERROR: Unable to read $Bitmap attribute of master file table!\n");
-        ExFreePoolWithTag(BitmapData, TAG_NTFS);
+        ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
         ReleaseAttributeContext(BitmapContext);
         return STATUS_OBJECT_NAME_NOT_FOUND;
     }
@@ -1772,12 +2024,13 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
     BitmapData[2] = 0xff;
 
     // Calculate bit count
-    BitmapBits.QuadPart = AttributeDataLength(&(DeviceExt->MFTContext->Record)) /
+    BitmapBits.QuadPart = AttributeDataLength(DeviceExt->MFTContext->pRecord) /
                           DeviceExt->NtfsInfo.BytesPerFileRecord;
     if (BitmapBits.HighPart != 0)
     {
         DPRINT1("\tFIXME: bitmap sizes beyond 32bits are not yet supported! (Your NTFS volume is too large)\n");
-        ExFreePoolWithTag(BitmapData, TAG_NTFS);
+        NtfsGlobalData->EnableWriteSupport = FALSE;
+        ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
         ReleaseAttributeContext(BitmapContext);
         return STATUS_NOT_IMPLEMENTED;
     }
@@ -1791,7 +2044,7 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
     {
         DPRINT1("Couldn't find free space in MFT for file record, increasing MFT size.\n");
 
-        ExFreePoolWithTag(BitmapData, TAG_NTFS);
+        ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
         ReleaseAttributeContext(BitmapContext);
 
         // Couldn't find a free record in the MFT, add some blank records and try again
@@ -1816,11 +2069,11 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
     BitmapData[2] = SystemReservedBits;
 
     // write the bitmap back to the MFT's $Bitmap attribute
-    Status = WriteAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize, &LengthWritten);
+    Status = WriteAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize, &LengthWritten, FileRecord);
     if (!NT_SUCCESS(Status))
     {
         DPRINT1("ERROR encountered when writing $Bitmap attribute!\n");
-        ExFreePoolWithTag(BitmapData, TAG_NTFS);
+        ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
         ReleaseAttributeContext(BitmapContext);
         return Status;
     }
@@ -1831,14 +2084,14 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
     if (!NT_SUCCESS(Status))
     {
         DPRINT1("ERROR: Unable to write file record!\n");
-        ExFreePoolWithTag(BitmapData, TAG_NTFS);
+        ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
         ReleaseAttributeContext(BitmapContext);
         return Status;
     }
 
     *DestinationIndex = MftIndex;
 
-    ExFreePoolWithTag(BitmapData, TAG_NTFS);
+    ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
     ReleaseAttributeContext(BitmapContext);
 
     return Status;
@@ -1892,13 +2145,17 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
     ULONG IndexRootOffset;
     ULONGLONG I30IndexRootLength;
     ULONG LengthWritten;
-    PNTFS_ATTR_RECORD DestinationAttribute;
     PINDEX_ROOT_ATTRIBUTE NewIndexRoot;
     ULONG AttributeLength;
     PNTFS_ATTR_RECORD NextAttribute;
     PB_TREE NewTree;
     ULONG BtreeIndexLength;
-    ULONG MaxIndexSize;
+    ULONG MaxIndexRootSize;
+    PB_TREE_KEY NewLeftKey;
+    PB_TREE_FILENAME_NODE NewRightHandNode;
+    LARGE_INTEGER MinIndexRootSize;
+    ULONG NewMaxIndexRootSize;
+    ULONG NodeSize;
 
     // Allocate memory for the parent directory
     ParentFileRecord = ExAllocatePoolWithTag(NonPagedPool,
@@ -1920,8 +2177,10 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
         return Status;
     }
 
+#ifndef NDEBUG
     DPRINT1("Dumping old parent file record:\n");
     NtfsDumpFileRecord(DeviceExt, ParentFileRecord);
+#endif
 
     // Find the index root attribute for the directory
     Status = FindAttribute(DeviceExt,
@@ -1940,14 +2199,32 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
     }
 
     // Find the maximum index size given what the file record can hold
-    MaxIndexSize = DeviceExt->NtfsInfo.BytesPerFileRecord
-        - IndexRootOffset
-        - IndexRootContext->Record.Resident.ValueOffset
-        - FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header)
-        - (sizeof(ULONG) * 2);
+    // First, find the max index size assuming index root is the last attribute
+    MaxIndexRootSize = DeviceExt->NtfsInfo.BytesPerFileRecord               // Start with the size of a file record
+                       - IndexRootOffset                                    // Subtract the length of everything that comes before index root
+                       - IndexRootContext->pRecord->Resident.ValueOffset    // Subtract the length of the attribute header for index root
+                       - sizeof(INDEX_ROOT_ATTRIBUTE)                       // Subtract the length of the index root header
+                       - (sizeof(ULONG) * 2);                               // Subtract the length of the file record end marker and padding
+
+    // Are there attributes after this one?
+    NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset + IndexRootContext->pRecord->Length);
+    if (NextAttribute->Type != AttributeEnd)
+    {
+        // Find the length of all attributes after this one, not counting the end marker
+        ULONG LengthOfAttributes = 0;
+        PNTFS_ATTR_RECORD CurrentAttribute = NextAttribute;
+        while (CurrentAttribute->Type != AttributeEnd)
+        {
+            LengthOfAttributes += CurrentAttribute->Length;
+            CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length);
+        }
+
+        // Leave room for the existing attributes
+        MaxIndexRootSize -= LengthOfAttributes;
+    }
 
     // Allocate memory for the index root data
-    I30IndexRootLength = AttributeDataLength(&IndexRootContext->Record);
+    I30IndexRootLength = AttributeDataLength(IndexRootContext->pRecord);
     I30IndexRoot = ExAllocatePoolWithTag(NonPagedPool, I30IndexRootLength, TAG_NTFS);
     if (!I30IndexRoot)
     {
@@ -1983,10 +2260,20 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
         return Status;
     }
 
+#ifndef NDEBUG
     DumpBTree(NewTree);
+#endif
 
     // Insert the key for the file we're adding
-    Status = NtfsInsertKey(FileReferenceNumber, FilenameAttribute, NewTree->RootNode, CaseSensitive);
+    Status = NtfsInsertKey(NewTree,
+                           FileReferenceNumber,
+                           FilenameAttribute,
+                           NewTree->RootNode,
+                           CaseSensitive,
+                           MaxIndexRootSize,
+                           I30IndexRoot->SizeOfEntry,
+                           &NewLeftKey,
+                           &NewRightHandNode);
     if (!NT_SUCCESS(Status))
     {
         DPRINT1("ERROR: Failed to insert key into B-Tree!\n");
@@ -1997,9 +2284,56 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
         return Status;
     }
 
+#ifndef NDEBUG
     DumpBTree(NewTree);
+#endif
+
+    // The root node can't be split
+    ASSERT(NewLeftKey == NULL);
+    ASSERT(NewRightHandNode == NULL);
 
     // Convert B*Tree back to Index
+
+    // Updating the index allocation can change the size available for the index root,
+    // And if the index root is demoted, the index allocation will need to be updated again,
+    // which may change the size available for index root... etc.
+    // My solution is to decrease index root to the size it would be if it was demoted,
+    // then UpdateIndexAllocation will have an accurate representation of the maximum space
+    // it can use in the file record. There's still a chance that the act of allocating an
+    // index node after demoting the index root will increase the size of the file record beyond
+    // it's limit, but if that happens, an attribute-list will most definitely be needed.
+    // This a bit hacky, but it seems to be functional.
+
+    // Calculate the minimum size of the index root attribute, considering one dummy key and one VCN
+    MinIndexRootSize.QuadPart = sizeof(INDEX_ROOT_ATTRIBUTE) // size of the index root headers
+                                + 0x18; // Size of dummy key with a VCN for a subnode
+    ASSERT(MinIndexRootSize.QuadPart % ATTR_RECORD_ALIGNMENT == 0);
+
+    // Temporarily shrink the index root to it's minimal size
+    AttributeLength = MinIndexRootSize.LowPart;
+    AttributeLength += sizeof(INDEX_ROOT_ATTRIBUTE);
+
+    
+    // FIXME: IndexRoot will probably be invalid until we're finished. If we fail before we finish, the directory will probably be toast.
+    // The potential for catastrophic data-loss exists!!! :)
+
+    // Update the length of the attribute in the file record of the parent directory
+    Status = InternalSetResidentAttributeLength(DeviceExt,
+                                                IndexRootContext,
+                                                ParentFileRecord,
+                                                IndexRootOffset,
+                                                AttributeLength);
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT1("ERROR: Unable to set length of index root!\n");
+        DestroyBTree(NewTree);
+        ReleaseAttributeContext(IndexRootContext);
+        ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
+        ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
+        return Status;
+    }
+
+    // Update the index allocation
     Status = UpdateIndexAllocation(DeviceExt, NewTree, I30IndexRoot->SizeOfEntry, ParentFileRecord);
     if (!NT_SUCCESS(Status))
     {
@@ -2011,8 +2345,99 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
         return Status;
     }
 
+#ifndef NDEBUG
+    DPRINT1("Index Allocation updated\n");
+    DumpBTree(NewTree);
+#endif
+
+    // Find the maximum index root size given what the file record can hold
+    // First, find the max index size assuming index root is the last attribute
+    NewMaxIndexRootSize =
+       DeviceExt->NtfsInfo.BytesPerFileRecord                // Start with the size of a file record
+        - IndexRootOffset                                    // Subtract the length of everything that comes before index root
+        - IndexRootContext->pRecord->Resident.ValueOffset    // Subtract the length of the attribute header for index root
+        - sizeof(INDEX_ROOT_ATTRIBUTE)                       // Subtract the length of the index root header
+        - (sizeof(ULONG) * 2);                               // Subtract the length of the file record end marker and padding
+
+    // Are there attributes after this one?
+    NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset + IndexRootContext->pRecord->Length);
+    if (NextAttribute->Type != AttributeEnd)
+    {
+        // Find the length of all attributes after this one, not counting the end marker
+        ULONG LengthOfAttributes = 0;
+        PNTFS_ATTR_RECORD CurrentAttribute = NextAttribute;
+        while (CurrentAttribute->Type != AttributeEnd)
+        {
+            LengthOfAttributes += CurrentAttribute->Length;
+            CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length);
+        }
+
+        // Leave room for the existing attributes
+        NewMaxIndexRootSize -= LengthOfAttributes;
+    }
+
+    // The index allocation and index bitmap may have grown, leaving less room for the index root,
+    // so now we need to double-check that index root isn't too large 
+    NodeSize = GetSizeOfIndexEntries(NewTree->RootNode);
+    if (NodeSize > NewMaxIndexRootSize)
+    {
+        DPRINT1("Demoting index root.\nNodeSize: 0x%lx\nNewMaxIndexRootSize: 0x%lx\n", NodeSize, NewMaxIndexRootSize);
+
+        Status = DemoteBTreeRoot(NewTree);
+        if (!NT_SUCCESS(Status))
+        {
+            DPRINT1("ERROR: Failed to demote index root!\n");
+            DestroyBTree(NewTree);
+            ReleaseAttributeContext(IndexRootContext);
+            ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
+            ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
+            return Status;
+        }
+
+        // We need to update the index allocation once more
+        Status = UpdateIndexAllocation(DeviceExt, NewTree, I30IndexRoot->SizeOfEntry, ParentFileRecord);
+        if (!NT_SUCCESS(Status))
+        {
+            DPRINT1("ERROR: Failed to update index allocation from B-Tree!\n");
+            DestroyBTree(NewTree);
+            ReleaseAttributeContext(IndexRootContext);
+            ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
+            ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
+            return Status;
+        }
+
+        // re-recalculate max size of index root
+        NewMaxIndexRootSize =
+            // Find the maximum index size given what the file record can hold
+            // First, find the max index size assuming index root is the last attribute
+            DeviceExt->NtfsInfo.BytesPerFileRecord               // Start with the size of a file record
+            - IndexRootOffset                                    // Subtract the length of everything that comes before index root
+            - IndexRootContext->pRecord->Resident.ValueOffset    // Subtract the length of the attribute header for index root
+            - sizeof(INDEX_ROOT_ATTRIBUTE)                       // Subtract the length of the index root header
+            - (sizeof(ULONG) * 2);                               // Subtract the length of the file record end marker and padding
+
+                                                                 // Are there attributes after this one?
+        NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset + IndexRootContext->pRecord->Length);
+        if (NextAttribute->Type != AttributeEnd)
+        {
+            // Find the length of all attributes after this one, not counting the end marker
+            ULONG LengthOfAttributes = 0;
+            PNTFS_ATTR_RECORD CurrentAttribute = NextAttribute;
+            while (CurrentAttribute->Type != AttributeEnd)
+            {
+                LengthOfAttributes += CurrentAttribute->Length;
+                CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length);
+            }
+
+            // Leave room for the existing attributes
+            NewMaxIndexRootSize -= LengthOfAttributes;
+        }
+
+        
+    }
+
     // Create the Index Root from the B*Tree
-    Status = CreateIndexRootFromBTree(DeviceExt, NewTree, MaxIndexSize, &NewIndexRoot, &BtreeIndexLength);
+    Status = CreateIndexRootFromBTree(DeviceExt, NewTree, NewMaxIndexRootSize, &NewIndexRoot, &BtreeIndexLength);
     if (!NT_SUCCESS(Status))
     {
         DPRINT1("ERROR: Failed to create Index root from B-Tree!\n");
@@ -2030,32 +2455,27 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
 
     // First, we need to resize the attribute.
     // CreateIndexRootFromBTree() should have verified that the index root fits within MaxIndexSize.
-    // We can't set the size as we normally would, because if we extend past the file record, 
-    // we must create an index allocation and index bitmap (TODO). Also TODO: support file records with
-    // $ATTRIBUTE_LIST's.
+    // We can't set the size as we normally would, because $INDEX_ROOT must always be resident.
     AttributeLength = NewIndexRoot->Header.AllocatedSize + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header);
     
-    if (AttributeLength != IndexRootContext->Record.Resident.ValueLength)
+    if (AttributeLength != IndexRootContext->pRecord->Resident.ValueLength)
     {
-        DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset);
-
-        // Find the attribute (or attribute-end marker) after the index root
-        NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)DestinationAttribute + DestinationAttribute->Length);
-        if (NextAttribute->Type != AttributeEnd)
+        // Update the length of the attribute in the file record of the parent directory
+        Status = InternalSetResidentAttributeLength(DeviceExt,
+                                                    IndexRootContext,
+                                                    ParentFileRecord,
+                                                    IndexRootOffset,
+                                                    AttributeLength);
+        if (!NT_SUCCESS(Status))
         {
-            DPRINT1("FIXME: For now, only resizing index root at the end of a file record is supported!\n");
             ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
             ReleaseAttributeContext(IndexRootContext);
             ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
             ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
-            return STATUS_NOT_IMPLEMENTED;
+            DPRINT1("ERROR: Unable to set resident attribute length!\n");
+            return Status;
         }
 
-        // Update the length of the attribute in the file record of the parent directory
-        InternalSetResidentAttributeLength(IndexRootContext,
-                                           ParentFileRecord,
-                                           IndexRootOffset,
-                                           AttributeLength);
     }
 
     NT_ASSERT(ParentFileRecord->BytesInUse <= DeviceExt->NtfsInfo.BytesPerFileRecord);
@@ -2077,8 +2497,9 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
                             0,
                             (PUCHAR)NewIndexRoot,
                             AttributeLength,
-                            &LengthWritten);
-    if (!NT_SUCCESS(Status))
+                            &LengthWritten,
+                            ParentFileRecord);
+    if (!NT_SUCCESS(Status) || LengthWritten != AttributeLength)
     {
         DPRINT1("ERROR: Unable to write new index root attribute to parent directory!\n");
         ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
@@ -2096,8 +2517,22 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
     }
     else
     {
-        DPRINT1("Dumping new parent file record:\n");
+#ifndef NDEBUG
+        DPRINT1("Dumping new B-Tree:\n");
+
+        Status = CreateBTreeFromIndex(DeviceExt, ParentFileRecord, IndexRootContext, NewIndexRoot, &NewTree);
+        if (!NT_SUCCESS(Status))
+        {
+            DPRINT1("ERROR: Couldn't re-create b-tree\n");
+            return Status;
+        }
+
+        DumpBTree(NewTree);
+
+        DestroyBTree(NewTree);
+
         NtfsDumpFileRecord(DeviceExt, ParentFileRecord);
+#endif
     }
 
     // Cleanup
@@ -2197,6 +2632,129 @@ CompareFileName(PUNICODE_STRING FileName,
     }
 }
 
+/**
+* @name UpdateMftMirror
+* @implemented
+*
+* Backs-up the first ~4 master file table entries to the $MFTMirr file.
+*
+* @param Vcb
+* Pointer to an NTFS_VCB for the volume whose Mft mirror is being updated.
+*
+* @return
+
+* STATUS_SUCCESS on success.
+* STATUS_INSUFFICIENT_RESOURCES if an allocation failed.
+* STATUS_UNSUCCESSFUL if we couldn't read the master file table.
+*
+* @remarks
+* NTFS maintains up-to-date copies of the first several mft entries in the $MFTMirr file. Usually, the first 4 file
+* records from the mft are stored. The exact number of entries is determined by the size of $MFTMirr's $DATA. 
+* If $MFTMirr is not up-to-date, chkdsk will reject every change it can find prior to when $MFTMirr was last updated.
+* Therefore, it's recommended to call this function if the volume changes considerably. For instance, IncreaseMftSize()
+* relies on this function to keep chkdsk from deleting the mft entries it creates. Note that under most instances, creating
+* or deleting a file will not affect the first ~four mft entries, and so will not require updating the mft mirror.
+*/
+NTSTATUS
+UpdateMftMirror(PNTFS_VCB Vcb)
+{
+    PFILE_RECORD_HEADER MirrorFileRecord;
+    PNTFS_ATTR_CONTEXT MirrDataContext;
+    PNTFS_ATTR_CONTEXT MftDataContext;
+    PCHAR DataBuffer;
+    ULONGLONG DataLength;
+    NTSTATUS Status;
+    ULONG BytesRead;
+    ULONG LengthWritten;
+
+    // Allocate memory for the Mft mirror file record
+    MirrorFileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
+    if (!MirrorFileRecord)
+    {
+        DPRINT1("Error: Failed to allocate memory for $MFTMirr!\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+
+    // Read the Mft Mirror file record
+    Status = ReadFileRecord(Vcb, NTFS_FILE_MFTMIRR, MirrorFileRecord);
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT1("ERROR: Failed to read $MFTMirr!\n");
+        ExFreePoolWithTag(MirrorFileRecord, TAG_NTFS);
+        return Status;
+    }
+
+    // Find the $DATA attribute of $MFTMirr
+    Status = FindAttribute(Vcb, MirrorFileRecord, AttributeData, L"", 0, &MirrDataContext, NULL);
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT1("ERROR: Couldn't find $DATA attribute!\n");
+        ExFreePoolWithTag(MirrorFileRecord, TAG_NTFS);
+        return Status;
+    }
+
+    // Find the $DATA attribute of $MFT
+    Status = FindAttribute(Vcb, Vcb->MasterFileTable, AttributeData, L"", 0, &MftDataContext, NULL);
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT1("ERROR: Couldn't find $DATA attribute!\n");
+        ReleaseAttributeContext(MirrDataContext);
+        ExFreePoolWithTag(MirrorFileRecord, TAG_NTFS);
+        return Status;
+    }
+
+    // Get the size of the mirror's $DATA attribute
+    DataLength = AttributeDataLength(MirrDataContext->pRecord);
+
+    ASSERT(DataLength % Vcb->NtfsInfo.BytesPerFileRecord == 0);
+
+    // Create buffer for the mirror's $DATA attribute
+    DataBuffer = ExAllocatePoolWithTag(NonPagedPool, DataLength, TAG_NTFS);
+    if (!DataBuffer)
+    {
+        DPRINT1("Error: Couldn't allocate memory for $DATA buffer!\n");
+        ReleaseAttributeContext(MftDataContext);
+        ReleaseAttributeContext(MirrDataContext);
+        ExFreePoolWithTag(MirrorFileRecord, TAG_NTFS);
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+
+    ASSERT(DataLength < ULONG_MAX);
+
+    // Back up the first several entries of the Mft's $DATA Attribute
+    BytesRead = ReadAttribute(Vcb, MftDataContext, 0, DataBuffer, (ULONG)DataLength);
+    if (BytesRead != (ULONG)DataLength)
+    {
+        DPRINT1("Error: Failed to read $DATA for $MFTMirr!\n");
+        ReleaseAttributeContext(MftDataContext);
+        ReleaseAttributeContext(MirrDataContext);
+        ExFreePoolWithTag(DataBuffer, TAG_NTFS);
+        ExFreePoolWithTag(MirrorFileRecord, TAG_NTFS);
+        return STATUS_UNSUCCESSFUL;
+    }
+
+    // Write the mirror's $DATA attribute
+    Status = WriteAttribute(Vcb,
+                             MirrDataContext,
+                             0,
+                             (PUCHAR)DataBuffer,
+                             DataLength,
+                             &LengthWritten,
+                             MirrorFileRecord);
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT1("ERROR: Failed to write $DATA attribute of $MFTMirr!\n");
+    }
+
+    // Cleanup
+    ReleaseAttributeContext(MftDataContext);
+    ReleaseAttributeContext(MirrDataContext);
+    ExFreePoolWithTag(DataBuffer, TAG_NTFS);
+    ExFreePoolWithTag(MirrorFileRecord, TAG_NTFS);
+
+    return Status;
+}
+
 #if 0
 static
 VOID
@@ -2222,10 +2780,158 @@ DumpIndexEntry(PINDEX_ENTRY_ATTRIBUTE IndexEntry)
 }
 #endif
 
+NTSTATUS
+BrowseSubNodeIndexEntries(PNTFS_VCB Vcb,
+                          PFILE_RECORD_HEADER MftRecord,
+                          ULONG IndexBlockSize,
+                          PUNICODE_STRING FileName,
+                          PNTFS_ATTR_CONTEXT IndexAllocationContext,
+                          PRTL_BITMAP Bitmap,
+                          ULONGLONG VCN,
+                          PULONG StartEntry,
+                          PULONG CurrentEntry,
+                          BOOLEAN DirSearch,
+                          BOOLEAN CaseSensitive,
+                          ULONGLONG *OutMFTIndex)
+{
+    PINDEX_BUFFER IndexRecord;
+    ULONGLONG Offset;
+    ULONG BytesRead;
+    PINDEX_ENTRY_ATTRIBUTE FirstEntry;
+    PINDEX_ENTRY_ATTRIBUTE LastEntry;
+    PINDEX_ENTRY_ATTRIBUTE IndexEntry;
+    ULONG NodeNumber;
+    NTSTATUS Status;
+
+    DPRINT("BrowseSubNodeIndexEntries(%p, %p, %lu, %wZ, %p, %p, %I64d, %lu, %lu, %s, %s, %p)\n",
+           Vcb,
+           MftRecord,
+           IndexBlockSize,
+           FileName,
+           IndexAllocationContext,
+           Bitmap,
+           VCN,
+           *StartEntry,
+           *CurrentEntry,
+           "FALSE",
+           DirSearch ? "TRUE" : "FALSE",
+           CaseSensitive ? "TRUE" : "FALSE",
+           OutMFTIndex);
+
+    // Calculate node number as VCN / Clusters per index record
+    NodeNumber = VCN / (Vcb->NtfsInfo.BytesPerIndexRecord / Vcb->NtfsInfo.BytesPerCluster);
+
+    // Is the bit for this node clear in the bitmap?
+    if (!RtlCheckBit(Bitmap, NodeNumber))
+    {
+        DPRINT1("File system corruption detected, node with VCN %I64u is being reused or is marked as deleted.\n", VCN);
+        return STATUS_DATA_ERROR;
+    }
+
+    // Clear the bit for this node so it can't be recursively referenced
+    RtlClearBits(Bitmap, NodeNumber, 1);
+
+    // Allocate memory for the index record
+    IndexRecord = ExAllocatePoolWithTag(NonPagedPool, IndexBlockSize, TAG_NTFS);
+    if (!IndexRecord)
+    {
+        DPRINT1("Unable to allocate memory for index record!\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+
+    // Calculate offset of index record
+    Offset = VCN * Vcb->NtfsInfo.BytesPerCluster;
+
+    // Read the index record
+    BytesRead = ReadAttribute(Vcb, IndexAllocationContext, Offset, (PCHAR)IndexRecord, IndexBlockSize);
+    if (BytesRead != IndexBlockSize)
+    {
+        DPRINT1("Unable to read index record!\n");
+        ExFreePoolWithTag(IndexRecord, TAG_NTFS);
+        return STATUS_UNSUCCESSFUL;
+    }
+
+    // Assert that we're dealing with an index record here
+    ASSERT(IndexRecord->Ntfs.Type == NRH_INDX_TYPE);
+
+    // Apply the fixup array to the index record
+    Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
+    if (!NT_SUCCESS(Status))
+    {
+        ExFreePoolWithTag(IndexRecord, TAG_NTFS);
+        DPRINT1("Failed to apply fixup array!\n");
+        return Status;
+    }
+
+    ASSERT(IndexRecord->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize);
+    FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexRecord->Header + IndexRecord->Header.FirstEntryOffset);
+    LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexRecord->Header + IndexRecord->Header.TotalSizeOfEntries);
+    ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexRecord + IndexBlockSize));
+
+    // Loop through all Index Entries of index, starting with FirstEntry
+    IndexEntry = FirstEntry;
+    while (IndexEntry <= LastEntry)
+    {
+        // Does IndexEntry have a sub-node?
+        if (IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)
+        {
+            if (!(IndexRecord->Header.Flags & INDEX_NODE_LARGE) || !IndexAllocationContext)
+            {
+                DPRINT1("Filesystem corruption detected!\n");
+            }
+            else
+            {
+                Status = BrowseSubNodeIndexEntries(Vcb,
+                                                   MftRecord,
+                                                   IndexBlockSize,
+                                                   FileName,
+                                                   IndexAllocationContext,
+                                                   Bitmap,
+                                                   GetIndexEntryVCN(IndexEntry),
+                                                   StartEntry,
+                                                   CurrentEntry,
+                                                   DirSearch,
+                                                   CaseSensitive,
+                                                   OutMFTIndex);
+                if (NT_SUCCESS(Status))
+                {
+                    ExFreePoolWithTag(IndexRecord, TAG_NTFS);
+                    return Status;
+                }
+            }
+        }
+
+        // Are we done?
+        if (IndexEntry->Flags & NTFS_INDEX_ENTRY_END)
+            break;
+
+        // If we've found a file whose index is greater than or equal to StartEntry that matches the search criteria
+        if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) >= NTFS_FILE_FIRST_USER_FILE &&
+            *CurrentEntry >= *StartEntry &&
+            IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
+            CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive))
+        {
+            *StartEntry = *CurrentEntry;
+            *OutMFTIndex = (IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK);
+            ExFreePoolWithTag(IndexRecord, TAG_NTFS);
+            return STATUS_SUCCESS;
+        }
+
+        // Advance to the next index entry
+        (*CurrentEntry) += 1;
+        ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
+        IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
+    }
+
+    ExFreePoolWithTag(IndexRecord, TAG_NTFS);
+
+    return STATUS_OBJECT_PATH_NOT_FOUND;
+}
+
 NTSTATUS
 BrowseIndexEntries(PDEVICE_EXTENSION Vcb,
                    PFILE_RECORD_HEADER MftRecord,
-                   PCHAR IndexRecord,
+                   PINDEX_ROOT_ATTRIBUTE IndexRecord,
                    ULONG IndexBlockSize,
                    PINDEX_ENTRY_ATTRIBUTE FirstEntry,
                    PINDEX_ENTRY_ATTRIBUTE LastEntry,
@@ -2237,11 +2943,12 @@ BrowseIndexEntries(PDEVICE_EXTENSION Vcb,
                    ULONGLONG *OutMFTIndex)
 {
     NTSTATUS Status;
-    ULONG RecordOffset;
     PINDEX_ENTRY_ATTRIBUTE IndexEntry;
-    PNTFS_ATTR_CONTEXT IndexAllocationCtx;
-    ULONGLONG IndexAllocationSize;
-    PINDEX_BUFFER IndexBuffer;
+    PNTFS_ATTR_CONTEXT IndexAllocationContext;
+    PNTFS_ATTR_CONTEXT BitmapContext;
+    PCHAR *BitmapMem;
+    ULONG *BitmapPtr;
+    RTL_BITMAP  Bitmap;
 
     DPRINT("BrowseIndexEntries(%p, %p, %p, %lu, %p, %p, %wZ, %lu, %lu, %s, %s, %p)\n",
            Vcb,
@@ -2257,10 +2964,100 @@ BrowseIndexEntries(PDEVICE_EXTENSION Vcb,
            CaseSensitive ? "TRUE" : "FALSE",
            OutMFTIndex);
 
+    // Find the $I30 index allocation, if there is one
+    Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationContext, NULL);
+    if (NT_SUCCESS(Status))
+    {
+        ULONGLONG BitmapLength;
+        // Find the bitmap attribute for the index
+        Status = FindAttribute(Vcb, MftRecord, AttributeBitmap, L"$I30", 4, &BitmapContext, NULL);
+        if (!NT_SUCCESS(Status))
+        {
+            DPRINT1("Potential file system corruption detected!\n");
+            ReleaseAttributeContext(IndexAllocationContext);
+            return Status;
+        }
+
+        // Get the length of the bitmap attribute
+        BitmapLength = AttributeDataLength(BitmapContext->pRecord);
+
+        // Allocate memory for the bitmap, including some padding; RtlInitializeBitmap() wants a pointer 
+        // that's ULONG-aligned, and it wants the size of the memory allocated for it to be a ULONG-multiple.
+        BitmapMem = ExAllocatePoolWithTag(NonPagedPool, BitmapLength + sizeof(ULONG), TAG_NTFS);
+        if (!BitmapMem)
+        {
+            DPRINT1("Error: failed to allocate bitmap!");
+            ReleaseAttributeContext(BitmapContext);
+            ReleaseAttributeContext(IndexAllocationContext);
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+
+        RtlZeroMemory(BitmapMem, BitmapLength + sizeof(ULONG));
+
+        // RtlInitializeBitmap() wants a pointer that's ULONG-aligned.
+        BitmapPtr = (PULONG)ALIGN_UP_BY((ULONG_PTR)BitmapMem, sizeof(ULONG));
+
+        // Read the existing bitmap data
+        Status = ReadAttribute(Vcb, BitmapContext, 0, (PCHAR)BitmapPtr, BitmapLength);
+        if (!NT_SUCCESS(Status))
+        {
+            DPRINT1("ERROR: Failed to read bitmap attribute!\n");
+            ExFreePoolWithTag(BitmapMem, TAG_NTFS);
+            ReleaseAttributeContext(BitmapContext);
+            ReleaseAttributeContext(IndexAllocationContext);
+            return Status;
+        }
+
+        // Initialize bitmap
+        RtlInitializeBitMap(&Bitmap, BitmapPtr, BitmapLength * 8);
+    }
+    else
+    {
+        // Couldn't find an index allocation
+        IndexAllocationContext = NULL;
+    }
+    
+
+    // Loop through all Index Entries of index, starting with FirstEntry
     IndexEntry = FirstEntry;
-    while (IndexEntry < LastEntry &&
-           !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
+    while (IndexEntry <= LastEntry)
     {
+        // Does IndexEntry have a sub-node?
+        if (IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)
+        {
+            if (!(IndexRecord->Header.Flags & INDEX_ROOT_LARGE) || !IndexAllocationContext)
+            {
+                DPRINT1("Filesystem corruption detected!\n");
+            }
+            else
+            {
+                Status = BrowseSubNodeIndexEntries(Vcb,
+                                                   MftRecord,
+                                                   IndexBlockSize,
+                                                   FileName,
+                                                   IndexAllocationContext,
+                                                   &Bitmap,
+                                                   GetIndexEntryVCN(IndexEntry),
+                                                   StartEntry,
+                                                   CurrentEntry,
+                                                   DirSearch,
+                                                   CaseSensitive,
+                                                   OutMFTIndex);
+                if (NT_SUCCESS(Status))
+                {
+                    ExFreePoolWithTag(BitmapMem, TAG_NTFS);
+                    ReleaseAttributeContext(BitmapContext);
+                    ReleaseAttributeContext(IndexAllocationContext);
+                    return Status;
+                }
+            }
+        }
+
+        // Are we done?
+        if (IndexEntry->Flags & NTFS_INDEX_ENTRY_END)
+            break;
+
+        // If we've found a file whose index is greater than or equal to StartEntry that matches the search criteria
         if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) >= NTFS_FILE_FIRST_USER_FILE &&
             *CurrentEntry >= *StartEntry &&
             IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
@@ -2268,71 +3065,29 @@ BrowseIndexEntries(PDEVICE_EXTENSION Vcb,
         {
             *StartEntry = *CurrentEntry;
             *OutMFTIndex = (IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK);
+            if (IndexAllocationContext)
+            {
+                ExFreePoolWithTag(BitmapMem, TAG_NTFS);
+                ReleaseAttributeContext(BitmapContext);
+                ReleaseAttributeContext(IndexAllocationContext);
+            }
             return STATUS_SUCCESS;
         }
 
+        // Advance to the next index entry
         (*CurrentEntry) += 1;
         ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
         IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
     }
 
-    /* If we're already browsing a subnode */
-    if (IndexRecord == NULL)
-    {
-        return STATUS_OBJECT_PATH_NOT_FOUND;
-    }
-
-    /* If there's no subnode */
-    if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE))
-    {
-        return STATUS_OBJECT_PATH_NOT_FOUND; 
-    }
-
-    Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL);
-    if (!NT_SUCCESS(Status))
-    {
-        DPRINT1("Corrupted filesystem!\n");
-        return Status;
-    }
-
-    IndexAllocationSize = AttributeDataLength(&IndexAllocationCtx->Record);
-    Status = STATUS_OBJECT_PATH_NOT_FOUND;
-    for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize)
+    if (IndexAllocationContext)
     {
-        ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize);
-        Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
-        if (!NT_SUCCESS(Status))
-        {
-            break;
-        }
-
-        IndexBuffer = (PINDEX_BUFFER)IndexRecord;
-        ASSERT(IndexBuffer->Ntfs.Type == NRH_INDX_TYPE);
-        ASSERT(IndexBuffer->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize);
-        FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.FirstEntryOffset);
-        LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.TotalSizeOfEntries);
-        ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexBuffer + IndexBlockSize));
-
-        Status = BrowseIndexEntries(NULL,
-                                    NULL,
-                                    NULL,
-                                    0,
-                                    FirstEntry,
-                                    LastEntry,
-                                    FileName,
-                                    StartEntry,
-                                    CurrentEntry,
-                                    DirSearch,
-                                    CaseSensitive,
-                                    OutMFTIndex);
-        if (NT_SUCCESS(Status))
-        {
-            break;
-        }
+        ExFreePoolWithTag(BitmapMem, TAG_NTFS);
+        ReleaseAttributeContext(BitmapContext);
+        ReleaseAttributeContext(IndexAllocationContext);
     }
 
-    ReleaseAttributeContext(IndexAllocationCtx);
-    return Status;    
+    return STATUS_OBJECT_PATH_NOT_FOUND;
 }
 
 NTSTATUS
@@ -2403,7 +3158,7 @@ NtfsFindMftRecord(PDEVICE_EXTENSION Vcb,
 
     Status = BrowseIndexEntries(Vcb,
                                 MftRecord,
-                                IndexRecord,
+                                (PINDEX_ROOT_ATTRIBUTE)IndexRecord,
                                 IndexRoot->SizeOfEntry,
                                 IndexEntry,
                                 IndexEntryEnd,
@@ -2488,6 +3243,24 @@ NtfsLookupFile(PDEVICE_EXTENSION Vcb,
     return NtfsLookupFileAt(Vcb, PathName, CaseSensitive, FileRecord, MFTIndex, NTFS_FILE_ROOT);
 }
 
+void
+NtfsDumpData(ULONG_PTR Buffer, ULONG Length)
+{
+    ULONG i, j;
+
+    // dump binary data, 8 bytes at a time
+    for (i = 0; i < Length; i += 8)
+    {
+        // display current offset, in hex
+        DbgPrint("\t%03x\t", i);
+
+        // display hex value of each of the next 8 bytes
+        for (j = 0; j < 8; j++)
+            DbgPrint("%02x ", *(PUCHAR)(Buffer + i + j));
+        DbgPrint("\n");
+    }
+}
+
 /**
 * @name NtfsDumpFileRecord
 * @implemented