[NTFS]
[reactos.git] / drivers / filesystems / ntfs / attrib.c
index 793ca29..61d8c8a 100644 (file)
@@ -23,6 +23,7 @@
  * PROGRAMMERS:      Eric Kohl
  *                   Valentin Verkhovsky
  *                   HervĂ© Poussineau (hpoussin@reactos.org)
+ *                   Pierre Schweitzer (pierre@reactos.org)
  */
 
 /* INCLUDES *****************************************************************/
 
 /* FUNCTIONS ****************************************************************/
 
+/**
+* @name AddRun
+* @implemented
+*
+* Adds a run of allocated clusters to a non-resident attribute.
+*
+* @param Vcb
+* Pointer to an NTFS_VCB for the destination volume.
+*
+* @param AttrContext
+* Pointer to an NTFS_ATTR_CONTEXT describing the destination attribute.
+*
+* @param AttrOffset
+* Byte offset of the destination attribute relative to its file record.
+*
+* @param FileRecord
+* Pointer to a complete copy of the file record containing the destination attribute. Must be at least
+* Vcb->NtfsInfo.BytesPerFileRecord bytes long.
+*
+* @param NextAssignedCluster
+* Logical cluster number of the start of the data run being added.
+*
+* @param RunLength
+* How many clusters are in the data run being added. Can't be 0.
+*
+* @return
+* STATUS_SUCCESS on success. STATUS_INVALID_PARAMETER if AttrContext describes a resident attribute.
+* STATUS_INSUFFICIENT_RESOURCES if ConvertDataRunsToLargeMCB() fails.
+* STATUS_BUFFER_TOO_SMALL if ConvertLargeMCBToDataRuns() fails.
+* STATUS_NOT_IMPLEMENTED if we need to migrate the attribute to an attribute list (TODO).
+*
+* @remarks
+* Clusters should have been allocated previously with NtfsAllocateClusters().
+* 
+*
+*/
+NTSTATUS
+AddRun(PNTFS_VCB Vcb,
+       PNTFS_ATTR_CONTEXT AttrContext,
+       ULONG AttrOffset,
+       PFILE_RECORD_HEADER FileRecord,
+       ULONGLONG NextAssignedCluster,
+       ULONG RunLength)
+{
+    NTSTATUS Status;
+    PUCHAR DataRun = (PUCHAR)&AttrContext->Record + AttrContext->Record.NonResident.MappingPairsOffset;
+    int DataRunMaxLength;
+    PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
+    LARGE_MCB DataRunsMCB;
+    ULONG NextAttributeOffset = AttrOffset + AttrContext->Record.Length;
+    ULONGLONG NextVBN = AttrContext->Record.NonResident.LowestVCN;
+
+    // Allocate some memory for the RunBuffer
+    PUCHAR RunBuffer;
+    int RunBufferOffset = 0;
+
+    if (!AttrContext->Record.IsNonResident)
+        return STATUS_INVALID_PARAMETER;
+
+    RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
+
+    // Convert the data runs to a map control block
+    Status = ConvertDataRunsToLargeMCB(DataRun, &DataRunsMCB, &NextVBN);
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT1("Unable to convert data runs to MCB (probably ran out of memory)!\n");
+        ExFreePoolWithTag(RunBuffer, TAG_NTFS);
+        return Status;
+    }
+
+    // Add newly-assigned clusters to mcb
+    _SEH2_TRY{
+        if (!FsRtlAddLargeMcbEntry(&DataRunsMCB,
+        NextVBN,
+                                   NextAssignedCluster,
+                                   RunLength))
+        {
+            FsRtlUninitializeLargeMcb(&DataRunsMCB);
+            ExFreePoolWithTag(RunBuffer, TAG_NTFS);
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+    } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+        FsRtlUninitializeLargeMcb(&DataRunsMCB);
+        ExFreePoolWithTag(RunBuffer, TAG_NTFS);
+        _SEH2_YIELD(return STATUS_INSUFFICIENT_RESOURCES);
+    } _SEH2_END;
+
+
+    // Convert the map control block back to encoded data runs
+    ConvertLargeMCBToDataRuns(&DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferOffset);
+
+    // Get the amount of free space between the start of the of the first data run and the attribute end
+    DataRunMaxLength = AttrContext->Record.Length - AttrContext->Record.NonResident.MappingPairsOffset;
+
+    // Do we need to extend the attribute (or convert to attribute list)?
+    if (DataRunMaxLength < RunBufferOffset)
+    {
+        PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset);
+        DataRunMaxLength += Vcb->NtfsInfo.BytesPerFileRecord - NextAttributeOffset - (sizeof(ULONG) * 2);
+
+        // Can we move the end of the attribute?
+        if (NextAttribute->Type != AttributeEnd || DataRunMaxLength < RunBufferOffset - 1)
+        {
+            DPRINT1("FIXME: Need to create attribute list! Max Data Run Length available: %d\n", DataRunMaxLength);
+            if (NextAttribute->Type != AttributeEnd)
+                DPRINT1("There's another attribute after this one with type %0xlx\n", NextAttribute->Type);
+            ExFreePoolWithTag(RunBuffer, TAG_NTFS);
+            FsRtlUninitializeLargeMcb(&DataRunsMCB);
+            return STATUS_NOT_IMPLEMENTED;
+        }
+
+        // calculate position of end markers
+        NextAttributeOffset = AttrOffset + AttrContext->Record.NonResident.MappingPairsOffset + RunBufferOffset;
+        NextAttributeOffset = ALIGN_UP_BY(NextAttributeOffset, 8);
+
+        // Write the end markers
+        NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset);
+        NextAttribute->Type = AttributeEnd;
+        NextAttribute->Length = FILE_RECORD_END;
+
+        // Update the length
+        DestinationAttribute->Length = NextAttributeOffset - AttrOffset;
+        AttrContext->Record.Length = DestinationAttribute->Length;
+
+        // We need to increase the FileRecord size
+        FileRecord->BytesInUse = NextAttributeOffset + (sizeof(ULONG) * 2);
+    }
+
+    // NOTE: from this point on the original attribute record will contain invalid data in it's runbuffer
+    // TODO: Elegant fix? Could we free the old Record and allocate a new one without issue?
+
+    // Update HighestVCN
+    DestinationAttribute->NonResident.HighestVCN =
+    AttrContext->Record.NonResident.HighestVCN = max(NextVBN - 1 + RunLength, 
+                                                     AttrContext->Record.NonResident.HighestVCN);
+
+    // Write data runs to destination attribute
+    RtlCopyMemory((PVOID)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), 
+                  RunBuffer, 
+                  RunBufferOffset);
+
+    // Update the file record
+    Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord);
+
+    ExFreePoolWithTag(RunBuffer, TAG_NTFS);
+    FsRtlUninitializeLargeMcb(&DataRunsMCB);
+
+    NtfsDumpDataRuns((PUCHAR)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), 0);
+
+    return Status;
+}
+
+/**
+* @name ConvertDataRunsToLargeMCB
+* @implemented
+*
+* Converts binary data runs to a map control block.
+*
+* @param DataRun
+* Pointer to the run data
+*
+* @param DataRunsMCB
+* Pointer to an unitialized LARGE_MCB structure.
+*
+* @return
+* STATUS_SUCCESS on success, STATUS_INSUFFICIENT_RESOURCES if we fail to
+* initialize the mcb or add an entry.
+*
+* @remarks
+* Initializes the LARGE_MCB pointed to by DataRunsMCB. If this function succeeds, you
+* need to call FsRtlUninitializeLargeMcb() when you're done with DataRunsMCB. This
+* function will ensure the LargeMCB has been unitialized in case of failure.
+*
+*/
+NTSTATUS
+ConvertDataRunsToLargeMCB(PUCHAR DataRun,
+                          PLARGE_MCB DataRunsMCB,
+                          PULONGLONG pNextVBN)
+{
+    LONGLONG  DataRunOffset;
+    ULONGLONG DataRunLength;
+    LONGLONG  DataRunStartLCN;
+    ULONGLONG NextCluster;
+
+    ULONGLONG LastLCN = 0;
+
+    // Initialize the MCB, potentially catch an exception
+    _SEH2_TRY{
+        FsRtlInitializeLargeMcb(DataRunsMCB, NonPagedPool);
+    } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+        _SEH2_YIELD(return STATUS_INSUFFICIENT_RESOURCES);
+    } _SEH2_END;
+
+    while (*DataRun != 0)
+    {
+        DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
+
+        if (DataRunOffset != -1)
+        {
+            // Normal data run.
+            DataRunStartLCN = LastLCN + DataRunOffset;
+            LastLCN = DataRunStartLCN;
+            NextCluster = LastLCN + DataRunLength;
+
+
+            _SEH2_TRY{
+                if (!FsRtlAddLargeMcbEntry(DataRunsMCB,
+                                           *pNextVBN,
+                                           DataRunStartLCN,
+                                           DataRunLength))
+                {
+                    FsRtlUninitializeLargeMcb(DataRunsMCB);
+                    return STATUS_INSUFFICIENT_RESOURCES;
+                }
+            } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+                FsRtlUninitializeLargeMcb(DataRunsMCB);
+                _SEH2_YIELD(return STATUS_INSUFFICIENT_RESOURCES);
+            } _SEH2_END;
+
+        }
+
+        *pNextVBN += DataRunLength;
+    }
+
+    return STATUS_SUCCESS;
+}
+
+/**
+* @name ConvertLargeMCBToDataRuns
+* @implemented
+*
+* Converts a map control block to a series of encoded data runs (used by non-resident attributes).
+*
+* @param DataRunsMCB
+* Pointer to a LARGE_MCB structure describing the data runs.
+*
+* @param RunBuffer
+* Pointer to the buffer that will receive the encoded data runs.
+*
+* @param MaxBufferSize
+* Size of RunBuffer, in bytes.
+*
+* @param UsedBufferSize
+* Pointer to a ULONG that will receive the size of the data runs in bytes. Can't be NULL.
+*
+* @return
+* STATUS_SUCCESS on success, STATUS_BUFFER_TOO_SMALL if RunBuffer is too small to contain the
+* complete output.
+*
+*/
+NTSTATUS
+ConvertLargeMCBToDataRuns(PLARGE_MCB DataRunsMCB,
+                          PUCHAR RunBuffer,
+                          ULONG MaxBufferSize,
+                          PULONG UsedBufferSize)
+{
+    NTSTATUS Status = STATUS_SUCCESS;
+    ULONG RunBufferOffset = 0;
+    LONGLONG  DataRunOffset;
+    ULONGLONG LastLCN = 0;
+
+    LONGLONG Vbn, Lbn, Count;
+
+
+    DPRINT("\t[Vbn, Lbn, Count]\n");
+
+    // convert each mcb entry to a data run
+    for (int i = 0; FsRtlGetNextLargeMcbEntry(DataRunsMCB, i, &Vbn, &Lbn, &Count); i++)
+    {
+        UCHAR DataRunOffsetSize = 0;
+        UCHAR DataRunLengthSize = 0;
+        UCHAR ControlByte = 0;
+
+        // [vbn, lbn, count]
+        DPRINT("\t[%I64d, %I64d,%I64d]\n", Vbn, Lbn, Count);
+
+        // TODO: check for holes and convert to sparse runs
+        DataRunOffset = Lbn - LastLCN;
+        LastLCN = Lbn;
+
+        // now we need to determine how to represent DataRunOffset with the minimum number of bytes
+        DPRINT("Determining how many bytes needed to represent %I64x\n", DataRunOffset);
+        DataRunOffsetSize = GetPackedByteCount(DataRunOffset, TRUE);
+        DPRINT("%d bytes needed.\n", DataRunOffsetSize);
+
+        // determine how to represent DataRunLengthSize with the minimum number of bytes
+        DPRINT("Determining how many bytes needed to represent %I64x\n", Count);
+        DataRunLengthSize = GetPackedByteCount(Count, TRUE);
+        DPRINT("%d bytes needed.\n", DataRunLengthSize);
+
+        // ensure the next data run + end marker would be > Max buffer size
+        if (RunBufferOffset + 2 + DataRunLengthSize + DataRunOffsetSize > MaxBufferSize)
+        {
+            Status = STATUS_BUFFER_TOO_SMALL;
+            DPRINT1("FIXME: Ran out of room in buffer for data runs!\n");
+            break;
+        }
+
+        // pack and copy the control byte
+        ControlByte = (DataRunOffsetSize << 4) + DataRunLengthSize;
+        RunBuffer[RunBufferOffset++] = ControlByte;
+
+        // copy DataRunLength
+        RtlCopyMemory(RunBuffer + RunBufferOffset, &Count, DataRunLengthSize);
+        RunBufferOffset += DataRunLengthSize;
+
+        // copy DataRunOffset
+        RtlCopyMemory(RunBuffer + RunBufferOffset, &DataRunOffset, DataRunOffsetSize);
+        RunBufferOffset += DataRunOffsetSize;
+    }
+
+    // End of data runs
+    RunBuffer[RunBufferOffset++] = 0;
+
+    *UsedBufferSize = RunBufferOffset;
+    DPRINT("New Size of DataRuns: %ld\n", *UsedBufferSize);
+
+    return Status;
+}
+
 PUCHAR
 DecodeRun(PUCHAR DataRun,
           LONGLONG *DataRunOffset,
@@ -92,6 +413,224 @@ FindRun(PNTFS_ATTR_RECORD NresAttr,
     return TRUE;
 }
 
+static
+NTSTATUS
+InternalReadNonResidentAttributes(PFIND_ATTR_CONTXT Context)
+{
+    ULONGLONG ListSize;
+    PNTFS_ATTR_RECORD Attribute;
+    PNTFS_ATTR_CONTEXT ListContext;
+
+    DPRINT("InternalReadNonResidentAttributes(%p)\n", Context);
+
+    Attribute = Context->CurrAttr;
+    ASSERT(Attribute->Type == AttributeAttributeList);
+
+    if (Context->OnlyResident)
+    {
+        Context->NonResidentStart = NULL;
+        Context->NonResidentEnd = NULL;
+        return STATUS_SUCCESS;
+    }
+
+    if (Context->NonResidentStart != NULL)
+    {
+        return STATUS_FILE_CORRUPT_ERROR;
+    }
+
+    ListContext = PrepareAttributeContext(Attribute);
+    ListSize = AttributeDataLength(&ListContext->Record);
+    if (ListSize > 0xFFFFFFFF)
+    {
+        ReleaseAttributeContext(ListContext);
+        return STATUS_BUFFER_OVERFLOW;
+    }
+
+    Context->NonResidentStart = ExAllocatePoolWithTag(NonPagedPool, (ULONG)ListSize, TAG_NTFS);
+    if (Context->NonResidentStart == NULL)
+    {
+        ReleaseAttributeContext(ListContext);
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+
+    if (ReadAttribute(Context->Vcb, ListContext, 0, (PCHAR)Context->NonResidentStart, (ULONG)ListSize) != ListSize)
+    {
+        ExFreePoolWithTag(Context->NonResidentStart, TAG_NTFS);
+        Context->NonResidentStart = NULL;
+        ReleaseAttributeContext(ListContext);
+        return STATUS_FILE_CORRUPT_ERROR;
+    }
+
+    ReleaseAttributeContext(ListContext);
+    Context->NonResidentEnd = (PNTFS_ATTR_RECORD)((PCHAR)Context->NonResidentStart + ListSize);
+    return STATUS_SUCCESS;
+}
+
+static
+PNTFS_ATTR_RECORD
+InternalGetNextAttribute(PFIND_ATTR_CONTXT Context)
+{
+    PNTFS_ATTR_RECORD NextAttribute;
+
+    if (Context->CurrAttr == (PVOID)-1)
+    {
+        return NULL;
+    }
+
+    if (Context->CurrAttr >= Context->FirstAttr &&
+        Context->CurrAttr < Context->LastAttr)
+    {
+        if (Context->CurrAttr->Length == 0)
+        {
+            DPRINT1("Broken length!\n");
+            Context->CurrAttr = (PVOID)-1;
+            return NULL;
+        }
+
+        NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Context->CurrAttr + Context->CurrAttr->Length);
+
+        if (NextAttribute > Context->LastAttr || NextAttribute < Context->FirstAttr)
+        {
+            DPRINT1("Broken length: 0x%lx!\n", Context->CurrAttr->Length);
+            Context->CurrAttr = (PVOID)-1;
+            return NULL;
+        }
+        
+        Context->Offset += ((ULONG_PTR)NextAttribute - (ULONG_PTR)Context->CurrAttr);
+        Context->CurrAttr = NextAttribute;
+
+        if (Context->CurrAttr < Context->LastAttr &&
+            Context->CurrAttr->Type != AttributeEnd)
+        {
+            return Context->CurrAttr;
+        }
+    }
+
+    if (Context->NonResidentStart == NULL)
+    {
+        Context->CurrAttr = (PVOID)-1;
+        return NULL;
+    }
+
+    if (Context->CurrAttr < Context->NonResidentStart ||
+        Context->CurrAttr >= Context->NonResidentEnd)
+    {
+        Context->CurrAttr = Context->NonResidentStart;
+    }
+    else if (Context->CurrAttr->Length != 0)
+    {
+        NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Context->CurrAttr + Context->CurrAttr->Length);
+        Context->Offset += ((ULONG_PTR)NextAttribute - (ULONG_PTR)Context->CurrAttr);
+        Context->CurrAttr = NextAttribute;
+    }
+    else
+    {
+        DPRINT1("Broken length!\n");
+        Context->CurrAttr = (PVOID)-1;
+        return NULL;
+    }
+
+    if (Context->CurrAttr < Context->NonResidentEnd &&
+        Context->CurrAttr->Type != AttributeEnd)
+    {
+        return Context->CurrAttr;
+    }
+
+    Context->CurrAttr = (PVOID)-1;
+    return NULL;
+}
+
+NTSTATUS
+FindFirstAttribute(PFIND_ATTR_CONTXT Context,
+                   PDEVICE_EXTENSION Vcb,
+                   PFILE_RECORD_HEADER FileRecord,
+                   BOOLEAN OnlyResident,
+                   PNTFS_ATTR_RECORD * Attribute)
+{
+    NTSTATUS Status;
+
+    DPRINT("FindFistAttribute(%p, %p, %p, %p, %u, %p)\n", Context, Vcb, FileRecord, OnlyResident, Attribute);
+
+    Context->Vcb = Vcb;
+    Context->OnlyResident = OnlyResident;
+    Context->FirstAttr = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->AttributeOffset);
+    Context->CurrAttr = Context->FirstAttr;
+    Context->LastAttr = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->BytesInUse);
+    Context->NonResidentStart = NULL;
+    Context->NonResidentEnd = NULL;
+    Context->Offset = FileRecord->AttributeOffset;
+
+    if (Context->FirstAttr->Type == AttributeEnd)
+    {
+        Context->CurrAttr = (PVOID)-1;
+        return STATUS_END_OF_FILE;
+    }
+    else if (Context->FirstAttr->Type == AttributeAttributeList)
+    {
+        Status = InternalReadNonResidentAttributes(Context);
+        if (!NT_SUCCESS(Status))
+        {
+            return Status;
+        }
+
+        *Attribute = InternalGetNextAttribute(Context);
+        if (*Attribute == NULL)
+        {
+            return STATUS_END_OF_FILE;
+        }
+    }
+    else
+    {
+        *Attribute = Context->CurrAttr;
+        Context->Offset = (UCHAR*)Context->CurrAttr - (UCHAR*)FileRecord;
+    }
+
+    return STATUS_SUCCESS;
+}
+
+NTSTATUS
+FindNextAttribute(PFIND_ATTR_CONTXT Context,
+                  PNTFS_ATTR_RECORD * Attribute)
+{
+    NTSTATUS Status;
+
+    DPRINT("FindNextAttribute(%p, %p)\n", Context, Attribute);
+
+    *Attribute = InternalGetNextAttribute(Context);
+    if (*Attribute == NULL)
+    {
+        return STATUS_END_OF_FILE;
+    }
+
+    if (Context->CurrAttr->Type != AttributeAttributeList)
+    {
+        return STATUS_SUCCESS;
+    }
+
+    Status = InternalReadNonResidentAttributes(Context);
+    if (!NT_SUCCESS(Status))
+    {
+        return Status;
+    }
+
+    *Attribute = InternalGetNextAttribute(Context);
+    if (*Attribute == NULL)
+    {
+        return STATUS_END_OF_FILE;
+    }
+
+    return STATUS_SUCCESS;
+}
+
+VOID
+FindCloseAttribute(PFIND_ATTR_CONTXT Context)
+{
+    if (Context->NonResidentStart != NULL)
+    {
+        ExFreePoolWithTag(Context->NonResidentStart, TAG_NTFS);
+        Context->NonResidentStart = NULL;
+    }
+}
 
 static
 VOID
