https://git.reactos.org/?p=reactos.git;a=commitdiff;h=a40ba448d401e53788db2…
commit a40ba448d401e53788db2356108130e81b597a6f
Author: Trevor Thompson <tmt256(a)email.vccs.edu>
AuthorDate: Fri Sep 1 00:27:34 2017 +0000
[NTFS] - Fix some errors that break building in C89 mode, and remove an extraneous "ninja livecd" that got inserted in a comment. Thanks to Doug Lyons for spotting these errors.
SplitBTree() - comment-out redundant code for finding the median key and improve comments.
svn path=/branches/GSoC_2016/NTFS/; revision=75727
---
drivers/filesystems/ntfs/btree.c | 30 ++++++++++++++++++++++--------
drivers/filesystems/ntfs/mft.c | 10 ++++++----
2 files changed, 28 insertions(+), 12 deletions(-)
diff --git a/drivers/filesystems/ntfs/btree.c b/drivers/filesystems/ntfs/btree.c
index bb745c5e9e..9fb1b29c0f 100644
--- a/drivers/filesystems/ntfs/btree.c
+++ b/drivers/filesystems/ntfs/btree.c
@@ -1139,7 +1139,7 @@ DemoteBTreeRoot(PB_TREE Tree)
#ifndef NDEBUG
DumpBTree(Tree);
-#endif;
+#endif
return STATUS_SUCCESS;
}
@@ -1779,7 +1779,7 @@ NtfsInsertKey(PB_TREE Tree,
#ifndef NDEBUG
DumpBTree(Tree);
-#endif NDEBUG
+#endif
}
}
else
@@ -1880,6 +1880,9 @@ SplitBTreeNode(PB_TREE Tree,
{
ULONG MedianKeyIndex;
PB_TREE_KEY LastKeyBeforeMedian, FirstKeyAfterMedian;
+ ULONG KeyCount;
+ ULONG HalfSize;
+ ULONG SizeSum;
ULONG i;
DPRINT1("SplitBTreeNode(%p, %p, %p, %p, %s) called\n",
@@ -1889,7 +1892,9 @@ SplitBTreeNode(PB_TREE Tree,
NewRightHandSibling,
CaseSensitive ? "TRUE" : "FALSE");
- //DumpBTreeNode(Node, 0, 0);
+#ifndef NDEBUG
+ DumpBTreeNode(Node, 0, 0);
+#endif
// Create the right hand sibling
*NewRightHandSibling = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_FILENAME_NODE), TAG_NTFS);
@@ -1901,20 +1906,29 @@ SplitBTreeNode(PB_TREE Tree,
RtlZeroMemory(*NewRightHandSibling, sizeof(B_TREE_FILENAME_NODE));
(*NewRightHandSibling)->DiskNeedsUpdating = TRUE;
+
+ // Find the last key before the median
+
+ // This is roughly how NTFS-3G calculates median, and it's not congruent with what Windows does:
+ /*
// find the median key index
MedianKeyIndex = (Node->KeyCount + 1) / 2;
MedianKeyIndex--;
- // Find the last key before the median
LastKeyBeforeMedian = Node->FirstKey;
for (i = 0; i < MedianKeyIndex - 1; i++)
- LastKeyBeforeMedian = LastKeyBeforeMedian->NextKey;
+ LastKeyBeforeMedian = LastKeyBeforeMedian->NextKey;*/
+
+ // The method we'll use is a little bit closer to how Windows determines the median but it's not identical.
+ // What Windows does is actually more complicated than this, I think because Windows allocates more slack space to Odd-numbered
+ // Index Records, leaving less room for index entries in these records (I haven't discovered why this is done).
+ // (Neither Windows nor chkdsk complain if we choose a different median than Windows would have chosen, as our median will be in the ballpark)
// Use size to locate the median key / index
- ULONG HalfSize = 2016; // half the allocated size after subtracting the first index entry offset (TODO: MATH)
- ULONG SizeSum = 0;
LastKeyBeforeMedian = Node->FirstKey;
MedianKeyIndex = 0;
+ HalfSize = 2016; // half the allocated size after subtracting the first index entry offset (TODO: MATH)
+ SizeSum = 0;
for (i = 0; i < Node->KeyCount; i++)
{
SizeSum += LastKeyBeforeMedian->IndexEntry->Length;
@@ -1989,7 +2003,7 @@ SplitBTreeNode(PB_TREE Tree,
// Update Node's KeyCount (remember to add 1 for the new dummy key)
Node->KeyCount = MedianKeyIndex + 2;
- ULONG KeyCount = CountBTreeKeys(Node->FirstKey);
+ KeyCount = CountBTreeKeys(Node->FirstKey);
ASSERT(Node->KeyCount == KeyCount);
// everything to the right of MedianKey becomes the right hand sibling of Node
diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c
index c4328a0491..9d96bad239 100644
--- a/drivers/filesystems/ntfs/mft.c
+++ b/drivers/filesystems/ntfs/mft.c
@@ -2138,6 +2138,9 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
ULONG MaxIndexRootSize;
PB_TREE_KEY NewLeftKey;
PB_TREE_FILENAME_NODE NewRightHandNode;
+ LARGE_INTEGER MinIndexRootSize;
+ ULONG NewMaxIndexRootSize;
+ ULONG NodeSize;
// Allocate memory for the parent directory
ParentFileRecord = ExAllocatePoolWithTag(NonPagedPool,
@@ -2287,7 +2290,6 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
// This a bit hacky, but it seems to be functional.
// Calculate the minimum size of the index root attribute, considering one dummy key and one VCN
- LARGE_INTEGER MinIndexRootSize;
MinIndexRootSize.QuadPart = sizeof(INDEX_ROOT_ATTRIBUTE) // size of the index root headers
+ 0x18; // Size of dummy key with a VCN for a subnode
ASSERT(MinIndexRootSize.QuadPart % ATTR_RECORD_ALIGNMENT == 0);
@@ -2335,7 +2337,7 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
// Find the maximum index root size given what the file record can hold
// First, find the max index size assuming index root is the last attribute
- ULONG NewMaxIndexRootSize =
+ NewMaxIndexRootSize =
DeviceExt->NtfsInfo.BytesPerFileRecord // Start with the size of a file record
- IndexRootOffset // Subtract the length of everything that comes before index root
- IndexRootContext->pRecord->Resident.ValueOffset // Subtract the length of the attribute header for index root
@@ -2361,7 +2363,7 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
// The index allocation and index bitmap may have grown, leaving less room for the index root,
// so now we need to double-check that index root isn't too large
- ULONG NodeSize = GetSizeOfIndexEntries(NewTree->RootNode);
+ NodeSize = GetSizeOfIndexEntries(NewTree->RootNode);
if (NodeSize > NewMaxIndexRootSize)
{
DPRINT1("Demoting index root.\nNodeSize: 0x%lx\nNewMaxIndexRootSize: 0x%lx\n", NodeSize, NewMaxIndexRootSize);
@@ -2624,7 +2626,7 @@ CompareFileName(PUNICODE_STRING FileName,
* @param Vcb
* Pointer to an NTFS_VCB for the volume whose Mft mirror is being updated.
*
-* @returninja livecd
+* @return
* STATUS_SUCCESS on success.
* STATUS_INSUFFICIENT_RESOURCES if an allocation failed.
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=52c30fdf37d3fd9a1583d…
commit 52c30fdf37d3fd9a1583d4052f6b11365099613c
Author: Trevor Thompson <tmt256(a)email.vccs.edu>
AuthorDate: Tue Aug 29 15:51:14 2017 +0000
[NTFS] - Add some helper functions for new features. Add some fixes. Add support for creating an index allocation, splitting a b-tree node, or "demoting" the index root. This allows for file creation without functional limitations.
+AddBitmap() - adds a $BITMAP attribute to a file record.
+AddIndexAllocation() - adds an $INDEX_ALLOCATION attribute to a file record.
+CountBTreeKeys() - Counts the number of linked B-Tree keys.
CreateIndexBufferFromBTreeNode() - Set INDEX_NODE_LARGE if the node has sub-nodes.
CreateIndexRootFromBTree() - Simplify the usage and math of MaxIndexSize; make it only account for the cumulative size of the index entries.
+DemoteBTreeRoot() - Replaces the contents of an index root with a dummy key, and puts those contents in a new node, which is made a child of the dummy key. This is done when an index root grows too large.
+GetIndexEntryVCN() - Retrieves the VCN from an index entry.
NtfsAddFilenameToDirectory() - Fix math for MaxIndexRootSize.
NtfsInsertKey() - Add support for splitting a B-Tree node. Don't check size of index root (that will be handled later).
+SplitBTreeNode() - Called when a B-Tree node grows too large.
UpdateIndexAllocation() - Create an $I30 index allocation attribute and bitmap attribute if needed.
UpdateIndexNode() - Update children before updating the current node. Store VCN of child nodes in the index entries of their respective keys.
svn path=/branches/GSoC_2016/NTFS/; revision=75707
---
drivers/filesystems/ntfs/attrib.c | 193 ++++++++++++
drivers/filesystems/ntfs/btree.c | 597 ++++++++++++++++++++++++++++++--------
drivers/filesystems/ntfs/mft.c | 186 +++++++++++-
drivers/filesystems/ntfs/ntfs.h | 35 ++-
4 files changed, 886 insertions(+), 125 deletions(-)
diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c
index 34bee2a060..846c16da80 100644
--- a/drivers/filesystems/ntfs/attrib.c
+++ b/drivers/filesystems/ntfs/attrib.c
@@ -36,6 +36,100 @@
/* FUNCTIONS ****************************************************************/
+/**
+* @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)
+{
+ 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
@@ -258,6 +352,105 @@ AddFileName(PFILE_RECORD_HEADER FileRecord,
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;
+
+ 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
diff --git a/drivers/filesystems/ntfs/btree.c b/drivers/filesystems/ntfs/btree.c
index cd9cf35069..bb745c5e9e 100644
--- a/drivers/filesystems/ntfs/btree.c
+++ b/drivers/filesystems/ntfs/btree.c
@@ -468,6 +468,33 @@ CompareTreeKeys(PB_TREE_KEY Key1, PB_TREE_KEY Key2, BOOLEAN CaseSensitive)
return Comparison;
}
+/**
+* @name CountBTreeKeys
+* @implemented
+*
+* Counts the number of linked B-Tree keys, starting with FirstKey.
+*
+* @param FirstKey
+* Pointer to a B_TREE_KEY that will be the first key to be counted.
+*
+* @return
+* The number of keys in a linked-list, including FirstKey and the final dummy key.
+*/
+ULONG
+CountBTreeKeys(PB_TREE_KEY FirstKey)
+{
+ ULONG Count = 0;
+ PB_TREE_KEY Current = FirstKey;
+
+ while (Current != NULL)
+ {
+ Count++;
+ Current = Current->NextKey;
+ }
+
+ return Count;
+}
+
PB_TREE_FILENAME_NODE
CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb,
PINDEX_ROOT_ATTRIBUTE IndexRoot,
@@ -852,6 +879,9 @@ GetSizeOfIndexEntries(PB_TREE_FILENAME_NODE Node)
*
* @param MaxIndexSize
* Describes how large the index can be before it will take too much space in the file record.
+* This is strictly the sum of the sizes of all index entries; it does not include the space
+* required by the index root header (INDEX_ROOT_ATTRIBUTE), since that size will be constant.
+*
* After reaching MaxIndexSize, an index can no longer be represented with just an index root
* attribute, and will require an index allocation and $I30 bitmap (TODO).
*
@@ -885,6 +915,10 @@ CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt,
DPRINT1("CreateIndexRootFromBTree(%p, %p, 0x%lx, %p, %p)\n", DeviceExt, Tree, MaxIndexSize, IndexRoot, Length);
+#ifndef NDEBUG
+ DumpBTree(Tree);
+#endif
+
if (!NewIndexRoot)
{
DPRINT1("Failed to allocate memory for Index Root!\n");
@@ -918,12 +952,10 @@ CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt,
for (i = 0; i < Tree->RootNode->KeyCount; i++)
{
// Would adding the current entry to the index increase the index size beyond the limit we've set?
- ULONG IndexSize = FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header)
- + NewIndexRoot->Header.TotalSizeOfEntries
- + CurrentNodeEntry->Length;
+ ULONG IndexSize = NewIndexRoot->Header.TotalSizeOfEntries - NewIndexRoot->Header.FirstEntryOffset + CurrentKey->IndexEntry->Length;
if (IndexSize > MaxIndexSize)
{
- DPRINT1("TODO: Adding file would require creating an index allocation!\n");
+ DPRINT1("TODO: Adding file would require creating an attribute list!\n");
ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
return STATUS_NOT_IMPLEMENTED;
}
@@ -962,6 +994,7 @@ NTSTATUS
CreateIndexBufferFromBTreeNode(PDEVICE_EXTENSION DeviceExt,
PB_TREE_FILENAME_NODE Node,
ULONG BufferSize,
+ BOOLEAN HasChildren,
PINDEX_BUFFER IndexBuffer)
{
ULONG i;
@@ -1014,7 +1047,9 @@ CreateIndexBufferFromBTreeNode(PDEVICE_EXTENSION DeviceExt,
// Add Length of Current Entry to Total Size of Entries
IndexBuffer->Header.TotalSizeOfEntries += CurrentNodeEntry->Length;
- // TODO: Check for child nodes
+ // Check for child nodes
+ if (HasChildren)
+ IndexBuffer->Header.Flags = INDEX_NODE_LARGE;
// Go to the next node entry
CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)CurrentNodeEntry + CurrentNodeEntry->Length);
@@ -1026,6 +1061,89 @@ CreateIndexBufferFromBTreeNode(PDEVICE_EXTENSION DeviceExt,
return Status;
}
+/**
+* @name DemoteBTreeRoot
+* @implemented
+*
+* Demoting the root means first putting all the keys in the root node into a new node, and making
+* the new node a child of a dummy key. The dummy key then becomes the sole contents of the root node.
+* The B-Tree gets one level deeper. This operation is needed when an index root grows too large for its file record.
+* Demotion is my own term; I might change the name later if I think of something more descriptive or can find
+* an appropriate name for this operation in existing B-Tree literature.
+*
+* @param Tree
+* Pointer to the B_TREE whose root is being demoted
+*
+* @returns
+* STATUS_SUCCESS on success.
+* STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
+*/
+NTSTATUS
+DemoteBTreeRoot(PB_TREE Tree)
+{
+ PB_TREE_FILENAME_NODE NewSubNode, NewIndexRoot;
+ PB_TREE_KEY DummyKey;
+
+ DPRINT1("Collapsing Index Root into sub-node.\n");
+
+#ifndef NDEBUG
+ DumpBTree(Tree);
+#endif
+
+ // Create a new node that will hold the keys currently in index root
+ NewSubNode = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_FILENAME_NODE), TAG_NTFS);
+ if (!NewSubNode)
+ {
+ DPRINT1("ERROR: Couldn't allocate memory for new sub-node.\n");
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+ RtlZeroMemory(NewSubNode, sizeof(B_TREE_FILENAME_NODE));
+
+ // Copy the applicable data from the old index root node
+ NewSubNode->KeyCount = Tree->RootNode->KeyCount;
+ NewSubNode->FirstKey = Tree->RootNode->FirstKey;
+ NewSubNode->DiskNeedsUpdating = TRUE;
+
+ // Create a new dummy key, and make the new node it's child
+ DummyKey = CreateDummyKey(TRUE);
+ if (!DummyKey)
+ {
+ DPRINT1("ERROR: Couldn't allocate memory for new root node.\n");
+ ExFreePoolWithTag(NewSubNode, TAG_NTFS);
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ // Make the new node a child of the dummy key
+ DummyKey->LesserChild = NewSubNode;
+
+ // Create a new index root node
+ NewIndexRoot = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_FILENAME_NODE), TAG_NTFS);
+ if (!NewIndexRoot)
+ {
+ DPRINT1("ERROR: Couldn't allocate memory for new index root.\n");
+ ExFreePoolWithTag(NewSubNode, TAG_NTFS);
+ ExFreePoolWithTag(DummyKey, TAG_NTFS);
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+ RtlZeroMemory(NewIndexRoot, sizeof(B_TREE_FILENAME_NODE));
+
+ NewIndexRoot->DiskNeedsUpdating = TRUE;
+
+ // Insert the dummy key into the new node
+ NewIndexRoot->FirstKey = DummyKey;
+ NewIndexRoot->KeyCount = 1;
+ NewIndexRoot->DiskNeedsUpdating = TRUE;
+
+ // Make the new node the Tree's root node
+ Tree->RootNode = NewIndexRoot;
+
+#ifndef NDEBUG
+ DumpBTree(Tree);
+#endif;
+
+ return STATUS_SUCCESS;
+}
+
/**
* @name SetIndexEntryVCN
* @implemented
@@ -1060,7 +1178,7 @@ UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt,
PFILE_RECORD_HEADER FileRecord)
{
// Find the index allocation and bitmap
- PNTFS_ATTR_CONTEXT IndexAllocationContext, BitmapContext;
+ PNTFS_ATTR_CONTEXT IndexAllocationContext;
PB_TREE_KEY CurrentKey;
NTSTATUS Status;
BOOLEAN HasIndexAllocation = FALSE;
@@ -1074,14 +1192,12 @@ UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt,
{
HasIndexAllocation = TRUE;
+#ifndef NDEBUG
PrintAllVCNs(DeviceExt,
IndexAllocationContext,
IndexBufferSize);
+#endif
}
-
- // TODO: Handle bitmap
- BitmapContext = NULL;
-
// Walk through the root node and update all the sub-nodes
CurrentKey = Tree->RootNode->FirstKey;
for (i = 0; i < Tree->RootNode->KeyCount; i++)
@@ -1090,8 +1206,46 @@ UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt,
{
if (!HasIndexAllocation)
{
- DPRINT1("FIXME: Need to add index allocation\n");
- return STATUS_NOT_IMPLEMENTED;
+ // We need to add an index allocation to the file record
+ PNTFS_ATTR_RECORD EndMarker = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->BytesInUse - (sizeof(ULONG) * 2));
+ DPRINT1("Adding index allocation...\n");
+
+ // Add index allocation to the very end of the file record
+ Status = AddIndexAllocation(DeviceExt,
+ FileRecord,
+ EndMarker,
+ L"$I30",
+ 4);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Failed to add index allocation!\n");
+ return Status;
+ }
+
+ // Find the new attribute
+ Status = FindAttribute(DeviceExt, FileRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationContext, &IndexAllocationOffset);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Couldn't find newly-created index allocation!\n");
+ return Status;
+ }
+
+ // Advance end marker
+ EndMarker = (PNTFS_ATTR_RECORD)((ULONG_PTR)EndMarker + EndMarker->Length);
+
+ // Add index bitmap to the very end of the file record
+ Status = AddBitmap(DeviceExt,
+ FileRecord,
+ EndMarker,
+ L"$I30",
+ 4);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Failed to add index bitmap!\n");
+ return Status;
+ }
+
+ HasIndexAllocation = TRUE;
}
// Is the Index Entry large enough to store the VCN?
@@ -1122,7 +1276,7 @@ UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt,
}
// Update the sub-node
- Status = UpdateIndexNode(DeviceExt, FileRecord, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, IndexAllocationOffset, BitmapContext);
+ Status = UpdateIndexNode(DeviceExt, FileRecord, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, IndexAllocationOffset);
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR: Failed to update index node!\n");
@@ -1136,12 +1290,17 @@ UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt,
CurrentKey = CurrentKey->NextKey;
}
+#ifndef NDEBUG
+ DumpBTree(Tree);
+#endif
+
if (HasIndexAllocation)
{
+#ifndef NDEBUG
PrintAllVCNs(DeviceExt,
IndexAllocationContext,
IndexBufferSize);
-
+#endif
ReleaseAttributeContext(IndexAllocationContext);
}
@@ -1154,22 +1313,74 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt,
PB_TREE_FILENAME_NODE Node,
ULONG IndexBufferSize,
PNTFS_ATTR_CONTEXT IndexAllocationContext,
- ULONG IndexAllocationOffset,
- PNTFS_ATTR_CONTEXT BitmapContext)
+ ULONG IndexAllocationOffset)
{
ULONG i;
PB_TREE_KEY CurrentKey = Node->FirstKey;
+ BOOLEAN HasChildren = FALSE;
NTSTATUS Status;
- DPRINT1("UpdateIndexNode(%p, %p, %p, %lu, %p, %lu, %p) called for index node with VCN %I64u\n",
- DeviceExt,
- FileRecord,
- Node,
- IndexBufferSize,
- IndexAllocationContext,
- IndexAllocationOffset,
- BitmapContext,
- Node->VCN);
+
+ DPRINT("UpdateIndexNode(%p, %p, %p, %lu, %p, %lu) called for index node with VCN %I64u\n",
+ DeviceExt,
+ FileRecord,
+ Node,
+ IndexBufferSize,
+ IndexAllocationContext,
+ IndexAllocationOffset,
+ Node->VCN);
+
+ // Walk through the node and look for children to update
+ for (i = 0; i < Node->KeyCount; i++)
+ {
+ ASSERT(CurrentKey);
+
+ // If there's a child node
+ if (CurrentKey->LesserChild)
+ {
+ HasChildren = TRUE;
+
+ // Update the child node on disk
+ Status = UpdateIndexNode(DeviceExt, FileRecord, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, IndexAllocationOffset);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Failed to update child node!\n");
+ return Status;
+ }
+
+ // Is the Index Entry large enough to store the VCN?
+ if (!CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)
+ {
+ // Allocate memory for the larger index entry
+ PINDEX_ENTRY_ATTRIBUTE NewEntry = ExAllocatePoolWithTag(NonPagedPool,
+ CurrentKey->IndexEntry->Length + sizeof(ULONGLONG),
+ TAG_NTFS);
+ if (!NewEntry)
+ {
+ DPRINT1("ERROR: Unable to allocate memory for new index entry!\n");
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ // Copy the old entry to the new one
+ RtlCopyMemory(NewEntry, CurrentKey->IndexEntry, CurrentKey->IndexEntry->Length);
+
+ NewEntry->Length += sizeof(ULONGLONG);
+
+ // Free the old memory
+ ExFreePoolWithTag(CurrentKey->IndexEntry, TAG_NTFS);
+
+ CurrentKey->IndexEntry = NewEntry;
+ }
+
+ // Update the VCN stored in the index entry of CurrentKey
+ SetIndexEntryVCN(CurrentKey->IndexEntry, CurrentKey->LesserChild->VCN);
+
+ CurrentKey->IndexEntry->Flags |= NTFS_INDEX_ENTRY_NODE;
+ }
+
+ CurrentKey = CurrentKey->NextKey;
+ }
+
// Do we need to write this node to disk?
if (Node->DiskNeedsUpdating)
@@ -1206,7 +1417,7 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt,
}
// Create the index buffer we'll be writing to disk to represent this node
- Status = CreateIndexBufferFromBTreeNode(DeviceExt, Node, IndexBufferSize, IndexBuffer);
+ Status = CreateIndexBufferFromBTreeNode(DeviceExt, Node, IndexBufferSize, HasChildren, IndexBuffer);
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR: Failed to create index buffer from node!\n");
@@ -1235,26 +1446,6 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt,
ExFreePoolWithTag(IndexBuffer, TAG_NTFS);
}
- // Walk through the node and look for children to update
- for (i = 0; i < Node->KeyCount; i++)
- {
- ASSERT(CurrentKey);
-
- // If there's a child node
- if (CurrentKey->LesserChild)
- {
- // Update the child node on disk
- Status = UpdateIndexNode(DeviceExt, FileRecord, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, IndexAllocationOffset, BitmapContext);
- if (!NT_SUCCESS(Status))
- {
- DPRINT1("ERROR: Failed to update child node!\n");
- return Status;
- }
- }
-
- CurrentKey = CurrentKey->NextKey;
- }
-
return STATUS_SUCCESS;
}
@@ -1438,6 +1629,16 @@ GetAllocationOffsetFromVCN(PDEVICE_EXTENSION DeviceExt,
return Vcn * DeviceExt->NtfsInfo.BytesPerCluster;
}
+ULONGLONG
+GetIndexEntryVCN(PINDEX_ENTRY_ATTRIBUTE IndexEntry)
+{
+ PULONGLONG Destination = (PULONGLONG)((ULONG_PTR)IndexEntry + IndexEntry->Length - sizeof(ULONGLONG));
+
+ ASSERT(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE);
+
+ return *Destination;
+}
+
/**
* @name NtfsInsertKey
* @implemented
@@ -1464,6 +1665,17 @@ GetAllocationOffsetFromVCN(PDEVICE_EXTENSION DeviceExt,
* The maximum size, in bytes, of node entries that can be stored in the index root before it will grow too large for
* the file record. This number is just the size of the entries, without any headers for the attribute or index root.
*
+* @param IndexRecordSize
+* The size, in bytes, of an index record for this index. AKA an index buffer. Usually set to 4096.
+*
+* @param MedianKey
+* Pointer to a PB_TREE_KEY that will receive a pointer to the median key, should the node grow too large and need to be split.
+* Will be set to NULL if the node isn't split.
+*
+* @param NewRightHandSibling
+* Pointer to a PB_TREE_FILENAME_NODE that will receive a pointer to a newly-created right-hand sibling node,
+* should the node grow too large and need to be split. Will be set to NULL if the node isn't split.
+*
* @remarks
* A node is always sorted, with the least comparable filename stored first and a dummy key to mark the end.
*/
@@ -1473,7 +1685,10 @@ NtfsInsertKey(PB_TREE Tree,
PFILENAME_ATTRIBUTE FileNameAttribute,
PB_TREE_FILENAME_NODE Node,
BOOLEAN CaseSensitive,
- ULONG MaxIndexRootSize)
+ ULONG MaxIndexRootSize,
+ ULONG IndexRecordSize,
+ PB_TREE_KEY *MedianKey,
+ PB_TREE_FILENAME_NODE *NewRightHandSibling)
{
PB_TREE_KEY NewKey, CurrentKey, PreviousKey;
NTSTATUS Status = STATUS_SUCCESS;
@@ -1482,13 +1697,19 @@ NtfsInsertKey(PB_TREE Tree,
ULONG MaxNodeSizeWithoutHeader;
ULONG i;
- DPRINT1("NtfsInsertKey(%p, 0x%I64x, %p, %p, %s, %lu)\n",
+ *MedianKey = NULL;
+ *NewRightHandSibling = NULL;
+
+ DPRINT1("NtfsInsertKey(%p, 0x%I64x, %p, %p, %s, %lu, %lu, %p, %p)\n",
Tree,
FileReference,
FileNameAttribute,
Node,
CaseSensitive ? "TRUE" : "FALSE",
- MaxIndexRootSize);
+ MaxIndexRootSize,
+ IndexRecordSize,
+ MedianKey,
+ NewRightHandSibling);
// Create the key for the filename attribute
NewKey = CreateBTreeKeyFromFilename(FileReference, FileNameAttribute);
@@ -1513,12 +1734,22 @@ NtfsInsertKey(PB_TREE Tree,
// Is NewKey < CurrentKey?
if (Comparison < 0)
{
-
// Does CurrentKey have a sub-node?
if (CurrentKey->LesserChild)
{
+ PB_TREE_KEY NewLeftKey;
+ PB_TREE_FILENAME_NODE NewChild;
+
// Insert the key into the child node
- Status = NtfsInsertKey(Tree, FileReference, FileNameAttribute, CurrentKey->LesserChild, CaseSensitive, 0);
+ Status = NtfsInsertKey(Tree,
+ FileReference,
+ FileNameAttribute,
+ CurrentKey->LesserChild,
+ CaseSensitive,
+ MaxIndexRootSize,
+ IndexRecordSize,
+ &NewLeftKey,
+ &NewChild);
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR: Failed to insert key.\n");
@@ -1526,6 +1757,30 @@ NtfsInsertKey(PB_TREE Tree,
return Status;
}
+ // Did the child node get split?
+ if (NewLeftKey)
+ {
+ ASSERT(NewChild != NULL);
+
+ // Insert the new left key to the left of the current key
+ NewLeftKey->NextKey = CurrentKey;
+
+ // Is CurrentKey the first key?
+ if (!PreviousKey)
+ Node->FirstKey = NewLeftKey;
+ else
+ PreviousKey->NextKey = NewLeftKey;
+
+ // CurrentKey->LesserChild will be the right-hand sibling
+ CurrentKey->LesserChild = NewChild;
+
+ Node->KeyCount++;
+ Node->DiskNeedsUpdating = TRUE;
+
+#ifndef NDEBUG
+ DumpBTree(Tree);
+#endif NDEBUG
+ }
}
else
{
@@ -1541,8 +1796,8 @@ NtfsInsertKey(PB_TREE Tree,
Node->FirstKey = NewKey;
else
PreviousKey->NextKey = NewKey;
- break;
}
+ break;
}
PreviousKey = CurrentKey;
@@ -1552,88 +1807,202 @@ NtfsInsertKey(PB_TREE Tree,
// Determine how much space the index entries will need
NodeSize = GetSizeOfIndexEntries(Node);
- // Is Node the root node?
- if (Node == Tree->RootNode)
+ // Is Node not the root node?
+ if (Node != Tree->RootNode)
{
- // Is the index root too large for the file record?
- if (NodeSize > MaxIndexRootSize)
- {
- PB_TREE_FILENAME_NODE NewSubNode, NewIndexRoot;
- PB_TREE_KEY DummyKey;
+ // Calculate maximum size of index entries without any headers
+ AllocatedNodeSize = IndexRecordSize - FIELD_OFFSET(INDEX_BUFFER, Header);
- DPRINT1("Collapsing Index Root into sub-node.\n") ;
+ // TODO: Replace magic with math
+ MaxNodeSizeWithoutHeader = AllocatedNodeSize - 0x28;
+
+ // Has the node grown larger than its allocated size?
+ if (NodeSize > MaxNodeSizeWithoutHeader)
+ {
+ NTSTATUS Status;
- DumpBTree(Tree);
-
- // Create a new node that will hold the keys currently in index root
- NewSubNode = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_FILENAME_NODE), TAG_NTFS);
- if (!NewSubNode)
+ Status = SplitBTreeNode(Tree, Node, MedianKey, NewRightHandSibling, CaseSensitive);
+ if (!NT_SUCCESS(Status))
{
- DPRINT1("ERROR: Couldn't allocate memory for new sub-node.\n");
- return STATUS_INSUFFICIENT_RESOURCES;
+ DPRINT1("ERROR: Failed to split B-Tree node!\n");
+ return Status;
}
- RtlZeroMemory(NewSubNode, sizeof(B_TREE_FILENAME_NODE));
- // Copy the applicable data from the old index root node
- NewSubNode->KeyCount = Node->KeyCount;
- NewSubNode->FirstKey = Node->FirstKey;
- NewSubNode->DiskNeedsUpdating = TRUE;
+ return Status;
+ }
+ }
- // Create a new dummy key, and make the new node it's child
- DummyKey = CreateDummyKey(TRUE);
- if (!DummyKey)
- {
- DPRINT1("ERROR: Couldn't allocate memory for new root node.\n");
- ExFreePoolWithTag(NewSubNode, TAG_NTFS);
- return STATUS_INSUFFICIENT_RESOURCES;
- }
+ // NewEntry and NewKey will be destroyed later by DestroyBTree()
- // Make the new node a child of the dummy key
- DummyKey->LesserChild = NewSubNode;
+ return Status;
+}
- // Create a new index root node
- NewIndexRoot = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_FILENAME_NODE), TAG_NTFS);
- if (!NewIndexRoot)
- {
- DPRINT1("ERROR: Couldn't allocate memory for new index root.\n");
- ExFreePoolWithTag(NewSubNode, TAG_NTFS);
- ExFreePoolWithTag(DummyKey, TAG_NTFS);
- return STATUS_INSUFFICIENT_RESOURCES;
- }
- RtlZeroMemory(NewIndexRoot, sizeof(B_TREE_FILENAME_NODE));
- NewIndexRoot->DiskNeedsUpdating = TRUE;
- // Insert the dummy key into the new node
- NewIndexRoot->FirstKey = DummyKey;
- NewIndexRoot->KeyCount = 1;
- NewIndexRoot->DiskNeedsUpdating = TRUE;
+/**
+* @name SplitBTreeNode
+* @implemented
+*
+* Splits a B-Tree node that has grown too large. Finds the median key and sets up a right-hand-sibling
+* node to contain the keys to the right of the median key.
+*
+* @param Tree
+* Pointer to the B_TREE which contains the node being split
+*
+* @param Node
+* Pointer to the B_TREE_FILENAME_NODE that needs to be split
+*
+* @param MedianKey
+* Pointer a PB_TREE_KEY that will receive the pointer to the key in the middle of the node being split
+*
+* @param NewRightHandSibling
+* Pointer to a PB_TREE_FILENAME_NODE that will receive a pointer to a newly-created B_TREE_FILENAME_NODE
+* containing the keys to the right of MedianKey.
+*
+* @param CaseSensitive
+* Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
+* if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag.
+*
+* @return
+* STATUS_SUCCESS on success.
+* STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
+*
+* @remarks
+* It's the responsibility of the caller to insert the new median key into the parent node, as well as making the
+* NewRightHandSibling the lesser child of the node that is currently Node's parent.
+*/
+NTSTATUS
+SplitBTreeNode(PB_TREE Tree,
+ PB_TREE_FILENAME_NODE Node,
+ PB_TREE_KEY *MedianKey,
+ PB_TREE_FILENAME_NODE *NewRightHandSibling,
+ BOOLEAN CaseSensitive)
+{
+ ULONG MedianKeyIndex;
+ PB_TREE_KEY LastKeyBeforeMedian, FirstKeyAfterMedian;
+ ULONG i;
- // Make the new node the Tree's root node
- Tree->RootNode = NewIndexRoot;
+ DPRINT1("SplitBTreeNode(%p, %p, %p, %p, %s) called\n",
+ Tree,
+ Node,
+ MedianKey,
+ NewRightHandSibling,
+ CaseSensitive ? "TRUE" : "FALSE");
- DumpBTree(Tree);
+ //DumpBTreeNode(Node, 0, 0);
- return STATUS_SUCCESS;
- }
+ // Create the right hand sibling
+ *NewRightHandSibling = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_FILENAME_NODE), TAG_NTFS);
+ if (*NewRightHandSibling == NULL)
+ {
+ DPRINT1("Error: Failed to allocate memory for right hand sibling!\n");
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+ RtlZeroMemory(*NewRightHandSibling, sizeof(B_TREE_FILENAME_NODE));
+ (*NewRightHandSibling)->DiskNeedsUpdating = TRUE;
+
+ // find the median key index
+ MedianKeyIndex = (Node->KeyCount + 1) / 2;
+ MedianKeyIndex--;
+
+ // Find the last key before the median
+ LastKeyBeforeMedian = Node->FirstKey;
+ for (i = 0; i < MedianKeyIndex - 1; i++)
+ LastKeyBeforeMedian = LastKeyBeforeMedian->NextKey;
+
+ // Use size to locate the median key / index
+ ULONG HalfSize = 2016; // half the allocated size after subtracting the first index entry offset (TODO: MATH)
+ ULONG SizeSum = 0;
+ LastKeyBeforeMedian = Node->FirstKey;
+ MedianKeyIndex = 0;
+ for (i = 0; i < Node->KeyCount; i++)
+ {
+ SizeSum += LastKeyBeforeMedian->IndexEntry->Length;
+
+ if (SizeSum > HalfSize)
+ break;
+
+ MedianKeyIndex++;
+ LastKeyBeforeMedian = LastKeyBeforeMedian->NextKey;
+ }
+
+ // Now we can get the median key and the key that follows it
+ *MedianKey = LastKeyBeforeMedian->NextKey;
+ FirstKeyAfterMedian = (*MedianKey)->NextKey;
+
+ DPRINT1("%lu keys, %lu median\n", Node->KeyCount, MedianKeyIndex);
+ DPRINT1("\t\tMedian: %.*S\n", (*MedianKey)->IndexEntry->FileName.NameLength, (*MedianKey)->IndexEntry->FileName.Name);
+
+ // "Node" will be the left hand sibling after the split, containing all keys prior to the median key
+
+ // We need to create a dummy pointer at the end of the LHS. The dummy's child will be the median's child.
+ LastKeyBeforeMedian->NextKey = CreateDummyKey(BooleanFlagOn((*MedianKey)->IndexEntry->Flags, NTFS_INDEX_ENTRY_NODE));
+ if (LastKeyBeforeMedian->NextKey == NULL)
+ {
+ DPRINT1("Error: Couldn't allocate dummy key!\n");
+ LastKeyBeforeMedian->NextKey = *MedianKey;
+ ExFreePoolWithTag(*NewRightHandSibling, TAG_NTFS);
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ // Did the median key have a child node?
+ if ((*MedianKey)->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)
+ {
+ // Set the child of the new dummy key
+ LastKeyBeforeMedian->NextKey->LesserChild = (*MedianKey)->LesserChild;
+
+ // Give the dummy key's index entry the same sub-node VCN the median
+ SetIndexEntryVCN(LastKeyBeforeMedian->NextKey->IndexEntry, GetIndexEntryVCN((*MedianKey)->IndexEntry));
}
else
{
- // TEMPTEMP: TODO: MATH
- AllocatedNodeSize = 0xfe8;
- MaxNodeSizeWithoutHeader = AllocatedNodeSize - 0x28;
-
- // Has the node grown larger than its allocated size?
- if (NodeSize > MaxNodeSizeWithoutHeader)
+ // Median key didn't have a child node, but it will. Create a new index entry large enough to store a VCN.
+ PINDEX_ENTRY_ATTRIBUTE NewIndexEntry = ExAllocatePoolWithTag(NonPagedPool,
+ (*MedianKey)->IndexEntry->Length + sizeof(ULONGLONG),
+ TAG_NTFS);
+ if (!NewIndexEntry)
{
- DPRINT1("FIXME: Splitting a node is still a WIP!\n");
- //SplitBTreeNode(NULL, Node);
- //DumpBTree(Tree);
- return STATUS_NOT_IMPLEMENTED;
+ DPRINT1("Unable to allocate memory for new index entry!\n");
+ LastKeyBeforeMedian->NextKey = *MedianKey;
+ ExFreePoolWithTag(*NewRightHandSibling, TAG_NTFS);
+ return STATUS_INSUFFICIENT_RESOURCES;
}
+
+ // Copy the old index entry to the new one
+ RtlCopyMemory(NewIndexEntry, (*MedianKey)->IndexEntry, (*MedianKey)->IndexEntry->Length);
+
+ // Use the new index entry after freeing the old one
+ ExFreePoolWithTag((*MedianKey)->IndexEntry, TAG_NTFS);
+ (*MedianKey)->IndexEntry = NewIndexEntry;
+
+ // Update the length for the VCN
+ (*MedianKey)->IndexEntry->Length += sizeof(ULONGLONG);
+
+ // Set the node flag
+ (*MedianKey)->IndexEntry->Flags |= NTFS_INDEX_ENTRY_NODE;
}
- // NewEntry and NewKey will be destroyed later by DestroyBTree()
+ // "Node" will become the child of the median key
+ (*MedianKey)->LesserChild = Node;
+ SetIndexEntryVCN((*MedianKey)->IndexEntry, Node->VCN);
- return Status;
+ // Update Node's KeyCount (remember to add 1 for the new dummy key)
+ Node->KeyCount = MedianKeyIndex + 2;
+
+ ULONG KeyCount = CountBTreeKeys(Node->FirstKey);
+ ASSERT(Node->KeyCount == KeyCount);
+
+ // everything to the right of MedianKey becomes the right hand sibling of Node
+ (*NewRightHandSibling)->FirstKey = FirstKeyAfterMedian;
+ (*NewRightHandSibling)->KeyCount = CountBTreeKeys(FirstKeyAfterMedian);
+
+#ifndef NDEBUG
+ DPRINT1("Left-hand node after split:\n");
+ DumpBTreeNode(Node, 0, 0);
+
+ DPRINT1("Right-hand sibling node after split:\n");
+ DumpBTreeNode(*NewRightHandSibling, 0, 0);
+#endif
+
+ return STATUS_SUCCESS;
}
\ No newline at end of file
diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c
index 547ab64a2b..c4328a0491 100644
--- a/drivers/filesystems/ntfs/mft.c
+++ b/drivers/filesystems/ntfs/mft.c
@@ -786,6 +786,11 @@ SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
DestinationAttribute->NonResident.AllocatedSize = AllocationSize;
DestinationAttribute->NonResident.DataSize = DataSize->QuadPart;
DestinationAttribute->NonResident.InitializedSize = DataSize->QuadPart;
+
+ // HighestVCN seems to be set incorrectly somewhere. Apply a hack-fix to reset it.
+ // HACKHACK FIXME: Fix for sparse files; this math won't work in that case.
+ AttrContext->pRecord->NonResident.HighestVCN = ((ULONGLONG)AllocationSize / Vcb->NtfsInfo.BytesPerCluster) - 1;
+ DestinationAttribute->NonResident.HighestVCN = AttrContext->pRecord->NonResident.HighestVCN;
DPRINT("Allocated Size: %I64u\n", DestinationAttribute->NonResident.AllocatedSize);
@@ -2131,6 +2136,8 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
PB_TREE NewTree;
ULONG BtreeIndexLength;
ULONG MaxIndexRootSize;
+ PB_TREE_KEY NewLeftKey;
+ PB_TREE_FILENAME_NODE NewRightHandNode;
// Allocate memory for the parent directory
ParentFileRecord = ExAllocatePoolWithTag(NonPagedPool,
@@ -2152,8 +2159,10 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
return Status;
}
+#ifndef NDEBUG
DPRINT1("Dumping old parent file record:\n");
NtfsDumpFileRecord(DeviceExt, ParentFileRecord);
+#endif
// Find the index root attribute for the directory
Status = FindAttribute(DeviceExt,
@@ -2176,7 +2185,7 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
MaxIndexRootSize = DeviceExt->NtfsInfo.BytesPerFileRecord // Start with the size of a file record
- IndexRootOffset // Subtract the length of everything that comes before index root
- IndexRootContext->pRecord->Resident.ValueOffset // Subtract the length of the attribute header for index root
- - FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header) // Subtract the length of the index header for index root
+ - sizeof(INDEX_ROOT_ATTRIBUTE) // Subtract the length of the index root header
- (sizeof(ULONG) * 2); // Subtract the length of the file record end marker and padding
// Are there attributes after this one?
@@ -2233,10 +2242,20 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
return Status;
}
+#ifndef NDEBUG
DumpBTree(NewTree);
+#endif
// Insert the key for the file we're adding
- Status = NtfsInsertKey(NewTree, FileReferenceNumber, FilenameAttribute, NewTree->RootNode, CaseSensitive, MaxIndexRootSize);
+ Status = NtfsInsertKey(NewTree,
+ FileReferenceNumber,
+ FilenameAttribute,
+ NewTree->RootNode,
+ CaseSensitive,
+ MaxIndexRootSize,
+ I30IndexRoot->SizeOfEntry,
+ &NewLeftKey,
+ &NewRightHandNode);
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR: Failed to insert key into B-Tree!\n");
@@ -2247,9 +2266,57 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
return Status;
}
+#ifndef NDEBUG
DumpBTree(NewTree);
+#endif
+
+ // The root node can't be split
+ ASSERT(NewLeftKey == NULL);
+ ASSERT(NewRightHandNode == NULL);
+
+ // Convert B*Tree back to Index
+
+ // Updating the index allocation can change the size available for the index root,
+ // And if the index root is demoted, the index allocation will need to be updated again,
+ // which may change the size available for index root... etc.
+ // My solution is to decrease index root to the size it would be if it was demoted,
+ // then UpdateIndexAllocation will have an accurate representation of the maximum space
+ // it can use in the file record. There's still a chance that the act of allocating an
+ // index node after demoting the index root will increase the size of the file record beyond
+ // it's limit, but if that happens, an attribute-list will most definitely be needed.
+ // This a bit hacky, but it seems to be functional.
+
+ // Calculate the minimum size of the index root attribute, considering one dummy key and one VCN
+ LARGE_INTEGER MinIndexRootSize;
+ MinIndexRootSize.QuadPart = sizeof(INDEX_ROOT_ATTRIBUTE) // size of the index root headers
+ + 0x18; // Size of dummy key with a VCN for a subnode
+ ASSERT(MinIndexRootSize.QuadPart % ATTR_RECORD_ALIGNMENT == 0);
+
+ // Temporarily shrink the index root to it's minimal size
+ AttributeLength = MinIndexRootSize.LowPart;
+ AttributeLength += sizeof(INDEX_ROOT_ATTRIBUTE);
+
+
+ // FIXME: IndexRoot will probably be invalid until we're finished. If we fail before we finish, the directory will probably be toast.
+ // The potential for catastrophic data-loss exists!!! :)
+
+ // Update the length of the attribute in the file record of the parent directory
+ Status = InternalSetResidentAttributeLength(DeviceExt,
+ IndexRootContext,
+ ParentFileRecord,
+ IndexRootOffset,
+ AttributeLength);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Unable to set length of index root!\n");
+ DestroyBTree(NewTree);
+ ReleaseAttributeContext(IndexRootContext);
+ ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
+ ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
+ return Status;
+ }
- // Convert B*Tree back to Index, starting with the index allocation
+ // Update the index allocation
Status = UpdateIndexAllocation(DeviceExt, NewTree, I30IndexRoot->SizeOfEntry, ParentFileRecord);
if (!NT_SUCCESS(Status))
{
@@ -2261,8 +2328,99 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
return Status;
}
+#ifndef NDEBUG
+ DPRINT1("Index Allocation updated\n");
+ DumpBTree(NewTree);
+#endif
+
+ // Find the maximum index root size given what the file record can hold
+ // First, find the max index size assuming index root is the last attribute
+ ULONG NewMaxIndexRootSize =
+ DeviceExt->NtfsInfo.BytesPerFileRecord // Start with the size of a file record
+ - IndexRootOffset // Subtract the length of everything that comes before index root
+ - IndexRootContext->pRecord->Resident.ValueOffset // Subtract the length of the attribute header for index root
+ - sizeof(INDEX_ROOT_ATTRIBUTE) // Subtract the length of the index root header
+ - (sizeof(ULONG) * 2); // Subtract the length of the file record end marker and padding
+
+ // Are there attributes after this one?
+ NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset + IndexRootContext->pRecord->Length);
+ if (NextAttribute->Type != AttributeEnd)
+ {
+ // Find the length of all attributes after this one, not counting the end marker
+ ULONG LengthOfAttributes = 0;
+ PNTFS_ATTR_RECORD CurrentAttribute = NextAttribute;
+ while (CurrentAttribute->Type != AttributeEnd)
+ {
+ LengthOfAttributes += CurrentAttribute->Length;
+ CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length);
+ }
+
+ // Leave room for the existing attributes
+ NewMaxIndexRootSize -= LengthOfAttributes;
+ }
+
+ // The index allocation and index bitmap may have grown, leaving less room for the index root,
+ // so now we need to double-check that index root isn't too large
+ ULONG NodeSize = GetSizeOfIndexEntries(NewTree->RootNode);
+ if (NodeSize > NewMaxIndexRootSize)
+ {
+ DPRINT1("Demoting index root.\nNodeSize: 0x%lx\nNewMaxIndexRootSize: 0x%lx\n", NodeSize, NewMaxIndexRootSize);
+
+ Status = DemoteBTreeRoot(NewTree);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Failed to demote index root!\n");
+ DestroyBTree(NewTree);
+ ReleaseAttributeContext(IndexRootContext);
+ ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
+ ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
+ return Status;
+ }
+
+ // We need to update the index allocation once more
+ Status = UpdateIndexAllocation(DeviceExt, NewTree, I30IndexRoot->SizeOfEntry, ParentFileRecord);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Failed to update index allocation from B-Tree!\n");
+ DestroyBTree(NewTree);
+ ReleaseAttributeContext(IndexRootContext);
+ ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
+ ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
+ return Status;
+ }
+
+ // re-recalculate max size of index root
+ NewMaxIndexRootSize =
+ // Find the maximum index size given what the file record can hold
+ // First, find the max index size assuming index root is the last attribute
+ DeviceExt->NtfsInfo.BytesPerFileRecord // Start with the size of a file record
+ - IndexRootOffset // Subtract the length of everything that comes before index root
+ - IndexRootContext->pRecord->Resident.ValueOffset // Subtract the length of the attribute header for index root
+ - sizeof(INDEX_ROOT_ATTRIBUTE) // Subtract the length of the index root header
+ - (sizeof(ULONG) * 2); // Subtract the length of the file record end marker and padding
+
+ // Are there attributes after this one?
+ NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset + IndexRootContext->pRecord->Length);
+ if (NextAttribute->Type != AttributeEnd)
+ {
+ // Find the length of all attributes after this one, not counting the end marker
+ ULONG LengthOfAttributes = 0;
+ PNTFS_ATTR_RECORD CurrentAttribute = NextAttribute;
+ while (CurrentAttribute->Type != AttributeEnd)
+ {
+ LengthOfAttributes += CurrentAttribute->Length;
+ CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length);
+ }
+
+ // Leave room for the existing attributes
+ NewMaxIndexRootSize -= LengthOfAttributes;
+ }
+
+
+ }
+
// Create the Index Root from the B*Tree
- Status = CreateIndexRootFromBTree(DeviceExt, NewTree, MaxIndexRootSize, &NewIndexRoot, &BtreeIndexLength);
+ Status = CreateIndexRootFromBTree(DeviceExt, NewTree, NewMaxIndexRootSize, &NewIndexRoot, &BtreeIndexLength);
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR: Failed to create Index root from B-Tree!\n");
@@ -2280,9 +2438,7 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
// First, we need to resize the attribute.
// CreateIndexRootFromBTree() should have verified that the index root fits within MaxIndexSize.
- // We can't set the size as we normally would, because if we extend past the file record,
- // we must create an index allocation and index bitmap (TODO). Also TODO: support file records with
- // $ATTRIBUTE_LIST's.
+ // We can't set the size as we normally would, because $INDEX_ROOT must always be resident.
AttributeLength = NewIndexRoot->Header.AllocatedSize + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header);
if (AttributeLength != IndexRootContext->pRecord->Resident.ValueLength)
@@ -2344,8 +2500,22 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
}
else
{
- DPRINT1("Dumping new parent file record:\n");
+#ifndef NDEBUG
+ DPRINT1("Dumping new B-Tree:\n");
+
+ Status = CreateBTreeFromIndex(DeviceExt, ParentFileRecord, IndexRootContext, NewIndexRoot, &NewTree);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Couldn't re-create b-tree\n");
+ return Status;
+ }
+
+ DumpBTree(NewTree);
+
+ DestroyBTree(NewTree);
+
NtfsDumpFileRecord(DeviceExt, ParentFileRecord);
+#endif
}
// Cleanup
diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h
index aba8b29196..5c709c61e9 100644
--- a/drivers/filesystems/ntfs/ntfs.h
+++ b/drivers/filesystems/ntfs/ntfs.h
@@ -564,6 +564,13 @@ NtfsMarkIrpContextForQueue(PNTFS_IRP_CONTEXT IrpContext)
//VOID
//NtfsDumpAttribute(PATTRIBUTE Attribute);
+NTSTATUS
+AddBitmap(PNTFS_VCB Vcb,
+ PFILE_RECORD_HEADER FileRecord,
+ PNTFS_ATTR_RECORD AttributeAddress,
+ PCWSTR Name,
+ USHORT NameLength);
+
NTSTATUS
AddData(PFILE_RECORD_HEADER FileRecord,
PNTFS_ATTR_RECORD AttributeAddress);
@@ -576,6 +583,13 @@ AddRun(PNTFS_VCB Vcb,
ULONGLONG NextAssignedCluster,
ULONG RunLength);
+NTSTATUS
+AddIndexAllocation(PNTFS_VCB Vcb,
+ PFILE_RECORD_HEADER FileRecord,
+ PNTFS_ATTR_RECORD AttributeAddress,
+ PCWSTR Name,
+ USHORT NameLength);
+
NTSTATUS
AddIndexRoot(PNTFS_VCB Vcb,
PFILE_RECORD_HEADER FileRecord,
@@ -723,6 +737,9 @@ CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt,
PINDEX_ROOT_ATTRIBUTE *IndexRoot,
ULONG *Length);
+NTSTATUS
+DemoteBTreeRoot(PB_TREE Tree);
+
VOID
DestroyBTree(PB_TREE Tree);
@@ -752,13 +769,26 @@ GetAllocationOffsetFromVCN(PDEVICE_EXTENSION DeviceExt,
ULONG IndexBufferSize,
ULONGLONG Vcn);
+ULONG
+GetSizeOfIndexEntries(PB_TREE_FILENAME_NODE Node);
+
NTSTATUS
NtfsInsertKey(PB_TREE Tree,
ULONGLONG FileReference,
PFILENAME_ATTRIBUTE FileNameAttribute,
PB_TREE_FILENAME_NODE Node,
BOOLEAN CaseSensitive,
- ULONG MaxIndexRootSize);
+ ULONG MaxIndexRootSize,
+ ULONG IndexRecordSize,
+ PB_TREE_KEY *MedianKey,
+ PB_TREE_FILENAME_NODE *NewRightHandSibling);
+
+NTSTATUS
+SplitBTreeNode(PB_TREE Tree,
+ PB_TREE_FILENAME_NODE Node,
+ PB_TREE_KEY *MedianKey,
+ PB_TREE_FILENAME_NODE *NewRightHandSibling,
+ BOOLEAN CaseSensitive);
NTSTATUS
UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt,
@@ -772,8 +802,7 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt,
PB_TREE_FILENAME_NODE Node,
ULONG IndexBufferSize,
PNTFS_ATTR_CONTEXT IndexAllocationContext,
- ULONG IndexAllocationOffset,
- PNTFS_ATTR_CONTEXT BitmapContext);
+ ULONG IndexAllocationOffset);
/* close.c */
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=5e7c11842ad0b24a9a7bc…
commit 5e7c11842ad0b24a9a7bc847e938a9cda0b2748e
Author: Trevor Thompson <tmt256(a)email.vccs.edu>
AuthorDate: Mon Aug 28 03:11:38 2017 +0000
[NTFS] - Fix increasing the mft size, to keep chkdsk happy.
IncreaseMftSize() - Add some fixes. Write blank records to newly-allocated mft entries, and update $MFTMirr when finished; these changes are needed for chkdsk. Increase size by 64 records instead of 8.
+UpdateMftMirror() - Backs up the first ~4 master file table entries to the $MFTMirr file.
svn path=/branches/GSoC_2016/NTFS/; revision=75694
---
drivers/filesystems/ntfs/mft.c | 200 ++++++++++++++++++++++++++++++++++++++--
drivers/filesystems/ntfs/ntfs.h | 3 +
2 files changed, 195 insertions(+), 8 deletions(-)
diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c
index 7d0157a761..547ab64a2b 100644
--- a/drivers/filesystems/ntfs/mft.c
+++ b/drivers/filesystems/ntfs/mft.c
@@ -30,6 +30,7 @@
/* INCLUDES *****************************************************************/
#include "ntfs.h"
+#include <ntintsafe.h>
#define NDEBUG
#include <debug.h>
@@ -228,7 +229,7 @@ AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord)
* STATUS_CANT_WAIT if CanWait was FALSE and the function could not get immediate, exclusive access to the MFT.
*
* @remarks
-* Increases the size of the Master File Table by 8 records. Bitmap entries for the new records are cleared,
+* Increases the size of the Master File Table by 64 records. Bitmap entries for the new records are cleared,
* and the bitmap is also enlarged if needed. Mimicking Windows' behavior when enlarging the mft is still TODO.
* This function will wait for exlusive access to the volume fcb.
*/
@@ -239,13 +240,17 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
LARGE_INTEGER BitmapSize;
LARGE_INTEGER DataSize;
LONGLONG BitmapSizeDifference;
- ULONG DataSizeDifference = Vcb->NtfsInfo.BytesPerFileRecord * 8;
+ ULONG NewRecords = ATTR_RECORD_ALIGNMENT * 8; // Allocate one new record for every bit of every byte we'll be adding to the bitmap
+ ULONG DataSizeDifference = Vcb->NtfsInfo.BytesPerFileRecord * NewRecords;
ULONG BitmapOffset;
PUCHAR BitmapBuffer;
ULONGLONG BitmapBytes;
ULONGLONG NewBitmapSize;
+ ULONGLONG FirstNewMftIndex;
ULONG BytesRead;
ULONG LengthWritten;
+ PFILE_RECORD_HEADER BlankFileRecord;
+ ULONG i;
NTSTATUS Status;
DPRINT1("IncreaseMftSize(%p, %s)\n", Vcb, CanWait ? "TRUE" : "FALSE");
@@ -256,11 +261,23 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
return STATUS_CANT_WAIT;
}
+ // Create a blank file record that will be used later
+ BlankFileRecord = NtfsCreateEmptyFileRecord(Vcb);
+ if (!BlankFileRecord)
+ {
+ DPRINT1("Error: Unable to create empty file record!\n");
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ // Clear the flags (file record is not in use)
+ BlankFileRecord->Flags = 0;
+
// Find the bitmap attribute of master file table
- Status = FindAttribute(Vcb, Vcb->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, &BitmapOffset);
+ Status = FindAttribute(Vcb, Vcb->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL);
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR: Couldn't find $BITMAP attribute of Mft!\n");
+ ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
ExReleaseResourceLite(&(Vcb->DirResource));
return Status;
}
@@ -271,9 +288,17 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
// Calculate the new mft size
DataSize.QuadPart = AttributeDataLength(Vcb->MFTContext->pRecord) + DataSizeDifference;
+ // Find the index of the first Mft entry that will be created
+ FirstNewMftIndex = AttributeDataLength(Vcb->MFTContext->pRecord) / Vcb->NtfsInfo.BytesPerFileRecord;
+
// Determine how many bytes will make up the bitmap
BitmapBytes = DataSize.QuadPart / Vcb->NtfsInfo.BytesPerFileRecord / 8;
-
+ if ((DataSize.QuadPart / Vcb->NtfsInfo.BytesPerFileRecord) % 8 != 0)
+ BitmapBytes++;
+
+ // Windows will always keep the number of bytes in a bitmap as a multiple of 8, so no bytes are wasted on slack
+ BitmapBytes = ALIGN_UP_BY(BitmapBytes, ATTR_RECORD_ALIGNMENT);
+
// Determine how much we need to adjust the bitmap size (it's possible we don't)
BitmapSizeDifference = BitmapBytes - BitmapSize.QuadPart;
NewBitmapSize = max(BitmapSize.QuadPart + BitmapSizeDifference, BitmapSize.QuadPart);
@@ -283,6 +308,7 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
if (!BitmapBuffer)
{
DPRINT1("ERROR: Unable to allocate memory for bitmap attribute!\n");
+ ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
ExReleaseResourceLite(&(Vcb->DirResource));
ReleaseAttributeContext(BitmapContext);
return STATUS_INSUFFICIENT_RESOURCES;
@@ -300,6 +326,7 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
if (BytesRead != BitmapSize.LowPart)
{
DPRINT1("ERROR: Bytes read != Bitmap size!\n");
+ ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
ExReleaseResourceLite(&(Vcb->DirResource));
ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
ReleaseAttributeContext(BitmapContext);
@@ -311,17 +338,29 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR: Failed to set size of $MFT data attribute!\n");
+ ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
ExReleaseResourceLite(&(Vcb->DirResource));
ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
ReleaseAttributeContext(BitmapContext);
return Status;
}
+
+ // We'll need to find the bitmap again, because its offset will have changed after resizing the data attribute
+ ReleaseAttributeContext(BitmapContext);
+ Status = FindAttribute(Vcb, Vcb->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, &BitmapOffset);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Couldn't find $BITMAP attribute of Mft!\n");
+ ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
+ ExReleaseResourceLite(&(Vcb->DirResource));
+ return Status;
+ }
// If the bitmap grew
if (BitmapSizeDifference > 0)
{
// Set the new bitmap size
- BitmapSize.QuadPart += BitmapSizeDifference;
+ BitmapSize.QuadPart = NewBitmapSize;
if (BitmapContext->pRecord->IsNonResident)
Status = SetNonResidentAttributeDataLength(Vcb, BitmapContext, BitmapOffset, Vcb->MasterFileTable, &BitmapSize);
else
@@ -330,6 +369,7 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR: Failed to set size of bitmap attribute!\n");
+ ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
ExReleaseResourceLite(&(Vcb->DirResource));
ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
ReleaseAttributeContext(BitmapContext);
@@ -337,13 +377,14 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
}
}
- //NtfsDumpFileAttributes(Vcb, FileRecord);
+ NtfsDumpFileAttributes(Vcb, Vcb->MasterFileTable);
// Update the file record with the new attribute sizes
Status = UpdateFileRecord(Vcb, Vcb->VolumeFcb->MFTIndex, Vcb->MasterFileTable);
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR: Failed to update $MFT file record!\n");
+ ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
ExReleaseResourceLite(&(Vcb->DirResource));
ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
ReleaseAttributeContext(BitmapContext);
@@ -351,9 +392,10 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
}
// Write out the new bitmap
- Status = WriteAttribute(Vcb, BitmapContext, BitmapOffset, BitmapBuffer, BitmapSize.LowPart, &LengthWritten, Vcb->MasterFileTable);
+ Status = WriteAttribute(Vcb, BitmapContext, 0, BitmapBuffer, NewBitmapSize, &LengthWritten, Vcb->MasterFileTable);
if (!NT_SUCCESS(Status))
{
+ ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
ExReleaseResourceLite(&(Vcb->DirResource));
ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
ReleaseAttributeContext(BitmapContext);
@@ -361,12 +403,31 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
return Status;
}
+ // Create blank records for the new file record entries.
+ for (i = 0; i < NewRecords; i++)
+ {
+ Status = UpdateFileRecord(Vcb, FirstNewMftIndex + i, BlankFileRecord);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Failed to write blank file record!\n");
+ ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
+ ExReleaseResourceLite(&(Vcb->DirResource));
+ ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
+ ReleaseAttributeContext(BitmapContext);
+ return Status;
+ }
+ }
+
+ // Update the mft mirror
+ Status = UpdateMftMirror(Vcb);
+
// Cleanup
+ ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
ExReleaseResourceLite(&(Vcb->DirResource));
ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
ReleaseAttributeContext(BitmapContext);
- return STATUS_SUCCESS;
+ return Status;
}
/**
@@ -2384,6 +2445,129 @@ CompareFileName(PUNICODE_STRING FileName,
}
}
+/**
+* @name UpdateMftMirror
+* @implemented
+*
+* Backs-up the first ~4 master file table entries to the $MFTMirr file.
+*
+* @param Vcb
+* Pointer to an NTFS_VCB for the volume whose Mft mirror is being updated.
+*
+* @returninja livecd
+
+* STATUS_SUCCESS on success.
+* STATUS_INSUFFICIENT_RESOURCES if an allocation failed.
+* STATUS_UNSUCCESSFUL if we couldn't read the master file table.
+*
+* @remarks
+* NTFS maintains up-to-date copies of the first several mft entries in the $MFTMirr file. Usually, the first 4 file
+* records from the mft are stored. The exact number of entries is determined by the size of $MFTMirr's $DATA.
+* If $MFTMirr is not up-to-date, chkdsk will reject every change it can find prior to when $MFTMirr was last updated.
+* Therefore, it's recommended to call this function if the volume changes considerably. For instance, IncreaseMftSize()
+* relies on this function to keep chkdsk from deleting the mft entries it creates. Note that under most instances, creating
+* or deleting a file will not affect the first ~four mft entries, and so will not require updating the mft mirror.
+*/
+NTSTATUS
+UpdateMftMirror(PNTFS_VCB Vcb)
+{
+ PFILE_RECORD_HEADER MirrorFileRecord;
+ PNTFS_ATTR_CONTEXT MirrDataContext;
+ PNTFS_ATTR_CONTEXT MftDataContext;
+ PCHAR DataBuffer;
+ ULONGLONG DataLength;
+ NTSTATUS Status;
+ ULONG BytesRead;
+ ULONG LengthWritten;
+
+ // Allocate memory for the Mft mirror file record
+ MirrorFileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
+ if (!MirrorFileRecord)
+ {
+ DPRINT1("Error: Failed to allocate memory for $MFTMirr!\n");
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ // Read the Mft Mirror file record
+ Status = ReadFileRecord(Vcb, NTFS_FILE_MFTMIRR, MirrorFileRecord);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Failed to read $MFTMirr!\n");
+ ExFreePoolWithTag(MirrorFileRecord, TAG_NTFS);
+ return Status;
+ }
+
+ // Find the $DATA attribute of $MFTMirr
+ Status = FindAttribute(Vcb, MirrorFileRecord, AttributeData, L"", 0, &MirrDataContext, NULL);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Couldn't find $DATA attribute!\n");
+ ExFreePoolWithTag(MirrorFileRecord, TAG_NTFS);
+ return Status;
+ }
+
+ // Find the $DATA attribute of $MFT
+ Status = FindAttribute(Vcb, Vcb->MasterFileTable, AttributeData, L"", 0, &MftDataContext, NULL);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Couldn't find $DATA attribute!\n");
+ ReleaseAttributeContext(MirrDataContext);
+ ExFreePoolWithTag(MirrorFileRecord, TAG_NTFS);
+ return Status;
+ }
+
+ // Get the size of the mirror's $DATA attribute
+ DataLength = AttributeDataLength(MirrDataContext->pRecord);
+
+ ASSERT(DataLength % Vcb->NtfsInfo.BytesPerFileRecord == 0);
+
+ // Create buffer for the mirror's $DATA attribute
+ DataBuffer = ExAllocatePoolWithTag(NonPagedPool, DataLength, TAG_NTFS);
+ if (!DataBuffer)
+ {
+ DPRINT1("Error: Couldn't allocate memory for $DATA buffer!\n");
+ ReleaseAttributeContext(MftDataContext);
+ ReleaseAttributeContext(MirrDataContext);
+ ExFreePoolWithTag(MirrorFileRecord, TAG_NTFS);
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ ASSERT(DataLength < ULONG_MAX);
+
+ // Back up the first several entries of the Mft's $DATA Attribute
+ BytesRead = ReadAttribute(Vcb, MftDataContext, 0, DataBuffer, (ULONG)DataLength);
+ if (BytesRead != (ULONG)DataLength)
+ {
+ DPRINT1("Error: Failed to read $DATA for $MFTMirr!\n");
+ ReleaseAttributeContext(MftDataContext);
+ ReleaseAttributeContext(MirrDataContext);
+ ExFreePoolWithTag(DataBuffer, TAG_NTFS);
+ ExFreePoolWithTag(MirrorFileRecord, TAG_NTFS);
+ return STATUS_UNSUCCESSFUL;
+ }
+
+ // Write the mirror's $DATA attribute
+ Status = WriteAttribute(Vcb,
+ MirrDataContext,
+ 0,
+ (PUCHAR)DataBuffer,
+ DataLength,
+ &LengthWritten,
+ MirrorFileRecord);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Failed to write $DATA attribute of $MFTMirr!\n");
+ }
+
+ // Cleanup
+ ReleaseAttributeContext(MftDataContext);
+ ReleaseAttributeContext(MirrDataContext);
+ ExFreePoolWithTag(DataBuffer, TAG_NTFS);
+ ExFreePoolWithTag(MirrorFileRecord, TAG_NTFS);
+
+ return Status;
+}
+
#if 0
static
VOID
diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h
index abd2d2791d..aba8b29196 100644
--- a/drivers/filesystems/ntfs/ntfs.h
+++ b/drivers/filesystems/ntfs/ntfs.h
@@ -1047,6 +1047,9 @@ CompareFileName(PUNICODE_STRING FileName,
BOOLEAN DirSearch,
BOOLEAN CaseSensitive);
+NTSTATUS
+UpdateMftMirror(PNTFS_VCB Vcb);
+
NTSTATUS
ReadFileRecord(PDEVICE_EXTENSION Vcb,
ULONGLONG index,
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=b033f00f58c48d139a866…
commit b033f00f58c48d139a86688818d5c3a7692037aa
Author: Trevor Thompson <tmt256(a)email.vccs.edu>
AuthorDate: Sun Aug 27 14:37:17 2017 +0000
[NTFS] - Add support for directory creation. Add some helper functions, some comments, and some fixes.
+AddIndexRoot() - Creates an $INDEX_ROOT attribute and adds it to a file record.
AddNewMftEntry() - Make sure the buffer used by RtlInitializeBitmap() is ULONG-aligned, and a ULONG-multiple in size, per MSDN.
AllocateIndexNode() - Calculate BytesNeeded correctly. Read $BITMAP attribute before increasing its length, in anticipation of a future commit that will check for a free bit before assigning a new index record to the end of the allocation. Use appropriate Set*AttributeDataLength() function, as $BITMAP can be resident or non-resident.
B_TREE_FILENAME_NODE - Give two members more accurate names: change "ExistsOnDisk" member to "HasValidVCN" and rename "NodeNumber" member "VCN."
+CreateEmptyBTree() - Creates a B-Tree to represent an empty directory (for AddIndexRoot).
+NtfsCreateEmptyFileRecord() - Creates an empty file record in memory, with no attributes.
CreateIndexRootFromBTree() - Fix TotalSizeOfEntries calculation.
+NtfsCreateDirectory() - Creates a file record for an empty directory and adds it to the mft.
svn path=/branches/GSoC_2016/NTFS/; revision=75692
---
drivers/filesystems/ntfs/attrib.c | 112 +++++++++++++++++-
drivers/filesystems/ntfs/btree.c | 208 +++++++++++++++++++++++----------
drivers/filesystems/ntfs/create.c | 239 ++++++++++++++++++++++++++++++++++----
drivers/filesystems/ntfs/mft.c | 32 +++--
drivers/filesystems/ntfs/ntfs.h | 40 ++++++-
5 files changed, 530 insertions(+), 101 deletions(-)
diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c
index e830264ad6..34bee2a060 100644
--- a/drivers/filesystems/ntfs/attrib.c
+++ b/drivers/filesystems/ntfs/attrib.c
@@ -167,7 +167,11 @@ AddFileName(PFILE_RECORD_HEADER FileRecord,
FileNameAttribute->LastWriteTime = SystemTime.QuadPart;
FileNameAttribute->LastAccessTime = SystemTime.QuadPart;
- FileNameAttribute->FileAttributes = NTFS_FILE_TYPE_ARCHIVE;
+ // 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);
@@ -254,6 +258,112 @@ AddFileName(PFILE_RECORD_HEADER FileRecord,
return Status;
}
+/**
+* @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)
+{
+ 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);
+
+ return STATUS_SUCCESS;
+}
+
/**
* @name AddRun
* @implemented
diff --git a/drivers/filesystems/ntfs/btree.c b/drivers/filesystems/ntfs/btree.c
index 727c147a2a..cd9cf35069 100644
--- a/drivers/filesystems/ntfs/btree.c
+++ b/drivers/filesystems/ntfs/btree.c
@@ -111,6 +111,7 @@ PrintAllVCNs(PDEVICE_EXTENSION Vcb,
* @remarks
* AllocateIndexNode() doesn't write any data to the index record it creates. Called by UpdateIndexNode().
* Don't call PrintAllVCNs() or NtfsDumpFileRecord() after calling AllocateIndexNode() before UpdateIndexNode() finishes.
+* Possible TODO: Create an empty node and write it to the allocated index node, so the index allocation is always valid.
*/
NTSTATUS
AllocateIndexNode(PDEVICE_EXTENSION DeviceExt,
@@ -167,11 +168,30 @@ AllocateIndexNode(PDEVICE_EXTENSION DeviceExt,
// See how many bytes we need to store the amount of bits we'll have
BytesNeeded = NextNodeNumber / 8;
- if (NextNodeNumber % 8 != 0)
- BytesNeeded++;
+ BytesNeeded++;
// Windows seems to allocate the bitmap in 8-byte chunks to keep any bytes from being wasted on padding
- ALIGN_UP(BytesNeeded, ATTR_RECORD_ALIGNMENT);
+ BytesNeeded = ALIGN_UP(BytesNeeded, ATTR_RECORD_ALIGNMENT);
+
+ // Allocate memory for the bitmap, including some padding; RtlInitializeBitmap() wants a pointer
+ // that's ULONG-aligned, and it wants the size of the memory allocated for it to be a ULONG-multiple.
+ BitmapMem = ExAllocatePoolWithTag(NonPagedPool, BytesNeeded + sizeof(ULONG), TAG_NTFS);
+ if (!BitmapMem)
+ {
+ DPRINT1("Error: failed to allocate bitmap!");
+ ReleaseAttributeContext(BitmapCtx);
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+ // RtlInitializeBitmap() wants a pointer that's ULONG-aligned.
+ BitmapPtr = (PULONG)ALIGN_UP_BY((ULONG_PTR)BitmapMem, sizeof(ULONG));
+
+ RtlZeroMemory(BitmapPtr, BytesNeeded);
+
+ // Read the existing bitmap data
+ Status = ReadAttribute(DeviceExt, BitmapCtx, 0, (PCHAR)BitmapPtr, BitmapLength);
+
+ // Initialize bitmap
+ RtlInitializeBitMap(&Bitmap, BitmapPtr, NextNodeNumber);
// Do we need to enlarge the bitmap?
if (BytesNeeded > BitmapLength)
@@ -179,11 +199,22 @@ AllocateIndexNode(PDEVICE_EXTENSION DeviceExt,
// TODO: handle synchronization issues that could occur from changing the directory's file record
// Change bitmap size
DataSize.QuadPart = BytesNeeded;
- Status = SetResidentAttributeDataLength(DeviceExt,
- BitmapCtx,
- BitmapOffset,
- FileRecord,
- &DataSize);
+ if (BitmapCtx->pRecord->IsNonResident)
+ {
+ Status = SetNonResidentAttributeDataLength(DeviceExt,
+ BitmapCtx,
+ BitmapOffset,
+ FileRecord,
+ &DataSize);
+ }
+ else
+ {
+ Status = SetResidentAttributeDataLength(DeviceExt,
+ BitmapCtx,
+ BitmapOffset,
+ FileRecord,
+ &DataSize);
+ }
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR: Failed to set length of bitmap attribute!\n");
@@ -215,18 +246,6 @@ AllocateIndexNode(PDEVICE_EXTENSION DeviceExt,
return Status;
}
- // Allocate memory for the bitmap. RtlInitializeBitmap() wants a pointer that's ULONG-aligned
- BitmapMem = ExAllocatePoolWithTag(NonPagedPool, BytesNeeded + sizeof(ULONG) - 1, TAG_NTFS);
- BitmapPtr = (PULONG)ALIGN_UP_BY((ULONG_PTR)BitmapMem, sizeof(ULONG));
-
- RtlZeroMemory(BitmapPtr, BytesNeeded);
-
- // Read the existing bitmap data
- Status = ReadAttribute(DeviceExt, BitmapCtx, 0, (PCHAR)BitmapPtr, BitmapLength);
-
- // Initialize bitmap
- RtlInitializeBitMap(&Bitmap, BitmapPtr, BytesNeeded);
-
// Set the bit for the new index record
RtlSetBits(&Bitmap, NextNodeNumber, 1);
@@ -246,6 +265,8 @@ AllocateIndexNode(PDEVICE_EXTENSION DeviceExt,
// Calculate VCN of new node number
*NewVCN = NextNodeNumber * (IndexBufferSize / DeviceExt->NtfsInfo.BytesPerCluster);
+ DPRINT("New VCN: %I64u\n", *NewVCN);
+
ExFreePoolWithTag(BitmapMem, TAG_NTFS);
ReleaseAttributeContext(BitmapCtx);
@@ -311,6 +332,63 @@ CreateDummyKey(BOOLEAN HasChildNode)
return NewDummyKey;
}
+/**
+* @name CreateEmptyBTree
+* @implemented
+*
+* Creates an empty B-Tree, which will contain a single root node which will contain a single dummy key.
+*
+* @param NewTree
+* Pointer to a PB_TREE that will receive the pointer of the newly-created B-Tree.
+*
+* @return
+* STATUS_SUCCESS on success. STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
+*/
+NTSTATUS
+CreateEmptyBTree(PB_TREE *NewTree)
+{
+ PB_TREE Tree = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE), TAG_NTFS);
+ PB_TREE_FILENAME_NODE RootNode = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_FILENAME_NODE), TAG_NTFS);
+ PB_TREE_KEY DummyKey;
+
+ DPRINT1("CreateEmptyBTree(%p) called\n", NewTree);
+
+ if (!Tree || !RootNode)
+ {
+ DPRINT1("Couldn't allocate enough memory for B-Tree!\n");
+ if (Tree)
+ ExFreePoolWithTag(Tree, TAG_NTFS);
+ if (RootNode)
+ ExFreePoolWithTag(RootNode, TAG_NTFS);
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ // Create the dummy key
+ DummyKey = CreateDummyKey(FALSE);
+ if (!DummyKey)
+ {
+ DPRINT1("ERROR: Failed to create dummy key!\n");
+ ExFreePoolWithTag(Tree, TAG_NTFS);
+ ExFreePoolWithTag(RootNode, TAG_NTFS);
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ RtlZeroMemory(Tree, sizeof(B_TREE));
+ RtlZeroMemory(RootNode, sizeof(B_TREE_FILENAME_NODE));
+
+ // Setup the Tree
+ RootNode->FirstKey = DummyKey;
+ RootNode->KeyCount = 1;
+ RootNode->DiskNeedsUpdating = TRUE;
+ Tree->RootNode = RootNode;
+
+ *NewTree = Tree;
+
+ // Memory will be freed when DestroyBTree() is called
+
+ return STATUS_SUCCESS;
+}
+
/**
* @name CompareTreeKeys
* @implemented
@@ -402,7 +480,7 @@ CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb,
ULONG CurrentEntryOffset = 0;
PINDEX_BUFFER NodeBuffer;
ULONG IndexBufferSize = Vcb->NtfsInfo.BytesPerIndexRecord;
- PULONGLONG NodeNumber;
+ PULONGLONG VCN;
PB_TREE_KEY CurrentKey;
NTSTATUS Status;
ULONGLONG IndexNodeOffset;
@@ -415,10 +493,9 @@ CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb,
}
// Get the node number from the end of the node entry
- NodeNumber = (PULONGLONG)((ULONG_PTR)NodeEntry + NodeEntry->Length - sizeof(ULONGLONG));
+ VCN = (PULONGLONG)((ULONG_PTR)NodeEntry + NodeEntry->Length - sizeof(ULONGLONG));
// Create the new tree node
- DPRINT1("About to allocate %ld for NewNode\n", sizeof(B_TREE_FILENAME_NODE));
NewNode = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_FILENAME_NODE), TAG_NTFS);
if (!NewNode)
{
@@ -449,7 +526,7 @@ CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb,
}
// Calculate offset into index allocation
- IndexNodeOffset = GetAllocationOffsetFromVCN(Vcb, IndexBufferSize, *NodeNumber);
+ IndexNodeOffset = GetAllocationOffsetFromVCN(Vcb, IndexBufferSize, *VCN);
// TODO: Confirm index bitmap has this node marked as in-use
@@ -462,7 +539,7 @@ CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb,
ASSERT(BytesRead == IndexBufferSize);
NT_ASSERT(NodeBuffer->Ntfs.Type == NRH_INDX_TYPE);
- NT_ASSERT(NodeBuffer->VCN == *NodeNumber);
+ NT_ASSERT(NodeBuffer->VCN == *VCN);
// Apply the fixup array to the node buffer
Status = FixupUpdateSequenceArray(Vcb, &NodeBuffer->Ntfs);
@@ -516,8 +593,6 @@ CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb,
// See if the current key has a sub-node
if (CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)
{
- DPRINT1("TODO: Only a node with a single-level is supported right now!\n");
- // Needs debugging:
CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb,
IndexRoot,
IndexAllocationAttributeCtx,
@@ -535,8 +610,6 @@ CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb,
// See if the current key has a sub-node
if (CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)
{
- DPRINT1("TODO: Only a node with a single-level is supported right now!\n");
- // Needs debugging:
CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb,
IndexRoot,
IndexAllocationAttributeCtx,
@@ -551,8 +624,8 @@ CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb,
CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)CurrentNodeEntry + CurrentNodeEntry->Length);
}
- NewNode->NodeNumber = *NodeNumber;
- NewNode->ExistsOnDisk = TRUE;
+ NewNode->VCN = *VCN;
+ NewNode->HasValidVCN = TRUE;
ExFreePoolWithTag(NodeBuffer, TAG_NTFS);
@@ -622,8 +695,6 @@ CreateBTreeFromIndex(PDEVICE_EXTENSION Vcb,
NULL);
if (!NT_SUCCESS(Status))
IndexAllocationContext = NULL;
- else
- PrintAllVCNs(Vcb, IndexAllocationContext, IndexRoot->SizeOfEntry);
// Setup the Tree
RootNode->FirstKey = CurrentKey;
@@ -871,7 +942,7 @@ CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt,
NewIndexRoot->Header.Flags = INDEX_ROOT_LARGE;
// Add Length of Current Entry to Total Size of Entries
- NewIndexRoot->Header.TotalSizeOfEntries += CurrentNodeEntry->Length;
+ NewIndexRoot->Header.TotalSizeOfEntries += CurrentKey->IndexEntry->Length;
// Go to the next node entry
CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)CurrentNodeEntry + CurrentNodeEntry->Length);
@@ -905,10 +976,12 @@ CreateIndexBufferFromBTreeNode(PDEVICE_EXTENSION DeviceExt,
IndexBuffer->Ntfs.UsaCount = 9;
// TODO: Check bitmap for VCN
- ASSERT(Node->ExistsOnDisk);
- IndexBuffer->VCN = Node->NodeNumber;
+ ASSERT(Node->HasValidVCN);
+ IndexBuffer->VCN = Node->VCN;
- IndexBuffer->Header.FirstEntryOffset = 0x28;
+ // Windows seems to alternate between using 0x28 and 0x40 for the first entry offset of each index buffer.
+ // Interestingly, neither Windows nor chkdsk seem to mind if we just use 0x28 for every index record.
+ IndexBuffer->Header.FirstEntryOffset = 0x28;
IndexBuffer->Header.AllocatedSize = BufferSize - FIELD_OFFSET(INDEX_BUFFER, Header);
// Start summing the total size of this node's entries
@@ -934,9 +1007,9 @@ CreateIndexBufferFromBTreeNode(PDEVICE_EXTENSION DeviceExt,
// Copy the index entry
RtlCopyMemory(CurrentNodeEntry, CurrentKey->IndexEntry, CurrentKey->IndexEntry->Length);
- DPRINT1("Index Node Entry Stream Length: %u\nIndex Node Entry Length: %u\n",
- CurrentNodeEntry->KeyLength,
- CurrentNodeEntry->Length);
+ DPRINT("Index Node Entry Stream Length: %u\nIndex Node Entry Length: %u\n",
+ CurrentNodeEntry->KeyLength,
+ CurrentNodeEntry->Length);
// Add Length of Current Entry to Total Size of Entries
IndexBuffer->Header.TotalSizeOfEntries += CurrentNodeEntry->Length;
@@ -1020,14 +1093,6 @@ UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt,
DPRINT1("FIXME: Need to add index allocation\n");
return STATUS_NOT_IMPLEMENTED;
}
-
- Status = UpdateIndexNode(DeviceExt, FileRecord, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, IndexAllocationOffset, BitmapContext);
- if (!NT_SUCCESS(Status))
- {
- DPRINT1("ERROR: Failed to update index node!\n");
- ReleaseAttributeContext(IndexAllocationContext);
- return Status;
- }
// Is the Index Entry large enough to store the VCN?
if (!CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)
@@ -1056,9 +1121,17 @@ UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt,
CurrentKey->IndexEntry->Flags |= NTFS_INDEX_ENTRY_NODE;
}
- // Update the VCN stored in the index entry of CurrentKey
- SetIndexEntryVCN(CurrentKey->IndexEntry, CurrentKey->LesserChild->NodeNumber);
+ // Update the sub-node
+ Status = UpdateIndexNode(DeviceExt, FileRecord, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, IndexAllocationOffset, BitmapContext);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Failed to update index node!\n");
+ ReleaseAttributeContext(IndexAllocationContext);
+ return Status;
+ }
+ // Update the VCN stored in the index entry of CurrentKey
+ SetIndexEntryVCN(CurrentKey->IndexEntry, CurrentKey->LesserChild->VCN);
}
CurrentKey = CurrentKey->NextKey;
}
@@ -1096,7 +1169,7 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt,
IndexAllocationContext,
IndexAllocationOffset,
BitmapContext,
- Node->NodeNumber);
+ Node->VCN);
// Do we need to write this node to disk?
if (Node->DiskNeedsUpdating)
@@ -1106,7 +1179,7 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt,
PINDEX_BUFFER IndexBuffer;
// Does the node need to be assigned a VCN?
- if (!Node->ExistsOnDisk)
+ if (!Node->HasValidVCN)
{
// Allocate the node
Status = AllocateIndexNode(DeviceExt,
@@ -1114,14 +1187,14 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt,
IndexBufferSize,
IndexAllocationContext,
IndexAllocationOffset,
- &Node->NodeNumber);
+ &Node->VCN);
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR: Failed to allocate index record in index allocation!\n");
return Status;
}
- Node->ExistsOnDisk = TRUE;
+ Node->HasValidVCN = TRUE;
}
// Allocate memory for an index buffer
@@ -1142,7 +1215,7 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt,
}
// Get Offset of index buffer in index allocation
- NodeOffset = GetAllocationOffsetFromVCN(DeviceExt, IndexBufferSize, Node->NodeNumber);
+ NodeOffset = GetAllocationOffsetFromVCN(DeviceExt, IndexBufferSize, Node->VCN);
// Write the buffer to the index allocation
Status = WriteAttribute(DeviceExt, IndexAllocationContext, NodeOffset, (const PUCHAR)IndexBuffer, IndexBufferSize, &LengthWritten, FileRecord);
@@ -1274,7 +1347,7 @@ DestroyBTree(PB_TREE Tree)
}
VOID
-DumpBTreeKey(PB_TREE_KEY Key, ULONG Number, ULONG Depth)
+DumpBTreeKey(PB_TREE Tree, PB_TREE_KEY Key, ULONG Number, ULONG Depth)
{
ULONG i;
for (i = 0; i < Depth; i++)
@@ -1298,7 +1371,7 @@ DumpBTreeKey(PB_TREE_KEY Key, ULONG Number, ULONG Depth)
if (Key->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)
{
if (Key->LesserChild)
- DumpBTreeNode(Key->LesserChild, Number, Depth + 1);
+ DumpBTreeNode(Tree, Key->LesserChild, Number, Depth + 1);
else
{
// This will be an assert once nodes with arbitrary depth are debugged
@@ -1308,18 +1381,28 @@ DumpBTreeKey(PB_TREE_KEY Key, ULONG Number, ULONG Depth)
}
VOID
-DumpBTreeNode(PB_TREE_FILENAME_NODE Node, ULONG Number, ULONG Depth)
+DumpBTreeNode(PB_TREE Tree,
+ PB_TREE_FILENAME_NODE Node,
+ ULONG Number,
+ ULONG Depth)
{
PB_TREE_KEY CurrentKey;
ULONG i;
for (i = 0; i < Depth; i++)
DbgPrint(" ");
- DbgPrint("Node #%d, Depth %d, has %d key%s\n", Number, Depth, Node->KeyCount, Node->KeyCount == 1 ? "" : "s");
+ DbgPrint("Node #%d, Depth %d, has %d key%s", Number, Depth, Node->KeyCount, Node->KeyCount == 1 ? "" : "s");
+
+ if (Node->HasValidVCN)
+ DbgPrint(" VCN: %I64u\n", Node->VCN);
+ else if (Tree->RootNode == Node)
+ DbgPrint(" Index Root");
+ else
+ DbgPrint(" NOT ASSIGNED VCN YET\n");
CurrentKey = Node->FirstKey;
- for (i = 1; i <= Node->KeyCount; i++)
+ for (i = 0; i < Node->KeyCount; i++)
{
- DumpBTreeKey(CurrentKey, i, Depth);
+ DumpBTreeKey(Tree, CurrentKey, i, Depth);
CurrentKey = CurrentKey->NextKey;
}
}
@@ -1340,7 +1423,7 @@ VOID
DumpBTree(PB_TREE Tree)
{
DbgPrint("B_TREE @ %p\n", Tree);
- DumpBTreeNode(Tree->RootNode, 0, 0);
+ DumpBTreeNode(Tree, Tree->RootNode, 0, 0);
}
// Calculates start of Index Buffer relative to the index allocation, given the node's VCN
@@ -1525,7 +1608,6 @@ NtfsInsertKey(PB_TREE Tree,
NewIndexRoot->FirstKey = DummyKey;
NewIndexRoot->KeyCount = 1;
NewIndexRoot->DiskNeedsUpdating = TRUE;
- NewIndexRoot->ExistsOnDisk = TRUE;
// Make the new node the Tree's root node
Tree->RootNode = NewIndexRoot;
diff --git a/drivers/filesystems/ntfs/create.c b/drivers/filesystems/ntfs/create.c
index e8d0b1eb14..55e1e95a38 100644
--- a/drivers/filesystems/ntfs/create.c
+++ b/drivers/filesystems/ntfs/create.c
@@ -569,25 +569,31 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject,
return STATUS_ACCESS_DENIED;
}
- // We can't create directories yet
+ // Was the user trying to create a directory?
if (RequestedOptions & FILE_DIRECTORY_FILE)
{
- DPRINT1("FIXME: Folder creation is still TODO!\n");
- return STATUS_NOT_IMPLEMENTED;
+ // Create the directory on disk
+ Status = NtfsCreateDirectory(DeviceExt,
+ FileObject,
+ BooleanFlagOn(Stack->Flags, SL_CASE_SENSITIVE),
+ BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT));
+ }
+ else
+ {
+ // Create the file record on disk
+ Status = NtfsCreateFileRecord(DeviceExt,
+ FileObject,
+ BooleanFlagOn(Stack->Flags, SL_CASE_SENSITIVE),
+ BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT));
}
- // Create the file record on disk
- Status = NtfsCreateFileRecord(DeviceExt,
- FileObject,
- BooleanFlagOn(Stack->Flags, SL_CASE_SENSITIVE),
- BooleanFlagOn(IrpContext->Flags,IRPCONTEXT_CANWAIT));
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR: Couldn't create file record!\n");
return Status;
}
- // Before we open the file we just created, we need to change the disposition (upper 8 bits of ULONG)
+ // Before we open the file/directory we just created, we need to change the disposition (upper 8 bits of ULONG)
// from create to open, since we already created the file
Stack->Parameters.Create.Options = (ULONG)FILE_OPEN << 24 | RequestedOptions;
@@ -651,40 +657,48 @@ NtfsCreate(PNTFS_IRP_CONTEXT IrpContext)
}
/**
-* @name NtfsCreateFileRecord()
+* @name NtfsCreateDirectory()
* @implemented
*
-* Creates a file record and saves it to the MFT. Adds the filename attribute of the
-* created file to the parent directory's index.
+* Creates a file record for a new directory and saves it to the MFT. Adds the filename attribute of the
+* created directory to the parent directory's index.
*
* @param DeviceExt
* Points to the target disk's DEVICE_EXTENSION
*
* @param FileObject
-* Pointer to a FILE_OBJECT describing the file to be created
+* Pointer to a FILE_OBJECT describing the directory to be created
+*
+* @param CaseSensitive
+* Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
+* if an application created the folder with the FILE_FLAG_POSIX_SEMANTICS flag.
*
* @param CanWait
* Boolean indicating if the function is allowed to wait for exclusive access to the master file table.
* This will only be relevant if the MFT doesn't have any free file records and needs to be enlarged.
-*
+*
* @return
-* STATUS_SUCCESS on success.
+* STATUS_SUCCESS on success.
* STATUS_INSUFFICIENT_RESOURCES if unable to allocate memory for the file record.
-* STATUS_CANT_WAIT if CanWait was FALSE and the function needed to resize the MFT but
+* STATUS_CANT_WAIT if CanWait was FALSE and the function needed to resize the MFT but
* couldn't get immediate, exclusive access to it.
*/
NTSTATUS
-NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt,
- PFILE_OBJECT FileObject,
- BOOLEAN CaseSensitive,
- BOOLEAN CanWait)
+NtfsCreateDirectory(PDEVICE_EXTENSION DeviceExt,
+ PFILE_OBJECT FileObject,
+ BOOLEAN CaseSensitive,
+ BOOLEAN CanWait)
{
+
NTSTATUS Status = STATUS_SUCCESS;
PFILE_RECORD_HEADER FileRecord;
PNTFS_ATTR_RECORD NextAttribute;
PFILENAME_ATTRIBUTE FilenameAttribute;
ULONGLONG ParentMftIndex;
ULONGLONG FileMftIndex;
+ PB_TREE Tree;
+ PINDEX_ROOT_ATTRIBUTE NewIndexRoot;
+ ULONG RootLength;
DPRINT1("NtfsCreateFileRecord(%p, %p, %s, %s)\n",
DeviceExt,
@@ -692,6 +706,126 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt,
CaseSensitive ? "TRUE" : "FALSE",
CanWait ? "TRUE" : "FALSE");
+ // Start with an empty file record
+ FileRecord = NtfsCreateEmptyFileRecord(DeviceExt);
+ if (!FileRecord)
+ {
+ DPRINT1("ERROR: Unable to allocate memory for file record!\n");
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ // Set the directory flag
+ FileRecord->Flags |= FRH_DIRECTORY;
+
+ // find where the first attribute will be added
+ NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->AttributeOffset);
+
+ // add first attribute, $STANDARD_INFORMATION
+ AddStandardInformation(FileRecord, NextAttribute);
+
+ // advance NextAttribute pointer to the next attribute
+ NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)NextAttribute + (ULONG_PTR)NextAttribute->Length);
+
+ // Add the $FILE_NAME attribute
+ AddFileName(FileRecord, NextAttribute, DeviceExt, FileObject, CaseSensitive, &ParentMftIndex);
+
+ // save a pointer to the filename attribute
+ FilenameAttribute = (PFILENAME_ATTRIBUTE)((ULONG_PTR)NextAttribute + NextAttribute->Resident.ValueOffset);
+
+ // advance NextAttribute pointer to the next attribute
+ NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)NextAttribute + (ULONG_PTR)NextAttribute->Length);
+
+ // Create an empty b-tree to represent our new index
+ Status = CreateEmptyBTree(&Tree);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Failed to create empty B-Tree!\n");
+ ExFreePoolWithTag(FileRecord, TAG_NTFS);
+ return Status;
+ }
+
+ // Calculate maximum size of index root
+ ULONG MaxIndexRootSize = DeviceExt->NtfsInfo.BytesPerFileRecord
+ - ((ULONG_PTR)NextAttribute - (ULONG_PTR)FileRecord)
+ - sizeof(ULONG) * 2;
+
+ // Create a new index record from the tree
+ Status = CreateIndexRootFromBTree(DeviceExt,
+ Tree,
+ MaxIndexRootSize,
+ &NewIndexRoot,
+ &RootLength);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Unable to create empty index root!\n");
+ DestroyBTree(Tree);
+ ExFreePoolWithTag(FileRecord, TAG_NTFS);
+ return Status;
+ }
+
+ // We're done with the B-Tree
+ DestroyBTree(Tree);
+
+ // add the $INDEX_ROOT attribute
+ Status = AddIndexRoot(DeviceExt, FileRecord, NextAttribute, NewIndexRoot, RootLength, L"$I30", 4);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Failed to add index root to new file record!\n");
+ ExFreePoolWithTag(FileRecord, TAG_NTFS);
+ return Status;
+ }
+
+
+#ifndef NDEBUG
+ NtfsDumpFileRecord(DeviceExt, FileRecord);
+#endif
+
+ // Now that we've built the file record in memory, we need to store it in the MFT.
+ Status = AddNewMftEntry(FileRecord, DeviceExt, &FileMftIndex, CanWait);
+ if (NT_SUCCESS(Status))
+ {
+ // The highest 2 bytes should be the sequence number, unless the parent happens to be root
+ if (FileMftIndex == NTFS_FILE_ROOT)
+ FileMftIndex = FileMftIndex + ((ULONGLONG)NTFS_FILE_ROOT << 48);
+ else
+ FileMftIndex = FileMftIndex + ((ULONGLONG)FileRecord->SequenceNumber << 48);
+
+ DPRINT1("New File Reference: 0x%016I64x\n", FileMftIndex);
+
+ // Add the filename attribute to the filename-index of the parent directory
+ Status = NtfsAddFilenameToDirectory(DeviceExt,
+ ParentMftIndex,
+ FileMftIndex,
+ FilenameAttribute,
+ CaseSensitive);
+ }
+
+ ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
+ ExFreePoolWithTag(FileRecord, TAG_NTFS);
+
+ return Status;
+}
+
+/**
+* @name NtfsCreateEmptyFileRecord
+* @implemented
+*
+* Creates a new, empty file record, with no attributes.
+*
+* @param DeviceExt
+* Pointer to the DEVICE_EXTENSION of the target volume the file record will be stored on.
+*
+* @return
+* A pointer to the newly-created FILE_RECORD_HEADER if the function succeeds, NULL otherwise.
+*/
+PFILE_RECORD_HEADER
+NtfsCreateEmptyFileRecord(PDEVICE_EXTENSION DeviceExt)
+{
+ PFILE_RECORD_HEADER FileRecord;
+ PNTFS_ATTR_RECORD NextAttribute;
+
+ DPRINT1("NtfsCreateEmptyFileRecord(%p)\n", DeviceExt);
+
// allocate memory for file record
FileRecord = ExAllocatePoolWithTag(NonPagedPool,
DeviceExt->NtfsInfo.BytesPerFileRecord,
@@ -699,7 +833,7 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt,
if (!FileRecord)
{
DPRINT1("ERROR: Unable to allocate memory for file record!\n");
- return STATUS_INSUFFICIENT_RESOURCES;
+ return NULL;
}
RtlZeroMemory(FileRecord, DeviceExt->NtfsInfo.BytesPerFileRecord);
@@ -719,7 +853,7 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt,
FileRecord->AttributeOffset = ALIGN_UP_BY(FileRecord->AttributeOffset, ATTR_RECORD_ALIGNMENT);
FileRecord->Flags = FRH_IN_USE;
FileRecord->BytesInUse = FileRecord->AttributeOffset + sizeof(ULONG) * 2;
-
+
// find where the first attribute will be added
NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->AttributeOffset);
@@ -727,9 +861,66 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt,
NextAttribute->Type = AttributeEnd;
NextAttribute->Length = FILE_RECORD_END;
+ return FileRecord;
+}
+
+
+/**
+* @name NtfsCreateFileRecord()
+* @implemented
+*
+* Creates a file record and saves it to the MFT. Adds the filename attribute of the
+* created file to the parent directory's index.
+*
+* @param DeviceExt
+* Points to the target disk's DEVICE_EXTENSION
+*
+* @param FileObject
+* Pointer to a FILE_OBJECT describing the file to be created
+*
+* @param CanWait
+* Boolean indicating if the function is allowed to wait for exclusive access to the master file table.
+* This will only be relevant if the MFT doesn't have any free file records and needs to be enlarged.
+*
+* @return
+* STATUS_SUCCESS on success.
+* STATUS_INSUFFICIENT_RESOURCES if unable to allocate memory for the file record.
+* STATUS_CANT_WAIT if CanWait was FALSE and the function needed to resize the MFT but
+* couldn't get immediate, exclusive access to it.
+*/
+NTSTATUS
+NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt,
+ PFILE_OBJECT FileObject,
+ BOOLEAN CaseSensitive,
+ BOOLEAN CanWait)
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ PFILE_RECORD_HEADER FileRecord;
+ PNTFS_ATTR_RECORD NextAttribute;
+ PFILENAME_ATTRIBUTE FilenameAttribute;
+ ULONGLONG ParentMftIndex;
+ ULONGLONG FileMftIndex;
+
+ DPRINT1("NtfsCreateFileRecord(%p, %p, %s, %s)\n",
+ DeviceExt,
+ FileObject,
+ CaseSensitive ? "TRUE" : "FALSE",
+ CanWait ? "TRUE" : "FALSE");
+
+ // allocate memory for file record
+ FileRecord = NtfsCreateEmptyFileRecord(DeviceExt);
+ if (!FileRecord)
+ {
+ DPRINT1("ERROR: Unable to allocate memory for file record!\n");
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ // find where the first attribute will be added
+ NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->AttributeOffset);
+
// add first attribute, $STANDARD_INFORMATION
AddStandardInformation(FileRecord, NextAttribute);
-
+
// advance NextAttribute pointer to the next attribute
NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)NextAttribute + (ULONG_PTR)NextAttribute->Length);
@@ -745,8 +936,10 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt,
// add the $DATA attribute
AddData(FileRecord, NextAttribute);
+#ifndef NDEBUG
// dump file record in memory (for debugging)
NtfsDumpFileRecord(DeviceExt, FileRecord);
+#endif
// Now that we've built the file record in memory, we need to store it in the MFT.
Status = AddNewMftEntry(FileRecord, DeviceExt, &FileMftIndex, CanWait);
diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c
index 0cd2767efb..7d0157a761 100644
--- a/drivers/filesystems/ntfs/mft.c
+++ b/drivers/filesystems/ntfs/mft.c
@@ -1893,6 +1893,7 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
ULONGLONG BitmapDataSize;
ULONGLONG AttrBytesRead;
PUCHAR BitmapData;
+ PUCHAR BitmapBuffer;
ULONG LengthWritten;
PNTFS_ATTR_CONTEXT BitmapContext;
LARGE_INTEGER BitmapBits;
@@ -1901,6 +1902,8 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
DPRINT1("AddNewMftEntry(%p, %p, %p, %s)\n", FileRecord, DeviceExt, DestinationIndex, CanWait ? "TRUE" : "FALSE");
// First, we have to read the mft's $Bitmap attribute
+
+ // Find the attribute
Status = FindAttribute(DeviceExt, DeviceExt->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL);
if (!NT_SUCCESS(Status))
{
@@ -1908,22 +1911,28 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
return Status;
}
- // allocate a buffer for the $Bitmap attribute
+ // Get size of bitmap
BitmapDataSize = AttributeDataLength(BitmapContext->pRecord);
- BitmapData = ExAllocatePoolWithTag(NonPagedPool, BitmapDataSize, TAG_NTFS);
- if (!BitmapData)
+
+ // RtlInitializeBitmap wants a ULONG-aligned pointer, and wants the memory passed to it to be a ULONG-multiple
+ // Allocate a buffer for the $Bitmap attribute plus enough to ensure we can get a ULONG-aligned pointer
+ BitmapBuffer = ExAllocatePoolWithTag(NonPagedPool, BitmapDataSize + sizeof(ULONG), TAG_NTFS);
+ if (!BitmapBuffer)
{
ReleaseAttributeContext(BitmapContext);
return STATUS_INSUFFICIENT_RESOURCES;
}
+ // Get a ULONG-aligned pointer for the bitmap itself
+ BitmapData = (PUCHAR)ALIGN_UP_BY((ULONG_PTR)BitmapBuffer, sizeof(ULONG));
+
// read $Bitmap attribute
AttrBytesRead = ReadAttribute(DeviceExt, BitmapContext, 0, (PCHAR)BitmapData, BitmapDataSize);
- if (AttrBytesRead == 0)
+ if (AttrBytesRead != BitmapDataSize)
{
DPRINT1("ERROR: Unable to read $Bitmap attribute of master file table!\n");
- ExFreePoolWithTag(BitmapData, TAG_NTFS);
+ ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
ReleaseAttributeContext(BitmapContext);
return STATUS_OBJECT_NAME_NOT_FOUND;
}
@@ -1939,7 +1948,8 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
if (BitmapBits.HighPart != 0)
{
DPRINT1("\tFIXME: bitmap sizes beyond 32bits are not yet supported! (Your NTFS volume is too large)\n");
- ExFreePoolWithTag(BitmapData, TAG_NTFS);
+ NtfsGlobalData->EnableWriteSupport = FALSE;
+ ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
ReleaseAttributeContext(BitmapContext);
return STATUS_NOT_IMPLEMENTED;
}
@@ -1953,7 +1963,7 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
{
DPRINT1("Couldn't find free space in MFT for file record, increasing MFT size.\n");
- ExFreePoolWithTag(BitmapData, TAG_NTFS);
+ ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
ReleaseAttributeContext(BitmapContext);
// Couldn't find a free record in the MFT, add some blank records and try again
@@ -1982,7 +1992,7 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR encountered when writing $Bitmap attribute!\n");
- ExFreePoolWithTag(BitmapData, TAG_NTFS);
+ ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
ReleaseAttributeContext(BitmapContext);
return Status;
}
@@ -1993,14 +2003,14 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR: Unable to write file record!\n");
- ExFreePoolWithTag(BitmapData, TAG_NTFS);
+ ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
ReleaseAttributeContext(BitmapContext);
return Status;
}
*DestinationIndex = MftIndex;
- ExFreePoolWithTag(BitmapData, TAG_NTFS);
+ ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
ReleaseAttributeContext(BitmapContext);
return Status;
@@ -2255,7 +2265,7 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
AttributeLength,
&LengthWritten,
ParentFileRecord);
- if (!NT_SUCCESS(Status))
+ if (!NT_SUCCESS(Status) || LengthWritten != AttributeLength)
{
DPRINT1("ERROR: Unable to write new index root attribute to parent directory!\n");
ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h
index 030b777c22..abd2d2791d 100644
--- a/drivers/filesystems/ntfs/ntfs.h
+++ b/drivers/filesystems/ntfs/ntfs.h
@@ -304,6 +304,12 @@ typedef struct
// relative to the beginning of the file record.
#define ATTR_RECORD_ALIGNMENT 8
+// Data runs are aligned to a 4-byte boundary, relative to the start of the attribute record
+#define DATA_RUN_ALIGNMENT 4
+
+// Value offset is aligned to a 4-byte boundary, relative to the start of the attribute record
+#define VALUE_OFFSET_ALIGNMENT 4
+
typedef struct
{
ULONGLONG CreationTime;
@@ -423,9 +429,9 @@ typedef struct _B_TREE_KEY
typedef struct _B_TREE_FILENAME_NODE
{
ULONG KeyCount;
- BOOLEAN ExistsOnDisk;
+ BOOLEAN HasValidVCN;
BOOLEAN DiskNeedsUpdating;
- ULONGLONG NodeNumber;
+ ULONGLONG VCN;
PB_TREE_KEY FirstKey;
} B_TREE_FILENAME_NODE, *PB_TREE_FILENAME_NODE;
@@ -570,6 +576,15 @@ AddRun(PNTFS_VCB Vcb,
ULONGLONG NextAssignedCluster,
ULONG RunLength);
+NTSTATUS
+AddIndexRoot(PNTFS_VCB Vcb,
+ PFILE_RECORD_HEADER FileRecord,
+ PNTFS_ATTR_RECORD AttributeAddress,
+ PINDEX_ROOT_ATTRIBUTE NewIndexRoot,
+ ULONG RootLength,
+ PCWSTR Name,
+ USHORT NameLength);
+
NTSTATUS
AddFileName(PFILE_RECORD_HEADER FileRecord,
PNTFS_ATTR_RECORD AttributeAddress,
@@ -718,10 +733,20 @@ VOID
DumpBTree(PB_TREE Tree);
VOID
-DumpBTreeNode(PB_TREE_FILENAME_NODE Node,
+DumpBTreeKey(PB_TREE Tree,
+ PB_TREE_KEY Key,
+ ULONG Number,
+ ULONG Depth);
+
+VOID
+DumpBTreeNode(PB_TREE Tree,
+ PB_TREE_FILENAME_NODE Node,
ULONG Number,
ULONG Depth);
+NTSTATUS
+CreateEmptyBTree(PB_TREE *NewTree);
+
ULONGLONG
GetAllocationOffsetFromVCN(PDEVICE_EXTENSION DeviceExt,
ULONG IndexBufferSize,
@@ -771,6 +796,15 @@ NtfsClose(PNTFS_IRP_CONTEXT IrpContext);
NTSTATUS
NtfsCreate(PNTFS_IRP_CONTEXT IrpContext);
+NTSTATUS
+NtfsCreateDirectory(PDEVICE_EXTENSION DeviceExt,
+ PFILE_OBJECT FileObject,
+ BOOLEAN CaseSensitive,
+ BOOLEAN CanWait);
+
+PFILE_RECORD_HEADER
+NtfsCreateEmptyFileRecord(PDEVICE_EXTENSION DeviceExt);
+
NTSTATUS
NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt,
PFILE_OBJECT FileObject,
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=88a7c3d14b5f74300baac…
commit 88a7c3d14b5f74300baacff3486b377901c2467f
Author: Trevor Thompson <tmt256(a)email.vccs.edu>
AuthorDate: Tue Aug 15 19:57:55 2017 +0000
[NTFS] - Allow for creating a file when the index root gets too large and needs to be moved into an index record. Add some helper functions.
+AllocateIndexNode() - Allocates a new index record in an index allocation.
+CreateDummyKey() - Creates the final B_TREE_KEY for a B_TREE_FILENAME_NODE. Also creates the associated index entry.
GetSizeOfIndexEntries() - Sums the size of each index entry in every key in a B-Tree node.
+SetIndexEntryVCN() - Sets the VCN of a given IndexEntry.
NtfsInsertKey() - Handle instance when the index root grows too large. If it does, add its contents to a new sub-node, and replace contents with a dummy-key whose child is the new node.
UpdateIndexAllocation() - Update index entry if a key has just been assigned a child allocation.
UpdateIndexNode() - Make sure the node exists on disk, and allocate an index record for it if it doesn't.
svn path=/branches/GSoC_2016/NTFS/; revision=75557
---
drivers/filesystems/ntfs/btree.c | 520 ++++++++++++++++++++++++++++++++++++---
drivers/filesystems/ntfs/mft.c | 35 ++-
drivers/filesystems/ntfs/ntfs.h | 8 +-
3 files changed, 519 insertions(+), 44 deletions(-)
diff --git a/drivers/filesystems/ntfs/btree.c b/drivers/filesystems/ntfs/btree.c
index 648111d9c0..727c147a2a 100644
--- a/drivers/filesystems/ntfs/btree.c
+++ b/drivers/filesystems/ntfs/btree.c
@@ -80,6 +80,237 @@ PrintAllVCNs(PDEVICE_EXTENSION Vcb,
ExFreePoolWithTag(Buffer, TAG_NTFS);
}
+/**
+* @name AllocateIndexNode
+* @implemented
+*
+* Allocates a new index record in an index allocation.
+*
+* @param DeviceExt
+* Pointer to the target DEVICE_EXTENSION describing the volume the node will be created on.
+*
+* @param FileRecord
+* Pointer to a copy of the file record containing the index.
+*
+* @param IndexBufferSize
+* Size of an index record for this index, in bytes. Commonly defined as 4096.
+*
+* @param IndexAllocationCtx
+* Pointer to an NTFS_ATTR_CONTEXT describing the index allocation attribute the node will be assigned to.
+*
+* @param IndexAllocationOffset
+* Offset of the index allocation attribute relative to the file record.
+*
+* @param NewVCN
+* Pointer to a ULONGLONG which will receive the VCN of the newly-assigned index record
+*
+* @returns
+* STATUS_SUCCESS in case of success.
+* STATUS_NOT_IMPLEMENTED if there's no $I30 bitmap attribute in the file record.
+*
+* @remarks
+* AllocateIndexNode() doesn't write any data to the index record it creates. Called by UpdateIndexNode().
+* Don't call PrintAllVCNs() or NtfsDumpFileRecord() after calling AllocateIndexNode() before UpdateIndexNode() finishes.
+*/
+NTSTATUS
+AllocateIndexNode(PDEVICE_EXTENSION DeviceExt,
+ PFILE_RECORD_HEADER FileRecord,
+ ULONG IndexBufferSize,
+ PNTFS_ATTR_CONTEXT IndexAllocationCtx,
+ ULONG IndexAllocationOffset,
+ PULONGLONG NewVCN)
+{
+ NTSTATUS Status;
+ PNTFS_ATTR_CONTEXT BitmapCtx;
+ ULONGLONG IndexAllocationLength, BitmapLength;
+ ULONG BitmapOffset;
+ ULONGLONG NextNodeNumber;
+ PCHAR *BitmapMem;
+ ULONG *BitmapPtr;
+ RTL_BITMAP Bitmap;
+ ULONG BytesWritten;
+ ULONG BytesNeeded;
+ LARGE_INTEGER DataSize;
+
+ DPRINT1("AllocateIndexNode(%p, %p, %lu, %p, %lu, %p) called.\n", DeviceExt,
+ FileRecord,
+ IndexBufferSize,
+ IndexAllocationCtx,
+ IndexAllocationOffset,
+ NewVCN);
+
+ // Get the length of the attribute allocation
+ IndexAllocationLength = AttributeDataLength(IndexAllocationCtx->pRecord);
+
+ // Find the bitmap attribute for the index
+ Status = FindAttribute(DeviceExt,
+ FileRecord,
+ AttributeBitmap,
+ L"$I30",
+ 4,
+ &BitmapCtx,
+ &BitmapOffset);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("FIXME: Need to add bitmap attribute!\n");
+ return STATUS_NOT_IMPLEMENTED;
+ }
+
+ // Get the length of the bitmap attribute
+ BitmapLength = AttributeDataLength(BitmapCtx->pRecord);
+
+ NextNodeNumber = IndexAllocationLength / DeviceExt->NtfsInfo.BytesPerIndexRecord;
+
+ // TODO: Find unused allocation in bitmap and use that space first
+
+ // Add another bit to bitmap
+
+ // See how many bytes we need to store the amount of bits we'll have
+ BytesNeeded = NextNodeNumber / 8;
+ if (NextNodeNumber % 8 != 0)
+ BytesNeeded++;
+
+ // Windows seems to allocate the bitmap in 8-byte chunks to keep any bytes from being wasted on padding
+ ALIGN_UP(BytesNeeded, ATTR_RECORD_ALIGNMENT);
+
+ // Do we need to enlarge the bitmap?
+ if (BytesNeeded > BitmapLength)
+ {
+ // TODO: handle synchronization issues that could occur from changing the directory's file record
+ // Change bitmap size
+ DataSize.QuadPart = BytesNeeded;
+ Status = SetResidentAttributeDataLength(DeviceExt,
+ BitmapCtx,
+ BitmapOffset,
+ FileRecord,
+ &DataSize);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Failed to set length of bitmap attribute!\n");
+ ReleaseAttributeContext(BitmapCtx);
+ return Status;
+ }
+ }
+
+ // Enlarge Index Allocation attribute
+ DataSize.QuadPart = IndexAllocationLength + IndexBufferSize;
+ Status = SetNonResidentAttributeDataLength(DeviceExt,
+ IndexAllocationCtx,
+ IndexAllocationOffset,
+ FileRecord,
+ &DataSize);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Failed to set length of index allocation!\n");
+ ReleaseAttributeContext(BitmapCtx);
+ return Status;
+ }
+
+ // Update file record on disk
+ Status = UpdateFileRecord(DeviceExt, IndexAllocationCtx->FileMFTIndex, FileRecord);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Failed to update file record!\n");
+ ReleaseAttributeContext(BitmapCtx);
+ return Status;
+ }
+
+ // Allocate memory for the bitmap. RtlInitializeBitmap() wants a pointer that's ULONG-aligned
+ BitmapMem = ExAllocatePoolWithTag(NonPagedPool, BytesNeeded + sizeof(ULONG) - 1, TAG_NTFS);
+ BitmapPtr = (PULONG)ALIGN_UP_BY((ULONG_PTR)BitmapMem, sizeof(ULONG));
+
+ RtlZeroMemory(BitmapPtr, BytesNeeded);
+
+ // Read the existing bitmap data
+ Status = ReadAttribute(DeviceExt, BitmapCtx, 0, (PCHAR)BitmapPtr, BitmapLength);
+
+ // Initialize bitmap
+ RtlInitializeBitMap(&Bitmap, BitmapPtr, BytesNeeded);
+
+ // Set the bit for the new index record
+ RtlSetBits(&Bitmap, NextNodeNumber, 1);
+
+ // Write the new bitmap attribute
+ Status = WriteAttribute(DeviceExt,
+ BitmapCtx,
+ 0,
+ (const PUCHAR)BitmapPtr,
+ BytesNeeded,
+ &BytesWritten,
+ FileRecord);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Unable to write to $I30 bitmap attribute!\n");
+ }
+
+ // Calculate VCN of new node number
+ *NewVCN = NextNodeNumber * (IndexBufferSize / DeviceExt->NtfsInfo.BytesPerCluster);
+
+ ExFreePoolWithTag(BitmapMem, TAG_NTFS);
+ ReleaseAttributeContext(BitmapCtx);
+
+ return Status;
+}
+
+/**
+* @name CreateDummyKey
+* @implemented
+*
+* Creates the final B_TREE_KEY for a B_TREE_FILENAME_NODE. Also creates the associated index entry.
+*
+* @param HasChildNode
+* BOOLEAN to indicate if this key will have a LesserChild.
+*
+* @return
+* The newly-created key.
+*/
+PB_TREE_KEY
+CreateDummyKey(BOOLEAN HasChildNode)
+{
+ PINDEX_ENTRY_ATTRIBUTE NewIndexEntry;
+ PB_TREE_KEY NewDummyKey;
+
+ // Calculate max size of a dummy key
+ ULONG EntrySize = ALIGN_UP_BY(FIELD_OFFSET(INDEX_ENTRY_ATTRIBUTE, FileName), 8);
+ EntrySize += sizeof(ULONGLONG); // for VCN
+
+ // Create the index entry for the key
+ NewIndexEntry = ExAllocatePoolWithTag(NonPagedPool, EntrySize, TAG_NTFS);
+ if (!NewIndexEntry)
+ {
+ DPRINT1("Couldn't allocate memory for dummy key index entry!\n");
+ return NULL;
+ }
+
+ RtlZeroMemory(NewIndexEntry, EntrySize);
+
+ if (HasChildNode)
+ {
+ NewIndexEntry->Flags = NTFS_INDEX_ENTRY_NODE | NTFS_INDEX_ENTRY_END;
+ }
+ else
+ {
+ NewIndexEntry->Flags = NTFS_INDEX_ENTRY_END;
+ EntrySize -= sizeof(ULONGLONG); // no VCN
+ }
+
+ NewIndexEntry->Length = EntrySize;
+
+ // Create the key
+ NewDummyKey = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_KEY), TAG_NTFS);
+ if (!NewDummyKey)
+ {
+ DPRINT1("Unable to allocate dummy key!\n");
+ ExFreePoolWithTag(NewIndexEntry, TAG_NTFS);
+ return NULL;
+ }
+ RtlZeroMemory(NewDummyKey, sizeof(B_TREE_KEY));
+
+ NewDummyKey->IndexEntry = NewIndexEntry;
+
+ return NewDummyKey;
+}
+
/**
* @name CompareTreeKeys
* @implemented
@@ -500,6 +731,42 @@ CreateBTreeFromIndex(PDEVICE_EXTENSION Vcb,
return STATUS_SUCCESS;
}
+/**
+* @name GetSizeOfIndexEntries
+* @implemented
+*
+* Sums the size of each index entry in every key in a B-Tree node.
+*
+* @param Node
+* Pointer to a B_TREE_FILENAME_NODE. The size of this node's index entries will be returned.
+*
+* @returns
+* The sum of the sizes of every index entry for each key in the B-Tree node.
+*
+* @remarks
+* Gets only the size of the index entries; doesn't include the size of any headers that would be added to an index record.
+*/
+ULONG
+GetSizeOfIndexEntries(PB_TREE_FILENAME_NODE Node)
+{
+ // Start summing the total size of this node's entries
+ ULONG NodeSize = 0;
+
+ // Walk through the list of Node Entries
+ PB_TREE_KEY CurrentKey = Node->FirstKey;
+ ULONG i;
+ for (i = 0; i < Node->KeyCount; i++)
+ {
+ ASSERT(CurrentKey->IndexEntry->Length != 0);
+
+ // Add the length of the current node
+ NodeSize += CurrentKey->IndexEntry->Length;
+ CurrentKey = CurrentKey->NextKey;
+ }
+
+ return NodeSize;
+}
+
/**
* @name CreateIndexRootFromBTree
* @implemented
@@ -686,6 +953,33 @@ CreateIndexBufferFromBTreeNode(PDEVICE_EXTENSION DeviceExt,
return Status;
}
+/**
+* @name SetIndexEntryVCN
+* @implemented
+*
+* Sets the VCN of a given IndexEntry.
+*
+* @param IndexEntry
+* Pointer to an INDEX_ENTRY_ATTRIBUTE structure that will have its VCN set.
+*
+* @param VCN
+* VCN to store in the index entry.
+*
+* @remarks
+* The index entry must have enough memory allocated to store the VCN, and must have the NTFS_INDEX_ENTRY_NODE flag set.
+* The VCN of an index entry is stored at the very end of the structure, after the filename attribute. Since the filename
+* attribute can be a variable size, this function makes setting this member easy.
+*/
+VOID
+SetIndexEntryVCN(PINDEX_ENTRY_ATTRIBUTE IndexEntry, ULONGLONG VCN)
+{
+ PULONGLONG Destination = (PULONGLONG)((ULONG_PTR)IndexEntry + IndexEntry->Length - sizeof(ULONGLONG));
+
+ ASSERT(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE);
+
+ *Destination = VCN;
+}
+
NTSTATUS
UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt,
PB_TREE Tree,
@@ -698,13 +992,20 @@ UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt,
NTSTATUS Status;
BOOLEAN HasIndexAllocation = FALSE;
ULONG i;
+ ULONG IndexAllocationOffset;
- DPRINT1("UpdateIndexAllocations() called.\n");
+ DPRINT1("UpdateIndexAllocation() called.\n");
- Status = FindAttribute(DeviceExt, FileRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationContext, NULL);
+ Status = FindAttribute(DeviceExt, FileRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationContext, &IndexAllocationOffset);
if (NT_SUCCESS(Status))
+ {
HasIndexAllocation = TRUE;
+ PrintAllVCNs(DeviceExt,
+ IndexAllocationContext,
+ IndexBufferSize);
+ }
+
// TODO: Handle bitmap
BitmapContext = NULL;
@@ -719,48 +1020,112 @@ UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt,
DPRINT1("FIXME: Need to add index allocation\n");
return STATUS_NOT_IMPLEMENTED;
}
- else
+
+ Status = UpdateIndexNode(DeviceExt, FileRecord, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, IndexAllocationOffset, BitmapContext);
+ if (!NT_SUCCESS(Status))
{
- Status = UpdateIndexNode(DeviceExt, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, BitmapContext);
- if (!NT_SUCCESS(Status))
+ DPRINT1("ERROR: Failed to update index node!\n");
+ ReleaseAttributeContext(IndexAllocationContext);
+ return Status;
+ }
+
+ // Is the Index Entry large enough to store the VCN?
+ if (!CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)
+ {
+ // Allocate memory for the larger index entry
+ PINDEX_ENTRY_ATTRIBUTE NewEntry = ExAllocatePoolWithTag(NonPagedPool,
+ CurrentKey->IndexEntry->Length + sizeof(ULONGLONG),
+ TAG_NTFS);
+ if (!NewEntry)
{
- DPRINT1("ERROR: Failed to update index node!\n");
- ReleaseAttributeContext(IndexAllocationContext);
- return Status;
+ DPRINT1("ERROR: Unable to allocate memory for new index entry!\n");
+ if (HasIndexAllocation)
+ ReleaseAttributeContext(IndexAllocationContext);
+ return STATUS_INSUFFICIENT_RESOURCES;
}
+
+ // Copy the old entry to the new one
+ RtlCopyMemory(NewEntry, CurrentKey->IndexEntry, CurrentKey->IndexEntry->Length);
+
+ NewEntry->Length += sizeof(ULONGLONG);
+
+ // Free the old memory
+ ExFreePoolWithTag(CurrentKey->IndexEntry, TAG_NTFS);
+
+ CurrentKey->IndexEntry = NewEntry;
+ CurrentKey->IndexEntry->Flags |= NTFS_INDEX_ENTRY_NODE;
}
+ // Update the VCN stored in the index entry of CurrentKey
+ SetIndexEntryVCN(CurrentKey->IndexEntry, CurrentKey->LesserChild->NodeNumber);
+
}
CurrentKey = CurrentKey->NextKey;
}
- if(HasIndexAllocation)
+ if (HasIndexAllocation)
+ {
+ PrintAllVCNs(DeviceExt,
+ IndexAllocationContext,
+ IndexBufferSize);
+
ReleaseAttributeContext(IndexAllocationContext);
+ }
return STATUS_SUCCESS;
}
NTSTATUS
UpdateIndexNode(PDEVICE_EXTENSION DeviceExt,
+ PFILE_RECORD_HEADER FileRecord,
PB_TREE_FILENAME_NODE Node,
ULONG IndexBufferSize,
PNTFS_ATTR_CONTEXT IndexAllocationContext,
+ ULONG IndexAllocationOffset,
PNTFS_ATTR_CONTEXT BitmapContext)
{
ULONG i;
PB_TREE_KEY CurrentKey = Node->FirstKey;
NTSTATUS Status;
- DPRINT1("UpdateIndexNode(%p, %p, %lu, %p, %p) called for index node with VCN %I64u\n", DeviceExt, Node, IndexBufferSize, IndexAllocationContext, BitmapContext, Node->NodeNumber);
+ DPRINT1("UpdateIndexNode(%p, %p, %p, %lu, %p, %lu, %p) called for index node with VCN %I64u\n",
+ DeviceExt,
+ FileRecord,
+ Node,
+ IndexBufferSize,
+ IndexAllocationContext,
+ IndexAllocationOffset,
+ BitmapContext,
+ Node->NodeNumber);
// Do we need to write this node to disk?
if (Node->DiskNeedsUpdating)
{
ULONGLONG NodeOffset;
ULONG LengthWritten;
+ PINDEX_BUFFER IndexBuffer;
+
+ // Does the node need to be assigned a VCN?
+ if (!Node->ExistsOnDisk)
+ {
+ // Allocate the node
+ Status = AllocateIndexNode(DeviceExt,
+ FileRecord,
+ IndexBufferSize,
+ IndexAllocationContext,
+ IndexAllocationOffset,
+ &Node->NodeNumber);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Failed to allocate index record in index allocation!\n");
+ return Status;
+ }
+
+ Node->ExistsOnDisk = TRUE;
+ }
// Allocate memory for an index buffer
- PINDEX_BUFFER IndexBuffer = ExAllocatePoolWithTag(NonPagedPool, IndexBufferSize, TAG_NTFS);
+ IndexBuffer = ExAllocatePoolWithTag(NonPagedPool, IndexBufferSize, TAG_NTFS);
if (!IndexBuffer)
{
DPRINT1("ERROR: Failed to allocate %lu bytes for index buffer!\n", IndexBufferSize);
@@ -780,7 +1145,7 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt,
NodeOffset = GetAllocationOffsetFromVCN(DeviceExt, IndexBufferSize, Node->NodeNumber);
// Write the buffer to the index allocation
- Status = WriteAttribute(DeviceExt, IndexAllocationContext, NodeOffset, (const PUCHAR)IndexBuffer, IndexBufferSize, &LengthWritten, NULL);
+ Status = WriteAttribute(DeviceExt, IndexAllocationContext, NodeOffset, (const PUCHAR)IndexBuffer, IndexBufferSize, &LengthWritten, FileRecord);
if (!NT_SUCCESS(Status) || LengthWritten != IndexBufferSize)
{
DPRINT1("ERROR: Failed to update index allocation!\n");
@@ -806,7 +1171,7 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt,
if (CurrentKey->LesserChild)
{
// Update the child node on disk
- Status = UpdateIndexNode(DeviceExt, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, BitmapContext);
+ Status = UpdateIndexNode(DeviceExt, FileRecord, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, IndexAllocationOffset, BitmapContext);
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR: Failed to update child node!\n");
@@ -996,6 +1361,9 @@ GetAllocationOffsetFromVCN(PDEVICE_EXTENSION DeviceExt,
*
* Inserts a FILENAME_ATTRIBUTE into a B-Tree node.
*
+* @param Tree
+* Pointer to the B_TREE the key (filename attribute) is being inserted into.
+*
* @param FileReference
* Reference number to the file being added. This will be a combination of the MFT index and update sequence number.
*
@@ -1009,27 +1377,35 @@ GetAllocationOffsetFromVCN(PDEVICE_EXTENSION DeviceExt,
* Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
* if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag.
*
+* @param MaxIndexRootSize
+* The maximum size, in bytes, of node entries that can be stored in the index root before it will grow too large for
+* the file record. This number is just the size of the entries, without any headers for the attribute or index root.
+*
* @remarks
* A node is always sorted, with the least comparable filename stored first and a dummy key to mark the end.
*/
NTSTATUS
-NtfsInsertKey(ULONGLONG FileReference,
+NtfsInsertKey(PB_TREE Tree,
+ ULONGLONG FileReference,
PFILENAME_ATTRIBUTE FileNameAttribute,
PB_TREE_FILENAME_NODE Node,
- BOOLEAN CaseSensitive)
+ BOOLEAN CaseSensitive,
+ ULONG MaxIndexRootSize)
{
PB_TREE_KEY NewKey, CurrentKey, PreviousKey;
- NTSTATUS Status = STATUS_SUCCESS;
+ NTSTATUS Status = STATUS_SUCCESS;
ULONG NodeSize;
ULONG AllocatedNodeSize;
ULONG MaxNodeSizeWithoutHeader;
ULONG i;
- DPRINT1("NtfsInsertKey(0x%I64x, %p, %p, %s)\n",
+ DPRINT1("NtfsInsertKey(%p, 0x%I64x, %p, %p, %s, %lu)\n",
+ Tree,
FileReference,
FileNameAttribute,
Node,
- CaseSensitive ? "TRUE" : "FALSE");
+ CaseSensitive ? "TRUE" : "FALSE",
+ MaxIndexRootSize);
// Create the key for the filename attribute
NewKey = CreateBTreeKeyFromFilename(FileReference, FileNameAttribute);
@@ -1044,6 +1420,11 @@ NtfsInsertKey(ULONGLONG FileReference,
// Should the New Key go before the current key?
LONG Comparison = CompareTreeKeys(NewKey, CurrentKey, CaseSensitive);
+ if (Comparison == 0)
+ {
+ DPRINT1("\t\tComparison == 0: %.*S\n", NewKey->IndexEntry->FileName.NameLength, NewKey->IndexEntry->FileName.Name);
+ DPRINT1("\t\tComparison == 0: %.*S\n", CurrentKey->IndexEntry->FileName.NameLength, CurrentKey->IndexEntry->FileName.Name);
+ }
ASSERT(Comparison != 0);
// Is NewKey < CurrentKey?
@@ -1054,7 +1435,14 @@ NtfsInsertKey(ULONGLONG FileReference,
if (CurrentKey->LesserChild)
{
// Insert the key into the child node
- Status = NtfsInsertKey(FileReference, FileNameAttribute, CurrentKey->LesserChild, CaseSensitive);
+ Status = NtfsInsertKey(Tree, FileReference, FileNameAttribute, CurrentKey->LesserChild, CaseSensitive, 0);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Failed to insert key.\n");
+ ExFreePoolWithTag(NewKey, TAG_NTFS);
+ return Status;
+ }
+
}
else
{
@@ -1078,25 +1466,89 @@ NtfsInsertKey(ULONGLONG FileReference,
CurrentKey = CurrentKey->NextKey;
}
- // Is the node larger than its allocated size?
- NodeSize = 0;
- CurrentKey = Node->FirstKey;
- for (i = 0; i < Node->KeyCount; i++)
+ // Determine how much space the index entries will need
+ NodeSize = GetSizeOfIndexEntries(Node);
+
+ // Is Node the root node?
+ if (Node == Tree->RootNode)
{
- NodeSize += CurrentKey->IndexEntry->Length;
- CurrentKey = CurrentKey->NextKey;
- }
+ // Is the index root too large for the file record?
+ if (NodeSize > MaxIndexRootSize)
+ {
+ PB_TREE_FILENAME_NODE NewSubNode, NewIndexRoot;
+ PB_TREE_KEY DummyKey;
+
+ DPRINT1("Collapsing Index Root into sub-node.\n") ;
+
+ DumpBTree(Tree);
+
+ // Create a new node that will hold the keys currently in index root
+ NewSubNode = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_FILENAME_NODE), TAG_NTFS);
+ if (!NewSubNode)
+ {
+ DPRINT1("ERROR: Couldn't allocate memory for new sub-node.\n");
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+ RtlZeroMemory(NewSubNode, sizeof(B_TREE_FILENAME_NODE));
+
+ // Copy the applicable data from the old index root node
+ NewSubNode->KeyCount = Node->KeyCount;
+ NewSubNode->FirstKey = Node->FirstKey;
+ NewSubNode->DiskNeedsUpdating = TRUE;
+
+ // Create a new dummy key, and make the new node it's child
+ DummyKey = CreateDummyKey(TRUE);
+ if (!DummyKey)
+ {
+ DPRINT1("ERROR: Couldn't allocate memory for new root node.\n");
+ ExFreePoolWithTag(NewSubNode, TAG_NTFS);
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ // Make the new node a child of the dummy key
+ DummyKey->LesserChild = NewSubNode;
+
+ // Create a new index root node
+ NewIndexRoot = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_FILENAME_NODE), TAG_NTFS);
+ if (!NewIndexRoot)
+ {
+ DPRINT1("ERROR: Couldn't allocate memory for new index root.\n");
+ ExFreePoolWithTag(NewSubNode, TAG_NTFS);
+ ExFreePoolWithTag(DummyKey, TAG_NTFS);
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+ RtlZeroMemory(NewIndexRoot, sizeof(B_TREE_FILENAME_NODE));
+
+ NewIndexRoot->DiskNeedsUpdating = TRUE;
+
+ // Insert the dummy key into the new node
+ NewIndexRoot->FirstKey = DummyKey;
+ NewIndexRoot->KeyCount = 1;
+ NewIndexRoot->DiskNeedsUpdating = TRUE;
+ NewIndexRoot->ExistsOnDisk = TRUE;
- // TEMPTEMP: TODO: MATH
- AllocatedNodeSize = 0xfe8;
- MaxNodeSizeWithoutHeader = AllocatedNodeSize - 0x28;
+ // Make the new node the Tree's root node
+ Tree->RootNode = NewIndexRoot;
- if (NodeSize > MaxNodeSizeWithoutHeader)
+ DumpBTree(Tree);
+
+ return STATUS_SUCCESS;
+ }
+ }
+ else
{
- DPRINT1("FIXME: Splitting a node is still a WIP!\n");
- //SplitBTreeNode(NULL, Node);
- //DumpBTree(Tree);
- return STATUS_NOT_IMPLEMENTED;
+ // TEMPTEMP: TODO: MATH
+ AllocatedNodeSize = 0xfe8;
+ MaxNodeSizeWithoutHeader = AllocatedNodeSize - 0x28;
+
+ // Has the node grown larger than its allocated size?
+ if (NodeSize > MaxNodeSizeWithoutHeader)
+ {
+ DPRINT1("FIXME: Splitting a node is still a WIP!\n");
+ //SplitBTreeNode(NULL, Node);
+ //DumpBTree(Tree);
+ return STATUS_NOT_IMPLEMENTED;
+ }
}
// NewEntry and NewKey will be destroyed later by DestroyBTree()
diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c
index aeee447e33..0cd2767efb 100644
--- a/drivers/filesystems/ntfs/mft.c
+++ b/drivers/filesystems/ntfs/mft.c
@@ -2056,9 +2056,10 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
ULONG LengthWritten;
PINDEX_ROOT_ATTRIBUTE NewIndexRoot;
ULONG AttributeLength;
+ PNTFS_ATTR_RECORD NextAttribute;
PB_TREE NewTree;
ULONG BtreeIndexLength;
- ULONG MaxIndexSize;
+ ULONG MaxIndexRootSize;
// Allocate memory for the parent directory
ParentFileRecord = ExAllocatePoolWithTag(NonPagedPool,
@@ -2100,11 +2101,29 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
}
// Find the maximum index size given what the file record can hold
- MaxIndexSize = DeviceExt->NtfsInfo.BytesPerFileRecord
- - IndexRootOffset
- - IndexRootContext->pRecord->Resident.ValueOffset
- - FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header)
- - (sizeof(ULONG) * 2);
+ // First, find the max index size assuming index root is the last attribute
+ MaxIndexRootSize = DeviceExt->NtfsInfo.BytesPerFileRecord // Start with the size of a file record
+ - IndexRootOffset // Subtract the length of everything that comes before index root
+ - IndexRootContext->pRecord->Resident.ValueOffset // Subtract the length of the attribute header for index root
+ - FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header) // Subtract the length of the index header for index root
+ - (sizeof(ULONG) * 2); // Subtract the length of the file record end marker and padding
+
+ // Are there attributes after this one?
+ NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset + IndexRootContext->pRecord->Length);
+ if (NextAttribute->Type != AttributeEnd)
+ {
+ // Find the length of all attributes after this one, not counting the end marker
+ ULONG LengthOfAttributes = 0;
+ PNTFS_ATTR_RECORD CurrentAttribute = NextAttribute;
+ while (CurrentAttribute->Type != AttributeEnd)
+ {
+ LengthOfAttributes += CurrentAttribute->Length;
+ CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length);
+ }
+
+ // Leave room for the existing attributes
+ MaxIndexRootSize -= LengthOfAttributes;
+ }
// Allocate memory for the index root data
I30IndexRootLength = AttributeDataLength(IndexRootContext->pRecord);
@@ -2146,7 +2165,7 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
DumpBTree(NewTree);
// Insert the key for the file we're adding
- Status = NtfsInsertKey(FileReferenceNumber, FilenameAttribute, NewTree->RootNode, CaseSensitive);
+ Status = NtfsInsertKey(NewTree, FileReferenceNumber, FilenameAttribute, NewTree->RootNode, CaseSensitive, MaxIndexRootSize);
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR: Failed to insert key into B-Tree!\n");
@@ -2172,7 +2191,7 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
}
// Create the Index Root from the B*Tree
- Status = CreateIndexRootFromBTree(DeviceExt, NewTree, MaxIndexSize, &NewIndexRoot, &BtreeIndexLength);
+ Status = CreateIndexRootFromBTree(DeviceExt, NewTree, MaxIndexRootSize, &NewIndexRoot, &BtreeIndexLength);
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR: Failed to create Index root from B-Tree!\n");
diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h
index 4d436a699e..030b777c22 100644
--- a/drivers/filesystems/ntfs/ntfs.h
+++ b/drivers/filesystems/ntfs/ntfs.h
@@ -728,10 +728,12 @@ GetAllocationOffsetFromVCN(PDEVICE_EXTENSION DeviceExt,
ULONGLONG Vcn);
NTSTATUS
-NtfsInsertKey(ULONGLONG FileReference,
+NtfsInsertKey(PB_TREE Tree,
+ ULONGLONG FileReference,
PFILENAME_ATTRIBUTE FileNameAttribute,
PB_TREE_FILENAME_NODE Node,
- BOOLEAN CaseSensitive);
+ BOOLEAN CaseSensitive,
+ ULONG MaxIndexRootSize);
NTSTATUS
UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt,
@@ -741,9 +743,11 @@ UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt,
NTSTATUS
UpdateIndexNode(PDEVICE_EXTENSION DeviceExt,
+ PFILE_RECORD_HEADER FileRecord,
PB_TREE_FILENAME_NODE Node,
ULONG IndexBufferSize,
PNTFS_ATTR_CONTEXT IndexAllocationContext,
+ ULONG IndexAllocationOffset,
PNTFS_ATTR_CONTEXT BitmapContext);
/* close.c */
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=d484d91eba5724f885cf6…
commit d484d91eba5724f885cf6662e9e8bd87c99d561b
Author: Trevor Thompson <tmt256(a)email.vccs.edu>
AuthorDate: Tue Aug 15 19:32:20 2017 +0000
[NTFS] - Allow for resizing an attribute in the middle of a file record. Add a helper function and minor improvements:
AddRun() - Allow for resizing the size of the data runs when the attribute isn't the last in the file record. Fix some comments.
CreateIndexBufferFromBTreeNode(), CreateIndexRootFromBTree - Fix math of IndexSize when checking if the index buffer is too large.
InternalSetResidentAttributeLength() - Allow changing the length of an attribute in the middle of a file record. Adjust the position of every attribute after the one being resized.
+MoveAttributes() - Moves a block of attributes to a new location in the file Record.
PrintAllVCNs() - Add consideration for an index allocation with a size of 0.
WriteAttribute() - Add optional parameter for a pointer to the file record being written to. If passed a file record, WriteAttribute() will skip reading the file record from disk, and will update the file record in memory before returning. This helps callers that use the file record after writing an attribute to stay in-sync with what's on disk.
svn path=/branches/GSoC_2016/NTFS/; revision=75554
---
drivers/filesystems/ntfs/attrib.c | 47 +++++---
drivers/filesystems/ntfs/btree.c | 26 +++--
drivers/filesystems/ntfs/mft.c | 234 +++++++++++++++++++++++++------------
drivers/filesystems/ntfs/ntfs.h | 12 +-
drivers/filesystems/ntfs/rw.c | 2 +-
drivers/filesystems/ntfs/volinfo.c | 2 +-
6 files changed, 219 insertions(+), 104 deletions(-)
diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c
index 8618862344..e6d3483553 100644
--- a/drivers/filesystems/ntfs/attrib.c
+++ b/drivers/filesystems/ntfs/attrib.c
@@ -352,18 +352,35 @@ AddRun(PNTFS_VCB Vcb,
PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset);
PNTFS_ATTR_RECORD NewRecord;
- DataRunMaxLength += Vcb->NtfsInfo.BytesPerFileRecord - NextAttributeOffset - (sizeof(ULONG) * 2);
+ // Add free space at the end of the file record to DataRunMaxLength
+ DataRunMaxLength += Vcb->NtfsInfo.BytesPerFileRecord - FileRecord->BytesInUse;
- // Can we move the end of the attribute?
- if (NextAttribute->Type != AttributeEnd || DataRunMaxLength < RunBufferSize - 1)
+ // Can we resize the attribute?
+ if (DataRunMaxLength < RunBufferSize)
{
- 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);
+ 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);
@@ -371,20 +388,24 @@ AddRun(PNTFS_VCB Vcb,
// Update the length of the destination attribute
DestinationAttribute->Length = NextAttributeOffset - AttrOffset;
- // Create a new copy of the attribute
+ // 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, which won't be large enough
+ // 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;
- // End the file record
- NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset);
- SetFileRecordEnd(FileRecord, NextAttribute, FILE_RECORD_END);
+ // 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
@@ -397,7 +418,7 @@ AddRun(PNTFS_VCB Vcb,
RunBuffer,
RunBufferSize);
- // Update the attribute copy in the attribute context
+ // Update the attribute record in the attribute context
RtlCopyMemory((PVOID)((ULONG_PTR)AttrContext->pRecord + AttrContext->pRecord->NonResident.MappingPairsOffset),
RunBuffer,
RunBufferSize);
@@ -827,7 +848,7 @@ FreeClusters(PNTFS_VCB Vcb,
}
// update $BITMAP file on disk
- Status = WriteAttribute(Vcb, DataContext, 0, BitmapData, (ULONG)BitmapDataSize, &LengthWritten);
+ Status = WriteAttribute(Vcb, DataContext, 0, BitmapData, (ULONG)BitmapDataSize, &LengthWritten, FileRecord);
if (!NT_SUCCESS(Status))
{
ReleaseAttributeContext(DataContext);
diff --git a/drivers/filesystems/ntfs/btree.c b/drivers/filesystems/ntfs/btree.c
index dbbf3f9f87..648111d9c0 100644
--- a/drivers/filesystems/ntfs/btree.c
+++ b/drivers/filesystems/ntfs/btree.c
@@ -46,6 +46,12 @@ PrintAllVCNs(PDEVICE_EXTENSION Vcb,
ULONGLONG i;
int Count = 0;
+ if (BufferSize == 0)
+ {
+ DPRINT1("Index Allocation is empty.\n");
+ return;
+ }
+
Buffer = ExAllocatePoolWithTag(NonPagedPool, BufferSize, TAG_NTFS);
BytesRead = ReadAttribute(Vcb, IndexAllocationContext, 0, (PCHAR)Buffer, BufferSize);
@@ -281,10 +287,10 @@ CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb,
{
DPRINT1("TODO: Only a node with a single-level is supported right now!\n");
// Needs debugging:
- /*CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb,
+ CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb,
IndexRoot,
IndexAllocationAttributeCtx,
- CurrentNodeEntry);*/
+ CurrentKey->IndexEntry);
}
CurrentKey = NextKey;
@@ -300,10 +306,10 @@ CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb,
{
DPRINT1("TODO: Only a node with a single-level is supported right now!\n");
// Needs debugging:
- /*CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb,
- IndexRoot,
- IndexAllocationAttributeCtx,
- CurrentNodeEntry);*/
+ CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb,
+ IndexRoot,
+ IndexAllocationAttributeCtx,
+ CurrentKey->IndexEntry);
}
break;
@@ -575,7 +581,6 @@ CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt,
{
// Would adding the current entry to the index increase the index size beyond the limit we've set?
ULONG IndexSize = FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header)
- + NewIndexRoot->Header.FirstEntryOffset
+ NewIndexRoot->Header.TotalSizeOfEntries
+ CurrentNodeEntry->Length;
if (IndexSize > MaxIndexSize)
@@ -649,7 +654,6 @@ CreateIndexBufferFromBTreeNode(PDEVICE_EXTENSION DeviceExt,
{
// Would adding the current entry to the index increase the node size beyond the allocation size?
ULONG IndexSize = FIELD_OFFSET(INDEX_BUFFER, Header)
- + IndexBuffer->Header.FirstEntryOffset
+ IndexBuffer->Header.TotalSizeOfEntries
+ CurrentNodeEntry->Length;
if (IndexSize > BufferSize)
@@ -776,7 +780,7 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt,
NodeOffset = GetAllocationOffsetFromVCN(DeviceExt, IndexBufferSize, Node->NodeNumber);
// Write the buffer to the index allocation
- Status = WriteAttribute(DeviceExt, IndexAllocationContext, NodeOffset, (const PUCHAR)IndexBuffer, IndexBufferSize, &LengthWritten);
+ Status = WriteAttribute(DeviceExt, IndexAllocationContext, NodeOffset, (const PUCHAR)IndexBuffer, IndexBufferSize, &LengthWritten, NULL);
if (!NT_SUCCESS(Status) || LengthWritten != IndexBufferSize)
{
DPRINT1("ERROR: Failed to update index allocation!\n");
@@ -945,10 +949,10 @@ DumpBTreeNode(PB_TREE_FILENAME_NODE Node, ULONG Number, ULONG Depth)
ULONG i;
for (i = 0; i < Depth; i++)
DbgPrint(" ");
- DbgPrint("Node #%d, Depth %d, has %d keys\n", Number, Depth, Node->KeyCount);
+ DbgPrint("Node #%d, Depth %d, has %d key%s\n", Number, Depth, Node->KeyCount, Node->KeyCount == 1 ? "" : "s");
CurrentKey = Node->FirstKey;
- for (i = 0; i < Node->KeyCount; i++)
+ for (i = 1; i <= Node->KeyCount; i++)
{
DumpBTreeKey(CurrentKey, i, Depth);
CurrentKey = CurrentKey->NextKey;
diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c
index 23f802dfca..aeee447e33 100644
--- a/drivers/filesystems/ntfs/mft.c
+++ b/drivers/filesystems/ntfs/mft.c
@@ -351,7 +351,7 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
}
// Write out the new bitmap
- Status = WriteAttribute(Vcb, BitmapContext, BitmapOffset, BitmapBuffer, BitmapSize.LowPart, &LengthWritten);
+ Status = WriteAttribute(Vcb, BitmapContext, BitmapOffset, BitmapBuffer, BitmapSize.LowPart, &LengthWritten, Vcb->MasterFileTable);
if (!NT_SUCCESS(Status))
{
ExReleaseResourceLite(&(Vcb->DirResource));
@@ -369,17 +369,72 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
return STATUS_SUCCESS;
}
+/**
+* @name MoveAttributes
+* @implemented
+*
+* Moves a block of attributes to a new location in the file Record. The attribute at FirstAttributeToMove
+* and every attribute after that will be moved to MoveTo.
+*
+* @param DeviceExt
+* Pointer to the DEVICE_EXTENSION (VCB) of the target volume.
+*
+* @param FirstAttributeToMove
+* Pointer to the first NTFS_ATTR_RECORD that needs to be moved. This pointer must reside within a file record.
+*
+* @param FirstAttributeOffset
+* Offset of FirstAttributeToMove relative to the beginning of the file record.
+*
+* @param MoveTo
+* ULONG_PTR with the memory location that will be the new location of the first attribute being moved.
+*
+* @return
+* The new location of the final attribute (i.e. AttributeEnd marker).
+*/
+PNTFS_ATTR_RECORD
+MoveAttributes(PDEVICE_EXTENSION DeviceExt,
+ PNTFS_ATTR_RECORD FirstAttributeToMove,
+ ULONG FirstAttributeOffset,
+ ULONG_PTR MoveTo)
+{
+ // Get the size of all attributes after this one
+ ULONG MemBlockSize = 0;
+ PNTFS_ATTR_RECORD CurrentAttribute = FirstAttributeToMove;
+ ULONG CurrentOffset = FirstAttributeOffset;
+ PNTFS_ATTR_RECORD FinalAttribute;
+
+ while (CurrentAttribute->Type != AttributeEnd && CurrentOffset < DeviceExt->NtfsInfo.BytesPerFileRecord)
+ {
+ CurrentOffset += CurrentAttribute->Length;
+ MemBlockSize += CurrentAttribute->Length;
+ CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length);
+ }
+
+ FinalAttribute = (PNTFS_ATTR_RECORD)(MoveTo + MemBlockSize);
+ MemBlockSize += sizeof(ULONG) * 2; // Add the AttributeEnd and file record end
+
+ ASSERT(MemBlockSize % ATTR_RECORD_ALIGNMENT == 0);
+
+ // Move the attributes after this one
+ RtlMoveMemory((PCHAR)MoveTo, FirstAttributeToMove, MemBlockSize);
+
+ return FinalAttribute;
+}
+
NTSTATUS
-InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext,
+InternalSetResidentAttributeLength(PDEVICE_EXTENSION DeviceExt,
+ PNTFS_ATTR_CONTEXT AttrContext,
PFILE_RECORD_HEADER FileRecord,
ULONG AttrOffset,
ULONG DataSize)
{
PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
+ PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Destination + Destination->Length);
+ PNTFS_ATTR_RECORD FinalAttribute;
+ ULONG OldAttributeLength = Destination->Length;
ULONG NextAttributeOffset;
- USHORT ValueOffset;
- DPRINT("InternalSetResidentAttributeLength( %p, %p, %lu, %lu )\n", AttrContext, FileRecord, AttrOffset, DataSize);
+ DPRINT1("InternalSetResidentAttributeLength( %p, %p, %p, %lu, %lu )\n", DeviceExt, AttrContext, FileRecord, AttrOffset, DataSize);
ASSERT(!AttrContext->pRecord->IsNonResident);
@@ -390,30 +445,46 @@ InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext,
Destination->Length = ALIGN_UP_BY(DataSize + AttrContext->pRecord->Resident.ValueOffset, ATTR_RECORD_ALIGNMENT);
NextAttributeOffset = AttrOffset + Destination->Length;
- // Ensure FileRecord has an up-to-date copy of the attribute
- ValueOffset = AttrContext->pRecord->Resident.ValueOffset;
- RtlCopyMemory((PCHAR)((ULONG_PTR)FileRecord + AttrOffset + ValueOffset),
- (PCHAR)((ULONG_PTR)AttrContext->pRecord + ValueOffset),
- min(DataSize, AttrContext->pRecord->Resident.ValueLength));
+ // Ensure NextAttributeOffset is aligned to an 8-byte boundary
+ ASSERT(NextAttributeOffset % ATTR_RECORD_ALIGNMENT == 0);
- // Free the old copy of the attribute in the context, as it will be the wrong length
- ExFreePoolWithTag(AttrContext->pRecord, TAG_NTFS);
+ // Will the new attribute be larger than the old one?
+ if (Destination->Length > OldAttributeLength)
+ {
+ // Free the old copy of the attribute in the context, as it will be the wrong length
+ ExFreePoolWithTag(AttrContext->pRecord, TAG_NTFS);
- // Create a new copy of the attribute for the context
- AttrContext->pRecord = ExAllocatePoolWithTag(NonPagedPool, Destination->Length, TAG_NTFS);
- if (!AttrContext->pRecord)
+ // Create a new copy of the attribute record for the context
+ AttrContext->pRecord = ExAllocatePoolWithTag(NonPagedPool, Destination->Length, TAG_NTFS);
+ if (!AttrContext->pRecord)
+ {
+ DPRINT1("Unable to allocate memory for attribute!\n");
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+ RtlZeroMemory((PVOID)((ULONG_PTR)AttrContext->pRecord + OldAttributeLength), Destination->Length - OldAttributeLength);
+ RtlCopyMemory(AttrContext->pRecord, Destination, OldAttributeLength);
+ }
+
+ // Are there attributes after this one that need to be moved?
+ if (NextAttribute->Type != AttributeEnd)
{
- DPRINT1("Unable to allocate memory for attribute!\n");
- return STATUS_INSUFFICIENT_RESOURCES;
+ // Move the attributes after this one
+ FinalAttribute = MoveAttributes(DeviceExt, NextAttribute, NextAttributeOffset, (ULONG_PTR)Destination + Destination->Length);
+ }
+ else
+ {
+ // advance to the final "attribute," adjust for the changed length of the attribute we're resizing
+ FinalAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)NextAttribute - OldAttributeLength + Destination->Length);
}
- RtlCopyMemory(AttrContext->pRecord, Destination, Destination->Length);
- // Ensure NextAttributeOffset is aligned to an 8-byte boundary
- ASSERT(NextAttributeOffset % ATTR_RECORD_ALIGNMENT == 0);
-
- // advance Destination to the final "attribute" and set the file record end
- Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)Destination + Destination->Length);
- SetFileRecordEnd(FileRecord, Destination, FILE_RECORD_END);
+ // Update pRecord's length
+ AttrContext->pRecord->Length = Destination->Length;
+ AttrContext->pRecord->Resident.ValueLength = DataSize;
+
+ // set the file record end
+ SetFileRecordEnd(FileRecord, FinalAttribute, FILE_RECORD_END);
+
+ //NtfsDumpFileRecord(DeviceExt, FileRecord);
return STATUS_SUCCESS;
}
@@ -519,6 +590,9 @@ SetFileRecordEnd(PFILE_RECORD_HEADER FileRecord,
PNTFS_ATTR_RECORD AttrEnd,
ULONG EndMarker)
{
+ // Ensure AttrEnd is aligned on an 8-byte boundary, relative to FileRecord
+ ASSERT(((ULONG_PTR)AttrEnd - (ULONG_PTR)FileRecord) % ATTR_RECORD_ALIGNMENT == 0);
+
// mark the end of attributes
AttrEnd->Type = AttributeEnd;
@@ -841,7 +915,7 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
// restore the back-up attribute, if we made one
if (AttribDataSize.QuadPart > 0)
{
- Status = WriteAttribute(Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart, &LengthWritten);
+ Status = WriteAttribute(Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart, &LengthWritten, FileRecord);
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR: Unable to write attribute data to non-resident clusters during migration!\n");
@@ -855,19 +929,10 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
}
}
}
- else if (DataSize->LowPart < AttrContext->pRecord->Resident.ValueLength)
- {
- // we need to decrease the length
- if (NextAttribute->Type != AttributeEnd)
- {
- DPRINT1("FIXME: Don't know how to decrease length of resident attribute unless it's the final attribute!\n");
- return STATUS_NOT_IMPLEMENTED;
- }
- }
// set the new length of the resident attribute (if we didn't migrate it)
if (!AttrContext->pRecord->IsNonResident)
- return InternalSetResidentAttributeLength(AttrContext, FileRecord, AttrOffset, DataSize->LowPart);
+ return InternalSetResidentAttributeLength(Vcb, AttrContext, FileRecord, AttrOffset, DataSize->LowPart);
return STATUS_SUCCESS;
}
@@ -1102,6 +1167,12 @@ ReadAttribute(PDEVICE_EXTENSION Vcb,
* @param RealLengthWritten
* Pointer to a ULONG which will receive how much data was written, in bytes
*
+* @param FileRecord
+* Optional pointer to a FILE_RECORD_HEADER that contains a copy of the file record
+* being written to. Can be NULL, in which case the file record will be read from disk.
+* If not-null, WriteAttribute() will skip reading from disk, and FileRecord
+* will be updated with the newly-written attribute before the function returns.
+*
* @return
* STATUS_SUCCESS if successful, an error code otherwise. STATUS_NOT_IMPLEMENTED if
* writing to a sparse file.
@@ -1117,7 +1188,8 @@ WriteAttribute(PDEVICE_EXTENSION Vcb,
ULONGLONG Offset,
const PUCHAR Buffer,
ULONG Length,
- PULONG RealLengthWritten)
+ PULONG RealLengthWritten,
+ PFILE_RECORD_HEADER FileRecord)
{
ULONGLONG LastLCN;
PUCHAR DataRun;
@@ -1129,12 +1201,13 @@ WriteAttribute(PDEVICE_EXTENSION Vcb,
NTSTATUS Status;
PUCHAR SourceBuffer = Buffer;
LONGLONG StartingOffset;
+ BOOLEAN FileRecordAllocated = FALSE;
//TEMPTEMP
PUCHAR TempBuffer;
- DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p)\n", Vcb, Context, Offset, Buffer, Length, RealLengthWritten);
+ DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p, %p)\n", Vcb, Context, Offset, Buffer, Length, RealLengthWritten, FileRecord);
*RealLengthWritten = 0;
@@ -1143,7 +1216,10 @@ WriteAttribute(PDEVICE_EXTENSION Vcb,
{
ULONG AttributeOffset;
PNTFS_ATTR_CONTEXT FoundContext;
- PFILE_RECORD_HEADER FileRecord;
+ PNTFS_ATTR_RECORD Destination;
+
+ // Ensure requested data is within the bounds of the attribute
+ ASSERT(Offset + Length <= Context->pRecord->Resident.ValueLength);
if (Offset + Length > Context->pRecord->Resident.ValueLength)
{
@@ -1151,16 +1227,21 @@ WriteAttribute(PDEVICE_EXTENSION Vcb,
return STATUS_INVALID_PARAMETER;
}
- FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
-
- if (!FileRecord)
+ // Do we need to read the file record?
+ if (FileRecord == NULL)
{
- DPRINT1("Error: Couldn't allocate file record!\n");
- return STATUS_NO_MEMORY;
- }
+ FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
+ if (!FileRecord)
+ {
+ DPRINT1("Error: Couldn't allocate file record!\n");
+ return STATUS_NO_MEMORY;
+ }
+
+ FileRecordAllocated = TRUE;
- // read the file record
- ReadFileRecord(Vcb, Context->FileMFTIndex, FileRecord);
+ // read the file record
+ ReadFileRecord(Vcb, Context->FileMFTIndex, FileRecord);
+ }
// find where to write the attribute data to
Status = FindAttribute(Vcb, FileRecord,
@@ -1173,28 +1254,37 @@ WriteAttribute(PDEVICE_EXTENSION Vcb,
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR: Couldn't find matching attribute!\n");
- ExFreePoolWithTag(FileRecord, TAG_NTFS);
+ if(FileRecordAllocated)
+ ExFreePoolWithTag(FileRecord, TAG_NTFS);
return Status;
}
+ Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttributeOffset);
+
DPRINT("Offset: %I64u, AttributeOffset: %u, ValueOffset: %u\n", Offset, AttributeOffset, Context->pRecord->Resident.ValueLength);
- Offset += AttributeOffset + Context->pRecord->Resident.ValueOffset;
-
- if (Offset + Length > Vcb->NtfsInfo.BytesPerFileRecord)
+
+ // Will we be writing past the end of the allocated file record?
+ if (Offset + Length + AttributeOffset + Context->pRecord->Resident.ValueOffset > Vcb->NtfsInfo.BytesPerFileRecord)
{
DPRINT1("DRIVER ERROR: Data being written extends past end of file record!\n");
ReleaseAttributeContext(FoundContext);
- ExFreePoolWithTag(FileRecord, TAG_NTFS);
+ if (FileRecordAllocated)
+ ExFreePoolWithTag(FileRecord, TAG_NTFS);
return STATUS_INVALID_PARAMETER;
}
- // copy the data being written into the file record
- RtlCopyMemory((PCHAR)FileRecord + Offset, Buffer, Length);
+ // copy the data being written into the file record. We cast Offset to ULONG, which is safe because it's range has been verified.
+ RtlCopyMemory((PCHAR)((ULONG_PTR)Destination + Context->pRecord->Resident.ValueOffset + (ULONG)Offset), Buffer, Length);
Status = UpdateFileRecord(Vcb, Context->FileMFTIndex, FileRecord);
+ // Update the context's copy of the resident attribute
+ ASSERT(Context->pRecord->Length == Destination->Length);
+ RtlCopyMemory((PVOID)Context->pRecord, Destination, Context->pRecord->Length);
+
ReleaseAttributeContext(FoundContext);
- ExFreePoolWithTag(FileRecord, TAG_NTFS);
+ if (FileRecordAllocated)
+ ExFreePoolWithTag(FileRecord, TAG_NTFS);
if (NT_SUCCESS(Status))
*RealLengthWritten = Length;
@@ -1519,7 +1609,7 @@ UpdateFileNameRecord(PDEVICE_EXTENSION Vcb,
{
// we need to write the index root attribute back to disk
ULONG LengthWritten;
- Status = WriteAttribute(Vcb, IndexRootCtx, 0, (PUCHAR)IndexRecord, AttributeDataLength(IndexRootCtx->pRecord), &LengthWritten);
+ Status = WriteAttribute(Vcb, IndexRootCtx, 0, (PUCHAR)IndexRecord, AttributeDataLength(IndexRootCtx->pRecord), &LengthWritten, MftRecord);
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR: Couldn't update Index Root!\n");
@@ -1661,7 +1751,7 @@ UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb,
break;
}
- Status = WriteAttribute(Vcb, IndexAllocationCtx, RecordOffset, (const PUCHAR)IndexRecord, IndexBlockSize, &Written);
+ Status = WriteAttribute(Vcb, IndexAllocationCtx, RecordOffset, (const PUCHAR)IndexRecord, IndexBlockSize, &Written, MftRecord);
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR Performing write!\n");
@@ -1714,7 +1804,13 @@ UpdateFileRecord(PDEVICE_EXTENSION Vcb,
AddFixupArray(Vcb, &FileRecord->Ntfs);
// write the file record to the master file table
- Status = WriteAttribute(Vcb, Vcb->MFTContext, MftIndex * Vcb->NtfsInfo.BytesPerFileRecord, (const PUCHAR)FileRecord, Vcb->NtfsInfo.BytesPerFileRecord, &BytesWritten);
+ Status = WriteAttribute(Vcb,
+ Vcb->MFTContext,
+ MftIndex * Vcb->NtfsInfo.BytesPerFileRecord,
+ (const PUCHAR)FileRecord,
+ Vcb->NtfsInfo.BytesPerFileRecord,
+ &BytesWritten,
+ FileRecord);
if (!NT_SUCCESS(Status))
{
@@ -1882,7 +1978,7 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
BitmapData[2] = SystemReservedBits;
// write the bitmap back to the MFT's $Bitmap attribute
- Status = WriteAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize, &LengthWritten);
+ Status = WriteAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize, &LengthWritten, FileRecord);
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR encountered when writing $Bitmap attribute!\n");
@@ -1958,10 +2054,8 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
ULONG IndexRootOffset;
ULONGLONG I30IndexRootLength;
ULONG LengthWritten;
- PNTFS_ATTR_RECORD DestinationAttribute;
PINDEX_ROOT_ATTRIBUTE NewIndexRoot;
ULONG AttributeLength;
- PNTFS_ATTR_RECORD NextAttribute;
PB_TREE NewTree;
ULONG BtreeIndexLength;
ULONG MaxIndexSize;
@@ -2065,7 +2159,7 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
DumpBTree(NewTree);
- // Convert B*Tree back to Index
+ // Convert B*Tree back to Index, starting with the index allocation
Status = UpdateIndexAllocation(DeviceExt, NewTree, I30IndexRoot->SizeOfEntry, ParentFileRecord);
if (!NT_SUCCESS(Status))
{
@@ -2103,22 +2197,9 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
if (AttributeLength != IndexRootContext->pRecord->Resident.ValueLength)
{
- DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset);
-
- // Find the attribute (or attribute-end marker) after the index root
- NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)DestinationAttribute + DestinationAttribute->Length);
- if (NextAttribute->Type != AttributeEnd)
- {
- DPRINT1("FIXME: For now, only resizing index root at the end of a file record is supported!\n");
- ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
- ReleaseAttributeContext(IndexRootContext);
- ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
- ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
- return STATUS_NOT_IMPLEMENTED;
- }
-
// Update the length of the attribute in the file record of the parent directory
- Status = InternalSetResidentAttributeLength(IndexRootContext,
+ Status = InternalSetResidentAttributeLength(DeviceExt,
+ IndexRootContext,
ParentFileRecord,
IndexRootOffset,
AttributeLength);
@@ -2153,7 +2234,8 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
0,
(PUCHAR)NewIndexRoot,
AttributeLength,
- &LengthWritten);
+ &LengthWritten,
+ ParentFileRecord);
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR: Unable to write new index root attribute to parent directory!\n");
diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h
index 668815ddd5..4d436a699e 100644
--- a/drivers/filesystems/ntfs/ntfs.h
+++ b/drivers/filesystems/ntfs/ntfs.h
@@ -954,17 +954,25 @@ WriteAttribute(PDEVICE_EXTENSION Vcb,
ULONGLONG Offset,
const PUCHAR Buffer,
ULONG Length,
- PULONG LengthWritten);
+ PULONG LengthWritten,
+ PFILE_RECORD_HEADER FileRecord);
ULONGLONG
AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord);
NTSTATUS
-InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext,
+InternalSetResidentAttributeLength(PDEVICE_EXTENSION DeviceExt,
+ PNTFS_ATTR_CONTEXT AttrContext,
PFILE_RECORD_HEADER FileRecord,
ULONG AttrOffset,
ULONG DataSize);
+PNTFS_ATTR_RECORD
+MoveAttributes(PDEVICE_EXTENSION DeviceExt,
+ PNTFS_ATTR_RECORD FirstAttributeToMove,
+ ULONG FirstAttributeOffset,
+ ULONG_PTR MoveTo);
+
NTSTATUS
SetAttributeDataLength(PFILE_OBJECT FileObject,
PNTFS_FCB Fcb,
diff --git a/drivers/filesystems/ntfs/rw.c b/drivers/filesystems/ntfs/rw.c
index 2c21f3a449..94d05de9ae 100644
--- a/drivers/filesystems/ntfs/rw.c
+++ b/drivers/filesystems/ntfs/rw.c
@@ -478,7 +478,7 @@ NTSTATUS NtfsWriteFile(PDEVICE_EXTENSION DeviceExt,
DPRINT("Length: %lu\tWriteOffset: %lu\tStreamSize: %I64u\n", Length, WriteOffset, StreamSize);
// Write the data to the attribute
- Status = WriteAttribute(DeviceExt, DataContext, WriteOffset, Buffer, Length, LengthWritten);
+ Status = WriteAttribute(DeviceExt, DataContext, WriteOffset, Buffer, Length, LengthWritten, FileRecord);
// Did the write fail?
if (!NT_SUCCESS(Status))
diff --git a/drivers/filesystems/ntfs/volinfo.c b/drivers/filesystems/ntfs/volinfo.c
index d1efa4c269..6b7e598819 100644
--- a/drivers/filesystems/ntfs/volinfo.c
+++ b/drivers/filesystems/ntfs/volinfo.c
@@ -196,7 +196,7 @@ NtfsAllocateClusters(PDEVICE_EXTENSION DeviceExt,
}
- Status = WriteAttribute(DeviceExt, DataContext, 0, BitmapData, (ULONG)BitmapDataSize, &LengthWritten);
+ Status = WriteAttribute(DeviceExt, DataContext, 0, BitmapData, (ULONG)BitmapDataSize, &LengthWritten, BitmapRecord);
ReleaseAttributeContext(DataContext);