3 * Copyright (C) 2002, 2017 ReactOS Team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
19 * COPYRIGHT: See COPYING in the top level directory
20 * PROJECT: ReactOS kernel
21 * FILE: drivers/filesystem/ntfs/btree.c
22 * PURPOSE: NTFS filesystem driver
23 * PROGRAMMERS: Trevor Thompson
26 /* INCLUDES *****************************************************************/
33 /* FUNCTIONS ****************************************************************/
35 // TEMP FUNCTION for diagnostic purposes.
36 // Prints VCN of every node in an index allocation
38 PrintAllVCNs(PDEVICE_EXTENSION Vcb
,
39 PNTFS_ATTR_CONTEXT IndexAllocationContext
,
42 ULONGLONG CurrentOffset
= 0;
43 PINDEX_BUFFER CurrentNode
, Buffer
;
44 ULONGLONG BufferSize
= AttributeDataLength(&IndexAllocationContext
->Record
);
49 Buffer
= ExAllocatePoolWithTag(NonPagedPool
, BufferSize
, TAG_NTFS
);
51 BytesRead
= ReadAttribute(Vcb
, IndexAllocationContext
, 0, (PCHAR
)Buffer
, BufferSize
);
53 ASSERT(BytesRead
= BufferSize
);
57 // loop through all the nodes
58 for (i
= 0; i
< BufferSize
; i
+= NodeSize
)
60 NTSTATUS Status
= FixupUpdateSequenceArray(Vcb
, &CurrentNode
->Ntfs
);
61 if (!NT_SUCCESS(Status
))
63 DPRINT1("ERROR: Fixing fixup failed!\n");
67 DPRINT1("Node #%d, VCN: %I64u\n", Count
, CurrentNode
->VCN
);
69 CurrentNode
= (PINDEX_BUFFER
)((ULONG_PTR
)CurrentNode
+ NodeSize
);
70 CurrentOffset
+= NodeSize
;
74 ExFreePoolWithTag(Buffer
, TAG_NTFS
);
78 * @name CompareTreeKeys
81 * Compare two B_TREE_KEY's to determine their order in the tree.
84 * Pointer to a B_TREE_KEY that will be compared.
87 * Pointer to the other B_TREE_KEY that will be compared.
89 * @param CaseSensitive
90 * Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
91 * if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag.
94 * 0 if the two keys are equal.
95 * < 0 if key1 is less thank key2
96 * > 0 if key1 is greater than key2
99 * Any other key is always less than the final (dummy) key in a node. Key1 must not be the dummy node.
102 CompareTreeKeys(PB_TREE_KEY Key1
, PB_TREE_KEY Key2
, BOOLEAN CaseSensitive
)
104 UNICODE_STRING Key1Name
, Key2Name
;
107 // Key1 must not be the final key (AKA the dummy key)
108 ASSERT(!(Key1
->IndexEntry
->Flags
& NTFS_INDEX_ENTRY_END
));
110 // If Key2 is the "dummy key", key 1 will always come first
111 if (Key2
->NextKey
== NULL
)
114 Key1Name
.Buffer
= Key1
->IndexEntry
->FileName
.Name
;
115 Key1Name
.Length
= Key1Name
.MaximumLength
116 = Key1
->IndexEntry
->FileName
.NameLength
* sizeof(WCHAR
);
118 Key2Name
.Buffer
= Key2
->IndexEntry
->FileName
.Name
;
119 Key2Name
.Length
= Key2Name
.MaximumLength
120 = Key2
->IndexEntry
->FileName
.NameLength
* sizeof(WCHAR
);
122 // Are the two keys the same length?
123 if (Key1Name
.Length
== Key2Name
.Length
)
124 return RtlCompareUnicodeString(&Key1Name
, &Key2Name
, !CaseSensitive
);
127 if (Key1Name
.Length
< Key2Name
.Length
)
129 // Truncate KeyName2 to be the same length as KeyName1
130 Key2Name
.Length
= Key1Name
.Length
;
132 // Compare the names of the same length
133 Comparison
= RtlCompareUnicodeString(&Key1Name
, &Key2Name
, !CaseSensitive
);
135 // If the truncated names are the same length, the shorter one comes first
142 // Truncate KeyName1 to be the same length as KeyName2
143 Key1Name
.Length
= Key2Name
.Length
;
145 // Compare the names of the same length
146 Comparison
= RtlCompareUnicodeString(&Key1Name
, &Key2Name
, !CaseSensitive
);
148 // If the truncated names are the same length, the shorter one comes first
156 PB_TREE_FILENAME_NODE
157 CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb
,
158 PINDEX_ROOT_ATTRIBUTE IndexRoot
,
159 PNTFS_ATTR_CONTEXT IndexAllocationAttributeCtx
,
160 PINDEX_ENTRY_ATTRIBUTE NodeEntry
)
162 PB_TREE_FILENAME_NODE NewNode
;
163 PINDEX_ENTRY_ATTRIBUTE CurrentNodeEntry
;
164 PINDEX_ENTRY_ATTRIBUTE FirstNodeEntry
;
165 ULONG CurrentEntryOffset
= 0;
166 PINDEX_BUFFER NodeBuffer
;
167 ULONG IndexBufferSize
= Vcb
->NtfsInfo
.BytesPerIndexRecord
;
168 PULONGLONG NodeNumber
;
169 PB_TREE_KEY CurrentKey
;
171 ULONGLONG IndexNodeOffset
;
174 if (IndexAllocationAttributeCtx
== NULL
)
176 DPRINT1("ERROR: Couldn't find index allocation attribute even though there should be one!\n");
180 // Get the node number from the end of the node entry
181 NodeNumber
= (PULONGLONG
)((ULONG_PTR
)NodeEntry
+ NodeEntry
->Length
- sizeof(ULONGLONG
));
183 // Create the new tree node
184 DPRINT1("About to allocate %ld for NewNode\n", sizeof(B_TREE_FILENAME_NODE
));
185 NewNode
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE_FILENAME_NODE
), TAG_NTFS
);
188 DPRINT1("ERROR: Couldn't allocate memory for new filename node.\n");
191 RtlZeroMemory(NewNode
, sizeof(B_TREE_FILENAME_NODE
));
193 // Create the first key
194 CurrentKey
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE_KEY
), TAG_NTFS
);
197 DPRINT1("ERROR: Failed to allocate memory for key!\n");
198 ExFreePoolWithTag(NewNode
, TAG_NTFS
);
201 RtlZeroMemory(CurrentKey
, sizeof(B_TREE_KEY
));
202 NewNode
->FirstKey
= CurrentKey
;
204 // Allocate memory for the node buffer
205 NodeBuffer
= ExAllocatePoolWithTag(NonPagedPool
, IndexBufferSize
, TAG_NTFS
);
208 DPRINT1("ERROR: Couldn't allocate memory for node buffer!\n");
209 ExFreePoolWithTag(CurrentKey
, TAG_NTFS
);
210 ExFreePoolWithTag(NewNode
, TAG_NTFS
);
214 // Calculate offset into index allocation
215 IndexNodeOffset
= GetAllocationOffsetFromVCN(Vcb
, IndexBufferSize
, *NodeNumber
);
217 // TODO: Confirm index bitmap has this node marked as in-use
220 BytesRead
= ReadAttribute(Vcb
,
221 IndexAllocationAttributeCtx
,
226 ASSERT(BytesRead
== IndexBufferSize
);
227 NT_ASSERT(NodeBuffer
->Ntfs
.Type
== NRH_INDX_TYPE
);
228 NT_ASSERT(NodeBuffer
->VCN
== *NodeNumber
);
230 // Apply the fixup array to the node buffer
231 Status
= FixupUpdateSequenceArray(Vcb
, &NodeBuffer
->Ntfs
);
232 if (!NT_SUCCESS(Status
))
234 DPRINT1("ERROR: Couldn't apply fixup array to index node buffer!\n");
235 ExFreePoolWithTag(NodeBuffer
, TAG_NTFS
);
236 ExFreePoolWithTag(CurrentKey
, TAG_NTFS
);
237 ExFreePoolWithTag(NewNode
, TAG_NTFS
);
241 // Walk through the index and create keys for all the entries
242 FirstNodeEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)(&NodeBuffer
->Header
)
243 + NodeBuffer
->Header
.FirstEntryOffset
);
244 CurrentNodeEntry
= FirstNodeEntry
;
245 while (CurrentEntryOffset
< NodeBuffer
->Header
.TotalSizeOfEntries
)
247 // Allocate memory for the current entry
248 CurrentKey
->IndexEntry
= ExAllocatePoolWithTag(NonPagedPool
, CurrentNodeEntry
->Length
, TAG_NTFS
);
249 if (!CurrentKey
->IndexEntry
)
251 DPRINT1("ERROR: Couldn't allocate memory for next key!\n");
252 DestroyBTreeNode(NewNode
);
253 ExFreePoolWithTag(NodeBuffer
, TAG_NTFS
);
259 // If this isn't the last entry
260 if (!(CurrentNodeEntry
->Flags
& NTFS_INDEX_ENTRY_END
))
262 // Create the next key
263 PB_TREE_KEY NextKey
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE_KEY
), TAG_NTFS
);
266 DPRINT1("ERROR: Couldn't allocate memory for next key!\n");
267 DestroyBTreeNode(NewNode
);
268 ExFreePoolWithTag(NodeBuffer
, TAG_NTFS
);
271 RtlZeroMemory(NextKey
, sizeof(B_TREE_KEY
));
273 // Add NextKey to the end of the list
274 CurrentKey
->NextKey
= NextKey
;
276 // Copy the current entry to its key
277 RtlCopyMemory(CurrentKey
->IndexEntry
, CurrentNodeEntry
, CurrentNodeEntry
->Length
);
279 // See if the current key has a sub-node
280 if (CurrentKey
->IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
)
282 DPRINT1("TODO: Only a node with a single-level is supported right now!\n");
284 /*CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb,
286 IndexAllocationAttributeCtx,
290 CurrentKey
= NextKey
;
294 // Copy the final entry to its key
295 RtlCopyMemory(CurrentKey
->IndexEntry
, CurrentNodeEntry
, CurrentNodeEntry
->Length
);
296 CurrentKey
->NextKey
= NULL
;
298 // See if the current key has a sub-node
299 if (CurrentKey
->IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
)
301 DPRINT1("TODO: Only a node with a single-level is supported right now!\n");
303 /*CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb,
305 IndexAllocationAttributeCtx,
312 // Advance to the next entry
313 CurrentEntryOffset
+= CurrentNodeEntry
->Length
;
314 CurrentNodeEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)CurrentNodeEntry
+ CurrentNodeEntry
->Length
);
317 NewNode
->NodeNumber
= *NodeNumber
;
318 NewNode
->ExistsOnDisk
= TRUE
;
320 ExFreePoolWithTag(NodeBuffer
, TAG_NTFS
);
326 * @name CreateBTreeFromIndex
329 * Parse an index and create a B-Tree in memory from it.
331 * @param IndexRootContext
332 * Pointer to an NTFS_ATTR_CONTEXT that describes the location of the index root attribute.
335 * Pointer to a PB_TREE that will receive the pointer to a newly-created B-Tree.
338 * STATUS_SUCCESS on success.
339 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
342 * Allocates memory for the entire tree. Caller is responsible for destroying the tree with DestroyBTree().
345 CreateBTreeFromIndex(PDEVICE_EXTENSION Vcb
,
346 PFILE_RECORD_HEADER FileRecordWithIndex
,
347 /*PCWSTR IndexName,*/
348 PNTFS_ATTR_CONTEXT IndexRootContext
,
349 PINDEX_ROOT_ATTRIBUTE IndexRoot
,
352 PINDEX_ENTRY_ATTRIBUTE CurrentNodeEntry
;
353 PB_TREE Tree
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE
), TAG_NTFS
);
354 PB_TREE_FILENAME_NODE RootNode
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE_FILENAME_NODE
), TAG_NTFS
);
355 PB_TREE_KEY CurrentKey
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE_KEY
), TAG_NTFS
);
356 ULONG CurrentOffset
= IndexRoot
->Header
.FirstEntryOffset
;
357 PNTFS_ATTR_CONTEXT IndexAllocationContext
= NULL
;
360 DPRINT1("CreateBTreeFromIndex(%p, %p)\n", IndexRoot
, NewTree
);
362 if (!Tree
|| !RootNode
|| !CurrentKey
)
364 DPRINT1("Couldn't allocate enough memory for B-Tree!\n");
366 ExFreePoolWithTag(Tree
, TAG_NTFS
);
368 ExFreePoolWithTag(CurrentKey
, TAG_NTFS
);
370 ExFreePoolWithTag(RootNode
, TAG_NTFS
);
371 return STATUS_INSUFFICIENT_RESOURCES
;
374 RtlZeroMemory(Tree
, sizeof(B_TREE
));
375 RtlZeroMemory(RootNode
, sizeof(B_TREE_FILENAME_NODE
));
376 RtlZeroMemory(CurrentKey
, sizeof(B_TREE_KEY
));
378 // See if the file record has an attribute allocation
379 Status
= FindAttribute(Vcb
,
381 AttributeIndexAllocation
,
384 &IndexAllocationContext
,
386 if (!NT_SUCCESS(Status
))
387 IndexAllocationContext
= NULL
;
389 PrintAllVCNs(Vcb
, IndexAllocationContext
, IndexRoot
->SizeOfEntry
);
392 RootNode
->FirstKey
= CurrentKey
;
393 Tree
->RootNode
= RootNode
;
395 // Make sure we won't try reading past the attribute-end
396 if (FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE
, Header
) + IndexRoot
->Header
.TotalSizeOfEntries
> IndexRootContext
->Record
.Resident
.ValueLength
)
398 DPRINT1("Filesystem corruption detected!\n");
400 return STATUS_FILE_CORRUPT_ERROR
;
403 // Start at the first node entry
404 CurrentNodeEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)IndexRoot
405 + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE
, Header
)
406 + IndexRoot
->Header
.FirstEntryOffset
);
408 // Create a key for each entry in the node
409 while (CurrentOffset
< IndexRoot
->Header
.TotalSizeOfEntries
)
411 // Allocate memory for the current entry
412 CurrentKey
->IndexEntry
= ExAllocatePoolWithTag(NonPagedPool
, CurrentNodeEntry
->Length
, TAG_NTFS
);
413 if (!CurrentKey
->IndexEntry
)
415 DPRINT1("ERROR: Couldn't allocate memory for next key!\n");
417 return STATUS_INSUFFICIENT_RESOURCES
;
420 RootNode
->KeyCount
++;
422 // If this isn't the last entry
423 if (!(CurrentNodeEntry
->Flags
& NTFS_INDEX_ENTRY_END
))
425 // Create the next key
426 PB_TREE_KEY NextKey
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE_KEY
), TAG_NTFS
);
429 DPRINT1("ERROR: Couldn't allocate memory for next key!\n");
431 return STATUS_INSUFFICIENT_RESOURCES
;
434 RtlZeroMemory(NextKey
, sizeof(B_TREE_KEY
));
436 // Add NextKey to the end of the list
437 CurrentKey
->NextKey
= NextKey
;
439 // Copy the current entry to its key
440 RtlCopyMemory(CurrentKey
->IndexEntry
, CurrentNodeEntry
, CurrentNodeEntry
->Length
);
442 // Does this key have a sub-node?
443 if (CurrentKey
->IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
)
445 // Create the child node
446 CurrentKey
->LesserChild
= CreateBTreeNodeFromIndexNode(Vcb
,
448 IndexAllocationContext
,
449 CurrentKey
->IndexEntry
);
450 if (!CurrentKey
->LesserChild
)
452 DPRINT1("ERROR: Couldn't create child node!\n");
454 return STATUS_NOT_IMPLEMENTED
;
458 // Advance to the next entry
459 CurrentOffset
+= CurrentNodeEntry
->Length
;
460 CurrentNodeEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)CurrentNodeEntry
+ CurrentNodeEntry
->Length
);
461 CurrentKey
= NextKey
;
465 // Copy the final entry to its key
466 RtlCopyMemory(CurrentKey
->IndexEntry
, CurrentNodeEntry
, CurrentNodeEntry
->Length
);
467 CurrentKey
->NextKey
= NULL
;
469 // Does this key have a sub-node?
470 if (CurrentKey
->IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
)
472 // Create the child node
473 CurrentKey
->LesserChild
= CreateBTreeNodeFromIndexNode(Vcb
,
475 IndexAllocationContext
,
476 CurrentKey
->IndexEntry
);
477 if (!CurrentKey
->LesserChild
)
479 DPRINT1("ERROR: Couldn't create child node!\n");
481 return STATUS_NOT_IMPLEMENTED
;
491 if (IndexAllocationContext
)
492 ReleaseAttributeContext(IndexAllocationContext
);
494 return STATUS_SUCCESS
;
498 * @name CreateIndexRootFromBTree
501 * Parse a B-Tree in memory and convert it into an index that can be written to disk.
504 * Pointer to the DEVICE_EXTENSION of the target drive.
507 * Pointer to a B_TREE that describes the index to be written.
509 * @param MaxIndexSize
510 * Describes how large the index can be before it will take too much space in the file record.
511 * After reaching MaxIndexSize, an index can no longer be represented with just an index root
512 * attribute, and will require an index allocation and $I30 bitmap (TODO).
515 * Pointer to a PINDEX_ROOT_ATTRIBUTE that will receive a pointer to the newly-created index.
518 * Pointer to a ULONG which will receive the length of the new index root.
521 * STATUS_SUCCESS on success.
522 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
523 * STATUS_NOT_IMPLEMENTED if the new index can't fit within MaxIndexSize.
526 * If the function succeeds, it's the caller's responsibility to free IndexRoot with ExFreePoolWithTag().
529 CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt
,
532 PINDEX_ROOT_ATTRIBUTE
*IndexRoot
,
536 PB_TREE_KEY CurrentKey
;
537 PINDEX_ENTRY_ATTRIBUTE CurrentNodeEntry
;
538 PINDEX_ROOT_ATTRIBUTE NewIndexRoot
= ExAllocatePoolWithTag(NonPagedPool
,
539 DeviceExt
->NtfsInfo
.BytesPerFileRecord
,
542 DPRINT1("CreateIndexRootFromBTree(%p, %p, 0x%lx, %p, %p)\n", DeviceExt
, Tree
, MaxIndexSize
, IndexRoot
, Length
);
546 DPRINT1("Failed to allocate memory for Index Root!\n");
547 return STATUS_INSUFFICIENT_RESOURCES
;
550 // Setup the new index root
551 RtlZeroMemory(NewIndexRoot
, DeviceExt
->NtfsInfo
.BytesPerFileRecord
);
553 NewIndexRoot
->AttributeType
= AttributeFileName
;
554 NewIndexRoot
->CollationRule
= COLLATION_FILE_NAME
;
555 NewIndexRoot
->SizeOfEntry
= DeviceExt
->NtfsInfo
.BytesPerIndexRecord
;
556 // If Bytes per index record is less than cluster size, clusters per index record becomes sectors per index
557 if (NewIndexRoot
->SizeOfEntry
< DeviceExt
->NtfsInfo
.BytesPerCluster
)
558 NewIndexRoot
->ClustersPerIndexRecord
= NewIndexRoot
->SizeOfEntry
/ DeviceExt
->NtfsInfo
.BytesPerSector
;
560 NewIndexRoot
->ClustersPerIndexRecord
= NewIndexRoot
->SizeOfEntry
/ DeviceExt
->NtfsInfo
.BytesPerCluster
;
562 // Setup the Index node header
563 NewIndexRoot
->Header
.FirstEntryOffset
= sizeof(INDEX_HEADER_ATTRIBUTE
);
564 NewIndexRoot
->Header
.Flags
= INDEX_ROOT_SMALL
;
566 // Start summing the total size of this node's entries
567 NewIndexRoot
->Header
.TotalSizeOfEntries
= NewIndexRoot
->Header
.FirstEntryOffset
;
569 // Setup each Node Entry
570 CurrentKey
= Tree
->RootNode
->FirstKey
;
571 CurrentNodeEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)NewIndexRoot
572 + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE
, Header
)
573 + NewIndexRoot
->Header
.FirstEntryOffset
);
574 for (i
= 0; i
< Tree
->RootNode
->KeyCount
; i
++)
576 // Would adding the current entry to the index increase the index size beyond the limit we've set?
577 ULONG IndexSize
= FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE
, Header
)
578 + NewIndexRoot
->Header
.FirstEntryOffset
579 + NewIndexRoot
->Header
.TotalSizeOfEntries
580 + CurrentNodeEntry
->Length
;
581 if (IndexSize
> MaxIndexSize
)
583 DPRINT1("TODO: Adding file would require creating an index allocation!\n");
584 ExFreePoolWithTag(NewIndexRoot
, TAG_NTFS
);
585 return STATUS_NOT_IMPLEMENTED
;
588 ASSERT(CurrentKey
->IndexEntry
->Length
!= 0);
590 // Copy the index entry
591 RtlCopyMemory(CurrentNodeEntry
, CurrentKey
->IndexEntry
, CurrentKey
->IndexEntry
->Length
);
593 DPRINT1("Index Node Entry Stream Length: %u\nIndex Node Entry Length: %u\n",
594 CurrentNodeEntry
->KeyLength
,
595 CurrentNodeEntry
->Length
);
597 // Does the current key have any sub-nodes?
598 if (CurrentKey
->LesserChild
)
599 NewIndexRoot
->Header
.Flags
= INDEX_ROOT_LARGE
;
601 // Add Length of Current Entry to Total Size of Entries
602 NewIndexRoot
->Header
.TotalSizeOfEntries
+= CurrentNodeEntry
->Length
;
604 // Go to the next node entry
605 CurrentNodeEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)CurrentNodeEntry
+ CurrentNodeEntry
->Length
);
607 CurrentKey
= CurrentKey
->NextKey
;
610 NewIndexRoot
->Header
.AllocatedSize
= NewIndexRoot
->Header
.TotalSizeOfEntries
;
612 *IndexRoot
= NewIndexRoot
;
613 *Length
= NewIndexRoot
->Header
.AllocatedSize
+ FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE
, Header
);
615 return STATUS_SUCCESS
;
619 CreateIndexBufferFromBTreeNode(PDEVICE_EXTENSION DeviceExt
,
620 PB_TREE_FILENAME_NODE Node
,
622 PINDEX_BUFFER IndexBuffer
)
625 PB_TREE_KEY CurrentKey
;
626 PINDEX_ENTRY_ATTRIBUTE CurrentNodeEntry
;
629 // TODO: Fix magic, do math
630 RtlZeroMemory(IndexBuffer
, BufferSize
);
631 IndexBuffer
->Ntfs
.Type
= NRH_INDX_TYPE
;
632 IndexBuffer
->Ntfs
.UsaOffset
= 0x28;
633 IndexBuffer
->Ntfs
.UsaCount
= 9;
635 // TODO: Check bitmap for VCN
636 ASSERT(Node
->ExistsOnDisk
);
637 IndexBuffer
->VCN
= Node
->NodeNumber
;
639 IndexBuffer
->Header
.FirstEntryOffset
= 0x28;
640 IndexBuffer
->Header
.AllocatedSize
= BufferSize
- FIELD_OFFSET(INDEX_BUFFER
, Header
);
642 // Start summing the total size of this node's entries
643 IndexBuffer
->Header
.TotalSizeOfEntries
= IndexBuffer
->Header
.FirstEntryOffset
;
645 CurrentKey
= Node
->FirstKey
;
646 CurrentNodeEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&(IndexBuffer
->Header
)
647 + IndexBuffer
->Header
.FirstEntryOffset
);
648 for (i
= 0; i
< Node
->KeyCount
; i
++)
650 // Would adding the current entry to the index increase the node size beyond the allocation size?
651 ULONG IndexSize
= FIELD_OFFSET(INDEX_BUFFER
, Header
)
652 + IndexBuffer
->Header
.FirstEntryOffset
653 + IndexBuffer
->Header
.TotalSizeOfEntries
654 + CurrentNodeEntry
->Length
;
655 if (IndexSize
> BufferSize
)
657 DPRINT1("TODO: Adding file would require creating a new node!\n");
658 return STATUS_NOT_IMPLEMENTED
;
661 ASSERT(CurrentKey
->IndexEntry
->Length
!= 0);
663 // Copy the index entry
664 RtlCopyMemory(CurrentNodeEntry
, CurrentKey
->IndexEntry
, CurrentKey
->IndexEntry
->Length
);
666 DPRINT1("Index Node Entry Stream Length: %u\nIndex Node Entry Length: %u\n",
667 CurrentNodeEntry
->KeyLength
,
668 CurrentNodeEntry
->Length
);
670 // Add Length of Current Entry to Total Size of Entries
671 IndexBuffer
->Header
.TotalSizeOfEntries
+= CurrentNodeEntry
->Length
;
673 // TODO: Check for child nodes
675 // Go to the next node entry
676 CurrentNodeEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)CurrentNodeEntry
+ CurrentNodeEntry
->Length
);
677 CurrentKey
= CurrentKey
->NextKey
;
680 Status
= AddFixupArray(DeviceExt
, &IndexBuffer
->Ntfs
);
686 UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt
,
688 ULONG IndexBufferSize
,
689 PFILE_RECORD_HEADER FileRecord
)
691 // Find the index allocation and bitmap
692 PNTFS_ATTR_CONTEXT IndexAllocationContext
, BitmapContext
;
693 PB_TREE_KEY CurrentKey
;
695 BOOLEAN HasIndexAllocation
= FALSE
;
698 DPRINT1("UpdateIndexAllocations() called.\n");
700 Status
= FindAttribute(DeviceExt
, FileRecord
, AttributeIndexAllocation
, L
"$I30", 4, &IndexAllocationContext
, NULL
);
701 if (NT_SUCCESS(Status
))
702 HasIndexAllocation
= TRUE
;
704 // TODO: Handle bitmap
705 BitmapContext
= NULL
;
707 // Walk through the root node and update all the sub-nodes
708 CurrentKey
= Tree
->RootNode
->FirstKey
;
709 for (i
= 0; i
< Tree
->RootNode
->KeyCount
; i
++)
711 if (CurrentKey
->LesserChild
)
713 if (!HasIndexAllocation
)
715 DPRINT1("FIXME: Need to add index allocation\n");
716 return STATUS_NOT_IMPLEMENTED
;
720 Status
= UpdateIndexNode(DeviceExt
, CurrentKey
->LesserChild
, IndexBufferSize
, IndexAllocationContext
, BitmapContext
);
721 if (!NT_SUCCESS(Status
))
723 DPRINT1("ERROR: Failed to update index node!\n");
724 ReleaseAttributeContext(IndexAllocationContext
);
730 CurrentKey
= CurrentKey
->NextKey
;
733 if(HasIndexAllocation
)
734 ReleaseAttributeContext(IndexAllocationContext
);
736 return STATUS_SUCCESS
;
740 UpdateIndexNode(PDEVICE_EXTENSION DeviceExt
,
741 PB_TREE_FILENAME_NODE Node
,
742 ULONG IndexBufferSize
,
743 PNTFS_ATTR_CONTEXT IndexAllocationContext
,
744 PNTFS_ATTR_CONTEXT BitmapContext
)
747 PB_TREE_KEY CurrentKey
= Node
->FirstKey
;
750 DPRINT1("UpdateIndexNode(%p, %p, %lu, %p, %p) called for index node with VCN %I64u\n", DeviceExt
, Node
, IndexBufferSize
, IndexAllocationContext
, BitmapContext
, Node
->NodeNumber
);
752 // Do we need to write this node to disk?
753 if (Node
->DiskNeedsUpdating
)
755 ULONGLONG NodeOffset
;
758 // Allocate memory for an index buffer
759 PINDEX_BUFFER IndexBuffer
= ExAllocatePoolWithTag(NonPagedPool
, IndexBufferSize
, TAG_NTFS
);
762 DPRINT1("ERROR: Failed to allocate %lu bytes for index buffer!\n", IndexBufferSize
);
763 return STATUS_INSUFFICIENT_RESOURCES
;
766 // Create the index buffer we'll be writing to disk to represent this node
767 Status
= CreateIndexBufferFromBTreeNode(DeviceExt
, Node
, IndexBufferSize
, IndexBuffer
);
768 if (!NT_SUCCESS(Status
))
770 DPRINT1("ERROR: Failed to create index buffer from node!\n");
771 ExFreePoolWithTag(IndexBuffer
, TAG_NTFS
);
775 // Get Offset of index buffer in index allocation
776 NodeOffset
= GetAllocationOffsetFromVCN(DeviceExt
, IndexBufferSize
, Node
->NodeNumber
);
778 // Write the buffer to the index allocation
779 Status
= WriteAttribute(DeviceExt
, IndexAllocationContext
, NodeOffset
, (const PUCHAR
)IndexBuffer
, IndexBufferSize
, &LengthWritten
);
780 if (!NT_SUCCESS(Status
) || LengthWritten
!= IndexBufferSize
)
782 DPRINT1("ERROR: Failed to update index allocation!\n");
783 ExFreePoolWithTag(IndexBuffer
, TAG_NTFS
);
784 if (!NT_SUCCESS(Status
))
787 return STATUS_END_OF_FILE
;
790 Node
->DiskNeedsUpdating
= FALSE
;
792 // Free the index buffer
793 ExFreePoolWithTag(IndexBuffer
, TAG_NTFS
);
796 // Walk through the node and look for children to update
797 for (i
= 0; i
< Node
->KeyCount
; i
++)
801 // If there's a child node
802 if (CurrentKey
->LesserChild
)
804 // Update the child node on disk
805 Status
= UpdateIndexNode(DeviceExt
, CurrentKey
->LesserChild
, IndexBufferSize
, IndexAllocationContext
, BitmapContext
);
806 if (!NT_SUCCESS(Status
))
808 DPRINT1("ERROR: Failed to update child node!\n");
813 CurrentKey
= CurrentKey
->NextKey
;
816 return STATUS_SUCCESS
;
820 CreateBTreeKeyFromFilename(ULONGLONG FileReference
, PFILENAME_ATTRIBUTE FileNameAttribute
)
823 ULONG AttributeSize
= GetFileNameAttributeLength(FileNameAttribute
);
824 ULONG EntrySize
= ALIGN_UP_BY(AttributeSize
+ FIELD_OFFSET(INDEX_ENTRY_ATTRIBUTE
, FileName
), 8);
826 // Create a new Index Entry for the file
827 PINDEX_ENTRY_ATTRIBUTE NewEntry
= ExAllocatePoolWithTag(NonPagedPool
, EntrySize
, TAG_NTFS
);
830 DPRINT1("ERROR: Failed to allocate memory for Index Entry!\n");
834 // Setup the Index Entry
835 RtlZeroMemory(NewEntry
, EntrySize
);
836 NewEntry
->Data
.Directory
.IndexedFile
= FileReference
;
837 NewEntry
->Length
= EntrySize
;
838 NewEntry
->KeyLength
= AttributeSize
;
840 // Copy the FileNameAttribute
841 RtlCopyMemory(&NewEntry
->FileName
, FileNameAttribute
, AttributeSize
);
844 NewKey
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE_KEY
), TAG_NTFS
);
847 DPRINT1("ERROR: Failed to allocate memory for new key!\n");
848 ExFreePoolWithTag(NewEntry
, TAG_NTFS
);
851 NewKey
->IndexEntry
= NewEntry
;
852 NewKey
->NextKey
= NULL
;
858 DestroyBTreeKey(PB_TREE_KEY Key
)
861 ExFreePoolWithTag(Key
->IndexEntry
, TAG_NTFS
);
863 if (Key
->LesserChild
)
864 DestroyBTreeNode(Key
->LesserChild
);
866 ExFreePoolWithTag(Key
, TAG_NTFS
);
870 DestroyBTreeNode(PB_TREE_FILENAME_NODE Node
)
873 PB_TREE_KEY CurrentKey
= Node
->FirstKey
;
875 for (i
= 0; i
< Node
->KeyCount
; i
++)
877 NT_ASSERT(CurrentKey
);
878 NextKey
= CurrentKey
->NextKey
;
879 DestroyBTreeKey(CurrentKey
);
880 CurrentKey
= NextKey
;
883 NT_ASSERT(NextKey
== NULL
);
885 ExFreePoolWithTag(Node
, TAG_NTFS
);
895 * Pointer to the B_TREE which will be destroyed.
898 * Destroys every bit of data stored in the tree.
901 DestroyBTree(PB_TREE Tree
)
903 DestroyBTreeNode(Tree
->RootNode
);
904 ExFreePoolWithTag(Tree
, TAG_NTFS
);
908 DumpBTreeKey(PB_TREE_KEY Key
, ULONG Number
, ULONG Depth
)
911 for (i
= 0; i
< Depth
; i
++)
913 DbgPrint(" Key #%d", Number
);
915 if (!(Key
->IndexEntry
->Flags
& NTFS_INDEX_ENTRY_END
))
917 UNICODE_STRING FileName
;
918 FileName
.Length
= Key
->IndexEntry
->FileName
.NameLength
* sizeof(WCHAR
);
919 FileName
.MaximumLength
= FileName
.Length
;
920 FileName
.Buffer
= Key
->IndexEntry
->FileName
.Name
;
921 DbgPrint(" '%wZ'\n", &FileName
);
925 DbgPrint(" (Dummy Key)\n");
928 // Is there a child node?
929 if (Key
->IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
)
931 if (Key
->LesserChild
)
932 DumpBTreeNode(Key
->LesserChild
, Number
, Depth
+ 1);
935 // This will be an assert once nodes with arbitrary depth are debugged
936 DPRINT1("DRIVER ERROR: No Key->LesserChild despite Key->IndexEntry->Flags indicating this is a node!\n");
942 DumpBTreeNode(PB_TREE_FILENAME_NODE Node
, ULONG Number
, ULONG Depth
)
944 PB_TREE_KEY CurrentKey
;
946 for (i
= 0; i
< Depth
; i
++)
948 DbgPrint("Node #%d, Depth %d, has %d keys\n", Number
, Depth
, Node
->KeyCount
);
950 CurrentKey
= Node
->FirstKey
;
951 for (i
= 0; i
< Node
->KeyCount
; i
++)
953 DumpBTreeKey(CurrentKey
, i
, Depth
);
954 CurrentKey
= CurrentKey
->NextKey
;
965 * Pointer to the B_TREE which will be displayed.
968 * Displays a diagnostic summary of a B_TREE.
971 DumpBTree(PB_TREE Tree
)
973 DbgPrint("B_TREE @ %p\n", Tree
);
974 DumpBTreeNode(Tree
->RootNode
, 0, 0);
977 // Calculates start of Index Buffer relative to the index allocation, given the node's VCN
979 GetAllocationOffsetFromVCN(PDEVICE_EXTENSION DeviceExt
,
980 ULONG IndexBufferSize
,
983 if (IndexBufferSize
< DeviceExt
->NtfsInfo
.BytesPerCluster
)
984 return Vcn
* DeviceExt
->NtfsInfo
.BytesPerSector
;
986 return Vcn
* DeviceExt
->NtfsInfo
.BytesPerCluster
;
990 * @name NtfsInsertKey
993 * Inserts a FILENAME_ATTRIBUTE into a B-Tree node.
995 * @param FileReference
996 * Reference number to the file being added. This will be a combination of the MFT index and update sequence number.
998 * @param FileNameAttribute
999 * Pointer to a FILENAME_ATTRIBUTE which is the data for the key that will be added to the tree. A copy will be made.
1002 * Pointer to a B_TREE_FILENAME_NODE into which a new key will be inserted, in order.
1004 * @param CaseSensitive
1005 * Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
1006 * if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag.
1009 * A node is always sorted, with the least comparable filename stored first and a dummy key to mark the end.
1012 NtfsInsertKey(ULONGLONG FileReference
,
1013 PFILENAME_ATTRIBUTE FileNameAttribute
,
1014 PB_TREE_FILENAME_NODE Node
,
1015 BOOLEAN CaseSensitive
)
1017 PB_TREE_KEY NewKey
, CurrentKey
, PreviousKey
;
1018 NTSTATUS Status
= STATUS_SUCCESS
;
1020 ULONG AllocatedNodeSize
;
1021 ULONG MaxNodeSizeWithoutHeader
;
1024 DPRINT1("NtfsInsertKey(0x%I64x, %p, %p, %s)\n",
1028 CaseSensitive
? "TRUE" : "FALSE");
1030 // Create the key for the filename attribute
1031 NewKey
= CreateBTreeKeyFromFilename(FileReference
, FileNameAttribute
);
1033 return STATUS_INSUFFICIENT_RESOURCES
;
1035 // Find where to insert the key
1036 CurrentKey
= Node
->FirstKey
;
1038 for (i
= 0; i
< Node
->KeyCount
; i
++)
1040 // Should the New Key go before the current key?
1041 LONG Comparison
= CompareTreeKeys(NewKey
, CurrentKey
, CaseSensitive
);
1043 ASSERT(Comparison
!= 0);
1045 // Is NewKey < CurrentKey?
1049 // Does CurrentKey have a sub-node?
1050 if (CurrentKey
->LesserChild
)
1052 // Insert the key into the child node
1053 Status
= NtfsInsertKey(FileReference
, FileNameAttribute
, CurrentKey
->LesserChild
, CaseSensitive
);
1057 // Insert New Key before Current Key
1058 NewKey
->NextKey
= CurrentKey
;
1060 // Increase KeyCount and mark node as dirty
1062 Node
->DiskNeedsUpdating
= TRUE
;
1064 // was CurrentKey the first key?
1065 if (CurrentKey
== Node
->FirstKey
)
1066 Node
->FirstKey
= NewKey
;
1068 PreviousKey
->NextKey
= NewKey
;
1073 PreviousKey
= CurrentKey
;
1074 CurrentKey
= CurrentKey
->NextKey
;
1077 // Is the node larger than its allocated size?
1079 CurrentKey
= Node
->FirstKey
;
1080 for (i
= 0; i
< Node
->KeyCount
; i
++)
1082 NodeSize
+= CurrentKey
->IndexEntry
->Length
;
1083 CurrentKey
= CurrentKey
->NextKey
;
1086 // TEMPTEMP: TODO: MATH
1087 AllocatedNodeSize
= 0xfe8;
1088 MaxNodeSizeWithoutHeader
= AllocatedNodeSize
- 0x28;
1090 if (NodeSize
> MaxNodeSizeWithoutHeader
)
1092 DPRINT1("FIXME: Splitting a node is still a WIP!\n");
1093 //SplitBTreeNode(NULL, Node);
1095 return STATUS_NOT_IMPLEMENTED
;
1098 // NewEntry and NewKey will be destroyed later by DestroyBTree()