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
PrintAllVCNs(PDEVICE_EXTENSION Vcb,PNTFS_ATTR_CONTEXT IndexAllocationContext,ULONG NodeSize)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
AllocateIndexNode(PDEVICE_EXTENSION DeviceExt,PFILE_RECORD_HEADER FileRecord,ULONG IndexBufferSize,PNTFS_ATTR_CONTEXT IndexAllocationCtx,ULONG IndexAllocationOffset,PULONGLONG NewVCN)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
CreateDummyKey(BOOLEAN HasChildNode)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
CreateEmptyBTree(PB_TREE * NewTree)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
CompareTreeKeys(PB_TREE_KEY Key1,PB_TREE_KEY Key2,BOOLEAN CaseSensitive)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
CountBTreeKeys(PB_TREE_KEY FirstKey)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
CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb,PINDEX_ROOT_ATTRIBUTE IndexRoot,PNTFS_ATTR_CONTEXT IndexAllocationAttributeCtx,PINDEX_ENTRY_ATTRIBUTE NodeEntry)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
CreateBTreeFromIndex(PDEVICE_EXTENSION Vcb,PFILE_RECORD_HEADER FileRecordWithIndex,PNTFS_ATTR_CONTEXT IndexRootContext,PINDEX_ROOT_ATTRIBUTE IndexRoot,PB_TREE * NewTree)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
GetSizeOfIndexEntries(PB_TREE_FILENAME_NODE Node)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
CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt,PB_TREE Tree,ULONG MaxIndexSize,PINDEX_ROOT_ATTRIBUTE * IndexRoot,ULONG * Length)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
CreateIndexBufferFromBTreeNode(PDEVICE_EXTENSION DeviceExt,PB_TREE_FILENAME_NODE Node,ULONG BufferSize,BOOLEAN HasChildren,PINDEX_BUFFER IndexBuffer)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
DemoteBTreeRoot(PB_TREE Tree)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
SetIndexEntryVCN(PINDEX_ENTRY_ATTRIBUTE IndexEntry,ULONGLONG VCN)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
UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt,PB_TREE Tree,ULONG IndexBufferSize,PFILE_RECORD_HEADER FileRecord)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
UpdateIndexNode(PDEVICE_EXTENSION DeviceExt,PFILE_RECORD_HEADER FileRecord,PB_TREE_FILENAME_NODE Node,ULONG IndexBufferSize,PNTFS_ATTR_CONTEXT IndexAllocationContext,ULONG IndexAllocationOffset)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
CreateBTreeKeyFromFilename(ULONGLONG FileReference,PFILENAME_ATTRIBUTE FileNameAttribute)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
DestroyBTreeKey(PB_TREE_KEY Key)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
DestroyBTreeNode(PB_TREE_FILENAME_NODE Node)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
DestroyBTree(PB_TREE Tree)1542 DestroyBTree(PB_TREE Tree)
1543 {
1544 DestroyBTreeNode(Tree->RootNode);
1545 ExFreePoolWithTag(Tree, TAG_NTFS);
1546 }
1547
1548 VOID
DumpBTreeKey(PB_TREE Tree,PB_TREE_KEY Key,ULONG Number,ULONG Depth)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
DumpBTreeNode(PB_TREE Tree,PB_TREE_FILENAME_NODE Node,ULONG Number,ULONG Depth)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
DumpBTree(PB_TREE Tree)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
GetAllocationOffsetFromVCN(PDEVICE_EXTENSION DeviceExt,ULONG IndexBufferSize,ULONGLONG Vcn)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
GetIndexEntryVCN(PINDEX_ENTRY_ATTRIBUTE IndexEntry)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
NtfsInsertKey(PB_TREE Tree,ULONGLONG FileReference,PFILENAME_ATTRIBUTE FileNameAttribute,PB_TREE_FILENAME_NODE Node,BOOLEAN CaseSensitive,ULONG MaxIndexRootSize,ULONG IndexRecordSize,PB_TREE_KEY * MedianKey,PB_TREE_FILENAME_NODE * NewRightHandSibling)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
SplitBTreeNode(PB_TREE Tree,PB_TREE_FILENAME_NODE Node,PB_TREE_KEY * MedianKey,PB_TREE_FILENAME_NODE * NewRightHandSibling,BOOLEAN CaseSensitive)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