xref: /reactos/drivers/filesystems/ntfs/btree.c (revision 9d3c3a75)
1 /*
2 *  ReactOS kernel
3 *  Copyright (C) 2002, 2017 ReactOS Team
4 *
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.
9 *
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.
14 *
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.
18 *
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
24 */
25 
26 /* INCLUDES *****************************************************************/
27 
28 #include "ntfs.h"
29 
30 #define NDEBUG
31 #include <debug.h>
32 
33 /* FUNCTIONS ****************************************************************/
34 
35 // TEMP FUNCTION for diagnostic purposes.
36 // Prints VCN of every node in an index allocation
37 VOID
38 PrintAllVCNs(PDEVICE_EXTENSION Vcb,
39              PNTFS_ATTR_CONTEXT IndexAllocationContext,
40              ULONG NodeSize)
41 {
42     ULONGLONG CurrentOffset = 0;
43     PINDEX_BUFFER CurrentNode, Buffer;
44     ULONGLONG BufferSize = AttributeDataLength(IndexAllocationContext->pRecord);
45     ULONG BytesRead;
46     ULONGLONG i;
47     int Count = 0;
48 
49     if (BufferSize == 0)
50     {
51         DPRINT1("Index Allocation is empty.\n");
52         return;
53     }
54 
55     Buffer = ExAllocatePoolWithTag(NonPagedPool, BufferSize, TAG_NTFS);
56 
57     BytesRead = ReadAttribute(Vcb, IndexAllocationContext, 0, (PCHAR)Buffer, BufferSize);
58 
59     ASSERT(BytesRead = BufferSize);
60 
61     CurrentNode = Buffer;
62 
63     // loop through all the nodes
64     for (i = 0; i < BufferSize; i += NodeSize)
65     {
66         NTSTATUS Status = FixupUpdateSequenceArray(Vcb, &CurrentNode->Ntfs);
67         if (!NT_SUCCESS(Status))
68         {
69             DPRINT1("ERROR: Fixing fixup failed!\n");
70             continue;
71         }
72 
73         DPRINT1("Node #%d, VCN: %I64u\n", Count, CurrentNode->VCN);
74 
75         CurrentNode = (PINDEX_BUFFER)((ULONG_PTR)CurrentNode + NodeSize);
76         CurrentOffset += NodeSize;
77         Count++;
78     }
79 
80     ExFreePoolWithTag(Buffer, TAG_NTFS);
81 }
82 
83 /**
84 * @name AllocateIndexNode
85 * @implemented
86 *
87 * Allocates a new index record in an index allocation.
88 *
89 * @param DeviceExt
90 * Pointer to the target DEVICE_EXTENSION describing the volume the node will be created on.
91 *
92 * @param FileRecord
93 * Pointer to a copy of the file record containing the index.
94 *
95 * @param IndexBufferSize
96 * Size of an index record for this index, in bytes. Commonly defined as 4096.
97 *
98 * @param IndexAllocationCtx
99 * Pointer to an NTFS_ATTR_CONTEXT describing the index allocation attribute the node will be assigned to.
100 *
101 * @param IndexAllocationOffset
102 * Offset of the index allocation attribute relative to the file record.
103 *
104 * @param NewVCN
105 * Pointer to a ULONGLONG which will receive the VCN of the newly-assigned index record
106 *
107 * @returns
108 * STATUS_SUCCESS in case of success.
109 * STATUS_NOT_IMPLEMENTED if there's no $I30 bitmap attribute in the file record.
110 *
111 * @remarks
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.
115 */
116 NTSTATUS
117 AllocateIndexNode(PDEVICE_EXTENSION DeviceExt,
118                   PFILE_RECORD_HEADER FileRecord,
119                   ULONG IndexBufferSize,
120                   PNTFS_ATTR_CONTEXT IndexAllocationCtx,
121                   ULONG IndexAllocationOffset,
122                   PULONGLONG NewVCN)
123 {
124     NTSTATUS Status;
125     PNTFS_ATTR_CONTEXT BitmapCtx;
126     ULONGLONG IndexAllocationLength, BitmapLength;
127     ULONG BitmapOffset;
128     ULONGLONG NextNodeNumber;
129     PCHAR *BitmapMem;
130     ULONG *BitmapPtr;
131     RTL_BITMAP Bitmap;
132     ULONG BytesWritten;
133     ULONG BytesNeeded;
134     LARGE_INTEGER DataSize;
135 
136     DPRINT1("AllocateIndexNode(%p, %p, %lu, %p, %lu, %p) called.\n", DeviceExt,
137             FileRecord,
138             IndexBufferSize,
139             IndexAllocationCtx,
140             IndexAllocationOffset,
141             NewVCN);
142 
143     // Get the length of the attribute allocation
144     IndexAllocationLength = AttributeDataLength(IndexAllocationCtx->pRecord);
145 
146     // Find the bitmap attribute for the index
147     Status = FindAttribute(DeviceExt,
148                            FileRecord,
149                            AttributeBitmap,
150                            L"$I30",
151                            4,
152                            &BitmapCtx,
153                            &BitmapOffset);
154     if (!NT_SUCCESS(Status))
155     {
156         DPRINT1("FIXME: Need to add bitmap attribute!\n");
157         return STATUS_NOT_IMPLEMENTED;
158     }
159 
160     // Get the length of the bitmap attribute
161     BitmapLength = AttributeDataLength(BitmapCtx->pRecord);
162 
163     NextNodeNumber = IndexAllocationLength / DeviceExt->NtfsInfo.BytesPerIndexRecord;
164 
165     // TODO: Find unused allocation in bitmap and use that space first
166 
167     // Add another bit to bitmap
168 
169     // See how many bytes we need to store the amount of bits we'll have
170     BytesNeeded = NextNodeNumber / 8;
171     BytesNeeded++;
172 
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);
175 
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);
179     if (!BitmapMem)
180     {
181         DPRINT1("Error: failed to allocate bitmap!");
182         ReleaseAttributeContext(BitmapCtx);
183         return STATUS_INSUFFICIENT_RESOURCES;
184     }
185     // RtlInitializeBitmap() wants a pointer that's ULONG-aligned.
186     BitmapPtr = (PULONG)ALIGN_UP_BY((ULONG_PTR)BitmapMem, sizeof(ULONG));
187 
188     RtlZeroMemory(BitmapPtr, BytesNeeded);
189 
190     // Read the existing bitmap data
191     Status = ReadAttribute(DeviceExt, BitmapCtx, 0, (PCHAR)BitmapPtr, BitmapLength);
192 
193     // Initialize bitmap
194     RtlInitializeBitMap(&Bitmap, BitmapPtr, NextNodeNumber);
195 
196     // Do we need to enlarge the bitmap?
197     if (BytesNeeded > BitmapLength)
198     {
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)
203         {
204             Status = SetNonResidentAttributeDataLength(DeviceExt,
205                                                        BitmapCtx,
206                                                        BitmapOffset,
207                                                        FileRecord,
208                                                        &DataSize);
209         }
210         else
211         {
212             Status = SetResidentAttributeDataLength(DeviceExt,
213                                                     BitmapCtx,
214                                                     BitmapOffset,
215                                                     FileRecord,
216                                                     &DataSize);
217         }
218         if (!NT_SUCCESS(Status))
219         {
220             DPRINT1("ERROR: Failed to set length of bitmap attribute!\n");
221             ReleaseAttributeContext(BitmapCtx);
222             return Status;
223         }
224     }
225 
226     // Enlarge Index Allocation attribute
227     DataSize.QuadPart = IndexAllocationLength + IndexBufferSize;
228     Status = SetNonResidentAttributeDataLength(DeviceExt,
229                                                IndexAllocationCtx,
230                                                IndexAllocationOffset,
231                                                FileRecord,
232                                                &DataSize);
233     if (!NT_SUCCESS(Status))
234     {
235         DPRINT1("ERROR: Failed to set length of index allocation!\n");
236         ReleaseAttributeContext(BitmapCtx);
237         return Status;
238     }
239 
240     // Update file record on disk
241     Status = UpdateFileRecord(DeviceExt, IndexAllocationCtx->FileMFTIndex, FileRecord);
242     if (!NT_SUCCESS(Status))
243     {
244         DPRINT1("ERROR: Failed to update file record!\n");
245         ReleaseAttributeContext(BitmapCtx);
246         return Status;
247     }
248 
249     // Set the bit for the new index record
250     RtlSetBits(&Bitmap, NextNodeNumber, 1);
251 
252     // Write the new bitmap attribute
253     Status = WriteAttribute(DeviceExt,
254                             BitmapCtx,
255                             0,
256                             (const PUCHAR)BitmapPtr,
257                             BytesNeeded,
258                             &BytesWritten,
259                             FileRecord);
260     if (!NT_SUCCESS(Status))
261     {
262         DPRINT1("ERROR: Unable to write to $I30 bitmap attribute!\n");
263     }
264 
265     // Calculate VCN of new node number
266     *NewVCN = NextNodeNumber * (IndexBufferSize / DeviceExt->NtfsInfo.BytesPerCluster);
267 
268     DPRINT("New VCN: %I64u\n", *NewVCN);
269 
270     ExFreePoolWithTag(BitmapMem, TAG_NTFS);
271     ReleaseAttributeContext(BitmapCtx);
272 
273     return Status;
274 }
275 
276 /**
277 * @name CreateDummyKey
278 * @implemented
279 *
280 * Creates the final B_TREE_KEY for a B_TREE_FILENAME_NODE. Also creates the associated index entry.
281 *
282 * @param HasChildNode
283 * BOOLEAN to indicate if this key will have a LesserChild.
284 *
285 * @return
286 * The newly-created key.
287 */
288 PB_TREE_KEY
289 CreateDummyKey(BOOLEAN HasChildNode)
290 {
291     PINDEX_ENTRY_ATTRIBUTE NewIndexEntry;
292     PB_TREE_KEY NewDummyKey;
293 
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
297 
298     // Create the index entry for the key
299     NewIndexEntry = ExAllocatePoolWithTag(NonPagedPool, EntrySize, TAG_NTFS);
300     if (!NewIndexEntry)
301     {
302         DPRINT1("Couldn't allocate memory for dummy key index entry!\n");
303         return NULL;
304     }
305 
306     RtlZeroMemory(NewIndexEntry, EntrySize);
307 
308     if (HasChildNode)
309     {
310         NewIndexEntry->Flags = NTFS_INDEX_ENTRY_NODE | NTFS_INDEX_ENTRY_END;
311     }
312     else
313     {
314         NewIndexEntry->Flags = NTFS_INDEX_ENTRY_END;
315         EntrySize -= sizeof(ULONGLONG); // no VCN
316     }
317 
318     NewIndexEntry->Length = EntrySize;
319 
320     // Create the key
321     NewDummyKey = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_KEY), TAG_NTFS);
322     if (!NewDummyKey)
323     {
324         DPRINT1("Unable to allocate dummy key!\n");
325         ExFreePoolWithTag(NewIndexEntry, TAG_NTFS);
326         return NULL;
327     }
328     RtlZeroMemory(NewDummyKey, sizeof(B_TREE_KEY));
329 
330     NewDummyKey->IndexEntry = NewIndexEntry;
331 
332     return NewDummyKey;
333 }
334 
335 /**
336 * @name CreateEmptyBTree
337 * @implemented
338 *
339 * Creates an empty B-Tree, which will contain a single root node which will contain a single dummy key.
340 *
341 * @param NewTree
342 * Pointer to a PB_TREE that will receive the pointer of the newly-created B-Tree.
343 *
344 * @return
345 * STATUS_SUCCESS on success. STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
346 */
347 NTSTATUS
348 CreateEmptyBTree(PB_TREE *NewTree)
349 {
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;
353 
354     DPRINT1("CreateEmptyBTree(%p) called\n", NewTree);
355 
356     if (!Tree || !RootNode)
357     {
358         DPRINT1("Couldn't allocate enough memory for B-Tree!\n");
359         if (Tree)
360             ExFreePoolWithTag(Tree, TAG_NTFS);
361         if (RootNode)
362             ExFreePoolWithTag(RootNode, TAG_NTFS);
363         return STATUS_INSUFFICIENT_RESOURCES;
364     }
365 
366     // Create the dummy key
367     DummyKey = CreateDummyKey(FALSE);
368     if (!DummyKey)
369     {
370         DPRINT1("ERROR: Failed to create dummy key!\n");
371         ExFreePoolWithTag(Tree, TAG_NTFS);
372         ExFreePoolWithTag(RootNode, TAG_NTFS);
373         return STATUS_INSUFFICIENT_RESOURCES;
374     }
375 
376     RtlZeroMemory(Tree, sizeof(B_TREE));
377     RtlZeroMemory(RootNode, sizeof(B_TREE_FILENAME_NODE));
378 
379     // Setup the Tree
380     RootNode->FirstKey = DummyKey;
381     RootNode->KeyCount = 1;
382     RootNode->DiskNeedsUpdating = TRUE;
383     Tree->RootNode = RootNode;
384 
385     *NewTree = Tree;
386 
387     // Memory will be freed when DestroyBTree() is called
388 
389     return STATUS_SUCCESS;
390 }
391 
392 /**
393 * @name CompareTreeKeys
394 * @implemented
395 *
396 * Compare two B_TREE_KEY's to determine their order in the tree.
397 *
398 * @param Key1
399 * Pointer to a B_TREE_KEY that will be compared.
400 *
401 * @param Key2
402 * Pointer to the other B_TREE_KEY that will be compared.
403 *
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.
407 *
408 * @returns
409 * 0 if the two keys are equal.
410 * < 0 if key1 is less thank key2
411 * > 0 if key1 is greater than key2
412 *
413 * @remarks
414 * Any other key is always less than the final (dummy) key in a node. Key1 must not be the dummy node.
415 */
416 LONG
417 CompareTreeKeys(PB_TREE_KEY Key1, PB_TREE_KEY Key2, BOOLEAN CaseSensitive)
418 {
419     UNICODE_STRING Key1Name, Key2Name;
420     LONG Comparison;
421 
422     // Key1 must not be the final key (AKA the dummy key)
423     ASSERT(!(Key1->IndexEntry->Flags & NTFS_INDEX_ENTRY_END));
424 
425     // If Key2 is the "dummy key", key 1 will always come first
426     if (Key2->NextKey == NULL)
427         return -1;
428 
429     Key1Name.Buffer = Key1->IndexEntry->FileName.Name;
430     Key1Name.Length = Key1Name.MaximumLength
431         = Key1->IndexEntry->FileName.NameLength * sizeof(WCHAR);
432 
433     Key2Name.Buffer = Key2->IndexEntry->FileName.Name;
434     Key2Name.Length = Key2Name.MaximumLength
435         = Key2->IndexEntry->FileName.NameLength * sizeof(WCHAR);
436 
437     // Are the two keys the same length?
438     if (Key1Name.Length == Key2Name.Length)
439         return RtlCompareUnicodeString(&Key1Name, &Key2Name, !CaseSensitive);
440 
441     // Is Key1 shorter?
442     if (Key1Name.Length < Key2Name.Length)
443     {
444         // Truncate KeyName2 to be the same length as KeyName1
445         Key2Name.Length = Key1Name.Length;
446 
447         // Compare the names of the same length
448         Comparison = RtlCompareUnicodeString(&Key1Name, &Key2Name, !CaseSensitive);
449 
450         // If the truncated names are the same length, the shorter one comes first
451         if (Comparison == 0)
452             return -1;
453     }
454     else
455     {
456         // Key2 is shorter
457         // Truncate KeyName1 to be the same length as KeyName2
458         Key1Name.Length = Key2Name.Length;
459 
460         // Compare the names of the same length
461         Comparison = RtlCompareUnicodeString(&Key1Name, &Key2Name, !CaseSensitive);
462 
463         // If the truncated names are the same length, the shorter one comes first
464         if (Comparison == 0)
465             return 1;
466     }
467 
468     return Comparison;
469 }
470 
471 /**
472 * @name CountBTreeKeys
473 * @implemented
474 *
475 * Counts the number of linked B-Tree keys, starting with FirstKey.
476 *
477 * @param FirstKey
478 * Pointer to a B_TREE_KEY that will be the first key to be counted.
479 *
480 * @return
481 * The number of keys in a linked-list, including FirstKey and the final dummy key.
482 */
483 ULONG
484 CountBTreeKeys(PB_TREE_KEY FirstKey)
485 {
486     ULONG Count = 0;
487     PB_TREE_KEY Current = FirstKey;
488 
489     while (Current != NULL)
490     {
491         Count++;
492         Current = Current->NextKey;
493     }
494 
495     return Count;
496 }
497 
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)
503 {
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;
510     PULONGLONG VCN;
511     PB_TREE_KEY CurrentKey;
512     NTSTATUS Status;
513     ULONGLONG IndexNodeOffset;
514     ULONG BytesRead;
515 
516     if (IndexAllocationAttributeCtx == NULL)
517     {
518         DPRINT1("ERROR: Couldn't find index allocation attribute even though there should be one!\n");
519         return NULL;
520     }
521 
522     // Get the node number from the end of the node entry
523     VCN = (PULONGLONG)((ULONG_PTR)NodeEntry + NodeEntry->Length - sizeof(ULONGLONG));
524 
525     // Create the new tree node
526     NewNode = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_FILENAME_NODE), TAG_NTFS);
527     if (!NewNode)
528     {
529         DPRINT1("ERROR: Couldn't allocate memory for new filename node.\n");
530         return NULL;
531     }
532     RtlZeroMemory(NewNode, sizeof(B_TREE_FILENAME_NODE));
533 
534     // Create the first key
535     CurrentKey = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_KEY), TAG_NTFS);
536     if (!CurrentKey)
537     {
538         DPRINT1("ERROR: Failed to allocate memory for key!\n");
539         ExFreePoolWithTag(NewNode, TAG_NTFS);
540         return NULL;
541     }
542     RtlZeroMemory(CurrentKey, sizeof(B_TREE_KEY));
543     NewNode->FirstKey = CurrentKey;
544 
545     // Allocate memory for the node buffer
546     NodeBuffer = ExAllocatePoolWithTag(NonPagedPool, IndexBufferSize, TAG_NTFS);
547     if (!NodeBuffer)
548     {
549         DPRINT1("ERROR: Couldn't allocate memory for node buffer!\n");
550         ExFreePoolWithTag(CurrentKey, TAG_NTFS);
551         ExFreePoolWithTag(NewNode, TAG_NTFS);
552         return NULL;
553     }
554 
555     // Calculate offset into index allocation
556     IndexNodeOffset = GetAllocationOffsetFromVCN(Vcb, IndexBufferSize, *VCN);
557 
558     // TODO: Confirm index bitmap has this node marked as in-use
559 
560     // Read the node
561     BytesRead = ReadAttribute(Vcb,
562                               IndexAllocationAttributeCtx,
563                               IndexNodeOffset,
564                               (PCHAR)NodeBuffer,
565                               IndexBufferSize);
566 
567     ASSERT(BytesRead == IndexBufferSize);
568     NT_ASSERT(NodeBuffer->Ntfs.Type == NRH_INDX_TYPE);
569     NT_ASSERT(NodeBuffer->VCN == *VCN);
570 
571     // Apply the fixup array to the node buffer
572     Status = FixupUpdateSequenceArray(Vcb, &NodeBuffer->Ntfs);
573     if (!NT_SUCCESS(Status))
574     {
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);
579         return NULL;
580     }
581 
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)
587     {
588         // Allocate memory for the current entry
589         CurrentKey->IndexEntry = ExAllocatePoolWithTag(NonPagedPool, CurrentNodeEntry->Length, TAG_NTFS);
590         if (!CurrentKey->IndexEntry)
591         {
592             DPRINT1("ERROR: Couldn't allocate memory for next key!\n");
593             DestroyBTreeNode(NewNode);
594             ExFreePoolWithTag(NodeBuffer, TAG_NTFS);
595             return NULL;
596         }
597 
598         NewNode->KeyCount++;
599 
600         // If this isn't the last entry
601         if (!(CurrentNodeEntry->Flags & NTFS_INDEX_ENTRY_END))
602         {
603             // Create the next key
604             PB_TREE_KEY NextKey = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_KEY), TAG_NTFS);
605             if (!NextKey)
606             {
607                 DPRINT1("ERROR: Couldn't allocate memory for next key!\n");
608                 DestroyBTreeNode(NewNode);
609                 ExFreePoolWithTag(NodeBuffer, TAG_NTFS);
610                 return NULL;
611             }
612             RtlZeroMemory(NextKey, sizeof(B_TREE_KEY));
613 
614             // Add NextKey to the end of the list
615             CurrentKey->NextKey = NextKey;
616 
617             // Copy the current entry to its key
618             RtlCopyMemory(CurrentKey->IndexEntry, CurrentNodeEntry, CurrentNodeEntry->Length);
619 
620             // See if the current key has a sub-node
621             if (CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)
622             {
623                 CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb,
624                                                                        IndexRoot,
625                                                                        IndexAllocationAttributeCtx,
626                                                                        CurrentKey->IndexEntry);
627             }
628 
629             CurrentKey = NextKey;
630         }
631         else
632         {
633             // Copy the final entry to its key
634             RtlCopyMemory(CurrentKey->IndexEntry, CurrentNodeEntry, CurrentNodeEntry->Length);
635             CurrentKey->NextKey = NULL;
636 
637             // See if the current key has a sub-node
638             if (CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)
639             {
640                 CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb,
641                                                                        IndexRoot,
642                                                                        IndexAllocationAttributeCtx,
643                                                                        CurrentKey->IndexEntry);
644             }
645 
646             break;
647         }
648 
649         // Advance to the next entry
650         CurrentEntryOffset += CurrentNodeEntry->Length;
651         CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)CurrentNodeEntry + CurrentNodeEntry->Length);
652     }
653 
654     NewNode->VCN = *VCN;
655     NewNode->HasValidVCN = TRUE;
656 
657     ExFreePoolWithTag(NodeBuffer, TAG_NTFS);
658 
659     return NewNode;
660 }
661 
662 /**
663 * @name CreateBTreeFromIndex
664 * @implemented
665 *
666 * Parse an index and create a B-Tree in memory from it.
667 *
668 * @param IndexRootContext
669 * Pointer to an NTFS_ATTR_CONTEXT that describes the location of the index root attribute.
670 *
671 * @param NewTree
672 * Pointer to a PB_TREE that will receive the pointer to a newly-created B-Tree.
673 *
674 * @returns
675 * STATUS_SUCCESS on success.
676 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
677 *
678 * @remarks
679 * Allocates memory for the entire tree. Caller is responsible for destroying the tree with DestroyBTree().
680 */
681 NTSTATUS
682 CreateBTreeFromIndex(PDEVICE_EXTENSION Vcb,
683                      PFILE_RECORD_HEADER FileRecordWithIndex,
684                      /*PCWSTR IndexName,*/
685                      PNTFS_ATTR_CONTEXT IndexRootContext,
686                      PINDEX_ROOT_ATTRIBUTE IndexRoot,
687                      PB_TREE *NewTree)
688 {
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;
695     NTSTATUS Status;
696 
697     DPRINT("CreateBTreeFromIndex(%p, %p)\n", IndexRoot, NewTree);
698 
699     if (!Tree || !RootNode || !CurrentKey)
700     {
701         DPRINT1("Couldn't allocate enough memory for B-Tree!\n");
702         if (Tree)
703             ExFreePoolWithTag(Tree, TAG_NTFS);
704         if (CurrentKey)
705             ExFreePoolWithTag(CurrentKey, TAG_NTFS);
706         if (RootNode)
707             ExFreePoolWithTag(RootNode, TAG_NTFS);
708         return STATUS_INSUFFICIENT_RESOURCES;
709     }
710 
711     RtlZeroMemory(Tree, sizeof(B_TREE));
712     RtlZeroMemory(RootNode, sizeof(B_TREE_FILENAME_NODE));
713     RtlZeroMemory(CurrentKey, sizeof(B_TREE_KEY));
714 
715     // See if the file record has an attribute allocation
716     Status = FindAttribute(Vcb,
717                            FileRecordWithIndex,
718                            AttributeIndexAllocation,
719                            L"$I30",
720                            4,
721                            &IndexAllocationContext,
722                            NULL);
723     if (!NT_SUCCESS(Status))
724         IndexAllocationContext = NULL;
725 
726     // Setup the Tree
727     RootNode->FirstKey = CurrentKey;
728     Tree->RootNode = RootNode;
729 
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)
732     {
733         DPRINT1("Filesystem corruption detected!\n");
734         DestroyBTree(Tree);
735         Status = STATUS_FILE_CORRUPT_ERROR;
736         goto Cleanup;
737     }
738 
739     // Start at the first node entry
740     CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexRoot
741                                                 + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header)
742                                                 + IndexRoot->Header.FirstEntryOffset);
743 
744     // Create a key for each entry in the node
745     while (CurrentOffset < IndexRoot->Header.TotalSizeOfEntries)
746     {
747         // Allocate memory for the current entry
748         CurrentKey->IndexEntry = ExAllocatePoolWithTag(NonPagedPool, CurrentNodeEntry->Length, TAG_NTFS);
749         if (!CurrentKey->IndexEntry)
750         {
751             DPRINT1("ERROR: Couldn't allocate memory for next key!\n");
752             DestroyBTree(Tree);
753             Status = STATUS_INSUFFICIENT_RESOURCES;
754             goto Cleanup;
755         }
756 
757         RootNode->KeyCount++;
758 
759         // If this isn't the last entry
760         if (!(CurrentNodeEntry->Flags & NTFS_INDEX_ENTRY_END))
761         {
762             // Create the next key
763             PB_TREE_KEY NextKey = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_KEY), TAG_NTFS);
764             if (!NextKey)
765             {
766                 DPRINT1("ERROR: Couldn't allocate memory for next key!\n");
767                 DestroyBTree(Tree);
768                 Status = STATUS_INSUFFICIENT_RESOURCES;
769                 goto Cleanup;
770             }
771 
772             RtlZeroMemory(NextKey, sizeof(B_TREE_KEY));
773 
774             // Add NextKey to the end of the list
775             CurrentKey->NextKey = NextKey;
776 
777             // Copy the current entry to its key
778             RtlCopyMemory(CurrentKey->IndexEntry, CurrentNodeEntry, CurrentNodeEntry->Length);
779 
780             // Does this key have a sub-node?
781             if (CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)
782             {
783                 // Create the child node
784                 CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb,
785                                                                        IndexRoot,
786                                                                        IndexAllocationContext,
787                                                                        CurrentKey->IndexEntry);
788                 if (!CurrentKey->LesserChild)
789                 {
790                     DPRINT1("ERROR: Couldn't create child node!\n");
791                     DestroyBTree(Tree);
792                     Status = STATUS_NOT_IMPLEMENTED;
793                     goto Cleanup;
794                 }
795             }
796 
797             // Advance to the next entry
798             CurrentOffset += CurrentNodeEntry->Length;
799             CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)CurrentNodeEntry + CurrentNodeEntry->Length);
800             CurrentKey = NextKey;
801         }
802         else
803         {
804             // Copy the final entry to its key
805             RtlCopyMemory(CurrentKey->IndexEntry, CurrentNodeEntry, CurrentNodeEntry->Length);
806             CurrentKey->NextKey = NULL;
807 
808             // Does this key have a sub-node?
809             if (CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)
810             {
811                 // Create the child node
812                 CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb,
813                                                                        IndexRoot,
814                                                                        IndexAllocationContext,
815                                                                        CurrentKey->IndexEntry);
816                 if (!CurrentKey->LesserChild)
817                 {
818                     DPRINT1("ERROR: Couldn't create child node!\n");
819                     DestroyBTree(Tree);
820                     Status = STATUS_NOT_IMPLEMENTED;
821                     goto Cleanup;
822                 }
823             }
824 
825             break;
826         }
827     }
828 
829     *NewTree = Tree;
830     Status = STATUS_SUCCESS;
831 
832 Cleanup:
833     if (IndexAllocationContext)
834         ReleaseAttributeContext(IndexAllocationContext);
835 
836     return Status;
837 }
838 
839 /**
840 * @name GetSizeOfIndexEntries
841 * @implemented
842 *
843 * Sums the size of each index entry in every key in a B-Tree node.
844 *
845 * @param Node
846 * Pointer to a B_TREE_FILENAME_NODE. The size of this node's index entries will be returned.
847 *
848 * @returns
849 * The sum of the sizes of every index entry for each key in the B-Tree node.
850 *
851 * @remarks
852 * Gets only the size of the index entries; doesn't include the size of any headers that would be added to an index record.
853 */
854 ULONG
855 GetSizeOfIndexEntries(PB_TREE_FILENAME_NODE Node)
856 {
857     // Start summing the total size of this node's entries
858     ULONG NodeSize = 0;
859 
860     // Walk through the list of Node Entries
861     PB_TREE_KEY CurrentKey = Node->FirstKey;
862     ULONG i;
863     for (i = 0; i < Node->KeyCount; i++)
864     {
865         ASSERT(CurrentKey->IndexEntry->Length != 0);
866 
867         // Add the length of the current node
868         NodeSize += CurrentKey->IndexEntry->Length;
869         CurrentKey = CurrentKey->NextKey;
870     }
871 
872     return NodeSize;
873 }
874 
875 /**
876 * @name CreateIndexRootFromBTree
877 * @implemented
878 *
879 * Parse a B-Tree in memory and convert it into an index that can be written to disk.
880 *
881 * @param DeviceExt
882 * Pointer to the DEVICE_EXTENSION of the target drive.
883 *
884 * @param Tree
885 * Pointer to a B_TREE that describes the index to be written.
886 *
887 * @param MaxIndexSize
888 * Describes how large the index can be before it will take too much space in the file record.
889 * This is strictly the sum of the sizes of all index entries; it does not include the space
890 * required by the index root header (INDEX_ROOT_ATTRIBUTE), since that size will be constant.
891 *
892 * After reaching MaxIndexSize, an index can no longer be represented with just an index root
893 * attribute, and will require an index allocation and $I30 bitmap (TODO).
894 *
895 * @param IndexRoot
896 * Pointer to a PINDEX_ROOT_ATTRIBUTE that will receive a pointer to the newly-created index.
897 *
898 * @param Length
899 * Pointer to a ULONG which will receive the length of the new index root.
900 *
901 * @returns
902 * STATUS_SUCCESS on success.
903 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
904 * STATUS_NOT_IMPLEMENTED if the new index can't fit within MaxIndexSize.
905 *
906 * @remarks
907 * If the function succeeds, it's the caller's responsibility to free IndexRoot with ExFreePoolWithTag().
908 */
909 NTSTATUS
910 CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt,
911                          PB_TREE Tree,
912                          ULONG MaxIndexSize,
913                          PINDEX_ROOT_ATTRIBUTE *IndexRoot,
914                          ULONG *Length)
915 {
916     ULONG i;
917     PB_TREE_KEY CurrentKey;
918     PINDEX_ENTRY_ATTRIBUTE CurrentNodeEntry;
919     PINDEX_ROOT_ATTRIBUTE NewIndexRoot = ExAllocatePoolWithTag(NonPagedPool,
920                                                                DeviceExt->NtfsInfo.BytesPerFileRecord,
921                                                                TAG_NTFS);
922 
923     DPRINT("CreateIndexRootFromBTree(%p, %p, 0x%lx, %p, %p)\n", DeviceExt, Tree, MaxIndexSize, IndexRoot, Length);
924 
925 #ifndef NDEBUG
926     DumpBTree(Tree);
927 #endif
928 
929     if (!NewIndexRoot)
930     {
931         DPRINT1("Failed to allocate memory for Index Root!\n");
932         return STATUS_INSUFFICIENT_RESOURCES;
933     }
934 
935     // Setup the new index root
936     RtlZeroMemory(NewIndexRoot, DeviceExt->NtfsInfo.BytesPerFileRecord);
937 
938     NewIndexRoot->AttributeType = AttributeFileName;
939     NewIndexRoot->CollationRule = COLLATION_FILE_NAME;
940     NewIndexRoot->SizeOfEntry = DeviceExt->NtfsInfo.BytesPerIndexRecord;
941     // If Bytes per index record is less than cluster size, clusters per index record becomes sectors per index
942     if (NewIndexRoot->SizeOfEntry < DeviceExt->NtfsInfo.BytesPerCluster)
943         NewIndexRoot->ClustersPerIndexRecord = NewIndexRoot->SizeOfEntry / DeviceExt->NtfsInfo.BytesPerSector;
944     else
945         NewIndexRoot->ClustersPerIndexRecord = NewIndexRoot->SizeOfEntry / DeviceExt->NtfsInfo.BytesPerCluster;
946 
947     // Setup the Index node header
948     NewIndexRoot->Header.FirstEntryOffset = sizeof(INDEX_HEADER_ATTRIBUTE);
949     NewIndexRoot->Header.Flags = INDEX_ROOT_SMALL;
950 
951     // Start summing the total size of this node's entries
952     NewIndexRoot->Header.TotalSizeOfEntries = NewIndexRoot->Header.FirstEntryOffset;
953 
954     // Setup each Node Entry
955     CurrentKey = Tree->RootNode->FirstKey;
956     CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)NewIndexRoot
957                                                 + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header)
958                                                 + NewIndexRoot->Header.FirstEntryOffset);
959     for (i = 0; i < Tree->RootNode->KeyCount; i++)
960     {
961         // Would adding the current entry to the index increase the index size beyond the limit we've set?
962         ULONG IndexSize = NewIndexRoot->Header.TotalSizeOfEntries - NewIndexRoot->Header.FirstEntryOffset + CurrentKey->IndexEntry->Length;
963         if (IndexSize > MaxIndexSize)
964         {
965             DPRINT1("TODO: Adding file would require creating an attribute list!\n");
966             ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
967             return STATUS_NOT_IMPLEMENTED;
968         }
969 
970         ASSERT(CurrentKey->IndexEntry->Length != 0);
971 
972         // Copy the index entry
973         RtlCopyMemory(CurrentNodeEntry, CurrentKey->IndexEntry, CurrentKey->IndexEntry->Length);
974 
975         DPRINT1("Index Node Entry Stream Length: %u\nIndex Node Entry Length: %u\n",
976                 CurrentNodeEntry->KeyLength,
977                 CurrentNodeEntry->Length);
978 
979         // Does the current key have any sub-nodes?
980         if (CurrentKey->LesserChild)
981             NewIndexRoot->Header.Flags = INDEX_ROOT_LARGE;
982 
983         // Add Length of Current Entry to Total Size of Entries
984         NewIndexRoot->Header.TotalSizeOfEntries += CurrentKey->IndexEntry->Length;
985 
986         // Go to the next node entry
987         CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)CurrentNodeEntry + CurrentNodeEntry->Length);
988 
989         CurrentKey = CurrentKey->NextKey;
990     }
991 
992     NewIndexRoot->Header.AllocatedSize = NewIndexRoot->Header.TotalSizeOfEntries;
993 
994     *IndexRoot = NewIndexRoot;
995     *Length = NewIndexRoot->Header.AllocatedSize + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header);
996 
997     return STATUS_SUCCESS;
998 }
999 
1000 NTSTATUS
1001 CreateIndexBufferFromBTreeNode(PDEVICE_EXTENSION DeviceExt,
1002                                PB_TREE_FILENAME_NODE Node,
1003                                ULONG BufferSize,
1004                                BOOLEAN HasChildren,
1005                                PINDEX_BUFFER IndexBuffer)
1006 {
1007     ULONG i;
1008     PB_TREE_KEY CurrentKey;
1009     PINDEX_ENTRY_ATTRIBUTE CurrentNodeEntry;
1010     NTSTATUS Status;
1011 
1012     // TODO: Fix magic, do math
1013     RtlZeroMemory(IndexBuffer, BufferSize);
1014     IndexBuffer->Ntfs.Type = NRH_INDX_TYPE;
1015     IndexBuffer->Ntfs.UsaOffset = 0x28;
1016     IndexBuffer->Ntfs.UsaCount = 9;
1017 
1018     // TODO: Check bitmap for VCN
1019     ASSERT(Node->HasValidVCN);
1020     IndexBuffer->VCN = Node->VCN;
1021 
1022     // Windows seems to alternate between using 0x28 and 0x40 for the first entry offset of each index buffer.
1023     // Interestingly, neither Windows nor chkdsk seem to mind if we just use 0x28 for every index record.
1024     IndexBuffer->Header.FirstEntryOffset = 0x28;
1025     IndexBuffer->Header.AllocatedSize = BufferSize - FIELD_OFFSET(INDEX_BUFFER, Header);
1026 
1027     // Start summing the total size of this node's entries
1028     IndexBuffer->Header.TotalSizeOfEntries = IndexBuffer->Header.FirstEntryOffset;
1029 
1030     CurrentKey = Node->FirstKey;
1031     CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&(IndexBuffer->Header)
1032                                                 + IndexBuffer->Header.FirstEntryOffset);
1033     for (i = 0; i < Node->KeyCount; i++)
1034     {
1035         // Would adding the current entry to the index increase the node size beyond the allocation size?
1036         ULONG IndexSize = FIELD_OFFSET(INDEX_BUFFER, Header)
1037             + IndexBuffer->Header.TotalSizeOfEntries
1038             + CurrentNodeEntry->Length;
1039         if (IndexSize > BufferSize)
1040         {
1041             DPRINT1("TODO: Adding file would require creating a new node!\n");
1042             return STATUS_NOT_IMPLEMENTED;
1043         }
1044 
1045         ASSERT(CurrentKey->IndexEntry->Length != 0);
1046 
1047         // Copy the index entry
1048         RtlCopyMemory(CurrentNodeEntry, CurrentKey->IndexEntry, CurrentKey->IndexEntry->Length);
1049 
1050         DPRINT("Index Node Entry Stream Length: %u\nIndex Node Entry Length: %u\n",
1051                CurrentNodeEntry->KeyLength,
1052                CurrentNodeEntry->Length);
1053 
1054         // Add Length of Current Entry to Total Size of Entries
1055         IndexBuffer->Header.TotalSizeOfEntries += CurrentNodeEntry->Length;
1056 
1057         // Check for child nodes
1058         if (HasChildren)
1059             IndexBuffer->Header.Flags = INDEX_NODE_LARGE;
1060 
1061         // Go to the next node entry
1062         CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)CurrentNodeEntry + CurrentNodeEntry->Length);
1063         CurrentKey = CurrentKey->NextKey;
1064     }
1065 
1066     Status = AddFixupArray(DeviceExt, &IndexBuffer->Ntfs);
1067 
1068     return Status;
1069 }
1070 
1071 /**
1072 * @name DemoteBTreeRoot
1073 * @implemented
1074 *
1075 * Demoting the root means first putting all the keys in the root node into a new node, and making
1076 * the new node a child of a dummy key. The dummy key then becomes the sole contents of the root node.
1077 * The B-Tree gets one level deeper. This operation is needed when an index root grows too large for its file record.
1078 * Demotion is my own term; I might change the name later if I think of something more descriptive or can find
1079 * an appropriate name for this operation in existing B-Tree literature.
1080 *
1081 * @param Tree
1082 * Pointer to the B_TREE whose root is being demoted
1083 *
1084 * @returns
1085 * STATUS_SUCCESS on success.
1086 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
1087 */
1088 NTSTATUS
1089 DemoteBTreeRoot(PB_TREE Tree)
1090 {
1091     PB_TREE_FILENAME_NODE NewSubNode, NewIndexRoot;
1092     PB_TREE_KEY DummyKey;
1093 
1094     DPRINT("Collapsing Index Root into sub-node.\n");
1095 
1096 #ifndef NDEBUG
1097     DumpBTree(Tree);
1098 #endif
1099 
1100     // Create a new node that will hold the keys currently in index root
1101     NewSubNode = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_FILENAME_NODE), TAG_NTFS);
1102     if (!NewSubNode)
1103     {
1104         DPRINT1("ERROR: Couldn't allocate memory for new sub-node.\n");
1105         return STATUS_INSUFFICIENT_RESOURCES;
1106     }
1107     RtlZeroMemory(NewSubNode, sizeof(B_TREE_FILENAME_NODE));
1108 
1109     // Copy the applicable data from the old index root node
1110     NewSubNode->KeyCount = Tree->RootNode->KeyCount;
1111     NewSubNode->FirstKey = Tree->RootNode->FirstKey;
1112     NewSubNode->DiskNeedsUpdating = TRUE;
1113 
1114     // Create a new dummy key, and make the new node it's child
1115     DummyKey = CreateDummyKey(TRUE);
1116     if (!DummyKey)
1117     {
1118         DPRINT1("ERROR: Couldn't allocate memory for new root node.\n");
1119         ExFreePoolWithTag(NewSubNode, TAG_NTFS);
1120         return STATUS_INSUFFICIENT_RESOURCES;
1121     }
1122 
1123     // Make the new node a child of the dummy key
1124     DummyKey->LesserChild = NewSubNode;
1125 
1126     // Create a new index root node
1127     NewIndexRoot = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_FILENAME_NODE), TAG_NTFS);
1128     if (!NewIndexRoot)
1129     {
1130         DPRINT1("ERROR: Couldn't allocate memory for new index root.\n");
1131         ExFreePoolWithTag(NewSubNode, TAG_NTFS);
1132         ExFreePoolWithTag(DummyKey, TAG_NTFS);
1133         return STATUS_INSUFFICIENT_RESOURCES;
1134     }
1135     RtlZeroMemory(NewIndexRoot, sizeof(B_TREE_FILENAME_NODE));
1136 
1137     NewIndexRoot->DiskNeedsUpdating = TRUE;
1138 
1139     // Insert the dummy key into the new node
1140     NewIndexRoot->FirstKey = DummyKey;
1141     NewIndexRoot->KeyCount = 1;
1142     NewIndexRoot->DiskNeedsUpdating = TRUE;
1143 
1144     // Make the new node the Tree's root node
1145     Tree->RootNode = NewIndexRoot;
1146 
1147 #ifndef NDEBUG
1148     DumpBTree(Tree);
1149 #endif
1150 
1151     return STATUS_SUCCESS;
1152 }
1153 
1154 /**
1155 * @name SetIndexEntryVCN
1156 * @implemented
1157 *
1158 * Sets the VCN of a given IndexEntry.
1159 *
1160 * @param IndexEntry
1161 * Pointer to an INDEX_ENTRY_ATTRIBUTE structure that will have its VCN set.
1162 *
1163 * @param VCN
1164 * VCN to store in the index entry.
1165 *
1166 * @remarks
1167 * The index entry must have enough memory allocated to store the VCN, and must have the NTFS_INDEX_ENTRY_NODE flag set.
1168 * The VCN of an index entry is stored at the very end of the structure, after the filename attribute. Since the filename
1169 * attribute can be a variable size, this function makes setting this member easy.
1170 */
1171 VOID
1172 SetIndexEntryVCN(PINDEX_ENTRY_ATTRIBUTE IndexEntry, ULONGLONG VCN)
1173 {
1174     PULONGLONG Destination = (PULONGLONG)((ULONG_PTR)IndexEntry + IndexEntry->Length - sizeof(ULONGLONG));
1175 
1176     ASSERT(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE);
1177 
1178     *Destination = VCN;
1179 }
1180 
1181 NTSTATUS
1182 UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt,
1183                       PB_TREE Tree,
1184                       ULONG IndexBufferSize,
1185                       PFILE_RECORD_HEADER FileRecord)
1186 {
1187     // Find the index allocation and bitmap
1188     PNTFS_ATTR_CONTEXT IndexAllocationContext;
1189     PB_TREE_KEY CurrentKey;
1190     NTSTATUS Status;
1191     BOOLEAN HasIndexAllocation = FALSE;
1192     ULONG i;
1193     ULONG IndexAllocationOffset;
1194 
1195     DPRINT("UpdateIndexAllocation() called.\n");
1196 
1197     Status = FindAttribute(DeviceExt, FileRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationContext, &IndexAllocationOffset);
1198     if (NT_SUCCESS(Status))
1199     {
1200         HasIndexAllocation = TRUE;
1201 
1202 #ifndef NDEBUG
1203         PrintAllVCNs(DeviceExt,
1204                      IndexAllocationContext,
1205                      IndexBufferSize);
1206 #endif
1207     }
1208     // Walk through the root node and update all the sub-nodes
1209     CurrentKey = Tree->RootNode->FirstKey;
1210     for (i = 0; i < Tree->RootNode->KeyCount; i++)
1211     {
1212         if (CurrentKey->LesserChild)
1213         {
1214             if (!HasIndexAllocation)
1215             {
1216                 // We need to add an index allocation to the file record
1217                 PNTFS_ATTR_RECORD EndMarker = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->BytesInUse - (sizeof(ULONG) * 2));
1218                 DPRINT1("Adding index allocation...\n");
1219 
1220                 // Add index allocation to the very end of the file record
1221                 Status = AddIndexAllocation(DeviceExt,
1222                                             FileRecord,
1223                                             EndMarker,
1224                                             L"$I30",
1225                                             4);
1226                 if (!NT_SUCCESS(Status))
1227                 {
1228                     DPRINT1("ERROR: Failed to add index allocation!\n");
1229                     return Status;
1230                 }
1231 
1232                 // Find the new attribute
1233                 Status = FindAttribute(DeviceExt, FileRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationContext, &IndexAllocationOffset);
1234                 if (!NT_SUCCESS(Status))
1235                 {
1236                     DPRINT1("ERROR: Couldn't find newly-created index allocation!\n");
1237                     return Status;
1238                 }
1239 
1240                 // Advance end marker
1241                 EndMarker = (PNTFS_ATTR_RECORD)((ULONG_PTR)EndMarker + EndMarker->Length);
1242 
1243                 // Add index bitmap to the very end of the file record
1244                 Status = AddBitmap(DeviceExt,
1245                                    FileRecord,
1246                                    EndMarker,
1247                                    L"$I30",
1248                                    4);
1249                 if (!NT_SUCCESS(Status))
1250                 {
1251                     DPRINT1("ERROR: Failed to add index bitmap!\n");
1252                     ReleaseAttributeContext(IndexAllocationContext);
1253                     return Status;
1254                 }
1255 
1256                 HasIndexAllocation = TRUE;
1257             }
1258 
1259             // Is the Index Entry large enough to store the VCN?
1260             if (!BooleanFlagOn(CurrentKey->IndexEntry->Flags, NTFS_INDEX_ENTRY_NODE))
1261             {
1262                 // Allocate memory for the larger index entry
1263                 PINDEX_ENTRY_ATTRIBUTE NewEntry = ExAllocatePoolWithTag(NonPagedPool,
1264                                                                         CurrentKey->IndexEntry->Length + sizeof(ULONGLONG),
1265                                                                         TAG_NTFS);
1266                 if (!NewEntry)
1267                 {
1268                     DPRINT1("ERROR: Unable to allocate memory for new index entry!\n");
1269                     if (HasIndexAllocation)
1270                         ReleaseAttributeContext(IndexAllocationContext);
1271                     return STATUS_INSUFFICIENT_RESOURCES;
1272                 }
1273 
1274                 // Copy the old entry to the new one
1275                 RtlCopyMemory(NewEntry, CurrentKey->IndexEntry, CurrentKey->IndexEntry->Length);
1276 
1277                 NewEntry->Length += sizeof(ULONGLONG);
1278 
1279                 // Free the old memory
1280                 ExFreePoolWithTag(CurrentKey->IndexEntry, TAG_NTFS);
1281 
1282                 CurrentKey->IndexEntry = NewEntry;
1283                 CurrentKey->IndexEntry->Flags |= NTFS_INDEX_ENTRY_NODE;
1284             }
1285 
1286             // Update the sub-node
1287             Status = UpdateIndexNode(DeviceExt, FileRecord, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, IndexAllocationOffset);
1288             if (!NT_SUCCESS(Status))
1289             {
1290                 DPRINT1("ERROR: Failed to update index node!\n");
1291                 ReleaseAttributeContext(IndexAllocationContext);
1292                 return Status;
1293             }
1294 
1295             // Update the VCN stored in the index entry of CurrentKey
1296             SetIndexEntryVCN(CurrentKey->IndexEntry, CurrentKey->LesserChild->VCN);
1297         }
1298         CurrentKey = CurrentKey->NextKey;
1299     }
1300 
1301 #ifndef NDEBUG
1302     DumpBTree(Tree);
1303 #endif
1304 
1305     if (HasIndexAllocation)
1306     {
1307 #ifndef NDEBUG
1308         PrintAllVCNs(DeviceExt,
1309                      IndexAllocationContext,
1310                      IndexBufferSize);
1311 #endif
1312         ReleaseAttributeContext(IndexAllocationContext);
1313     }
1314 
1315     return STATUS_SUCCESS;
1316 }
1317 
1318 NTSTATUS
1319 UpdateIndexNode(PDEVICE_EXTENSION DeviceExt,
1320                 PFILE_RECORD_HEADER FileRecord,
1321                 PB_TREE_FILENAME_NODE Node,
1322                 ULONG IndexBufferSize,
1323                 PNTFS_ATTR_CONTEXT IndexAllocationContext,
1324                 ULONG IndexAllocationOffset)
1325 {
1326     ULONG i;
1327     PB_TREE_KEY CurrentKey = Node->FirstKey;
1328     BOOLEAN HasChildren = FALSE;
1329     NTSTATUS Status;
1330 
1331 
1332     DPRINT("UpdateIndexNode(%p, %p, %p, %lu, %p, %lu) called for index node with VCN %I64u\n",
1333            DeviceExt,
1334            FileRecord,
1335            Node,
1336            IndexBufferSize,
1337            IndexAllocationContext,
1338            IndexAllocationOffset,
1339            Node->VCN);
1340 
1341     // Walk through the node and look for children to update
1342     for (i = 0; i < Node->KeyCount; i++)
1343     {
1344         ASSERT(CurrentKey);
1345 
1346         // If there's a child node
1347         if (CurrentKey->LesserChild)
1348         {
1349             HasChildren = TRUE;
1350 
1351             // Update the child node on disk
1352             Status = UpdateIndexNode(DeviceExt, FileRecord, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, IndexAllocationOffset);
1353             if (!NT_SUCCESS(Status))
1354             {
1355                 DPRINT1("ERROR: Failed to update child node!\n");
1356                 return Status;
1357             }
1358 
1359             // Is the Index Entry large enough to store the VCN?
1360             if (!BooleanFlagOn(CurrentKey->IndexEntry->Flags, NTFS_INDEX_ENTRY_NODE))
1361             {
1362                 // Allocate memory for the larger index entry
1363                 PINDEX_ENTRY_ATTRIBUTE NewEntry = ExAllocatePoolWithTag(NonPagedPool,
1364                                                                         CurrentKey->IndexEntry->Length + sizeof(ULONGLONG),
1365                                                                         TAG_NTFS);
1366                 if (!NewEntry)
1367                 {
1368                     DPRINT1("ERROR: Unable to allocate memory for new index entry!\n");
1369                     return STATUS_INSUFFICIENT_RESOURCES;
1370                 }
1371 
1372                 // Copy the old entry to the new one
1373                 RtlCopyMemory(NewEntry, CurrentKey->IndexEntry, CurrentKey->IndexEntry->Length);
1374 
1375                 NewEntry->Length += sizeof(ULONGLONG);
1376 
1377                 // Free the old memory
1378                 ExFreePoolWithTag(CurrentKey->IndexEntry, TAG_NTFS);
1379 
1380                 CurrentKey->IndexEntry = NewEntry;
1381             }
1382 
1383             // Update the VCN stored in the index entry of CurrentKey
1384             SetIndexEntryVCN(CurrentKey->IndexEntry, CurrentKey->LesserChild->VCN);
1385 
1386             CurrentKey->IndexEntry->Flags |= NTFS_INDEX_ENTRY_NODE;
1387         }
1388 
1389         CurrentKey = CurrentKey->NextKey;
1390     }
1391 
1392 
1393     // Do we need to write this node to disk?
1394     if (Node->DiskNeedsUpdating)
1395     {
1396         ULONGLONG NodeOffset;
1397         ULONG LengthWritten;
1398         PINDEX_BUFFER IndexBuffer;
1399 
1400         // Does the node need to be assigned a VCN?
1401         if (!Node->HasValidVCN)
1402         {
1403             // Allocate the node
1404             Status = AllocateIndexNode(DeviceExt,
1405                                        FileRecord,
1406                                        IndexBufferSize,
1407                                        IndexAllocationContext,
1408                                        IndexAllocationOffset,
1409                                        &Node->VCN);
1410             if (!NT_SUCCESS(Status))
1411             {
1412                 DPRINT1("ERROR: Failed to allocate index record in index allocation!\n");
1413                 return Status;
1414             }
1415 
1416             Node->HasValidVCN = TRUE;
1417         }
1418 
1419         // Allocate memory for an index buffer
1420         IndexBuffer = ExAllocatePoolWithTag(NonPagedPool, IndexBufferSize, TAG_NTFS);
1421         if (!IndexBuffer)
1422         {
1423             DPRINT1("ERROR: Failed to allocate %lu bytes for index buffer!\n", IndexBufferSize);
1424             return STATUS_INSUFFICIENT_RESOURCES;
1425         }
1426 
1427         // Create the index buffer we'll be writing to disk to represent this node
1428         Status = CreateIndexBufferFromBTreeNode(DeviceExt, Node, IndexBufferSize, HasChildren, IndexBuffer);
1429         if (!NT_SUCCESS(Status))
1430         {
1431             DPRINT1("ERROR: Failed to create index buffer from node!\n");
1432             ExFreePoolWithTag(IndexBuffer, TAG_NTFS);
1433             return Status;
1434         }
1435 
1436         // Get Offset of index buffer in index allocation
1437         NodeOffset = GetAllocationOffsetFromVCN(DeviceExt, IndexBufferSize, Node->VCN);
1438 
1439         // Write the buffer to the index allocation
1440         Status = WriteAttribute(DeviceExt, IndexAllocationContext, NodeOffset, (const PUCHAR)IndexBuffer, IndexBufferSize, &LengthWritten, FileRecord);
1441         if (!NT_SUCCESS(Status) || LengthWritten != IndexBufferSize)
1442         {
1443             DPRINT1("ERROR: Failed to update index allocation!\n");
1444             ExFreePoolWithTag(IndexBuffer, TAG_NTFS);
1445             if (!NT_SUCCESS(Status))
1446                 return Status;
1447             else
1448                 return STATUS_END_OF_FILE;
1449         }
1450 
1451         Node->DiskNeedsUpdating = FALSE;
1452 
1453         // Free the index buffer
1454         ExFreePoolWithTag(IndexBuffer, TAG_NTFS);
1455     }
1456 
1457     return STATUS_SUCCESS;
1458 }
1459 
1460 PB_TREE_KEY
1461 CreateBTreeKeyFromFilename(ULONGLONG FileReference, PFILENAME_ATTRIBUTE FileNameAttribute)
1462 {
1463     PB_TREE_KEY NewKey;
1464     ULONG AttributeSize = GetFileNameAttributeLength(FileNameAttribute);
1465     ULONG EntrySize = ALIGN_UP_BY(AttributeSize + FIELD_OFFSET(INDEX_ENTRY_ATTRIBUTE, FileName), 8);
1466 
1467     // Create a new Index Entry for the file
1468     PINDEX_ENTRY_ATTRIBUTE NewEntry = ExAllocatePoolWithTag(NonPagedPool, EntrySize, TAG_NTFS);
1469     if (!NewEntry)
1470     {
1471         DPRINT1("ERROR: Failed to allocate memory for Index Entry!\n");
1472         return NULL;
1473     }
1474 
1475     // Setup the Index Entry
1476     RtlZeroMemory(NewEntry, EntrySize);
1477     NewEntry->Data.Directory.IndexedFile = FileReference;
1478     NewEntry->Length = EntrySize;
1479     NewEntry->KeyLength = AttributeSize;
1480 
1481     // Copy the FileNameAttribute
1482     RtlCopyMemory(&NewEntry->FileName, FileNameAttribute, AttributeSize);
1483 
1484     // Setup the New Key
1485     NewKey = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_KEY), TAG_NTFS);
1486     if (!NewKey)
1487     {
1488         DPRINT1("ERROR: Failed to allocate memory for new key!\n");
1489         ExFreePoolWithTag(NewEntry, TAG_NTFS);
1490         return NULL;
1491     }
1492     NewKey->IndexEntry = NewEntry;
1493     NewKey->NextKey = NULL;
1494 
1495     return NewKey;
1496 }
1497 
1498 VOID
1499 DestroyBTreeKey(PB_TREE_KEY Key)
1500 {
1501     if (Key->IndexEntry)
1502         ExFreePoolWithTag(Key->IndexEntry, TAG_NTFS);
1503 
1504     if (Key->LesserChild)
1505         DestroyBTreeNode(Key->LesserChild);
1506 
1507     ExFreePoolWithTag(Key, TAG_NTFS);
1508 }
1509 
1510 VOID
1511 DestroyBTreeNode(PB_TREE_FILENAME_NODE Node)
1512 {
1513     PB_TREE_KEY NextKey;
1514     PB_TREE_KEY CurrentKey = Node->FirstKey;
1515     ULONG i;
1516     for (i = 0; i < Node->KeyCount; i++)
1517     {
1518         NT_ASSERT(CurrentKey);
1519         NextKey = CurrentKey->NextKey;
1520         DestroyBTreeKey(CurrentKey);
1521         CurrentKey = NextKey;
1522     }
1523 
1524     NT_ASSERT(NextKey == NULL);
1525 
1526     ExFreePoolWithTag(Node, TAG_NTFS);
1527 }
1528 
1529 /**
1530 * @name DestroyBTree
1531 * @implemented
1532 *
1533 * Destroys a B-Tree.
1534 *
1535 * @param Tree
1536 * Pointer to the B_TREE which will be destroyed.
1537 *
1538 * @remarks
1539 * Destroys every bit of data stored in the tree.
1540 */
1541 VOID
1542 DestroyBTree(PB_TREE Tree)
1543 {
1544     DestroyBTreeNode(Tree->RootNode);
1545     ExFreePoolWithTag(Tree, TAG_NTFS);
1546 }
1547 
1548 VOID
1549 DumpBTreeKey(PB_TREE Tree, PB_TREE_KEY Key, ULONG Number, ULONG Depth)
1550 {
1551     ULONG i;
1552     for (i = 0; i < Depth; i++)
1553         DbgPrint(" ");
1554     DbgPrint(" Key #%d", Number);
1555 
1556     if (!(Key->IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
1557     {
1558         UNICODE_STRING FileName;
1559         FileName.Length = Key->IndexEntry->FileName.NameLength * sizeof(WCHAR);
1560         FileName.MaximumLength = FileName.Length;
1561         FileName.Buffer = Key->IndexEntry->FileName.Name;
1562         DbgPrint(" '%wZ'\n", &FileName);
1563     }
1564     else
1565     {
1566         DbgPrint(" (Dummy Key)\n");
1567     }
1568 
1569     // Is there a child node?
1570     if (Key->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)
1571     {
1572         if (Key->LesserChild)
1573             DumpBTreeNode(Tree, Key->LesserChild, Number, Depth + 1);
1574         else
1575         {
1576             // This will be an assert once nodes with arbitrary depth are debugged
1577             DPRINT1("DRIVER ERROR: No Key->LesserChild despite Key->IndexEntry->Flags indicating this is a node!\n");
1578         }
1579     }
1580 }
1581 
1582 VOID
1583 DumpBTreeNode(PB_TREE Tree,
1584               PB_TREE_FILENAME_NODE Node,
1585               ULONG Number,
1586               ULONG Depth)
1587 {
1588     PB_TREE_KEY CurrentKey;
1589     ULONG i;
1590     for (i = 0; i < Depth; i++)
1591         DbgPrint(" ");
1592     DbgPrint("Node #%d, Depth %d, has %d key%s", Number, Depth, Node->KeyCount, Node->KeyCount == 1 ? "" : "s");
1593 
1594     if (Node->HasValidVCN)
1595         DbgPrint(" VCN: %I64u\n", Node->VCN);
1596     else if (Tree->RootNode == Node)
1597         DbgPrint(" Index Root");
1598     else
1599         DbgPrint(" NOT ASSIGNED VCN YET\n");
1600 
1601     CurrentKey = Node->FirstKey;
1602     for (i = 0; i < Node->KeyCount; i++)
1603     {
1604         DumpBTreeKey(Tree, CurrentKey, i, Depth);
1605         CurrentKey = CurrentKey->NextKey;
1606     }
1607 }
1608 
1609 /**
1610 * @name DumpBTree
1611 * @implemented
1612 *
1613 * Displays a B-Tree.
1614 *
1615 * @param Tree
1616 * Pointer to the B_TREE which will be displayed.
1617 *
1618 * @remarks
1619 * Displays a diagnostic summary of a B_TREE.
1620 */
1621 VOID
1622 DumpBTree(PB_TREE Tree)
1623 {
1624     DbgPrint("B_TREE @ %p\n", Tree);
1625     DumpBTreeNode(Tree, Tree->RootNode, 0, 0);
1626 }
1627 
1628 // Calculates start of Index Buffer relative to the index allocation, given the node's VCN
1629 ULONGLONG
1630 GetAllocationOffsetFromVCN(PDEVICE_EXTENSION DeviceExt,
1631                            ULONG IndexBufferSize,
1632                            ULONGLONG Vcn)
1633 {
1634     if (IndexBufferSize < DeviceExt->NtfsInfo.BytesPerCluster)
1635         return Vcn * DeviceExt->NtfsInfo.BytesPerSector;
1636 
1637     return Vcn * DeviceExt->NtfsInfo.BytesPerCluster;
1638 }
1639 
1640 ULONGLONG
1641 GetIndexEntryVCN(PINDEX_ENTRY_ATTRIBUTE IndexEntry)
1642 {
1643     PULONGLONG Destination = (PULONGLONG)((ULONG_PTR)IndexEntry + IndexEntry->Length - sizeof(ULONGLONG));
1644 
1645     ASSERT(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE);
1646 
1647     return *Destination;
1648 }
1649 
1650 /**
1651 * @name NtfsInsertKey
1652 * @implemented
1653 *
1654 * Inserts a FILENAME_ATTRIBUTE into a B-Tree node.
1655 *
1656 * @param Tree
1657 * Pointer to the B_TREE the key (filename attribute) is being inserted into.
1658 *
1659 * @param FileReference
1660 * Reference number to the file being added. This will be a combination of the MFT index and update sequence number.
1661 *
1662 * @param FileNameAttribute
1663 * Pointer to a FILENAME_ATTRIBUTE which is the data for the key that will be added to the tree. A copy will be made.
1664 *
1665 * @param Node
1666 * Pointer to a B_TREE_FILENAME_NODE into which a new key will be inserted, in order.
1667 *
1668 * @param CaseSensitive
1669 * Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
1670 * if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag.
1671 *
1672 * @param MaxIndexRootSize
1673 * The maximum size, in bytes, of node entries that can be stored in the index root before it will grow too large for
1674 * the file record. This number is just the size of the entries, without any headers for the attribute or index root.
1675 *
1676 * @param IndexRecordSize
1677 * The size, in bytes, of an index record for this index. AKA an index buffer. Usually set to 4096.
1678 *
1679 * @param MedianKey
1680 * 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.
1681 * Will be set to NULL if the node isn't split.
1682 *
1683 * @param NewRightHandSibling
1684 * Pointer to a PB_TREE_FILENAME_NODE that will receive a pointer to a newly-created right-hand sibling node,
1685 * should the node grow too large and need to be split. Will be set to NULL if the node isn't split.
1686 *
1687 * @remarks
1688 * A node is always sorted, with the least comparable filename stored first and a dummy key to mark the end.
1689 */
1690 NTSTATUS
1691 NtfsInsertKey(PB_TREE Tree,
1692               ULONGLONG FileReference,
1693               PFILENAME_ATTRIBUTE FileNameAttribute,
1694               PB_TREE_FILENAME_NODE Node,
1695               BOOLEAN CaseSensitive,
1696               ULONG MaxIndexRootSize,
1697               ULONG IndexRecordSize,
1698               PB_TREE_KEY *MedianKey,
1699               PB_TREE_FILENAME_NODE *NewRightHandSibling)
1700 {
1701     PB_TREE_KEY NewKey, CurrentKey, PreviousKey;
1702     NTSTATUS Status = STATUS_SUCCESS;
1703     ULONG NodeSize;
1704     ULONG AllocatedNodeSize;
1705     ULONG MaxNodeSizeWithoutHeader;
1706     ULONG i;
1707 
1708     *MedianKey = NULL;
1709     *NewRightHandSibling = NULL;
1710 
1711     DPRINT("NtfsInsertKey(%p, 0x%I64x, %p, %p, %s, %lu, %lu, %p, %p)\n",
1712            Tree,
1713            FileReference,
1714            FileNameAttribute,
1715            Node,
1716            CaseSensitive ? "TRUE" : "FALSE",
1717            MaxIndexRootSize,
1718            IndexRecordSize,
1719            MedianKey,
1720            NewRightHandSibling);
1721 
1722     // Create the key for the filename attribute
1723     NewKey = CreateBTreeKeyFromFilename(FileReference, FileNameAttribute);
1724     if (!NewKey)
1725         return STATUS_INSUFFICIENT_RESOURCES;
1726 
1727     // Find where to insert the key
1728     CurrentKey = Node->FirstKey;
1729     PreviousKey = NULL;
1730     for (i = 0; i < Node->KeyCount; i++)
1731     {
1732         // Should the New Key go before the current key?
1733         LONG Comparison = CompareTreeKeys(NewKey, CurrentKey, CaseSensitive);
1734 
1735         if (Comparison == 0)
1736         {
1737             DPRINT1("\t\tComparison == 0: %.*S\n", NewKey->IndexEntry->FileName.NameLength, NewKey->IndexEntry->FileName.Name);
1738             DPRINT1("\t\tComparison == 0: %.*S\n", CurrentKey->IndexEntry->FileName.NameLength, CurrentKey->IndexEntry->FileName.Name);
1739         }
1740         ASSERT(Comparison != 0);
1741 
1742         // Is NewKey < CurrentKey?
1743         if (Comparison < 0)
1744         {
1745             // Does CurrentKey have a sub-node?
1746             if (CurrentKey->LesserChild)
1747             {
1748                 PB_TREE_KEY NewLeftKey;
1749                 PB_TREE_FILENAME_NODE NewChild;
1750 
1751                 // Insert the key into the child node
1752                 Status = NtfsInsertKey(Tree,
1753                                        FileReference,
1754                                        FileNameAttribute,
1755                                        CurrentKey->LesserChild,
1756                                        CaseSensitive,
1757                                        MaxIndexRootSize,
1758                                        IndexRecordSize,
1759                                        &NewLeftKey,
1760                                        &NewChild);
1761                 if (!NT_SUCCESS(Status))
1762                 {
1763                     DPRINT1("ERROR: Failed to insert key.\n");
1764                     ExFreePoolWithTag(NewKey, TAG_NTFS);
1765                     return Status;
1766                 }
1767 
1768                 // Did the child node get split?
1769                 if (NewLeftKey)
1770                 {
1771                     ASSERT(NewChild != NULL);
1772 
1773                     // Insert the new left key to the left of the current key
1774                     NewLeftKey->NextKey = CurrentKey;
1775 
1776                     // Is CurrentKey the first key?
1777                     if (!PreviousKey)
1778                         Node->FirstKey = NewLeftKey;
1779                     else
1780                         PreviousKey->NextKey = NewLeftKey;
1781 
1782                     // CurrentKey->LesserChild will be the right-hand sibling
1783                     CurrentKey->LesserChild = NewChild;
1784 
1785                     Node->KeyCount++;
1786                     Node->DiskNeedsUpdating = TRUE;
1787 
1788 #ifndef NDEBUG
1789                     DumpBTree(Tree);
1790 #endif
1791                 }
1792             }
1793             else
1794             {
1795                 // Insert New Key before Current Key
1796                 NewKey->NextKey = CurrentKey;
1797 
1798                 // Increase KeyCount and mark node as dirty
1799                 Node->KeyCount++;
1800                 Node->DiskNeedsUpdating = TRUE;
1801 
1802                 // was CurrentKey the first key?
1803                 if (CurrentKey == Node->FirstKey)
1804                     Node->FirstKey = NewKey;
1805                 else
1806                     PreviousKey->NextKey = NewKey;
1807             }
1808             break;
1809         }
1810 
1811         PreviousKey = CurrentKey;
1812         CurrentKey = CurrentKey->NextKey;
1813     }
1814 
1815     // Determine how much space the index entries will need
1816     NodeSize = GetSizeOfIndexEntries(Node);
1817 
1818     // Is Node not the root node?
1819     if (Node != Tree->RootNode)
1820     {
1821         // Calculate maximum size of index entries without any headers
1822         AllocatedNodeSize = IndexRecordSize - FIELD_OFFSET(INDEX_BUFFER, Header);
1823 
1824         // TODO: Replace magic with math
1825         MaxNodeSizeWithoutHeader = AllocatedNodeSize - 0x28;
1826 
1827         // Has the node grown larger than its allocated size?
1828         if (NodeSize > MaxNodeSizeWithoutHeader)
1829         {
1830             NTSTATUS Status;
1831 
1832             Status = SplitBTreeNode(Tree, Node, MedianKey, NewRightHandSibling, CaseSensitive);
1833             if (!NT_SUCCESS(Status))
1834             {
1835                 DPRINT1("ERROR: Failed to split B-Tree node!\n");
1836                 return Status;
1837             }
1838 
1839             return Status;
1840         }
1841     }
1842 
1843     // NewEntry and NewKey will be destroyed later by DestroyBTree()
1844 
1845     return Status;
1846 }
1847 
1848 
1849 
1850 /**
1851 * @name SplitBTreeNode
1852 * @implemented
1853 *
1854 * Splits a B-Tree node that has grown too large. Finds the median key and sets up a right-hand-sibling
1855 * node to contain the keys to the right of the median key.
1856 *
1857 * @param Tree
1858 * Pointer to the B_TREE which contains the node being split
1859 *
1860 * @param Node
1861 * Pointer to the B_TREE_FILENAME_NODE that needs to be split
1862 *
1863 * @param MedianKey
1864 * Pointer a PB_TREE_KEY that will receive the pointer to the key in the middle of the node being split
1865 *
1866 * @param NewRightHandSibling
1867 * Pointer to a PB_TREE_FILENAME_NODE that will receive a pointer to a newly-created B_TREE_FILENAME_NODE
1868 * containing the keys to the right of MedianKey.
1869 *
1870 * @param CaseSensitive
1871 * Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
1872 * if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag.
1873 *
1874 * @return
1875 * STATUS_SUCCESS on success.
1876 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
1877 *
1878 * @remarks
1879 * It's the responsibility of the caller to insert the new median key into the parent node, as well as making the
1880 * NewRightHandSibling the lesser child of the node that is currently Node's parent.
1881 */
1882 NTSTATUS
1883 SplitBTreeNode(PB_TREE Tree,
1884                PB_TREE_FILENAME_NODE Node,
1885                PB_TREE_KEY *MedianKey,
1886                PB_TREE_FILENAME_NODE *NewRightHandSibling,
1887                BOOLEAN CaseSensitive)
1888 {
1889     ULONG MedianKeyIndex;
1890     PB_TREE_KEY LastKeyBeforeMedian, FirstKeyAfterMedian;
1891     ULONG KeyCount;
1892     ULONG HalfSize;
1893     ULONG SizeSum;
1894     ULONG i;
1895 
1896     DPRINT("SplitBTreeNode(%p, %p, %p, %p, %s) called\n",
1897             Tree,
1898             Node,
1899             MedianKey,
1900             NewRightHandSibling,
1901             CaseSensitive ? "TRUE" : "FALSE");
1902 
1903 #ifndef NDEBUG
1904     DumpBTreeNode(Tree, Node, 0, 0);
1905 #endif
1906 
1907     // Create the right hand sibling
1908     *NewRightHandSibling = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_FILENAME_NODE), TAG_NTFS);
1909     if (*NewRightHandSibling == NULL)
1910     {
1911         DPRINT1("Error: Failed to allocate memory for right hand sibling!\n");
1912         return STATUS_INSUFFICIENT_RESOURCES;
1913     }
1914     RtlZeroMemory(*NewRightHandSibling, sizeof(B_TREE_FILENAME_NODE));
1915     (*NewRightHandSibling)->DiskNeedsUpdating = TRUE;
1916 
1917 
1918     // Find the last key before the median
1919 
1920     // This is roughly how NTFS-3G calculates median, and it's not congruent with what Windows does:
1921     /*
1922     // find the median key index
1923     MedianKeyIndex = (Node->KeyCount + 1) / 2;
1924     MedianKeyIndex--;
1925 
1926     LastKeyBeforeMedian = Node->FirstKey;
1927     for (i = 0; i < MedianKeyIndex - 1; i++)
1928         LastKeyBeforeMedian = LastKeyBeforeMedian->NextKey;*/
1929 
1930     // The method we'll use is a little bit closer to how Windows determines the median but it's not identical.
1931     // What Windows does is actually more complicated than this, I think because Windows allocates more slack space to Odd-numbered
1932     // Index Records, leaving less room for index entries in these records (I haven't discovered why this is done).
1933     // (Neither Windows nor chkdsk complain if we choose a different median than Windows would have chosen, as our median will be in the ballpark)
1934 
1935     // Use size to locate the median key / index
1936     LastKeyBeforeMedian = Node->FirstKey;
1937     MedianKeyIndex = 0;
1938     HalfSize = 2016; // half the allocated size after subtracting the first index entry offset (TODO: MATH)
1939     SizeSum = 0;
1940     for (i = 0; i < Node->KeyCount; i++)
1941     {
1942         SizeSum += LastKeyBeforeMedian->IndexEntry->Length;
1943 
1944         if (SizeSum > HalfSize)
1945             break;
1946 
1947         MedianKeyIndex++;
1948         LastKeyBeforeMedian = LastKeyBeforeMedian->NextKey;
1949     }
1950 
1951     // Now we can get the median key and the key that follows it
1952     *MedianKey = LastKeyBeforeMedian->NextKey;
1953     FirstKeyAfterMedian = (*MedianKey)->NextKey;
1954 
1955     DPRINT1("%lu keys, %lu median\n", Node->KeyCount, MedianKeyIndex);
1956     DPRINT1("\t\tMedian: %.*S\n", (*MedianKey)->IndexEntry->FileName.NameLength, (*MedianKey)->IndexEntry->FileName.Name);
1957 
1958     // "Node" will be the left hand sibling after the split, containing all keys prior to the median key
1959 
1960     // We need to create a dummy pointer at the end of the LHS. The dummy's child will be the median's child.
1961     LastKeyBeforeMedian->NextKey = CreateDummyKey(BooleanFlagOn((*MedianKey)->IndexEntry->Flags, NTFS_INDEX_ENTRY_NODE));
1962     if (LastKeyBeforeMedian->NextKey == NULL)
1963     {
1964         DPRINT1("Error: Couldn't allocate dummy key!\n");
1965         LastKeyBeforeMedian->NextKey = *MedianKey;
1966         ExFreePoolWithTag(*NewRightHandSibling, TAG_NTFS);
1967         return STATUS_INSUFFICIENT_RESOURCES;
1968     }
1969 
1970     // Did the median key have a child node?
1971     if ((*MedianKey)->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)
1972     {
1973         // Set the child of the new dummy key
1974         LastKeyBeforeMedian->NextKey->LesserChild = (*MedianKey)->LesserChild;
1975 
1976         // Give the dummy key's index entry the same sub-node VCN the median
1977         SetIndexEntryVCN(LastKeyBeforeMedian->NextKey->IndexEntry, GetIndexEntryVCN((*MedianKey)->IndexEntry));
1978     }
1979     else
1980     {
1981         // Median key didn't have a child node, but it will. Create a new index entry large enough to store a VCN.
1982         PINDEX_ENTRY_ATTRIBUTE NewIndexEntry = ExAllocatePoolWithTag(NonPagedPool,
1983                                                                      (*MedianKey)->IndexEntry->Length + sizeof(ULONGLONG),
1984                                                                      TAG_NTFS);
1985         if (!NewIndexEntry)
1986         {
1987             DPRINT1("Unable to allocate memory for new index entry!\n");
1988             LastKeyBeforeMedian->NextKey = *MedianKey;
1989             ExFreePoolWithTag(*NewRightHandSibling, TAG_NTFS);
1990             return STATUS_INSUFFICIENT_RESOURCES;
1991         }
1992 
1993         // Copy the old index entry to the new one
1994         RtlCopyMemory(NewIndexEntry, (*MedianKey)->IndexEntry, (*MedianKey)->IndexEntry->Length);
1995 
1996         // Use the new index entry after freeing the old one
1997         ExFreePoolWithTag((*MedianKey)->IndexEntry, TAG_NTFS);
1998         (*MedianKey)->IndexEntry = NewIndexEntry;
1999 
2000         // Update the length for the VCN
2001         (*MedianKey)->IndexEntry->Length += sizeof(ULONGLONG);
2002 
2003         // Set the node flag
2004         (*MedianKey)->IndexEntry->Flags |= NTFS_INDEX_ENTRY_NODE;
2005     }
2006 
2007     // "Node" will become the child of the median key
2008     (*MedianKey)->LesserChild = Node;
2009     SetIndexEntryVCN((*MedianKey)->IndexEntry, Node->VCN);
2010 
2011     // Update Node's KeyCount (remember to add 1 for the new dummy key)
2012     Node->KeyCount = MedianKeyIndex + 2;
2013 
2014     KeyCount = CountBTreeKeys(Node->FirstKey);
2015     ASSERT(Node->KeyCount == KeyCount);
2016 
2017     // everything to the right of MedianKey becomes the right hand sibling of Node
2018     (*NewRightHandSibling)->FirstKey = FirstKeyAfterMedian;
2019     (*NewRightHandSibling)->KeyCount = CountBTreeKeys(FirstKeyAfterMedian);
2020 
2021 #ifndef NDEBUG
2022     DPRINT1("Left-hand node after split:\n");
2023     DumpBTreeNode(Tree, Node, 0, 0);
2024 
2025     DPRINT1("Right-hand sibling node after split:\n");
2026     DumpBTreeNode(Tree, *NewRightHandSibling, 0, 0);
2027 #endif
2028 
2029     return STATUS_SUCCESS;
2030 }
2031