@@ -105,7 +644,8 @@ NtfsDumpFileNameAttribute(PNTFS_ATTR_RECORD Attribute)
 
     FileNameAttr = (PFILENAME_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
     DbgPrint(" (%x) '%.*S' ", FileNameAttr->NameType, FileNameAttr->NameLength, FileNameAttr->Name);
-    DbgPrint(" '%x' ", FileNameAttr->FileAttributes);
+    DbgPrint(" '%x' \n", FileNameAttr->FileAttributes);
+    DbgPrint(" AllocatedSize: %I64u\nDataSize: %I64u\n", FileNameAttr->AllocatedSize, FileNameAttr->DataSize);
 }
 
 
@@ -184,7 +724,8 @@ NtfsDumpIndexRootAttribute(PNTFS_ATTR_RECORD Attribute)
 
 static
 VOID
-NtfsDumpAttribute(PNTFS_ATTR_RECORD Attribute)
+NtfsDumpAttribute(PDEVICE_EXTENSION Vcb,
+                  PNTFS_ATTR_RECORD Attribute)
 {
     UNICODE_STRING Name;
 
@@ -201,10 +742,6 @@ NtfsDumpAttribute(PNTFS_ATTR_RECORD Attribute)
             NtfsDumpStandardInformationAttribute(Attribute);
             break;
 
-        case AttributeAttributeList:
-            DbgPrint("  $ATTRIBUTE_LIST ");
-            break;
-
         case AttributeObjectId:
             DbgPrint("  $OBJECT_ID ");
             break;
@@ -264,54 +801,132 @@ NtfsDumpAttribute(PNTFS_ATTR_RECORD Attribute)
             break;
     }
 
