[NTFS] Correctly find attributes stored in another file record in MFT (and referenced...
[reactos.git] / drivers / filesystems / ntfs / mft.c
index 87820e6..e1411f0 100644 (file)
@@ -30,6 +30,7 @@
 /* INCLUDES *****************************************************************/
 
 #include "ntfs.h"
+#include <ntintsafe.h>
 
 #define NDEBUG
 #include <debug.h>
@@ -41,16 +42,31 @@ PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord)
 {
     PNTFS_ATTR_CONTEXT Context;
 
-    Context = ExAllocatePoolWithTag(NonPagedPool,
-                                    FIELD_OFFSET(NTFS_ATTR_CONTEXT, Record) + AttrRecord->Length,
-                                    TAG_NTFS);
-    RtlCopyMemory(&Context->Record, AttrRecord, AttrRecord->Length);
+    Context = ExAllocateFromNPagedLookasideList(&NtfsGlobalData->AttrCtxtLookasideList);
+    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");
+        ExFreeToNPagedLookasideList(&NtfsGlobalData->AttrCtxtLookasideList, Context);
+        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,7 +90,8 @@ PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord)
         if (!NT_SUCCESS(ConvertDataRunsToLargeMCB(DataRun, &Context->DataRunsMCB, &NextVBN)))
         {
             DPRINT1("Unable to convert data runs to MCB!\n");
-            ExFreePoolWithTag(Context, TAG_NTFS);
+            ExFreePoolWithTag(Context->pRecord, TAG_NTFS);
+            ExFreeToNPagedLookasideList(&NtfsGlobalData->AttrCtxtLookasideList, Context);
             return NULL;
         }
     }
@@ -86,12 +103,17 @@ PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord)
 VOID
 ReleaseAttributeContext(PNTFS_ATTR_CONTEXT Context)
 {
-    if (Context->Record.IsNonResident)
+    if (Context->pRecord)
     {
-        FsRtlUninitializeLargeMcb(&Context->DataRunsMCB);
+        if (Context->pRecord->IsNonResident)
+        {
+            FsRtlUninitializeLargeMcb(&Context->DataRunsMCB);
+        }
+
+        ExFreePoolWithTag(Context->pRecord, TAG_NTFS);
     }
 
-    ExFreePoolWithTag(Context, TAG_NTFS);
+    ExFreeToNPagedLookasideList(&NtfsGlobalData->AttrCtxtLookasideList, Context);
 }
 
 
@@ -118,6 +140,7 @@ FindAttribute(PDEVICE_EXTENSION Vcb,
     NTSTATUS Status;
     FIND_ATTR_CONTXT Context;
     PNTFS_ATTR_RECORD Attribute;
+    PNTFS_ATTRIBUTE_LIST_ITEM AttrListItem;
 
     DPRINT("FindAttribute(%p, %p, 0x%x, %S, %lu, %p, %p)\n", Vcb, MftRecord, Type, Name, NameLength, AttrCtx, Offset);
 
@@ -162,6 +185,61 @@ FindAttribute(PDEVICE_EXTENSION Vcb,
         Status = FindNextAttribute(&Context, &Attribute);
     }
 
+    /* No attribute found, check if it is referenced in another file record */
+    Status = FindFirstAttributeListItem(&Context, &AttrListItem);
+    while (NT_SUCCESS(Status))
+    {
+        if (AttrListItem->Type == Type && AttrListItem->NameLength == NameLength)
+        {
+            if (NameLength != 0)
+            {
+                PWCHAR AttrName;
+
+                AttrName = (PWCHAR)((PCHAR)AttrListItem + AttrListItem->NameOffset);
+                DPRINT("%.*S, %.*S\n", AttrListItem->NameLength, AttrName, NameLength, Name);
+                if (RtlCompareMemory(AttrName, Name, NameLength * sizeof(WCHAR)) == (NameLength  * sizeof(WCHAR)))
+                {
+                    Found = TRUE;
+                }
+            }
+            else
+            {
+                Found = TRUE;
+            }
+
+            if (Found == TRUE)
+            {
+                /* Get the MFT Index of attribute */
+                ULONGLONG MftIndex;
+                PFILE_RECORD_HEADER RemoteHdr;
+
+                MftIndex = AttrListItem->MFTIndex & NTFS_MFT_MASK;
+                RemoteHdr = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList);
+
+                if (RemoteHdr == NULL)
+                {
+                    FindCloseAttribute(&Context);
+                    return STATUS_INSUFFICIENT_RESOURCES;
+                }
+
+                /* Check we are not reading ourselves */
+                if (MftRecord->MFTRecordNumber == MftIndex)
+                {
+                    DPRINT1("Attribute list references missing attribute to this file entry !");
+                    ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, RemoteHdr);
+                    FindCloseAttribute(&Context);
+                    return STATUS_OBJECT_NAME_NOT_FOUND;
+                }
+                /* Read the new file record */
+                ReadFileRecord(Vcb, MftIndex, RemoteHdr);
+                Status = FindAttribute(Vcb, RemoteHdr, Type, Name, NameLength, AttrCtx, Offset);
+                ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, RemoteHdr);
+                FindCloseAttribute(&Context);
+                return Status;
+            }
+        }
+        Status = FindNextAttributeListItem(&Context, &AttrListItem);
+    }
     FindCloseAttribute(&Context);
     return STATUS_OBJECT_NAME_NOT_FOUND;
 }
