[NTFS] Correctly find attributes stored in another file record in MFT (and referenced...
[reactos.git] / drivers / filesystems / ntfs / attrib.c
index f17b70a..006465c 100644 (file)
  * PROJECT:          ReactOS kernel
  * FILE:             drivers/filesystem/ntfs/attrib.c
  * PURPOSE:          NTFS filesystem driver
- * PROGRAMMER:       Eric Kohl
- * Updated     by       Valentin Verkhovsky  2003/09/12
+ * PROGRAMMERS:      Eric Kohl
+ *                   Valentin Verkhovsky
+ *                   HervĂ© Poussineau (hpoussin@reactos.org)
+ *                   Pierre Schweitzer (pierre@reactos.org)
  */
 
 /* INCLUDES *****************************************************************/
 
 #include "ntfs.h"
+#include <ntintsafe.h>
 
 #define NDEBUG
 #include <debug.h>
 
 /* FUNCTIONS ****************************************************************/
 
-static
-ULONG
-RunLength(PUCHAR run)
+/**
+* @name AddBitmap
+* @implemented
+*
+* Adds a $BITMAP attribute to a given FileRecord.
+*
+* @param Vcb
+* Pointer to an NTFS_VCB for the destination volume.
+*
+* @param FileRecord
+* Pointer to a complete file record to add the attribute to.
+*
+* @param AttributeAddress
+* Pointer to the region of memory that will receive the $INDEX_ALLOCATION attribute.
+* This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
+*
+* @param Name
+* Pointer to a string of 16-bit Unicode characters naming the attribute. Most often L"$I30".
+*
+* @param NameLength
+* The number of wide-characters in the name. L"$I30" Would use 4 here.
+*
+* @return
+* STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
+* of the given file record, or if the file record isn't large enough for the attribute.
+*
+* @remarks
+* Only adding the attribute to the end of the file record is supported; AttributeAddress must
+* be of type AttributeEnd.
+* This could be improved by adding an $ATTRIBUTE_LIST to the file record if there's not enough space.
+*
+*/
+NTSTATUS
+AddBitmap(PNTFS_VCB Vcb,
+          PFILE_RECORD_HEADER FileRecord,
+          PNTFS_ATTR_RECORD AttributeAddress,
+          PCWSTR Name,
+          USHORT NameLength)
 {
-    return(*run & 0x0f) + ((*run >> 4) & 0x0f) + 1;
+    ULONG AttributeLength;
+    // Calculate the header length
+    ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR);
+    ULONG FileRecordEnd = AttributeAddress->Length;
+    ULONG NameOffset;
+    ULONG ValueOffset;
+    // We'll start out with 8 bytes of bitmap data
+    ULONG ValueLength = 8;
+    ULONG BytesAvailable;
+
+    if (AttributeAddress->Type != AttributeEnd)
+    {
+        DPRINT1("FIXME: Can only add $BITMAP attribute to the end of a file record.\n");
+        return STATUS_NOT_IMPLEMENTED;
+    }
+
+    NameOffset = ResidentHeaderLength;
+
+    // Calculate ValueOffset, which will be aligned to a 4-byte boundary
+    ValueOffset = ALIGN_UP_BY(NameOffset + (sizeof(WCHAR) * NameLength), VALUE_OFFSET_ALIGNMENT);
+
+    // Calculate length of attribute
+    AttributeLength = ValueOffset + ValueLength;
+    AttributeLength = ALIGN_UP_BY(AttributeLength, ATTR_RECORD_ALIGNMENT);
+
+    // Make sure the file record is large enough for the new attribute
+    BytesAvailable = Vcb->NtfsInfo.BytesPerFileRecord - FileRecord->BytesInUse;
+    if (BytesAvailable < AttributeLength)
+    {
+        DPRINT1("FIXME: Not enough room in file record for index allocation attribute!\n");
+        return STATUS_NOT_IMPLEMENTED;
+    }
+
+    // Set Attribute fields
+    RtlZeroMemory(AttributeAddress, AttributeLength);
+
+    AttributeAddress->Type = AttributeBitmap;
+    AttributeAddress->Length = AttributeLength;
+    AttributeAddress->NameLength = NameLength;
+    AttributeAddress->NameOffset = NameOffset;
+    AttributeAddress->Instance = FileRecord->NextAttributeNumber++;
+
+    AttributeAddress->Resident.ValueLength = ValueLength;
+    AttributeAddress->Resident.ValueOffset = ValueOffset;
+
+    // Set the name
+    RtlCopyMemory((PCHAR)((ULONG_PTR)AttributeAddress + NameOffset), Name, NameLength * sizeof(WCHAR));
+
+    // move the attribute-end and file-record-end markers to the end of the file record
+    AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length);
+    SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd);
+
+    return STATUS_SUCCESS;
 }
 
+/**
+* @name AddData
+* @implemented
+*
+* Adds a $DATA attribute to a given FileRecord.
+*
+* @param FileRecord
+* Pointer to a complete file record to add the attribute to. Caller is responsible for
+* ensuring FileRecord is large enough to contain $DATA.
+*
+* @param AttributeAddress
+* Pointer to the region of memory that will receive the $DATA attribute.
+* This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
+*
+* @return
+* STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
+* of the given file record.
+*
+* @remarks
+* Only adding the attribute to the end of the file record is supported; AttributeAddress must
+* be of type AttributeEnd.
+* As it's implemented, this function is only intended to assist in creating new file records. It
+* could be made more general-purpose by considering file records with an $ATTRIBUTE_LIST.
+* It's the caller's responsibility to ensure the given file record has enough memory allocated
+* for the attribute.
+*/
+NTSTATUS
+AddData(PFILE_RECORD_HEADER FileRecord,
+        PNTFS_ATTR_RECORD AttributeAddress)
+{
+    ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR);
+    ULONG FileRecordEnd = AttributeAddress->Length;
 