-    if (Attribute->NameLength != 0)
+    if (Attribute->Type != AttributeAttributeList)
     {
-        Name.Length = Attribute->NameLength * sizeof(WCHAR);
-        Name.MaximumLength = Name.Length;
-        Name.Buffer = (PWCHAR)((ULONG_PTR)Attribute + Attribute->NameOffset);
+        if (Attribute->NameLength != 0)
+        {
+            Name.Length = Attribute->NameLength * sizeof(WCHAR);
+            Name.MaximumLength = Name.Length;
+            Name.Buffer = (PWCHAR)((ULONG_PTR)Attribute + Attribute->NameOffset);
+
+            DbgPrint("'%wZ' ", &Name);
+        }
+
+        DbgPrint("(%s)\n",
+                 Attribute->IsNonResident ? "non-resident" : "resident");
+
+        if (Attribute->IsNonResident)
+        {
+            FindRun(Attribute,0,&lcn, &runcount);
 
-        DbgPrint("'%wZ' ", &Name);
+            DbgPrint("  AllocatedSize %I64u  DataSize %I64u InitilizedSize %I64u\n",
+                     Attribute->NonResident.AllocatedSize, Attribute->NonResident.DataSize, Attribute->NonResident.InitializedSize);
+            DbgPrint("  logical clusters: %I64u - %I64u\n",
+                     lcn, lcn + runcount - 1);
+        }
+        else
+            DbgPrint("    %u bytes of data\n", Attribute->Resident.ValueLength);
     }
