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
);
48 Buffer
= ExAllocatePoolWithTag(NonPagedPool
, BufferSize
, TAG_NTFS
);
50 ULONG BytesRead
= ReadAttribute(Vcb
, IndexAllocationContext
, 0, (PCHAR
)Buffer
, BufferSize
);
52 ASSERT(BytesRead
= BufferSize
);
56 // loop through all the nodes
57 for (i
= 0; i
< BufferSize
; i
+= NodeSize
)
59 NTSTATUS Status
= FixupUpdateSequenceArray(Vcb
, &CurrentNode
->Ntfs
);
60 if (!NT_SUCCESS(Status
))
62 DPRINT1("ERROR: Fixing fixup failed!\n");
66 DPRINT1("Node #%d, VCN: %I64u\n", Count
, CurrentNode
->VCN
);
68 CurrentNode
= (PINDEX_BUFFER
)((ULONG_PTR
)CurrentNode
+ NodeSize
);
69 CurrentOffset
+= NodeSize
;
73 ExFreePoolWithTag(Buffer
, TAG_NTFS
);
77 * @name CompareTreeKeys
80 * Compare two B_TREE_KEY's to determine their order in the tree.
83 * Pointer to a B_TREE_KEY that will be compared.
86 * Pointer to the other B_TREE_KEY that will be compared.
88 * @param CaseSensitive
89 * Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
90 * if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag.
93 * 0 if the two keys are equal.
94 * < 0 if key1 is less thank key2
95 * > 0 if key1 is greater than key2
98 * Any other key is always less than the final (dummy) key in a node. Key1 must not be the dummy node.
101 CompareTreeKeys(PB_TREE_KEY Key1
, PB_TREE_KEY Key2
, BOOLEAN CaseSensitive
)
103 UNICODE_STRING Key1Name
, Key2Name
;
106 // Key1 must not be the final key (AKA the dummy key)
107 ASSERT(!(Key1
->IndexEntry
->Flags
& NTFS_INDEX_ENTRY_END
));
109 // If Key2 is the "dummy key", key 1 will always come first
110 if (Key2
->NextKey
== NULL
)
113 Key1Name
.Buffer
= Key1
->IndexEntry
->FileName
.Name
;
114 Key1Name
.Length
= Key1Name
.MaximumLength
115 = Key1
->IndexEntry
->FileName
.NameLength
* sizeof(WCHAR
);
117 Key2Name
.Buffer
= Key2
->IndexEntry
->FileName
.Name
;
118 Key2Name
.Length
= Key2Name
.MaximumLength
119 = Key2
->IndexEntry
->FileName
.NameLength
* sizeof(WCHAR
);
121 // Are the two keys the same length?
122 if (Key1Name
.Length
== Key2Name
.Length
)
123 return RtlCompareUnicodeString(&Key1Name
, &Key2Name
, !CaseSensitive
);
126 if (Key1Name
.Length
< Key2Name
.Length
)
128 // Truncate KeyName2 to be the same length as KeyName1
129 Key2Name
.Length
= Key1Name
.Length
;
131 // Compare the names of the same length
132 Comparison
= RtlCompareUnicodeString(&Key1Name
, &Key2Name
, !CaseSensitive
);
134 // If the truncated names are the same length, the shorter one comes first
141 // Truncate KeyName1 to be the same length as KeyName2
142 Key1Name
.Length
= Key2Name
.Length
;
144 // Compare the names of the same length
145 Comparison
= RtlCompareUnicodeString(&Key1Name
, &Key2Name
, !CaseSensitive
);
147 // If the truncated names are the same length, the shorter one comes first
156 * @name CreateBTreeFromIndex
159 * Parse an index and create a B-Tree in memory from it.
161 * @param IndexRootContext
162 * Pointer to an NTFS_ATTR_CONTEXT that describes the location of the index root attribute.
165 * Pointer to a PB_TREE that will receive the pointer to a newly-created B-Tree.
168 * STATUS_SUCCESS on success.
169 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
172 * Allocates memory for the entire tree. Caller is responsible for destroying the tree with DestroyBTree().
175 CreateBTreeFromIndex(PNTFS_ATTR_CONTEXT IndexRootContext
,
176 PINDEX_ROOT_ATTRIBUTE IndexRoot
,
179 PINDEX_ENTRY_ATTRIBUTE CurrentNodeEntry
;
180 PB_TREE Tree
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE
), TAG_NTFS
);
181 PB_TREE_FILENAME_NODE RootNode
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE_FILENAME_NODE
), TAG_NTFS
);
182 PB_TREE_KEY CurrentKey
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE_KEY
), TAG_NTFS
);
183 ULONG CurrentOffset
= IndexRoot
->Header
.FirstEntryOffset
;
185 DPRINT1("CreateBTreeFromIndex(%p, %p, %p)\n", IndexRootContext
, IndexRoot
, NewTree
);
187 if (!Tree
|| !RootNode
|| !CurrentKey
)
189 DPRINT1("Couldn't allocate enough memory for B-Tree!\n");
191 ExFreePoolWithTag(Tree
, TAG_NTFS
);
193 ExFreePoolWithTag(CurrentKey
, TAG_NTFS
);
195 ExFreePoolWithTag(RootNode
, TAG_NTFS
);
196 return STATUS_INSUFFICIENT_RESOURCES
;
199 RtlZeroMemory(Tree
, sizeof(B_TREE
));
200 RtlZeroMemory(RootNode
, sizeof(B_TREE_FILENAME_NODE
));
201 RtlZeroMemory(CurrentKey
, sizeof(B_TREE_KEY
));
204 RootNode
->FirstKey
= CurrentKey
;
205 Tree
->RootNode
= RootNode
;
207 // Make sure we won't try reading past the attribute-end
208 if (FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE
, Header
) + IndexRoot
->Header
.TotalSizeOfEntries
> IndexRootContext
->Record
.Resident
.ValueLength
)
210 DPRINT1("Filesystem corruption detected!\n");
212 return STATUS_FILE_CORRUPT_ERROR
;
215 // Start at the first node entry
216 CurrentNodeEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)IndexRoot
217 + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE
, Header
)
218 + IndexRoot
->Header
.FirstEntryOffset
);
220 // Create a key for each entry in the node
221 while (CurrentOffset
< IndexRoot
->Header
.TotalSizeOfEntries
)
223 // Allocate memory for the current entry
224 CurrentKey
->IndexEntry
= ExAllocatePoolWithTag(NonPagedPool
, CurrentNodeEntry
->Length
, TAG_NTFS
);
225 if (!CurrentKey
->IndexEntry
)
227 DPRINT1("ERROR: Couldn't allocate memory for next key!\n");
229 return STATUS_INSUFFICIENT_RESOURCES
;
232 RootNode
->KeyCount
++;
234 // If this isn't the last entry
235 if (!(CurrentNodeEntry
->Flags
& NTFS_INDEX_ENTRY_END
))
237 // Create the next key
238 PB_TREE_KEY NextKey
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(PB_TREE_KEY
), TAG_NTFS
);
241 DPRINT1("ERROR: Couldn't allocate memory for next key!\n");
243 return STATUS_INSUFFICIENT_RESOURCES
;
246 RtlZeroMemory(NextKey
, sizeof(PB_TREE_KEY
));
248 // Add NextKey to the end of the list
249 CurrentKey
->NextKey
= NextKey
;
251 // Copy the current entry to its key
252 RtlCopyMemory(CurrentKey
->IndexEntry
, CurrentNodeEntry
, CurrentNodeEntry
->Length
);
254 // Make sure this B-Tree is only one level deep (flat list)
255 if (CurrentKey
->IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
)
257 DPRINT1("TODO: Only directories with single-level B-Trees are supported right now!\n");
259 return STATUS_NOT_IMPLEMENTED
;
262 // Advance to the next entry
263 CurrentOffset
+= CurrentNodeEntry
->Length
;
264 CurrentNodeEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)CurrentNodeEntry
+ CurrentNodeEntry
->Length
);
265 CurrentKey
= NextKey
;
269 // Copy the final entry to its key
270 RtlCopyMemory(CurrentKey
->IndexEntry
, CurrentNodeEntry
, CurrentNodeEntry
->Length
);
271 CurrentKey
->NextKey
= NULL
;
273 // Make sure this B-Tree is only one level deep (flat list)
274 if (CurrentKey
->IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
)
276 DPRINT1("TODO: Only directories with single-level B-Trees are supported right now!\n");
278 return STATUS_NOT_IMPLEMENTED
;
287 return STATUS_SUCCESS
;
291 * @name CreateIndexRootFromBTree
294 * Parse a B-Tree in memory and convert it into an index that can be written to disk.
297 * Pointer to the DEVICE_EXTENSION of the target drive.
300 * Pointer to a B_TREE that describes the index to be written.
302 * @param MaxIndexSize
303 * Describes how large the index can be before it will take too much space in the file record.
304 * After reaching MaxIndexSize, an index can no longer be represented with just an index root
305 * attribute, and will require an index allocation and $I30 bitmap (TODO).
308 * Pointer to a PINDEX_ROOT_ATTRIBUTE that will receive a pointer to the newly-created index.
311 * Pointer to a ULONG which will receive the length of the new index root.
314 * STATUS_SUCCESS on success.
315 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
316 * STATUS_NOT_IMPLEMENTED if the new index can't fit within MaxIndexSize.
319 * If the function succeeds, it's the caller's responsibility to free IndexRoot with ExFreePoolWithTag().
322 CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt
,
325 PINDEX_ROOT_ATTRIBUTE
*IndexRoot
,
329 PB_TREE_KEY CurrentKey
;
330 PINDEX_ENTRY_ATTRIBUTE CurrentNodeEntry
;
331 PINDEX_ROOT_ATTRIBUTE NewIndexRoot
= ExAllocatePoolWithTag(NonPagedPool
,
332 DeviceExt
->NtfsInfo
.BytesPerFileRecord
,
335 DPRINT1("CreateIndexRootFromBTree(%p, %p, 0x%lx, %p, %p)\n", DeviceExt
, Tree
, MaxIndexSize
, IndexRoot
, Length
);
339 DPRINT1("Failed to allocate memory for Index Root!\n");
340 return STATUS_INSUFFICIENT_RESOURCES
;
343 // Setup the new index root
344 RtlZeroMemory(NewIndexRoot
, DeviceExt
->NtfsInfo
.BytesPerFileRecord
);
346 NewIndexRoot
->AttributeType
= AttributeFileName
;
347 NewIndexRoot
->CollationRule
= COLLATION_FILE_NAME
;
348 NewIndexRoot
->SizeOfEntry
= DeviceExt
->NtfsInfo
.BytesPerIndexRecord
;
349 // If Bytes per index record is less than cluster size, clusters per index record becomes sectors per index
350 if (NewIndexRoot
->SizeOfEntry
< DeviceExt
->NtfsInfo
.BytesPerCluster
)
351 NewIndexRoot
->ClustersPerIndexRecord
= NewIndexRoot
->SizeOfEntry
/ DeviceExt
->NtfsInfo
.BytesPerSector
;
353 NewIndexRoot
->ClustersPerIndexRecord
= NewIndexRoot
->SizeOfEntry
/ DeviceExt
->NtfsInfo
.BytesPerCluster
;
355 // Setup the Index node header
356 NewIndexRoot
->Header
.FirstEntryOffset
= sizeof(INDEX_HEADER_ATTRIBUTE
);
357 NewIndexRoot
->Header
.Flags
= INDEX_ROOT_SMALL
;
359 // Start summing the total size of this node's entries
360 NewIndexRoot
->Header
.TotalSizeOfEntries
= NewIndexRoot
->Header
.FirstEntryOffset
;
362 // Setup each Node Entry
363 CurrentKey
= Tree
->RootNode
->FirstKey
;
364 CurrentNodeEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)NewIndexRoot
365 + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE
, Header
)
366 + NewIndexRoot
->Header
.FirstEntryOffset
);
367 for (i
= 0; i
< Tree
->RootNode
->KeyCount
; i
++)
369 // Would adding the current entry to the index increase the index size beyond the limit we've set?
370 ULONG IndexSize
= FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE
, Header
)
371 + NewIndexRoot
->Header
.FirstEntryOffset
372 + NewIndexRoot
->Header
.TotalSizeOfEntries
373 + CurrentNodeEntry
->Length
;
374 if (IndexSize
> MaxIndexSize
)
376 DPRINT1("TODO: Adding file would require creating an index allocation!\n");
377 ExFreePoolWithTag(NewIndexRoot
, TAG_NTFS
);
378 return STATUS_NOT_IMPLEMENTED
;
381 ASSERT(CurrentKey
->IndexEntry
->Length
!= 0);
383 // Copy the index entry
384 RtlCopyMemory(CurrentNodeEntry
, CurrentKey
->IndexEntry
, CurrentKey
->IndexEntry
->Length
);
386 DPRINT1("Index Node Entry Stream Length: %u\nIndex Node Entry Length: %u\n",
387 CurrentNodeEntry
->KeyLength
,
388 CurrentNodeEntry
->Length
);
390 // Add Length of Current Entry to Total Size of Entries
391 NewIndexRoot
->Header
.TotalSizeOfEntries
+= CurrentNodeEntry
->Length
;
393 // Go to the next node entry
394 CurrentNodeEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)CurrentNodeEntry
+ CurrentNodeEntry
->Length
);
396 CurrentKey
= CurrentKey
->NextKey
;
399 NewIndexRoot
->Header
.AllocatedSize
= NewIndexRoot
->Header
.TotalSizeOfEntries
;
401 *IndexRoot
= NewIndexRoot
;
402 *Length
= NewIndexRoot
->Header
.AllocatedSize
+ FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE
, Header
);
404 return STATUS_SUCCESS
;
408 DestroyBTreeKey(PB_TREE_KEY Key
)
411 ExFreePoolWithTag(Key
->IndexEntry
, TAG_NTFS
);
413 // We'll destroy Key->LesserChild here after we start using it
415 ExFreePoolWithTag(Key
, TAG_NTFS
);
419 DestroyBTreeNode(PB_TREE_FILENAME_NODE Node
)
422 PB_TREE_KEY CurrentKey
= Node
->FirstKey
;
424 for (i
= 0; i
< Node
->KeyCount
; i
++)
426 NT_ASSERT(CurrentKey
);
427 NextKey
= CurrentKey
->NextKey
;
428 DestroyBTreeKey(CurrentKey
);
429 CurrentKey
= NextKey
;
432 NT_ASSERT(NextKey
== NULL
);
434 ExFreePoolWithTag(Node
, TAG_NTFS
);
444 * Pointer to the B_TREE which will be destroyed.
447 * Destroys every bit of data stored in the tree.
450 DestroyBTree(PB_TREE Tree
)
452 DestroyBTreeNode(Tree
->RootNode
);
453 ExFreePoolWithTag(Tree
, TAG_NTFS
);
457 DumpBTreeKey(PB_TREE_KEY Key
, ULONG Number
, ULONG Depth
)
460 for (i
= 0; i
< Depth
; i
++)
462 DbgPrint(" Key #%d", Number
);
464 if (!(Key
->IndexEntry
->Flags
& NTFS_INDEX_ENTRY_END
))
466 UNICODE_STRING FileName
;
467 FileName
.Length
= Key
->IndexEntry
->FileName
.NameLength
* sizeof(WCHAR
);
468 FileName
.MaximumLength
= FileName
.Length
;
469 FileName
.Buffer
= Key
->IndexEntry
->FileName
.Name
;
470 DbgPrint(" '%wZ'\n", &FileName
);
474 DbgPrint(" (Dummy Key)\n");
479 DumpBTreeNode(PB_TREE_FILENAME_NODE Node
, ULONG Number
, ULONG Depth
)
481 PB_TREE_KEY CurrentKey
;
483 for (i
= 0; i
< Depth
; i
++)
485 DbgPrint("Node #%d, Depth %d\n", Number
, Depth
);
487 CurrentKey
= Node
->FirstKey
;
488 for (i
= 0; i
< Node
->KeyCount
; i
++)
490 DumpBTreeKey(CurrentKey
, i
, Depth
);
491 CurrentKey
= CurrentKey
->NextKey
;
502 * Pointer to the B_TREE which will be displayed.
505 * Displays a diagnostic summary of a B_TREE.
508 DumpBTree(PB_TREE Tree
)
510 DbgPrint("B_TREE @ %p\n", Tree
);
511 DumpBTreeNode(Tree
->RootNode
, 0, 0);
514 // Calculates start of Index Buffer relative to the index allocation, given the node's VCN
516 GetAllocationOffsetFromVCN(PDEVICE_EXTENSION DeviceExt
,
517 ULONG IndexBufferSize
,
520 if (IndexBufferSize
< DeviceExt
->NtfsInfo
.BytesPerCluster
)
521 return Vcn
* DeviceExt
->NtfsInfo
.BytesPerSector
;
523 return Vcn
* DeviceExt
->NtfsInfo
.BytesPerCluster
;
527 * @name NtfsInsertKey
530 * Inserts a FILENAME_ATTRIBUTE into a B-Tree node.
532 * @param FileReference
533 * Reference number to the file being added. This will be a combination of the MFT index and update sequence number.
535 * @param FileNameAttribute
536 * Pointer to a FILENAME_ATTRIBUTE which is the data for the key that will be added to the tree. A copy will be made.
539 * Pointer to a B_TREE_FILENAME_NODE into which a new key will be inserted, in order.
541 * @param CaseSensitive
542 * Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
543 * if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag.
546 * A node is always sorted, with the least comparable filename stored first and a dummy key to mark the end.
549 NtfsInsertKey(ULONGLONG FileReference
,
550 PFILENAME_ATTRIBUTE FileNameAttribute
,
551 PB_TREE_FILENAME_NODE Node
,
552 BOOLEAN CaseSensitive
)
554 // Calculate size of Attribute and Index Entry
555 ULONG AttributeSize
= GetFileNameAttributeLength(FileNameAttribute
);
556 ULONG EntrySize
= ALIGN_UP_BY(AttributeSize
+ FIELD_OFFSET(INDEX_ENTRY_ATTRIBUTE
, FileName
), 8);
557 PINDEX_ENTRY_ATTRIBUTE NewEntry
;
558 PB_TREE_KEY NewKey
, CurrentKey
, PreviousKey
;
561 DPRINT1("NtfsInsertKey(0x%02I64, %p, %p, %s)\n",
565 CaseSensitive
? "TRUE" : "FALSE");
567 // Create a new Index Entry for the file
568 NewEntry
= ExAllocatePoolWithTag(NonPagedPool
, EntrySize
, TAG_NTFS
);
571 DPRINT1("ERROR: Failed to allocate memory for Index Entry!\n");
572 return STATUS_INSUFFICIENT_RESOURCES
;
575 // Setup the Index Entry
576 RtlZeroMemory(NewEntry
, EntrySize
);
577 NewEntry
->Data
.Directory
.IndexedFile
= FileReference
;
578 NewEntry
->Length
= EntrySize
;
579 NewEntry
->KeyLength
= AttributeSize
;
581 // Copy the FileNameAttribute
582 RtlCopyMemory(&NewEntry
->FileName
, FileNameAttribute
, AttributeSize
);
585 NewKey
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(B_TREE_KEY
), TAG_NTFS
);
588 DPRINT1("ERROR: Failed to allocate memory for new key!\n");
589 ExFreePoolWithTag(NewEntry
, TAG_NTFS
);
590 return STATUS_INSUFFICIENT_RESOURCES
;
592 NewKey
->IndexEntry
= NewEntry
;
593 NewKey
->NextKey
= NULL
;
595 // Find where to insert the key
596 CurrentKey
= Node
->FirstKey
;
598 for (i
= 0; i
< Node
->KeyCount
; i
++)
600 // Should the New Key go before the current key?
601 LONG Comparison
= CompareTreeKeys(NewKey
, CurrentKey
, CaseSensitive
);
603 ASSERT(Comparison
!= 0);
605 // Is NewKey < CurrentKey?
608 // Insert New Key before Current Key
609 NewKey
->NextKey
= CurrentKey
;
611 // was CurrentKey the first key?
612 if (CurrentKey
== Node
->FirstKey
)
613 Node
->FirstKey
= NewKey
;
615 PreviousKey
->NextKey
= NewKey
;
619 PreviousKey
= CurrentKey
;
620 CurrentKey
= CurrentKey
->NextKey
;
625 // NewEntry and NewKey will be destroyed later by DestroyBTree()
627 return STATUS_SUCCESS
;