-static
-LONGLONG
-RunLCN(PUCHAR run)
+    if (AttributeAddress->Type != AttributeEnd)
+    {
+        DPRINT1("FIXME: Can only add $DATA attribute to the end of a file record.\n");
+        return STATUS_NOT_IMPLEMENTED;
+    }
+
+    AttributeAddress->Type = AttributeData;
+    AttributeAddress->Length = ResidentHeaderLength;
+    AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, ATTR_RECORD_ALIGNMENT);
+    AttributeAddress->Resident.ValueLength = 0;
+    AttributeAddress->Resident.ValueOffset = ResidentHeaderLength;
+
+    // for unnamed $DATA attributes, NameOffset equals header length
+    AttributeAddress->NameOffset = ResidentHeaderLength;
+    AttributeAddress->Instance = FileRecord->NextAttributeNumber++;
+
+    // move the attribute-end and file-record-end markers to the end of the file record
+    AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length);
+    SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd);
+
+    return STATUS_SUCCESS;
+}
+
+/**
+* @name AddFileName
+* @implemented
+*
+* Adds a $FILE_NAME attribute to a given FileRecord.
+*
+* @param FileRecord
+* Pointer to a complete file record to add the attribute to. Caller is responsible for
+* ensuring FileRecord is large enough to contain $FILE_NAME.
+*
+* @param AttributeAddress
+* Pointer to the region of memory that will receive the $FILE_NAME attribute.
+* This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
+*
+* @param DeviceExt
+* Points to the target disk's DEVICE_EXTENSION.
+*
+* @param FileObject
+* Pointer to the FILE_OBJECT which represents the new name.
+* This parameter is used to determine the filename and parent directory.
+*
+* @param CaseSensitive
+* Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
+* if an application opened the file with the FILE_FLAG_POSIX_SEMANTICS flag.
+*
+* @param ParentMftIndex
+* Pointer to a ULONGLONG which will receive the index of the parent directory.
+*
+* @return
+* STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
+* of the given file record.
+*
+* @remarks
+* Only adding the attribute to the end of the file record is supported; AttributeAddress must
+* be of type AttributeEnd.
+* As it's implemented, this function is only intended to assist in creating new file records. It
+* could be made more general-purpose by considering file records with an $ATTRIBUTE_LIST.
+* It's the caller's responsibility to ensure the given file record has enough memory allocated
+* for the attribute.
+*/
+NTSTATUS
+AddFileName(PFILE_RECORD_HEADER FileRecord,
+            PNTFS_ATTR_RECORD AttributeAddress,
+            PDEVICE_EXTENSION DeviceExt,
+            PFILE_OBJECT FileObject,
+            BOOLEAN CaseSensitive,
+            PULONGLONG ParentMftIndex)
 {
-    UCHAR n1 = *run & 0x0f;
-    UCHAR n2 = (*run >> 4) & 0x0f;
-    LONGLONG lcn = (n2 == 0) ? 0 : (CHAR)(run[n1 + n2]);
-    LONG i = 0;
+    ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR);
+    PFILENAME_ATTRIBUTE FileNameAttribute;
+    LARGE_INTEGER SystemTime;
+    ULONG FileRecordEnd = AttributeAddress->Length;
+    ULONGLONG CurrentMFTIndex = NTFS_FILE_ROOT;
+    UNICODE_STRING Current, Remaining, FilenameNoPath;
+    NTSTATUS Status = STATUS_SUCCESS;
+    ULONG FirstEntry;
+
+    if (AttributeAddress->Type != AttributeEnd)
+    {
+        DPRINT1("FIXME: Can only add $FILE_NAME attribute to the end of a file record.\n");
+        return STATUS_NOT_IMPLEMENTED;
+    }
+
+    AttributeAddress->Type = AttributeFileName;
+    AttributeAddress->Instance = FileRecord->NextAttributeNumber++;
+
+    FileNameAttribute = (PFILENAME_ATTRIBUTE)((LONG_PTR)AttributeAddress + ResidentHeaderLength);
+
+    // set timestamps
+    KeQuerySystemTime(&SystemTime);
+    FileNameAttribute->CreationTime = SystemTime.QuadPart;
+    FileNameAttribute->ChangeTime = SystemTime.QuadPart;
+    FileNameAttribute->LastWriteTime = SystemTime.QuadPart;
+    FileNameAttribute->LastAccessTime = SystemTime.QuadPart;
 
-    for (i = n1 +n2 - 1; i > n1; i--)
-        lcn = (lcn << 8) + run[i];
-    return lcn;
+    // Is this a directory?
+    if(FileRecord->Flags & FRH_DIRECTORY)
+        FileNameAttribute->FileAttributes = NTFS_FILE_TYPE_DIRECTORY;
+    else
+        FileNameAttribute->FileAttributes = NTFS_FILE_TYPE_ARCHIVE;
+
+    // we need to extract the filename from the path
+    DPRINT1("Pathname: %wZ\n", &FileObject->FileName);
+
+    FsRtlDissectName(FileObject->FileName, &Current, &Remaining);
+
+    FilenameNoPath.Buffer = Current.Buffer;
+    FilenameNoPath.MaximumLength = FilenameNoPath.Length = Current.Length;
+
+    while (Current.Length != 0)
+    {
+        DPRINT1("Current: %wZ\n", &Current);
+
+        if (Remaining.Length != 0)
+        {
+            FilenameNoPath.Buffer = Remaining.Buffer;
+            FilenameNoPath.Length = FilenameNoPath.MaximumLength = Remaining.Length;
+        }
+
+        FirstEntry = 0;
+        Status = NtfsFindMftRecord(DeviceExt,
+                                   CurrentMFTIndex,
+                                   &Current,
+                                   &FirstEntry,
+                                   FALSE,
+                                   CaseSensitive,
+                                   &CurrentMFTIndex);
+        if (!NT_SUCCESS(Status))
+            break;
+
+        if (Remaining.Length == 0 )
+        {
+            if (Current.Length != 0)
+            {
+                FilenameNoPath.Buffer = Current.Buffer;
+                FilenameNoPath.Length = FilenameNoPath.MaximumLength = Current.Length;
+            }
+            break;
+        }
+
+        FsRtlDissectName(Remaining, &Current, &Remaining);
+    }
+
+    DPRINT1("MFT Index of parent: %I64u\n", CurrentMFTIndex);
+
+    // set reference to parent directory
+    FileNameAttribute->DirectoryFileReferenceNumber = CurrentMFTIndex;
+    *ParentMftIndex = CurrentMFTIndex;
+
+    DPRINT1("SequenceNumber: 0x%02x\n", FileRecord->SequenceNumber);
+
+    // The highest 2 bytes should be the sequence number, unless the parent happens to be root
+    if (CurrentMFTIndex == NTFS_FILE_ROOT)
+        FileNameAttribute->DirectoryFileReferenceNumber |= (ULONGLONG)NTFS_FILE_ROOT << 48;
+    else
+        FileNameAttribute->DirectoryFileReferenceNumber |= (ULONGLONG)FileRecord->SequenceNumber << 48;
+
+    DPRINT1("FileNameAttribute->DirectoryFileReferenceNumber: 0x%016I64x\n", FileNameAttribute->DirectoryFileReferenceNumber);
+
+    FileNameAttribute->NameLength = FilenameNoPath.Length / sizeof(WCHAR);
+    RtlCopyMemory(FileNameAttribute->Name, FilenameNoPath.Buffer, FilenameNoPath.Length);
+
+    // For now, we're emulating the way Windows behaves when 8.3 name generation is disabled
+    // TODO: add DOS Filename as needed
+    if (!CaseSensitive && RtlIsNameLegalDOS8Dot3(&FilenameNoPath, NULL, NULL))
+        FileNameAttribute->NameType = NTFS_FILE_NAME_WIN32_AND_DOS;
+    else
+        FileNameAttribute->NameType = NTFS_FILE_NAME_POSIX;
+    
+    FileRecord->LinkCount++;
+
+    AttributeAddress->Length = ResidentHeaderLength +
+        FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + FilenameNoPath.Length;
+    AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, ATTR_RECORD_ALIGNMENT);
+
+    AttributeAddress->Resident.ValueLength = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + FilenameNoPath.Length;
+    AttributeAddress->Resident.ValueOffset = ResidentHeaderLength;
+    AttributeAddress->Resident.Flags = RA_INDEXED;
+
+    // move the attribute-end and file-record-end markers to the end of the file record
+    AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length);
+    SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd);
+
+    return Status;
 }
 