+}
+
 
-    DbgPrint("(%s)\n",
-             Attribute->IsNonResident ? "non-resident" : "resident");
+VOID NtfsDumpDataRunData(PUCHAR DataRun)
+{
+    UCHAR DataRunOffsetSize;
+    UCHAR DataRunLengthSize;
+    CHAR i;
 
-    if (Attribute->IsNonResident)
+    DbgPrint("%02x ", *DataRun);
+
+    if (*DataRun == 0)
+        return;
+
+    DataRunOffsetSize = (*DataRun >> 4) & 0xF;
+    DataRunLengthSize = *DataRun & 0xF;
+
+    DataRun++;
+    for (i = 0; i < DataRunLengthSize; i++)
     {
-        FindRun(Attribute,0,&lcn, &runcount);
+        DbgPrint("%02x ", *DataRun);
+        DataRun++;
+    }
 
-        DbgPrint("  AllocatedSize %I64u  DataSize %I64u\n",
-                 Attribute->NonResident.AllocatedSize, Attribute->NonResident.DataSize);
-        DbgPrint("  logical clusters: %I64u - %I64u\n",
-                 lcn, lcn + runcount - 1);
+    for (i = 0; i < DataRunOffsetSize; i++)
+    {
+        DbgPrint("%02x ", *DataRun);
+        DataRun++;
     }
+
+    NtfsDumpDataRunData(DataRun);
 }
 
 
 VOID