@@ -173,7 +251,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 +285,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 +296,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 +317,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");
+        ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
         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 +364,7 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
     if (!BitmapBuffer)
     {
         DPRINT1("ERROR: Unable to allocate memory for bitmap attribute!\n");
+        ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
         ExReleaseResourceLite(&(Vcb->DirResource));
         ReleaseAttributeContext(BitmapContext);
         return STATUS_INSUFFICIENT_RESOURCES;
@@ -279,6 +382,7 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
     if (BytesRead != BitmapSize.LowPart)
     {
         DPRINT1("ERROR: Bytes read != Bitmap size!\n");
+        ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
         ExReleaseResourceLite(&(Vcb->DirResource));
         ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
         ReleaseAttributeContext(BitmapContext);
@@ -290,18 +394,30 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
     if (!NT_SUCCESS(Status))
     {
         DPRINT1("ERROR: Failed to set size of $MFT data attribute!\n");
+        ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
         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");
+        ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
+        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 +425,7 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
         if (!NT_SUCCESS(Status))
         {
             DPRINT1("ERROR: Failed to set size of bitmap attribute!\n");
+            ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
             ExReleaseResourceLite(&(Vcb->DirResource));
             ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
             ReleaseAttributeContext(BitmapContext);
@@ -316,13 +433,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");
+        ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
         ExReleaseResourceLite(&(Vcb->DirResource));
         ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
         ReleaseAttributeContext(BitmapContext);
@@ -330,9 +448,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))
     {
+        ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
         ExReleaseResourceLite(&(Vcb->DirResource));
         ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
         ReleaseAttributeContext(BitmapContext);
@@ -340,48 +459,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");
+            ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
+            ExReleaseResourceLite(&(Vcb->DirResource));
+            ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
+            ReleaseAttributeContext(BitmapContext);
+            return Status;
+        }
+    }
+
+    // Update the mft mirror
+    Status = UpdateMftMirror(Vcb);
+
     // Cleanup
+    ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
     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)
     {
-        USHORT Padding = ATTR_RECORD_ALIGNMENT - (NextAttributeOffset % ATTR_RECORD_ALIGNMENT);
-        NextAttributeOffset += Padding;
-        AttrContext->Record.Length += Padding;
-        Destination->Length += Padding;
+        // 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);
     }
-    
-    // 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);
+
+    // Are there attributes after this one that need to be moved?
+    if (NextAttribute->Type != AttributeEnd)
+    {
+        // Move the attributes after this one
+        FinalAttribute = MoveAttributes(DeviceExt, NextAttribute, NextAttributeOffset, (ULONG_PTR)Destination + Destination->Length);
+    }
+    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 +621,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 +630,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 +639,7 @@ SetAttributeDataLength(PFILE_OBJECT FileObject,
         }
     }
 
-    if (AttrContext->Record.IsNonResident)
+    if (AttrContext->pRecord->IsNonResident)
     {
         Status = SetNonResidentAttributeDataLength(Fcb->Vcb,
                                                    AttrContext,
@@ -448,8 +670,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 +707,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 +763,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 +782,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 +792,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 +826,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 +835,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 +897,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 +918,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 +951,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);
+
+                // 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 + length in bytes of the name
-                AttrContext->Record.NonResident.MappingPairsOffset = Destination->NonResident.MappingPairsOffset = 0x40 + (Destination->NameLength * 2);
+                // 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,
@@ -750,12 +1002,14 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
                 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 
                 {
                     DPRINT1("Unable to create LargeMcb!\n");
-                    ExFreePoolWithTag(AttribData, TAG_NTFS);
+                    if (AttribDataSize.QuadPart > 0)
+                        ExFreePoolWithTag(AttribData, TAG_NTFS);
+                    ExFreePoolWithTag(NewRecord, TAG_NTFS);
                     _SEH2_YIELD(return _SEH2_GetExceptionCode());
                 } _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);
