3 * Copyright (C) 2002, 2017 ReactOS Team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
19 * COPYRIGHT: See COPYING in the top level directory
20 * PROJECT: ReactOS kernel
21 * FILE: drivers/filesystem/ntfs/btree.c
22 * PURPOSE: NTFS filesystem driver
23 * PROGRAMMERS: Trevor Thompson
26 /* INCLUDES *****************************************************************/
33 /* FUNCTIONS ****************************************************************/
35 // TEMP FUNCTION for diagnostic purposes.
36 // Prints VCN of every node in an index allocation
38 PrintAllVCNs(PDEVICE_EXTENSION Vcb
,
39 PNTFS_ATTR_CONTEXT IndexAllocationContext
,
42 ULONGLONG CurrentOffset
= 0;
43 PINDEX_BUFFER CurrentNode
, Buffer
;
44 ULONGLONG BufferSize
= AttributeDataLength(IndexAllocationContext
->pRecord
);
51 DPRINT1("Index Allocation is empty.\n");
55 Buffer
= ExAllocatePoolWithTag(NonPagedPool
, BufferSize
, TAG_NTFS
);
57 BytesRead
= ReadAttribute(Vcb
, IndexAllocationContext
, 0, (PCHAR
)Buffer
, BufferSize
);
59 ASSERT(BytesRead
= BufferSize
);
63 // loop through all the nodes
64 for (i
= 0; i
< BufferSize
; i
+= NodeSize
)
66 NTSTATUS Status
= FixupUpdateSequenceArray(Vcb
, &CurrentNode
->Ntfs
);
67 if (!NT_SUCCESS(Status
))
69 DPRINT1("ERROR: Fixing fixup failed!\n");
73 DPRINT1("Node #%d, VCN: %I64u\n", Count
, CurrentNode
->VCN
);
75 CurrentNode
= (PINDEX_BUFFER
)((ULONG_PTR
)CurrentNode
+ NodeSize
);
76 CurrentOffset
+= NodeSize
;
80 ExFreePoolWithTag(Buffer
, TAG_NTFS
);
84 * @name AllocateIndexNode
87 * Allocates a new index record in an index allocation.
90 * Pointer to the target DEVICE_EXTENSION describing the volume the node will be created on.
93 * Pointer to a copy of the file record containing the index.
95 * @param IndexBufferSize
96 * Size of an index record for this index, in bytes. Commonly defined as 4096.
98 * @param IndexAllocationCtx
99 * Pointer to an NTFS_ATTR_CONTEXT describing the index allocation attribute the node will be assigned to.
101 * @param IndexAllocationOffset
102 * Offset of the index allocation attribute relative to the file record.
105 * Pointer to a ULONGLONG which will receive the VCN of the newly-assigned index record
108 * STATUS_SUCCESS in case of success.
109 * STATUS_NOT_IMPLEMENTED if there's no $I30 bitmap attribute in the file record.
112 * AllocateIndexNode() doesn't write any data to the index record it creates. Called by UpdateIndexNode().
113 * Don't call PrintAllVCNs() or NtfsDumpFileRecord() after calling AllocateIndexNode() before UpdateIndexNode() finishes.
114 * Possible TODO: Create an empty node and write it to the allocated index node, so the index allocation is always valid.
117 AllocateIndexNode(PDEVICE_EXTENSION DeviceExt
,
118 PFILE_RECORD_HEADER FileRecord
,
119 ULONG IndexBufferSize
,
120 PNTFS_ATTR_CONTEXT IndexAllocationCtx
,
121 ULONG IndexAllocationOffset
,
125 PNTFS_ATTR_CONTEXT BitmapCtx
;
126 ULONGLONG IndexAllocationLength
, BitmapLength
;
128 ULONGLONG NextNodeNumber
;
134 LARGE_INTEGER DataSize
;
136 DPRINT1("AllocateIndexNode(%p, %p, %lu, %p, %lu, %p) called.\n", DeviceExt
,
140 IndexAllocationOffset
,
143 // Get the length of the attribute allocation
144 IndexAllocationLength
= AttributeDataLength(IndexAllocationCtx
->pRecord
);
146 // Find the bitmap attribute for the index
147 Status
= FindAttribute(DeviceExt
,
154 if (!NT_SUCCESS(Status
))
156 DPRINT1("FIXME: Need to add bitmap attribute!\n");
157 return STATUS_NOT_IMPLEMENTED
;
160 // Get the length of the bitmap attribute
161 BitmapLength
= AttributeDataLength(BitmapCtx
->pRecord
);
163 NextNodeNumber
= IndexAllocationLength
/ DeviceExt
->NtfsInfo
.BytesPerIndexRecord
;
165 // TODO: Find unused allocation in bitmap and use that space first
167 // Add another bit to bitmap
169 // See how many bytes we need to store the amount of bits we'll have
170 BytesNeeded
= NextNodeNumber
/ 8;
173 // Windows seems to allocate the bitmap in 8-byte chunks to keep any bytes from being wasted on padding
174 BytesNeeded
= ALIGN_UP(BytesNeeded
, ATTR_RECORD_ALIGNMENT
);
176 // Allocate memory for the bitmap, including some padding; RtlInitializeBitmap() wants a pointer
177 // that's ULONG-aligned, and it wants the size of the memory allocated for it to be a ULONG-multiple.
178 BitmapMem
= ExAllocatePoolWithTag(NonPagedPool
, BytesNeeded
+ sizeof(ULONG
), TAG_NTFS
);
181 DPRINT1("Error: failed to allocate bitmap!");
182 ReleaseAttributeContext(BitmapCtx
);
183 return STATUS_INSUFFICIENT_RESOURCES
;
185 // RtlInitializeBitmap() wants a pointer that's ULONG-aligned.
186 BitmapPtr
= (PULONG
)ALIGN_UP_BY((ULONG_PTR
)BitmapMem
, sizeof(ULONG
));
188 RtlZeroMemory(BitmapPtr
, BytesNeeded
);
190 // Read the existing bitmap data
191 Status
= ReadAttribute(DeviceExt
, BitmapCtx
, 0, (PCHAR
)BitmapPtr
, BitmapLength
);
194 RtlInitializeBitMap(&Bitmap
, BitmapPtr
, NextNodeNumber
);
196 // Do we need to enlarge the bitmap?
197 if (BytesNeeded
> BitmapLength
)
199 // TODO: handle synchronization issues that could occur from changing the directory's file record
200 // Change bitmap size
201 DataSize
.QuadPart
= BytesNeeded
;
202 if (BitmapCtx
->pRecord
->IsNonResident
)
204 Status
= SetNonResidentAttributeDataLength(DeviceExt
,
212 Status
= SetResidentAttributeDataLength(DeviceExt
,
218 if (!NT_SUCCESS(Status
))
220 DPRINT1("ERROR: Failed to set length of bitmap attribute!\n");
221 ReleaseAttributeContext(BitmapCtx
);
226 // Enlarge Index Allocation attribute
227 DataSize
.QuadPart
= IndexAllocationLength
+ IndexBufferSize
;
228 Status
= SetNonResidentAttributeDataLength(DeviceExt
,
230 IndexAllocationOffset
,
233 if (!NT_SUCCESS(Status
))
235 DPRINT1("ERROR: Failed to set length of index allocation!\n");
236 ReleaseAttributeContext(BitmapCtx
);
240 // Update file record on disk
241 Status
= UpdateFileRecord(DeviceExt
, IndexAllocationCtx
->FileMFTIndex
, FileRecord
);
242 if (!NT_SUCCESS(Status
))
244 DPRINT1("ERROR: Failed to update file record!\n");
245 ReleaseAttributeContext(BitmapCtx
);
249 // Set the bit for the new index record
250 RtlSetBits(&Bitmap
, NextNodeNumber
, 1);
252 // Write the new bitmap attribute
253 Status
= WriteAttribute(DeviceExt
,
256 (const PUCHAR
)BitmapPtr
,
260 if (!NT_SUCCESS(Status
))
262 DPRINT1("ERROR: Unable to write to $I30 bitmap attribute!\n");
265 // Calculate VCN of new node number
266 *NewVCN
= NextNodeNumber
* (IndexBufferSize
/ DeviceExt
->NtfsInfo
.BytesPerCluster
);
268 DPRINT("New VCN: %I64u\n", *NewVCN
);
270 ExFreePoolWithTag(BitmapMem
, TAG_NTFS
);
271 ReleaseAttributeContext(BitmapCtx
);
277 * @name CreateDummyKey
280 * Creates the final B_TREE_KEY for a B_TREE_FILENAME_NODE. Also creates the associated index entry.
282 * @param HasChildNode
283 * BOOLEAN to indicate if this key will have a LesserChild.
286 * The newly-created key.
289 CreateDummyKey(BOOLEAN HasChildNode
)
291 PINDEX_ENTRY_ATTRIBUTE NewIndexEntry
;
292 PB_TREE_KEY NewDummyKey
;
294 // Calculate max size of a dummy key
295 ULONG EntrySize
= ALIGN_UP_BY(FIELD_OFFSET(INDEX_ENTRY_ATTRIBUTE
, FileName
), 8);
296 EntrySize
+= sizeof(ULONGLONG
); // for VCN
298 // Create the index entry for the key
299 NewIndexEntry
= ExAllocatePoolWithTag(NonPagedPool
, EntrySize
, TAG_NTFS
);
302 DPRINT1("Couldn't allocate memory for dummy key index entry!\n");
306 RtlZeroMemory(NewIndexEntry
, EntrySize
);
310 NewIndexEntry
->Flags
= NTFS_INDEX_ENTRY_NODE
| NTFS_INDEX_ENTRY_END
;
314 NewIndexEntry
->Flags
= NTFS_INDEX_ENTRY_END
;
315 EntrySize
-= sizeof(ULONGLONG
); // no VCN
318 NewIndexEntry
->Length
= EntrySize
;
321 NewDummyKey
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE_KEY
), TAG_NTFS
);
324 DPRINT1("Unable to allocate dummy key!\n");
325 ExFreePoolWithTag(NewIndexEntry
, TAG_NTFS
);
328 RtlZeroMemory(NewDummyKey
, sizeof(B_TREE_KEY
));
330 NewDummyKey
->IndexEntry
= NewIndexEntry
;
336 * @name CreateEmptyBTree
339 * Creates an empty B-Tree, which will contain a single root node which will contain a single dummy key.
342 * Pointer to a PB_TREE that will receive the pointer of the newly-created B-Tree.
345 * STATUS_SUCCESS on success. STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
348 CreateEmptyBTree(PB_TREE
*NewTree
)
350 PB_TREE Tree
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE
), TAG_NTFS
);
351 PB_TREE_FILENAME_NODE RootNode
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE_FILENAME_NODE
), TAG_NTFS
);
352 PB_TREE_KEY DummyKey
;
354 DPRINT1("CreateEmptyBTree(%p) called\n", NewTree
);
356 if (!Tree
|| !RootNode
)
358 DPRINT1("Couldn't allocate enough memory for B-Tree!\n");
360 ExFreePoolWithTag(Tree
, TAG_NTFS
);
362 ExFreePoolWithTag(RootNode
, TAG_NTFS
);
363 return STATUS_INSUFFICIENT_RESOURCES
;
366 // Create the dummy key
367 DummyKey
= CreateDummyKey(FALSE
);
370 DPRINT1("ERROR: Failed to create dummy key!\n");
371 ExFreePoolWithTag(Tree
, TAG_NTFS
);
372 ExFreePoolWithTag(RootNode
, TAG_NTFS
);
373 return STATUS_INSUFFICIENT_RESOURCES
;
376 RtlZeroMemory(Tree
, sizeof(B_TREE
));
377 RtlZeroMemory(RootNode
, sizeof(B_TREE_FILENAME_NODE
));
380 RootNode
->FirstKey
= DummyKey
;
381 RootNode
->KeyCount
= 1;
382 RootNode
->DiskNeedsUpdating
= TRUE
;
383 Tree
->RootNode
= RootNode
;
387 // Memory will be freed when DestroyBTree() is called
389 return STATUS_SUCCESS
;
393 * @name CompareTreeKeys
396 * Compare two B_TREE_KEY's to determine their order in the tree.
399 * Pointer to a B_TREE_KEY that will be compared.
402 * Pointer to the other B_TREE_KEY that will be compared.
404 * @param CaseSensitive
405 * Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
406 * if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag.
409 * 0 if the two keys are equal.
410 * < 0 if key1 is less thank key2
411 * > 0 if key1 is greater than key2
414 * Any other key is always less than the final (dummy) key in a node. Key1 must not be the dummy node.
417 CompareTreeKeys(PB_TREE_KEY Key1
, PB_TREE_KEY Key2
, BOOLEAN CaseSensitive
)
419 UNICODE_STRING Key1Name
, Key2Name
;
422 // Key1 must not be the final key (AKA the dummy key)
423 ASSERT(!(Key1
->IndexEntry
->Flags
& NTFS_INDEX_ENTRY_END
));
425 // If Key2 is the "dummy key", key 1 will always come first
426 if (Key2
->NextKey
== NULL
)
429 Key1Name
.Buffer
= Key1
->IndexEntry
->FileName
.Name
;
430 Key1Name
.Length
= Key1Name
.MaximumLength
431 = Key1
->IndexEntry
->FileName
.NameLength
* sizeof(WCHAR
);
433 Key2Name
.Buffer
= Key2
->IndexEntry
->FileName
.Name
;
434 Key2Name
.Length
= Key2Name
.MaximumLength
435 = Key2
->IndexEntry
->FileName
.NameLength
* sizeof(WCHAR
);
437 // Are the two keys the same length?
438 if (Key1Name
.Length
== Key2Name
.Length
)
439 return RtlCompareUnicodeString(&Key1Name
, &Key2Name
, !CaseSensitive
);
442 if (Key1Name
.Length
< Key2Name
.Length
)
444 // Truncate KeyName2 to be the same length as KeyName1
445 Key2Name
.Length
= Key1Name
.Length
;
447 // Compare the names of the same length
448 Comparison
= RtlCompareUnicodeString(&Key1Name
, &Key2Name
, !CaseSensitive
);
450 // If the truncated names are the same length, the shorter one comes first
457 // Truncate KeyName1 to be the same length as KeyName2
458 Key1Name
.Length
= Key2Name
.Length
;
460 // Compare the names of the same length
461 Comparison
= RtlCompareUnicodeString(&Key1Name
, &Key2Name
, !CaseSensitive
);
463 // If the truncated names are the same length, the shorter one comes first
472 * @name CountBTreeKeys
475 * Counts the number of linked B-Tree keys, starting with FirstKey.
478 * Pointer to a B_TREE_KEY that will be the first key to be counted.
481 * The number of keys in a linked-list, including FirstKey and the final dummy key.
484 CountBTreeKeys(PB_TREE_KEY FirstKey
)
487 PB_TREE_KEY Current
= FirstKey
;
489 while (Current
!= NULL
)
492 Current
= Current
->NextKey
;
498 PB_TREE_FILENAME_NODE
499 CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb
,
500 PINDEX_ROOT_ATTRIBUTE IndexRoot
,
501 PNTFS_ATTR_CONTEXT IndexAllocationAttributeCtx
,
502 PINDEX_ENTRY_ATTRIBUTE NodeEntry
)
504 PB_TREE_FILENAME_NODE NewNode
;
505 PINDEX_ENTRY_ATTRIBUTE CurrentNodeEntry
;
506 PINDEX_ENTRY_ATTRIBUTE FirstNodeEntry
;
507 ULONG CurrentEntryOffset
= 0;
508 PINDEX_BUFFER NodeBuffer
;
509 ULONG IndexBufferSize
= Vcb
->NtfsInfo
.BytesPerIndexRecord
;
511 PB_TREE_KEY CurrentKey
;
513 ULONGLONG IndexNodeOffset
;
516 if (IndexAllocationAttributeCtx
== NULL
)
518 DPRINT1("ERROR: Couldn't find index allocation attribute even though there should be one!\n");
522 // Get the node number from the end of the node entry
523 VCN
= (PULONGLONG
)((ULONG_PTR
)NodeEntry
+ NodeEntry
->Length
- sizeof(ULONGLONG
));
525 // Create the new tree node
526 NewNode
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE_FILENAME_NODE
), TAG_NTFS
);
529 DPRINT1("ERROR: Couldn't allocate memory for new filename node.\n");
532 RtlZeroMemory(NewNode
, sizeof(B_TREE_FILENAME_NODE
));
534 // Create the first key
535 CurrentKey
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE_KEY
), TAG_NTFS
);
538 DPRINT1("ERROR: Failed to allocate memory for key!\n");
539 ExFreePoolWithTag(NewNode
, TAG_NTFS
);
542 RtlZeroMemory(CurrentKey
, sizeof(B_TREE_KEY
));
543 NewNode
->FirstKey
= CurrentKey
;
545 // Allocate memory for the node buffer
546 NodeBuffer
= ExAllocatePoolWithTag(NonPagedPool
, IndexBufferSize
, TAG_NTFS
);
549 DPRINT1("ERROR: Couldn't allocate memory for node buffer!\n");
550 ExFreePoolWithTag(CurrentKey
, TAG_NTFS
);
551 ExFreePoolWithTag(NewNode
, TAG_NTFS
);
555 // Calculate offset into index allocation
556 IndexNodeOffset
= GetAllocationOffsetFromVCN(Vcb
, IndexBufferSize
, *VCN
);
558 // TODO: Confirm index bitmap has this node marked as in-use
561 BytesRead
= ReadAttribute(Vcb
,
562 IndexAllocationAttributeCtx
,
567 ASSERT(BytesRead
== IndexBufferSize
);
568 NT_ASSERT(NodeBuffer
->Ntfs
.Type
== NRH_INDX_TYPE
);
569 NT_ASSERT(NodeBuffer
->VCN
== *VCN
);
571 // Apply the fixup array to the node buffer
572 Status
= FixupUpdateSequenceArray(Vcb
, &NodeBuffer
->Ntfs
);
573 if (!NT_SUCCESS(Status
))
575 DPRINT1("ERROR: Couldn't apply fixup array to index node buffer!\n");
576 ExFreePoolWithTag(NodeBuffer
, TAG_NTFS
);
577 ExFreePoolWithTag(CurrentKey
, TAG_NTFS
);
578 ExFreePoolWithTag(NewNode
, TAG_NTFS
);
582 // Walk through the index and create keys for all the entries
583 FirstNodeEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)(&NodeBuffer
->Header
)
584 + NodeBuffer
->Header
.FirstEntryOffset
);
585 CurrentNodeEntry
= FirstNodeEntry
;
586 while (CurrentEntryOffset
< NodeBuffer
->Header
.TotalSizeOfEntries
)
588 // Allocate memory for the current entry
589 CurrentKey
->IndexEntry
= ExAllocatePoolWithTag(NonPagedPool
, CurrentNodeEntry
->Length
, TAG_NTFS
);
590 if (!CurrentKey
->IndexEntry
)
592 DPRINT1("ERROR: Couldn't allocate memory for next key!\n");
593 DestroyBTreeNode(NewNode
);
594 ExFreePoolWithTag(NodeBuffer
, TAG_NTFS
);
600 // If this isn't the last entry
601 if (!(CurrentNodeEntry
->Flags
& NTFS_INDEX_ENTRY_END
))
603 // Create the next key
604 PB_TREE_KEY NextKey
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE_KEY
), TAG_NTFS
);
607 DPRINT1("ERROR: Couldn't allocate memory for next key!\n");
608 DestroyBTreeNode(NewNode
);
609 ExFreePoolWithTag(NodeBuffer
, TAG_NTFS
);
612 RtlZeroMemory(NextKey
, sizeof(B_TREE_KEY
));
614 // Add NextKey to the end of the list
615 CurrentKey
->NextKey
= NextKey
;
617 // Copy the current entry to its key
618 RtlCopyMemory(CurrentKey
->IndexEntry
, CurrentNodeEntry
, CurrentNodeEntry
->Length
);
620 // See if the current key has a sub-node
621 if (CurrentKey
->IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
)
623 CurrentKey
->LesserChild
= CreateBTreeNodeFromIndexNode(Vcb
,
625 IndexAllocationAttributeCtx
,
626 CurrentKey
->IndexEntry
);
629 CurrentKey
= NextKey
;
633 // Copy the final entry to its key
634 RtlCopyMemory(CurrentKey
->IndexEntry
, CurrentNodeEntry
, CurrentNodeEntry
->Length
);
635 CurrentKey
->NextKey
= NULL
;
637 // See if the current key has a sub-node
638 if (CurrentKey
->IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
)
640 CurrentKey
->LesserChild
= CreateBTreeNodeFromIndexNode(Vcb
,
642 IndexAllocationAttributeCtx
,
643 CurrentKey
->IndexEntry
);
649 // Advance to the next entry
650 CurrentEntryOffset
+= CurrentNodeEntry
->Length
;
651 CurrentNodeEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)CurrentNodeEntry
+ CurrentNodeEntry
->Length
);
655 NewNode
->HasValidVCN
= TRUE
;
657 ExFreePoolWithTag(NodeBuffer
, TAG_NTFS
);
663 * @name CreateBTreeFromIndex
666 * Parse an index and create a B-Tree in memory from it.
668 * @param IndexRootContext
669 * Pointer to an NTFS_ATTR_CONTEXT that describes the location of the index root attribute.
672 * Pointer to a PB_TREE that will receive the pointer to a newly-created B-Tree.
675 * STATUS_SUCCESS on success.
676 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
679 * Allocates memory for the entire tree. Caller is responsible for destroying the tree with DestroyBTree().
682 CreateBTreeFromIndex(PDEVICE_EXTENSION Vcb
,
683 PFILE_RECORD_HEADER FileRecordWithIndex
,
684 /*PCWSTR IndexName,*/
685 PNTFS_ATTR_CONTEXT IndexRootContext
,
686 PINDEX_ROOT_ATTRIBUTE IndexRoot
,
689 PINDEX_ENTRY_ATTRIBUTE CurrentNodeEntry
;
690 PB_TREE Tree
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE
), TAG_NTFS
);
691 PB_TREE_FILENAME_NODE RootNode
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE_FILENAME_NODE
), TAG_NTFS
);
692 PB_TREE_KEY CurrentKey
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE_KEY
), TAG_NTFS
);
693 ULONG CurrentOffset
= IndexRoot
->Header
.FirstEntryOffset
;
694 PNTFS_ATTR_CONTEXT IndexAllocationContext
= NULL
;
697 DPRINT1("CreateBTreeFromIndex(%p, %p)\n", IndexRoot
, NewTree
);
699 if (!Tree
|| !RootNode
|| !CurrentKey
)
701 DPRINT1("Couldn't allocate enough memory for B-Tree!\n");
703 ExFreePoolWithTag(Tree
, TAG_NTFS
);
705 ExFreePoolWithTag(CurrentKey
, TAG_NTFS
);
707 ExFreePoolWithTag(RootNode
, TAG_NTFS
);
708 return STATUS_INSUFFICIENT_RESOURCES
;
711 RtlZeroMemory(Tree
, sizeof(B_TREE
));
712 RtlZeroMemory(RootNode
, sizeof(B_TREE_FILENAME_NODE
));
713 RtlZeroMemory(CurrentKey
, sizeof(B_TREE_KEY
));
715 // See if the file record has an attribute allocation
716 Status
= FindAttribute(Vcb
,
718 AttributeIndexAllocation
,
721 &IndexAllocationContext
,
723 if (!NT_SUCCESS(Status
))
724 IndexAllocationContext
= NULL
;
727 RootNode
->FirstKey
= CurrentKey
;
728 Tree
->RootNode
= RootNode
;
730 // Make sure we won't try reading past the attribute-end
731 if (FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE
, Header
) + IndexRoot
->Header
.TotalSizeOfEntries
> IndexRootContext
->pRecord
->Resident
.ValueLength
)
733 DPRINT1("Filesystem corruption detected!\n");
735 return STATUS_FILE_CORRUPT_ERROR
;
738 // Start at the first node entry
739 CurrentNodeEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)IndexRoot
740 + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE
, Header
)
741 + IndexRoot
->Header
.FirstEntryOffset
);
743 // Create a key for each entry in the node
744 while (CurrentOffset
< IndexRoot
->Header
.TotalSizeOfEntries
)
746 // Allocate memory for the current entry
747 CurrentKey
->IndexEntry
= ExAllocatePoolWithTag(NonPagedPool
, CurrentNodeEntry
->Length
, TAG_NTFS
);
748 if (!CurrentKey
->IndexEntry
)
750 DPRINT1("ERROR: Couldn't allocate memory for next key!\n");
752 return STATUS_INSUFFICIENT_RESOURCES
;
755 RootNode
->KeyCount
++;
757 // If this isn't the last entry
758 if (!(CurrentNodeEntry
->Flags
& NTFS_INDEX_ENTRY_END
))
760 // Create the next key
761 PB_TREE_KEY NextKey
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE_KEY
), TAG_NTFS
);
764 DPRINT1("ERROR: Couldn't allocate memory for next key!\n");
766 return STATUS_INSUFFICIENT_RESOURCES
;
769 RtlZeroMemory(NextKey
, sizeof(B_TREE_KEY
));
771 // Add NextKey to the end of the list
772 CurrentKey
->NextKey
= NextKey
;
774 // Copy the current entry to its key
775 RtlCopyMemory(CurrentKey
->IndexEntry
, CurrentNodeEntry
, CurrentNodeEntry
->Length
);
777 // Does this key have a sub-node?
778 if (CurrentKey
->IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
)
780 // Create the child node
781 CurrentKey
->LesserChild
= CreateBTreeNodeFromIndexNode(Vcb
,
783 IndexAllocationContext
,
784 CurrentKey
->IndexEntry
);
785 if (!CurrentKey
->LesserChild
)
787 DPRINT1("ERROR: Couldn't create child node!\n");
789 return STATUS_NOT_IMPLEMENTED
;
793 // Advance to the next entry
794 CurrentOffset
+= CurrentNodeEntry
->Length
;
795 CurrentNodeEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)CurrentNodeEntry
+ CurrentNodeEntry
->Length
);
796 CurrentKey
= NextKey
;
800 // Copy the final entry to its key
801 RtlCopyMemory(CurrentKey
->IndexEntry
, CurrentNodeEntry
, CurrentNodeEntry
->Length
);
802 CurrentKey
->NextKey
= NULL
;
804 // Does this key have a sub-node?
805 if (CurrentKey
->IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
)
807 // Create the child node
808 CurrentKey
->LesserChild
= CreateBTreeNodeFromIndexNode(Vcb
,
810 IndexAllocationContext
,
811 CurrentKey
->IndexEntry
);
812 if (!CurrentKey
->LesserChild
)
814 DPRINT1("ERROR: Couldn't create child node!\n");
816 return STATUS_NOT_IMPLEMENTED
;
826 if (IndexAllocationContext
)
827 ReleaseAttributeContext(IndexAllocationContext
);
829 return STATUS_SUCCESS
;
833 * @name GetSizeOfIndexEntries
836 * Sums the size of each index entry in every key in a B-Tree node.
839 * Pointer to a B_TREE_FILENAME_NODE. The size of this node's index entries will be returned.
842 * The sum of the sizes of every index entry for each key in the B-Tree node.
845 * Gets only the size of the index entries; doesn't include the size of any headers that would be added to an index record.
848 GetSizeOfIndexEntries(PB_TREE_FILENAME_NODE Node
)
850 // Start summing the total size of this node's entries
853 // Walk through the list of Node Entries
854 PB_TREE_KEY CurrentKey
= Node
->FirstKey
;
856 for (i
= 0; i
< Node
->KeyCount
; i
++)
858 ASSERT(CurrentKey
->IndexEntry
->Length
!= 0);
860 // Add the length of the current node
861 NodeSize
+= CurrentKey
->IndexEntry
->Length
;
862 CurrentKey
= CurrentKey
->NextKey
;
869 * @name CreateIndexRootFromBTree
872 * Parse a B-Tree in memory and convert it into an index that can be written to disk.
875 * Pointer to the DEVICE_EXTENSION of the target drive.
878 * Pointer to a B_TREE that describes the index to be written.
880 * @param MaxIndexSize
881 * Describes how large the index can be before it will take too much space in the file record.
882 * This is strictly the sum of the sizes of all index entries; it does not include the space
883 * required by the index root header (INDEX_ROOT_ATTRIBUTE), since that size will be constant.
885 * After reaching MaxIndexSize, an index can no longer be represented with just an index root
886 * attribute, and will require an index allocation and $I30 bitmap (TODO).
889 * Pointer to a PINDEX_ROOT_ATTRIBUTE that will receive a pointer to the newly-created index.
892 * Pointer to a ULONG which will receive the length of the new index root.
895 * STATUS_SUCCESS on success.
896 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
897 * STATUS_NOT_IMPLEMENTED if the new index can't fit within MaxIndexSize.
900 * If the function succeeds, it's the caller's responsibility to free IndexRoot with ExFreePoolWithTag().
903 CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt
,
906 PINDEX_ROOT_ATTRIBUTE
*IndexRoot
,
910 PB_TREE_KEY CurrentKey
;
911 PINDEX_ENTRY_ATTRIBUTE CurrentNodeEntry
;
912 PINDEX_ROOT_ATTRIBUTE NewIndexRoot
= ExAllocatePoolWithTag(NonPagedPool
,
913 DeviceExt
->NtfsInfo
.BytesPerFileRecord
,
916 DPRINT1("CreateIndexRootFromBTree(%p, %p, 0x%lx, %p, %p)\n", DeviceExt
, Tree
, MaxIndexSize
, IndexRoot
, Length
);
924 DPRINT1("Failed to allocate memory for Index Root!\n");
925 return STATUS_INSUFFICIENT_RESOURCES
;
928 // Setup the new index root
929 RtlZeroMemory(NewIndexRoot
, DeviceExt
->NtfsInfo
.BytesPerFileRecord
);
931 NewIndexRoot
->AttributeType
= AttributeFileName
;
932 NewIndexRoot
->CollationRule
= COLLATION_FILE_NAME
;
933 NewIndexRoot
->SizeOfEntry
= DeviceExt
->NtfsInfo
.BytesPerIndexRecord
;
934 // If Bytes per index record is less than cluster size, clusters per index record becomes sectors per index
935 if (NewIndexRoot
->SizeOfEntry
< DeviceExt
->NtfsInfo
.BytesPerCluster
)
936 NewIndexRoot
->ClustersPerIndexRecord
= NewIndexRoot
->SizeOfEntry
/ DeviceExt
->NtfsInfo
.BytesPerSector
;
938 NewIndexRoot
->ClustersPerIndexRecord
= NewIndexRoot
->SizeOfEntry
/ DeviceExt
->NtfsInfo
.BytesPerCluster
;
940 // Setup the Index node header
941 NewIndexRoot
->Header
.FirstEntryOffset
= sizeof(INDEX_HEADER_ATTRIBUTE
);
942 NewIndexRoot
->Header
.Flags
= INDEX_ROOT_SMALL
;
944 // Start summing the total size of this node's entries
945 NewIndexRoot
->Header
.TotalSizeOfEntries
= NewIndexRoot
->Header
.FirstEntryOffset
;
947 // Setup each Node Entry
948 CurrentKey
= Tree
->RootNode
->FirstKey
;
949 CurrentNodeEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)NewIndexRoot
950 + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE
, Header
)
951 + NewIndexRoot
->Header
.FirstEntryOffset
);
952 for (i
= 0; i
< Tree
->RootNode
->KeyCount
; i
++)
954 // Would adding the current entry to the index increase the index size beyond the limit we've set?
955 ULONG IndexSize
= NewIndexRoot
->Header
.TotalSizeOfEntries
- NewIndexRoot
->Header
.FirstEntryOffset
+ CurrentKey
->IndexEntry
->Length
;
956 if (IndexSize
> MaxIndexSize
)
958 DPRINT1("TODO: Adding file would require creating an attribute list!\n");
959 ExFreePoolWithTag(NewIndexRoot
, TAG_NTFS
);
960 return STATUS_NOT_IMPLEMENTED
;
963 ASSERT(CurrentKey
->IndexEntry
->Length
!= 0);
965 // Copy the index entry
966 RtlCopyMemory(CurrentNodeEntry
, CurrentKey
->IndexEntry
, CurrentKey
->IndexEntry
->Length
);
968 DPRINT1("Index Node Entry Stream Length: %u\nIndex Node Entry Length: %u\n",
969 CurrentNodeEntry
->KeyLength
,
970 CurrentNodeEntry
->Length
);
972 // Does the current key have any sub-nodes?
973 if (CurrentKey
->LesserChild
)
974 NewIndexRoot
->Header
.Flags
= INDEX_ROOT_LARGE
;
976 // Add Length of Current Entry to Total Size of Entries
977 NewIndexRoot
->Header
.TotalSizeOfEntries
+= CurrentKey
->IndexEntry
->Length
;
979 // Go to the next node entry
980 CurrentNodeEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)CurrentNodeEntry
+ CurrentNodeEntry
->Length
);
982 CurrentKey
= CurrentKey
->NextKey
;
985 NewIndexRoot
->Header
.AllocatedSize
= NewIndexRoot
->Header
.TotalSizeOfEntries
;
987 *IndexRoot
= NewIndexRoot
;
988 *Length
= NewIndexRoot
->Header
.AllocatedSize
+ FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE
, Header
);
990 return STATUS_SUCCESS
;
994 CreateIndexBufferFromBTreeNode(PDEVICE_EXTENSION DeviceExt
,
995 PB_TREE_FILENAME_NODE Node
,
998 PINDEX_BUFFER IndexBuffer
)
1001 PB_TREE_KEY CurrentKey
;
1002 PINDEX_ENTRY_ATTRIBUTE CurrentNodeEntry
;
1005 // TODO: Fix magic, do math
1006 RtlZeroMemory(IndexBuffer
, BufferSize
);
1007 IndexBuffer
->Ntfs
.Type
= NRH_INDX_TYPE
;
1008 IndexBuffer
->Ntfs
.UsaOffset
= 0x28;
1009 IndexBuffer
->Ntfs
.UsaCount
= 9;
1011 // TODO: Check bitmap for VCN
1012 ASSERT(Node
->HasValidVCN
);
1013 IndexBuffer
->VCN
= Node
->VCN
;
1015 // Windows seems to alternate between using 0x28 and 0x40 for the first entry offset of each index buffer.
1016 // Interestingly, neither Windows nor chkdsk seem to mind if we just use 0x28 for every index record.
1017 IndexBuffer
->Header
.FirstEntryOffset
= 0x28;
1018 IndexBuffer
->Header
.AllocatedSize
= BufferSize
- FIELD_OFFSET(INDEX_BUFFER
, Header
);
1020 // Start summing the total size of this node's entries
1021 IndexBuffer
->Header
.TotalSizeOfEntries
= IndexBuffer
->Header
.FirstEntryOffset
;
1023 CurrentKey
= Node
->FirstKey
;
1024 CurrentNodeEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&(IndexBuffer
->Header
)
1025 + IndexBuffer
->Header
.FirstEntryOffset
);
1026 for (i
= 0; i
< Node
->KeyCount
; i
++)
1028 // Would adding the current entry to the index increase the node size beyond the allocation size?
1029 ULONG IndexSize
= FIELD_OFFSET(INDEX_BUFFER
, Header
)
1030 + IndexBuffer
->Header
.TotalSizeOfEntries
1031 + CurrentNodeEntry
->Length
;
1032 if (IndexSize
> BufferSize
)
1034 DPRINT1("TODO: Adding file would require creating a new node!\n");
1035 return STATUS_NOT_IMPLEMENTED
;
1038 ASSERT(CurrentKey
->IndexEntry
->Length
!= 0);
1040 // Copy the index entry
1041 RtlCopyMemory(CurrentNodeEntry
, CurrentKey
->IndexEntry
, CurrentKey
->IndexEntry
->Length
);
1043 DPRINT("Index Node Entry Stream Length: %u\nIndex Node Entry Length: %u\n",
1044 CurrentNodeEntry
->KeyLength
,
1045 CurrentNodeEntry
->Length
);
1047 // Add Length of Current Entry to Total Size of Entries
1048 IndexBuffer
->Header
.TotalSizeOfEntries
+= CurrentNodeEntry
->Length
;
1050 // Check for child nodes
1052 IndexBuffer
->Header
.Flags
= INDEX_NODE_LARGE
;
1054 // Go to the next node entry
1055 CurrentNodeEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)CurrentNodeEntry
+ CurrentNodeEntry
->Length
);
1056 CurrentKey
= CurrentKey
->NextKey
;
1059 Status
= AddFixupArray(DeviceExt
, &IndexBuffer
->Ntfs
);
1065 * @name DemoteBTreeRoot
1068 * Demoting the root means first putting all the keys in the root node into a new node, and making
1069 * the new node a child of a dummy key. The dummy key then becomes the sole contents of the root node.
1070 * The B-Tree gets one level deeper. This operation is needed when an index root grows too large for its file record.
1071 * Demotion is my own term; I might change the name later if I think of something more descriptive or can find
1072 * an appropriate name for this operation in existing B-Tree literature.
1075 * Pointer to the B_TREE whose root is being demoted
1078 * STATUS_SUCCESS on success.
1079 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
1082 DemoteBTreeRoot(PB_TREE Tree
)
1084 PB_TREE_FILENAME_NODE NewSubNode
, NewIndexRoot
;
1085 PB_TREE_KEY DummyKey
;
1087 DPRINT1("Collapsing Index Root into sub-node.\n");
1093 // Create a new node that will hold the keys currently in index root
1094 NewSubNode
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE_FILENAME_NODE
), TAG_NTFS
);
1097 DPRINT1("ERROR: Couldn't allocate memory for new sub-node.\n");
1098 return STATUS_INSUFFICIENT_RESOURCES
;
1100 RtlZeroMemory(NewSubNode
, sizeof(B_TREE_FILENAME_NODE
));
1102 // Copy the applicable data from the old index root node
1103 NewSubNode
->KeyCount
= Tree
->RootNode
->KeyCount
;
1104 NewSubNode
->FirstKey
= Tree
->RootNode
->FirstKey
;
1105 NewSubNode
->DiskNeedsUpdating
= TRUE
;
1107 // Create a new dummy key, and make the new node it's child
1108 DummyKey
= CreateDummyKey(TRUE
);
1111 DPRINT1("ERROR: Couldn't allocate memory for new root node.\n");
1112 ExFreePoolWithTag(NewSubNode
, TAG_NTFS
);
1113 return STATUS_INSUFFICIENT_RESOURCES
;
1116 // Make the new node a child of the dummy key
1117 DummyKey
->LesserChild
= NewSubNode
;
1119 // Create a new index root node
1120 NewIndexRoot
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE_FILENAME_NODE
), TAG_NTFS
);
1123 DPRINT1("ERROR: Couldn't allocate memory for new index root.\n");
1124 ExFreePoolWithTag(NewSubNode
, TAG_NTFS
);
1125 ExFreePoolWithTag(DummyKey
, TAG_NTFS
);
1126 return STATUS_INSUFFICIENT_RESOURCES
;
1128 RtlZeroMemory(NewIndexRoot
, sizeof(B_TREE_FILENAME_NODE
));
1130 NewIndexRoot
->DiskNeedsUpdating
= TRUE
;
1132 // Insert the dummy key into the new node
1133 NewIndexRoot
->FirstKey
= DummyKey
;
1134 NewIndexRoot
->KeyCount
= 1;
1135 NewIndexRoot
->DiskNeedsUpdating
= TRUE
;
1137 // Make the new node the Tree's root node
1138 Tree
->RootNode
= NewIndexRoot
;
1144 return STATUS_SUCCESS
;
1148 * @name SetIndexEntryVCN
1151 * Sets the VCN of a given IndexEntry.
1154 * Pointer to an INDEX_ENTRY_ATTRIBUTE structure that will have its VCN set.
1157 * VCN to store in the index entry.
1160 * The index entry must have enough memory allocated to store the VCN, and must have the NTFS_INDEX_ENTRY_NODE flag set.
1161 * The VCN of an index entry is stored at the very end of the structure, after the filename attribute. Since the filename
1162 * attribute can be a variable size, this function makes setting this member easy.
1165 SetIndexEntryVCN(PINDEX_ENTRY_ATTRIBUTE IndexEntry
, ULONGLONG VCN
)
1167 PULONGLONG Destination
= (PULONGLONG
)((ULONG_PTR
)IndexEntry
+ IndexEntry
->Length
- sizeof(ULONGLONG
));
1169 ASSERT(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
);
1175 UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt
,
1177 ULONG IndexBufferSize
,
1178 PFILE_RECORD_HEADER FileRecord
)
1180 // Find the index allocation and bitmap
1181 PNTFS_ATTR_CONTEXT IndexAllocationContext
;
1182 PB_TREE_KEY CurrentKey
;
1184 BOOLEAN HasIndexAllocation
= FALSE
;
1186 ULONG IndexAllocationOffset
;
1188 DPRINT1("UpdateIndexAllocation() called.\n");
1190 Status
= FindAttribute(DeviceExt
, FileRecord
, AttributeIndexAllocation
, L
"$I30", 4, &IndexAllocationContext
, &IndexAllocationOffset
);
1191 if (NT_SUCCESS(Status
))
1193 HasIndexAllocation
= TRUE
;
1196 PrintAllVCNs(DeviceExt
,
1197 IndexAllocationContext
,
1201 // Walk through the root node and update all the sub-nodes
1202 CurrentKey
= Tree
->RootNode
->FirstKey
;
1203 for (i
= 0; i
< Tree
->RootNode
->KeyCount
; i
++)
1205 if (CurrentKey
->LesserChild
)
1207 if (!HasIndexAllocation
)
1209 // We need to add an index allocation to the file record
1210 PNTFS_ATTR_RECORD EndMarker
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ FileRecord
->BytesInUse
- (sizeof(ULONG
) * 2));
1211 DPRINT1("Adding index allocation...\n");
1213 // Add index allocation to the very end of the file record
1214 Status
= AddIndexAllocation(DeviceExt
,
1219 if (!NT_SUCCESS(Status
))
1221 DPRINT1("ERROR: Failed to add index allocation!\n");
1225 // Find the new attribute
1226 Status
= FindAttribute(DeviceExt
, FileRecord
, AttributeIndexAllocation
, L
"$I30", 4, &IndexAllocationContext
, &IndexAllocationOffset
);
1227 if (!NT_SUCCESS(Status
))
1229 DPRINT1("ERROR: Couldn't find newly-created index allocation!\n");
1233 // Advance end marker
1234 EndMarker
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)EndMarker
+ EndMarker
->Length
);
1236 // Add index bitmap to the very end of the file record
1237 Status
= AddBitmap(DeviceExt
,
1242 if (!NT_SUCCESS(Status
))
1244 DPRINT1("ERROR: Failed to add index bitmap!\n");
1248 HasIndexAllocation
= TRUE
;
1251 // Is the Index Entry large enough to store the VCN?
1252 if (!CurrentKey
->IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
)
1254 // Allocate memory for the larger index entry
1255 PINDEX_ENTRY_ATTRIBUTE NewEntry
= ExAllocatePoolWithTag(NonPagedPool
,
1256 CurrentKey
->IndexEntry
->Length
+ sizeof(ULONGLONG
),
1260 DPRINT1("ERROR: Unable to allocate memory for new index entry!\n");
1261 if (HasIndexAllocation
)
1262 ReleaseAttributeContext(IndexAllocationContext
);
1263 return STATUS_INSUFFICIENT_RESOURCES
;
1266 // Copy the old entry to the new one
1267 RtlCopyMemory(NewEntry
, CurrentKey
->IndexEntry
, CurrentKey
->IndexEntry
->Length
);
1269 NewEntry
->Length
+= sizeof(ULONGLONG
);
1271 // Free the old memory
1272 ExFreePoolWithTag(CurrentKey
->IndexEntry
, TAG_NTFS
);
1274 CurrentKey
->IndexEntry
= NewEntry
;
1275 CurrentKey
->IndexEntry
->Flags
|= NTFS_INDEX_ENTRY_NODE
;
1278 // Update the sub-node
1279 Status
= UpdateIndexNode(DeviceExt
, FileRecord
, CurrentKey
->LesserChild
, IndexBufferSize
, IndexAllocationContext
, IndexAllocationOffset
);
1280 if (!NT_SUCCESS(Status
))
1282 DPRINT1("ERROR: Failed to update index node!\n");
1283 ReleaseAttributeContext(IndexAllocationContext
);
1287 // Update the VCN stored in the index entry of CurrentKey
1288 SetIndexEntryVCN(CurrentKey
->IndexEntry
, CurrentKey
->LesserChild
->VCN
);
1290 CurrentKey
= CurrentKey
->NextKey
;
1297 if (HasIndexAllocation
)
1300 PrintAllVCNs(DeviceExt
,
1301 IndexAllocationContext
,
1304 ReleaseAttributeContext(IndexAllocationContext
);
1307 return STATUS_SUCCESS
;
1311 UpdateIndexNode(PDEVICE_EXTENSION DeviceExt
,
1312 PFILE_RECORD_HEADER FileRecord
,
1313 PB_TREE_FILENAME_NODE Node
,
1314 ULONG IndexBufferSize
,
1315 PNTFS_ATTR_CONTEXT IndexAllocationContext
,
1316 ULONG IndexAllocationOffset
)
1319 PB_TREE_KEY CurrentKey
= Node
->FirstKey
;
1320 BOOLEAN HasChildren
= FALSE
;
1324 DPRINT("UpdateIndexNode(%p, %p, %p, %lu, %p, %lu) called for index node with VCN %I64u\n",
1329 IndexAllocationContext
,
1330 IndexAllocationOffset
,
1333 // Walk through the node and look for children to update
1334 for (i
= 0; i
< Node
->KeyCount
; i
++)
1338 // If there's a child node
1339 if (CurrentKey
->LesserChild
)
1343 // Update the child node on disk
1344 Status
= UpdateIndexNode(DeviceExt
, FileRecord
, CurrentKey
->LesserChild
, IndexBufferSize
, IndexAllocationContext
, IndexAllocationOffset
);
1345 if (!NT_SUCCESS(Status
))
1347 DPRINT1("ERROR: Failed to update child node!\n");
1351 // Is the Index Entry large enough to store the VCN?
1352 if (!CurrentKey
->IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
)
1354 // Allocate memory for the larger index entry
1355 PINDEX_ENTRY_ATTRIBUTE NewEntry
= ExAllocatePoolWithTag(NonPagedPool
,
1356 CurrentKey
->IndexEntry
->Length
+ sizeof(ULONGLONG
),
1360 DPRINT1("ERROR: Unable to allocate memory for new index entry!\n");
1361 return STATUS_INSUFFICIENT_RESOURCES
;
1364 // Copy the old entry to the new one
1365 RtlCopyMemory(NewEntry
, CurrentKey
->IndexEntry
, CurrentKey
->IndexEntry
->Length
);
1367 NewEntry
->Length
+= sizeof(ULONGLONG
);
1369 // Free the old memory
1370 ExFreePoolWithTag(CurrentKey
->IndexEntry
, TAG_NTFS
);
1372 CurrentKey
->IndexEntry
= NewEntry
;
1375 // Update the VCN stored in the index entry of CurrentKey
1376 SetIndexEntryVCN(CurrentKey
->IndexEntry
, CurrentKey
->LesserChild
->VCN
);
1378 CurrentKey
->IndexEntry
->Flags
|= NTFS_INDEX_ENTRY_NODE
;
1381 CurrentKey
= CurrentKey
->NextKey
;
1385 // Do we need to write this node to disk?
1386 if (Node
->DiskNeedsUpdating
)
1388 ULONGLONG NodeOffset
;
1389 ULONG LengthWritten
;
1390 PINDEX_BUFFER IndexBuffer
;
1392 // Does the node need to be assigned a VCN?
1393 if (!Node
->HasValidVCN
)
1395 // Allocate the node
1396 Status
= AllocateIndexNode(DeviceExt
,
1399 IndexAllocationContext
,
1400 IndexAllocationOffset
,
1402 if (!NT_SUCCESS(Status
))
1404 DPRINT1("ERROR: Failed to allocate index record in index allocation!\n");
1408 Node
->HasValidVCN
= TRUE
;
1411 // Allocate memory for an index buffer
1412 IndexBuffer
= ExAllocatePoolWithTag(NonPagedPool
, IndexBufferSize
, TAG_NTFS
);
1415 DPRINT1("ERROR: Failed to allocate %lu bytes for index buffer!\n", IndexBufferSize
);
1416 return STATUS_INSUFFICIENT_RESOURCES
;
1419 // Create the index buffer we'll be writing to disk to represent this node
1420 Status
= CreateIndexBufferFromBTreeNode(DeviceExt
, Node
, IndexBufferSize
, HasChildren
, IndexBuffer
);
1421 if (!NT_SUCCESS(Status
))
1423 DPRINT1("ERROR: Failed to create index buffer from node!\n");
1424 ExFreePoolWithTag(IndexBuffer
, TAG_NTFS
);
1428 // Get Offset of index buffer in index allocation
1429 NodeOffset
= GetAllocationOffsetFromVCN(DeviceExt
, IndexBufferSize
, Node
->VCN
);
1431 // Write the buffer to the index allocation
1432 Status
= WriteAttribute(DeviceExt
, IndexAllocationContext
, NodeOffset
, (const PUCHAR
)IndexBuffer
, IndexBufferSize
, &LengthWritten
, FileRecord
);
1433 if (!NT_SUCCESS(Status
) || LengthWritten
!= IndexBufferSize
)
1435 DPRINT1("ERROR: Failed to update index allocation!\n");
1436 ExFreePoolWithTag(IndexBuffer
, TAG_NTFS
);
1437 if (!NT_SUCCESS(Status
))
1440 return STATUS_END_OF_FILE
;
1443 Node
->DiskNeedsUpdating
= FALSE
;
1445 // Free the index buffer
1446 ExFreePoolWithTag(IndexBuffer
, TAG_NTFS
);
1449 return STATUS_SUCCESS
;
1453 CreateBTreeKeyFromFilename(ULONGLONG FileReference
, PFILENAME_ATTRIBUTE FileNameAttribute
)
1456 ULONG AttributeSize
= GetFileNameAttributeLength(FileNameAttribute
);
1457 ULONG EntrySize
= ALIGN_UP_BY(AttributeSize
+ FIELD_OFFSET(INDEX_ENTRY_ATTRIBUTE
, FileName
), 8);
1459 // Create a new Index Entry for the file
1460 PINDEX_ENTRY_ATTRIBUTE NewEntry
= ExAllocatePoolWithTag(NonPagedPool
, EntrySize
, TAG_NTFS
);
1463 DPRINT1("ERROR: Failed to allocate memory for Index Entry!\n");
1467 // Setup the Index Entry
1468 RtlZeroMemory(NewEntry
, EntrySize
);
1469 NewEntry
->Data
.Directory
.IndexedFile
= FileReference
;
1470 NewEntry
->Length
= EntrySize
;
1471 NewEntry
->KeyLength
= AttributeSize
;
1473 // Copy the FileNameAttribute
1474 RtlCopyMemory(&NewEntry
->FileName
, FileNameAttribute
, AttributeSize
);
1476 // Setup the New Key
1477 NewKey
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE_KEY
), TAG_NTFS
);
1480 DPRINT1("ERROR: Failed to allocate memory for new key!\n");
1481 ExFreePoolWithTag(NewEntry
, TAG_NTFS
);
1484 NewKey
->IndexEntry
= NewEntry
;
1485 NewKey
->NextKey
= NULL
;
1491 DestroyBTreeKey(PB_TREE_KEY Key
)
1493 if (Key
->IndexEntry
)
1494 ExFreePoolWithTag(Key
->IndexEntry
, TAG_NTFS
);
1496 if (Key
->LesserChild
)
1497 DestroyBTreeNode(Key
->LesserChild
);
1499 ExFreePoolWithTag(Key
, TAG_NTFS
);
1503 DestroyBTreeNode(PB_TREE_FILENAME_NODE Node
)
1505 PB_TREE_KEY NextKey
;
1506 PB_TREE_KEY CurrentKey
= Node
->FirstKey
;
1508 for (i
= 0; i
< Node
->KeyCount
; i
++)
1510 NT_ASSERT(CurrentKey
);
1511 NextKey
= CurrentKey
->NextKey
;
1512 DestroyBTreeKey(CurrentKey
);
1513 CurrentKey
= NextKey
;
1516 NT_ASSERT(NextKey
== NULL
);
1518 ExFreePoolWithTag(Node
, TAG_NTFS
);
1522 * @name DestroyBTree
1525 * Destroys a B-Tree.
1528 * Pointer to the B_TREE which will be destroyed.
1531 * Destroys every bit of data stored in the tree.
1534 DestroyBTree(PB_TREE Tree
)
1536 DestroyBTreeNode(Tree
->RootNode
);
1537 ExFreePoolWithTag(Tree
, TAG_NTFS
);
1541 DumpBTreeKey(PB_TREE Tree
, PB_TREE_KEY Key
, ULONG Number
, ULONG Depth
)
1544 for (i
= 0; i
< Depth
; i
++)
1546 DbgPrint(" Key #%d", Number
);
1548 if (!(Key
->IndexEntry
->Flags
& NTFS_INDEX_ENTRY_END
))
1550 UNICODE_STRING FileName
;
1551 FileName
.Length
= Key
->IndexEntry
->FileName
.NameLength
* sizeof(WCHAR
);
1552 FileName
.MaximumLength
= FileName
.Length
;
1553 FileName
.Buffer
= Key
->IndexEntry
->FileName
.Name
;
1554 DbgPrint(" '%wZ'\n", &FileName
);
1558 DbgPrint(" (Dummy Key)\n");
1561 // Is there a child node?
1562 if (Key
->IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
)
1564 if (Key
->LesserChild
)
1565 DumpBTreeNode(Tree
, Key
->LesserChild
, Number
, Depth
+ 1);
1568 // This will be an assert once nodes with arbitrary depth are debugged
1569 DPRINT1("DRIVER ERROR: No Key->LesserChild despite Key->IndexEntry->Flags indicating this is a node!\n");
1575 DumpBTreeNode(PB_TREE Tree
,
1576 PB_TREE_FILENAME_NODE Node
,
1580 PB_TREE_KEY CurrentKey
;
1582 for (i
= 0; i
< Depth
; i
++)
1584 DbgPrint("Node #%d, Depth %d, has %d key%s", Number
, Depth
, Node
->KeyCount
, Node
->KeyCount
== 1 ? "" : "s");
1586 if (Node
->HasValidVCN
)
1587 DbgPrint(" VCN: %I64u\n", Node
->VCN
);
1588 else if (Tree
->RootNode
== Node
)
1589 DbgPrint(" Index Root");
1591 DbgPrint(" NOT ASSIGNED VCN YET\n");
1593 CurrentKey
= Node
->FirstKey
;
1594 for (i
= 0; i
< Node
->KeyCount
; i
++)
1596 DumpBTreeKey(Tree
, CurrentKey
, i
, Depth
);
1597 CurrentKey
= CurrentKey
->NextKey
;
1605 * Displays a B-Tree.
1608 * Pointer to the B_TREE which will be displayed.
1611 * Displays a diagnostic summary of a B_TREE.
1614 DumpBTree(PB_TREE Tree
)
1616 DbgPrint("B_TREE @ %p\n", Tree
);
1617 DumpBTreeNode(Tree
, Tree
->RootNode
, 0, 0);
1620 // Calculates start of Index Buffer relative to the index allocation, given the node's VCN
1622 GetAllocationOffsetFromVCN(PDEVICE_EXTENSION DeviceExt
,
1623 ULONG IndexBufferSize
,
1626 if (IndexBufferSize
< DeviceExt
->NtfsInfo
.BytesPerCluster
)
1627 return Vcn
* DeviceExt
->NtfsInfo
.BytesPerSector
;
1629 return Vcn
* DeviceExt
->NtfsInfo
.BytesPerCluster
;
1633 GetIndexEntryVCN(PINDEX_ENTRY_ATTRIBUTE IndexEntry
)
1635 PULONGLONG Destination
= (PULONGLONG
)((ULONG_PTR
)IndexEntry
+ IndexEntry
->Length
- sizeof(ULONGLONG
));
1637 ASSERT(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
);
1639 return *Destination
;
1643 * @name NtfsInsertKey
1646 * Inserts a FILENAME_ATTRIBUTE into a B-Tree node.
1649 * Pointer to the B_TREE the key (filename attribute) is being inserted into.
1651 * @param FileReference
1652 * Reference number to the file being added. This will be a combination of the MFT index and update sequence number.
1654 * @param FileNameAttribute
1655 * Pointer to a FILENAME_ATTRIBUTE which is the data for the key that will be added to the tree. A copy will be made.
1658 * Pointer to a B_TREE_FILENAME_NODE into which a new key will be inserted, in order.
1660 * @param CaseSensitive
1661 * Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
1662 * if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag.
1664 * @param MaxIndexRootSize
1665 * The maximum size, in bytes, of node entries that can be stored in the index root before it will grow too large for
1666 * the file record. This number is just the size of the entries, without any headers for the attribute or index root.
1668 * @param IndexRecordSize
1669 * The size, in bytes, of an index record for this index. AKA an index buffer. Usually set to 4096.
1672 * Pointer to a PB_TREE_KEY that will receive a pointer to the median key, should the node grow too large and need to be split.
1673 * Will be set to NULL if the node isn't split.
1675 * @param NewRightHandSibling
1676 * Pointer to a PB_TREE_FILENAME_NODE that will receive a pointer to a newly-created right-hand sibling node,
1677 * should the node grow too large and need to be split. Will be set to NULL if the node isn't split.
1680 * A node is always sorted, with the least comparable filename stored first and a dummy key to mark the end.
1683 NtfsInsertKey(PB_TREE Tree
,
1684 ULONGLONG FileReference
,
1685 PFILENAME_ATTRIBUTE FileNameAttribute
,
1686 PB_TREE_FILENAME_NODE Node
,
1687 BOOLEAN CaseSensitive
,
1688 ULONG MaxIndexRootSize
,
1689 ULONG IndexRecordSize
,
1690 PB_TREE_KEY
*MedianKey
,
1691 PB_TREE_FILENAME_NODE
*NewRightHandSibling
)
1693 PB_TREE_KEY NewKey
, CurrentKey
, PreviousKey
;
1694 NTSTATUS Status
= STATUS_SUCCESS
;
1696 ULONG AllocatedNodeSize
;
1697 ULONG MaxNodeSizeWithoutHeader
;
1701 *NewRightHandSibling
= NULL
;
1703 DPRINT1("NtfsInsertKey(%p, 0x%I64x, %p, %p, %s, %lu, %lu, %p, %p)\n",
1708 CaseSensitive
? "TRUE" : "FALSE",
1712 NewRightHandSibling
);
1714 // Create the key for the filename attribute
1715 NewKey
= CreateBTreeKeyFromFilename(FileReference
, FileNameAttribute
);
1717 return STATUS_INSUFFICIENT_RESOURCES
;
1719 // Find where to insert the key
1720 CurrentKey
= Node
->FirstKey
;
1722 for (i
= 0; i
< Node
->KeyCount
; i
++)
1724 // Should the New Key go before the current key?
1725 LONG Comparison
= CompareTreeKeys(NewKey
, CurrentKey
, CaseSensitive
);
1727 if (Comparison
== 0)
1729 DPRINT1("\t\tComparison == 0: %.*S\n", NewKey
->IndexEntry
->FileName
.NameLength
, NewKey
->IndexEntry
->FileName
.Name
);
1730 DPRINT1("\t\tComparison == 0: %.*S\n", CurrentKey
->IndexEntry
->FileName
.NameLength
, CurrentKey
->IndexEntry
->FileName
.Name
);
1732 ASSERT(Comparison
!= 0);
1734 // Is NewKey < CurrentKey?
1737 // Does CurrentKey have a sub-node?
1738 if (CurrentKey
->LesserChild
)
1740 PB_TREE_KEY NewLeftKey
;
1741 PB_TREE_FILENAME_NODE NewChild
;
1743 // Insert the key into the child node
1744 Status
= NtfsInsertKey(Tree
,
1747 CurrentKey
->LesserChild
,
1753 if (!NT_SUCCESS(Status
))
1755 DPRINT1("ERROR: Failed to insert key.\n");
1756 ExFreePoolWithTag(NewKey
, TAG_NTFS
);
1760 // Did the child node get split?
1763 ASSERT(NewChild
!= NULL
);
1765 // Insert the new left key to the left of the current key
1766 NewLeftKey
->NextKey
= CurrentKey
;
1768 // Is CurrentKey the first key?
1770 Node
->FirstKey
= NewLeftKey
;
1772 PreviousKey
->NextKey
= NewLeftKey
;
1774 // CurrentKey->LesserChild will be the right-hand sibling
1775 CurrentKey
->LesserChild
= NewChild
;
1778 Node
->DiskNeedsUpdating
= TRUE
;
1787 // Insert New Key before Current Key
1788 NewKey
->NextKey
= CurrentKey
;
1790 // Increase KeyCount and mark node as dirty
1792 Node
->DiskNeedsUpdating
= TRUE
;
1794 // was CurrentKey the first key?
1795 if (CurrentKey
== Node
->FirstKey
)
1796 Node
->FirstKey
= NewKey
;
1798 PreviousKey
->NextKey
= NewKey
;
1803 PreviousKey
= CurrentKey
;
1804 CurrentKey
= CurrentKey
->NextKey
;
1807 // Determine how much space the index entries will need
1808 NodeSize
= GetSizeOfIndexEntries(Node
);
1810 // Is Node not the root node?
1811 if (Node
!= Tree
->RootNode
)
1813 // Calculate maximum size of index entries without any headers
1814 AllocatedNodeSize
= IndexRecordSize
- FIELD_OFFSET(INDEX_BUFFER
, Header
);
1816 // TODO: Replace magic with math
1817 MaxNodeSizeWithoutHeader
= AllocatedNodeSize
- 0x28;
1819 // Has the node grown larger than its allocated size?
1820 if (NodeSize
> MaxNodeSizeWithoutHeader
)
1824 Status
= SplitBTreeNode(Tree
, Node
, MedianKey
, NewRightHandSibling
, CaseSensitive
);
1825 if (!NT_SUCCESS(Status
))
1827 DPRINT1("ERROR: Failed to split B-Tree node!\n");
1835 // NewEntry and NewKey will be destroyed later by DestroyBTree()
1843 * @name SplitBTreeNode
1846 * Splits a B-Tree node that has grown too large. Finds the median key and sets up a right-hand-sibling
1847 * node to contain the keys to the right of the median key.
1850 * Pointer to the B_TREE which contains the node being split
1853 * Pointer to the B_TREE_FILENAME_NODE that needs to be split
1856 * Pointer a PB_TREE_KEY that will receive the pointer to the key in the middle of the node being split
1858 * @param NewRightHandSibling
1859 * Pointer to a PB_TREE_FILENAME_NODE that will receive a pointer to a newly-created B_TREE_FILENAME_NODE
1860 * containing the keys to the right of MedianKey.
1862 * @param CaseSensitive
1863 * Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
1864 * if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag.
1867 * STATUS_SUCCESS on success.
1868 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
1871 * It's the responsibility of the caller to insert the new median key into the parent node, as well as making the
1872 * NewRightHandSibling the lesser child of the node that is currently Node's parent.
1875 SplitBTreeNode(PB_TREE Tree
,
1876 PB_TREE_FILENAME_NODE Node
,
1877 PB_TREE_KEY
*MedianKey
,
1878 PB_TREE_FILENAME_NODE
*NewRightHandSibling
,
1879 BOOLEAN CaseSensitive
)
1881 ULONG MedianKeyIndex
;
1882 PB_TREE_KEY LastKeyBeforeMedian
, FirstKeyAfterMedian
;
1888 DPRINT1("SplitBTreeNode(%p, %p, %p, %p, %s) called\n",
1892 NewRightHandSibling
,
1893 CaseSensitive
? "TRUE" : "FALSE");
1896 DumpBTreeNode(Node
, 0, 0);
1899 // Create the right hand sibling
1900 *NewRightHandSibling
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE_FILENAME_NODE
), TAG_NTFS
);
1901 if (*NewRightHandSibling
== NULL
)
1903 DPRINT1("Error: Failed to allocate memory for right hand sibling!\n");
1904 return STATUS_INSUFFICIENT_RESOURCES
;
1906 RtlZeroMemory(*NewRightHandSibling
, sizeof(B_TREE_FILENAME_NODE
));
1907 (*NewRightHandSibling
)->DiskNeedsUpdating
= TRUE
;
1910 // Find the last key before the median
1912 // This is roughly how NTFS-3G calculates median, and it's not congruent with what Windows does:
1914 // find the median key index
1915 MedianKeyIndex = (Node->KeyCount + 1) / 2;
1918 LastKeyBeforeMedian = Node->FirstKey;
1919 for (i = 0; i < MedianKeyIndex - 1; i++)
1920 LastKeyBeforeMedian = LastKeyBeforeMedian->NextKey;*/
1922 // The method we'll use is a little bit closer to how Windows determines the median but it's not identical.
1923 // What Windows does is actually more complicated than this, I think because Windows allocates more slack space to Odd-numbered
1924 // Index Records, leaving less room for index entries in these records (I haven't discovered why this is done).
1925 // (Neither Windows nor chkdsk complain if we choose a different median than Windows would have chosen, as our median will be in the ballpark)
1927 // Use size to locate the median key / index
1928 LastKeyBeforeMedian
= Node
->FirstKey
;
1930 HalfSize
= 2016; // half the allocated size after subtracting the first index entry offset (TODO: MATH)
1932 for (i
= 0; i
< Node
->KeyCount
; i
++)
1934 SizeSum
+= LastKeyBeforeMedian
->IndexEntry
->Length
;
1936 if (SizeSum
> HalfSize
)
1940 LastKeyBeforeMedian
= LastKeyBeforeMedian
->NextKey
;
1943 // Now we can get the median key and the key that follows it
1944 *MedianKey
= LastKeyBeforeMedian
->NextKey
;
1945 FirstKeyAfterMedian
= (*MedianKey
)->NextKey
;
1947 DPRINT1("%lu keys, %lu median\n", Node
->KeyCount
, MedianKeyIndex
);
1948 DPRINT1("\t\tMedian: %.*S\n", (*MedianKey
)->IndexEntry
->FileName
.NameLength
, (*MedianKey
)->IndexEntry
->FileName
.Name
);
1950 // "Node" will be the left hand sibling after the split, containing all keys prior to the median key
1952 // We need to create a dummy pointer at the end of the LHS. The dummy's child will be the median's child.
1953 LastKeyBeforeMedian
->NextKey
= CreateDummyKey(BooleanFlagOn((*MedianKey
)->IndexEntry
->Flags
, NTFS_INDEX_ENTRY_NODE
));
1954 if (LastKeyBeforeMedian
->NextKey
== NULL
)
1956 DPRINT1("Error: Couldn't allocate dummy key!\n");
1957 LastKeyBeforeMedian
->NextKey
= *MedianKey
;
1958 ExFreePoolWithTag(*NewRightHandSibling
, TAG_NTFS
);
1959 return STATUS_INSUFFICIENT_RESOURCES
;
1962 // Did the median key have a child node?
1963 if ((*MedianKey
)->IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
)
1965 // Set the child of the new dummy key
1966 LastKeyBeforeMedian
->NextKey
->LesserChild
= (*MedianKey
)->LesserChild
;
1968 // Give the dummy key's index entry the same sub-node VCN the median
1969 SetIndexEntryVCN(LastKeyBeforeMedian
->NextKey
->IndexEntry
, GetIndexEntryVCN((*MedianKey
)->IndexEntry
));
1973 // Median key didn't have a child node, but it will. Create a new index entry large enough to store a VCN.
1974 PINDEX_ENTRY_ATTRIBUTE NewIndexEntry
= ExAllocatePoolWithTag(NonPagedPool
,
1975 (*MedianKey
)->IndexEntry
->Length
+ sizeof(ULONGLONG
),
1979 DPRINT1("Unable to allocate memory for new index entry!\n");
1980 LastKeyBeforeMedian
->NextKey
= *MedianKey
;
1981 ExFreePoolWithTag(*NewRightHandSibling
, TAG_NTFS
);
1982 return STATUS_INSUFFICIENT_RESOURCES
;
1985 // Copy the old index entry to the new one
1986 RtlCopyMemory(NewIndexEntry
, (*MedianKey
)->IndexEntry
, (*MedianKey
)->IndexEntry
->Length
);
1988 // Use the new index entry after freeing the old one
1989 ExFreePoolWithTag((*MedianKey
)->IndexEntry
, TAG_NTFS
);
1990 (*MedianKey
)->IndexEntry
= NewIndexEntry
;
1992 // Update the length for the VCN
1993 (*MedianKey
)->IndexEntry
->Length
+= sizeof(ULONGLONG
);
1995 // Set the node flag
1996 (*MedianKey
)->IndexEntry
->Flags
|= NTFS_INDEX_ENTRY_NODE
;
1999 // "Node" will become the child of the median key
2000 (*MedianKey
)->LesserChild
= Node
;
2001 SetIndexEntryVCN((*MedianKey
)->IndexEntry
, Node
->VCN
);
2003 // Update Node's KeyCount (remember to add 1 for the new dummy key)
2004 Node
->KeyCount
= MedianKeyIndex
+ 2;
2006 KeyCount
= CountBTreeKeys(Node
->FirstKey
);
2007 ASSERT(Node
->KeyCount
== KeyCount
);
2009 // everything to the right of MedianKey becomes the right hand sibling of Node
2010 (*NewRightHandSibling
)->FirstKey
= FirstKeyAfterMedian
;
2011 (*NewRightHandSibling
)->KeyCount
= CountBTreeKeys(FirstKeyAfterMedian
);
2014 DPRINT1("Left-hand node after split:\n");
2015 DumpBTreeNode(Node
, 0, 0);
2017 DPRINT1("Right-hand sibling node after split:\n");
2018 DumpBTreeNode(*NewRightHandSibling
, 0, 0);
2021 return STATUS_SUCCESS
;