-NtfsDumpFileAttributes(PFILE_RECORD_HEADER FileRecord)
+NtfsDumpDataRuns(PVOID StartOfRun,
+                 ULONGLONG CurrentLCN)
 {
+    PUCHAR DataRun = StartOfRun;
+    LONGLONG DataRunOffset;
+    ULONGLONG DataRunLength;
+
+    if (CurrentLCN == 0)
+    {
+        DPRINT1("Dumping data runs.\n\tData:\n\t\t");
+        NtfsDumpDataRunData(StartOfRun);
+        DbgPrint("\n\tRuns:\n\t\tOff\t\tLCN\t\tLength\n");
+    }
+
+    DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
+
+    if (DataRunOffset != -1)
+        CurrentLCN += DataRunOffset;
+
+    DbgPrint("\t\t%I64d\t", DataRunOffset);
+    if (DataRunOffset < 99999)
+        DbgPrint("\t");
+    DbgPrint("%I64u\t", CurrentLCN);
+    if (CurrentLCN < 99999)
+        DbgPrint("\t");
+    DbgPrint("%I64u\n", DataRunLength);
+
+    if (*DataRun == 0)
+        DbgPrint("\t\t00\n");
+    else
+        NtfsDumpDataRuns(DataRun, CurrentLCN);
+}
+
+
+VOID
+NtfsDumpFileAttributes(PDEVICE_EXTENSION Vcb,
+                       PFILE_RECORD_HEADER FileRecord)
+{
+    NTSTATUS Status;
+    FIND_ATTR_CONTXT Context;
     PNTFS_ATTR_RECORD Attribute;
 
-    Attribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->AttributeOffset);
-    while (Attribute < (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->BytesInUse) &&
-           Attribute->Type != AttributeEnd)
+    Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute);
+    while (NT_SUCCESS(Status))
     {
-        NtfsDumpAttribute(Attribute);
+        NtfsDumpAttribute(Vcb, Attribute);
 
-        Attribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Attribute + Attribute->Length);
+        Status = FindNextAttribute(&Context, &Attribute);
     }