@@ -764,9 +1018,14 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
                     DPRINT1("ERROR: Couldn't update file record to continue migration!\n");
                     if (AttribDataSize.QuadPart > 0)
                         ExFreePoolWithTag(AttribData, TAG_NTFS);
+                    ExFreePoolWithTag(NewRecord, TAG_NTFS);
                     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))
@@ -780,7 +1039,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");
@@ -794,19 +1053,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;
 }
@@ -831,13 +1081,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;
     }
 
@@ -866,6 +1122,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;
@@ -901,6 +1161,7 @@ ReadAttribute(PDEVICE_EXTENSION Vcb,
 
             if (*DataRun == 0)
             {
+                ExFreePoolWithTag(TempBuffer, TAG_NTFS);
                 return AlreadyRead;
             }
 
@@ -995,7 +1256,7 @@ ReadAttribute(PDEVICE_EXTENSION Vcb,
     } /* if Disk */
 
     // TEMPTEMP
-    if (Context->Record.IsNonResident)
+    if (Context->pRecord->IsNonResident)
         ExFreePoolWithTag(TempBuffer, TAG_NTFS);
 
     Context->CacheRun = DataRun;
@@ -1035,6 +1296,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.
@@ -1050,7 +1317,8 @@ WriteAttribute(PDEVICE_EXTENSION Vcb,
                ULONGLONG Offset,
                const PUCHAR Buffer,
                ULONG Length,
-               PULONG RealLengthWritten)
+               PULONG RealLengthWritten,
+               PFILE_RECORD_HEADER FileRecord)
 {
     ULONGLONG LastLCN;
     PUCHAR DataRun;
@@ -1062,72 +1330,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 = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList);
+            if (!FileRecord)
+            {
+                DPRINT1("Error: Couldn't allocate file record!\n");
+                return STATUS_NO_MEMORY;
+            }
 
-        // read the file record
-        ReadFileRecord(Vcb, Context->FileMFTIndex, FileRecord);
+            FileRecordAllocated = TRUE;
+
+            // 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)
+                ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, FileRecord);
             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)
+                ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, FileRecord);
             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)
+            ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, FileRecord);
 
         if (NT_SUCCESS(Status))
             *RealLengthWritten = Length;
@@ -1156,7 +1442,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,
@@ -1181,7 +1471,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?
@@ -1198,7 +1489,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;
@@ -1232,7 +1524,7 @@ WriteAttribute(PDEVICE_EXTENSION Vcb,
         Context->CacheRunLastLCN = LastLCN;
         Context->CacheRunCurrentOffset = CurrentOffset;
 
-        return Status;
+        goto Cleanup;
     }
 
     Length -= WriteLength;