+/**
+* @name AddIndexAllocation
+* @implemented
+*
+* Adds an $INDEX_ALLOCATION attribute to a given FileRecord.
+*
+* @param Vcb
+* Pointer to an NTFS_VCB for the destination volume.
+*
+* @param FileRecord
+* Pointer to a complete file record to add the attribute to.
+*
+* @param AttributeAddress
+* Pointer to the region of memory that will receive the $INDEX_ALLOCATION attribute.
+* This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
+*
+* @param Name
+* Pointer to a string of 16-bit Unicode characters naming the attribute. Most often, this will be L"$I30".
+*
+* @param NameLength
+* The number of wide-characters in the name. L"$I30" Would use 4 here.
+*
+* @return
+* STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
+* of the given file record, or if the file record isn't large enough for the attribute.
+*
+* @remarks
+* Only adding the attribute to the end of the file record is supported; AttributeAddress must
+* be of type AttributeEnd.
+* This could be improved by adding an $ATTRIBUTE_LIST to the file record if there's not enough space.
+* 
+*/
+NTSTATUS
+AddIndexAllocation(PNTFS_VCB Vcb,
+                   PFILE_RECORD_HEADER FileRecord,
+                   PNTFS_ATTR_RECORD AttributeAddress,
+                   PCWSTR Name,
+                   USHORT NameLength)
+{
+    ULONG RecordLength;
+    ULONG FileRecordEnd;
+    ULONG NameOffset;
+    ULONG DataRunOffset;
+    ULONG BytesAvailable;
 
-static
-ULONGLONG
-RunCount(PUCHAR run)
+    if (AttributeAddress->Type != AttributeEnd)
+    {
+        DPRINT1("FIXME: Can only add $INDEX_ALLOCATION attribute to the end of a file record.\n");
+        return STATUS_NOT_IMPLEMENTED;
+    }
+
+    // Calculate the name offset
+    NameOffset = FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize);
+
+    // Calculate the offset to the first data run
+    DataRunOffset = (sizeof(WCHAR) * NameLength) + NameOffset;
+    // The data run offset must be aligned to a 4-byte boundary
+    DataRunOffset = ALIGN_UP_BY(DataRunOffset, DATA_RUN_ALIGNMENT);
+
+    // Calculate the length of the new attribute; the empty data run will consist of a single byte
+    RecordLength = DataRunOffset + 1;
+
+    // The size of the attribute itself must be aligned to an 8 - byte boundary
+    RecordLength = ALIGN_UP_BY(RecordLength, ATTR_RECORD_ALIGNMENT);
+
+    // Back up the last 4-bytes of the file record (even though this value doesn't matter)
+    FileRecordEnd = AttributeAddress->Length;
+
+    // Make sure the file record can contain the new attribute
+    BytesAvailable = Vcb->NtfsInfo.BytesPerFileRecord - FileRecord->BytesInUse;
+    if (BytesAvailable < RecordLength)
+    {
+        DPRINT1("FIXME: Not enough room in file record for index allocation attribute!\n");
+        return STATUS_NOT_IMPLEMENTED;
+    }
+
+    // Set fields of attribute header
+    RtlZeroMemory(AttributeAddress, RecordLength);
+    
+    AttributeAddress->Type = AttributeIndexAllocation;
+    AttributeAddress->Length = RecordLength;
+    AttributeAddress->IsNonResident = TRUE;
+    AttributeAddress->NameLength = NameLength;
+    AttributeAddress->NameOffset = NameOffset;
+    AttributeAddress->Instance = FileRecord->NextAttributeNumber++;
+
+    AttributeAddress->NonResident.MappingPairsOffset = DataRunOffset;
+    AttributeAddress->NonResident.HighestVCN = (LONGLONG)-1;
+
+    // Set the name
+    RtlCopyMemory((PCHAR)((ULONG_PTR)AttributeAddress + NameOffset), Name, NameLength * sizeof(WCHAR));
+
+    // move the attribute-end and file-record-end markers to the end of the file record
+    AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length);
+    SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd);
+
+    return STATUS_SUCCESS;
+}
+
+/**
+* @name AddIndexRoot
+* @implemented
+*
+* Adds an $INDEX_ROOT attribute to a given FileRecord.
+*
+* @param Vcb
+* Pointer to an NTFS_VCB for the destination volume.
+*
+* @param FileRecord
+* Pointer to a complete file record to add the attribute to. Caller is responsible for
+* ensuring FileRecord is large enough to contain $INDEX_ROOT.
+*
+* @param AttributeAddress
+* Pointer to the region of memory that will receive the $INDEX_ROOT attribute.
+* This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
+*
+* @param NewIndexRoot
+* Pointer to an INDEX_ROOT_ATTRIBUTE containing the index root that will be copied to the new attribute.
+*
+* @param RootLength
+* The length of NewIndexRoot, in bytes.
+*
+* @param Name
+* Pointer to a string of 16-bit Unicode characters naming the attribute. Most often, this will be L"$I30".
+*
+* @param NameLength
+* The number of wide-characters in the name. L"$I30" Would use 4 here.
+*
+* @return
+* STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
+* of the given file record.
+*
+* @remarks
+* This function is intended to assist in creating new folders.
+* Only adding the attribute to the end of the file record is supported; AttributeAddress must
+* be of type AttributeEnd.
+* It's the caller's responsibility to ensure the given file record has enough memory allocated
+* for the attribute, and this memory must have been zeroed.
+*/
+NTSTATUS
+AddIndexRoot(PNTFS_VCB Vcb,
+             PFILE_RECORD_HEADER FileRecord,
+             PNTFS_ATTR_RECORD AttributeAddress,
+             PINDEX_ROOT_ATTRIBUTE NewIndexRoot,
+             ULONG RootLength,
+             PCWSTR Name,
+             USHORT NameLength)
 {
-    UCHAR n =  *run & 0xf;
-    ULONGLONG count = 0;
-    ULONG i = 0;
+    ULONG AttributeLength;
+    // Calculate the header length
+    ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR);
+    // Back up the file record's final ULONG (even though it doesn't matter)
+    ULONG FileRecordEnd = AttributeAddress->Length;
+    ULONG NameOffset;
+    ULONG ValueOffset;
+    ULONG BytesAvailable;
+
+    if (AttributeAddress->Type != AttributeEnd)
+    {
+        DPRINT1("FIXME: Can only add $DATA attribute to the end of a file record.\n");
+        return STATUS_NOT_IMPLEMENTED;
+    }
+
+    NameOffset = ResidentHeaderLength;
+
+    // Calculate ValueOffset, which will be aligned to a 4-byte boundary
+    ValueOffset = ALIGN_UP_BY(NameOffset + (sizeof(WCHAR) * NameLength), VALUE_OFFSET_ALIGNMENT);
+
+    // Calculate length of attribute
+    AttributeLength = ValueOffset + RootLength;
+    AttributeLength = ALIGN_UP_BY(AttributeLength, ATTR_RECORD_ALIGNMENT);
+
+    // Make sure the file record is large enough for the new attribute
+    BytesAvailable = Vcb->NtfsInfo.BytesPerFileRecord - FileRecord->BytesInUse;
+    if (BytesAvailable < AttributeLength)
+    {
+        DPRINT1("FIXME: Not enough room in file record for index allocation attribute!\n");
+        return STATUS_NOT_IMPLEMENTED;
+    }
+
+    // Set Attribute fields
+    RtlZeroMemory(AttributeAddress, AttributeLength);
+
+    AttributeAddress->Type = AttributeIndexRoot;
+    AttributeAddress->Length = AttributeLength;
+    AttributeAddress->NameLength = NameLength;
+    AttributeAddress->NameOffset = NameOffset;
+    AttributeAddress->Instance = FileRecord->NextAttributeNumber++;
+
+    AttributeAddress->Resident.ValueLength = RootLength;
+    AttributeAddress->Resident.ValueOffset = ValueOffset;
+
+    // Set the name
+    RtlCopyMemory((PCHAR)((ULONG_PTR)AttributeAddress + NameOffset), Name, NameLength * sizeof(WCHAR));
+
+    // Copy the index root attribute
+    RtlCopyMemory((PCHAR)((ULONG_PTR)AttributeAddress + ValueOffset), NewIndexRoot, RootLength);
+
+    // move the attribute-end and file-record-end markers to the end of the file record
+    AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length);
+    SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd);
 
-    for (i = n; i > 0; i--)
-        count = (count << 8) + run[i];
-    return count;
+    return STATUS_SUCCESS;
 }
 