+
+    FindCloseAttribute(&Context);
 }
 
 PFILENAME_ATTRIBUTE
-GetFileNameFromRecord(PFILE_RECORD_HEADER FileRecord, UCHAR NameType)
+GetFileNameFromRecord(PDEVICE_EXTENSION Vcb,
+                      PFILE_RECORD_HEADER FileRecord,
+                      UCHAR NameType)
 {
+    FIND_ATTR_CONTXT Context;
     PNTFS_ATTR_RECORD Attribute;
     PFILENAME_ATTRIBUTE Name;
+    NTSTATUS Status;
 
-    Attribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->AttributeOffset);
-    while (Attribute < (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->BytesInUse) &&
-           Attribute->Type != AttributeEnd)
+    Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute);
+    while (NT_SUCCESS(Status))
     {
         if (Attribute->Type == AttributeFileName)
         {
@@ -320,50 +935,159 @@ GetFileNameFromRecord(PFILE_RECORD_HEADER FileRecord, UCHAR NameType)
                 (Name->NameType == NTFS_FILE_NAME_WIN32_AND_DOS && NameType == NTFS_FILE_NAME_WIN32) ||
                 (Name->NameType == NTFS_FILE_NAME_WIN32_AND_DOS && NameType == NTFS_FILE_NAME_DOS))
             {
+                FindCloseAttribute(&Context);
                 return Name;
             }
         }
 