@@ -1265,7 +1557,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
         {
@@ -1296,7 +1589,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;
@@ -1318,10 +1612,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;
@@ -1329,6 +1619,11 @@ WriteAttribute(PDEVICE_EXTENSION Vcb,
     Context->CacheRunLastLCN = LastLCN;
     Context->CacheRunCurrentOffset = CurrentOffset;
 
+Cleanup:
+    // TEMPTEMP
+    if (Context->pRecord->IsNonResident)
+        ExFreePoolWithTag(TempBuffer, TAG_NTFS);
+
     return Status;
 }
 
@@ -1387,9 +1682,7 @@ UpdateFileNameRecord(PDEVICE_EXTENSION Vcb,
            NewAllocationSize,
            CaseSensitive ? "TRUE" : "FALSE");
 
-    MftRecord = ExAllocatePoolWithTag(NonPagedPool,
-                                      Vcb->NtfsInfo.BytesPerFileRecord,
-                                      TAG_NTFS);
+    MftRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList);
     if (MftRecord == NULL)
     {
         return STATUS_INSUFFICIENT_RESOURCES;
@@ -1398,7 +1691,7 @@ UpdateFileNameRecord(PDEVICE_EXTENSION Vcb,
     Status = ReadFileRecord(Vcb, ParentMFTIndex, MftRecord);
     if (!NT_SUCCESS(Status))
     {
-        ExFreePoolWithTag(MftRecord, TAG_NTFS);
+        ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
         return Status;
     }
 
@@ -1406,7 +1699,7 @@ UpdateFileNameRecord(PDEVICE_EXTENSION Vcb,
     Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL);
     if (!NT_SUCCESS(Status))
     {
-        ExFreePoolWithTag(MftRecord, TAG_NTFS);
+        ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
         return Status;
     }
 
@@ -1414,17 +1707,18 @@ UpdateFileNameRecord(PDEVICE_EXTENSION Vcb,
     if (IndexRecord == NULL)
     {
         ReleaseAttributeContext(IndexRootCtx);
-        ExFreePoolWithTag(MftRecord, TAG_NTFS);
+        ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
         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");
         ExFreePoolWithTag(IndexRecord, TAG_NTFS);
         ReleaseAttributeContext(IndexRootCtx);
-        ExFreePoolWithTag(MftRecord, TAG_NTFS);
+        ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
+        return Status;
     }
 
     IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord;
@@ -1452,7 +1746,7 @@ UpdateFileNameRecord(PDEVICE_EXTENSION Vcb,
     {
         // we need to write the index root attribute back to disk
         ULONG LengthWritten;
-        Status = WriteAttribute(Vcb, IndexRootCtx, 0, 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");
@@ -1462,7 +1756,7 @@ UpdateFileNameRecord(PDEVICE_EXTENSION Vcb,
 
     ReleaseAttributeContext(IndexRootCtx);
     ExFreePoolWithTag(IndexRecord, TAG_NTFS);
-    ExFreePoolWithTag(MftRecord, TAG_NTFS);
+    ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
 
     return Status;
 }
@@ -1514,7 +1808,7 @@ UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb,
     while (IndexEntry < LastEntry &&
            !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
     {
-        if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) > 0x10 &&
+        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))
@@ -1550,7 +1844,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)
     {
@@ -1594,7 +1888,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");
@@ -1647,7 +1941,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))
     {
@@ -1730,6 +2030,7 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
     ULONGLONG BitmapDataSize;
     ULONGLONG AttrBytesRead;
     PUCHAR BitmapData;
+    PUCHAR BitmapBuffer;
     ULONG LengthWritten;
     PNTFS_ATTR_CONTEXT BitmapContext;
     LARGE_INTEGER BitmapBits;
@@ -1738,6 +2039,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))
     {
@@ -1745,22 +2048,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, BitmapData, BitmapDataSize);
+    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;
     }
@@ -1771,12 +2081,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;
     }
@@ -1790,7 +2101,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
@@ -1815,11 +2126,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;
     }
@@ -1830,70 +2141,515 @@ 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;
 }
 
+/**
+* @name NtfsAddFilenameToDirectory
+* @implemented
+*
+* Adds a $FILE_NAME attribute to a given directory index.
+*
+* @param DeviceExt
+* Points to the target disk's DEVICE_EXTENSION.
+*
+* @param DirectoryMftIndex
+* Mft index of the parent directory which will receive the file.
+*
+* @param FileReferenceNumber
+* File reference of the file to be added to the directory. This is a combination of the
+* Mft index and sequence number.
+*
+* @param FilenameAttribute
+* Pointer to the FILENAME_ATTRIBUTE of the file being added to the directory.
+*
+* @param CaseSensitive
+* Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
+* if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag.
+*
+* @return
+* STATUS_SUCCESS on success.
+* STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
+* STATUS_NOT_IMPLEMENTED if target address isn't at the end of the given file record.
+*
+* @remarks
+* WIP - Can only support a few files in a directory.
+* One FILENAME_ATTRIBUTE is added to the directory's index for each link to that file. So, each
+* file which contains one FILENAME_ATTRIBUTE for a long name and another for the 8.3 name, will
+* get both attributes added to its parent directory.
+*/
 NTSTATUS