+/**
+* @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 or if we fail to allocate a 
+* buffer for the new data runs.
+* STATUS_INSUFFICIENT_RESOURCES or STATUS_UNSUCCESSFUL if FsRtlAddLargeMcbEntry() fails.
+* STATUS_BUFFER_TOO_SMALL if ConvertLargeMCBToDataRuns() fails.
+* STATUS_NOT_IMPLEMENTED if we need to migrate the attribute to an attribute list (TODO).
+*
+* @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;
+    int DataRunMaxLength;
+    PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
+    ULONG NextAttributeOffset = AttrOffset + AttrContext->pRecord->Length;
+    ULONGLONG NextVBN = 0;
+
+    PUCHAR RunBuffer;
+    ULONG RunBufferSize;
+
+    if (!AttrContext->pRecord->IsNonResident)
+        return STATUS_INVALID_PARAMETER;
+
+    if (AttrContext->pRecord->NonResident.AllocatedSize != 0)
+        NextVBN = AttrContext->pRecord->NonResident.HighestVCN + 1;
+
+    // Add newly-assigned clusters to mcb
+    _SEH2_TRY
+    {
+        if (!FsRtlAddLargeMcbEntry(&AttrContext->DataRunsMCB,
+                                   NextVBN,
+                                   NextAssignedCluster,
+                                   RunLength))
+        {
+            ExRaiseStatus(STATUS_UNSUCCESSFUL);
+        }
+    }
+    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 
+    {
+        DPRINT1("Failed to add LargeMcb Entry!\n");
+        _SEH2_YIELD(return _SEH2_GetExceptionCode());
+    }
+    _SEH2_END;
+
+    RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
+    if (!RunBuffer)
+    {
+        DPRINT1("ERROR: Couldn't allocate memory for data runs!\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+
+    // Convert the map control block back to encoded data runs
+    ConvertLargeMCBToDataRuns(&AttrContext->DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferSize);
+
+    // Get the amount of free space between the start of the of the first data run and the attribute end
+    DataRunMaxLength = AttrContext->pRecord->Length - AttrContext->pRecord->NonResident.MappingPairsOffset;
+
+    // Do we need to extend the attribute (or convert to attribute list)?
+    if (DataRunMaxLength < RunBufferSize)
+    {
+        PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset);
+        PNTFS_ATTR_RECORD NewRecord;
+
+        // Add free space at the end of the file record to DataRunMaxLength
+        DataRunMaxLength += Vcb->NtfsInfo.BytesPerFileRecord - FileRecord->BytesInUse;
+
+        // Can we resize the attribute?
+        if (DataRunMaxLength < RunBufferSize)
+        {
+            DPRINT1("FIXME: Need to create attribute list! Max Data Run Length available: %d, RunBufferSize: %d\n", DataRunMaxLength, RunBufferSize);
+            ExFreePoolWithTag(RunBuffer, TAG_NTFS);
+            return STATUS_NOT_IMPLEMENTED;
+        }
+
+        // Are there more attributes after the one we're resizing?
+        if (NextAttribute->Type != AttributeEnd)
+        {
+            PNTFS_ATTR_RECORD FinalAttribute;
+
+            // Calculate where to move the trailing attributes
+            ULONG_PTR MoveTo = (ULONG_PTR)DestinationAttribute + AttrContext->pRecord->NonResident.MappingPairsOffset + RunBufferSize;
+            MoveTo = ALIGN_UP_BY(MoveTo, ATTR_RECORD_ALIGNMENT);
+
+            DPRINT1("Moving attribute(s) after this one starting with type 0x%lx\n", NextAttribute->Type);
+
+            // Move the trailing attributes; FinalAttribute will point to the end marker
+            FinalAttribute = MoveAttributes(Vcb, NextAttribute, NextAttributeOffset, MoveTo);
+
+            // set the file record end
+            SetFileRecordEnd(FileRecord, FinalAttribute, FILE_RECORD_END);
+        }
+
+        // calculate position of end markers
+        NextAttributeOffset = AttrOffset + AttrContext->pRecord->NonResident.MappingPairsOffset + RunBufferSize;
+        NextAttributeOffset = ALIGN_UP_BY(NextAttributeOffset, ATTR_RECORD_ALIGNMENT);
+
+        // Update the length of the destination attribute
+        DestinationAttribute->Length = NextAttributeOffset - AttrOffset;
+
+        // Create a new copy of the attribute record
+        NewRecord = ExAllocatePoolWithTag(NonPagedPool, DestinationAttribute->Length, TAG_NTFS);
+        RtlCopyMemory(NewRecord, AttrContext->pRecord, AttrContext->pRecord->Length);
+        NewRecord->Length = DestinationAttribute->Length;
+
+        // Free the old copy of the attribute record, which won't be large enough
+        ExFreePoolWithTag(AttrContext->pRecord, TAG_NTFS);
+
+        // Set the attribute context's record to the new copy
+        AttrContext->pRecord = NewRecord;
+
+        // if NextAttribute is the AttributeEnd marker
+        if (NextAttribute->Type == AttributeEnd)
+        {
+            // End the file record
+            NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset);
+            SetFileRecordEnd(FileRecord, NextAttribute, FILE_RECORD_END);
+        }
+    }
+
+    // Update HighestVCN
+    DestinationAttribute->NonResident.HighestVCN =
+    AttrContext->pRecord->NonResident.HighestVCN = max(NextVBN - 1 + RunLength,
+                                                     AttrContext->pRecord->NonResident.HighestVCN);
+
+    // Write data runs to destination attribute
+    RtlCopyMemory((PVOID)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), 
+                  RunBuffer, 
+                  RunBufferSize);
+
+    // Update the attribute record in the attribute context
+    RtlCopyMemory((PVOID)((ULONG_PTR)AttrContext->pRecord + AttrContext->pRecord->NonResident.MappingPairsOffset),
+                  RunBuffer,
+                  RunBufferSize);
+
+    // Update the file record
+    Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord);
+
+    ExFreePoolWithTag(RunBuffer, TAG_NTFS);
+
+    NtfsDumpDataRuns((PUCHAR)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), 0);
+
+    return Status;
+}
+
+/**
+* @name AddStandardInformation
+* @implemented
+*
+* Adds a $STANDARD_INFORMATION attribute to a given FileRecord.
+*
+* @param FileRecord
+* Pointer to a complete file record to add the attribute to. Caller is responsible for
+* ensuring FileRecord is large enough to contain $STANDARD_INFORMATION.
+*
+* @param AttributeAddress
+* Pointer to the region of memory that will receive the $STANDARD_INFORMATION attribute.
+* This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
+*
+* @return
+* STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
+* of the given file record.
+*
+* @remarks
+* Only adding the attribute to the end of the file record is supported; AttributeAddress must
+* be of type AttributeEnd.
+* As it's implemented, this function is only intended to assist in creating new file records. It
+* could be made more general-purpose by considering file records with an $ATTRIBUTE_LIST.
+* It's the caller's responsibility to ensure the given file record has enough memory allocated
+* for the attribute.
+*/
+NTSTATUS
+AddStandardInformation(PFILE_RECORD_HEADER FileRecord,
+                       PNTFS_ATTR_RECORD AttributeAddress)
+{
+    ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR);
+    PSTANDARD_INFORMATION StandardInfo = (PSTANDARD_INFORMATION)((LONG_PTR)AttributeAddress + ResidentHeaderLength);
+    LARGE_INTEGER SystemTime;
+    ULONG FileRecordEnd = AttributeAddress->Length;
+
+    if (AttributeAddress->Type != AttributeEnd)
+    {
+        DPRINT1("FIXME: Can only add $STANDARD_INFORMATION attribute to the end of a file record.\n");
+        return STATUS_NOT_IMPLEMENTED;
+    }
+
+    AttributeAddress->Type = AttributeStandardInformation;
+    AttributeAddress->Length = sizeof(STANDARD_INFORMATION) + ResidentHeaderLength;
+    AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, ATTR_RECORD_ALIGNMENT);
+    AttributeAddress->Resident.ValueLength = sizeof(STANDARD_INFORMATION);
+    AttributeAddress->Resident.ValueOffset = ResidentHeaderLength;
+    AttributeAddress->Instance = FileRecord->NextAttributeNumber++;
+
+    // set dates and times
+    KeQuerySystemTime(&SystemTime);
+    StandardInfo->CreationTime = SystemTime.QuadPart;
+    StandardInfo->ChangeTime = SystemTime.QuadPart;
+    StandardInfo->LastWriteTime = SystemTime.QuadPart;
+    StandardInfo->LastAccessTime = SystemTime.QuadPart;
+    StandardInfo->FileAttribute = NTFS_FILE_TYPE_ARCHIVE;
+
+    // move the attribute-end and file-record-end markers to the end of the file record
+    AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length);
+    SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd);
+
+    return STATUS_SUCCESS;
+}
+
+/**
+* @name ConvertDataRunsToLargeMCB
+* @implemented
+*
+* 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 or STATUS_UNSUCCESSFUL 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 LastLCN = 0;
+
+    // Initialize the MCB, potentially catch an exception
+    _SEH2_TRY{
+        FsRtlInitializeLargeMcb(DataRunsMCB, NonPagedPool);
+    } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+        _SEH2_YIELD(return _SEH2_GetExceptionCode());
+    } _SEH2_END;
+
+    while (*DataRun != 0)
+    {
+        DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
+
+        if (DataRunOffset != -1)
+        {
+            // Normal data run.
+            DataRunStartLCN = LastLCN + DataRunOffset;
+            LastLCN = DataRunStartLCN;
+
+            _SEH2_TRY{
+                if (!FsRtlAddLargeMcbEntry(DataRunsMCB,
+                                           *pNextVBN,
+                                           DataRunStartLCN,
+                                           DataRunLength))
+                {
+                    ExRaiseStatus(STATUS_UNSUCCESSFUL);
+                }
+            } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+                FsRtlUninitializeLargeMcb(DataRunsMCB);
+                _SEH2_YIELD(return _SEH2_GetExceptionCode());
+            } _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;
+    ULONG i;
+
+
+    DPRINT("\t[Vbn, Lbn, Count]\n");
+
+    // convert each mcb entry to a data run
+    for (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,
+          ULONGLONG *DataRunLength)
+{
+    UCHAR DataRunOffsetSize;
+    UCHAR DataRunLengthSize;
+    CHAR i;
+
+    DataRunOffsetSize = (*DataRun >> 4) & 0xF;
+    DataRunLengthSize = *DataRun & 0xF;
+    *DataRunOffset = 0;
+    *DataRunLength = 0;
+    DataRun++;
+    for (i = 0; i < DataRunLengthSize; i++)
+    {
+        *DataRunLength += ((ULONG64)*DataRun) << (i * 8);
+        DataRun++;
+    }
+
+    /* NTFS 3+ sparse files */
+    if (DataRunOffsetSize == 0)
+    {
+        *DataRunOffset = -1;
+    }
+    else
+    {
+        for (i = 0; i < DataRunOffsetSize - 1; i++)
+        {
+            *DataRunOffset += ((ULONG64)*DataRun) << (i * 8);
+            DataRun++;
+        }
+        /* The last byte contains sign so we must process it different way. */
+        *DataRunOffset = ((LONG64)(CHAR)(*(DataRun++)) << (i * 8)) + *DataRunOffset;
+    }
+
+    DPRINT("DataRunOffsetSize: %x\n", DataRunOffsetSize);
+    DPRINT("DataRunLengthSize: %x\n", DataRunLengthSize);
+    DPRINT("DataRunOffset: %x\n", *DataRunOffset);
+    DPRINT("DataRunLength: %x\n", *DataRunLength);
+
+    return DataRun;
+}
 
 BOOLEAN
