https://git.reactos.org/?p=reactos.git;a=commitdiff;h=b033f00f58c48d139a8668...
commit b033f00f58c48d139a86688818d5c3a7692037aa Author: Trevor Thompson tmt256@email.vccs.edu AuthorDate: Sun Aug 27 14:37:17 2017 +0000
[NTFS] - Add support for directory creation. Add some helper functions, some comments, and some fixes. +AddIndexRoot() - Creates an $INDEX_ROOT attribute and adds it to a file record. AddNewMftEntry() - Make sure the buffer used by RtlInitializeBitmap() is ULONG-aligned, and a ULONG-multiple in size, per MSDN. AllocateIndexNode() - Calculate BytesNeeded correctly. Read $BITMAP attribute before increasing its length, in anticipation of a future commit that will check for a free bit before assigning a new index record to the end of the allocation. Use appropriate Set*AttributeDataLength() function, as $BITMAP can be resident or non-resident. B_TREE_FILENAME_NODE - Give two members more accurate names: change "ExistsOnDisk" member to "HasValidVCN" and rename "NodeNumber" member "VCN." +CreateEmptyBTree() - Creates a B-Tree to represent an empty directory (for AddIndexRoot). +NtfsCreateEmptyFileRecord() - Creates an empty file record in memory, with no attributes. CreateIndexRootFromBTree() - Fix TotalSizeOfEntries calculation. +NtfsCreateDirectory() - Creates a file record for an empty directory and adds it to the mft.
svn path=/branches/GSoC_2016/NTFS/; revision=75692 --- drivers/filesystems/ntfs/attrib.c | 112 +++++++++++++++++- drivers/filesystems/ntfs/btree.c | 208 +++++++++++++++++++++++---------- drivers/filesystems/ntfs/create.c | 239 ++++++++++++++++++++++++++++++++++---- drivers/filesystems/ntfs/mft.c | 32 +++-- drivers/filesystems/ntfs/ntfs.h | 40 ++++++- 5 files changed, 530 insertions(+), 101 deletions(-)
diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index e830264ad6..34bee2a060 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -167,7 +167,11 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, FileNameAttribute->LastWriteTime = SystemTime.QuadPart; FileNameAttribute->LastAccessTime = SystemTime.QuadPart;
- FileNameAttribute->FileAttributes = NTFS_FILE_TYPE_ARCHIVE; + // Is this a directory? + if(FileRecord->Flags & FRH_DIRECTORY) + FileNameAttribute->FileAttributes = NTFS_FILE_TYPE_DIRECTORY; + else + FileNameAttribute->FileAttributes = NTFS_FILE_TYPE_ARCHIVE;
// we need to extract the filename from the path DPRINT1("Pathname: %wZ\n", &FileObject->FileName); @@ -254,6 +258,112 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, return Status; }
+/** +* @name AddIndexRoot +* @implemented +* +* Adds an $INDEX_ROOT attribute to a given FileRecord. +* +* @param Vcb +* Pointer to an NTFS_VCB for the destination volume. +* +* @param FileRecord +* Pointer to a complete file record to add the attribute to. Caller is responsible for +* ensuring FileRecord is large enough to contain $INDEX_ROOT. +* +* @param AttributeAddress +* Pointer to the region of memory that will receive the $INDEX_ROOT attribute. +* This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord). +* +* @param NewIndexRoot +* Pointer to an INDEX_ROOT_ATTRIBUTE containing the index root that will be copied to the new attribute. +* +* @param RootLength +* The length of NewIndexRoot, in bytes. +* +* @param Name +* Pointer to a string of 16-bit Unicode characters naming the attribute. Most often, this will be L"$I30". +* +* @param NameLength +* The number of wide-characters in the name. L"$I30" Would use 4 here. +* +* @return +* STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end +* of the given file record. +* +* @remarks +* This function is intended to assist in creating new folders. +* Only adding the attribute to the end of the file record is supported; AttributeAddress must +* be of type AttributeEnd. +* It's the caller's responsibility to ensure the given file record has enough memory allocated +* for the attribute, and this memory must have been zeroed. +*/ +NTSTATUS +AddIndexRoot(PNTFS_VCB Vcb, + PFILE_RECORD_HEADER FileRecord, + PNTFS_ATTR_RECORD AttributeAddress, + PINDEX_ROOT_ATTRIBUTE NewIndexRoot, + ULONG RootLength, + PCWSTR Name, + USHORT NameLength) +{ + ULONG AttributeLength; + // Calculate the header length + ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR); + // Back up the file record's final ULONG (even though it doesn't matter) + ULONG FileRecordEnd = AttributeAddress->Length; + ULONG NameOffset; + ULONG ValueOffset; + ULONG BytesAvailable; + + if (AttributeAddress->Type != AttributeEnd) + { + DPRINT1("FIXME: Can only add $DATA attribute to the end of a file record.\n"); + return STATUS_NOT_IMPLEMENTED; + } + + NameOffset = ResidentHeaderLength; + + // Calculate ValueOffset, which will be aligned to a 4-byte boundary + ValueOffset = ALIGN_UP_BY(NameOffset + (sizeof(WCHAR) * NameLength), VALUE_OFFSET_ALIGNMENT); + + // Calculate length of attribute + AttributeLength = ValueOffset + RootLength; + AttributeLength = ALIGN_UP_BY(AttributeLength, ATTR_RECORD_ALIGNMENT); + + // Make sure the file record is large enough for the new attribute + BytesAvailable = Vcb->NtfsInfo.BytesPerFileRecord - FileRecord->BytesInUse; + if (BytesAvailable < AttributeLength) + { + DPRINT1("FIXME: Not enough room in file record for index allocation attribute!\n"); + return STATUS_NOT_IMPLEMENTED; + } + + // Set Attribute fields + RtlZeroMemory(AttributeAddress, AttributeLength); + + AttributeAddress->Type = AttributeIndexRoot; + AttributeAddress->Length = AttributeLength; + AttributeAddress->NameLength = NameLength; + AttributeAddress->NameOffset = NameOffset; + AttributeAddress->Instance = FileRecord->NextAttributeNumber++; + + AttributeAddress->Resident.ValueLength = RootLength; + AttributeAddress->Resident.ValueOffset = ValueOffset; + + // Set the name + RtlCopyMemory((PCHAR)((ULONG_PTR)AttributeAddress + NameOffset), Name, NameLength * sizeof(WCHAR)); + + // Copy the index root attribute + RtlCopyMemory((PCHAR)((ULONG_PTR)AttributeAddress + ValueOffset), NewIndexRoot, RootLength); + + // move the attribute-end and file-record-end markers to the end of the file record + AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length); + SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd); + + return STATUS_SUCCESS; +} + /** * @name AddRun * @implemented diff --git a/drivers/filesystems/ntfs/btree.c b/drivers/filesystems/ntfs/btree.c index 727c147a2a..cd9cf35069 100644 --- a/drivers/filesystems/ntfs/btree.c +++ b/drivers/filesystems/ntfs/btree.c @@ -111,6 +111,7 @@ PrintAllVCNs(PDEVICE_EXTENSION Vcb, * @remarks * AllocateIndexNode() doesn't write any data to the index record it creates. Called by UpdateIndexNode(). * Don't call PrintAllVCNs() or NtfsDumpFileRecord() after calling AllocateIndexNode() before UpdateIndexNode() finishes. +* Possible TODO: Create an empty node and write it to the allocated index node, so the index allocation is always valid. */ NTSTATUS AllocateIndexNode(PDEVICE_EXTENSION DeviceExt, @@ -167,11 +168,30 @@ AllocateIndexNode(PDEVICE_EXTENSION DeviceExt,
// See how many bytes we need to store the amount of bits we'll have BytesNeeded = NextNodeNumber / 8; - if (NextNodeNumber % 8 != 0) - BytesNeeded++; + BytesNeeded++;
// Windows seems to allocate the bitmap in 8-byte chunks to keep any bytes from being wasted on padding - ALIGN_UP(BytesNeeded, ATTR_RECORD_ALIGNMENT); + BytesNeeded = ALIGN_UP(BytesNeeded, ATTR_RECORD_ALIGNMENT); + + // Allocate memory for the bitmap, including some padding; RtlInitializeBitmap() wants a pointer + // that's ULONG-aligned, and it wants the size of the memory allocated for it to be a ULONG-multiple. + BitmapMem = ExAllocatePoolWithTag(NonPagedPool, BytesNeeded + sizeof(ULONG), TAG_NTFS); + if (!BitmapMem) + { + DPRINT1("Error: failed to allocate bitmap!"); + ReleaseAttributeContext(BitmapCtx); + return STATUS_INSUFFICIENT_RESOURCES; + } + // RtlInitializeBitmap() wants a pointer that's ULONG-aligned. + BitmapPtr = (PULONG)ALIGN_UP_BY((ULONG_PTR)BitmapMem, sizeof(ULONG)); + + RtlZeroMemory(BitmapPtr, BytesNeeded); + + // Read the existing bitmap data + Status = ReadAttribute(DeviceExt, BitmapCtx, 0, (PCHAR)BitmapPtr, BitmapLength); + + // Initialize bitmap + RtlInitializeBitMap(&Bitmap, BitmapPtr, NextNodeNumber);
// Do we need to enlarge the bitmap? if (BytesNeeded > BitmapLength) @@ -179,11 +199,22 @@ AllocateIndexNode(PDEVICE_EXTENSION DeviceExt, // TODO: handle synchronization issues that could occur from changing the directory's file record // Change bitmap size DataSize.QuadPart = BytesNeeded; - Status = SetResidentAttributeDataLength(DeviceExt, - BitmapCtx, - BitmapOffset, - FileRecord, - &DataSize); + if (BitmapCtx->pRecord->IsNonResident) + { + Status = SetNonResidentAttributeDataLength(DeviceExt, + BitmapCtx, + BitmapOffset, + FileRecord, + &DataSize); + } + else + { + Status = SetResidentAttributeDataLength(DeviceExt, + BitmapCtx, + BitmapOffset, + FileRecord, + &DataSize); + } if (!NT_SUCCESS(Status)) { DPRINT1("ERROR: Failed to set length of bitmap attribute!\n"); @@ -215,18 +246,6 @@ AllocateIndexNode(PDEVICE_EXTENSION DeviceExt, return Status; }
- // Allocate memory for the bitmap. RtlInitializeBitmap() wants a pointer that's ULONG-aligned - BitmapMem = ExAllocatePoolWithTag(NonPagedPool, BytesNeeded + sizeof(ULONG) - 1, TAG_NTFS); - BitmapPtr = (PULONG)ALIGN_UP_BY((ULONG_PTR)BitmapMem, sizeof(ULONG)); - - RtlZeroMemory(BitmapPtr, BytesNeeded); - - // Read the existing bitmap data - Status = ReadAttribute(DeviceExt, BitmapCtx, 0, (PCHAR)BitmapPtr, BitmapLength); - - // Initialize bitmap - RtlInitializeBitMap(&Bitmap, BitmapPtr, BytesNeeded); - // Set the bit for the new index record RtlSetBits(&Bitmap, NextNodeNumber, 1);
@@ -246,6 +265,8 @@ AllocateIndexNode(PDEVICE_EXTENSION DeviceExt, // Calculate VCN of new node number *NewVCN = NextNodeNumber * (IndexBufferSize / DeviceExt->NtfsInfo.BytesPerCluster);
+ DPRINT("New VCN: %I64u\n", *NewVCN); + ExFreePoolWithTag(BitmapMem, TAG_NTFS); ReleaseAttributeContext(BitmapCtx);
@@ -311,6 +332,63 @@ CreateDummyKey(BOOLEAN HasChildNode) return NewDummyKey; }
+/** +* @name CreateEmptyBTree +* @implemented +* +* Creates an empty B-Tree, which will contain a single root node which will contain a single dummy key. +* +* @param NewTree +* Pointer to a PB_TREE that will receive the pointer of the newly-created B-Tree. +* +* @return +* STATUS_SUCCESS on success. STATUS_INSUFFICIENT_RESOURCES if an allocation fails. +*/ +NTSTATUS +CreateEmptyBTree(PB_TREE *NewTree) +{ + PB_TREE Tree = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE), TAG_NTFS); + PB_TREE_FILENAME_NODE RootNode = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_FILENAME_NODE), TAG_NTFS); + PB_TREE_KEY DummyKey; + + DPRINT1("CreateEmptyBTree(%p) called\n", NewTree); + + if (!Tree || !RootNode) + { + DPRINT1("Couldn't allocate enough memory for B-Tree!\n"); + if (Tree) + ExFreePoolWithTag(Tree, TAG_NTFS); + if (RootNode) + ExFreePoolWithTag(RootNode, TAG_NTFS); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Create the dummy key + DummyKey = CreateDummyKey(FALSE); + if (!DummyKey) + { + DPRINT1("ERROR: Failed to create dummy key!\n"); + ExFreePoolWithTag(Tree, TAG_NTFS); + ExFreePoolWithTag(RootNode, TAG_NTFS); + return STATUS_INSUFFICIENT_RESOURCES; + } + + RtlZeroMemory(Tree, sizeof(B_TREE)); + RtlZeroMemory(RootNode, sizeof(B_TREE_FILENAME_NODE)); + + // Setup the Tree + RootNode->FirstKey = DummyKey; + RootNode->KeyCount = 1; + RootNode->DiskNeedsUpdating = TRUE; + Tree->RootNode = RootNode; + + *NewTree = Tree; + + // Memory will be freed when DestroyBTree() is called + + return STATUS_SUCCESS; +} + /** * @name CompareTreeKeys * @implemented @@ -402,7 +480,7 @@ CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb, ULONG CurrentEntryOffset = 0; PINDEX_BUFFER NodeBuffer; ULONG IndexBufferSize = Vcb->NtfsInfo.BytesPerIndexRecord; - PULONGLONG NodeNumber; + PULONGLONG VCN; PB_TREE_KEY CurrentKey; NTSTATUS Status; ULONGLONG IndexNodeOffset; @@ -415,10 +493,9 @@ CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb, }
// Get the node number from the end of the node entry - NodeNumber = (PULONGLONG)((ULONG_PTR)NodeEntry + NodeEntry->Length - sizeof(ULONGLONG)); + VCN = (PULONGLONG)((ULONG_PTR)NodeEntry + NodeEntry->Length - sizeof(ULONGLONG));
// Create the new tree node - DPRINT1("About to allocate %ld for NewNode\n", sizeof(B_TREE_FILENAME_NODE)); NewNode = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_FILENAME_NODE), TAG_NTFS); if (!NewNode) { @@ -449,7 +526,7 @@ CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb, }
// Calculate offset into index allocation - IndexNodeOffset = GetAllocationOffsetFromVCN(Vcb, IndexBufferSize, *NodeNumber); + IndexNodeOffset = GetAllocationOffsetFromVCN(Vcb, IndexBufferSize, *VCN);
// TODO: Confirm index bitmap has this node marked as in-use
@@ -462,7 +539,7 @@ CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb,
ASSERT(BytesRead == IndexBufferSize); NT_ASSERT(NodeBuffer->Ntfs.Type == NRH_INDX_TYPE); - NT_ASSERT(NodeBuffer->VCN == *NodeNumber); + NT_ASSERT(NodeBuffer->VCN == *VCN);
// Apply the fixup array to the node buffer Status = FixupUpdateSequenceArray(Vcb, &NodeBuffer->Ntfs); @@ -516,8 +593,6 @@ CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb, // See if the current key has a sub-node if (CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) { - DPRINT1("TODO: Only a node with a single-level is supported right now!\n"); - // Needs debugging: CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb, IndexRoot, IndexAllocationAttributeCtx, @@ -535,8 +610,6 @@ CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb, // See if the current key has a sub-node if (CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) { - DPRINT1("TODO: Only a node with a single-level is supported right now!\n"); - // Needs debugging: CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb, IndexRoot, IndexAllocationAttributeCtx, @@ -551,8 +624,8 @@ CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb, CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)CurrentNodeEntry + CurrentNodeEntry->Length); }
- NewNode->NodeNumber = *NodeNumber; - NewNode->ExistsOnDisk = TRUE; + NewNode->VCN = *VCN; + NewNode->HasValidVCN = TRUE;
ExFreePoolWithTag(NodeBuffer, TAG_NTFS);
@@ -622,8 +695,6 @@ CreateBTreeFromIndex(PDEVICE_EXTENSION Vcb, NULL); if (!NT_SUCCESS(Status)) IndexAllocationContext = NULL; - else - PrintAllVCNs(Vcb, IndexAllocationContext, IndexRoot->SizeOfEntry);
// Setup the Tree RootNode->FirstKey = CurrentKey; @@ -871,7 +942,7 @@ CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt, NewIndexRoot->Header.Flags = INDEX_ROOT_LARGE;
// Add Length of Current Entry to Total Size of Entries - NewIndexRoot->Header.TotalSizeOfEntries += CurrentNodeEntry->Length; + NewIndexRoot->Header.TotalSizeOfEntries += CurrentKey->IndexEntry->Length;
// Go to the next node entry CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)CurrentNodeEntry + CurrentNodeEntry->Length); @@ -905,10 +976,12 @@ CreateIndexBufferFromBTreeNode(PDEVICE_EXTENSION DeviceExt, IndexBuffer->Ntfs.UsaCount = 9;
// TODO: Check bitmap for VCN - ASSERT(Node->ExistsOnDisk); - IndexBuffer->VCN = Node->NodeNumber; + ASSERT(Node->HasValidVCN); + IndexBuffer->VCN = Node->VCN;
- IndexBuffer->Header.FirstEntryOffset = 0x28; + // Windows seems to alternate between using 0x28 and 0x40 for the first entry offset of each index buffer. + // Interestingly, neither Windows nor chkdsk seem to mind if we just use 0x28 for every index record. + IndexBuffer->Header.FirstEntryOffset = 0x28; IndexBuffer->Header.AllocatedSize = BufferSize - FIELD_OFFSET(INDEX_BUFFER, Header);
// Start summing the total size of this node's entries @@ -934,9 +1007,9 @@ CreateIndexBufferFromBTreeNode(PDEVICE_EXTENSION DeviceExt, // Copy the index entry RtlCopyMemory(CurrentNodeEntry, CurrentKey->IndexEntry, CurrentKey->IndexEntry->Length);
- DPRINT1("Index Node Entry Stream Length: %u\nIndex Node Entry Length: %u\n", - CurrentNodeEntry->KeyLength, - CurrentNodeEntry->Length); + DPRINT("Index Node Entry Stream Length: %u\nIndex Node Entry Length: %u\n", + CurrentNodeEntry->KeyLength, + CurrentNodeEntry->Length);
// Add Length of Current Entry to Total Size of Entries IndexBuffer->Header.TotalSizeOfEntries += CurrentNodeEntry->Length; @@ -1020,14 +1093,6 @@ UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt, DPRINT1("FIXME: Need to add index allocation\n"); return STATUS_NOT_IMPLEMENTED; } - - Status = UpdateIndexNode(DeviceExt, FileRecord, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, IndexAllocationOffset, BitmapContext); - if (!NT_SUCCESS(Status)) - { - DPRINT1("ERROR: Failed to update index node!\n"); - ReleaseAttributeContext(IndexAllocationContext); - return Status; - }
// Is the Index Entry large enough to store the VCN? if (!CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) @@ -1056,9 +1121,17 @@ UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt, CurrentKey->IndexEntry->Flags |= NTFS_INDEX_ENTRY_NODE; }
- // Update the VCN stored in the index entry of CurrentKey - SetIndexEntryVCN(CurrentKey->IndexEntry, CurrentKey->LesserChild->NodeNumber); + // Update the sub-node + Status = UpdateIndexNode(DeviceExt, FileRecord, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, IndexAllocationOffset, BitmapContext); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to update index node!\n"); + ReleaseAttributeContext(IndexAllocationContext); + return Status; + }
+ // Update the VCN stored in the index entry of CurrentKey + SetIndexEntryVCN(CurrentKey->IndexEntry, CurrentKey->LesserChild->VCN); } CurrentKey = CurrentKey->NextKey; } @@ -1096,7 +1169,7 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt, IndexAllocationContext, IndexAllocationOffset, BitmapContext, - Node->NodeNumber); + Node->VCN);
// Do we need to write this node to disk? if (Node->DiskNeedsUpdating) @@ -1106,7 +1179,7 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt, PINDEX_BUFFER IndexBuffer;
// Does the node need to be assigned a VCN? - if (!Node->ExistsOnDisk) + if (!Node->HasValidVCN) { // Allocate the node Status = AllocateIndexNode(DeviceExt, @@ -1114,14 +1187,14 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt, IndexBufferSize, IndexAllocationContext, IndexAllocationOffset, - &Node->NodeNumber); + &Node->VCN); if (!NT_SUCCESS(Status)) { DPRINT1("ERROR: Failed to allocate index record in index allocation!\n"); return Status; }
- Node->ExistsOnDisk = TRUE; + Node->HasValidVCN = TRUE; }
// Allocate memory for an index buffer @@ -1142,7 +1215,7 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt, }
// Get Offset of index buffer in index allocation - NodeOffset = GetAllocationOffsetFromVCN(DeviceExt, IndexBufferSize, Node->NodeNumber); + NodeOffset = GetAllocationOffsetFromVCN(DeviceExt, IndexBufferSize, Node->VCN);
// Write the buffer to the index allocation Status = WriteAttribute(DeviceExt, IndexAllocationContext, NodeOffset, (const PUCHAR)IndexBuffer, IndexBufferSize, &LengthWritten, FileRecord); @@ -1274,7 +1347,7 @@ DestroyBTree(PB_TREE Tree) }
VOID -DumpBTreeKey(PB_TREE_KEY Key, ULONG Number, ULONG Depth) +DumpBTreeKey(PB_TREE Tree, PB_TREE_KEY Key, ULONG Number, ULONG Depth) { ULONG i; for (i = 0; i < Depth; i++) @@ -1298,7 +1371,7 @@ DumpBTreeKey(PB_TREE_KEY Key, ULONG Number, ULONG Depth) if (Key->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) { if (Key->LesserChild) - DumpBTreeNode(Key->LesserChild, Number, Depth + 1); + DumpBTreeNode(Tree, Key->LesserChild, Number, Depth + 1); else { // This will be an assert once nodes with arbitrary depth are debugged @@ -1308,18 +1381,28 @@ DumpBTreeKey(PB_TREE_KEY Key, ULONG Number, ULONG Depth) }
VOID -DumpBTreeNode(PB_TREE_FILENAME_NODE Node, ULONG Number, ULONG Depth) +DumpBTreeNode(PB_TREE Tree, + PB_TREE_FILENAME_NODE Node, + ULONG Number, + ULONG Depth) { PB_TREE_KEY CurrentKey; ULONG i; for (i = 0; i < Depth; i++) DbgPrint(" "); - DbgPrint("Node #%d, Depth %d, has %d key%s\n", Number, Depth, Node->KeyCount, Node->KeyCount == 1 ? "" : "s"); + DbgPrint("Node #%d, Depth %d, has %d key%s", Number, Depth, Node->KeyCount, Node->KeyCount == 1 ? "" : "s"); + + if (Node->HasValidVCN) + DbgPrint(" VCN: %I64u\n", Node->VCN); + else if (Tree->RootNode == Node) + DbgPrint(" Index Root"); + else + DbgPrint(" NOT ASSIGNED VCN YET\n");
CurrentKey = Node->FirstKey; - for (i = 1; i <= Node->KeyCount; i++) + for (i = 0; i < Node->KeyCount; i++) { - DumpBTreeKey(CurrentKey, i, Depth); + DumpBTreeKey(Tree, CurrentKey, i, Depth); CurrentKey = CurrentKey->NextKey; } } @@ -1340,7 +1423,7 @@ VOID DumpBTree(PB_TREE Tree) { DbgPrint("B_TREE @ %p\n", Tree); - DumpBTreeNode(Tree->RootNode, 0, 0); + DumpBTreeNode(Tree, Tree->RootNode, 0, 0); }
// Calculates start of Index Buffer relative to the index allocation, given the node's VCN @@ -1525,7 +1608,6 @@ NtfsInsertKey(PB_TREE Tree, NewIndexRoot->FirstKey = DummyKey; NewIndexRoot->KeyCount = 1; NewIndexRoot->DiskNeedsUpdating = TRUE; - NewIndexRoot->ExistsOnDisk = TRUE;
// Make the new node the Tree's root node Tree->RootNode = NewIndexRoot; diff --git a/drivers/filesystems/ntfs/create.c b/drivers/filesystems/ntfs/create.c index e8d0b1eb14..55e1e95a38 100644 --- a/drivers/filesystems/ntfs/create.c +++ b/drivers/filesystems/ntfs/create.c @@ -569,25 +569,31 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject, return STATUS_ACCESS_DENIED; }
- // We can't create directories yet + // Was the user trying to create a directory? if (RequestedOptions & FILE_DIRECTORY_FILE) { - DPRINT1("FIXME: Folder creation is still TODO!\n"); - return STATUS_NOT_IMPLEMENTED; + // Create the directory on disk + Status = NtfsCreateDirectory(DeviceExt, + FileObject, + BooleanFlagOn(Stack->Flags, SL_CASE_SENSITIVE), + BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT)); + } + else + { + // Create the file record on disk + Status = NtfsCreateFileRecord(DeviceExt, + FileObject, + BooleanFlagOn(Stack->Flags, SL_CASE_SENSITIVE), + BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT)); }
- // Create the file record on disk - Status = NtfsCreateFileRecord(DeviceExt, - FileObject, - BooleanFlagOn(Stack->Flags, SL_CASE_SENSITIVE), - BooleanFlagOn(IrpContext->Flags,IRPCONTEXT_CANWAIT)); if (!NT_SUCCESS(Status)) { DPRINT1("ERROR: Couldn't create file record!\n"); return Status; }
- // Before we open the file we just created, we need to change the disposition (upper 8 bits of ULONG) + // Before we open the file/directory we just created, we need to change the disposition (upper 8 bits of ULONG) // from create to open, since we already created the file Stack->Parameters.Create.Options = (ULONG)FILE_OPEN << 24 | RequestedOptions;
@@ -651,40 +657,48 @@ NtfsCreate(PNTFS_IRP_CONTEXT IrpContext) }
/** -* @name NtfsCreateFileRecord() +* @name NtfsCreateDirectory() * @implemented * -* Creates a file record and saves it to the MFT. Adds the filename attribute of the -* created file to the parent directory's index. +* Creates a file record for a new directory and saves it to the MFT. Adds the filename attribute of the +* created directory to the parent directory's index. * * @param DeviceExt * Points to the target disk's DEVICE_EXTENSION * * @param FileObject -* Pointer to a FILE_OBJECT describing the file to be created +* Pointer to a FILE_OBJECT describing the directory to be created +* +* @param CaseSensitive +* Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE +* if an application created the folder with the FILE_FLAG_POSIX_SEMANTICS flag. * * @param CanWait * Boolean indicating if the function is allowed to wait for exclusive access to the master file table. * This will only be relevant if the MFT doesn't have any free file records and needs to be enlarged. -* +* * @return -* STATUS_SUCCESS on success. +* STATUS_SUCCESS on success. * STATUS_INSUFFICIENT_RESOURCES if unable to allocate memory for the file record. -* STATUS_CANT_WAIT if CanWait was FALSE and the function needed to resize the MFT but +* STATUS_CANT_WAIT if CanWait was FALSE and the function needed to resize the MFT but * couldn't get immediate, exclusive access to it. */ NTSTATUS -NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, - PFILE_OBJECT FileObject, - BOOLEAN CaseSensitive, - BOOLEAN CanWait) +NtfsCreateDirectory(PDEVICE_EXTENSION DeviceExt, + PFILE_OBJECT FileObject, + BOOLEAN CaseSensitive, + BOOLEAN CanWait) { + NTSTATUS Status = STATUS_SUCCESS; PFILE_RECORD_HEADER FileRecord; PNTFS_ATTR_RECORD NextAttribute; PFILENAME_ATTRIBUTE FilenameAttribute; ULONGLONG ParentMftIndex; ULONGLONG FileMftIndex; + PB_TREE Tree; + PINDEX_ROOT_ATTRIBUTE NewIndexRoot; + ULONG RootLength;
DPRINT1("NtfsCreateFileRecord(%p, %p, %s, %s)\n", DeviceExt, @@ -692,6 +706,126 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, CaseSensitive ? "TRUE" : "FALSE", CanWait ? "TRUE" : "FALSE");
+ // Start with an empty file record + FileRecord = NtfsCreateEmptyFileRecord(DeviceExt); + if (!FileRecord) + { + DPRINT1("ERROR: Unable to allocate memory for file record!\n"); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Set the directory flag + FileRecord->Flags |= FRH_DIRECTORY; + + // find where the first attribute will be added + NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->AttributeOffset); + + // add first attribute, $STANDARD_INFORMATION + AddStandardInformation(FileRecord, NextAttribute); + + // advance NextAttribute pointer to the next attribute + NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)NextAttribute + (ULONG_PTR)NextAttribute->Length); + + // Add the $FILE_NAME attribute + AddFileName(FileRecord, NextAttribute, DeviceExt, FileObject, CaseSensitive, &ParentMftIndex); + + // save a pointer to the filename attribute + FilenameAttribute = (PFILENAME_ATTRIBUTE)((ULONG_PTR)NextAttribute + NextAttribute->Resident.ValueOffset); + + // advance NextAttribute pointer to the next attribute + NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)NextAttribute + (ULONG_PTR)NextAttribute->Length); + + // Create an empty b-tree to represent our new index + Status = CreateEmptyBTree(&Tree); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to create empty B-Tree!\n"); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + return Status; + } + + // Calculate maximum size of index root + ULONG MaxIndexRootSize = DeviceExt->NtfsInfo.BytesPerFileRecord + - ((ULONG_PTR)NextAttribute - (ULONG_PTR)FileRecord) + - sizeof(ULONG) * 2; + + // Create a new index record from the tree + Status = CreateIndexRootFromBTree(DeviceExt, + Tree, + MaxIndexRootSize, + &NewIndexRoot, + &RootLength); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Unable to create empty index root!\n"); + DestroyBTree(Tree); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + return Status; + } + + // We're done with the B-Tree + DestroyBTree(Tree); + + // add the $INDEX_ROOT attribute + Status = AddIndexRoot(DeviceExt, FileRecord, NextAttribute, NewIndexRoot, RootLength, L"$I30", 4); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to add index root to new file record!\n"); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + return Status; + } + + +#ifndef NDEBUG + NtfsDumpFileRecord(DeviceExt, FileRecord); +#endif + + // Now that we've built the file record in memory, we need to store it in the MFT. + Status = AddNewMftEntry(FileRecord, DeviceExt, &FileMftIndex, CanWait); + if (NT_SUCCESS(Status)) + { + // The highest 2 bytes should be the sequence number, unless the parent happens to be root + if (FileMftIndex == NTFS_FILE_ROOT) + FileMftIndex = FileMftIndex + ((ULONGLONG)NTFS_FILE_ROOT << 48); + else + FileMftIndex = FileMftIndex + ((ULONGLONG)FileRecord->SequenceNumber << 48); + + DPRINT1("New File Reference: 0x%016I64x\n", FileMftIndex); + + // Add the filename attribute to the filename-index of the parent directory + Status = NtfsAddFilenameToDirectory(DeviceExt, + ParentMftIndex, + FileMftIndex, + FilenameAttribute, + CaseSensitive); + } + + ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + + return Status; +} + +/** +* @name NtfsCreateEmptyFileRecord +* @implemented +* +* Creates a new, empty file record, with no attributes. +* +* @param DeviceExt +* Pointer to the DEVICE_EXTENSION of the target volume the file record will be stored on. +* +* @return +* A pointer to the newly-created FILE_RECORD_HEADER if the function succeeds, NULL otherwise. +*/ +PFILE_RECORD_HEADER +NtfsCreateEmptyFileRecord(PDEVICE_EXTENSION DeviceExt) +{ + PFILE_RECORD_HEADER FileRecord; + PNTFS_ATTR_RECORD NextAttribute; + + DPRINT1("NtfsCreateEmptyFileRecord(%p)\n", DeviceExt); + // allocate memory for file record FileRecord = ExAllocatePoolWithTag(NonPagedPool, DeviceExt->NtfsInfo.BytesPerFileRecord, @@ -699,7 +833,7 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, if (!FileRecord) { DPRINT1("ERROR: Unable to allocate memory for file record!\n"); - return STATUS_INSUFFICIENT_RESOURCES; + return NULL; }
RtlZeroMemory(FileRecord, DeviceExt->NtfsInfo.BytesPerFileRecord); @@ -719,7 +853,7 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, FileRecord->AttributeOffset = ALIGN_UP_BY(FileRecord->AttributeOffset, ATTR_RECORD_ALIGNMENT); FileRecord->Flags = FRH_IN_USE; FileRecord->BytesInUse = FileRecord->AttributeOffset + sizeof(ULONG) * 2; - + // find where the first attribute will be added NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->AttributeOffset);
@@ -727,9 +861,66 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, NextAttribute->Type = AttributeEnd; NextAttribute->Length = FILE_RECORD_END;
+ return FileRecord; +} + + +/** +* @name NtfsCreateFileRecord() +* @implemented +* +* Creates a file record and saves it to the MFT. Adds the filename attribute of the +* created file to the parent directory's index. +* +* @param DeviceExt +* Points to the target disk's DEVICE_EXTENSION +* +* @param FileObject +* Pointer to a FILE_OBJECT describing the file to be created +* +* @param CanWait +* Boolean indicating if the function is allowed to wait for exclusive access to the master file table. +* This will only be relevant if the MFT doesn't have any free file records and needs to be enlarged. +* +* @return +* STATUS_SUCCESS on success. +* STATUS_INSUFFICIENT_RESOURCES if unable to allocate memory for the file record. +* STATUS_CANT_WAIT if CanWait was FALSE and the function needed to resize the MFT but +* couldn't get immediate, exclusive access to it. +*/ +NTSTATUS +NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, + PFILE_OBJECT FileObject, + BOOLEAN CaseSensitive, + BOOLEAN CanWait) +{ + NTSTATUS Status = STATUS_SUCCESS; + PFILE_RECORD_HEADER FileRecord; + PNTFS_ATTR_RECORD NextAttribute; + PFILENAME_ATTRIBUTE FilenameAttribute; + ULONGLONG ParentMftIndex; + ULONGLONG FileMftIndex; + + DPRINT1("NtfsCreateFileRecord(%p, %p, %s, %s)\n", + DeviceExt, + FileObject, + CaseSensitive ? "TRUE" : "FALSE", + CanWait ? "TRUE" : "FALSE"); + + // allocate memory for file record + FileRecord = NtfsCreateEmptyFileRecord(DeviceExt); + if (!FileRecord) + { + DPRINT1("ERROR: Unable to allocate memory for file record!\n"); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // find where the first attribute will be added + NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->AttributeOffset); + // add first attribute, $STANDARD_INFORMATION AddStandardInformation(FileRecord, NextAttribute); - + // advance NextAttribute pointer to the next attribute NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)NextAttribute + (ULONG_PTR)NextAttribute->Length);
@@ -745,8 +936,10 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, // add the $DATA attribute AddData(FileRecord, NextAttribute);
+#ifndef NDEBUG // dump file record in memory (for debugging) NtfsDumpFileRecord(DeviceExt, FileRecord); +#endif
// Now that we've built the file record in memory, we need to store it in the MFT. Status = AddNewMftEntry(FileRecord, DeviceExt, &FileMftIndex, CanWait); diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 0cd2767efb..7d0157a761 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -1893,6 +1893,7 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, ULONGLONG BitmapDataSize; ULONGLONG AttrBytesRead; PUCHAR BitmapData; + PUCHAR BitmapBuffer; ULONG LengthWritten; PNTFS_ATTR_CONTEXT BitmapContext; LARGE_INTEGER BitmapBits; @@ -1901,6 +1902,8 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, DPRINT1("AddNewMftEntry(%p, %p, %p, %s)\n", FileRecord, DeviceExt, DestinationIndex, CanWait ? "TRUE" : "FALSE");
// First, we have to read the mft's $Bitmap attribute + + // Find the attribute Status = FindAttribute(DeviceExt, DeviceExt->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL); if (!NT_SUCCESS(Status)) { @@ -1908,22 +1911,28 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, return Status; }
- // allocate a buffer for the $Bitmap attribute + // Get size of bitmap BitmapDataSize = AttributeDataLength(BitmapContext->pRecord); - BitmapData = ExAllocatePoolWithTag(NonPagedPool, BitmapDataSize, TAG_NTFS); - if (!BitmapData) + + // RtlInitializeBitmap wants a ULONG-aligned pointer, and wants the memory passed to it to be a ULONG-multiple + // Allocate a buffer for the $Bitmap attribute plus enough to ensure we can get a ULONG-aligned pointer + BitmapBuffer = ExAllocatePoolWithTag(NonPagedPool, BitmapDataSize + sizeof(ULONG), TAG_NTFS); + if (!BitmapBuffer) { ReleaseAttributeContext(BitmapContext); return STATUS_INSUFFICIENT_RESOURCES; }
+ // Get a ULONG-aligned pointer for the bitmap itself + BitmapData = (PUCHAR)ALIGN_UP_BY((ULONG_PTR)BitmapBuffer, sizeof(ULONG)); + // read $Bitmap attribute AttrBytesRead = ReadAttribute(DeviceExt, BitmapContext, 0, (PCHAR)BitmapData, BitmapDataSize);
- if (AttrBytesRead == 0) + if (AttrBytesRead != BitmapDataSize) { DPRINT1("ERROR: Unable to read $Bitmap attribute of master file table!\n"); - ExFreePoolWithTag(BitmapData, TAG_NTFS); + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); ReleaseAttributeContext(BitmapContext); return STATUS_OBJECT_NAME_NOT_FOUND; } @@ -1939,7 +1948,8 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, if (BitmapBits.HighPart != 0) { DPRINT1("\tFIXME: bitmap sizes beyond 32bits are not yet supported! (Your NTFS volume is too large)\n"); - ExFreePoolWithTag(BitmapData, TAG_NTFS); + NtfsGlobalData->EnableWriteSupport = FALSE; + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); ReleaseAttributeContext(BitmapContext); return STATUS_NOT_IMPLEMENTED; } @@ -1953,7 +1963,7 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, { DPRINT1("Couldn't find free space in MFT for file record, increasing MFT size.\n");
- ExFreePoolWithTag(BitmapData, TAG_NTFS); + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); ReleaseAttributeContext(BitmapContext);
// Couldn't find a free record in the MFT, add some blank records and try again @@ -1982,7 +1992,7 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, if (!NT_SUCCESS(Status)) { DPRINT1("ERROR encountered when writing $Bitmap attribute!\n"); - ExFreePoolWithTag(BitmapData, TAG_NTFS); + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); ReleaseAttributeContext(BitmapContext); return Status; } @@ -1993,14 +2003,14 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, if (!NT_SUCCESS(Status)) { DPRINT1("ERROR: Unable to write file record!\n"); - ExFreePoolWithTag(BitmapData, TAG_NTFS); + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); ReleaseAttributeContext(BitmapContext); return Status; }
*DestinationIndex = MftIndex;
- ExFreePoolWithTag(BitmapData, TAG_NTFS); + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); ReleaseAttributeContext(BitmapContext);
return Status; @@ -2255,7 +2265,7 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, AttributeLength, &LengthWritten, ParentFileRecord); - if (!NT_SUCCESS(Status)) + if (!NT_SUCCESS(Status) || LengthWritten != AttributeLength) { DPRINT1("ERROR: Unable to write new index root attribute to parent directory!\n"); ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index 030b777c22..abd2d2791d 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -304,6 +304,12 @@ typedef struct // relative to the beginning of the file record. #define ATTR_RECORD_ALIGNMENT 8
+// Data runs are aligned to a 4-byte boundary, relative to the start of the attribute record +#define DATA_RUN_ALIGNMENT 4 + +// Value offset is aligned to a 4-byte boundary, relative to the start of the attribute record +#define VALUE_OFFSET_ALIGNMENT 4 + typedef struct { ULONGLONG CreationTime; @@ -423,9 +429,9 @@ typedef struct _B_TREE_KEY typedef struct _B_TREE_FILENAME_NODE { ULONG KeyCount; - BOOLEAN ExistsOnDisk; + BOOLEAN HasValidVCN; BOOLEAN DiskNeedsUpdating; - ULONGLONG NodeNumber; + ULONGLONG VCN; PB_TREE_KEY FirstKey; } B_TREE_FILENAME_NODE, *PB_TREE_FILENAME_NODE;
@@ -570,6 +576,15 @@ AddRun(PNTFS_VCB Vcb, ULONGLONG NextAssignedCluster, ULONG RunLength);
+NTSTATUS +AddIndexRoot(PNTFS_VCB Vcb, + PFILE_RECORD_HEADER FileRecord, + PNTFS_ATTR_RECORD AttributeAddress, + PINDEX_ROOT_ATTRIBUTE NewIndexRoot, + ULONG RootLength, + PCWSTR Name, + USHORT NameLength); + NTSTATUS AddFileName(PFILE_RECORD_HEADER FileRecord, PNTFS_ATTR_RECORD AttributeAddress, @@ -718,10 +733,20 @@ VOID DumpBTree(PB_TREE Tree);
VOID -DumpBTreeNode(PB_TREE_FILENAME_NODE Node, +DumpBTreeKey(PB_TREE Tree, + PB_TREE_KEY Key, + ULONG Number, + ULONG Depth); + +VOID +DumpBTreeNode(PB_TREE Tree, + PB_TREE_FILENAME_NODE Node, ULONG Number, ULONG Depth);
+NTSTATUS +CreateEmptyBTree(PB_TREE *NewTree); + ULONGLONG GetAllocationOffsetFromVCN(PDEVICE_EXTENSION DeviceExt, ULONG IndexBufferSize, @@ -771,6 +796,15 @@ NtfsClose(PNTFS_IRP_CONTEXT IrpContext); NTSTATUS NtfsCreate(PNTFS_IRP_CONTEXT IrpContext);
+NTSTATUS +NtfsCreateDirectory(PDEVICE_EXTENSION DeviceExt, + PFILE_OBJECT FileObject, + BOOLEAN CaseSensitive, + BOOLEAN CanWait); + +PFILE_RECORD_HEADER +NtfsCreateEmptyFileRecord(PDEVICE_EXTENSION DeviceExt); + NTSTATUS NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, PFILE_OBJECT FileObject,