-AddFixupArray(PDEVICE_EXTENSION Vcb,
-              PNTFS_RECORD_HEADER Record)
+NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
+                           ULONGLONG DirectoryMftIndex,
+                           ULONGLONG FileReferenceNumber,
+                           PFILENAME_ATTRIBUTE FilenameAttribute,
+                           BOOLEAN CaseSensitive)
 {
-    USHORT *pShortToFixUp;
-    ULONG ArrayEntryCount = Record->UsaCount - 1;
-    ULONG Offset = Vcb->NtfsInfo.BytesPerSector - 2;
-    ULONG i;
+    NTSTATUS Status = STATUS_SUCCESS;
+    PFILE_RECORD_HEADER ParentFileRecord;
+    PNTFS_ATTR_CONTEXT IndexRootContext;
+    PINDEX_ROOT_ATTRIBUTE I30IndexRoot;
+    ULONG IndexRootOffset;
+    ULONGLONG I30IndexRootLength;
+    ULONG LengthWritten;
+    PINDEX_ROOT_ATTRIBUTE NewIndexRoot;
+    ULONG AttributeLength;
+    PNTFS_ATTR_RECORD NextAttribute;
+    PB_TREE NewTree;
+    ULONG BtreeIndexLength;
+    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 = ExAllocateFromNPagedLookasideList(&DeviceExt->FileRecLookasideList);
+    if (!ParentFileRecord)
+    {
+        DPRINT1("ERROR: Couldn't allocate memory for file record!\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
 
-    PFIXUP_ARRAY fixupArray = (PFIXUP_ARRAY)((UCHAR*)Record + Record->UsaOffset);
+    // Open the parent directory
+    Status = ReadFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord);
+    if (!NT_SUCCESS(Status))
+    {
+        ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
+        DPRINT1("ERROR: Couldn't read parent directory with index %I64u\n",
+                DirectoryMftIndex);
+        return Status;
+    }
 
-    DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb, Record, fixupArray->USN, ArrayEntryCount);
+#ifndef NDEBUG
+    DPRINT1("Dumping old parent file record:\n");
+    NtfsDumpFileRecord(DeviceExt, ParentFileRecord);
+#endif
 
-    fixupArray->USN++;
+    // Find the index root attribute for the directory
+    Status = FindAttribute(DeviceExt,
+                           ParentFileRecord,
+                           AttributeIndexRoot,
+                           L"$I30",
+                           4,
+                           &IndexRootContext,
+                           &IndexRootOffset);
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT1("ERROR: Couldn't find $I30 $INDEX_ROOT attribute for parent directory with MFT #: %I64u!\n",
+                DirectoryMftIndex);
+        ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
+        return Status;
+    }
 
-    for (i = 0; i < ArrayEntryCount; i++)
+    // 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
+    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)
     {
-        DPRINT("USN: %u\tOffset: %u\n", fixupArray->USN, Offset);
+        // 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);
+        }
 
-        pShortToFixUp = (USHORT*)((PCHAR)Record + Offset);
-        fixupArray->Array[i] = *pShortToFixUp;
-        *pShortToFixUp = fixupArray->USN;
-        Offset += Vcb->NtfsInfo.BytesPerSector;
+        // Leave room for the existing attributes
+        MaxIndexRootSize -= LengthOfAttributes;
     }
 
-    return STATUS_SUCCESS;
-}
+    // Allocate memory for the index root data
+    I30IndexRootLength = AttributeDataLength(IndexRootContext->pRecord);
+    I30IndexRoot = ExAllocatePoolWithTag(NonPagedPool, I30IndexRootLength, TAG_NTFS);
+    if (!I30IndexRoot)
+    {
+        DPRINT1("ERROR: Couldn't allocate memory for index root attribute!\n");
+        ReleaseAttributeContext(IndexRootContext);
+        ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
 
-NTSTATUS
-ReadLCN(PDEVICE_EXTENSION Vcb,
-        ULONGLONG lcn,
-        ULONG count,
-        PVOID buffer)
-{
-    LARGE_INTEGER DiskSector;
+    // Read the Index Root
+    Status = ReadAttribute(DeviceExt, IndexRootContext, 0, (PCHAR)I30IndexRoot, I30IndexRootLength);
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT1("ERROR: Couln't read index root attribute for Mft index #%I64u\n", DirectoryMftIndex);
+        ReleaseAttributeContext(IndexRootContext);
+        ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
+        ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
+        return Status;
+    }
 
-    DiskSector.QuadPart = lcn;
+    // Convert the index to a B*Tree
+    Status = CreateBTreeFromIndex(DeviceExt,
+                                  ParentFileRecord,
+                                  IndexRootContext,
+                                  I30IndexRoot,
+                                  &NewTree);
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT1("ERROR: Failed to create B-Tree from Index!\n");
+        ReleaseAttributeContext(IndexRootContext);
+        ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
+        ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
+        return Status;
+    }
 