-FindRun(PNONRESIDENT_ATTRIBUTE NresAttr,
+FindRun(PNTFS_ATTR_RECORD NresAttr,
         ULONGLONG vcn,
         PULONGLONG lcn,
         PULONGLONG count)
 {
-    PUCHAR run;
-    ULONGLONG base = NresAttr->StartVcn;
-
-    if (vcn < NresAttr->StartVcn || vcn > NresAttr->LastVcn)
+    if (vcn < NresAttr->NonResident.LowestVCN || vcn > NresAttr->NonResident.HighestVCN)
         return FALSE;
 
-    *lcn = 0;
+    DecodeRun((PUCHAR)((ULONG_PTR)NresAttr + NresAttr->NonResident.MappingPairsOffset), (PLONGLONG)lcn, count);
+
+    return TRUE;
+}
+
+/**
+* @name FreeClusters
+* @implemented
+*
+* Shrinks the allocation size of a non-resident attribute by a given number of clusters.
+* Frees the clusters from the volume's $BITMAP file as well as the attribute's data runs.
+*
+* @param Vcb
+* Pointer to an NTFS_VCB for the destination volume.
+*
+* @param AttrContext
+* Pointer to an NTFS_ATTR_CONTEXT describing the attribute from which the clusters will be freed.
+*
+* @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 attribute. Must be at least
+* Vcb->NtfsInfo.BytesPerFileRecord bytes long.
+*
+* @param ClustersToFree
+* Number of clusters that should be freed from the end of the data stream. Must be no more
+* Than the number of clusters assigned to the attribute (HighestVCN + 1).
+*
+* @return
+* STATUS_SUCCESS on success. STATUS_INVALID_PARAMETER if AttrContext describes a resident attribute,
+* or if the caller requested more clusters be freed than the attribute has been allocated.
+* STATUS_INSUFFICIENT_RESOURCES if allocating a buffer for the data runs fails or
+* if ConvertDataRunsToLargeMCB() fails.
+* STATUS_BUFFER_TOO_SMALL if ConvertLargeMCBToDataRuns() fails.
+*
+*
+*/
+NTSTATUS
+FreeClusters(PNTFS_VCB Vcb,
+             PNTFS_ATTR_CONTEXT AttrContext,
+             ULONG AttrOffset,
+             PFILE_RECORD_HEADER FileRecord,
+             ULONG ClustersToFree)
+{
+    NTSTATUS Status = STATUS_SUCCESS;
+    ULONG ClustersLeftToFree = ClustersToFree;
+
+    PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
+    ULONG NextAttributeOffset = AttrOffset + AttrContext->pRecord->Length;
+    PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset);
+
+    PUCHAR RunBuffer;
+    ULONG RunBufferSize = 0;
+
+    PFILE_RECORD_HEADER BitmapRecord;
+    PNTFS_ATTR_CONTEXT DataContext;
+    ULONGLONG BitmapDataSize;
+    PUCHAR BitmapData;
+    RTL_BITMAP Bitmap;
+    ULONG LengthWritten;
+
+    if (!AttrContext->pRecord->IsNonResident)
+    {
+        return STATUS_INVALID_PARAMETER;
+    }
 
-    for (run = (PUCHAR)((ULONG_PTR)NresAttr + NresAttr->RunArrayOffset);
-         *run != 0; run += RunLength(run))
+    // Read the $Bitmap file
+    BitmapRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList);
+    if (BitmapRecord == NULL)
     {
-        *lcn += RunLCN(run);
-        *count = RunCount(run);
+        DPRINT1("Error: Unable to allocate memory for bitmap file record!\n");
+        return STATUS_NO_MEMORY;
+    }
+
+    Status = ReadFileRecord(Vcb, NTFS_FILE_BITMAP, BitmapRecord);
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT1("Error: Unable to read file record for bitmap!\n");
+        ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BitmapRecord);
+        return 0;
+    }
+
+    Status = FindAttribute(Vcb, BitmapRecord, AttributeData, L"", 0, &DataContext, NULL);
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT1("Error: Unable to find data attribute for bitmap file!\n");
+        ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BitmapRecord);
+        return 0;
+    }
 
