2017-06-28 03:45:52 +00:00
|
|
|
/*
|
|
|
|
* 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
|
2017-06-29 02:36:00 +00:00
|
|
|
* Any other key is always less than the final (dummy) key in a node. Key1 must not be the dummy node.
|
2017-06-28 03:45:52 +00:00
|
|
|
*/
|
|
|
|
LONG
|
|
|
|
CompareTreeKeys(PB_TREE_KEY Key1, PB_TREE_KEY Key2, BOOLEAN CaseSensitive)
|
|
|
|
{
|
|
|
|
UNICODE_STRING Key1Name, Key2Name;
|
2017-06-29 02:36:00 +00:00
|
|
|
LONG Comparison;
|
2017-06-28 03:45:52 +00:00
|
|
|
|
2017-07-04 21:47:43 +00:00
|
|
|
ASSERT(!(Key1->IndexEntry->Flags & NTFS_INDEX_ENTRY_END));
|
|
|
|
|
2017-06-28 03:45:52 +00:00
|
|
|
// If Key2 is the "dummy key", key 1 will always come first
|
|
|
|
if (Key2->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);
|
|
|
|
|
2017-06-29 02:36:00 +00:00
|
|
|
// Are the two keys the same length?
|
2017-07-05 22:10:22 +00:00
|
|
|
if (Key1Name.Length == Key2Name.Length)
|
2017-06-29 02:36:00 +00:00
|
|
|
return RtlCompareUnicodeString(&Key1Name, &Key2Name, !CaseSensitive);
|
|
|
|
|
|
|
|
// Is Key1 shorter?
|
|
|
|
if (Key1Name.Length < Key2Name.Length)
|
|
|
|
{
|
|
|
|
// Truncate KeyName2 to be the same length as KeyName1
|
|
|
|
Key2Name.Length = Key1Name.Length;
|
|
|
|
|
|
|
|
// Compare the names of the same length
|
|
|
|
Comparison = RtlCompareUnicodeString(&Key1Name, &Key2Name, !CaseSensitive);
|
|
|
|
|
|
|
|
// If the truncated files are the same length, the shorter one comes first
|
|
|
|
if (Comparison == 0)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Key2 is shorter
|
|
|
|
// Truncate KeyName1 to be the same length as KeyName2
|
|
|
|
Key1Name.Length = Key2Name.Length;
|
|
|
|
|
|
|
|
// Compare the names of the same length
|
|
|
|
Comparison = RtlCompareUnicodeString(&Key1Name, &Key2Name, !CaseSensitive);
|
|
|
|
|
|
|
|
// If the truncated files are the same length, the shorter one comes first
|
|
|
|
if (Comparison == 0)
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return Comparison;
|
2017-06-28 03:45:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @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);
|
2017-07-18 19:59:36 +00:00
|
|
|
ULONG CurrentOffset = IndexRoot->Header.FirstEntryOffset;
|
2017-06-28 03:45:52 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2017-07-18 19:59:36 +00:00
|
|
|
// Make sure we won't try reading past the attribute-end
|
|
|
|
if (FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header) + IndexRoot->Header.TotalSizeOfEntries > IndexRootContext->Record.Resident.ValueLength)
|
|
|
|
{
|
|
|
|
DPRINT1("Filesystem corruption detected!\n");
|
|
|
|
DestroyBTree(Tree);
|
|
|
|
return STATUS_FILE_CORRUPT_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start at the first node entry
|
2017-06-28 03:45:52 +00:00
|
|
|
CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexRoot
|
|
|
|
+ FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header)
|
|
|
|
+ IndexRoot->Header.FirstEntryOffset);
|
2017-07-18 19:59:36 +00:00
|
|
|
|
|
|
|
// Create a key for each entry in the node
|
|
|
|
while (CurrentOffset < IndexRoot->Header.TotalSizeOfEntries)
|
2017-06-28 03:45:52 +00:00
|
|
|
{
|
|
|
|
// 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
|
2017-07-18 19:59:36 +00:00
|
|
|
CurrentOffset += CurrentNodeEntry->Length;
|
2017-06-28 03:45:52 +00:00
|
|
|
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)
|
|
|
|
{
|
2017-07-05 16:29:16 +00:00
|
|
|
ULONG i;
|
2017-06-29 02:36:00 +00:00
|
|
|
PB_TREE_KEY CurrentKey;
|
2017-06-28 03:45:52 +00:00
|
|
|
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
|
2017-06-29 02:36:00 +00:00
|
|
|
CurrentKey = Tree->RootNode->FirstKey;
|
2017-06-28 03:45:52 +00:00
|
|
|
CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)NewIndexRoot
|
|
|
|
+ FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header)
|
|
|
|
+ NewIndexRoot->Header.FirstEntryOffset);
|
2017-06-29 02:36:00 +00:00
|
|
|
for (i = 0; i < Tree->RootNode->KeyCount; i++)
|
2017-06-28 03:45:52 +00:00
|
|
|
{
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2017-07-04 21:47:43 +00:00
|
|
|
ASSERT(CurrentKey->IndexEntry->Length != 0);
|
|
|
|
|
2017-06-28 03:45:52 +00:00
|
|
|
// Copy the index entry
|
2017-07-04 21:47:43 +00:00
|
|
|
RtlCopyMemory(CurrentNodeEntry, CurrentKey->IndexEntry, CurrentKey->IndexEntry->Length);
|
2017-06-28 03:45:52 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2017-07-18 19:59:36 +00:00
|
|
|
// Go to the next node entry
|
2017-06-28 03:45:52 +00:00
|
|
|
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;
|
2017-07-05 16:29:16 +00:00
|
|
|
ULONG i;
|
2017-06-29 02:36:00 +00:00
|
|
|
for (i = 0; i < Node->KeyCount; i++)
|
2017-06-28 03:45:52 +00:00
|
|
|
{
|
|
|
|
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
|
2017-07-05 16:29:16 +00:00
|
|
|
DumpBTreeKey(PB_TREE_KEY Key, ULONG Number, ULONG Depth)
|
2017-06-28 03:45:52 +00:00
|
|
|
{
|
2017-07-05 16:29:16 +00:00
|
|
|
ULONG i;
|
2017-06-29 02:36:00 +00:00
|
|
|
for (i = 0; i < Depth; i++)
|
2017-06-28 03:45:52 +00:00
|
|
|
DbgPrint(" ");
|
|
|
|
DbgPrint(" Key #%d", Number);
|
|
|
|
|
|
|
|
if (!(Key->IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
|
|
|
|
{
|
|
|
|
UNICODE_STRING FileName;
|
2017-07-04 21:47:43 +00:00
|
|
|
FileName.Length = Key->IndexEntry->FileName.NameLength * sizeof(WCHAR);
|
2017-06-28 03:45:52 +00:00
|
|
|
FileName.MaximumLength = FileName.Length;
|
|
|
|
FileName.Buffer = Key->IndexEntry->FileName.Name;
|
|
|
|
DbgPrint(" '%wZ'\n", &FileName);
|
|
|
|
}
|
|
|
|
else
|
2017-07-05 22:10:22 +00:00
|
|
|
{
|
2017-06-28 03:45:52 +00:00
|
|
|
DbgPrint(" (Dummy Key)\n");
|
2017-07-05 22:10:22 +00:00
|
|
|
}
|
2017-06-28 03:45:52 +00:00
|
|
|
}
|
|
|
|
|
2017-06-29 02:36:00 +00:00
|
|
|
VOID
|
2017-07-05 16:29:16 +00:00
|
|
|
DumpBTreeNode(PB_TREE_FILENAME_NODE Node, ULONG Number, ULONG Depth)
|
2017-06-28 03:45:52 +00:00
|
|
|
{
|
2017-06-29 02:36:00 +00:00
|
|
|
PB_TREE_KEY CurrentKey;
|
2017-07-05 16:29:16 +00:00
|
|
|
ULONG i;
|
2017-06-29 02:36:00 +00:00
|
|
|
for (i = 0; i < Depth; i++)
|
2017-06-28 03:45:52 +00:00
|
|
|
DbgPrint(" ");
|
|
|
|
DbgPrint("Node #%d, Depth %d\n", Number, Depth);
|
|
|
|
|
2017-06-29 02:36:00 +00:00
|
|
|
CurrentKey = Node->FirstKey;
|
|
|
|
for (i = 0; i < Node->KeyCount; i++)
|
2017-06-28 03:45:52 +00:00
|
|
|
{
|
|
|
|
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;
|
2017-06-29 02:36:00 +00:00
|
|
|
PB_TREE_KEY NewKey, CurrentKey, PreviousKey;
|
2017-07-05 16:29:16 +00:00
|
|
|
ULONG i;
|
2017-06-28 03:45:52 +00:00
|
|
|
|
|
|
|
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
|
2017-06-29 02:36:00 +00:00
|
|
|
NewKey = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_KEY), TAG_NTFS);
|
2017-07-04 21:47:43 +00:00
|
|
|
if (!NewKey)
|
|
|
|
{
|
|
|
|
DPRINT1("ERROR: Failed to allocate memory for new key!\n");
|
|
|
|
ExFreePoolWithTag(NewEntry, TAG_NTFS);
|
|
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
}
|
2017-06-28 03:45:52 +00:00
|
|
|
NewKey->IndexEntry = NewEntry;
|
|
|
|
NewKey->NextKey = NULL;
|
|
|
|
|
|
|
|
// Find where to insert the key
|
2017-06-29 02:36:00 +00:00
|
|
|
CurrentKey = Node->FirstKey;
|
|
|
|
PreviousKey = NULL;
|
|
|
|
for (i = 0; i < Node->KeyCount; i++)
|
2017-06-28 03:45:52 +00:00
|
|
|
{
|
|
|
|
// Should the New Key go before the current key?
|
|
|
|
LONG Comparison = CompareTreeKeys(NewKey, CurrentKey, CaseSensitive);
|
2017-07-18 19:59:36 +00:00
|
|
|
|
|
|
|
ASSERT(Comparison != 0);
|
|
|
|
|
|
|
|
// Is NewKey < CurrentKey?
|
2017-06-28 03:45:52 +00:00
|
|
|
if (Comparison < 0)
|
|
|
|
{
|
|
|
|
// 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;
|
|
|
|
}
|