-    return NtfsReadSectors(Vcb->StorageDevice,
-                           DiskSector.u.LowPart * Vcb->NtfsInfo.SectorsPerCluster,
-                           count * Vcb->NtfsInfo.SectorsPerCluster,
-                           Vcb->NtfsInfo.BytesPerSector,
-                           buffer,
-                           FALSE);
-}
+#ifndef NDEBUG
+    DumpBTree(NewTree);
+#endif
 
+    // Insert the key for the file we're adding
+    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");
+        DestroyBTree(NewTree);
+        ReleaseAttributeContext(IndexRootContext);
+        ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
+        ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
+        return Status;
+    }
 
-BOOLEAN
-CompareFileName(PUNICODE_STRING FileName,
-                PINDEX_ENTRY_ATTRIBUTE IndexEntry,
-                BOOLEAN DirSearch,
+#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);
+        ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
+        return Status;
+    }
+
+    // Update the index allocation
+    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);
+        ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
+        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);
+            ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
+            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);
+            ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
+            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, NewMaxIndexRootSize, &NewIndexRoot, &BtreeIndexLength);
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT1("ERROR: Failed to create Index root from B-Tree!\n");
+        DestroyBTree(NewTree);
+        ReleaseAttributeContext(IndexRootContext);
+        ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
+        ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
+        return Status;
+    }
+
+    // We're done with the B-Tree now
+    DestroyBTree(NewTree);
+
+    // Write back the new index root attribute to the parent directory file record
+
+    // 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 $INDEX_ROOT must always be resident.
+    AttributeLength = NewIndexRoot->Header.AllocatedSize + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header);
+    
+    if (AttributeLength != IndexRootContext->pRecord->Resident.ValueLength)
+    {
+        // 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))
+        {
+            ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
+            ReleaseAttributeContext(IndexRootContext);
+            ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
+            ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
+            DPRINT1("ERROR: Unable to set resident attribute length!\n");
+            return Status;
+        }
+
+    }
+
+    NT_ASSERT(ParentFileRecord->BytesInUse <= DeviceExt->NtfsInfo.BytesPerFileRecord);
+
+    Status = UpdateFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord);
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT1("ERROR: Failed to update file record of directory with index: %llx\n", DirectoryMftIndex);
+        ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
+        ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
+        ReleaseAttributeContext(IndexRootContext);
+        ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
+        return Status;
+    }
+
+    // Write the new index root to disk
+    Status = WriteAttribute(DeviceExt,
+                            IndexRootContext,
+                            0,
+                            (PUCHAR)NewIndexRoot,
+                            AttributeLength,
+                            &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);
+        ReleaseAttributeContext(IndexRootContext);
+        ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
+        ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
+        return Status;
+    }
+
+    // re-read the parent file record, so we can dump it
+    Status = ReadFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord);
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT1("ERROR: Couldn't read parent directory after messing with it!\n");
+    }
+    else
+    {
+#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
+    ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
+    ReleaseAttributeContext(IndexRootContext);
+    ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
+    ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
+
+    return Status;
+}
+
+NTSTATUS
+AddFixupArray(PDEVICE_EXTENSION Vcb,
+              PNTFS_RECORD_HEADER Record)
+{
+    USHORT *pShortToFixUp;
+    ULONG ArrayEntryCount = Record->UsaCount - 1;
+    ULONG Offset = Vcb->NtfsInfo.BytesPerSector - 2;
+    ULONG i;
+
+    PFIXUP_ARRAY fixupArray = (PFIXUP_ARRAY)((UCHAR*)Record + Record->UsaOffset);
+
+    DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb, Record, fixupArray->USN, ArrayEntryCount);
+
+    fixupArray->USN++;
+
+    for (i = 0; i < ArrayEntryCount; i++)
+    {
+        DPRINT("USN: %u\tOffset: %u\n", fixupArray->USN, Offset);
+
+        pShortToFixUp = (USHORT*)((PCHAR)Record + Offset);
+        fixupArray->Array[i] = *pShortToFixUp;
+        *pShortToFixUp = fixupArray->USN;
+        Offset += Vcb->NtfsInfo.BytesPerSector;
+    }
+
+    return STATUS_SUCCESS;
+}
+
+NTSTATUS
+ReadLCN(PDEVICE_EXTENSION Vcb,
+        ULONGLONG lcn,
+        ULONG count,
+        PVOID buffer)
+{
+    LARGE_INTEGER DiskSector;
+
+    DiskSector.QuadPart = lcn;
+
+    return NtfsReadSectors(Vcb->StorageDevice,
+                           DiskSector.u.LowPart * Vcb->NtfsInfo.SectorsPerCluster,
+                           count * Vcb->NtfsInfo.SectorsPerCluster,
+                           Vcb->NtfsInfo.BytesPerSector,
+                           buffer,
+                           FALSE);
+}
+
+
+BOOLEAN
+CompareFileName(PUNICODE_STRING FileName,
+                PINDEX_ENTRY_ATTRIBUTE IndexEntry,
+                BOOLEAN DirSearch,
                 BOOLEAN CaseSensitive)
 {
     BOOLEAN Ret, Alloc = FALSE;
@@ -1931,6 +2687,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 = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList);
+    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");
+        ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord);
+        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");
+        ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord);
+        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);
+        ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord);
+        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);
+        ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord);
+        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);
+        ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord);
+        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);
+    ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord);
+
+    return Status;
+}
+
 #if 0
 static
 VOID