-        if (base <= vcn && vcn < base + *count)
+    BitmapDataSize = AttributeDataLength(DataContext->pRecord);
+    BitmapDataSize = min(BitmapDataSize, ULONG_MAX);
+    ASSERT((BitmapDataSize * 8) >= Vcb->NtfsInfo.ClusterCount);
+    BitmapData = ExAllocatePoolWithTag(NonPagedPool, ROUND_UP(BitmapDataSize, Vcb->NtfsInfo.BytesPerSector), TAG_NTFS);
+    if (BitmapData == NULL)
+    {
+        DPRINT1("Error: Unable to allocate memory for bitmap file data!\n");
+        ReleaseAttributeContext(DataContext);
+        ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BitmapRecord);
+        return 0;
+    }
+
+    ReadAttribute(Vcb, DataContext, 0, (PCHAR)BitmapData, (ULONG)BitmapDataSize);
+
+    RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, Vcb->NtfsInfo.ClusterCount);
+    
+    // free clusters in $BITMAP file
+    while (ClustersLeftToFree > 0)
+    {
+        LONGLONG LargeVbn, LargeLbn;
+
+        if (!FsRtlLookupLastLargeMcbEntry(&AttrContext->DataRunsMCB, &LargeVbn, &LargeLbn))
         {
-            *lcn = (RunLCN(run) == 0) ? 0 : *lcn + vcn - base;
-            *count -= (ULONG)(vcn - base);
+            Status = STATUS_INVALID_PARAMETER;
+            DPRINT1("DRIVER ERROR: FreeClusters called to free %lu clusters, which is %lu more clusters than are assigned to attribute!",
+                    ClustersToFree,
+                    ClustersLeftToFree);
+            break;
+        }
 
-            return TRUE;
+        if (LargeLbn != -1)
+        {
+            // deallocate this cluster
+            RtlClearBits(&Bitmap, LargeLbn, 1);
         }
-        else
+        FsRtlTruncateLargeMcb(&AttrContext->DataRunsMCB, AttrContext->pRecord->NonResident.HighestVCN);
+
+        // decrement HighestVCN, but don't let it go below 0
+        AttrContext->pRecord->NonResident.HighestVCN = min(AttrContext->pRecord->NonResident.HighestVCN, AttrContext->pRecord->NonResident.HighestVCN - 1);
+        ClustersLeftToFree--;
+    }
+
+    // update $BITMAP file on disk
+    Status = WriteAttribute(Vcb, DataContext, 0, BitmapData, (ULONG)BitmapDataSize, &LengthWritten, FileRecord);
+    if (!NT_SUCCESS(Status))
+    {
+        ReleaseAttributeContext(DataContext);
+        ExFreePoolWithTag(BitmapData, TAG_NTFS);
+        ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BitmapRecord);
+        return Status;
+    }
+
+    ReleaseAttributeContext(DataContext);
+    ExFreePoolWithTag(BitmapData, TAG_NTFS);
+    ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BitmapRecord);
+    
+    // Save updated data runs to file record
+
+    // Allocate some memory for a new RunBuffer
+    RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
+    if (!RunBuffer)
+    {
+        DPRINT1("ERROR: Couldn't allocate memory for data runs!\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+
+    // Convert the map control block back to encoded data runs
+    ConvertLargeMCBToDataRuns(&AttrContext->DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferSize);
+
+    // Update HighestVCN
+    DestinationAttribute->NonResident.HighestVCN = AttrContext->pRecord->NonResident.HighestVCN;
+
+    // Write data runs to destination attribute
+    RtlCopyMemory((PVOID)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset),
+                  RunBuffer,
+                  RunBufferSize);
+
+    // Is DestinationAttribute the last attribute in the file record?
+    if (NextAttribute->Type == AttributeEnd)
+    {
+        // update attribute length
+        DestinationAttribute->Length = ALIGN_UP_BY(AttrContext->pRecord->NonResident.MappingPairsOffset + RunBufferSize,
+                                                 ATTR_RECORD_ALIGNMENT);
+
+        ASSERT(DestinationAttribute->Length <= AttrContext->pRecord->Length);
+
+        AttrContext->pRecord->Length = DestinationAttribute->Length;
+
+        // write end markers
+        NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)DestinationAttribute + DestinationAttribute->Length);
+        SetFileRecordEnd(FileRecord, NextAttribute, FILE_RECORD_END);
+    }
+
+    // Update the file record
+    Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord);
+
+    ExFreePoolWithTag(RunBuffer, TAG_NTFS);
+
+    NtfsDumpDataRuns((PUCHAR)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), 0);
+
+    return Status;
+}
+
+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->pRecord);
+    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_ATTRIBUTE_LIST_ITEM)((PCHAR)Context->NonResidentStart + ListSize);
+    return STATUS_SUCCESS;
+}
+
+static
+PNTFS_ATTRIBUTE_LIST_ITEM
+InternalGetNextAttributeListItem(PFIND_ATTR_CONTXT Context)
+{
+    PNTFS_ATTRIBUTE_LIST_ITEM NextItem;
+
+    if (Context->NonResidentCur == (PVOID)-1)
+    {
+        return NULL;
+    }
+
+    if (Context->NonResidentCur == NULL || Context->NonResidentCur->Type == AttributeEnd)
+    {
+        Context->NonResidentCur = (PVOID)-1;
+        return NULL;
+    }
+
+    if (Context->NonResidentCur->Length == 0)
+    {
+        DPRINT1("Broken length list entry length !");
+        Context->NonResidentCur = (PVOID)-1;
+        return NULL;
+    }
+
+    NextItem = (PNTFS_ATTRIBUTE_LIST_ITEM)((PCHAR)Context->NonResidentCur + Context->NonResidentCur->Length);
+    if (NextItem->Length == 0 || NextItem->Type == AttributeEnd)
+    {
+        Context->NonResidentCur = (PVOID)-1;
+        return NULL;
+    }
+
+    if (NextItem < Context->NonResidentStart || NextItem > Context->NonResidentEnd)
+    {
+        Context->NonResidentCur = (PVOID)-1;
+        return NULL;
+    }
+
+    Context->NonResidentCur = NextItem;
+    return NextItem;
+}
+
+NTSTATUS
+FindFirstAttributeListItem(PFIND_ATTR_CONTXT Context,
+                           PNTFS_ATTRIBUTE_LIST_ITEM *Item)
+{
+    if (Context->NonResidentStart == NULL || Context->NonResidentStart->Type == AttributeEnd)
+    {
+        return STATUS_UNSUCCESSFUL;
+    }
+
+    Context->NonResidentCur = Context->NonResidentStart;
+    *Item = Context->NonResidentCur;
+    return STATUS_SUCCESS;
+}
+
+NTSTATUS
+FindNextAttributeListItem(PFIND_ATTR_CONTXT Context,
+                          PNTFS_ATTRIBUTE_LIST_ITEM *Item)
+{
+    *Item = InternalGetNextAttributeListItem(Context);
+    if (*Item == NULL)
+    {
+        return STATUS_UNSUCCESSFUL;
+    }
+    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)
         {
-            base += *count;
+            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;
     }
 