-        Attribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Attribute + Attribute->Length);
+        Status = FindNextAttribute(&Context, &Attribute);
     }
 
+    FindCloseAttribute(&Context);
     return NULL;
 }
 
+/**
+* GetPackedByteCount
+* Returns the minimum number of bytes needed to represent the value of a
+* 64-bit number. Used to encode data runs.
+*/
+UCHAR
+GetPackedByteCount(LONGLONG NumberToPack,
+                   BOOLEAN IsSigned)
+{
+    int bytes = 0;
+    if (!IsSigned)
+    {
+        if (NumberToPack >= 0x0100000000000000)
+            return 8;
+        if (NumberToPack >= 0x0001000000000000)
+            return 7;
+        if (NumberToPack >= 0x0000010000000000)
+            return 6;
+        if (NumberToPack >= 0x0000000100000000)
+            return 5;
+        if (NumberToPack >= 0x0000000001000000)
+            return 4;
+        if (NumberToPack >= 0x0000000000010000)
+            return 3;
+        if (NumberToPack >= 0x0000000000000100)
+            return 2;
+        return 1;
+    }
+
+    if (NumberToPack > 0)
+    {
+        // we have to make sure the number that gets encoded won't be interpreted as negative
+        if (NumberToPack >= 0x0080000000000000)
+            return 8;
+        if (NumberToPack >= 0x0000800000000000)
+            return 7;
+        if (NumberToPack >= 0x0000008000000000)
+            return 6;
+        if (NumberToPack >= 0x0000000080000000)
+            return 5;
+        if (NumberToPack >= 0x0000000000800000)
+            return 4;
+        if (NumberToPack >= 0x0000000000008000)
+            return 3;
+        if (NumberToPack >= 0x0000000000000080)
+            return 2;
+        return 1;
+    }
+    else
+    {
+        // negative number
+        if (NumberToPack <= 0xff80000000000000)
+            return 8;
+        if (NumberToPack <= 0xffff800000000000)
+            return 7;
+        if (NumberToPack <= 0xffffff8000000000)
+            return 6;
+        if (NumberToPack <= 0xffffffff80000000)
+            return 5;
+        if (NumberToPack <= 0xffffffffff800000)
+            return 4;
+        if (NumberToPack <= 0xffffffffffff8000)
+            return 3;
+        if (NumberToPack <= 0xffffffffffffff80)
+            return 2;
+        return 1;
+    }
+    return bytes;
+}
+
+NTSTATUS
+GetLastClusterInDataRun(PDEVICE_EXTENSION Vcb, PNTFS_ATTR_RECORD Attribute, PULONGLONG LastCluster)
+{
+    LONGLONG DataRunOffset;
+    ULONGLONG DataRunLength;
+    LONGLONG DataRunStartLCN;
+
+    ULONGLONG LastLCN = 0;
+    PUCHAR DataRun = (PUCHAR)Attribute + Attribute->NonResident.MappingPairsOffset;
+
+    if (!Attribute->IsNonResident)
+        return STATUS_INVALID_PARAMETER;
+
+    while (1)
+    {
+        DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
+       
+        if (DataRunOffset != -1)
+        {
+            // Normal data run.
+            DataRunStartLCN = LastLCN + DataRunOffset;
+            LastLCN = DataRunStartLCN;
+            *LastCluster = LastLCN + DataRunLength - 1;
+        }             
+
+        if (*DataRun == 0)            
+            break;
+    }
+
+    return STATUS_SUCCESS;
+}
+
 PSTANDARD_INFORMATION