@@ -1956,10 +2835,155 @@ 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 marked as deleted.\n", VCN);
+        return STATUS_DATA_ERROR;
+    }
+
+    // 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,
@@ -1971,11 +2995,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,
@@ -1991,82 +3016,130 @@ BrowseIndexEntries(PDEVICE_EXTENSION Vcb,
            CaseSensitive ? "TRUE" : "FALSE",
            OutMFTIndex);
 
-    IndexEntry = FirstEntry;
-    while (IndexEntry < LastEntry &&
-           !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
+    // Find the $I30 index allocation, if there is one
+    Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationContext, NULL);
+    if (NT_SUCCESS(Status))
     {
-        if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) >= 0x10 &&
-            *CurrentEntry >= *StartEntry &&
-            IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
-            CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive))
+        ULONGLONG BitmapLength;
+        // Find the bitmap attribute for the index
+        Status = FindAttribute(Vcb, MftRecord, AttributeBitmap, L"$I30", 4, &BitmapContext, NULL);
+        if (!NT_SUCCESS(Status))
         {
-            *StartEntry = *CurrentEntry;
-            *OutMFTIndex = (IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK);
-            return STATUS_SUCCESS;
+            DPRINT1("Potential file system corruption detected!\n");
+            ReleaseAttributeContext(IndexAllocationContext);
+            return Status;
         }
 
-        (*CurrentEntry) += 1;
-        ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
-        IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
-    }
+        // Get the length of the bitmap attribute
+        BitmapLength = AttributeDataLength(BitmapContext->pRecord);
 
-    /* If we're already browsing a subnode */
-    if (IndexRecord == NULL)
-    {
-        return STATUS_OBJECT_PATH_NOT_FOUND;
-    }
+        // 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;
+        }
 
-    /* If there's no subnode */
-    if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE))
-    {
-        return STATUS_OBJECT_PATH_NOT_FOUND; 
-    }
+        RtlZeroMemory(BitmapMem, BitmapLength + sizeof(ULONG));
 
-    Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL);
-    if (!NT_SUCCESS(Status))
+        // 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
     {
-        DPRINT1("Corrupted filesystem!\n");
-        return Status;
+        // Couldn't find an index allocation
+        IndexAllocationContext = NULL;
     }
+    
 
-    IndexAllocationSize = AttributeDataLength(&IndexAllocationCtx->Record);
-    Status = STATUS_OBJECT_PATH_NOT_FOUND;
-    for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize)
+    // Loop through all Index Entries of index, starting with FirstEntry
+    IndexEntry = FirstEntry;
+    while (IndexEntry <= LastEntry)
     {
-        ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize);
-        Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
-        if (!NT_SUCCESS(Status))
+        // Does IndexEntry have a sub-node?
+        if (IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)
         {
-            break;
+            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;
+                }
+            }
         }
 
-        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));
+        // Are we done?
+        if (IndexEntry->Flags & NTFS_INDEX_ENTRY_END)
+            break;
 
-        Status = BrowseIndexEntries(NULL,
-                                    NULL,
-                                    NULL,
-                                    0,
-                                    FirstEntry,
-                                    LastEntry,
-                                    FileName,
-                                    StartEntry,
-                                    CurrentEntry,
-                                    DirSearch,
-                                    CaseSensitive,
-                                    OutMFTIndex);
-        if (NT_SUCCESS(Status))
+        // 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))
         {
-            break;
+            *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);
     }
 
