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 DPRINT1("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 DPRINT1("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 DPRINT1("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 DPRINT1("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 DPRINT1("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 DPRINT1("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(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(Node, 0, 0); 2024 2025 DPRINT1("Right-hand sibling node after split:\n"); 2026 DumpBTreeNode(*NewRightHandSibling, 0, 0); 2027 #endif 2028 2029 return STATUS_SUCCESS; 2030 } 2031