-GetStandardInformationFromRecord(PFILE_RECORD_HEADER FileRecord)
+GetStandardInformationFromRecord(PDEVICE_EXTENSION Vcb,
+                                 PFILE_RECORD_HEADER FileRecord)
 {
+    NTSTATUS Status;
+    FIND_ATTR_CONTXT Context;
     PNTFS_ATTR_RECORD Attribute;
     PSTANDARD_INFORMATION StdInfo;
 
-    Attribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->AttributeOffset);
-    while (Attribute < (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->BytesInUse) &&
-           Attribute->Type != AttributeEnd)
+    Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute);
+    while (NT_SUCCESS(Status))
     {
         if (Attribute->Type == AttributeStandardInformation)
         {
             StdInfo = (PSTANDARD_INFORMATION)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
+            FindCloseAttribute(&Context);
             return StdInfo;
         }
 
-        Attribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Attribute + Attribute->Length);
+        Status = FindNextAttribute(&Context, &Attribute);
     }
 
+    FindCloseAttribute(&Context);
     return NULL;
 }
 
 PFILENAME_ATTRIBUTE
-GetBestFileNameFromRecord(PFILE_RECORD_HEADER FileRecord)
+GetBestFileNameFromRecord(PDEVICE_EXTENSION Vcb,
+                          PFILE_RECORD_HEADER FileRecord)
 {
     PFILENAME_ATTRIBUTE FileName;
 
-    FileName = GetFileNameFromRecord(FileRecord, NTFS_FILE_NAME_POSIX);
+    FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_POSIX);
     if (FileName == NULL)
     {
-        FileName = GetFileNameFromRecord(FileRecord, NTFS_FILE_NAME_WIN32);
+        FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_WIN32);
         if (FileName == NULL)
         {
-            FileName = GetFileNameFromRecord(FileRecord, NTFS_FILE_NAME_DOS);
+            FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_DOS);
         }
     }