-    ReleaseAttributeContext(IndexAllocationCtx);
-    return Status;    
+    if (IndexAllocationContext)
+    {
+        ExFreePoolWithTag(BitmapMem, TAG_NTFS);
+        ReleaseAttributeContext(BitmapContext);
+        ReleaseAttributeContext(IndexAllocationContext);
+    }
+
+    return STATUS_OBJECT_PATH_NOT_FOUND;
 }
 
 NTSTATUS
@@ -2095,9 +3168,7 @@ NtfsFindMftRecord(PDEVICE_EXTENSION Vcb,
            CaseSensitive ? "TRUE" : "FALSE",
            OutMFTIndex);
 
-    MftRecord = ExAllocatePoolWithTag(NonPagedPool,
-                                      Vcb->NtfsInfo.BytesPerFileRecord,
-                                      TAG_NTFS);
+    MftRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList);
     if (MftRecord == NULL)
     {
         return STATUS_INSUFFICIENT_RESOURCES;
@@ -2106,7 +3177,7 @@ NtfsFindMftRecord(PDEVICE_EXTENSION Vcb,
     Status = ReadFileRecord(Vcb, MFTIndex, MftRecord);
     if (!NT_SUCCESS(Status))
     {
-        ExFreePoolWithTag(MftRecord, TAG_NTFS);
+        ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
         return Status;
     }
 
@@ -2114,7 +3185,7 @@ NtfsFindMftRecord(PDEVICE_EXTENSION Vcb,
     Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL);
     if (!NT_SUCCESS(Status))
     {
-        ExFreePoolWithTag(MftRecord, TAG_NTFS);
+        ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
         return Status;
     }
 
@@ -2122,7 +3193,7 @@ NtfsFindMftRecord(PDEVICE_EXTENSION Vcb,
     if (IndexRecord == NULL)
     {
         ReleaseAttributeContext(IndexRootCtx);
-        ExFreePoolWithTag(MftRecord, TAG_NTFS);
+        ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
         return STATUS_INSUFFICIENT_RESOURCES;
     }
 
@@ -2137,7 +3208,7 @@ NtfsFindMftRecord(PDEVICE_EXTENSION Vcb,
 
     Status = BrowseIndexEntries(Vcb,
                                 MftRecord,
-                                IndexRecord,
+                                (PINDEX_ROOT_ATTRIBUTE)IndexRecord,
                                 IndexRoot->SizeOfEntry,
                                 IndexEntry,
                                 IndexEntryEnd,
@@ -2149,7 +3220,7 @@ NtfsFindMftRecord(PDEVICE_EXTENSION Vcb,
                                 OutMFTIndex);
 
     ExFreePoolWithTag(IndexRecord, TAG_NTFS);
-    ExFreePoolWithTag(MftRecord, TAG_NTFS);
+    ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
 
     return Status;
 }
@@ -2192,7 +3263,7 @@ NtfsLookupFileAt(PDEVICE_EXTENSION Vcb,
         FsRtlDissectName(Current, &Current, &Remaining);
     }
 
-    *FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
+    *FileRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList);
     if (*FileRecord == NULL)
     {
         DPRINT("NtfsLookupFileAt: Can't allocate MFT record\n");
@@ -2203,7 +3274,7 @@ NtfsLookupFileAt(PDEVICE_EXTENSION Vcb,
     if (!NT_SUCCESS(Status))
     {
         DPRINT("NtfsLookupFileAt: Can't read MFT record\n");
-        ExFreePoolWithTag(*FileRecord, TAG_NTFS);
+        ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, *FileRecord);
         return Status;
     }
 
@@ -2222,6 +3293,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
@@ -2289,7 +3378,7 @@ NtfsFindFileAt(PDEVICE_EXTENSION Vcb,
         return Status;
     }
 
-    *FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
+    *FileRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList);
     if (*FileRecord == NULL)
     {
         DPRINT("NtfsFindFileAt: Can't allocate MFT record\n");
@@ -2300,7 +3389,7 @@ NtfsFindFileAt(PDEVICE_EXTENSION Vcb,
     if (!NT_SUCCESS(Status))
     {
         DPRINT("NtfsFindFileAt: Can't read MFT record\n");
-        ExFreePoolWithTag(*FileRecord, TAG_NTFS);
+        ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, *FileRecord);
         return Status;
     }