https://git.reactos.org/?p=reactos.git;a=commitdiff;h=54f5c3b6ec26f1758b878…
commit 54f5c3b6ec26f1758b878b901f4a2f3f9a1a47cc
Author: Trevor Thompson <tmt256(a)email.vccs.edu>
AuthorDate: Wed Jun 28 03:45:52 2017 +0000
[NTFS] - Begin to implement B-Trees. Allow for creating several new files in a directory.
NtfsAddFilenameToDirectory() - Add CaseSensitive parameter. Update to use new B-Tree code: First, the index is read and converted to a B-Tree in memory. Next, a key for the new file is inserted into the tree. Finally, the tree is converted back to an index root attribute which is written to disk.
+btree.c - Includes functions related to B-Trees (AKA B*Trees).
ntfs.h - Added several structures for representing B-Trees in memory.
Known limitations: For simplicity, only trees with a depth of one are currently supported (i.e. an ordered list of filenames). Directories that have or will require an index allocation to store all their filenames are still TODO. As a consequence, the user will only be able to create about 6 files in a directory.
svn path=/branches/GSoC_2016/NTFS/; revision=75223
---
drivers/filesystems/ntfs/CMakeLists.txt | 1 +
drivers/filesystems/ntfs/attrib.c | 6 +
drivers/filesystems/ntfs/btree.c | 520 ++++++++++++++++++++++++++++++++
drivers/filesystems/ntfs/create.c | 3 +-
drivers/filesystems/ntfs/dirctl.c | 117 ++++---
drivers/filesystems/ntfs/ntfs.h | 61 +++-
6 files changed, 643 insertions(+), 65 deletions(-)
diff --git a/drivers/filesystems/ntfs/CMakeLists.txt b/drivers/filesystems/ntfs/CMakeLists.txt
index ff3b35f647..b9c72a72e7 100644
--- a/drivers/filesystems/ntfs/CMakeLists.txt
+++ b/drivers/filesystems/ntfs/CMakeLists.txt
@@ -1,6 +1,7 @@
list(APPEND SOURCE
attrib.c
+ btree.c
blockdev.c
cleanup.c
close.c
diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c
index 90e20d3819..239e1c0400 100644
--- a/drivers/filesystems/ntfs/attrib.c
+++ b/drivers/filesystems/ntfs/attrib.c
@@ -1566,6 +1566,12 @@ GetStandardInformationFromRecord(PDEVICE_EXTENSION Vcb,
return NULL;
}
+ULONG GetFileNameAttributeLength(PFILENAME_ATTRIBUTE FileNameAttribute)
+{
+ ULONG Length = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + (FileNameAttribute->NameLength * sizeof(WCHAR));
+ return Length;
+}
+
PFILENAME_ATTRIBUTE
GetBestFileNameFromRecord(PDEVICE_EXTENSION Vcb,
PFILE_RECORD_HEADER FileRecord)
diff --git a/drivers/filesystems/ntfs/btree.c b/drivers/filesystems/ntfs/btree.c
new file mode 100644
index 0000000000..604c02c290
--- /dev/null
+++ b/drivers/filesystems/ntfs/btree.c
@@ -0,0 +1,520 @@
+/*
+* ReactOS kernel
+* Copyright (C) 2002, 2017 ReactOS Team
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+* COPYRIGHT: See COPYING in the top level directory
+* PROJECT: ReactOS kernel
+* FILE: drivers/filesystem/ntfs/btree.c
+* PURPOSE: NTFS filesystem driver
+* PROGRAMMERS: Trevor Thompson
+*/
+
+/* INCLUDES *****************************************************************/
+
+#include "ntfs.h"
+
+#define NDEBUG
+#include <debug.h>
+
+/* FUNCTIONS ****************************************************************/
+
+/**
+* @name CompareTreeKeys
+* @implemented
+*
+* Compare two B_TREE_KEY's to determine their order in the tree.
+*
+* @param Key1
+* Pointer to a B_TREE_KEY that will be compared.
+*
+* @param Key2
+* Pointer to the other B_TREE_KEY that will be compared.
+*
+* @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.
+*
+* @returns
+* 0 if the two keys are equal.
+* < 0 if key1 is less thank key2
+* > 0 if key1 is greater than key2
+*
+* @remarks
+* Any other key is always less than the final (dummy) key in a node.
+*/
+LONG
+CompareTreeKeys(PB_TREE_KEY Key1, PB_TREE_KEY Key2, BOOLEAN CaseSensitive)
+{
+ UNICODE_STRING Key1Name, Key2Name;
+
+ // If Key2 is the "dummy key", key 1 will always come first
+ if (Key2->NextKey == NULL)
+ return -1;
+
+ // If Key1 is the "dummy key", key 2 will always come first
+ if (Key1->NextKey == NULL)
+ return 1;
+
+ Key1Name.Buffer = Key1->IndexEntry->FileName.Name;
+ Key1Name.Length = Key1Name.MaximumLength
+ = Key1->IndexEntry->FileName.NameLength * sizeof(WCHAR);
+
+ Key2Name.Buffer = Key2->IndexEntry->FileName.Name;
+ Key2Name.Length = Key2Name.MaximumLength
+ = Key2->IndexEntry->FileName.NameLength * sizeof(WCHAR);
+
+ return RtlCompareUnicodeString(&Key1Name, &Key2Name, !CaseSensitive);
+}
+
+/**
+* @name CreateBTreeFromIndex
+* @implemented
+*
+* Parse an index and create a B-Tree in memory from it.
+*
+* @param IndexRootContext
+* Pointer to an NTFS_ATTR_CONTEXT that describes the location of the index root attribute.
+*
+* @param NewTree
+* Pointer to a PB_TREE that will receive the pointer to a newly-created B-Tree.
+*
+* @returns
+* STATUS_SUCCESS on success.
+* STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
+*
+* @remarks
+* Allocates memory for the entire tree. Caller is responsible for destroying the tree with DestroyBTree().
+*/
+NTSTATUS
+CreateBTreeFromIndex(PNTFS_ATTR_CONTEXT IndexRootContext,
+ PINDEX_ROOT_ATTRIBUTE IndexRoot,
+ PB_TREE *NewTree)
+{
+ PINDEX_ENTRY_ATTRIBUTE CurrentNodeEntry;
+ 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 CurrentKey = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_KEY), TAG_NTFS);
+
+ DPRINT1("CreateBTreeFromIndex(%p, %p, %p)\n", IndexRootContext, IndexRoot, NewTree);
+
+ if (!Tree || !RootNode || !CurrentKey)
+ {
+ DPRINT1("Couldn't allocate enough memory for B-Tree!\n");
+ if (Tree)
+ ExFreePoolWithTag(Tree, TAG_NTFS);
+ if (CurrentKey)
+ ExFreePoolWithTag(CurrentKey, TAG_NTFS);
+ if (RootNode)
+ ExFreePoolWithTag(RootNode, TAG_NTFS);
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ RtlZeroMemory(Tree, sizeof(B_TREE));
+ RtlZeroMemory(RootNode, sizeof(B_TREE_FILENAME_NODE));
+ RtlZeroMemory(CurrentKey, sizeof(B_TREE_KEY));
+
+ // Setup the Tree
+ RootNode->FirstKey = CurrentKey;
+ Tree->RootNode = RootNode;
+
+ // Create a key for each entry in the node
+ CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexRoot
+ + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header)
+ + IndexRoot->Header.FirstEntryOffset);
+ while (TRUE)
+ {
+ // Allocate memory for the current entry
+ CurrentKey->IndexEntry = ExAllocatePoolWithTag(NonPagedPool, CurrentNodeEntry->Length, TAG_NTFS);
+ if (!CurrentKey->IndexEntry)
+ {
+ DPRINT1("ERROR: Couldn't allocate memory for next key!\n");
+ DestroyBTree(Tree);
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ RootNode->KeyCount++;
+
+ // If this isn't the last entry
+ if (!(CurrentNodeEntry->Flags & NTFS_INDEX_ENTRY_END))
+ {
+ // Create the next key
+ PB_TREE_KEY NextKey = ExAllocatePoolWithTag(NonPagedPool, sizeof(PB_TREE_KEY), TAG_NTFS);
+ if (!NextKey)
+ {
+ DPRINT1("ERROR: Couldn't allocate memory for next key!\n");
+ DestroyBTree(Tree);
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ RtlZeroMemory(NextKey, sizeof(PB_TREE_KEY));
+
+ // Add NextKey to the end of the list
+ CurrentKey->NextKey = NextKey;
+
+ // Copy the current entry to its key
+ RtlCopyMemory(CurrentKey->IndexEntry, CurrentNodeEntry, CurrentNodeEntry->Length);
+
+ // Make sure this B-Tree is only one level deep (flat list)
+ if (CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)
+ {
+ DPRINT1("TODO: Only directories with single-level B-Trees are supported right now!\n");
+ DestroyBTree(Tree);
+ return STATUS_NOT_IMPLEMENTED;
+ }
+
+ // Advance to the next entry
+ CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)CurrentNodeEntry + CurrentNodeEntry->Length);
+ CurrentKey = NextKey;
+ }
+ else
+ {
+ // Copy the final entry to its key
+ RtlCopyMemory(CurrentKey->IndexEntry, CurrentNodeEntry, CurrentNodeEntry->Length);
+ CurrentKey->NextKey = NULL;
+
+ // Make sure this B-Tree is only one level deep (flat list)
+ if (CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)
+ {
+ DPRINT1("TODO: Only directories with single-level B-Trees are supported right now!\n");
+ DestroyBTree(Tree);
+ return STATUS_NOT_IMPLEMENTED;
+ }
+
+ break;
+ }
+ }
+
+ *NewTree = Tree;
+
+ return STATUS_SUCCESS;
+}
+
+/**
+* @name CreateIndexRootFromBTree
+* @implemented
+*
+* Parse a B-Tree in memory and convert it into an index that can be written to disk.
+*
+* @param DeviceExt
+* Pointer to the DEVICE_EXTENSION of the target drive.
+*
+* @param Tree
+* Pointer to a B_TREE that describes the index to be written.
+*
+* @param MaxIndexSize
+* Describes how large the index can be before it will take too much space in the file record.
+* 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).
+*
+* @param IndexRoot
+* Pointer to a PINDEX_ROOT_ATTRIBUTE that will receive a pointer to the newly-created index.
+*
+* @param Length
+* Pointer to a ULONG which will receive the length of the new index root.
+*
+* @returns
+* STATUS_SUCCESS on success.
+* STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
+* STATUS_NOT_IMPLEMENTED if the new index can't fit within MaxIndexSize.
+*
+* @remarks
+* If the function succeeds, it's the caller's responsibility to free IndexRoot with ExFreePoolWithTag().
+*/
+NTSTATUS
+CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt,
+ PB_TREE Tree,
+ ULONG MaxIndexSize,
+ PINDEX_ROOT_ATTRIBUTE *IndexRoot,
+ ULONG *Length)
+{
+ PINDEX_ENTRY_ATTRIBUTE CurrentNodeEntry;
+ PINDEX_ROOT_ATTRIBUTE NewIndexRoot = ExAllocatePoolWithTag(NonPagedPool,
+ DeviceExt->NtfsInfo.BytesPerFileRecord,
+ TAG_NTFS);
+
+ DPRINT1("CreateIndexRootFromBTree(%p, %p, 0x%lx, %p, %p)\n", DeviceExt, Tree, MaxIndexSize, IndexRoot, Length);
+
+ if (!NewIndexRoot)
+ {
+ DPRINT1("Failed to allocate memory for Index Root!\n");
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ // Setup the new index root
+ RtlZeroMemory(NewIndexRoot, DeviceExt->NtfsInfo.BytesPerFileRecord);
+
+ NewIndexRoot->AttributeType = AttributeFileName;
+ NewIndexRoot->CollationRule = COLLATION_FILE_NAME;
+ NewIndexRoot->SizeOfEntry = DeviceExt->NtfsInfo.BytesPerIndexRecord;
+ // If Bytes per index record is less than cluster size, clusters per index record becomes sectors per index
+ if (NewIndexRoot->SizeOfEntry < DeviceExt->NtfsInfo.BytesPerCluster)
+ NewIndexRoot->ClustersPerIndexRecord = NewIndexRoot->SizeOfEntry / DeviceExt->NtfsInfo.BytesPerSector;
+ else
+ NewIndexRoot->ClustersPerIndexRecord = NewIndexRoot->SizeOfEntry / DeviceExt->NtfsInfo.BytesPerCluster;
+
+ // Setup the Index node header
+ NewIndexRoot->Header.FirstEntryOffset = sizeof(INDEX_HEADER_ATTRIBUTE);
+ NewIndexRoot->Header.Flags = INDEX_ROOT_SMALL;
+
+ // Start summing the total size of this node's entries
+ NewIndexRoot->Header.TotalSizeOfEntries = NewIndexRoot->Header.FirstEntryOffset;
+
+ // Setup each Node Entry
+ PB_TREE_KEY CurrentKey = Tree->RootNode->FirstKey;
+ CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)NewIndexRoot
+ + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header)
+ + NewIndexRoot->Header.FirstEntryOffset);
+ for (int 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.FirstEntryOffset
+ + NewIndexRoot->Header.TotalSizeOfEntries
+ + CurrentNodeEntry->Length;
+ if (IndexSize > MaxIndexSize)
+ {
+ DPRINT1("TODO: Adding file would require creating an index allocation!\n");
+ ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
+ return STATUS_NOT_IMPLEMENTED;
+ }
+
+ // Copy the index entry
+ if (CurrentKey->IndexEntry->Length > 0)
+ RtlCopyMemory(CurrentNodeEntry, CurrentKey->IndexEntry, CurrentKey->IndexEntry->Length);
+ else
+ DPRINT1("DRIVER ERROR: CurrentKey->IndexEntry->Length <= 0 !\n");
+
+ DPRINT1("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
+ NewIndexRoot->Header.TotalSizeOfEntries += CurrentNodeEntry->Length;
+
+ // Go to the next node
+ CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)CurrentNodeEntry + CurrentNodeEntry->Length);
+
+ CurrentKey = CurrentKey->NextKey;
+ }
+
+ NewIndexRoot->Header.AllocatedSize = NewIndexRoot->Header.TotalSizeOfEntries;
+
+ *IndexRoot = NewIndexRoot;
+ *Length = NewIndexRoot->Header.AllocatedSize + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header);
+
+ return STATUS_SUCCESS;
+}
+
+VOID
+DestroyBTreeKey(PB_TREE_KEY Key)
+{
+ if (Key->IndexEntry)
+ ExFreePoolWithTag(Key->IndexEntry, TAG_NTFS);
+
+ // We'll destroy Key->LesserChild here after we start using it
+
+ ExFreePoolWithTag(Key, TAG_NTFS);
+}
+
+VOID
+DestroyBTreeNode(PB_TREE_FILENAME_NODE Node)
+{
+ PB_TREE_KEY NextKey;
+ PB_TREE_KEY CurrentKey = Node->FirstKey;
+ for (int i = 0; i < Node->KeyCount; i++)
+ {
+ NT_ASSERT(CurrentKey);
+ NextKey = CurrentKey->NextKey;
+ DestroyBTreeKey(CurrentKey);
+ CurrentKey = NextKey;
+ }
+
+ NT_ASSERT(NextKey == NULL);
+
+ ExFreePoolWithTag(Node, TAG_NTFS);
+}
+
+/**
+* @name DestroyBTree
+* @implemented
+*
+* Destroys a B-Tree.
+*
+* @param Tree
+* Pointer to the B_TREE which will be destroyed.
+*
+* @remarks
+* Destroys every bit of data stored in the tree.
+*/
+VOID
+DestroyBTree(PB_TREE Tree)
+{
+ DestroyBTreeNode(Tree->RootNode);
+ ExFreePoolWithTag(Tree, TAG_NTFS);
+}
+
+VOID
+DumpBTreeKey(PB_TREE_KEY Key, int Number, int Depth)
+{
+ for (int i = 0; i < Depth; i++)
+ DbgPrint(" ");
+ DbgPrint(" Key #%d", Number);
+
+ if (!(Key->IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
+ {
+ UNICODE_STRING FileName;
+ FileName.Length = Key->IndexEntry->FileName.NameLength * 2;
+ FileName.MaximumLength = FileName.Length;
+ FileName.Buffer = Key->IndexEntry->FileName.Name;
+ DbgPrint(" '%wZ'\n", &FileName);
+ }
+ else
+ DbgPrint(" (Dummy Key)\n");
+}
+
+DumpBTreeNode(PB_TREE_FILENAME_NODE Node, int Number, int Depth)
+{
+ for (int i = 0; i < Depth; i++)
+ DbgPrint(" ");
+ DbgPrint("Node #%d, Depth %d\n", Number, Depth);
+
+ PB_TREE_KEY CurrentKey = Node->FirstKey;
+ for (int i = 0; i < Node->KeyCount; i++)
+ {
+ DumpBTreeKey(CurrentKey, i, Depth);
+ CurrentKey = CurrentKey->NextKey;
+ }
+}
+
+/**
+* @name DumpBTree
+* @implemented
+*
+* Displays a B-Tree.
+*
+* @param Tree
+* Pointer to the B_TREE which will be displayed.
+*
+* @remarks
+* Displays a diagnostic summary of a B_TREE.
+*/
+VOID
+DumpBTree(PB_TREE Tree)
+{
+ DbgPrint("B_TREE @ %p\n", Tree);
+ DumpBTreeNode(Tree->RootNode, 0, 0);
+}
+
+/**
+* @name NtfsInsertKey
+* @implemented
+*
+* Inserts a FILENAME_ATTRIBUTE into a B-Tree node.
+*
+* @param FileReference
+* Reference number to the file being added. This will be a combination of the MFT index and update sequence number.
+*
+* @param FileNameAttribute
+* Pointer to a FILENAME_ATTRIBUTE which is the data for the key that will be added to the tree. A copy will be made.
+*
+* @param Node
+* Pointer to a B_TREE_FILENAME_NODE into which a new key will be inserted, in order.
+*
+* @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.
+*
+* @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,
+ PFILENAME_ATTRIBUTE FileNameAttribute,
+ PB_TREE_FILENAME_NODE Node,
+ BOOLEAN CaseSensitive)
+{
+ // Calculate size of Attribute and Index Entry
+ ULONG AttributeSize = GetFileNameAttributeLength(FileNameAttribute);
+ ULONG EntrySize = ALIGN_UP_BY(AttributeSize + FIELD_OFFSET(INDEX_ENTRY_ATTRIBUTE, FileName), 8);
+ PINDEX_ENTRY_ATTRIBUTE NewEntry;
+
+ DPRINT1("NtfsInsertKey(0x%02I64, %p, %p, %s)\n",
+ FileReference,
+ FileNameAttribute,
+ Node,
+ CaseSensitive ? "TRUE" : "FALSE");
+
+ // Create a new Index Entry for the file
+ NewEntry = ExAllocatePoolWithTag(NonPagedPool, EntrySize, TAG_NTFS);
+ if (!NewEntry)
+ {
+ DPRINT1("ERROR: Failed to allocate memory for Index Entry!\n");
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ // Setup the Index Entry
+ RtlZeroMemory(NewEntry, EntrySize);
+ NewEntry->Data.Directory.IndexedFile = FileReference;
+ NewEntry->Length = EntrySize;
+ NewEntry->KeyLength = AttributeSize;
+
+ // Copy the FileNameAttribute
+ RtlCopyMemory(&NewEntry->FileName, FileNameAttribute, AttributeSize);
+
+ // Setup the New Key
+ PB_TREE_KEY NewKey = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_KEY), TAG_NTFS);
+ NewKey->IndexEntry = NewEntry;
+ NewKey->NextKey = NULL;
+
+ // Find where to insert the key
+ PB_TREE_KEY CurrentKey = Node->FirstKey;
+ PB_TREE_KEY PreviousKey = NULL;
+ for (int i = 0; i < Node->KeyCount; i++)
+ {
+ // Should the New Key go before the current key?
+ LONG Comparison = CompareTreeKeys(NewKey, CurrentKey, CaseSensitive);
+ if (Comparison == 0)
+ {
+ DPRINT1("DRIVER ERROR: Asked to insert key into tree that already has it!\n");
+ ExFreePoolWithTag(NewKey, TAG_NTFS);
+ ExFreePoolWithTag(NewEntry, TAG_NTFS);
+ return STATUS_INVALID_PARAMETER;
+ }
+ if (Comparison < 0)
+ {
+ // NewKey is < CurrentKey
+ // Insert New Key before Current Key
+ NewKey->NextKey = CurrentKey;
+
+ // was CurrentKey the first key?
+ if (CurrentKey == Node->FirstKey)
+ Node->FirstKey = NewKey;
+ else
+ PreviousKey->NextKey = NewKey;
+ break;
+ }
+
+ PreviousKey = CurrentKey;
+ CurrentKey = CurrentKey->NextKey;
+ }
+
+ Node->KeyCount++;
+
+ // NewEntry and NewKey will be destroyed later by DestroyBTree()
+
+ return STATUS_SUCCESS;
+}
\ No newline at end of file
diff --git a/drivers/filesystems/ntfs/create.c b/drivers/filesystems/ntfs/create.c
index f52cd7d187..586da89b36 100644
--- a/drivers/filesystems/ntfs/create.c
+++ b/drivers/filesystems/ntfs/create.c
@@ -747,7 +747,8 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt,
Status = NtfsAddFilenameToDirectory(DeviceExt,
ParentMftIndex,
FileMftIndex,
- FilenameAttribute);
+ FilenameAttribute,
+ CaseSensitive);
}
ExFreePoolWithTag(FileRecord, TAG_NTFS);
diff --git a/drivers/filesystems/ntfs/dirctl.c b/drivers/filesystems/ntfs/dirctl.c
index 4d19052ac9..ba7231dc80 100644
--- a/drivers/filesystems/ntfs/dirctl.c
+++ b/drivers/filesystems/ntfs/dirctl.c
@@ -53,13 +53,17 @@
* @param FilenameAttribute
* Pointer to the FILENAME_ATTRIBUTE of the file being added to the directory.
*
+* @param CaseSensitive
+* Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
+* if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag.
+*
* @return
* STATUS_SUCCESS on success.
* STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
* STATUS_NOT_IMPLEMENTED if target address isn't at the end of the given file record.
*
* @remarks
-* WIP - Can only support an empty directory.
+* WIP - Can only support a few files in a directory.
* One FILENAME_ATTRIBUTE is added to the directory's index for each link to that file. So, each
* file which contains one FILENAME_ATTRIBUTE for a long name and another for the 8.3 name, will
* get both attributes added to its parent directory.
@@ -68,7 +72,8 @@ NTSTATUS
NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
ULONGLONG DirectoryMftIndex,
ULONGLONG FileReferenceNumber,
- PFILENAME_ATTRIBUTE FilenameAttribute)
+ PFILENAME_ATTRIBUTE FilenameAttribute,
+ BOOLEAN CaseSensitive)
{
NTSTATUS Status = STATUS_SUCCESS;
PFILE_RECORD_HEADER ParentFileRecord;
@@ -76,12 +81,14 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
PINDEX_ROOT_ATTRIBUTE I30IndexRoot;
ULONG IndexRootOffset;
ULONGLONG I30IndexRootLength;
- PINDEX_ENTRY_ATTRIBUTE IndexNodeEntry;
ULONG LengthWritten;
PNTFS_ATTR_RECORD DestinationAttribute;
PINDEX_ROOT_ATTRIBUTE NewIndexRoot;
ULONG AttributeLength;
PNTFS_ATTR_RECORD NextAttribute;
+ PB_TREE NewTree;
+ ULONG BtreeIndexLength;
+ ULONG MaxIndexSize;
// Allocate memory for the parent directory
ParentFileRecord = ExAllocatePoolWithTag(NonPagedPool,
@@ -122,9 +129,15 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
return Status;
}
- I30IndexRootLength = AttributeDataLength(&IndexRootContext->Record);
+ // Find the maximum index size given what the file record can hold
+ MaxIndexSize = DeviceExt->NtfsInfo.BytesPerFileRecord
+ - IndexRootOffset
+ - IndexRootContext->Record.Resident.ValueOffset
+ - FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header)
+ - (sizeof(ULONG) * 2);
// Allocate memory for the index root data
+ I30IndexRootLength = AttributeDataLength(&IndexRootContext->Record);
I30IndexRoot = (PINDEX_ROOT_ATTRIBUTE)ExAllocatePoolWithTag(NonPagedPool, I30IndexRootLength, TAG_NTFS);
if (!I30IndexRoot)
{
@@ -144,82 +157,59 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
return Status;
}
- // Make sure it's empty (temporarily)
- IndexNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)I30IndexRoot + I30IndexRoot->Header.FirstEntryOffset + 0x10);
- if (IndexNodeEntry->Data.Directory.IndexedFile != 0 || IndexNodeEntry->Flags != 2)
+ // Convert the index to a B*Tree
+ Status = CreateBTreeFromIndex(IndexRootContext, I30IndexRoot, &NewTree);
+ if (!NT_SUCCESS(Status))
{
- DPRINT1("FIXME: File-creation is only supported in empty directories right now! Be patient! :)\n");
+ DPRINT1("ERROR: Failed to create B-Tree from Index!\n");
ReleaseAttributeContext(IndexRootContext);
ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
- return STATUS_NOT_IMPLEMENTED;
+ return Status;
}
-
- // Now we need to setup a new index root attribute to replace the old one
- NewIndexRoot = ExAllocatePoolWithTag(NonPagedPool, DeviceExt->NtfsInfo.BytesPerIndexRecord, TAG_NTFS);
- if (!NewIndexRoot)
+
+ DumpBTree(NewTree);
+
+ // Insert the key for the file we're adding
+ Status = NtfsInsertKey(FileReferenceNumber, FilenameAttribute, NewTree->RootNode, CaseSensitive);
+ if (!NT_SUCCESS(Status))
{
- DPRINT1("ERROR: Unable to allocate memory for new index root attribute!\n");
+ DPRINT1("ERROR: Failed to insert key into B-Tree!\n");
+ DestroyBTree(NewTree);
ReleaseAttributeContext(IndexRootContext);
ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
- return STATUS_INSUFFICIENT_RESOURCES;
+ return Status;
}
+
+ DumpBTree(NewTree);
- // Setup the new index record
- RtlZeroMemory(NewIndexRoot, DeviceExt->NtfsInfo.BytesPerIndexRecord); // shouldn't be necessary but aids in debugging
-
- NewIndexRoot->AttributeType = AttributeFileName;
- NewIndexRoot->CollationRule = COLLATION_FILE_NAME;
- NewIndexRoot->SizeOfEntry = DeviceExt->NtfsInfo.BytesPerIndexRecord;
- // If Bytes per index record is less than cluster size, clusters per index record becomes sectors per index
- if(NewIndexRoot->SizeOfEntry < DeviceExt->NtfsInfo.BytesPerCluster)
- NewIndexRoot->ClustersPerIndexRecord = NewIndexRoot->SizeOfEntry / DeviceExt->NtfsInfo.BytesPerSector;
- else
- NewIndexRoot->ClustersPerIndexRecord = NewIndexRoot->SizeOfEntry / DeviceExt->NtfsInfo.BytesPerCluster;
-
- // Setup the Index node header
- NewIndexRoot->Header.FirstEntryOffset = 0x10;
- NewIndexRoot->Header.Flags = INDEX_ROOT_SMALL;
- // still need to calculate sizes
-
- // The first index node entry will be for the filename we're adding
- IndexNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)NewIndexRoot + NewIndexRoot->Header.FirstEntryOffset + 0x10);
- IndexNodeEntry->Data.Directory.IndexedFile = FileReferenceNumber;
- IndexNodeEntry->Flags = INDEX_ROOT_SMALL;
- IndexNodeEntry->KeyLength = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + (2 * FilenameAttribute->NameLength);
- IndexNodeEntry->Length = ALIGN_UP_BY(IndexNodeEntry->KeyLength, 8) + FIELD_OFFSET(INDEX_ENTRY_ATTRIBUTE, FileName);
-
- // Now we can calculate the Node length (temp logic)
- NewIndexRoot->Header.TotalSizeOfEntries = NewIndexRoot->Header.FirstEntryOffset + IndexNodeEntry->Length + 0x10;
- NewIndexRoot->Header.AllocatedSize = NewIndexRoot->Header.TotalSizeOfEntries;
-
- DPRINT1("New Index Node Entry Stream Length: %u\nNew Inde Node Entry Length: %u\n",
- IndexNodeEntry->KeyLength,
- IndexNodeEntry->Length);
-
- // copy over the attribute proper
- RtlCopyMemory(&IndexNodeEntry->FileName, FilenameAttribute, IndexNodeEntry->KeyLength);
-
- // Now setup the dummy key
- IndexNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexNodeEntry + IndexNodeEntry->Length);
-
- IndexNodeEntry->Data.Directory.IndexedFile = 0;
- IndexNodeEntry->Length = 0x10;
- IndexNodeEntry->KeyLength = 0;
- IndexNodeEntry->Flags = NTFS_INDEX_ENTRY_END;
-
- // This is when we'd normally setup the length (already done above)
+ // Convert B*Tree back to Index Root
+ Status = CreateIndexRootFromBTree(DeviceExt, NewTree, MaxIndexSize, &NewIndexRoot, &BtreeIndexLength);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Failed to create Index root from B-Tree!\n");
+ DestroyBTree(NewTree);
+ ReleaseAttributeContext(IndexRootContext);
+ ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
+ ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
+ return Status;
+ }
+
+ // We're done with the B-Tree now
+ DestroyBTree(NewTree);
// Write back the new index root attribute to the parent directory file record
- // First, we need to make sure the attribute is large enough.
+ // 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.
AttributeLength = NewIndexRoot->Header.AllocatedSize + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header);
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)
{
@@ -230,24 +220,27 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
return STATUS_NOT_IMPLEMENTED;
}
-
+
// Update the length of the attribute in the file record of the parent directory
InternalSetResidentAttributeLength(IndexRootContext,
ParentFileRecord,
IndexRootOffset,
AttributeLength);
+ NT_ASSERT(ParentFileRecord->BytesInUse <= DeviceExt->NtfsInfo.BytesPerFileRecord);
+
Status = UpdateFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord);
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR: Failed to update file record of directory with index: %llx\n", DirectoryMftIndex);
ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
+ ReleaseAttributeContext(IndexRootContext);
ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
return Status;
}
- // Update the parent directory with the new index root
+ // Write the new index root to disk
Status = WriteAttribute(DeviceExt,
IndexRootContext,
0,
diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h
index d738ef5fe2..3e01f469a4 100644
--- a/drivers/filesystems/ntfs/ntfs.h
+++ b/drivers/filesystems/ntfs/ntfs.h
@@ -395,6 +395,28 @@ typedef struct
FILENAME_ATTRIBUTE FileName;
} INDEX_ENTRY_ATTRIBUTE, *PINDEX_ENTRY_ATTRIBUTE;
+// Keys are arranged in nodes as an ordered, linked list
+typedef struct _B_TREE_KEY
+{
+ struct _B_TREE_KEY *NextKey;
+ // PB_TREE_FILENAME_NODE LesserChild; // we aren't worried about multi-level trees yet
+ PINDEX_ENTRY_ATTRIBUTE IndexEntry; // must be last member for FIELD_OFFSET
+}B_TREE_KEY, *PB_TREE_KEY;
+
+// Every Node is just an ordered list of keys.
+// Sub-nodes can be found attached to a key (if they exist).
+// A key's sub-node precedes that key in the ordered list.
+typedef struct
+{
+ int KeyCount;
+ PB_TREE_KEY FirstKey;
+} B_TREE_FILENAME_NODE, *PB_TREE_FILENAME_NODE;
+
+typedef struct
+{
+ PB_TREE_FILENAME_NODE RootNode;
+} B_TREE, *PB_TREE;
+
typedef struct
{
ULONGLONG Unknown1;
@@ -559,6 +581,8 @@ DecodeRun(PUCHAR DataRun,
LONGLONG *DataRunOffset,
ULONGLONG *DataRunLength);
+ULONG GetFileNameAttributeLength(PFILENAME_ATTRIBUTE FileNameAttribute);
+
VOID
NtfsDumpDataRuns(PVOID StartOfRun,
ULONGLONG CurrentLCN);
@@ -645,6 +669,38 @@ NtfsDeviceIoControl(IN PDEVICE_OBJECT DeviceObject,
IN BOOLEAN Override);
+/* btree.c */
+
+LONG
+CompareTreeKeys(PB_TREE_KEY Key1,
+ PB_TREE_KEY Key2,
+ BOOLEAN CaseSensitive);
+
+NTSTATUS
+CreateBTreeFromIndex(/*PDEVICE_EXTENSION Vcb,*/
+ PNTFS_ATTR_CONTEXT IndexRootContext,
+ PINDEX_ROOT_ATTRIBUTE IndexRoot,
+ PB_TREE *NewTree);
+
+NTSTATUS
+CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt,
+ PB_TREE Tree,
+ ULONG MaxIndexSize,
+ PINDEX_ROOT_ATTRIBUTE *IndexRoot,
+ ULONG *Length);
+
+VOID
+DestroyBTree(PB_TREE Tree);
+
+VOID
+DumpBTree(PB_TREE Tree);
+
+NTSTATUS
+NtfsInsertKey(ULONGLONG FileReference,
+ PFILENAME_ATTRIBUTE FileNameAttribute,
+ PB_TREE_FILENAME_NODE Node,
+ BOOLEAN CaseSensitive);
+
/* close.c */
NTSTATUS
@@ -683,8 +739,9 @@ NtfsDeviceControl(PNTFS_IRP_CONTEXT IrpContext);
NTSTATUS
NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
ULONGLONG DirectoryMftIndex,
- ULONGLONG FileMftIndex,
- PFILENAME_ATTRIBUTE FilenameAttribute);
+ ULONGLONG FileReferenceNumber,
+ PFILENAME_ATTRIBUTE FilenameAttribute,
+ BOOLEAN CaseSensitive);
ULONGLONG
NtfsGetFileSize(PDEVICE_EXTENSION DeviceExt,
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=98ddf610bcd804bebd056…
commit 98ddf610bcd804bebd056f1916b59e46dc5388eb
Author: Trevor Thompson <tmt256(a)email.vccs.edu>
AuthorDate: Fri Jun 23 17:30:13 2017 +0000
[NTFS] - Fix IncreaseMftSize(); check IrpContext to see if waiting for exclusive access to the MFT is allowed. As pointed out by Pierre.
svn path=/branches/GSoC_2016/NTFS/; revision=75170
---
drivers/filesystems/ntfs/create.c | 24 ++++++++++++++++--------
drivers/filesystems/ntfs/mft.c | 27 +++++++++++++++++++--------
drivers/filesystems/ntfs/ntfs.h | 6 ++++--
3 files changed, 39 insertions(+), 18 deletions(-)
diff --git a/drivers/filesystems/ntfs/create.c b/drivers/filesystems/ntfs/create.c
index 07a9f5ac3e..96f563e75d 100644
--- a/drivers/filesystems/ntfs/create.c
+++ b/drivers/filesystems/ntfs/create.c
@@ -323,7 +323,7 @@ NtfsOpenFile(PDEVICE_EXTENSION DeviceExt,
static
NTSTATUS
NtfsCreateFile(PDEVICE_OBJECT DeviceObject,
- PIRP Irp)
+ PNTFS_IRP_CONTEXT IrpContext)
{
PDEVICE_EXTENSION DeviceExt;
PIO_STACK_LOCATION Stack;
@@ -334,8 +334,9 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject,
// PWSTR FileName;
NTSTATUS Status;
UNICODE_STRING FullPath;
+ PIRP Irp = IrpContext->Irp;
- DPRINT1("NtfsCreateFile(%p, %p) called\n", DeviceObject, Irp);
+ DPRINT1("NtfsCreateFile(%p, %p) called\n", DeviceObject, IrpContext);
DeviceExt = DeviceObject->DeviceExtension;
ASSERT(DeviceExt);
@@ -561,7 +562,7 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject,
}
// Create the file record on disk
- Status = NtfsCreateFileRecord(DeviceExt, FileObject);
+ Status = NtfsCreateFileRecord(DeviceExt, FileObject, BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT));
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR: Couldn't create file record!\n");
@@ -569,7 +570,7 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject,
}
// Now we should be able to open the file
- return NtfsCreateFile(DeviceObject, Irp);
+ return NtfsCreateFile(DeviceObject, IrpContext);
}
}
@@ -615,7 +616,7 @@ NtfsCreate(PNTFS_IRP_CONTEXT IrpContext)
ExAcquireResourceExclusiveLite(&DeviceExt->DirResource,
TRUE);
Status = NtfsCreateFile(DeviceObject,
- IrpContext->Irp);
+ IrpContext);
ExReleaseResourceLite(&DeviceExt->DirResource);
return Status;
@@ -634,13 +635,20 @@ NtfsCreate(PNTFS_IRP_CONTEXT IrpContext)
* @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)
+ PFILE_OBJECT FileObject,
+ BOOLEAN CanWait)
{
NTSTATUS Status = STATUS_SUCCESS;
PFILE_RECORD_HEADER FileRecord;
@@ -649,7 +657,7 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt,
ULONGLONG ParentMftIndex;
ULONGLONG FileMftIndex;
- DPRINT1("NtfsCreateFileRecord(%p, %p)\n", DeviceExt, FileObject);
+ DPRINT1("NtfsCreateFileRecord(%p, %p, %s)\n", DeviceExt, FileObject, CanWait ? "TRUE" : "FALSE");
// allocate memory for file record
FileRecord = ExAllocatePoolWithTag(NonPagedPool,
@@ -708,7 +716,7 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt,
NtfsDumpFileRecord(DeviceExt, FileRecord);
// Now that we've built the file record in memory, we need to store it in the MFT.
- Status = AddNewMftEntry(FileRecord, DeviceExt, &FileMftIndex);
+ 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
diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c
index 62b2979031..8df62ed542 100644
--- a/drivers/filesystems/ntfs/mft.c
+++ b/drivers/filesystems/ntfs/mft.c
@@ -195,10 +195,16 @@ AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord)
* @param Vcb
* Pointer to the VCB (DEVICE_EXTENSION) of the target volume.
*
+*
+* @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 an allocation fails.
* STATUS_INVALID_PARAMETER if there was an error reading the Mft's bitmap.
+* 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,
@@ -206,7 +212,7 @@ AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord)
* This function will wait for exlusive access to the volume fcb.
*/
NTSTATUS
-IncreaseMftSize(PDEVICE_EXTENSION Vcb)
+IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
{
PNTFS_ATTR_CONTEXT BitmapContext;
LARGE_INTEGER BitmapSize;
@@ -221,10 +227,10 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb)
ULONG LengthWritten;
NTSTATUS Status;
- DPRINT1("IncreaseMftSize(%p)\n", Vcb);
+ DPRINT1("IncreaseMftSize(%p, %s)\n", Vcb, CanWait ? "TRUE" : "FALSE");
// We need exclusive access to the mft while we change its size
- if (!ExAcquireResourceExclusiveLite(&(Vcb->DirResource), TRUE))
+ if (!ExAcquireResourceExclusiveLite(&(Vcb->DirResource), CanWait))
{
return STATUS_CANT_WAIT;
}
@@ -1638,17 +1644,22 @@ FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb,
* @param DestinationIndex
* Pointer to a ULONGLONG which will receive the MFT index where the file record was stored.
*
+* @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_OBJECT_NAME_NOT_FOUND if we can't find the MFT's $Bitmap or if we weren't able
* to read the attribute.
* STATUS_INSUFFICIENT_RESOURCES if we can't allocate enough memory for a copy of $Bitmap.
-*
+* STATUS_CANT_WAIT if CanWait was FALSE and the function could not get immediate, exclusive access to the MFT.
*/
NTSTATUS
AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
PDEVICE_EXTENSION DeviceExt,
- PULONGLONG DestinationIndex)
+ PULONGLONG DestinationIndex,
+ BOOLEAN CanWait)
{
NTSTATUS Status = STATUS_SUCCESS;
ULONGLONG MftIndex;
@@ -1661,7 +1672,7 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
LARGE_INTEGER BitmapBits;
UCHAR SystemReservedBits;
- DPRINT1("AddNewMftEntry(%p, %p, %p)\n", FileRecord, DeviceExt, DestinationIndex);
+ DPRINT1("AddNewMftEntry(%p, %p, %p, %s)\n", FileRecord, DeviceExt, DestinationIndex, CanWait ? "TRUE" : "FALSE");
// First, we have to read the mft's $Bitmap attribute
Status = FindAttribute(DeviceExt, DeviceExt->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL);
@@ -1717,14 +1728,14 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
ReleaseAttributeContext(BitmapContext);
// Couldn't find a free record in the MFT, add some blank records and try again
- Status = IncreaseMftSize(DeviceExt);
+ Status = IncreaseMftSize(DeviceExt, CanWait);
if (!NT_SUCCESS(Status))
{
DPRINT1("ERROR: Couldn't find space in MFT for file or increase MFT size!\n");
return Status;
}
- return AddNewMftEntry(FileRecord, DeviceExt, DestinationIndex);
+ return AddNewMftEntry(FileRecord, DeviceExt, DestinationIndex, CanWait);
}
DPRINT1("Creating file record at MFT index: %I64u\n", MftIndex);
diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h
index b9336758ef..799f8e2a4f 100644
--- a/drivers/filesystems/ntfs/ntfs.h
+++ b/drivers/filesystems/ntfs/ntfs.h
@@ -667,7 +667,8 @@ NtfsCreate(PNTFS_IRP_CONTEXT IrpContext);
NTSTATUS
NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt,
- PFILE_OBJECT FileObject);
+ PFILE_OBJECT FileObject,
+ BOOLEAN CanWait);
/* devctl.c */
@@ -825,7 +826,8 @@ NtfsFileSystemControl(PNTFS_IRP_CONTEXT IrpContext);
NTSTATUS
AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
PDEVICE_EXTENSION DeviceExt,
- PULONGLONG DestinationIndex);
+ PULONGLONG DestinationIndex,
+ BOOLEAN CanWait);
PNTFS_ATTR_CONTEXT
PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord);
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=9ab86116a9cdddac4ced4…
commit 9ab86116a9cdddac4ced4ea66dc6d228dc751aa9
Author: Trevor Thompson <tmt256(a)email.vccs.edu>
AuthorDate: Fri Jun 16 06:00:09 2017 +0000
[NTFS] - Add support for expanding the master file table. Fix a bug with BrowseIndexEntries(). Improve diagnostic output.
-AddNewMftEntry() - Increase size of MFT as needed. Fix math for bitmap length. Don't assign file records to MFT indices 0x10 - 0x17; In Windows, these records aren't used unless they have to be, even though they are marked as unused in the bitmap.
+IncreaseMftSize() - Adds room for additional file records in the master file table.
-BrowseIndexEntries() - allow for the rare situation when a non-system file has an MFT index of 0x10.
svn path=/branches/GSoC_2016/NTFS/; revision=75056
---
drivers/filesystems/ntfs/create.c | 2 +
drivers/filesystems/ntfs/mft.c | 199 ++++++++++++++++++++++++++++++++++++--
2 files changed, 192 insertions(+), 9 deletions(-)
diff --git a/drivers/filesystems/ntfs/create.c b/drivers/filesystems/ntfs/create.c
index ef39b44373..07a9f5ac3e 100644
--- a/drivers/filesystems/ntfs/create.c
+++ b/drivers/filesystems/ntfs/create.c
@@ -649,6 +649,8 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt,
ULONGLONG ParentMftIndex;
ULONGLONG FileMftIndex;
+ DPRINT1("NtfsCreateFileRecord(%p, %p)\n", DeviceExt, FileObject);
+
// allocate memory for file record
FileRecord = ExAllocatePoolWithTag(NonPagedPool,
DeviceExt->NtfsInfo.BytesPerFileRecord,
diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c
index 52a7caaa43..62b2979031 100644
--- a/drivers/filesystems/ntfs/mft.c
+++ b/drivers/filesystems/ntfs/mft.c
@@ -186,6 +186,161 @@ AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord)
return AttrRecord->Resident.ValueLength;
}
+/**
+* @name IncreaseMftSize
+* @implemented
+*
+* Increases the size of the master file table on a volume, increasing the space available for file records.
+*
+* @param Vcb
+* Pointer to the VCB (DEVICE_EXTENSION) of the target volume.
+*
+* @return
+* STATUS_SUCCESS on success.
+* STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
+* STATUS_INVALID_PARAMETER if there was an error reading the Mft's bitmap.
+*
+* @remarks
+* Increases the size of the Master File Table by 8 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.
+*/
+NTSTATUS
+IncreaseMftSize(PDEVICE_EXTENSION Vcb)
+{
+ PNTFS_ATTR_CONTEXT BitmapContext;
+ LARGE_INTEGER BitmapSize;
+ LARGE_INTEGER DataSize;
+ LONGLONG BitmapSizeDifference;
+ ULONG DataSizeDifference = Vcb->NtfsInfo.BytesPerFileRecord * 8;
+ ULONG BitmapOffset;
+ PUCHAR BitmapBuffer;
+ ULONGLONG BitmapBytes;
+ ULONGLONG NewBitmapSize;
+ ULONG BytesRead;
+ ULONG LengthWritten;
+ NTSTATUS Status;
+
+ DPRINT1("IncreaseMftSize(%p)\n", Vcb);
+
+ // We need exclusive access to the mft while we change its size
+ if (!ExAcquireResourceExclusiveLite(&(Vcb->DirResource), TRUE))
+ {
+ return STATUS_CANT_WAIT;
+ }
+
+ // Find the bitmap attribute of master file table
+ Status = FindAttribute(Vcb, Vcb->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, &BitmapOffset);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Couldn't find $BITMAP attribute of Mft!\n");
+ ExReleaseResourceLite(&(Vcb->DirResource));
+ return Status;
+ }
+
+ // Get size of Bitmap Attribute
+ BitmapSize.QuadPart = AttributeDataLength(&BitmapContext->Record);
+
+ // Calculate the new mft size
+ DataSize.QuadPart = AttributeDataLength(&(Vcb->MFTContext->Record)) + DataSizeDifference;
+
+ // Determine how many bytes will make up the bitmap
+ BitmapBytes = DataSize.QuadPart / Vcb->NtfsInfo.BytesPerFileRecord / 8;
+
+ // 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);
+
+ // Allocate memory for the bitmap
+ BitmapBuffer = ExAllocatePoolWithTag(NonPagedPool, NewBitmapSize, TAG_NTFS);
+ if (!BitmapBuffer)
+ {
+ DPRINT1("ERROR: Unable to allocate memory for bitmap attribute!\n");
+ ExReleaseResourceLite(&(Vcb->DirResource));
+ ReleaseAttributeContext(BitmapContext);
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ // Zero the bytes we'll be adding
+ RtlZeroMemory((PUCHAR)((ULONG_PTR)BitmapBuffer), NewBitmapSize);
+
+ // Read the bitmap attribute
+ BytesRead = ReadAttribute(Vcb,
+ BitmapContext,
+ 0,
+ (PCHAR)BitmapBuffer,
+ BitmapSize.LowPart);
+ if (BytesRead != BitmapSize.LowPart)
+ {
+ DPRINT1("ERROR: Bytes read != Bitmap size!\n");
+ ExReleaseResourceLite(&(Vcb->DirResource));
+ ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
+ ReleaseAttributeContext(BitmapContext);
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ // Increase the mft size
+ Status = SetNonResidentAttributeDataLength(Vcb, Vcb->MFTContext, Vcb->MftDataOffset, Vcb->MasterFileTable, &DataSize);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Failed to set size of $MFT data attribute!\n");
+ ExReleaseResourceLite(&(Vcb->DirResource));
+ ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
+ ReleaseAttributeContext(BitmapContext);
+ return Status;
+ }
+
+ // If the bitmap grew
+ if (BitmapSizeDifference > 0)
+ {
+ // Set the new bitmap size
+ BitmapSize.QuadPart += BitmapSizeDifference;
+ if (BitmapContext->Record.IsNonResident)
+ Status = SetNonResidentAttributeDataLength(Vcb, BitmapContext, BitmapOffset, Vcb->MasterFileTable, &BitmapSize);
+ else
+ Status = SetResidentAttributeDataLength(Vcb, BitmapContext, BitmapOffset, Vcb->MasterFileTable, &BitmapSize);
+
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Failed to set size of bitmap attribute!\n");
+ ExReleaseResourceLite(&(Vcb->DirResource));
+ ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
+ ReleaseAttributeContext(BitmapContext);
+ return Status;
+ }
+ }
+
+ //NtfsDumpFileAttributes(Vcb, FileRecord);
+
+ // 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");
+ ExReleaseResourceLite(&(Vcb->DirResource));
+ ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
+ ReleaseAttributeContext(BitmapContext);
+ return Status;
+ }
+
+ // Write out the new bitmap
+ Status = WriteAttribute(Vcb, BitmapContext, BitmapOffset, BitmapBuffer, BitmapSize.LowPart, &LengthWritten);
+ if (!NT_SUCCESS(Status))
+ {
+ ExReleaseResourceLite(&(Vcb->DirResource));
+ ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
+ ReleaseAttributeContext(BitmapContext);
+ DPRINT1("ERROR: Couldn't write to bitmap attribute of $MFT!\n");
+ }
+
+ // Cleanup
+ ExReleaseResourceLite(&(Vcb->DirResource));
+ ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
+ ReleaseAttributeContext(BitmapContext);
+
+ return STATUS_SUCCESS;
+}
+
VOID
InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext,
PFILE_RECORD_HEADER FileRecord,
@@ -351,7 +506,7 @@ SetFileRecordEnd(PFILE_RECORD_HEADER FileRecord,
* STATUS_INVALID_PARAMETER if we can't find the last cluster in the data run.
*
* @remarks
-* Called by SetAttributeDataLength(). Use SetAttributeDataLength() unless you have a good
+* Called by SetAttributeDataLength() and IncreaseMftSize(). Use SetAttributeDataLength() unless you have a good
* reason to use this. Doesn't update the file record on disk. Doesn't inform the cache controller of changes with
* any associated files. Synchronization is the callers responsibility.
*/
@@ -485,7 +640,7 @@ SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
* last attribute listed in the file record.
*
* @remarks
-* Called by SetAttributeDataLength(). Use SetAttributeDataLength() unless you have a good
+* Called by SetAttributeDataLength() and IncreaseMftSize(). Use SetAttributeDataLength() unless you have a good
* reason to use this. Doesn't update the file record on disk. Doesn't inform the cache controller of changes with
* any associated files. Synchronization is the callers responsibility.
*/
@@ -1488,7 +1643,6 @@ FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb,
* STATUS_OBJECT_NAME_NOT_FOUND if we can't find the MFT's $Bitmap or if we weren't able
* to read the attribute.
* STATUS_INSUFFICIENT_RESOURCES if we can't allocate enough memory for a copy of $Bitmap.
-* STATUS_NOT_IMPLEMENTED if we need to increase the size of the MFT.
*
*/
NTSTATUS
@@ -1503,9 +1657,13 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
ULONGLONG AttrBytesRead;
PVOID BitmapData;
ULONG LengthWritten;
+ PNTFS_ATTR_CONTEXT BitmapContext;
+ LARGE_INTEGER BitmapBits;
+ UCHAR SystemReservedBits;
+
+ DPRINT1("AddNewMftEntry(%p, %p, %p)\n", FileRecord, DeviceExt, DestinationIndex);
// First, we have to read the mft's $Bitmap attribute
- PNTFS_ATTR_CONTEXT BitmapContext;
Status = FindAttribute(DeviceExt, DeviceExt->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL);
if (!NT_SUCCESS(Status))
{
@@ -1533,20 +1691,40 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
return STATUS_OBJECT_NAME_NOT_FOUND;
}
+ // we need to backup the bits for records 0x10 - 0x17 and leave them unassigned if they aren't assigned
+ RtlCopyMemory(&SystemReservedBits, (PVOID)((ULONG_PTR)BitmapData + 2), 1);
+ RtlFillMemory((PVOID)((ULONG_PTR)BitmapData + 2), 1, (UCHAR)0xFF);
+
+ // Calculate bit count
+ BitmapBits.QuadPart = AttributeDataLength(&(DeviceExt->MFTContext->Record)) /
+ DeviceExt->NtfsInfo.BytesPerFileRecord;
+ if (BitmapBits.HighPart != 0)
+ {
+ DPRINT1("\tFIXME: bitmap sizes beyond 32bits are not yet supported!\n");
+ BitmapBits.LowPart = 0xFFFFFFFF;
+ }
+
// convert buffer into bitmap
- RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, BitmapDataSize * 8);
+ RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, BitmapBits.LowPart);
// set next available bit, preferrably after 23rd bit
MftIndex = RtlFindClearBitsAndSet(&Bitmap, 1, 24);
if ((LONG)MftIndex == -1)
{
- DPRINT1("ERROR: Couldn't find free space in MFT for file record!\n");
+ DPRINT1("Couldn't find free space in MFT for file record, increasing MFT size.\n");
ExFreePoolWithTag(BitmapData, TAG_NTFS);
ReleaseAttributeContext(BitmapContext);
- // TODO: increase mft size
- return STATUS_NOT_IMPLEMENTED;
+ // Couldn't find a free record in the MFT, add some blank records and try again
+ Status = IncreaseMftSize(DeviceExt);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Couldn't find space in MFT for file or increase MFT size!\n");
+ return Status;
+ }
+
+ return AddNewMftEntry(FileRecord, DeviceExt, DestinationIndex);
}
DPRINT1("Creating file record at MFT index: %I64u\n", MftIndex);
@@ -1556,6 +1734,9 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
// [BitmapData should have been updated via RtlFindClearBitsAndSet()]
+ // Restore the system reserved bits
+ RtlCopyMemory((PVOID)((ULONG_PTR)BitmapData + 2), &SystemReservedBits, 1);
+
// write the bitmap back to the MFT's $Bitmap attribute
Status = WriteAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize, &LengthWritten);
if (!NT_SUCCESS(Status))
@@ -1723,7 +1904,7 @@ BrowseIndexEntries(PDEVICE_EXTENSION Vcb,
while (IndexEntry < LastEntry &&
!(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
{
- if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) > 0x10 &&
+ if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) >= 0x10 &&
*CurrentEntry >= *StartEntry &&
IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
CompareFileName(FileName, IndexEntry, DirSearch))
https://git.reactos.org/?p=reactos.git;a=commitdiff;h=1417f286e03dbb76ad9dd…
commit 1417f286e03dbb76ad9ddd99929fbfef5b789a68
Author: Trevor Thompson <tmt256(a)email.vccs.edu>
AuthorDate: Fri Jun 16 05:43:52 2017 +0000
[NTFS] - Restructure some code in preparation for the next commit:
-SetAttributeDataLength() has been split into two functions, SetNonResidentAttributeDataLength() and SetResidentAttributeDataLength(). This should improve code readibility and allows for resizing an attribute when there's no FileObject associated with it.
-Added "MftDataOffset" member to DEVICE_EXTENSION, which stores the offset of the Mft's $DATA attribute. (I'm starting to think it's better to add a member for offset to NTFS_ATTR_CONTEXT directly, but I'll save that level of restructuring for a future commit.)
svn path=/branches/GSoC_2016/NTFS/; revision=75055
---
drivers/filesystems/ntfs/fsctl.c | 8 +-
drivers/filesystems/ntfs/mft.c | 557 ++++++++++++++++++++++++---------------
drivers/filesystems/ntfs/ntfs.h | 15 ++
3 files changed, 360 insertions(+), 220 deletions(-)
diff --git a/drivers/filesystems/ntfs/fsctl.c b/drivers/filesystems/ntfs/fsctl.c
index a1f3048cd0..8acdd64e16 100644
--- a/drivers/filesystems/ntfs/fsctl.c
+++ b/drivers/filesystems/ntfs/fsctl.c
@@ -295,7 +295,13 @@ NtfsGetVolumeData(PDEVICE_OBJECT DeviceObject,
return Status;
}
- Status = FindAttribute(DeviceExt, DeviceExt->MasterFileTable, AttributeData, L"", 0, &DeviceExt->MFTContext, NULL);
+ Status = FindAttribute(DeviceExt,
+ DeviceExt->MasterFileTable,
+ AttributeData,
+ L"",
+ 0,
+ &DeviceExt->MFTContext,
+ &DeviceExt->MftDataOffset);
if (!NT_SUCCESS(Status))
{
DPRINT1("Can't find data attribute for Master File Table.\n");
diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c
index 5b9b291e4e..52a7caaa43 100644
--- a/drivers/filesystems/ntfs/mft.c
+++ b/drivers/filesystems/ntfs/mft.c
@@ -234,7 +234,6 @@ SetAttributeDataLength(PFILE_OBJECT FileObject,
PLARGE_INTEGER DataSize)
{
NTSTATUS Status = STATUS_SUCCESS;
- ULONG BytesPerCluster = Fcb->Vcb->NtfsInfo.BytesPerCluster;
// are we truncating the file?
if (DataSize->QuadPart < AttributeDataLength(&AttrContext->Record))
@@ -248,229 +247,26 @@ SetAttributeDataLength(PFILE_OBJECT FileObject,
if (AttrContext->Record.IsNonResident)
{
- ULONGLONG AllocationSize = ROUND_UP(DataSize->QuadPart, BytesPerCluster);
- PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
- ULONG ExistingClusters = AttrContext->Record.NonResident.AllocatedSize / BytesPerCluster;
-
- // do we need to increase the allocation size?
- if (AttrContext->Record.NonResident.AllocatedSize < AllocationSize)
- {
- ULONG ClustersNeeded = (AllocationSize / BytesPerCluster) - ExistingClusters;
- LARGE_INTEGER LastClusterInDataRun;
- ULONG NextAssignedCluster;
- ULONG AssignedClusters;
-
- if (ExistingClusters == 0)
- {
- LastClusterInDataRun.QuadPart = 0;
- }
- else
- {
- if (!FsRtlLookupLargeMcbEntry(&AttrContext->DataRunsMCB,
- (LONGLONG)AttrContext->Record.NonResident.HighestVCN,
- (PLONGLONG)&LastClusterInDataRun.QuadPart,
- NULL,
- NULL,
- NULL,
- NULL))
- {
- DPRINT1("Error looking up final large MCB entry!\n");
-
- // Most likely, HighestVCN went above the largest mapping
- DPRINT1("Highest VCN of record: %I64u\n", AttrContext->Record.NonResident.HighestVCN);
- return STATUS_INVALID_PARAMETER;
- }
- }
-
- DPRINT("LastClusterInDataRun: %I64u\n", LastClusterInDataRun.QuadPart);
- DPRINT("Highest VCN of record: %I64u\n", AttrContext->Record.NonResident.HighestVCN);
-
- while (ClustersNeeded > 0)
- {
- Status = NtfsAllocateClusters(Fcb->Vcb,
- LastClusterInDataRun.LowPart + 1,
- ClustersNeeded,
- &NextAssignedCluster,
- &AssignedClusters);
-
- if (!NT_SUCCESS(Status))
- {
- DPRINT1("Error: Unable to allocate requested clusters!\n");
- return Status;
- }
-
- // now we need to add the clusters we allocated to the data run
- Status = AddRun(Fcb->Vcb, AttrContext, AttrOffset, FileRecord, NextAssignedCluster, AssignedClusters);
- if (!NT_SUCCESS(Status))
- {
- DPRINT1("Error: Unable to add data run!\n");
- return Status;
- }
-
- ClustersNeeded -= AssignedClusters;
- LastClusterInDataRun.LowPart = NextAssignedCluster + AssignedClusters - 1;
- }
- }
- else if (AttrContext->Record.NonResident.AllocatedSize > AllocationSize)
- {
- // shrink allocation size
- ULONG ClustersToFree = ExistingClusters - (AllocationSize / BytesPerCluster);
- Status = FreeClusters(Fcb->Vcb, AttrContext, AttrOffset, FileRecord, ClustersToFree);
- }
-
- // TODO: is the file compressed, encrypted, or sparse?
-
- // NOTE: we need to have acquired the main resource exclusively, as well as(?) the PagingIoResource
-
- Fcb->RFCB.AllocationSize.QuadPart = AllocationSize;
- AttrContext->Record.NonResident.AllocatedSize = AllocationSize;
- AttrContext->Record.NonResident.DataSize = DataSize->QuadPart;
- AttrContext->Record.NonResident.InitializedSize = DataSize->QuadPart;
-
- DestinationAttribute->NonResident.AllocatedSize = AllocationSize;
- DestinationAttribute->NonResident.DataSize = DataSize->QuadPart;
- DestinationAttribute->NonResident.InitializedSize = DataSize->QuadPart;
-
- DPRINT("Allocated Size: %I64u\n", DestinationAttribute->NonResident.AllocatedSize);
+ Status = SetNonResidentAttributeDataLength(Fcb->Vcb,
+ AttrContext,
+ AttrOffset,
+ FileRecord,
+ DataSize);
}
else
{
// resident attribute
+ Status = SetResidentAttributeDataLength(Fcb->Vcb,
+ AttrContext,
+ AttrOffset,
+ FileRecord,
+ DataSize);
+ }
- // find the next attribute
- ULONG NextAttributeOffset = AttrOffset + AttrContext->Record.Length;
- PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((PCHAR)FileRecord + NextAttributeOffset);
-
- //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord);
-
- // Do we need to increase the data length?
- if (DataSize->QuadPart > AttrContext->Record.Resident.ValueLength)
- {
- // There's usually padding at the end of a record. Do we need to extend past it?
- ULONG MaxValueLength = AttrContext->Record.Length - AttrContext->Record.Resident.ValueOffset;
- if (MaxValueLength < DataSize->LowPart)
- {
- // If this is the last attribute, we could move the end marker to the very end of the file record
- MaxValueLength += Fcb->Vcb->NtfsInfo.BytesPerFileRecord - NextAttributeOffset - (sizeof(ULONG) * 2);
-
- if (MaxValueLength < DataSize->LowPart || NextAttribute->Type != AttributeEnd)
- {
- // convert attribute to non-resident
- PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
- LARGE_INTEGER AttribDataSize;
- PVOID AttribData;
- ULONG EndAttributeOffset;
- ULONG LengthWritten;
-
- DPRINT1("Converting attribute to non-resident.\n");
-
- AttribDataSize.QuadPart = AttrContext->Record.Resident.ValueLength;
-
- // Is there existing data we need to back-up?
- if (AttribDataSize.QuadPart > 0)
- {
- AttribData = ExAllocatePoolWithTag(NonPagedPool, AttribDataSize.QuadPart, TAG_NTFS);
- if (AttribData == NULL)
- {
- DPRINT1("ERROR: Couldn't allocate memory for attribute data. Can't migrate to non-resident!\n");
- return STATUS_INSUFFICIENT_RESOURCES;
- }
-
- // read data to temp buffer
- Status = ReadAttribute(Fcb->Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart);
- if (!NT_SUCCESS(Status))
- {
- DPRINT1("ERROR: Unable to read attribute before migrating!\n");
- ExFreePoolWithTag(AttribData, TAG_NTFS);
- return Status;
- }
- }
-
- // Start by turning this attribute into a 0-length, non-resident attribute, then enlarge it.
-
- // Zero out the NonResident structure
- RtlZeroMemory(&AttrContext->Record.NonResident.LowestVCN,
- FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize) - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.LowestVCN));
- RtlZeroMemory(&Destination->NonResident.LowestVCN,
- FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize) - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.LowestVCN));
-
- // update the mapping pairs offset, which will be 0x40 + length in bytes of the name
- AttrContext->Record.NonResident.MappingPairsOffset = Destination->NonResident.MappingPairsOffset = 0x40 + (Destination->NameLength * 2);
-
- // mark the attribute as non-resident
- AttrContext->Record.IsNonResident = Destination->IsNonResident = 1;
-
- // update the end of the file record
- // calculate position of end markers (1 byte for empty data run)
- EndAttributeOffset = AttrOffset + AttrContext->Record.NonResident.MappingPairsOffset + 1;
- EndAttributeOffset = ALIGN_UP_BY(EndAttributeOffset, 8);
-
- // Update the length
- Destination->Length = EndAttributeOffset - AttrOffset;
- AttrContext->Record.Length = Destination->Length;
-
- // Update the file record end
- SetFileRecordEnd(FileRecord,
- (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + EndAttributeOffset),
- FILE_RECORD_END);
-
- // update file record on disk
- Status = UpdateFileRecord(Fcb->Vcb, AttrContext->FileMFTIndex, FileRecord);
- if (!NT_SUCCESS(Status))
- {
- DPRINT1("ERROR: Couldn't update file record to continue migration!\n");
- if (AttribDataSize.QuadPart > 0)
- ExFreePoolWithTag(AttribData, TAG_NTFS);
- return Status;
- }
-
- // Initialize the MCB, potentially catch an exception
- _SEH2_TRY{
- FsRtlInitializeLargeMcb(&AttrContext->DataRunsMCB, NonPagedPool);
- } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
- _SEH2_YIELD(return _SEH2_GetExceptionCode());
- } _SEH2_END;
-
- // Now we can treat the attribute as non-resident and enlarge it normally
- Status = SetAttributeDataLength(FileObject, Fcb, AttrContext, AttrOffset, FileRecord, DataSize);
- if (!NT_SUCCESS(Status))
- {
- DPRINT1("ERROR: Unable to migrate resident attribute!\n");
- if (AttribDataSize.QuadPart > 0)
- ExFreePoolWithTag(AttribData, TAG_NTFS);
- return Status;
- }
-
- // restore the back-up attribute, if we made one
- if (AttribDataSize.QuadPart > 0)
- {
- Status = WriteAttribute(Fcb->Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart, &LengthWritten);
- if (!NT_SUCCESS(Status))
- {
- DPRINT1("ERROR: Unable to write attribute data to non-resident clusters during migration!\n");
- // TODO: Reverse migration so no data is lost
- ExFreePoolWithTag(AttribData, TAG_NTFS);
- return Status;
- }
-
- ExFreePoolWithTag(AttribData, TAG_NTFS);
- }
- }
- }
- }
- else if (DataSize->LowPart < AttrContext->Record.Resident.ValueLength)
- {
- // we need to decrease the length
- if (NextAttribute->Type != AttributeEnd)
- {
- DPRINT1("FIXME: Don't know how to decrease length of resident attribute unless it's the final attribute!\n");
- return STATUS_NOT_IMPLEMENTED;
- }
- }
-
- // set the new length of the resident attribute (if we didn't migrate it)
- if(!AttrContext->Record.IsNonResident)
- InternalSetResidentAttributeLength(AttrContext, FileRecord, AttrOffset, DataSize->LowPart);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Failed to set size of attribute!\n");
+ return Status;
}
//NtfsDumpFileAttributes(Fcb->Vcb, FileRecord);
@@ -480,6 +276,10 @@ SetAttributeDataLength(PFILE_OBJECT FileObject,
if (NT_SUCCESS(Status))
{
+ if(AttrContext->Record.IsNonResident)
+ Fcb->RFCB.AllocationSize.QuadPart = AttrContext->Record.NonResident.AllocatedSize;
+ else
+ Fcb->RFCB.AllocationSize = *DataSize;
Fcb->RFCB.FileSize = *DataSize;
Fcb->RFCB.ValidDataLength = *DataSize;
CcSetFileSizes(FileObject, (PCC_FILE_SIZES)&Fcb->RFCB.AllocationSize);
@@ -523,6 +323,325 @@ SetFileRecordEnd(PFILE_RECORD_HEADER FileRecord,
FileRecord->BytesInUse = (ULONG_PTR)AttrEnd - (ULONG_PTR)FileRecord + sizeof(ULONG) * 2;
}
+/**
+* @name SetNonResidentAttributeDataLength
+* @implemented
+*
+* Called by SetAttributeDataLength() to set the size of a non-resident attribute. Doesn't update the file record.
+*
+* @param Vcb
+* Pointer to a DEVICE_EXTENSION describing the target disk.
+*
+* @param AttrContext
+* PNTFS_ATTR_CONTEXT describing the location of the attribute whose size is being set.
+*
+* @param AttrOffset
+* Offset, from the beginning of the record, of the attribute being sized.
+*
+* @param FileRecord
+* Pointer to a file record containing the attribute to be resized. Must be a complete file record,
+* not just the header.
+*
+* @param DataSize
+* Pointer to a LARGE_INTEGER describing the new size of the attribute's data.
+*
+* @return
+* STATUS_SUCCESS on success;
+* STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
+* STATUS_INVALID_PARAMETER if we can't find the last cluster in the data run.
+*
+* @remarks
+* Called by SetAttributeDataLength(). Use SetAttributeDataLength() unless you have a good
+* reason to use this. Doesn't update the file record on disk. Doesn't inform the cache controller of changes with
+* any associated files. Synchronization is the callers responsibility.
+*/
+NTSTATUS
+SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
+ PNTFS_ATTR_CONTEXT AttrContext,
+ ULONG AttrOffset,
+ PFILE_RECORD_HEADER FileRecord,
+ PLARGE_INTEGER DataSize)
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ ULONG BytesPerCluster = Vcb->NtfsInfo.BytesPerCluster;
+ ULONGLONG AllocationSize = ROUND_UP(DataSize->QuadPart, BytesPerCluster);
+ PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
+ ULONG ExistingClusters = AttrContext->Record.NonResident.AllocatedSize / BytesPerCluster;
+
+ if (!AttrContext->Record.IsNonResident)
+ {
+ DPRINT1("ERROR: SetNonResidentAttributeDataLength() called for resident attribute!\n");
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ // do we need to increase the allocation size?
+ if (AttrContext->Record.NonResident.AllocatedSize < AllocationSize)
+ {
+ ULONG ClustersNeeded = (AllocationSize / BytesPerCluster) - ExistingClusters;
+ LARGE_INTEGER LastClusterInDataRun;
+ ULONG NextAssignedCluster;
+ ULONG AssignedClusters;
+
+ if (ExistingClusters == 0)
+ {
+ LastClusterInDataRun.QuadPart = 0;
+ }
+ else
+ {
+ if (!FsRtlLookupLargeMcbEntry(&AttrContext->DataRunsMCB,
+ (LONGLONG)AttrContext->Record.NonResident.HighestVCN,
+ (PLONGLONG)&LastClusterInDataRun.QuadPart,
+ NULL,
+ NULL,
+ NULL,
+ NULL))
+ {
+ DPRINT1("Error looking up final large MCB entry!\n");
+
+ // Most likely, HighestVCN went above the largest mapping
+ DPRINT1("Highest VCN of record: %I64u\n", AttrContext->Record.NonResident.HighestVCN);
+ return STATUS_INVALID_PARAMETER;
+ }
+ }
+
+ DPRINT("LastClusterInDataRun: %I64u\n", LastClusterInDataRun.QuadPart);
+ DPRINT("Highest VCN of record: %I64u\n", AttrContext->Record.NonResident.HighestVCN);
+
+ while (ClustersNeeded > 0)
+ {
+ Status = NtfsAllocateClusters(Vcb,
+ LastClusterInDataRun.LowPart + 1,
+ ClustersNeeded,
+ &NextAssignedCluster,
+ &AssignedClusters);
+
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("Error: Unable to allocate requested clusters!\n");
+ return Status;
+ }
+
+ // now we need to add the clusters we allocated to the data run
+ Status = AddRun(Vcb, AttrContext, AttrOffset, FileRecord, NextAssignedCluster, AssignedClusters);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("Error: Unable to add data run!\n");
+ return Status;
+ }
+
+ ClustersNeeded -= AssignedClusters;
+ LastClusterInDataRun.LowPart = NextAssignedCluster + AssignedClusters - 1;
+ }
+ }
+ else if (AttrContext->Record.NonResident.AllocatedSize > AllocationSize)
+ {
+ // shrink allocation size
+ ULONG ClustersToFree = ExistingClusters - (AllocationSize / BytesPerCluster);
+ Status = FreeClusters(Vcb, AttrContext, AttrOffset, FileRecord, ClustersToFree);
+ }
+
+ // TODO: is the file compressed, encrypted, or sparse?
+
+ AttrContext->Record.NonResident.AllocatedSize = AllocationSize;
+ AttrContext->Record.NonResident.DataSize = DataSize->QuadPart;
+ AttrContext->Record.NonResident.InitializedSize = DataSize->QuadPart;
+
+ DestinationAttribute->NonResident.AllocatedSize = AllocationSize;
+ DestinationAttribute->NonResident.DataSize = DataSize->QuadPart;
+ DestinationAttribute->NonResident.InitializedSize = DataSize->QuadPart;
+
+ DPRINT("Allocated Size: %I64u\n", DestinationAttribute->NonResident.AllocatedSize);
+
+ return Status;
+}
+
+/**
+* @name SetResidentAttributeDataLength
+* @implemented
+*
+* Called by SetAttributeDataLength() to set the size of a non-resident attribute. Doesn't update the file record.
+*
+* @param Vcb
+* Pointer to a DEVICE_EXTENSION describing the target disk.
+*
+* @param AttrContext
+* PNTFS_ATTR_CONTEXT describing the location of the attribute whose size is being set.
+*
+* @param AttrOffset
+* Offset, from the beginning of the record, of the attribute being sized.
+*
+* @param FileRecord
+* Pointer to a file record containing the attribute to be resized. Must be a complete file record,
+* not just the header.
+*
+* @param DataSize
+* Pointer to a LARGE_INTEGER describing the new size of the attribute's data.
+*
+* @return
+* STATUS_SUCCESS on success;
+* STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
+* STATUS_INVALID_PARAMETER if AttrContext describes a non-resident attribute.
+* STATUS_NOT_IMPLEMENTED if requested to decrease the size of an attribute that isn't the
+* last attribute listed in the file record.
+*
+* @remarks
+* Called by SetAttributeDataLength(). Use SetAttributeDataLength() unless you have a good
+* reason to use this. Doesn't update the file record on disk. Doesn't inform the cache controller of changes with
+* any associated files. Synchronization is the callers responsibility.
+*/
+NTSTATUS
+SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
+ PNTFS_ATTR_CONTEXT AttrContext,
+ ULONG AttrOffset,
+ PFILE_RECORD_HEADER FileRecord,
+ PLARGE_INTEGER DataSize)
+{
+ NTSTATUS Status;
+
+ // find the next attribute
+ ULONG NextAttributeOffset = AttrOffset + AttrContext->Record.Length;
+ PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((PCHAR)FileRecord + NextAttributeOffset);
+
+ if (AttrContext->Record.IsNonResident)
+ {
+ DPRINT1("ERROR: SetResidentAttributeDataLength() called for non-resident attribute!\n");
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //NtfsDumpFileAttributes(Vcb, FileRecord);
+
+ // Do we need to increase the data length?
+ if (DataSize->QuadPart > AttrContext->Record.Resident.ValueLength)
+ {
+ // There's usually padding at the end of a record. Do we need to extend past it?
+ ULONG MaxValueLength = AttrContext->Record.Length - AttrContext->Record.Resident.ValueOffset;
+ if (MaxValueLength < DataSize->LowPart)
+ {
+ // If this is the last attribute, we could move the end marker to the very end of the file record
+ MaxValueLength += Vcb->NtfsInfo.BytesPerFileRecord - NextAttributeOffset - (sizeof(ULONG) * 2);
+
+ if (MaxValueLength < DataSize->LowPart || NextAttribute->Type != AttributeEnd)
+ {
+ // convert attribute to non-resident
+ PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
+ LARGE_INTEGER AttribDataSize;
+ PVOID AttribData;
+ ULONG EndAttributeOffset;
+ ULONG LengthWritten;
+
+ DPRINT1("Converting attribute to non-resident.\n");
+
+ AttribDataSize.QuadPart = AttrContext->Record.Resident.ValueLength;
+
+ // Is there existing data we need to back-up?
+ if (AttribDataSize.QuadPart > 0)
+ {
+ AttribData = ExAllocatePoolWithTag(NonPagedPool, AttribDataSize.QuadPart, TAG_NTFS);
+ if (AttribData == NULL)
+ {
+ DPRINT1("ERROR: Couldn't allocate memory for attribute data. Can't migrate to non-resident!\n");
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ // read data to temp buffer
+ Status = ReadAttribute(Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Unable to read attribute before migrating!\n");
+ ExFreePoolWithTag(AttribData, TAG_NTFS);
+ return Status;
+ }
+ }
+
+ // Start by turning this attribute into a 0-length, non-resident attribute, then enlarge it.
+
+ // Zero out the NonResident structure
+ RtlZeroMemory(&AttrContext->Record.NonResident.LowestVCN,
+ FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize) - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.LowestVCN));
+ RtlZeroMemory(&Destination->NonResident.LowestVCN,
+ FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize) - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.LowestVCN));
+
+ // update the mapping pairs offset, which will be 0x40 + length in bytes of the name
+ AttrContext->Record.NonResident.MappingPairsOffset = Destination->NonResident.MappingPairsOffset = 0x40 + (Destination->NameLength * 2);
+
+ // mark the attribute as non-resident
+ AttrContext->Record.IsNonResident = Destination->IsNonResident = 1;
+
+ // update the end of the file record
+ // calculate position of end markers (1 byte for empty data run)
+ EndAttributeOffset = AttrOffset + AttrContext->Record.NonResident.MappingPairsOffset + 1;
+ EndAttributeOffset = ALIGN_UP_BY(EndAttributeOffset, 8);
+
+ // Update the length
+ Destination->Length = EndAttributeOffset - AttrOffset;
+ AttrContext->Record.Length = Destination->Length;
+
+ // Update the file record end
+ SetFileRecordEnd(FileRecord,
+ (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + EndAttributeOffset),
+ FILE_RECORD_END);
+
+ // update file record on disk
+ Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Couldn't update file record to continue migration!\n");
+ if (AttribDataSize.QuadPart > 0)
+ ExFreePoolWithTag(AttribData, TAG_NTFS);
+ return Status;
+ }
+
+ // Initialize the MCB, potentially catch an exception
+ _SEH2_TRY{
+ FsRtlInitializeLargeMcb(&AttrContext->DataRunsMCB, NonPagedPool);
+ } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+ _SEH2_YIELD(return _SEH2_GetExceptionCode());
+ } _SEH2_END;
+
+ // Now we can treat the attribute as non-resident and enlarge it normally
+ Status = SetNonResidentAttributeDataLength(Vcb, AttrContext, AttrOffset, FileRecord, DataSize);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Unable to migrate resident attribute!\n");
+ if (AttribDataSize.QuadPart > 0)
+ ExFreePoolWithTag(AttribData, TAG_NTFS);
+ return Status;
+ }
+
+ // restore the back-up attribute, if we made one
+ if (AttribDataSize.QuadPart > 0)
+ {
+ Status = WriteAttribute(Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart, &LengthWritten);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Unable to write attribute data to non-resident clusters during migration!\n");
+ // TODO: Reverse migration so no data is lost
+ ExFreePoolWithTag(AttribData, TAG_NTFS);
+ return Status;
+ }
+
+ ExFreePoolWithTag(AttribData, TAG_NTFS);
+ }
+ }
+ }
+ }
+ else if (DataSize->LowPart < AttrContext->Record.Resident.ValueLength)
+ {
+ // we need to decrease the length
+ if (NextAttribute->Type != AttributeEnd)
+ {
+ DPRINT1("FIXME: Don't know how to decrease length of resident attribute unless it's the final attribute!\n");
+ return STATUS_NOT_IMPLEMENTED;
+ }
+ }
+
+ // set the new length of the resident attribute (if we didn't migrate it)
+ if (!AttrContext->Record.IsNonResident)
+ InternalSetResidentAttributeLength(AttrContext, FileRecord, AttrOffset, DataSize->LowPart);
+
+ return STATUS_SUCCESS;
+}
+
ULONG
ReadAttribute(PDEVICE_EXTENSION Vcb,
PNTFS_ATTR_CONTEXT Context,
diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h
index a3265db9da..b9336758ef 100644
--- a/drivers/filesystems/ntfs/ntfs.h
+++ b/drivers/filesystems/ntfs/ntfs.h
@@ -116,6 +116,7 @@ typedef struct
NTFS_INFO NtfsInfo;
+ ULONG MftDataOffset;
ULONG Flags;
ULONG OpenHandleCount;
@@ -869,6 +870,20 @@ SetFileRecordEnd(PFILE_RECORD_HEADER FileRecord,
PNTFS_ATTR_RECORD AttrEnd,
ULONG EndMarker);
+NTSTATUS
+SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
+ PNTFS_ATTR_CONTEXT AttrContext,
+ ULONG AttrOffset,
+ PFILE_RECORD_HEADER FileRecord,
+ PLARGE_INTEGER DataSize);
+
+NTSTATUS
+SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
+ PNTFS_ATTR_CONTEXT AttrContext,
+ ULONG AttrOffset,
+ PFILE_RECORD_HEADER FileRecord,
+ PLARGE_INTEGER DataSize);
+
ULONGLONG
AttributeAllocatedLength(PNTFS_ATTR_RECORD AttrRecord);