-    return FALSE;
+    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
-NtfsDumpFileNameAttribute(PATTRIBUTE Attribute)
+NtfsDumpFileNameAttribute(PNTFS_ATTR_RECORD Attribute)
 {
-    PRESIDENT_ATTRIBUTE ResAttr;
     PFILENAME_ATTRIBUTE FileNameAttr;
 
     DbgPrint("  $FILE_NAME ");
 
-    ResAttr = (PRESIDENT_ATTRIBUTE)Attribute;
-//    DbgPrint(" Length %lu  Offset %hu ", ResAttr->ValueLength, ResAttr->ValueOffset);
+//    DbgPrint(" Length %lu  Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
+
+    FileNameAttr = (PFILENAME_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
+    DbgPrint(" (%x) '%.*S' ", FileNameAttr->NameType, FileNameAttr->NameLength, FileNameAttr->Name);
+    DbgPrint(" '%x' \n", FileNameAttr->FileAttributes);
+    DbgPrint(" AllocatedSize: %I64u\nDataSize: %I64u\n", FileNameAttr->AllocatedSize, FileNameAttr->DataSize);
+    DbgPrint(" File reference: 0x%016I64x\n", FileNameAttr->DirectoryFileReferenceNumber);
+}
+
 
-    FileNameAttr = (PFILENAME_ATTRIBUTE)((ULONG_PTR)ResAttr + ResAttr->ValueOffset);
-    DbgPrint(" '%.*S' ", FileNameAttr->NameLength, FileNameAttr->Name);
+static
+VOID
+NtfsDumpStandardInformationAttribute(PNTFS_ATTR_RECORD Attribute)
+{
+    PSTANDARD_INFORMATION StandardInfoAttr;
+
+    DbgPrint("  $STANDARD_INFORMATION ");
+
+//    DbgPrint(" Length %lu  Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
+
+    StandardInfoAttr = (PSTANDARD_INFORMATION)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
+    DbgPrint(" '%x' ", StandardInfoAttr->FileAttribute);
 }
 
 
 static
 VOID
-NtfsDumpVolumeNameAttribute(PATTRIBUTE Attribute)
+NtfsDumpVolumeNameAttribute(PNTFS_ATTR_RECORD Attribute)
 {
-    PRESIDENT_ATTRIBUTE ResAttr;
     PWCHAR VolumeName;
 
     DbgPrint("  $VOLUME_NAME ");
 
-    ResAttr = (PRESIDENT_ATTRIBUTE)Attribute;
-//    DbgPrint(" Length %lu  Offset %hu ", ResAttr->ValueLength, ResAttr->ValueOffset);
+//    DbgPrint(" Length %lu  Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
 
-    VolumeName = (PWCHAR)((ULONG_PTR)ResAttr + ResAttr->ValueOffset);
-    DbgPrint(" '%.*S' ", ResAttr->ValueLength / sizeof(WCHAR), VolumeName);
+    VolumeName = (PWCHAR)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
+    DbgPrint(" '%.*S' ", Attribute->Resident.ValueLength / sizeof(WCHAR), VolumeName);
 }
 
 
 static
 VOID
-NtfsDumpVolumeInformationAttribute(PATTRIBUTE Attribute)
+NtfsDumpVolumeInformationAttribute(PNTFS_ATTR_RECORD Attribute)
 {
-    PRESIDENT_ATTRIBUTE ResAttr;
     PVOLINFO_ATTRIBUTE VolInfoAttr;
 
     DbgPrint("  $VOLUME_INFORMATION ");
 
-    ResAttr = (PRESIDENT_ATTRIBUTE)Attribute;
-//    DbgPrint(" Length %lu  Offset %hu ", ResAttr->ValueLength, ResAttr->ValueOffset);
+//    DbgPrint(" Length %lu  Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
 
-    VolInfoAttr = (PVOLINFO_ATTRIBUTE)((ULONG_PTR)ResAttr + ResAttr->ValueOffset);
+    VolInfoAttr = (PVOLINFO_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
     DbgPrint(" NTFS Version %u.%u  Flags 0x%04hx ",
              VolInfoAttr->MajorVersion,
              VolInfoAttr->MinorVersion,
@@ -163,26 +1539,97 @@ NtfsDumpVolumeInformationAttribute(PATTRIBUTE Attribute)
 
 static
 VOID
-NtfsDumpAttribute (PATTRIBUTE Attribute)
+NtfsDumpIndexRootAttribute(PNTFS_ATTR_RECORD Attribute)
+{
+    PINDEX_ROOT_ATTRIBUTE IndexRootAttr;
+    ULONG CurrentOffset;
+    ULONG CurrentNode;
+
+    IndexRootAttr = (PINDEX_ROOT_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
+
+    if (IndexRootAttr->AttributeType == AttributeFileName)
+        ASSERT(IndexRootAttr->CollationRule == COLLATION_FILE_NAME);
+
+    DbgPrint("  $INDEX_ROOT (%u bytes per index record, %u clusters) ", IndexRootAttr->SizeOfEntry, IndexRootAttr->ClustersPerIndexRecord);
+
+    if (IndexRootAttr->Header.Flags == INDEX_ROOT_SMALL)
+    {
+        DbgPrint(" (small)\n");
+    }
+    else
+    {
+        ASSERT(IndexRootAttr->Header.Flags == INDEX_ROOT_LARGE);
+        DbgPrint(" (large)\n");
+    }
+
+    DbgPrint("   Offset to first index: 0x%lx\n   Total size of index entries: 0x%lx\n   Allocated size of node: 0x%lx\n",
+             IndexRootAttr->Header.FirstEntryOffset,
+             IndexRootAttr->Header.TotalSizeOfEntries,
+             IndexRootAttr->Header.AllocatedSize);
+    CurrentOffset = IndexRootAttr->Header.FirstEntryOffset;
+    CurrentNode = 0;
+    // print details of every node in the index
+    while (CurrentOffset < IndexRootAttr->Header.TotalSizeOfEntries)
+    {
+        PINDEX_ENTRY_ATTRIBUTE currentIndexExtry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexRootAttr + 0x10 + CurrentOffset);
+        DbgPrint("   Index Node Entry %lu", CurrentNode++);
+        if (BooleanFlagOn(currentIndexExtry->Flags, NTFS_INDEX_ENTRY_NODE))
+            DbgPrint(" (Branch)");
+        else
+            DbgPrint(" (Leaf)");
+        if (BooleanFlagOn(currentIndexExtry->Flags, NTFS_INDEX_ENTRY_END))
+        {
+            DbgPrint(" (Dummy Key)");
+        }
+        DbgPrint("\n    File Reference: 0x%016I64x\n", currentIndexExtry->Data.Directory.IndexedFile);
+        DbgPrint("    Index Entry Length: 0x%x\n", currentIndexExtry->Length);
+        DbgPrint("    Index Key Length: 0x%x\n", currentIndexExtry->KeyLength);
+
+        // if this isn't the final (dummy) node, print info about the key (Filename attribute)
+        if (!(currentIndexExtry->Flags & NTFS_INDEX_ENTRY_END))
+        {
+            UNICODE_STRING Name;
+            DbgPrint("     Parent File Reference: 0x%016I64x\n", currentIndexExtry->FileName.DirectoryFileReferenceNumber);
+            DbgPrint("     $FILENAME indexed: ");
+            Name.Length = currentIndexExtry->FileName.NameLength * sizeof(WCHAR);
+            Name.MaximumLength = Name.Length;
+            Name.Buffer = currentIndexExtry->FileName.Name;
+            DbgPrint("'%wZ'\n", &Name);
+        }
+
+        // if this node has a sub-node beneath it
+        if (currentIndexExtry->Flags & NTFS_INDEX_ENTRY_NODE)
+        {
+            // Print the VCN of the sub-node
+            PULONGLONG SubNodeVCN = (PULONGLONG)((ULONG_PTR)currentIndexExtry + currentIndexExtry->Length - sizeof(ULONGLONG));
+            DbgPrint("    VCN of sub-node: 0x%llx\n", *SubNodeVCN);
+        }
+        
+        CurrentOffset += currentIndexExtry->Length;
+        ASSERT(currentIndexExtry->Length);
+    }
+
+}
+
+
+static
+VOID
+NtfsDumpAttribute(PDEVICE_EXTENSION Vcb,
+                  PNTFS_ATTR_RECORD Attribute)
 {
-    PNONRESIDENT_ATTRIBUTE NresAttr;
     UNICODE_STRING Name;
 
     ULONGLONG lcn = 0;
     ULONGLONG runcount = 0;
 
-    switch (Attribute->AttributeType)
+    switch (Attribute->Type)
     {
         case AttributeFileName:
             NtfsDumpFileNameAttribute(Attribute);
             break;
 
         case AttributeStandardInformation:
-            DbgPrint("  $STANDARD_INFORMATION ");
-            break;
-
-        case AttributeAttributeList:
-            DbgPrint("  $ATTRIBUTE_LIST ");
+            NtfsDumpStandardInformationAttribute(Attribute);
             break;
 
         case AttributeObjectId:
@@ -207,7 +1654,7 @@ NtfsDumpAttribute (PATTRIBUTE Attribute)
             break;
 
         case AttributeIndexRoot:
-            DbgPrint("  $INDEX_ROOT ");
+            NtfsDumpIndexRootAttribute(Attribute);
             break;
 
         case AttributeIndexAllocation:
@@ -240,49 +1687,317 @@ NtfsDumpAttribute (PATTRIBUTE Attribute)
 
         default:
             DbgPrint("  Attribute %lx ",
-                     Attribute->AttributeType);
+                     Attribute->Type);
             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("'%wZ' ", &Name);
+        DbgPrint("(%s)\n",
+                 Attribute->IsNonResident ? "non-resident" : "resident");
+
+        if (Attribute->IsNonResident)
+        {
+            FindRun(Attribute,0,&lcn, &runcount);
+
+            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->Nonresident ? "non-resident" : "resident");
 
-    if (Attribute->Nonresident)
+VOID NtfsDumpDataRunData(PUCHAR DataRun)
+{
+    UCHAR DataRunOffsetSize;
+    UCHAR DataRunLengthSize;
+    CHAR i;
+
+    DbgPrint("%02x ", *DataRun);
+
+    if (*DataRun == 0)
+        return;
+
+    DataRunOffsetSize = (*DataRun >> 4) & 0xF;
+    DataRunLengthSize = *DataRun & 0xF;
+
+    DataRun++;
+    for (i = 0; i < DataRunLengthSize; i++)
+    {
+        DbgPrint("%02x ", *DataRun);
+        DataRun++;
+    }
+
+    for (i = 0; i < DataRunOffsetSize; i++)
     {
-        NresAttr = (PNONRESIDENT_ATTRIBUTE)Attribute;
+        DbgPrint("%02x ", *DataRun);
+        DataRun++;
+    }
+
+    NtfsDumpDataRunData(DataRun);
+}
 
-        FindRun(NresAttr,0,&lcn, &runcount);
 
-        DbgPrint("  AllocatedSize %I64u  DataSize %I64u\n",
-                 NresAttr->AllocatedSize, NresAttr->DataSize);
-        DbgPrint("  logical clusters: %I64u - %I64u\n",
-                 lcn, lcn + runcount - 1);
+VOID
+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(PFILE_RECORD_HEADER FileRecord)
+NtfsDumpFileAttributes(PDEVICE_EXTENSION Vcb,
+                       PFILE_RECORD_HEADER FileRecord)
 {
-    PATTRIBUTE Attribute;
+    NTSTATUS Status;
+    FIND_ATTR_CONTXT Context;
+    PNTFS_ATTR_RECORD Attribute;
+
+    Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute);
+    while (NT_SUCCESS(Status))
+    {
+        NtfsDumpAttribute(Vcb, Attribute);
+
+        Status = FindNextAttribute(&Context, &Attribute);
+    }
+
+    FindCloseAttribute(&Context);
+}
+
+PFILENAME_ATTRIBUTE
+GetFileNameFromRecord(PDEVICE_EXTENSION Vcb,
+                      PFILE_RECORD_HEADER FileRecord,
+                      UCHAR NameType)
+{
+    FIND_ATTR_CONTXT Context;
+    PNTFS_ATTR_RECORD Attribute;
+    PFILENAME_ATTRIBUTE Name;
+    NTSTATUS Status;
+
+    Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute);
+    while (NT_SUCCESS(Status))
+    {
+        if (Attribute->Type == AttributeFileName)
+        {
+            Name = (PFILENAME_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
+            if (Name->NameType == 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;
+            }
+        }
+
+        Status = FindNextAttribute(&Context, &Attribute);
+    }
+
+    FindCloseAttribute(&Context);
+    return NULL;
+}
 
-    Attribute = (PATTRIBUTE)((ULONG_PTR)FileRecord + FileRecord->AttributeOffset);
-    while (Attribute < (PATTRIBUTE)((ULONG_PTR)FileRecord + FileRecord->BytesInUse) &&
-           Attribute->AttributeType != (ATTRIBUTE_TYPE)-1)
+/**
+* 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)
+{
+    if (!IsSigned)
     {
-        NtfsDumpAttribute(Attribute);
+        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;
+    }
 
-        Attribute = (PATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Length);
+    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;
     }
+    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;
+}
+
+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(PDEVICE_EXTENSION Vcb,
+                                 PFILE_RECORD_HEADER FileRecord)
+{
+    NTSTATUS Status;
+    FIND_ATTR_CONTXT Context;
+    PNTFS_ATTR_RECORD Attribute;
+    PSTANDARD_INFORMATION StdInfo;
+
+    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;
+        }
+
+        Status = FindNextAttribute(&Context, &Attribute);
+    }
+
+    FindCloseAttribute(&Context);
+    return NULL;
+}
+
+/**
+* @name GetFileNameAttributeLength
+* @implemented
+*
+* Returns the size of a given FILENAME_ATTRIBUTE, in bytes.
+*
+* @param FileNameAttribute
+* Pointer to a FILENAME_ATTRIBUTE to determine the size of.
+*
+* @remarks
+* The length of a FILENAME_ATTRIBUTE is variable and is dependent on the length of the file name stored at the end.
+* This function operates on the FILENAME_ATTRIBUTE proper, so don't try to pass it a PNTFS_ATTR_RECORD.
+*/
+ULONG GetFileNameAttributeLength(PFILENAME_ATTRIBUTE FileNameAttribute)
+{
+    ULONG Length = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + (FileNameAttribute->NameLength * sizeof(WCHAR));
+    return Length;
+}
+
+PFILENAME_ATTRIBUTE
+GetBestFileNameFromRecord(PDEVICE_EXTENSION Vcb,
+                          PFILE_RECORD_HEADER FileRecord)
+{
+    PFILENAME_ATTRIBUTE FileName;
+
+    FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_POSIX);
+    if (FileName == NULL)
+    {
+        FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_WIN32);
+        if (FileName == NULL)
+        {
+            FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_DOS);
+        }
+    }
+
+    return FileName;
 }
 
 /* EOF */