diff --git a/boot/bootdata/hivesys.inf b/boot/bootdata/hivesys.inf index 02cfc68c751..692cba2cc6f 100644 --- a/boot/bootdata/hivesys.inf +++ b/boot/bootdata/hivesys.inf @@ -1605,6 +1605,8 @@ HKLM,"SYSTEM\CurrentControlSet\Services\Ntfs","Group",0x00000000,"File System" HKLM,"SYSTEM\CurrentControlSet\Services\Ntfs","ImagePath",0x00020000,"system32\drivers\ntfs.sys" HKLM,"SYSTEM\CurrentControlSet\Services\Ntfs","Start",0x00010001,0x00000003 HKLM,"SYSTEM\CurrentControlSet\Services\Ntfs","Type",0x00010001,0x00000002 +; un-comment the line below to enable EXPERIMENTAL write-support on NTFS volumes: +;HKLM,"SYSTEM\CurrentControlSet\Services\Ntfs","MyDataDoesNotMatterSoEnableExperimentalWriteSupportForEveryNTFSVolume",0x00010001,0x00000001 ; Null device driver HKLM,"SYSTEM\CurrentControlSet\Services\Null","ErrorControl",0x00010001,0x00000000 diff --git a/drivers/filesystems/ntfs/CMakeLists.txt b/drivers/filesystems/ntfs/CMakeLists.txt index ff3b35f647f..0b1af78947f 100644 --- a/drivers/filesystems/ntfs/CMakeLists.txt +++ b/drivers/filesystems/ntfs/CMakeLists.txt @@ -2,6 +2,7 @@ list(APPEND SOURCE attrib.c blockdev.c + btree.c cleanup.c close.c create.c diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index 1883951c444..846c16da808 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -29,12 +29,939 @@ /* INCLUDES *****************************************************************/ #include "ntfs.h" +#include #define NDEBUG #include /* FUNCTIONS ****************************************************************/ +/** +* @name AddBitmap +* @implemented +* +* Adds a $BITMAP 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. +* +* @param AttributeAddress +* Pointer to the region of memory that will receive the $INDEX_ALLOCATION attribute. +* This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord). +* +* @param Name +* Pointer to a string of 16-bit Unicode characters naming the attribute. Most often 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, or if the file record isn't large enough for the attribute. +* +* @remarks +* Only adding the attribute to the end of the file record is supported; AttributeAddress must +* be of type AttributeEnd. +* This could be improved by adding an $ATTRIBUTE_LIST to the file record if there's not enough space. +* +*/ +NTSTATUS +AddBitmap(PNTFS_VCB Vcb, + PFILE_RECORD_HEADER FileRecord, + PNTFS_ATTR_RECORD AttributeAddress, + PCWSTR Name, + USHORT NameLength) +{ + ULONG AttributeLength; + // Calculate the header length + ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR); + ULONG FileRecordEnd = AttributeAddress->Length; + ULONG NameOffset; + ULONG ValueOffset; + // We'll start out with 8 bytes of bitmap data + ULONG ValueLength = 8; + ULONG BytesAvailable; + + if (AttributeAddress->Type != AttributeEnd) + { + DPRINT1("FIXME: Can only add $BITMAP 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 + ValueLength; + 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 = AttributeBitmap; + AttributeAddress->Length = AttributeLength; + AttributeAddress->NameLength = NameLength; + AttributeAddress->NameOffset = NameOffset; + AttributeAddress->Instance = FileRecord->NextAttributeNumber++; + + AttributeAddress->Resident.ValueLength = ValueLength; + AttributeAddress->Resident.ValueOffset = ValueOffset; + + // Set the name + RtlCopyMemory((PCHAR)((ULONG_PTR)AttributeAddress + NameOffset), Name, NameLength * sizeof(WCHAR)); + + // 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 AddData +* @implemented +* +* Adds a $DATA attribute to a given FileRecord. +* +* @param FileRecord +* Pointer to a complete file record to add the attribute to. Caller is responsible for +* ensuring FileRecord is large enough to contain $DATA. +* +* @param AttributeAddress +* Pointer to the region of memory that will receive the $DATA attribute. +* This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord). +* +* @return +* STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end +* of the given file record. +* +* @remarks +* Only adding the attribute to the end of the file record is supported; AttributeAddress must +* be of type AttributeEnd. +* As it's implemented, this function is only intended to assist in creating new file records. It +* could be made more general-purpose by considering file records with an $ATTRIBUTE_LIST. +* It's the caller's responsibility to ensure the given file record has enough memory allocated +* for the attribute. +*/ +NTSTATUS +AddData(PFILE_RECORD_HEADER FileRecord, + PNTFS_ATTR_RECORD AttributeAddress) +{ + ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR); + ULONG FileRecordEnd = AttributeAddress->Length; + + if (AttributeAddress->Type != AttributeEnd) + { + DPRINT1("FIXME: Can only add $DATA attribute to the end of a file record.\n"); + return STATUS_NOT_IMPLEMENTED; + } + + AttributeAddress->Type = AttributeData; + AttributeAddress->Length = ResidentHeaderLength; + AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, ATTR_RECORD_ALIGNMENT); + AttributeAddress->Resident.ValueLength = 0; + AttributeAddress->Resident.ValueOffset = ResidentHeaderLength; + + // for unnamed $DATA attributes, NameOffset equals header length + AttributeAddress->NameOffset = ResidentHeaderLength; + AttributeAddress->Instance = FileRecord->NextAttributeNumber++; + + // 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 AddFileName +* @implemented +* +* Adds a $FILE_NAME attribute to a given FileRecord. +* +* @param FileRecord +* Pointer to a complete file record to add the attribute to. Caller is responsible for +* ensuring FileRecord is large enough to contain $FILE_NAME. +* +* @param AttributeAddress +* Pointer to the region of memory that will receive the $FILE_NAME attribute. +* This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord). +* +* @param DeviceExt +* Points to the target disk's DEVICE_EXTENSION. +* +* @param FileObject +* Pointer to the FILE_OBJECT which represents the new name. +* This parameter is used to determine the filename and parent directory. +* +* @param CaseSensitive +* Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE +* if an application opened the file with the FILE_FLAG_POSIX_SEMANTICS flag. +* +* @param ParentMftIndex +* Pointer to a ULONGLONG which will receive the index of the parent directory. +* +* @return +* STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end +* of the given file record. +* +* @remarks +* Only adding the attribute to the end of the file record is supported; AttributeAddress must +* be of type AttributeEnd. +* As it's implemented, this function is only intended to assist in creating new file records. It +* could be made more general-purpose by considering file records with an $ATTRIBUTE_LIST. +* It's the caller's responsibility to ensure the given file record has enough memory allocated +* for the attribute. +*/ +NTSTATUS +AddFileName(PFILE_RECORD_HEADER FileRecord, + PNTFS_ATTR_RECORD AttributeAddress, + PDEVICE_EXTENSION DeviceExt, + PFILE_OBJECT FileObject, + BOOLEAN CaseSensitive, + PULONGLONG ParentMftIndex) +{ + ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR); + PFILENAME_ATTRIBUTE FileNameAttribute; + LARGE_INTEGER SystemTime; + ULONG FileRecordEnd = AttributeAddress->Length; + ULONGLONG CurrentMFTIndex = NTFS_FILE_ROOT; + UNICODE_STRING Current, Remaining, FilenameNoPath; + NTSTATUS Status = STATUS_SUCCESS; + ULONG FirstEntry; + + if (AttributeAddress->Type != AttributeEnd) + { + DPRINT1("FIXME: Can only add $FILE_NAME attribute to the end of a file record.\n"); + return STATUS_NOT_IMPLEMENTED; + } + + AttributeAddress->Type = AttributeFileName; + AttributeAddress->Instance = FileRecord->NextAttributeNumber++; + + FileNameAttribute = (PFILENAME_ATTRIBUTE)((LONG_PTR)AttributeAddress + ResidentHeaderLength); + + // set timestamps + KeQuerySystemTime(&SystemTime); + FileNameAttribute->CreationTime = SystemTime.QuadPart; + FileNameAttribute->ChangeTime = SystemTime.QuadPart; + FileNameAttribute->LastWriteTime = SystemTime.QuadPart; + FileNameAttribute->LastAccessTime = SystemTime.QuadPart; + + // 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); + + FsRtlDissectName(FileObject->FileName, &Current, &Remaining); + + FilenameNoPath.Buffer = Current.Buffer; + FilenameNoPath.MaximumLength = FilenameNoPath.Length = Current.Length; + + while (Current.Length != 0) + { + DPRINT1("Current: %wZ\n", &Current); + + if (Remaining.Length != 0) + { + FilenameNoPath.Buffer = Remaining.Buffer; + FilenameNoPath.Length = FilenameNoPath.MaximumLength = Remaining.Length; + } + + FirstEntry = 0; + Status = NtfsFindMftRecord(DeviceExt, + CurrentMFTIndex, + &Current, + &FirstEntry, + FALSE, + CaseSensitive, + &CurrentMFTIndex); + if (!NT_SUCCESS(Status)) + break; + + if (Remaining.Length == 0 ) + { + if (Current.Length != 0) + { + FilenameNoPath.Buffer = Current.Buffer; + FilenameNoPath.Length = FilenameNoPath.MaximumLength = Current.Length; + } + break; + } + + FsRtlDissectName(Remaining, &Current, &Remaining); + } + + DPRINT1("MFT Index of parent: %I64u\n", CurrentMFTIndex); + + // set reference to parent directory + FileNameAttribute->DirectoryFileReferenceNumber = CurrentMFTIndex; + *ParentMftIndex = CurrentMFTIndex; + + DPRINT1("SequenceNumber: 0x%02x\n", FileRecord->SequenceNumber); + + // The highest 2 bytes should be the sequence number, unless the parent happens to be root + if (CurrentMFTIndex == NTFS_FILE_ROOT) + FileNameAttribute->DirectoryFileReferenceNumber |= (ULONGLONG)NTFS_FILE_ROOT << 48; + else + FileNameAttribute->DirectoryFileReferenceNumber |= (ULONGLONG)FileRecord->SequenceNumber << 48; + + DPRINT1("FileNameAttribute->DirectoryFileReferenceNumber: 0x%016I64x\n", FileNameAttribute->DirectoryFileReferenceNumber); + + FileNameAttribute->NameLength = FilenameNoPath.Length / sizeof(WCHAR); + RtlCopyMemory(FileNameAttribute->Name, FilenameNoPath.Buffer, FilenameNoPath.Length); + + // For now, we're emulating the way Windows behaves when 8.3 name generation is disabled + // TODO: add DOS Filename as needed + if (!CaseSensitive && RtlIsNameLegalDOS8Dot3(&FilenameNoPath, NULL, NULL)) + FileNameAttribute->NameType = NTFS_FILE_NAME_WIN32_AND_DOS; + else + FileNameAttribute->NameType = NTFS_FILE_NAME_POSIX; + + FileRecord->LinkCount++; + + AttributeAddress->Length = ResidentHeaderLength + + FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + FilenameNoPath.Length; + AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, ATTR_RECORD_ALIGNMENT); + + AttributeAddress->Resident.ValueLength = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + FilenameNoPath.Length; + AttributeAddress->Resident.ValueOffset = ResidentHeaderLength; + AttributeAddress->Resident.Flags = RA_INDEXED; + + // 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; +} + +/** +* @name AddIndexAllocation +* @implemented +* +* Adds an $INDEX_ALLOCATION 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. +* +* @param AttributeAddress +* Pointer to the region of memory that will receive the $INDEX_ALLOCATION attribute. +* This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord). +* +* @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, or if the file record isn't large enough for the attribute. +* +* @remarks +* Only adding the attribute to the end of the file record is supported; AttributeAddress must +* be of type AttributeEnd. +* This could be improved by adding an $ATTRIBUTE_LIST to the file record if there's not enough space. +* +*/ +NTSTATUS +AddIndexAllocation(PNTFS_VCB Vcb, + PFILE_RECORD_HEADER FileRecord, + PNTFS_ATTR_RECORD AttributeAddress, + PCWSTR Name, + USHORT NameLength) +{ + ULONG RecordLength; + ULONG FileRecordEnd; + ULONG NameOffset; + ULONG DataRunOffset; + ULONG BytesAvailable; + + if (AttributeAddress->Type != AttributeEnd) + { + DPRINT1("FIXME: Can only add $INDEX_ALLOCATION attribute to the end of a file record.\n"); + return STATUS_NOT_IMPLEMENTED; + } + + // Calculate the name offset + NameOffset = FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize); + + // Calculate the offset to the first data run + DataRunOffset = (sizeof(WCHAR) * NameLength) + NameOffset; + // The data run offset must be aligned to a 4-byte boundary + DataRunOffset = ALIGN_UP_BY(DataRunOffset, DATA_RUN_ALIGNMENT); + + // Calculate the length of the new attribute; the empty data run will consist of a single byte + RecordLength = DataRunOffset + 1; + + // The size of the attribute itself must be aligned to an 8 - byte boundary + RecordLength = ALIGN_UP_BY(RecordLength, ATTR_RECORD_ALIGNMENT); + + // Back up the last 4-bytes of the file record (even though this value doesn't matter) + FileRecordEnd = AttributeAddress->Length; + + // Make sure the file record can contain the new attribute + BytesAvailable = Vcb->NtfsInfo.BytesPerFileRecord - FileRecord->BytesInUse; + if (BytesAvailable < RecordLength) + { + DPRINT1("FIXME: Not enough room in file record for index allocation attribute!\n"); + return STATUS_NOT_IMPLEMENTED; + } + + // Set fields of attribute header + RtlZeroMemory(AttributeAddress, RecordLength); + + AttributeAddress->Type = AttributeIndexAllocation; + AttributeAddress->Length = RecordLength; + AttributeAddress->IsNonResident = TRUE; + AttributeAddress->NameLength = NameLength; + AttributeAddress->NameOffset = NameOffset; + AttributeAddress->Instance = FileRecord->NextAttributeNumber++; + + AttributeAddress->NonResident.MappingPairsOffset = DataRunOffset; + AttributeAddress->NonResident.HighestVCN = (LONGLONG)-1; + + // Set the name + RtlCopyMemory((PCHAR)((ULONG_PTR)AttributeAddress + NameOffset), Name, NameLength * sizeof(WCHAR)); + + // 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 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 +* +* Adds a run of allocated clusters to a non-resident attribute. +* +* @param Vcb +* Pointer to an NTFS_VCB for the destination volume. +* +* @param AttrContext +* Pointer to an NTFS_ATTR_CONTEXT describing the destination attribute. +* +* @param AttrOffset +* Byte offset of the destination attribute relative to its file record. +* +* @param FileRecord +* Pointer to a complete copy of the file record containing the destination attribute. Must be at least +* Vcb->NtfsInfo.BytesPerFileRecord bytes long. +* +* @param NextAssignedCluster +* Logical cluster number of the start of the data run being added. +* +* @param RunLength +* How many clusters are in the data run being added. Can't be 0. +* +* @return +* STATUS_SUCCESS on success. STATUS_INVALID_PARAMETER if AttrContext describes a resident attribute. +* STATUS_INSUFFICIENT_RESOURCES if ConvertDataRunsToLargeMCB() fails or if we fail to allocate a +* buffer for the new data runs. +* STATUS_INSUFFICIENT_RESOURCES or STATUS_UNSUCCESSFUL if FsRtlAddLargeMcbEntry() fails. +* STATUS_BUFFER_TOO_SMALL if ConvertLargeMCBToDataRuns() fails. +* STATUS_NOT_IMPLEMENTED if we need to migrate the attribute to an attribute list (TODO). +* +* @remarks +* Clusters should have been allocated previously with NtfsAllocateClusters(). +* +* +*/ +NTSTATUS +AddRun(PNTFS_VCB Vcb, + PNTFS_ATTR_CONTEXT AttrContext, + ULONG AttrOffset, + PFILE_RECORD_HEADER FileRecord, + ULONGLONG NextAssignedCluster, + ULONG RunLength) +{ + NTSTATUS Status; + int DataRunMaxLength; + PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset); + ULONG NextAttributeOffset = AttrOffset + AttrContext->pRecord->Length; + ULONGLONG NextVBN = 0; + + PUCHAR RunBuffer; + ULONG RunBufferSize; + + if (!AttrContext->pRecord->IsNonResident) + return STATUS_INVALID_PARAMETER; + + if (AttrContext->pRecord->NonResident.AllocatedSize != 0) + NextVBN = AttrContext->pRecord->NonResident.HighestVCN + 1; + + // Add newly-assigned clusters to mcb + _SEH2_TRY + { + if (!FsRtlAddLargeMcbEntry(&AttrContext->DataRunsMCB, + NextVBN, + NextAssignedCluster, + RunLength)) + { + ExRaiseStatus(STATUS_UNSUCCESSFUL); + } + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + DPRINT1("Failed to add LargeMcb Entry!\n"); + _SEH2_YIELD(return _SEH2_GetExceptionCode()); + } + _SEH2_END; + + RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS); + if (!RunBuffer) + { + DPRINT1("ERROR: Couldn't allocate memory for data runs!\n"); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Convert the map control block back to encoded data runs + ConvertLargeMCBToDataRuns(&AttrContext->DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferSize); + + // Get the amount of free space between the start of the of the first data run and the attribute end + DataRunMaxLength = AttrContext->pRecord->Length - AttrContext->pRecord->NonResident.MappingPairsOffset; + + // Do we need to extend the attribute (or convert to attribute list)? + if (DataRunMaxLength < RunBufferSize) + { + PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset); + PNTFS_ATTR_RECORD NewRecord; + + // Add free space at the end of the file record to DataRunMaxLength + DataRunMaxLength += Vcb->NtfsInfo.BytesPerFileRecord - FileRecord->BytesInUse; + + // Can we resize the attribute? + if (DataRunMaxLength < RunBufferSize) + { + DPRINT1("FIXME: Need to create attribute list! Max Data Run Length available: %d, RunBufferSize: %d\n", DataRunMaxLength, RunBufferSize); + ExFreePoolWithTag(RunBuffer, TAG_NTFS); + return STATUS_NOT_IMPLEMENTED; + } + + // Are there more attributes after the one we're resizing? + if (NextAttribute->Type != AttributeEnd) + { + PNTFS_ATTR_RECORD FinalAttribute; + + // Calculate where to move the trailing attributes + ULONG_PTR MoveTo = (ULONG_PTR)DestinationAttribute + AttrContext->pRecord->NonResident.MappingPairsOffset + RunBufferSize; + MoveTo = ALIGN_UP_BY(MoveTo, ATTR_RECORD_ALIGNMENT); + + DPRINT1("Moving attribute(s) after this one starting with type 0x%lx\n", NextAttribute->Type); + + // Move the trailing attributes; FinalAttribute will point to the end marker + FinalAttribute = MoveAttributes(Vcb, NextAttribute, NextAttributeOffset, MoveTo); + + // set the file record end + SetFileRecordEnd(FileRecord, FinalAttribute, FILE_RECORD_END); + } + + // calculate position of end markers + NextAttributeOffset = AttrOffset + AttrContext->pRecord->NonResident.MappingPairsOffset + RunBufferSize; + NextAttributeOffset = ALIGN_UP_BY(NextAttributeOffset, ATTR_RECORD_ALIGNMENT); + + // Update the length of the destination attribute + DestinationAttribute->Length = NextAttributeOffset - AttrOffset; + + // Create a new copy of the attribute record + NewRecord = ExAllocatePoolWithTag(NonPagedPool, DestinationAttribute->Length, TAG_NTFS); + RtlCopyMemory(NewRecord, AttrContext->pRecord, AttrContext->pRecord->Length); + NewRecord->Length = DestinationAttribute->Length; + + // Free the old copy of the attribute record, which won't be large enough + ExFreePoolWithTag(AttrContext->pRecord, TAG_NTFS); + + // Set the attribute context's record to the new copy + AttrContext->pRecord = NewRecord; + + // if NextAttribute is the AttributeEnd marker + if (NextAttribute->Type == AttributeEnd) + { + // End the file record + NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset); + SetFileRecordEnd(FileRecord, NextAttribute, FILE_RECORD_END); + } + } + + // Update HighestVCN + DestinationAttribute->NonResident.HighestVCN = + AttrContext->pRecord->NonResident.HighestVCN = max(NextVBN - 1 + RunLength, + AttrContext->pRecord->NonResident.HighestVCN); + + // Write data runs to destination attribute + RtlCopyMemory((PVOID)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), + RunBuffer, + RunBufferSize); + + // Update the attribute record in the attribute context + RtlCopyMemory((PVOID)((ULONG_PTR)AttrContext->pRecord + AttrContext->pRecord->NonResident.MappingPairsOffset), + RunBuffer, + RunBufferSize); + + // Update the file record + Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord); + + ExFreePoolWithTag(RunBuffer, TAG_NTFS); + + NtfsDumpDataRuns((PUCHAR)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), 0); + + return Status; +} + +/** +* @name AddStandardInformation +* @implemented +* +* Adds a $STANDARD_INFORMATION attribute to a given FileRecord. +* +* @param FileRecord +* Pointer to a complete file record to add the attribute to. Caller is responsible for +* ensuring FileRecord is large enough to contain $STANDARD_INFORMATION. +* +* @param AttributeAddress +* Pointer to the region of memory that will receive the $STANDARD_INFORMATION attribute. +* This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord). +* +* @return +* STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end +* of the given file record. +* +* @remarks +* Only adding the attribute to the end of the file record is supported; AttributeAddress must +* be of type AttributeEnd. +* As it's implemented, this function is only intended to assist in creating new file records. It +* could be made more general-purpose by considering file records with an $ATTRIBUTE_LIST. +* It's the caller's responsibility to ensure the given file record has enough memory allocated +* for the attribute. +*/ +NTSTATUS +AddStandardInformation(PFILE_RECORD_HEADER FileRecord, + PNTFS_ATTR_RECORD AttributeAddress) +{ + ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR); + PSTANDARD_INFORMATION StandardInfo = (PSTANDARD_INFORMATION)((LONG_PTR)AttributeAddress + ResidentHeaderLength); + LARGE_INTEGER SystemTime; + ULONG FileRecordEnd = AttributeAddress->Length; + + if (AttributeAddress->Type != AttributeEnd) + { + DPRINT1("FIXME: Can only add $STANDARD_INFORMATION attribute to the end of a file record.\n"); + return STATUS_NOT_IMPLEMENTED; + } + + AttributeAddress->Type = AttributeStandardInformation; + AttributeAddress->Length = sizeof(STANDARD_INFORMATION) + ResidentHeaderLength; + AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, ATTR_RECORD_ALIGNMENT); + AttributeAddress->Resident.ValueLength = sizeof(STANDARD_INFORMATION); + AttributeAddress->Resident.ValueOffset = ResidentHeaderLength; + AttributeAddress->Instance = FileRecord->NextAttributeNumber++; + + // set dates and times + KeQuerySystemTime(&SystemTime); + StandardInfo->CreationTime = SystemTime.QuadPart; + StandardInfo->ChangeTime = SystemTime.QuadPart; + StandardInfo->LastWriteTime = SystemTime.QuadPart; + StandardInfo->LastAccessTime = SystemTime.QuadPart; + StandardInfo->FileAttribute = NTFS_FILE_TYPE_ARCHIVE; + + // 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 ConvertDataRunsToLargeMCB +* @implemented +* +* Converts binary data runs to a map control block. +* +* @param DataRun +* Pointer to the run data +* +* @param DataRunsMCB +* Pointer to an unitialized LARGE_MCB structure. +* +* @return +* STATUS_SUCCESS on success, STATUS_INSUFFICIENT_RESOURCES or STATUS_UNSUCCESSFUL if we fail to +* initialize the mcb or add an entry. +* +* @remarks +* Initializes the LARGE_MCB pointed to by DataRunsMCB. If this function succeeds, you +* need to call FsRtlUninitializeLargeMcb() when you're done with DataRunsMCB. This +* function will ensure the LargeMCB has been unitialized in case of failure. +* +*/ +NTSTATUS +ConvertDataRunsToLargeMCB(PUCHAR DataRun, + PLARGE_MCB DataRunsMCB, + PULONGLONG pNextVBN) +{ + LONGLONG DataRunOffset; + ULONGLONG DataRunLength; + LONGLONG DataRunStartLCN; + ULONGLONG LastLCN = 0; + + // Initialize the MCB, potentially catch an exception + _SEH2_TRY{ + FsRtlInitializeLargeMcb(DataRunsMCB, NonPagedPool); + } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + _SEH2_YIELD(return _SEH2_GetExceptionCode()); + } _SEH2_END; + + while (*DataRun != 0) + { + DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength); + + if (DataRunOffset != -1) + { + // Normal data run. + DataRunStartLCN = LastLCN + DataRunOffset; + LastLCN = DataRunStartLCN; + + _SEH2_TRY{ + if (!FsRtlAddLargeMcbEntry(DataRunsMCB, + *pNextVBN, + DataRunStartLCN, + DataRunLength)) + { + ExRaiseStatus(STATUS_UNSUCCESSFUL); + } + } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + FsRtlUninitializeLargeMcb(DataRunsMCB); + _SEH2_YIELD(return _SEH2_GetExceptionCode()); + } _SEH2_END; + + } + + *pNextVBN += DataRunLength; + } + + return STATUS_SUCCESS; +} + +/** +* @name ConvertLargeMCBToDataRuns +* @implemented +* +* Converts a map control block to a series of encoded data runs (used by non-resident attributes). +* +* @param DataRunsMCB +* Pointer to a LARGE_MCB structure describing the data runs. +* +* @param RunBuffer +* Pointer to the buffer that will receive the encoded data runs. +* +* @param MaxBufferSize +* Size of RunBuffer, in bytes. +* +* @param UsedBufferSize +* Pointer to a ULONG that will receive the size of the data runs in bytes. Can't be NULL. +* +* @return +* STATUS_SUCCESS on success, STATUS_BUFFER_TOO_SMALL if RunBuffer is too small to contain the +* complete output. +* +*/ +NTSTATUS +ConvertLargeMCBToDataRuns(PLARGE_MCB DataRunsMCB, + PUCHAR RunBuffer, + ULONG MaxBufferSize, + PULONG UsedBufferSize) +{ + NTSTATUS Status = STATUS_SUCCESS; + ULONG RunBufferOffset = 0; + LONGLONG DataRunOffset; + ULONGLONG LastLCN = 0; + LONGLONG Vbn, Lbn, Count; + ULONG i; + + + DPRINT("\t[Vbn, Lbn, Count]\n"); + + // convert each mcb entry to a data run + for (i = 0; FsRtlGetNextLargeMcbEntry(DataRunsMCB, i, &Vbn, &Lbn, &Count); i++) + { + UCHAR DataRunOffsetSize = 0; + UCHAR DataRunLengthSize = 0; + UCHAR ControlByte = 0; + + // [vbn, lbn, count] + DPRINT("\t[%I64d, %I64d,%I64d]\n", Vbn, Lbn, Count); + + // TODO: check for holes and convert to sparse runs + DataRunOffset = Lbn - LastLCN; + LastLCN = Lbn; + + // now we need to determine how to represent DataRunOffset with the minimum number of bytes + DPRINT("Determining how many bytes needed to represent %I64x\n", DataRunOffset); + DataRunOffsetSize = GetPackedByteCount(DataRunOffset, TRUE); + DPRINT("%d bytes needed.\n", DataRunOffsetSize); + + // determine how to represent DataRunLengthSize with the minimum number of bytes + DPRINT("Determining how many bytes needed to represent %I64x\n", Count); + DataRunLengthSize = GetPackedByteCount(Count, TRUE); + DPRINT("%d bytes needed.\n", DataRunLengthSize); + + // ensure the next data run + end marker would be <= Max buffer size + if (RunBufferOffset + 2 + DataRunLengthSize + DataRunOffsetSize > MaxBufferSize) + { + Status = STATUS_BUFFER_TOO_SMALL; + DPRINT1("FIXME: Ran out of room in buffer for data runs!\n"); + break; + } + + // pack and copy the control byte + ControlByte = (DataRunOffsetSize << 4) + DataRunLengthSize; + RunBuffer[RunBufferOffset++] = ControlByte; + + // copy DataRunLength + RtlCopyMemory(RunBuffer + RunBufferOffset, &Count, DataRunLengthSize); + RunBufferOffset += DataRunLengthSize; + + // copy DataRunOffset + RtlCopyMemory(RunBuffer + RunBufferOffset, &DataRunOffset, DataRunOffsetSize); + RunBufferOffset += DataRunOffsetSize; + } + + // End of data runs + RunBuffer[RunBufferOffset++] = 0; + + *UsedBufferSize = RunBufferOffset; + DPRINT("New Size of DataRuns: %ld\n", *UsedBufferSize); + + return Status; +} + PUCHAR DecodeRun(PUCHAR DataRun, LONGLONG *DataRunOffset, @@ -93,6 +1020,197 @@ FindRun(PNTFS_ATTR_RECORD NresAttr, return TRUE; } +/** +* @name FreeClusters +* @implemented +* +* Shrinks the allocation size of a non-resident attribute by a given number of clusters. +* Frees the clusters from the volume's $BITMAP file as well as the attribute's data runs. +* +* @param Vcb +* Pointer to an NTFS_VCB for the destination volume. +* +* @param AttrContext +* Pointer to an NTFS_ATTR_CONTEXT describing the attribute from which the clusters will be freed. +* +* @param AttrOffset +* Byte offset of the destination attribute relative to its file record. +* +* @param FileRecord +* Pointer to a complete copy of the file record containing the attribute. Must be at least +* Vcb->NtfsInfo.BytesPerFileRecord bytes long. +* +* @param ClustersToFree +* Number of clusters that should be freed from the end of the data stream. Must be no more +* Than the number of clusters assigned to the attribute (HighestVCN + 1). +* +* @return +* STATUS_SUCCESS on success. STATUS_INVALID_PARAMETER if AttrContext describes a resident attribute, +* or if the caller requested more clusters be freed than the attribute has been allocated. +* STATUS_INSUFFICIENT_RESOURCES if allocating a buffer for the data runs fails or +* if ConvertDataRunsToLargeMCB() fails. +* STATUS_BUFFER_TOO_SMALL if ConvertLargeMCBToDataRuns() fails. +* +* +*/ +NTSTATUS +FreeClusters(PNTFS_VCB Vcb, + PNTFS_ATTR_CONTEXT AttrContext, + ULONG AttrOffset, + PFILE_RECORD_HEADER FileRecord, + ULONG ClustersToFree) +{ + NTSTATUS Status = STATUS_SUCCESS; + ULONG ClustersLeftToFree = ClustersToFree; + + PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset); + ULONG NextAttributeOffset = AttrOffset + AttrContext->pRecord->Length; + PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset); + + PUCHAR RunBuffer; + ULONG RunBufferSize = 0; + + PFILE_RECORD_HEADER BitmapRecord; + PNTFS_ATTR_CONTEXT DataContext; + ULONGLONG BitmapDataSize; + PUCHAR BitmapData; + RTL_BITMAP Bitmap; + ULONG LengthWritten; + + if (!AttrContext->pRecord->IsNonResident) + { + return STATUS_INVALID_PARAMETER; + } + + // Read the $Bitmap file + BitmapRecord = ExAllocatePoolWithTag(NonPagedPool, + Vcb->NtfsInfo.BytesPerFileRecord, + TAG_NTFS); + if (BitmapRecord == NULL) + { + DPRINT1("Error: Unable to allocate memory for bitmap file record!\n"); + return STATUS_NO_MEMORY; + } + + Status = ReadFileRecord(Vcb, NTFS_FILE_BITMAP, BitmapRecord); + if (!NT_SUCCESS(Status)) + { + DPRINT1("Error: Unable to read file record for bitmap!\n"); + ExFreePoolWithTag(BitmapRecord, TAG_NTFS); + return 0; + } + + Status = FindAttribute(Vcb, BitmapRecord, AttributeData, L"", 0, &DataContext, NULL); + if (!NT_SUCCESS(Status)) + { + DPRINT1("Error: Unable to find data attribute for bitmap file!\n"); + ExFreePoolWithTag(BitmapRecord, TAG_NTFS); + return 0; + } + + BitmapDataSize = AttributeDataLength(DataContext->pRecord); + BitmapDataSize = min(BitmapDataSize, ULONG_MAX); + ASSERT((BitmapDataSize * 8) >= Vcb->NtfsInfo.ClusterCount); + BitmapData = ExAllocatePoolWithTag(NonPagedPool, ROUND_UP(BitmapDataSize, Vcb->NtfsInfo.BytesPerSector), TAG_NTFS); + if (BitmapData == NULL) + { + DPRINT1("Error: Unable to allocate memory for bitmap file data!\n"); + ReleaseAttributeContext(DataContext); + ExFreePoolWithTag(BitmapRecord, TAG_NTFS); + return 0; + } + + ReadAttribute(Vcb, DataContext, 0, (PCHAR)BitmapData, (ULONG)BitmapDataSize); + + RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, Vcb->NtfsInfo.ClusterCount); + + // free clusters in $BITMAP file + while (ClustersLeftToFree > 0) + { + LONGLONG LargeVbn, LargeLbn; + + if (!FsRtlLookupLastLargeMcbEntry(&AttrContext->DataRunsMCB, &LargeVbn, &LargeLbn)) + { + Status = STATUS_INVALID_PARAMETER; + DPRINT1("DRIVER ERROR: FreeClusters called to free %lu clusters, which is %lu more clusters than are assigned to attribute!", + ClustersToFree, + ClustersLeftToFree); + break; + } + + if (LargeLbn != -1) + { + // deallocate this cluster + RtlClearBits(&Bitmap, LargeLbn, 1); + } + FsRtlTruncateLargeMcb(&AttrContext->DataRunsMCB, AttrContext->pRecord->NonResident.HighestVCN); + + // decrement HighestVCN, but don't let it go below 0 + AttrContext->pRecord->NonResident.HighestVCN = min(AttrContext->pRecord->NonResident.HighestVCN, AttrContext->pRecord->NonResident.HighestVCN - 1); + ClustersLeftToFree--; + } + + // update $BITMAP file on disk + Status = WriteAttribute(Vcb, DataContext, 0, BitmapData, (ULONG)BitmapDataSize, &LengthWritten, FileRecord); + if (!NT_SUCCESS(Status)) + { + ReleaseAttributeContext(DataContext); + ExFreePoolWithTag(BitmapData, TAG_NTFS); + ExFreePoolWithTag(BitmapRecord, TAG_NTFS); + return Status; + } + + ReleaseAttributeContext(DataContext); + ExFreePoolWithTag(BitmapData, TAG_NTFS); + ExFreePoolWithTag(BitmapRecord, TAG_NTFS); + + // Save updated data runs to file record + + // Allocate some memory for a new RunBuffer + RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS); + if (!RunBuffer) + { + DPRINT1("ERROR: Couldn't allocate memory for data runs!\n"); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Convert the map control block back to encoded data runs + ConvertLargeMCBToDataRuns(&AttrContext->DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferSize); + + // Update HighestVCN + DestinationAttribute->NonResident.HighestVCN = AttrContext->pRecord->NonResident.HighestVCN; + + // Write data runs to destination attribute + RtlCopyMemory((PVOID)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), + RunBuffer, + RunBufferSize); + + // Is DestinationAttribute the last attribute in the file record? + if (NextAttribute->Type == AttributeEnd) + { + // update attribute length + DestinationAttribute->Length = ALIGN_UP_BY(AttrContext->pRecord->NonResident.MappingPairsOffset + RunBufferSize, + ATTR_RECORD_ALIGNMENT); + + ASSERT(DestinationAttribute->Length <= AttrContext->pRecord->Length); + + AttrContext->pRecord->Length = DestinationAttribute->Length; + + // write end markers + NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)DestinationAttribute + DestinationAttribute->Length); + SetFileRecordEnd(FileRecord, NextAttribute, FILE_RECORD_END); + } + + // Update the file record + Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord); + + ExFreePoolWithTag(RunBuffer, TAG_NTFS); + + NtfsDumpDataRuns((PUCHAR)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), 0); + + return Status; +} + static NTSTATUS InternalReadNonResidentAttributes(PFIND_ATTR_CONTXT Context) @@ -119,7 +1237,7 @@ InternalReadNonResidentAttributes(PFIND_ATTR_CONTXT Context) } ListContext = PrepareAttributeContext(Attribute); - ListSize = AttributeDataLength(&ListContext->Record); + ListSize = AttributeDataLength(ListContext->pRecord); if (ListSize > 0xFFFFFFFF) { ReleaseAttributeContext(ListContext); @@ -150,6 +1268,8 @@ static PNTFS_ATTR_RECORD InternalGetNextAttribute(PFIND_ATTR_CONTXT Context) { + PNTFS_ATTR_RECORD NextAttribute; + if (Context->CurrAttr == (PVOID)-1) { return NULL; @@ -165,7 +1285,18 @@ InternalGetNextAttribute(PFIND_ATTR_CONTXT Context) return NULL; } - Context->CurrAttr = (PNTFS_ATTR_RECORD)((ULONG_PTR)Context->CurrAttr + Context->CurrAttr->Length); + NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Context->CurrAttr + Context->CurrAttr->Length); + + if (NextAttribute > Context->LastAttr || NextAttribute < Context->FirstAttr) + { + DPRINT1("Broken length: 0x%lx!\n", Context->CurrAttr->Length); + Context->CurrAttr = (PVOID)-1; + return NULL; + } + + Context->Offset += ((ULONG_PTR)NextAttribute - (ULONG_PTR)Context->CurrAttr); + Context->CurrAttr = NextAttribute; + if (Context->CurrAttr < Context->LastAttr && Context->CurrAttr->Type != AttributeEnd) { @@ -186,7 +1317,9 @@ InternalGetNextAttribute(PFIND_ATTR_CONTXT Context) } else if (Context->CurrAttr->Length != 0) { - Context->CurrAttr = (PNTFS_ATTR_RECORD)((ULONG_PTR)Context->CurrAttr + Context->CurrAttr->Length); + NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Context->CurrAttr + Context->CurrAttr->Length); + Context->Offset += ((ULONG_PTR)NextAttribute - (ULONG_PTR)Context->CurrAttr); + Context->CurrAttr = NextAttribute; } else { @@ -223,6 +1356,7 @@ FindFirstAttribute(PFIND_ATTR_CONTXT Context, Context->LastAttr = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->BytesInUse); Context->NonResidentStart = NULL; Context->NonResidentEnd = NULL; + Context->Offset = FileRecord->AttributeOffset; if (Context->FirstAttr->Type == AttributeEnd) { @@ -246,6 +1380,7 @@ FindFirstAttribute(PFIND_ATTR_CONTXT Context, else { *Attribute = Context->CurrAttr; + Context->Offset = (UCHAR*)Context->CurrAttr - (UCHAR*)FileRecord; } return STATUS_SUCCESS; @@ -307,7 +1442,9 @@ NtfsDumpFileNameAttribute(PNTFS_ATTR_RECORD Attribute) FileNameAttr = (PFILENAME_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset); DbgPrint(" (%x) '%.*S' ", FileNameAttr->NameType, FileNameAttr->NameLength, FileNameAttr->Name); - DbgPrint(" '%x' ", FileNameAttr->FileAttributes); + DbgPrint(" '%x' \n", FileNameAttr->FileAttributes); + DbgPrint(" AllocatedSize: %I64u\nDataSize: %I64u\n", FileNameAttr->AllocatedSize, FileNameAttr->DataSize); + DbgPrint(" File reference: 0x%016I64x\n", FileNameAttr->DirectoryFileReferenceNumber); } @@ -364,23 +1501,73 @@ VOID NtfsDumpIndexRootAttribute(PNTFS_ATTR_RECORD Attribute) { PINDEX_ROOT_ATTRIBUTE IndexRootAttr; + ULONG CurrentOffset; + ULONG CurrentNode; IndexRootAttr = (PINDEX_ROOT_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset); if (IndexRootAttr->AttributeType == AttributeFileName) ASSERT(IndexRootAttr->CollationRule == COLLATION_FILE_NAME); - DbgPrint(" $INDEX_ROOT (%uB, %u) ", IndexRootAttr->SizeOfEntry, IndexRootAttr->ClustersPerIndexRecord); + DbgPrint(" $INDEX_ROOT (%u bytes per index record, %u clusters) ", IndexRootAttr->SizeOfEntry, IndexRootAttr->ClustersPerIndexRecord); if (IndexRootAttr->Header.Flags == INDEX_ROOT_SMALL) { - DbgPrint(" (small) "); + DbgPrint(" (small)\n"); } else { ASSERT(IndexRootAttr->Header.Flags == INDEX_ROOT_LARGE); - DbgPrint(" (large) "); + DbgPrint(" (large)\n"); } + + DbgPrint(" Offset to first index: 0x%lx\n Total size of index entries: 0x%lx\n Allocated size of node: 0x%lx\n", + IndexRootAttr->Header.FirstEntryOffset, + IndexRootAttr->Header.TotalSizeOfEntries, + IndexRootAttr->Header.AllocatedSize); + CurrentOffset = IndexRootAttr->Header.FirstEntryOffset; + CurrentNode = 0; + // print details of every node in the index + while (CurrentOffset < IndexRootAttr->Header.TotalSizeOfEntries) + { + PINDEX_ENTRY_ATTRIBUTE currentIndexExtry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexRootAttr + 0x10 + CurrentOffset); + DbgPrint(" Index Node Entry %lu", CurrentNode++); + if (BooleanFlagOn(currentIndexExtry->Flags, NTFS_INDEX_ENTRY_NODE)) + DbgPrint(" (Branch)"); + else + DbgPrint(" (Leaf)"); + if (BooleanFlagOn(currentIndexExtry->Flags, NTFS_INDEX_ENTRY_END)) + { + DbgPrint(" (Dummy Key)"); + } + DbgPrint("\n File Reference: 0x%016I64x\n", currentIndexExtry->Data.Directory.IndexedFile); + DbgPrint(" Index Entry Length: 0x%x\n", currentIndexExtry->Length); + DbgPrint(" Index Key Length: 0x%x\n", currentIndexExtry->KeyLength); + + // if this isn't the final (dummy) node, print info about the key (Filename attribute) + if (!(currentIndexExtry->Flags & NTFS_INDEX_ENTRY_END)) + { + UNICODE_STRING Name; + DbgPrint(" Parent File Reference: 0x%016I64x\n", currentIndexExtry->FileName.DirectoryFileReferenceNumber); + DbgPrint(" $FILENAME indexed: "); + Name.Length = currentIndexExtry->FileName.NameLength * sizeof(WCHAR); + Name.MaximumLength = Name.Length; + Name.Buffer = currentIndexExtry->FileName.Name; + DbgPrint("'%wZ'\n", &Name); + } + + // if this node has a sub-node beneath it + if (currentIndexExtry->Flags & NTFS_INDEX_ENTRY_NODE) + { + // Print the VCN of the sub-node + PULONGLONG SubNodeVCN = (PULONGLONG)((ULONG_PTR)currentIndexExtry + currentIndexExtry->Length - sizeof(ULONGLONG)); + DbgPrint(" VCN of sub-node: 0x%llx\n", *SubNodeVCN); + } + + CurrentOffset += currentIndexExtry->Length; + ASSERT(currentIndexExtry->Length); + } + } @@ -481,15 +1668,83 @@ NtfsDumpAttribute(PDEVICE_EXTENSION Vcb, { FindRun(Attribute,0,&lcn, &runcount); - DbgPrint(" AllocatedSize %I64u DataSize %I64u\n", - Attribute->NonResident.AllocatedSize, Attribute->NonResident.DataSize); + DbgPrint(" AllocatedSize %I64u DataSize %I64u InitilizedSize %I64u\n", + Attribute->NonResident.AllocatedSize, Attribute->NonResident.DataSize, Attribute->NonResident.InitializedSize); DbgPrint(" logical clusters: %I64u - %I64u\n", lcn, lcn + runcount - 1); } + else + DbgPrint(" %u bytes of data\n", Attribute->Resident.ValueLength); } } +VOID NtfsDumpDataRunData(PUCHAR DataRun) +{ + UCHAR DataRunOffsetSize; + UCHAR DataRunLengthSize; + CHAR i; + + DbgPrint("%02x ", *DataRun); + + if (*DataRun == 0) + return; + + DataRunOffsetSize = (*DataRun >> 4) & 0xF; + DataRunLengthSize = *DataRun & 0xF; + + DataRun++; + for (i = 0; i < DataRunLengthSize; i++) + { + DbgPrint("%02x ", *DataRun); + DataRun++; + } + + for (i = 0; i < DataRunOffsetSize; i++) + { + DbgPrint("%02x ", *DataRun); + DataRun++; + } + + NtfsDumpDataRunData(DataRun); +} + + +VOID +NtfsDumpDataRuns(PVOID StartOfRun, + ULONGLONG CurrentLCN) +{ + PUCHAR DataRun = StartOfRun; + LONGLONG DataRunOffset; + ULONGLONG DataRunLength; + + if (CurrentLCN == 0) + { + DPRINT1("Dumping data runs.\n\tData:\n\t\t"); + NtfsDumpDataRunData(StartOfRun); + DbgPrint("\n\tRuns:\n\t\tOff\t\tLCN\t\tLength\n"); + } + + DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength); + + if (DataRunOffset != -1) + CurrentLCN += DataRunOffset; + + DbgPrint("\t\t%I64d\t", DataRunOffset); + if (DataRunOffset < 99999) + DbgPrint("\t"); + DbgPrint("%I64u\t", CurrentLCN); + if (CurrentLCN < 99999) + DbgPrint("\t"); + DbgPrint("%I64u\n", DataRunLength); + + if (*DataRun == 0) + DbgPrint("\t\t00\n"); + else + NtfsDumpDataRuns(DataRun, CurrentLCN); +} + + VOID NtfsDumpFileAttributes(PDEVICE_EXTENSION Vcb, PFILE_RECORD_HEADER FileRecord) @@ -541,6 +1796,105 @@ GetFileNameFromRecord(PDEVICE_EXTENSION Vcb, return NULL; } +/** +* GetPackedByteCount +* Returns the minimum number of bytes needed to represent the value of a +* 64-bit number. Used to encode data runs. +*/ +UCHAR +GetPackedByteCount(LONGLONG NumberToPack, + BOOLEAN IsSigned) +{ + if (!IsSigned) + { + if (NumberToPack >= 0x0100000000000000) + return 8; + if (NumberToPack >= 0x0001000000000000) + return 7; + if (NumberToPack >= 0x0000010000000000) + return 6; + if (NumberToPack >= 0x0000000100000000) + return 5; + if (NumberToPack >= 0x0000000001000000) + return 4; + if (NumberToPack >= 0x0000000000010000) + return 3; + if (NumberToPack >= 0x0000000000000100) + return 2; + return 1; + } + + if (NumberToPack > 0) + { + // we have to make sure the number that gets encoded won't be interpreted as negative + if (NumberToPack >= 0x0080000000000000) + return 8; + if (NumberToPack >= 0x0000800000000000) + return 7; + if (NumberToPack >= 0x0000008000000000) + return 6; + if (NumberToPack >= 0x0000000080000000) + return 5; + if (NumberToPack >= 0x0000000000800000) + return 4; + if (NumberToPack >= 0x0000000000008000) + return 3; + if (NumberToPack >= 0x0000000000000080) + return 2; + } + else + { + // negative number + if (NumberToPack <= 0xff80000000000000) + return 8; + if (NumberToPack <= 0xffff800000000000) + return 7; + if (NumberToPack <= 0xffffff8000000000) + return 6; + if (NumberToPack <= 0xffffffff80000000) + return 5; + if (NumberToPack <= 0xffffffffff800000) + return 4; + if (NumberToPack <= 0xffffffffffff8000) + return 3; + if (NumberToPack <= 0xffffffffffffff80) + return 2; + } + return 1; +} + +NTSTATUS +GetLastClusterInDataRun(PDEVICE_EXTENSION Vcb, PNTFS_ATTR_RECORD Attribute, PULONGLONG LastCluster) +{ + LONGLONG DataRunOffset; + ULONGLONG DataRunLength; + LONGLONG DataRunStartLCN; + + ULONGLONG LastLCN = 0; + PUCHAR DataRun = (PUCHAR)Attribute + Attribute->NonResident.MappingPairsOffset; + + if (!Attribute->IsNonResident) + return STATUS_INVALID_PARAMETER; + + while (1) + { + DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength); + + if (DataRunOffset != -1) + { + // Normal data run. + DataRunStartLCN = LastLCN + DataRunOffset; + LastLCN = DataRunStartLCN; + *LastCluster = LastLCN + DataRunLength - 1; + } + + if (*DataRun == 0) + break; + } + + return STATUS_SUCCESS; +} + PSTANDARD_INFORMATION GetStandardInformationFromRecord(PDEVICE_EXTENSION Vcb, PFILE_RECORD_HEADER FileRecord) @@ -567,6 +1921,25 @@ GetStandardInformationFromRecord(PDEVICE_EXTENSION Vcb, return NULL; } +/** +* @name GetFileNameAttributeLength +* @implemented +* +* Returns the size of a given FILENAME_ATTRIBUTE, in bytes. +* +* @param FileNameAttribute +* Pointer to a FILENAME_ATTRIBUTE to determine the size of. +* +* @remarks +* The length of a FILENAME_ATTRIBUTE is variable and is dependent on the length of the file name stored at the end. +* This function operates on the FILENAME_ATTRIBUTE proper, so don't try to pass it a PNTFS_ATTR_RECORD. +*/ +ULONG GetFileNameAttributeLength(PFILENAME_ATTRIBUTE FileNameAttribute) +{ + ULONG Length = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + (FileNameAttribute->NameLength * sizeof(WCHAR)); + return Length; +} + PFILENAME_ATTRIBUTE GetBestFileNameFromRecord(PDEVICE_EXTENSION Vcb, PFILE_RECORD_HEADER FileRecord) diff --git a/drivers/filesystems/ntfs/blockdev.c b/drivers/filesystems/ntfs/blockdev.c index 6c90131b19f..70658b03c3f 100644 --- a/drivers/filesystems/ntfs/blockdev.c +++ b/drivers/filesystems/ntfs/blockdev.c @@ -20,7 +20,8 @@ * PROJECT: ReactOS kernel * FILE: drivers/filesystem/ntfs/blockdev.c * PURPOSE: NTFS filesystem driver - * PROGRAMMER: Eric Kohl + * PROGRAMMERS: Eric Kohl + * Trevor Thompson */ /* INCLUDES *****************************************************************/ @@ -51,7 +52,7 @@ NtfsReadDisk(IN PDEVICE_OBJECT DeviceObject, BOOLEAN AllocatedBuffer = FALSE; PUCHAR ReadBuffer = Buffer; - DPRINT("NtfsReadDisk(%p, %I64x, %u, %u, %p, %d)\n", DeviceObject, StartingOffset, Length, SectorSize, Buffer, Override); + DPRINT("NtfsReadDisk(%p, %I64x, %lu, %lu, %p, %d)\n", DeviceObject, StartingOffset, Length, SectorSize, Buffer, Override); KeInitializeEvent(&Event, NotificationEvent, @@ -129,6 +130,180 @@ NtfsReadDisk(IN PDEVICE_OBJECT DeviceObject, return Status; } +/** +* @name NtfsWriteDisk +* @implemented +* +* Writes data from the given buffer to the given DeviceObject. +* +* @param DeviceObject +* Device to write to +* +* @param StartingOffset +* Offset, in bytes, from the start of the device object where the data will be written +* +* @param Length +* How much data will be written, in bytes +* +* @param SectorSize +* Size of the sector on the disk that the write must be aligned to +* +* @param Buffer +* The data that's being written to the device +* +* @return +* STATUS_SUCCESS in case of success, STATUS_INSUFFICIENT_RESOURCES if a memory allocation failed, +* or whatever status IoCallDriver() sets. +* +* @remarks Called by NtfsWriteFile(). May perform a read-modify-write operation if the +* requested write is not sector-aligned. +* +*/ +NTSTATUS +NtfsWriteDisk(IN PDEVICE_OBJECT DeviceObject, + IN LONGLONG StartingOffset, + IN ULONG Length, + IN ULONG SectorSize, + IN const PUCHAR Buffer) +{ + IO_STATUS_BLOCK IoStatus; + LARGE_INTEGER Offset; + KEVENT Event; + PIRP Irp; + NTSTATUS Status; + ULONGLONG RealWriteOffset; + ULONG RealLength; + BOOLEAN AllocatedBuffer = FALSE; + PUCHAR TempBuffer = NULL; + + DPRINT("NtfsWriteDisk(%p, %I64x, %lu, %lu, %p)\n", DeviceObject, StartingOffset, Length, SectorSize, Buffer); + + if (Length == 0) + return STATUS_SUCCESS; + + RealWriteOffset = (ULONGLONG)StartingOffset; + RealLength = Length; + + // Does the write need to be adjusted to be sector-aligned? + if ((RealWriteOffset % SectorSize) != 0 || (RealLength % SectorSize) != 0) + { + ULONGLONG relativeOffset; + + // We need to do a read-modify-write. We'll start be copying the entire + // contents of every sector that will be overwritten. + // TODO: Optimize (read no more than necessary) + + RealWriteOffset = ROUND_DOWN(StartingOffset, SectorSize); + RealLength = ROUND_UP(Length, SectorSize); + + // Would the end of our sector-aligned write fall short of the requested write? + if (RealWriteOffset + RealLength < StartingOffset + Length) + { + RealLength += SectorSize; + } + + // Did we underestimate the memory required somehow? + if (RealLength + RealWriteOffset < StartingOffset + Length) + { + DPRINT1("\a\t\t\t\t\tFIXME: calculated less memory than needed!\n"); + DPRINT1("StartingOffset: %lu\tLength: %lu\tRealWriteOffset: %lu\tRealLength: %lu\n", + StartingOffset, Length, RealWriteOffset, RealLength); + + RealLength += SectorSize; + } + + // Allocate a buffer to copy the existing data to + TempBuffer = ExAllocatePoolWithTag(NonPagedPool, RealLength, TAG_NTFS); + + // Did we fail to allocate it? + if (TempBuffer == NULL) + { + DPRINT1("Not enough memory!\n"); + + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Read the sectors we'll be overwriting into TempBuffer + Status = NtfsReadDisk(DeviceObject, RealWriteOffset, RealLength, SectorSize, TempBuffer, FALSE); + + // Did we fail the read? + if (!NT_SUCCESS(Status)) + { + RtlSecureZeroMemory(TempBuffer, RealLength); + ExFreePoolWithTag(TempBuffer, TAG_NTFS); + return Status; + } + + // Calculate where the new data should be written to, relative to the start of TempBuffer + relativeOffset = StartingOffset - RealWriteOffset; + + // Modify the tempbuffer with the data being read + RtlCopyMemory(TempBuffer + relativeOffset, Buffer, Length); + + AllocatedBuffer = TRUE; + } + + // set the destination offset + Offset.QuadPart = RealWriteOffset; + + // setup the notification event for the write + KeInitializeEvent(&Event, + NotificationEvent, + FALSE); + + DPRINT("Building synchronous FSD Request...\n"); + + // Build an IRP requesting the lower-level [disk] driver to perform the write + // TODO: Forward the existing IRP instead + Irp = IoBuildSynchronousFsdRequest(IRP_MJ_WRITE, + DeviceObject, + // if we allocated a temp buffer, use that instead of the Buffer parameter + ((AllocatedBuffer) ? TempBuffer : Buffer), + RealLength, + &Offset, + &Event, + &IoStatus); + // Did we fail to build the IRP? + if (Irp == NULL) + { + DPRINT1("IoBuildSynchronousFsdRequest failed\n"); + + if (AllocatedBuffer) + { + RtlSecureZeroMemory(TempBuffer, RealLength); + ExFreePoolWithTag(TempBuffer, TAG_NTFS); + } + + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Call the next-lower driver to perform the write + DPRINT("Calling IO Driver with irp %p\n", Irp); + Status = IoCallDriver(DeviceObject, Irp); + + // Wait until the next-lower driver has completed the IRP + DPRINT("Waiting for IO Operation for %p\n", Irp); + if (Status == STATUS_PENDING) + { + DPRINT("Operation pending\n"); + KeWaitForSingleObject(&Event, Suspended, KernelMode, FALSE, NULL); + DPRINT("Getting IO Status... for %p\n", Irp); + Status = IoStatus.Status; + } + + if (AllocatedBuffer) + { + // zero the buffer before freeing it, so private user data can't be snooped + RtlSecureZeroMemory(TempBuffer, RealLength); + + ExFreePoolWithTag(TempBuffer, TAG_NTFS); + } + + DPRINT("NtfsWriteDisk() done (Status %x)\n", Status); + + return Status; +} + NTSTATUS NtfsReadSectors(IN PDEVICE_OBJECT DeviceObject, IN ULONG DiskSector, diff --git a/drivers/filesystems/ntfs/btree.c b/drivers/filesystems/ntfs/btree.c new file mode 100644 index 00000000000..9fb1b29c0f5 --- /dev/null +++ b/drivers/filesystems/ntfs/btree.c @@ -0,0 +1,2022 @@ +/* +* ReactOS kernel +* Copyright (C) 2002, 2017 ReactOS Team +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. +* +* COPYRIGHT: See COPYING in the top level directory +* PROJECT: ReactOS kernel +* FILE: drivers/filesystem/ntfs/btree.c +* PURPOSE: NTFS filesystem driver +* PROGRAMMERS: Trevor Thompson +*/ + +/* INCLUDES *****************************************************************/ + +#include "ntfs.h" + +#define NDEBUG +#include + +/* FUNCTIONS ****************************************************************/ + +// TEMP FUNCTION for diagnostic purposes. +// Prints VCN of every node in an index allocation +VOID +PrintAllVCNs(PDEVICE_EXTENSION Vcb, + PNTFS_ATTR_CONTEXT IndexAllocationContext, + ULONG NodeSize) +{ + ULONGLONG CurrentOffset = 0; + PINDEX_BUFFER CurrentNode, Buffer; + ULONGLONG BufferSize = AttributeDataLength(IndexAllocationContext->pRecord); + ULONG BytesRead; + ULONGLONG i; + int Count = 0; + + if (BufferSize == 0) + { + DPRINT1("Index Allocation is empty.\n"); + return; + } + + Buffer = ExAllocatePoolWithTag(NonPagedPool, BufferSize, TAG_NTFS); + + BytesRead = ReadAttribute(Vcb, IndexAllocationContext, 0, (PCHAR)Buffer, BufferSize); + + ASSERT(BytesRead = BufferSize); + + CurrentNode = Buffer; + + // loop through all the nodes + for (i = 0; i < BufferSize; i += NodeSize) + { + NTSTATUS Status = FixupUpdateSequenceArray(Vcb, &CurrentNode->Ntfs); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Fixing fixup failed!\n"); + continue; + } + + DPRINT1("Node #%d, VCN: %I64u\n", Count, CurrentNode->VCN); + + CurrentNode = (PINDEX_BUFFER)((ULONG_PTR)CurrentNode + NodeSize); + CurrentOffset += NodeSize; + Count++; + } + + ExFreePoolWithTag(Buffer, TAG_NTFS); +} + +/** +* @name AllocateIndexNode +* @implemented +* +* Allocates a new index record in an index allocation. +* +* @param DeviceExt +* Pointer to the target DEVICE_EXTENSION describing the volume the node will be created on. +* +* @param FileRecord +* Pointer to a copy of the file record containing the index. +* +* @param IndexBufferSize +* Size of an index record for this index, in bytes. Commonly defined as 4096. +* +* @param IndexAllocationCtx +* Pointer to an NTFS_ATTR_CONTEXT describing the index allocation attribute the node will be assigned to. +* +* @param IndexAllocationOffset +* Offset of the index allocation attribute relative to the file record. +* +* @param NewVCN +* Pointer to a ULONGLONG which will receive the VCN of the newly-assigned index record +* +* @returns +* STATUS_SUCCESS in case of success. +* STATUS_NOT_IMPLEMENTED if there's no $I30 bitmap attribute in the file record. +* +* @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, + PFILE_RECORD_HEADER FileRecord, + ULONG IndexBufferSize, + PNTFS_ATTR_CONTEXT IndexAllocationCtx, + ULONG IndexAllocationOffset, + PULONGLONG NewVCN) +{ + NTSTATUS Status; + PNTFS_ATTR_CONTEXT BitmapCtx; + ULONGLONG IndexAllocationLength, BitmapLength; + ULONG BitmapOffset; + ULONGLONG NextNodeNumber; + PCHAR *BitmapMem; + ULONG *BitmapPtr; + RTL_BITMAP Bitmap; + ULONG BytesWritten; + ULONG BytesNeeded; + LARGE_INTEGER DataSize; + + DPRINT1("AllocateIndexNode(%p, %p, %lu, %p, %lu, %p) called.\n", DeviceExt, + FileRecord, + IndexBufferSize, + IndexAllocationCtx, + IndexAllocationOffset, + NewVCN); + + // Get the length of the attribute allocation + IndexAllocationLength = AttributeDataLength(IndexAllocationCtx->pRecord); + + // Find the bitmap attribute for the index + Status = FindAttribute(DeviceExt, + FileRecord, + AttributeBitmap, + L"$I30", + 4, + &BitmapCtx, + &BitmapOffset); + if (!NT_SUCCESS(Status)) + { + DPRINT1("FIXME: Need to add bitmap attribute!\n"); + return STATUS_NOT_IMPLEMENTED; + } + + // Get the length of the bitmap attribute + BitmapLength = AttributeDataLength(BitmapCtx->pRecord); + + NextNodeNumber = IndexAllocationLength / DeviceExt->NtfsInfo.BytesPerIndexRecord; + + // TODO: Find unused allocation in bitmap and use that space first + + // Add another bit to bitmap + + // See how many bytes we need to store the amount of bits we'll have + BytesNeeded = NextNodeNumber / 8; + BytesNeeded++; + + // Windows seems to allocate the bitmap in 8-byte chunks to keep any bytes from being wasted on padding + 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) + { + // TODO: handle synchronization issues that could occur from changing the directory's file record + // Change bitmap size + DataSize.QuadPart = BytesNeeded; + 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"); + ReleaseAttributeContext(BitmapCtx); + return Status; + } + } + + // Enlarge Index Allocation attribute + DataSize.QuadPart = IndexAllocationLength + IndexBufferSize; + Status = SetNonResidentAttributeDataLength(DeviceExt, + IndexAllocationCtx, + IndexAllocationOffset, + FileRecord, + &DataSize); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to set length of index allocation!\n"); + ReleaseAttributeContext(BitmapCtx); + return Status; + } + + // Update file record on disk + Status = UpdateFileRecord(DeviceExt, IndexAllocationCtx->FileMFTIndex, FileRecord); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to update file record!\n"); + ReleaseAttributeContext(BitmapCtx); + return Status; + } + + // Set the bit for the new index record + RtlSetBits(&Bitmap, NextNodeNumber, 1); + + // Write the new bitmap attribute + Status = WriteAttribute(DeviceExt, + BitmapCtx, + 0, + (const PUCHAR)BitmapPtr, + BytesNeeded, + &BytesWritten, + FileRecord); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Unable to write to $I30 bitmap attribute!\n"); + } + + // Calculate VCN of new node number + *NewVCN = NextNodeNumber * (IndexBufferSize / DeviceExt->NtfsInfo.BytesPerCluster); + + DPRINT("New VCN: %I64u\n", *NewVCN); + + ExFreePoolWithTag(BitmapMem, TAG_NTFS); + ReleaseAttributeContext(BitmapCtx); + + return Status; +} + +/** +* @name CreateDummyKey +* @implemented +* +* Creates the final B_TREE_KEY for a B_TREE_FILENAME_NODE. Also creates the associated index entry. +* +* @param HasChildNode +* BOOLEAN to indicate if this key will have a LesserChild. +* +* @return +* The newly-created key. +*/ +PB_TREE_KEY +CreateDummyKey(BOOLEAN HasChildNode) +{ + PINDEX_ENTRY_ATTRIBUTE NewIndexEntry; + PB_TREE_KEY NewDummyKey; + + // Calculate max size of a dummy key + ULONG EntrySize = ALIGN_UP_BY(FIELD_OFFSET(INDEX_ENTRY_ATTRIBUTE, FileName), 8); + EntrySize += sizeof(ULONGLONG); // for VCN + + // Create the index entry for the key + NewIndexEntry = ExAllocatePoolWithTag(NonPagedPool, EntrySize, TAG_NTFS); + if (!NewIndexEntry) + { + DPRINT1("Couldn't allocate memory for dummy key index entry!\n"); + return NULL; + } + + RtlZeroMemory(NewIndexEntry, EntrySize); + + if (HasChildNode) + { + NewIndexEntry->Flags = NTFS_INDEX_ENTRY_NODE | NTFS_INDEX_ENTRY_END; + } + else + { + NewIndexEntry->Flags = NTFS_INDEX_ENTRY_END; + EntrySize -= sizeof(ULONGLONG); // no VCN + } + + NewIndexEntry->Length = EntrySize; + + // Create the key + NewDummyKey = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_KEY), TAG_NTFS); + if (!NewDummyKey) + { + DPRINT1("Unable to allocate dummy key!\n"); + ExFreePoolWithTag(NewIndexEntry, TAG_NTFS); + return NULL; + } + RtlZeroMemory(NewDummyKey, sizeof(B_TREE_KEY)); + + NewDummyKey->IndexEntry = NewIndexEntry; + + 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 +* +* Compare two B_TREE_KEY's to determine their order in the tree. +* +* @param Key1 +* Pointer to a B_TREE_KEY that will be compared. +* +* @param Key2 +* Pointer to the other B_TREE_KEY that will be compared. +* +* @param CaseSensitive +* Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE +* if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag. +* +* @returns +* 0 if the two keys are equal. +* < 0 if key1 is less thank key2 +* > 0 if key1 is greater than key2 +* +* @remarks +* Any other key is always less than the final (dummy) key in a node. Key1 must not be the dummy node. +*/ +LONG +CompareTreeKeys(PB_TREE_KEY Key1, PB_TREE_KEY Key2, BOOLEAN CaseSensitive) +{ + UNICODE_STRING Key1Name, Key2Name; + LONG Comparison; + + // Key1 must not be the final key (AKA the dummy key) + ASSERT(!(Key1->IndexEntry->Flags & NTFS_INDEX_ENTRY_END)); + + // If Key2 is the "dummy key", key 1 will always come first + if (Key2->NextKey == NULL) + return -1; + + Key1Name.Buffer = Key1->IndexEntry->FileName.Name; + Key1Name.Length = Key1Name.MaximumLength + = Key1->IndexEntry->FileName.NameLength * sizeof(WCHAR); + + Key2Name.Buffer = Key2->IndexEntry->FileName.Name; + Key2Name.Length = Key2Name.MaximumLength + = Key2->IndexEntry->FileName.NameLength * sizeof(WCHAR); + + // Are the two keys the same length? + if (Key1Name.Length == Key2Name.Length) + return RtlCompareUnicodeString(&Key1Name, &Key2Name, !CaseSensitive); + + // Is Key1 shorter? + if (Key1Name.Length < Key2Name.Length) + { + // Truncate KeyName2 to be the same length as KeyName1 + Key2Name.Length = Key1Name.Length; + + // Compare the names of the same length + Comparison = RtlCompareUnicodeString(&Key1Name, &Key2Name, !CaseSensitive); + + // If the truncated names are the same length, the shorter one comes first + if (Comparison == 0) + return -1; + } + else + { + // Key2 is shorter + // Truncate KeyName1 to be the same length as KeyName2 + Key1Name.Length = Key2Name.Length; + + // Compare the names of the same length + Comparison = RtlCompareUnicodeString(&Key1Name, &Key2Name, !CaseSensitive); + + // If the truncated names are the same length, the shorter one comes first + if (Comparison == 0) + return 1; + } + + return Comparison; +} + +/** +* @name CountBTreeKeys +* @implemented +* +* Counts the number of linked B-Tree keys, starting with FirstKey. +* +* @param FirstKey +* Pointer to a B_TREE_KEY that will be the first key to be counted. +* +* @return +* The number of keys in a linked-list, including FirstKey and the final dummy key. +*/ +ULONG +CountBTreeKeys(PB_TREE_KEY FirstKey) +{ + ULONG Count = 0; + PB_TREE_KEY Current = FirstKey; + + while (Current != NULL) + { + Count++; + Current = Current->NextKey; + } + + return Count; +} + +PB_TREE_FILENAME_NODE +CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb, + PINDEX_ROOT_ATTRIBUTE IndexRoot, + PNTFS_ATTR_CONTEXT IndexAllocationAttributeCtx, + PINDEX_ENTRY_ATTRIBUTE NodeEntry) +{ + PB_TREE_FILENAME_NODE NewNode; + PINDEX_ENTRY_ATTRIBUTE CurrentNodeEntry; + PINDEX_ENTRY_ATTRIBUTE FirstNodeEntry; + ULONG CurrentEntryOffset = 0; + PINDEX_BUFFER NodeBuffer; + ULONG IndexBufferSize = Vcb->NtfsInfo.BytesPerIndexRecord; + PULONGLONG VCN; + PB_TREE_KEY CurrentKey; + NTSTATUS Status; + ULONGLONG IndexNodeOffset; + ULONG BytesRead; + + if (IndexAllocationAttributeCtx == NULL) + { + DPRINT1("ERROR: Couldn't find index allocation attribute even though there should be one!\n"); + return NULL; + } + + // Get the node number from the end of the node entry + VCN = (PULONGLONG)((ULONG_PTR)NodeEntry + NodeEntry->Length - sizeof(ULONGLONG)); + + // Create the new tree node + NewNode = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_FILENAME_NODE), TAG_NTFS); + if (!NewNode) + { + DPRINT1("ERROR: Couldn't allocate memory for new filename node.\n"); + return NULL; + } + RtlZeroMemory(NewNode, sizeof(B_TREE_FILENAME_NODE)); + + // Create the first key + CurrentKey = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_KEY), TAG_NTFS); + if (!CurrentKey) + { + DPRINT1("ERROR: Failed to allocate memory for key!\n"); + ExFreePoolWithTag(NewNode, TAG_NTFS); + return NULL; + } + RtlZeroMemory(CurrentKey, sizeof(B_TREE_KEY)); + NewNode->FirstKey = CurrentKey; + + // Allocate memory for the node buffer + NodeBuffer = ExAllocatePoolWithTag(NonPagedPool, IndexBufferSize, TAG_NTFS); + if (!NodeBuffer) + { + DPRINT1("ERROR: Couldn't allocate memory for node buffer!\n"); + ExFreePoolWithTag(CurrentKey, TAG_NTFS); + ExFreePoolWithTag(NewNode, TAG_NTFS); + return NULL; + } + + // Calculate offset into index allocation + IndexNodeOffset = GetAllocationOffsetFromVCN(Vcb, IndexBufferSize, *VCN); + + // TODO: Confirm index bitmap has this node marked as in-use + + // Read the node + BytesRead = ReadAttribute(Vcb, + IndexAllocationAttributeCtx, + IndexNodeOffset, + (PCHAR)NodeBuffer, + IndexBufferSize); + + ASSERT(BytesRead == IndexBufferSize); + NT_ASSERT(NodeBuffer->Ntfs.Type == NRH_INDX_TYPE); + NT_ASSERT(NodeBuffer->VCN == *VCN); + + // Apply the fixup array to the node buffer + Status = FixupUpdateSequenceArray(Vcb, &NodeBuffer->Ntfs); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Couldn't apply fixup array to index node buffer!\n"); + ExFreePoolWithTag(NodeBuffer, TAG_NTFS); + ExFreePoolWithTag(CurrentKey, TAG_NTFS); + ExFreePoolWithTag(NewNode, TAG_NTFS); + return NULL; + } + + // Walk through the index and create keys for all the entries + FirstNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)(&NodeBuffer->Header) + + NodeBuffer->Header.FirstEntryOffset); + CurrentNodeEntry = FirstNodeEntry; + while (CurrentEntryOffset < NodeBuffer->Header.TotalSizeOfEntries) + { + // Allocate memory for the current entry + CurrentKey->IndexEntry = ExAllocatePoolWithTag(NonPagedPool, CurrentNodeEntry->Length, TAG_NTFS); + if (!CurrentKey->IndexEntry) + { + DPRINT1("ERROR: Couldn't allocate memory for next key!\n"); + DestroyBTreeNode(NewNode); + ExFreePoolWithTag(NodeBuffer, TAG_NTFS); + return NULL; + } + + NewNode->KeyCount++; + + // If this isn't the last entry + if (!(CurrentNodeEntry->Flags & NTFS_INDEX_ENTRY_END)) + { + // Create the next key + PB_TREE_KEY NextKey = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_KEY), TAG_NTFS); + if (!NextKey) + { + DPRINT1("ERROR: Couldn't allocate memory for next key!\n"); + DestroyBTreeNode(NewNode); + ExFreePoolWithTag(NodeBuffer, TAG_NTFS); + return NULL; + } + RtlZeroMemory(NextKey, sizeof(B_TREE_KEY)); + + // Add NextKey to the end of the list + CurrentKey->NextKey = NextKey; + + // Copy the current entry to its key + RtlCopyMemory(CurrentKey->IndexEntry, CurrentNodeEntry, CurrentNodeEntry->Length); + + // See if the current key has a sub-node + if (CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) + { + CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb, + IndexRoot, + IndexAllocationAttributeCtx, + CurrentKey->IndexEntry); + } + + CurrentKey = NextKey; + } + else + { + // Copy the final entry to its key + RtlCopyMemory(CurrentKey->IndexEntry, CurrentNodeEntry, CurrentNodeEntry->Length); + CurrentKey->NextKey = NULL; + + // See if the current key has a sub-node + if (CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) + { + CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb, + IndexRoot, + IndexAllocationAttributeCtx, + CurrentKey->IndexEntry); + } + + break; + } + + // Advance to the next entry + CurrentEntryOffset += CurrentNodeEntry->Length; + CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)CurrentNodeEntry + CurrentNodeEntry->Length); + } + + NewNode->VCN = *VCN; + NewNode->HasValidVCN = TRUE; + + ExFreePoolWithTag(NodeBuffer, TAG_NTFS); + + return NewNode; +} + +/** +* @name CreateBTreeFromIndex +* @implemented +* +* Parse an index and create a B-Tree in memory from it. +* +* @param IndexRootContext +* Pointer to an NTFS_ATTR_CONTEXT that describes the location of the index root attribute. +* +* @param NewTree +* Pointer to a PB_TREE that will receive the pointer to a newly-created B-Tree. +* +* @returns +* STATUS_SUCCESS on success. +* STATUS_INSUFFICIENT_RESOURCES if an allocation fails. +* +* @remarks +* Allocates memory for the entire tree. Caller is responsible for destroying the tree with DestroyBTree(). +*/ +NTSTATUS +CreateBTreeFromIndex(PDEVICE_EXTENSION Vcb, + PFILE_RECORD_HEADER FileRecordWithIndex, + /*PCWSTR IndexName,*/ + PNTFS_ATTR_CONTEXT IndexRootContext, + PINDEX_ROOT_ATTRIBUTE IndexRoot, + PB_TREE *NewTree) +{ + PINDEX_ENTRY_ATTRIBUTE CurrentNodeEntry; + 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 CurrentKey = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_KEY), TAG_NTFS); + ULONG CurrentOffset = IndexRoot->Header.FirstEntryOffset; + PNTFS_ATTR_CONTEXT IndexAllocationContext = NULL; + NTSTATUS Status; + + DPRINT1("CreateBTreeFromIndex(%p, %p)\n", IndexRoot, NewTree); + + if (!Tree || !RootNode || !CurrentKey) + { + DPRINT1("Couldn't allocate enough memory for B-Tree!\n"); + if (Tree) + ExFreePoolWithTag(Tree, TAG_NTFS); + if (CurrentKey) + ExFreePoolWithTag(CurrentKey, TAG_NTFS); + if (RootNode) + ExFreePoolWithTag(RootNode, TAG_NTFS); + return STATUS_INSUFFICIENT_RESOURCES; + } + + RtlZeroMemory(Tree, sizeof(B_TREE)); + RtlZeroMemory(RootNode, sizeof(B_TREE_FILENAME_NODE)); + RtlZeroMemory(CurrentKey, sizeof(B_TREE_KEY)); + + // See if the file record has an attribute allocation + Status = FindAttribute(Vcb, + FileRecordWithIndex, + AttributeIndexAllocation, + L"$I30", + 4, + &IndexAllocationContext, + NULL); + if (!NT_SUCCESS(Status)) + IndexAllocationContext = NULL; + + // Setup the Tree + RootNode->FirstKey = CurrentKey; + Tree->RootNode = RootNode; + + // Make sure we won't try reading past the attribute-end + if (FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header) + IndexRoot->Header.TotalSizeOfEntries > IndexRootContext->pRecord->Resident.ValueLength) + { + DPRINT1("Filesystem corruption detected!\n"); + DestroyBTree(Tree); + return STATUS_FILE_CORRUPT_ERROR; + } + + // Start at the first node entry + CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexRoot + + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header) + + IndexRoot->Header.FirstEntryOffset); + + // Create a key for each entry in the node + while (CurrentOffset < IndexRoot->Header.TotalSizeOfEntries) + { + // Allocate memory for the current entry + CurrentKey->IndexEntry = ExAllocatePoolWithTag(NonPagedPool, CurrentNodeEntry->Length, TAG_NTFS); + if (!CurrentKey->IndexEntry) + { + DPRINT1("ERROR: Couldn't allocate memory for next key!\n"); + DestroyBTree(Tree); + return STATUS_INSUFFICIENT_RESOURCES; + } + + RootNode->KeyCount++; + + // If this isn't the last entry + if (!(CurrentNodeEntry->Flags & NTFS_INDEX_ENTRY_END)) + { + // Create the next key + PB_TREE_KEY NextKey = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_KEY), TAG_NTFS); + if (!NextKey) + { + DPRINT1("ERROR: Couldn't allocate memory for next key!\n"); + DestroyBTree(Tree); + return STATUS_INSUFFICIENT_RESOURCES; + } + + RtlZeroMemory(NextKey, sizeof(B_TREE_KEY)); + + // Add NextKey to the end of the list + CurrentKey->NextKey = NextKey; + + // Copy the current entry to its key + RtlCopyMemory(CurrentKey->IndexEntry, CurrentNodeEntry, CurrentNodeEntry->Length); + + // Does this key have a sub-node? + if (CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) + { + // Create the child node + CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb, + IndexRoot, + IndexAllocationContext, + CurrentKey->IndexEntry); + if (!CurrentKey->LesserChild) + { + DPRINT1("ERROR: Couldn't create child node!\n"); + DestroyBTree(Tree); + return STATUS_NOT_IMPLEMENTED; + } + } + + // Advance to the next entry + CurrentOffset += CurrentNodeEntry->Length; + CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)CurrentNodeEntry + CurrentNodeEntry->Length); + CurrentKey = NextKey; + } + else + { + // Copy the final entry to its key + RtlCopyMemory(CurrentKey->IndexEntry, CurrentNodeEntry, CurrentNodeEntry->Length); + CurrentKey->NextKey = NULL; + + // Does this key have a sub-node? + if (CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) + { + // Create the child node + CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb, + IndexRoot, + IndexAllocationContext, + CurrentKey->IndexEntry); + if (!CurrentKey->LesserChild) + { + DPRINT1("ERROR: Couldn't create child node!\n"); + DestroyBTree(Tree); + return STATUS_NOT_IMPLEMENTED; + } + } + + break; + } + } + + *NewTree = Tree; + + if (IndexAllocationContext) + ReleaseAttributeContext(IndexAllocationContext); + + return STATUS_SUCCESS; +} + +/** +* @name GetSizeOfIndexEntries +* @implemented +* +* Sums the size of each index entry in every key in a B-Tree node. +* +* @param Node +* Pointer to a B_TREE_FILENAME_NODE. The size of this node's index entries will be returned. +* +* @returns +* The sum of the sizes of every index entry for each key in the B-Tree node. +* +* @remarks +* Gets only the size of the index entries; doesn't include the size of any headers that would be added to an index record. +*/ +ULONG +GetSizeOfIndexEntries(PB_TREE_FILENAME_NODE Node) +{ + // Start summing the total size of this node's entries + ULONG NodeSize = 0; + + // Walk through the list of Node Entries + PB_TREE_KEY CurrentKey = Node->FirstKey; + ULONG i; + for (i = 0; i < Node->KeyCount; i++) + { + ASSERT(CurrentKey->IndexEntry->Length != 0); + + // Add the length of the current node + NodeSize += CurrentKey->IndexEntry->Length; + CurrentKey = CurrentKey->NextKey; + } + + return NodeSize; +} + +/** +* @name CreateIndexRootFromBTree +* @implemented +* +* Parse a B-Tree in memory and convert it into an index that can be written to disk. +* +* @param DeviceExt +* Pointer to the DEVICE_EXTENSION of the target drive. +* +* @param Tree +* Pointer to a B_TREE that describes the index to be written. +* +* @param MaxIndexSize +* Describes how large the index can be before it will take too much space in the file record. +* This is strictly the sum of the sizes of all index entries; it does not include the space +* required by the index root header (INDEX_ROOT_ATTRIBUTE), since that size will be constant. +* +* After reaching MaxIndexSize, an index can no longer be represented with just an index root +* attribute, and will require an index allocation and $I30 bitmap (TODO). +* +* @param IndexRoot +* Pointer to a PINDEX_ROOT_ATTRIBUTE that will receive a pointer to the newly-created index. +* +* @param Length +* Pointer to a ULONG which will receive the length of the new index root. +* +* @returns +* STATUS_SUCCESS on success. +* STATUS_INSUFFICIENT_RESOURCES if an allocation fails. +* STATUS_NOT_IMPLEMENTED if the new index can't fit within MaxIndexSize. +* +* @remarks +* If the function succeeds, it's the caller's responsibility to free IndexRoot with ExFreePoolWithTag(). +*/ +NTSTATUS +CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt, + PB_TREE Tree, + ULONG MaxIndexSize, + PINDEX_ROOT_ATTRIBUTE *IndexRoot, + ULONG *Length) +{ + ULONG i; + PB_TREE_KEY CurrentKey; + PINDEX_ENTRY_ATTRIBUTE CurrentNodeEntry; + PINDEX_ROOT_ATTRIBUTE NewIndexRoot = ExAllocatePoolWithTag(NonPagedPool, + DeviceExt->NtfsInfo.BytesPerFileRecord, + TAG_NTFS); + + DPRINT1("CreateIndexRootFromBTree(%p, %p, 0x%lx, %p, %p)\n", DeviceExt, Tree, MaxIndexSize, IndexRoot, Length); + +#ifndef NDEBUG + DumpBTree(Tree); +#endif + + if (!NewIndexRoot) + { + DPRINT1("Failed to allocate memory for Index Root!\n"); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Setup the new index root + RtlZeroMemory(NewIndexRoot, DeviceExt->NtfsInfo.BytesPerFileRecord); + + NewIndexRoot->AttributeType = AttributeFileName; + NewIndexRoot->CollationRule = COLLATION_FILE_NAME; + NewIndexRoot->SizeOfEntry = DeviceExt->NtfsInfo.BytesPerIndexRecord; + // If Bytes per index record is less than cluster size, clusters per index record becomes sectors per index + if (NewIndexRoot->SizeOfEntry < DeviceExt->NtfsInfo.BytesPerCluster) + NewIndexRoot->ClustersPerIndexRecord = NewIndexRoot->SizeOfEntry / DeviceExt->NtfsInfo.BytesPerSector; + else + NewIndexRoot->ClustersPerIndexRecord = NewIndexRoot->SizeOfEntry / DeviceExt->NtfsInfo.BytesPerCluster; + + // Setup the Index node header + NewIndexRoot->Header.FirstEntryOffset = sizeof(INDEX_HEADER_ATTRIBUTE); + NewIndexRoot->Header.Flags = INDEX_ROOT_SMALL; + + // Start summing the total size of this node's entries + NewIndexRoot->Header.TotalSizeOfEntries = NewIndexRoot->Header.FirstEntryOffset; + + // Setup each Node Entry + CurrentKey = Tree->RootNode->FirstKey; + CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)NewIndexRoot + + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header) + + NewIndexRoot->Header.FirstEntryOffset); + for (i = 0; i < Tree->RootNode->KeyCount; i++) + { + // Would adding the current entry to the index increase the index size beyond the limit we've set? + ULONG IndexSize = NewIndexRoot->Header.TotalSizeOfEntries - NewIndexRoot->Header.FirstEntryOffset + CurrentKey->IndexEntry->Length; + if (IndexSize > MaxIndexSize) + { + DPRINT1("TODO: Adding file would require creating an attribute list!\n"); + ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); + return STATUS_NOT_IMPLEMENTED; + } + + ASSERT(CurrentKey->IndexEntry->Length != 0); + + // 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); + + // Does the current key have any sub-nodes? + if (CurrentKey->LesserChild) + NewIndexRoot->Header.Flags = INDEX_ROOT_LARGE; + + // Add Length of Current Entry to Total Size of Entries + NewIndexRoot->Header.TotalSizeOfEntries += CurrentKey->IndexEntry->Length; + + // Go to the next node entry + CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)CurrentNodeEntry + CurrentNodeEntry->Length); + + CurrentKey = CurrentKey->NextKey; + } + + NewIndexRoot->Header.AllocatedSize = NewIndexRoot->Header.TotalSizeOfEntries; + + *IndexRoot = NewIndexRoot; + *Length = NewIndexRoot->Header.AllocatedSize + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header); + + return STATUS_SUCCESS; +} + +NTSTATUS +CreateIndexBufferFromBTreeNode(PDEVICE_EXTENSION DeviceExt, + PB_TREE_FILENAME_NODE Node, + ULONG BufferSize, + BOOLEAN HasChildren, + PINDEX_BUFFER IndexBuffer) +{ + ULONG i; + PB_TREE_KEY CurrentKey; + PINDEX_ENTRY_ATTRIBUTE CurrentNodeEntry; + NTSTATUS Status; + + // TODO: Fix magic, do math + RtlZeroMemory(IndexBuffer, BufferSize); + IndexBuffer->Ntfs.Type = NRH_INDX_TYPE; + IndexBuffer->Ntfs.UsaOffset = 0x28; + IndexBuffer->Ntfs.UsaCount = 9; + + // TODO: Check bitmap for VCN + ASSERT(Node->HasValidVCN); + IndexBuffer->VCN = Node->VCN; + + // 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 + IndexBuffer->Header.TotalSizeOfEntries = IndexBuffer->Header.FirstEntryOffset; + + CurrentKey = Node->FirstKey; + CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&(IndexBuffer->Header) + + IndexBuffer->Header.FirstEntryOffset); + for (i = 0; i < Node->KeyCount; i++) + { + // Would adding the current entry to the index increase the node size beyond the allocation size? + ULONG IndexSize = FIELD_OFFSET(INDEX_BUFFER, Header) + + IndexBuffer->Header.TotalSizeOfEntries + + CurrentNodeEntry->Length; + if (IndexSize > BufferSize) + { + DPRINT1("TODO: Adding file would require creating a new node!\n"); + return STATUS_NOT_IMPLEMENTED; + } + + ASSERT(CurrentKey->IndexEntry->Length != 0); + + // Copy the index entry + RtlCopyMemory(CurrentNodeEntry, CurrentKey->IndexEntry, CurrentKey->IndexEntry->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; + + // Check for child nodes + if (HasChildren) + IndexBuffer->Header.Flags = INDEX_NODE_LARGE; + + // Go to the next node entry + CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)CurrentNodeEntry + CurrentNodeEntry->Length); + CurrentKey = CurrentKey->NextKey; + } + + Status = AddFixupArray(DeviceExt, &IndexBuffer->Ntfs); + + return Status; +} + +/** +* @name DemoteBTreeRoot +* @implemented +* +* Demoting the root means first putting all the keys in the root node into a new node, and making +* the new node a child of a dummy key. The dummy key then becomes the sole contents of the root node. +* The B-Tree gets one level deeper. This operation is needed when an index root grows too large for its file record. +* Demotion is my own term; I might change the name later if I think of something more descriptive or can find +* an appropriate name for this operation in existing B-Tree literature. +* +* @param Tree +* Pointer to the B_TREE whose root is being demoted +* +* @returns +* STATUS_SUCCESS on success. +* STATUS_INSUFFICIENT_RESOURCES if an allocation fails. +*/ +NTSTATUS +DemoteBTreeRoot(PB_TREE Tree) +{ + PB_TREE_FILENAME_NODE NewSubNode, NewIndexRoot; + PB_TREE_KEY DummyKey; + + DPRINT1("Collapsing Index Root into sub-node.\n"); + +#ifndef NDEBUG + DumpBTree(Tree); +#endif + + // Create a new node that will hold the keys currently in index root + NewSubNode = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_FILENAME_NODE), TAG_NTFS); + if (!NewSubNode) + { + DPRINT1("ERROR: Couldn't allocate memory for new sub-node.\n"); + return STATUS_INSUFFICIENT_RESOURCES; + } + RtlZeroMemory(NewSubNode, sizeof(B_TREE_FILENAME_NODE)); + + // Copy the applicable data from the old index root node + NewSubNode->KeyCount = Tree->RootNode->KeyCount; + NewSubNode->FirstKey = Tree->RootNode->FirstKey; + NewSubNode->DiskNeedsUpdating = TRUE; + + // Create a new dummy key, and make the new node it's child + DummyKey = CreateDummyKey(TRUE); + if (!DummyKey) + { + DPRINT1("ERROR: Couldn't allocate memory for new root node.\n"); + ExFreePoolWithTag(NewSubNode, TAG_NTFS); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Make the new node a child of the dummy key + DummyKey->LesserChild = NewSubNode; + + // Create a new index root node + NewIndexRoot = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_FILENAME_NODE), TAG_NTFS); + if (!NewIndexRoot) + { + DPRINT1("ERROR: Couldn't allocate memory for new index root.\n"); + ExFreePoolWithTag(NewSubNode, TAG_NTFS); + ExFreePoolWithTag(DummyKey, TAG_NTFS); + return STATUS_INSUFFICIENT_RESOURCES; + } + RtlZeroMemory(NewIndexRoot, sizeof(B_TREE_FILENAME_NODE)); + + NewIndexRoot->DiskNeedsUpdating = TRUE; + + // Insert the dummy key into the new node + NewIndexRoot->FirstKey = DummyKey; + NewIndexRoot->KeyCount = 1; + NewIndexRoot->DiskNeedsUpdating = TRUE; + + // Make the new node the Tree's root node + Tree->RootNode = NewIndexRoot; + +#ifndef NDEBUG + DumpBTree(Tree); +#endif + + return STATUS_SUCCESS; +} + +/** +* @name SetIndexEntryVCN +* @implemented +* +* Sets the VCN of a given IndexEntry. +* +* @param IndexEntry +* Pointer to an INDEX_ENTRY_ATTRIBUTE structure that will have its VCN set. +* +* @param VCN +* VCN to store in the index entry. +* +* @remarks +* The index entry must have enough memory allocated to store the VCN, and must have the NTFS_INDEX_ENTRY_NODE flag set. +* The VCN of an index entry is stored at the very end of the structure, after the filename attribute. Since the filename +* attribute can be a variable size, this function makes setting this member easy. +*/ +VOID +SetIndexEntryVCN(PINDEX_ENTRY_ATTRIBUTE IndexEntry, ULONGLONG VCN) +{ + PULONGLONG Destination = (PULONGLONG)((ULONG_PTR)IndexEntry + IndexEntry->Length - sizeof(ULONGLONG)); + + ASSERT(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE); + + *Destination = VCN; +} + +NTSTATUS +UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt, + PB_TREE Tree, + ULONG IndexBufferSize, + PFILE_RECORD_HEADER FileRecord) +{ + // Find the index allocation and bitmap + PNTFS_ATTR_CONTEXT IndexAllocationContext; + PB_TREE_KEY CurrentKey; + NTSTATUS Status; + BOOLEAN HasIndexAllocation = FALSE; + ULONG i; + ULONG IndexAllocationOffset; + + DPRINT1("UpdateIndexAllocation() called.\n"); + + Status = FindAttribute(DeviceExt, FileRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationContext, &IndexAllocationOffset); + if (NT_SUCCESS(Status)) + { + HasIndexAllocation = TRUE; + +#ifndef NDEBUG + PrintAllVCNs(DeviceExt, + IndexAllocationContext, + IndexBufferSize); +#endif + } + // Walk through the root node and update all the sub-nodes + CurrentKey = Tree->RootNode->FirstKey; + for (i = 0; i < Tree->RootNode->KeyCount; i++) + { + if (CurrentKey->LesserChild) + { + if (!HasIndexAllocation) + { + // We need to add an index allocation to the file record + PNTFS_ATTR_RECORD EndMarker = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->BytesInUse - (sizeof(ULONG) * 2)); + DPRINT1("Adding index allocation...\n"); + + // Add index allocation to the very end of the file record + Status = AddIndexAllocation(DeviceExt, + FileRecord, + EndMarker, + L"$I30", + 4); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to add index allocation!\n"); + return Status; + } + + // Find the new attribute + Status = FindAttribute(DeviceExt, FileRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationContext, &IndexAllocationOffset); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Couldn't find newly-created index allocation!\n"); + return Status; + } + + // Advance end marker + EndMarker = (PNTFS_ATTR_RECORD)((ULONG_PTR)EndMarker + EndMarker->Length); + + // Add index bitmap to the very end of the file record + Status = AddBitmap(DeviceExt, + FileRecord, + EndMarker, + L"$I30", + 4); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to add index bitmap!\n"); + return Status; + } + + HasIndexAllocation = TRUE; + } + + // Is the Index Entry large enough to store the VCN? + if (!CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) + { + // Allocate memory for the larger index entry + PINDEX_ENTRY_ATTRIBUTE NewEntry = ExAllocatePoolWithTag(NonPagedPool, + CurrentKey->IndexEntry->Length + sizeof(ULONGLONG), + TAG_NTFS); + if (!NewEntry) + { + DPRINT1("ERROR: Unable to allocate memory for new index entry!\n"); + if (HasIndexAllocation) + ReleaseAttributeContext(IndexAllocationContext); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Copy the old entry to the new one + RtlCopyMemory(NewEntry, CurrentKey->IndexEntry, CurrentKey->IndexEntry->Length); + + NewEntry->Length += sizeof(ULONGLONG); + + // Free the old memory + ExFreePoolWithTag(CurrentKey->IndexEntry, TAG_NTFS); + + CurrentKey->IndexEntry = NewEntry; + CurrentKey->IndexEntry->Flags |= NTFS_INDEX_ENTRY_NODE; + } + + // Update the sub-node + Status = UpdateIndexNode(DeviceExt, FileRecord, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, IndexAllocationOffset); + 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; + } + +#ifndef NDEBUG + DumpBTree(Tree); +#endif + + if (HasIndexAllocation) + { +#ifndef NDEBUG + PrintAllVCNs(DeviceExt, + IndexAllocationContext, + IndexBufferSize); +#endif + ReleaseAttributeContext(IndexAllocationContext); + } + + return STATUS_SUCCESS; +} + +NTSTATUS +UpdateIndexNode(PDEVICE_EXTENSION DeviceExt, + PFILE_RECORD_HEADER FileRecord, + PB_TREE_FILENAME_NODE Node, + ULONG IndexBufferSize, + PNTFS_ATTR_CONTEXT IndexAllocationContext, + ULONG IndexAllocationOffset) +{ + ULONG i; + PB_TREE_KEY CurrentKey = Node->FirstKey; + BOOLEAN HasChildren = FALSE; + NTSTATUS Status; + + + DPRINT("UpdateIndexNode(%p, %p, %p, %lu, %p, %lu) called for index node with VCN %I64u\n", + DeviceExt, + FileRecord, + Node, + IndexBufferSize, + IndexAllocationContext, + IndexAllocationOffset, + Node->VCN); + + // Walk through the node and look for children to update + for (i = 0; i < Node->KeyCount; i++) + { + ASSERT(CurrentKey); + + // If there's a child node + if (CurrentKey->LesserChild) + { + HasChildren = TRUE; + + // Update the child node on disk + Status = UpdateIndexNode(DeviceExt, FileRecord, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, IndexAllocationOffset); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to update child node!\n"); + return Status; + } + + // Is the Index Entry large enough to store the VCN? + if (!CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) + { + // Allocate memory for the larger index entry + PINDEX_ENTRY_ATTRIBUTE NewEntry = ExAllocatePoolWithTag(NonPagedPool, + CurrentKey->IndexEntry->Length + sizeof(ULONGLONG), + TAG_NTFS); + if (!NewEntry) + { + DPRINT1("ERROR: Unable to allocate memory for new index entry!\n"); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Copy the old entry to the new one + RtlCopyMemory(NewEntry, CurrentKey->IndexEntry, CurrentKey->IndexEntry->Length); + + NewEntry->Length += sizeof(ULONGLONG); + + // Free the old memory + ExFreePoolWithTag(CurrentKey->IndexEntry, TAG_NTFS); + + CurrentKey->IndexEntry = NewEntry; + } + + // Update the VCN stored in the index entry of CurrentKey + SetIndexEntryVCN(CurrentKey->IndexEntry, CurrentKey->LesserChild->VCN); + + CurrentKey->IndexEntry->Flags |= NTFS_INDEX_ENTRY_NODE; + } + + CurrentKey = CurrentKey->NextKey; + } + + + // Do we need to write this node to disk? + if (Node->DiskNeedsUpdating) + { + ULONGLONG NodeOffset; + ULONG LengthWritten; + PINDEX_BUFFER IndexBuffer; + + // Does the node need to be assigned a VCN? + if (!Node->HasValidVCN) + { + // Allocate the node + Status = AllocateIndexNode(DeviceExt, + FileRecord, + IndexBufferSize, + IndexAllocationContext, + IndexAllocationOffset, + &Node->VCN); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to allocate index record in index allocation!\n"); + return Status; + } + + Node->HasValidVCN = TRUE; + } + + // Allocate memory for an index buffer + IndexBuffer = ExAllocatePoolWithTag(NonPagedPool, IndexBufferSize, TAG_NTFS); + if (!IndexBuffer) + { + DPRINT1("ERROR: Failed to allocate %lu bytes for index buffer!\n", IndexBufferSize); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Create the index buffer we'll be writing to disk to represent this node + Status = CreateIndexBufferFromBTreeNode(DeviceExt, Node, IndexBufferSize, HasChildren, IndexBuffer); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to create index buffer from node!\n"); + ExFreePoolWithTag(IndexBuffer, TAG_NTFS); + return Status; + } + + // Get Offset of index buffer in index allocation + NodeOffset = GetAllocationOffsetFromVCN(DeviceExt, IndexBufferSize, Node->VCN); + + // Write the buffer to the index allocation + Status = WriteAttribute(DeviceExt, IndexAllocationContext, NodeOffset, (const PUCHAR)IndexBuffer, IndexBufferSize, &LengthWritten, FileRecord); + if (!NT_SUCCESS(Status) || LengthWritten != IndexBufferSize) + { + DPRINT1("ERROR: Failed to update index allocation!\n"); + ExFreePoolWithTag(IndexBuffer, TAG_NTFS); + if (!NT_SUCCESS(Status)) + return Status; + else + return STATUS_END_OF_FILE; + } + + Node->DiskNeedsUpdating = FALSE; + + // Free the index buffer + ExFreePoolWithTag(IndexBuffer, TAG_NTFS); + } + + return STATUS_SUCCESS; +} + +PB_TREE_KEY +CreateBTreeKeyFromFilename(ULONGLONG FileReference, PFILENAME_ATTRIBUTE FileNameAttribute) +{ + PB_TREE_KEY NewKey; + ULONG AttributeSize = GetFileNameAttributeLength(FileNameAttribute); + ULONG EntrySize = ALIGN_UP_BY(AttributeSize + FIELD_OFFSET(INDEX_ENTRY_ATTRIBUTE, FileName), 8); + + // Create a new Index Entry for the file + PINDEX_ENTRY_ATTRIBUTE NewEntry = ExAllocatePoolWithTag(NonPagedPool, EntrySize, TAG_NTFS); + if (!NewEntry) + { + DPRINT1("ERROR: Failed to allocate memory for Index Entry!\n"); + return NULL; + } + + // Setup the Index Entry + RtlZeroMemory(NewEntry, EntrySize); + NewEntry->Data.Directory.IndexedFile = FileReference; + NewEntry->Length = EntrySize; + NewEntry->KeyLength = AttributeSize; + + // Copy the FileNameAttribute + RtlCopyMemory(&NewEntry->FileName, FileNameAttribute, AttributeSize); + + // Setup the New Key + NewKey = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_KEY), TAG_NTFS); + if (!NewKey) + { + DPRINT1("ERROR: Failed to allocate memory for new key!\n"); + ExFreePoolWithTag(NewEntry, TAG_NTFS); + return NULL; + } + NewKey->IndexEntry = NewEntry; + NewKey->NextKey = NULL; + + return NewKey; +} + +VOID +DestroyBTreeKey(PB_TREE_KEY Key) +{ + if (Key->IndexEntry) + ExFreePoolWithTag(Key->IndexEntry, TAG_NTFS); + + if (Key->LesserChild) + DestroyBTreeNode(Key->LesserChild); + + ExFreePoolWithTag(Key, TAG_NTFS); +} + +VOID +DestroyBTreeNode(PB_TREE_FILENAME_NODE Node) +{ + PB_TREE_KEY NextKey; + PB_TREE_KEY CurrentKey = Node->FirstKey; + ULONG i; + for (i = 0; i < Node->KeyCount; i++) + { + NT_ASSERT(CurrentKey); + NextKey = CurrentKey->NextKey; + DestroyBTreeKey(CurrentKey); + CurrentKey = NextKey; + } + + NT_ASSERT(NextKey == NULL); + + ExFreePoolWithTag(Node, TAG_NTFS); +} + +/** +* @name DestroyBTree +* @implemented +* +* Destroys a B-Tree. +* +* @param Tree +* Pointer to the B_TREE which will be destroyed. +* +* @remarks +* Destroys every bit of data stored in the tree. +*/ +VOID +DestroyBTree(PB_TREE Tree) +{ + DestroyBTreeNode(Tree->RootNode); + ExFreePoolWithTag(Tree, TAG_NTFS); +} + +VOID +DumpBTreeKey(PB_TREE Tree, PB_TREE_KEY Key, ULONG Number, ULONG Depth) +{ + ULONG i; + for (i = 0; i < Depth; i++) + DbgPrint(" "); + DbgPrint(" Key #%d", Number); + + if (!(Key->IndexEntry->Flags & NTFS_INDEX_ENTRY_END)) + { + UNICODE_STRING FileName; + FileName.Length = Key->IndexEntry->FileName.NameLength * sizeof(WCHAR); + FileName.MaximumLength = FileName.Length; + FileName.Buffer = Key->IndexEntry->FileName.Name; + DbgPrint(" '%wZ'\n", &FileName); + } + else + { + DbgPrint(" (Dummy Key)\n"); + } + + // Is there a child node? + if (Key->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) + { + if (Key->LesserChild) + DumpBTreeNode(Tree, Key->LesserChild, Number, Depth + 1); + else + { + // This will be an assert once nodes with arbitrary depth are debugged + DPRINT1("DRIVER ERROR: No Key->LesserChild despite Key->IndexEntry->Flags indicating this is a node!\n"); + } + } +} + +VOID +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", 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 = 0; i < Node->KeyCount; i++) + { + DumpBTreeKey(Tree, CurrentKey, i, Depth); + CurrentKey = CurrentKey->NextKey; + } +} + +/** +* @name DumpBTree +* @implemented +* +* Displays a B-Tree. +* +* @param Tree +* Pointer to the B_TREE which will be displayed. +* +* @remarks +* Displays a diagnostic summary of a B_TREE. +*/ +VOID +DumpBTree(PB_TREE Tree) +{ + DbgPrint("B_TREE @ %p\n", Tree); + DumpBTreeNode(Tree, Tree->RootNode, 0, 0); +} + +// Calculates start of Index Buffer relative to the index allocation, given the node's VCN +ULONGLONG +GetAllocationOffsetFromVCN(PDEVICE_EXTENSION DeviceExt, + ULONG IndexBufferSize, + ULONGLONG Vcn) +{ + if (IndexBufferSize < DeviceExt->NtfsInfo.BytesPerCluster) + return Vcn * DeviceExt->NtfsInfo.BytesPerSector; + + return Vcn * DeviceExt->NtfsInfo.BytesPerCluster; +} + +ULONGLONG +GetIndexEntryVCN(PINDEX_ENTRY_ATTRIBUTE IndexEntry) +{ + PULONGLONG Destination = (PULONGLONG)((ULONG_PTR)IndexEntry + IndexEntry->Length - sizeof(ULONGLONG)); + + ASSERT(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE); + + return *Destination; +} + +/** +* @name NtfsInsertKey +* @implemented +* +* Inserts a FILENAME_ATTRIBUTE into a B-Tree node. +* +* @param Tree +* Pointer to the B_TREE the key (filename attribute) is being inserted into. +* +* @param FileReference +* Reference number to the file being added. This will be a combination of the MFT index and update sequence number. +* +* @param FileNameAttribute +* Pointer to a FILENAME_ATTRIBUTE which is the data for the key that will be added to the tree. A copy will be made. +* +* @param Node +* Pointer to a B_TREE_FILENAME_NODE into which a new key will be inserted, in order. +* +* @param CaseSensitive +* Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE +* if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag. +* +* @param MaxIndexRootSize +* The maximum size, in bytes, of node entries that can be stored in the index root before it will grow too large for +* the file record. This number is just the size of the entries, without any headers for the attribute or index root. +* +* @param IndexRecordSize +* The size, in bytes, of an index record for this index. AKA an index buffer. Usually set to 4096. +* +* @param MedianKey +* 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. +* Will be set to NULL if the node isn't split. +* +* @param NewRightHandSibling +* Pointer to a PB_TREE_FILENAME_NODE that will receive a pointer to a newly-created right-hand sibling node, +* should the node grow too large and need to be split. Will be set to NULL if the node isn't split. +* +* @remarks +* A node is always sorted, with the least comparable filename stored first and a dummy key to mark the end. +*/ +NTSTATUS +NtfsInsertKey(PB_TREE Tree, + ULONGLONG FileReference, + PFILENAME_ATTRIBUTE FileNameAttribute, + PB_TREE_FILENAME_NODE Node, + BOOLEAN CaseSensitive, + ULONG MaxIndexRootSize, + ULONG IndexRecordSize, + PB_TREE_KEY *MedianKey, + PB_TREE_FILENAME_NODE *NewRightHandSibling) +{ + PB_TREE_KEY NewKey, CurrentKey, PreviousKey; + NTSTATUS Status = STATUS_SUCCESS; + ULONG NodeSize; + ULONG AllocatedNodeSize; + ULONG MaxNodeSizeWithoutHeader; + ULONG i; + + *MedianKey = NULL; + *NewRightHandSibling = NULL; + + DPRINT1("NtfsInsertKey(%p, 0x%I64x, %p, %p, %s, %lu, %lu, %p, %p)\n", + Tree, + FileReference, + FileNameAttribute, + Node, + CaseSensitive ? "TRUE" : "FALSE", + MaxIndexRootSize, + IndexRecordSize, + MedianKey, + NewRightHandSibling); + + // Create the key for the filename attribute + NewKey = CreateBTreeKeyFromFilename(FileReference, FileNameAttribute); + if (!NewKey) + return STATUS_INSUFFICIENT_RESOURCES; + + // Find where to insert the key + CurrentKey = Node->FirstKey; + PreviousKey = NULL; + for (i = 0; i < Node->KeyCount; i++) + { + // Should the New Key go before the current key? + LONG Comparison = CompareTreeKeys(NewKey, CurrentKey, CaseSensitive); + + if (Comparison == 0) + { + DPRINT1("\t\tComparison == 0: %.*S\n", NewKey->IndexEntry->FileName.NameLength, NewKey->IndexEntry->FileName.Name); + DPRINT1("\t\tComparison == 0: %.*S\n", CurrentKey->IndexEntry->FileName.NameLength, CurrentKey->IndexEntry->FileName.Name); + } + ASSERT(Comparison != 0); + + // Is NewKey < CurrentKey? + if (Comparison < 0) + { + // Does CurrentKey have a sub-node? + if (CurrentKey->LesserChild) + { + PB_TREE_KEY NewLeftKey; + PB_TREE_FILENAME_NODE NewChild; + + // Insert the key into the child node + Status = NtfsInsertKey(Tree, + FileReference, + FileNameAttribute, + CurrentKey->LesserChild, + CaseSensitive, + MaxIndexRootSize, + IndexRecordSize, + &NewLeftKey, + &NewChild); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to insert key.\n"); + ExFreePoolWithTag(NewKey, TAG_NTFS); + return Status; + } + + // Did the child node get split? + if (NewLeftKey) + { + ASSERT(NewChild != NULL); + + // Insert the new left key to the left of the current key + NewLeftKey->NextKey = CurrentKey; + + // Is CurrentKey the first key? + if (!PreviousKey) + Node->FirstKey = NewLeftKey; + else + PreviousKey->NextKey = NewLeftKey; + + // CurrentKey->LesserChild will be the right-hand sibling + CurrentKey->LesserChild = NewChild; + + Node->KeyCount++; + Node->DiskNeedsUpdating = TRUE; + +#ifndef NDEBUG + DumpBTree(Tree); +#endif + } + } + else + { + // Insert New Key before Current Key + NewKey->NextKey = CurrentKey; + + // Increase KeyCount and mark node as dirty + Node->KeyCount++; + Node->DiskNeedsUpdating = TRUE; + + // was CurrentKey the first key? + if (CurrentKey == Node->FirstKey) + Node->FirstKey = NewKey; + else + PreviousKey->NextKey = NewKey; + } + break; + } + + PreviousKey = CurrentKey; + CurrentKey = CurrentKey->NextKey; + } + + // Determine how much space the index entries will need + NodeSize = GetSizeOfIndexEntries(Node); + + // Is Node not the root node? + if (Node != Tree->RootNode) + { + // Calculate maximum size of index entries without any headers + AllocatedNodeSize = IndexRecordSize - FIELD_OFFSET(INDEX_BUFFER, Header); + + // TODO: Replace magic with math + MaxNodeSizeWithoutHeader = AllocatedNodeSize - 0x28; + + // Has the node grown larger than its allocated size? + if (NodeSize > MaxNodeSizeWithoutHeader) + { + NTSTATUS Status; + + Status = SplitBTreeNode(Tree, Node, MedianKey, NewRightHandSibling, CaseSensitive); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to split B-Tree node!\n"); + return Status; + } + + return Status; + } + } + + // NewEntry and NewKey will be destroyed later by DestroyBTree() + + return Status; +} + + + +/** +* @name SplitBTreeNode +* @implemented +* +* Splits a B-Tree node that has grown too large. Finds the median key and sets up a right-hand-sibling +* node to contain the keys to the right of the median key. +* +* @param Tree +* Pointer to the B_TREE which contains the node being split +* +* @param Node +* Pointer to the B_TREE_FILENAME_NODE that needs to be split +* +* @param MedianKey +* Pointer a PB_TREE_KEY that will receive the pointer to the key in the middle of the node being split +* +* @param NewRightHandSibling +* Pointer to a PB_TREE_FILENAME_NODE that will receive a pointer to a newly-created B_TREE_FILENAME_NODE +* containing the keys to the right of MedianKey. +* +* @param CaseSensitive +* Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE +* if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag. +* +* @return +* STATUS_SUCCESS on success. +* STATUS_INSUFFICIENT_RESOURCES if an allocation fails. +* +* @remarks +* It's the responsibility of the caller to insert the new median key into the parent node, as well as making the +* NewRightHandSibling the lesser child of the node that is currently Node's parent. +*/ +NTSTATUS +SplitBTreeNode(PB_TREE Tree, + PB_TREE_FILENAME_NODE Node, + PB_TREE_KEY *MedianKey, + PB_TREE_FILENAME_NODE *NewRightHandSibling, + BOOLEAN CaseSensitive) +{ + ULONG MedianKeyIndex; + PB_TREE_KEY LastKeyBeforeMedian, FirstKeyAfterMedian; + ULONG KeyCount; + ULONG HalfSize; + ULONG SizeSum; + ULONG i; + + DPRINT1("SplitBTreeNode(%p, %p, %p, %p, %s) called\n", + Tree, + Node, + MedianKey, + NewRightHandSibling, + CaseSensitive ? "TRUE" : "FALSE"); + +#ifndef NDEBUG + DumpBTreeNode(Node, 0, 0); +#endif + + // Create the right hand sibling + *NewRightHandSibling = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_FILENAME_NODE), TAG_NTFS); + if (*NewRightHandSibling == NULL) + { + DPRINT1("Error: Failed to allocate memory for right hand sibling!\n"); + return STATUS_INSUFFICIENT_RESOURCES; + } + RtlZeroMemory(*NewRightHandSibling, sizeof(B_TREE_FILENAME_NODE)); + (*NewRightHandSibling)->DiskNeedsUpdating = TRUE; + + + // Find the last key before the median + + // This is roughly how NTFS-3G calculates median, and it's not congruent with what Windows does: + /* + // find the median key index + MedianKeyIndex = (Node->KeyCount + 1) / 2; + MedianKeyIndex--; + + LastKeyBeforeMedian = Node->FirstKey; + for (i = 0; i < MedianKeyIndex - 1; i++) + LastKeyBeforeMedian = LastKeyBeforeMedian->NextKey;*/ + + // The method we'll use is a little bit closer to how Windows determines the median but it's not identical. + // What Windows does is actually more complicated than this, I think because Windows allocates more slack space to Odd-numbered + // Index Records, leaving less room for index entries in these records (I haven't discovered why this is done). + // (Neither Windows nor chkdsk complain if we choose a different median than Windows would have chosen, as our median will be in the ballpark) + + // Use size to locate the median key / index + LastKeyBeforeMedian = Node->FirstKey; + MedianKeyIndex = 0; + HalfSize = 2016; // half the allocated size after subtracting the first index entry offset (TODO: MATH) + SizeSum = 0; + for (i = 0; i < Node->KeyCount; i++) + { + SizeSum += LastKeyBeforeMedian->IndexEntry->Length; + + if (SizeSum > HalfSize) + break; + + MedianKeyIndex++; + LastKeyBeforeMedian = LastKeyBeforeMedian->NextKey; + } + + // Now we can get the median key and the key that follows it + *MedianKey = LastKeyBeforeMedian->NextKey; + FirstKeyAfterMedian = (*MedianKey)->NextKey; + + DPRINT1("%lu keys, %lu median\n", Node->KeyCount, MedianKeyIndex); + DPRINT1("\t\tMedian: %.*S\n", (*MedianKey)->IndexEntry->FileName.NameLength, (*MedianKey)->IndexEntry->FileName.Name); + + // "Node" will be the left hand sibling after the split, containing all keys prior to the median key + + // We need to create a dummy pointer at the end of the LHS. The dummy's child will be the median's child. + LastKeyBeforeMedian->NextKey = CreateDummyKey(BooleanFlagOn((*MedianKey)->IndexEntry->Flags, NTFS_INDEX_ENTRY_NODE)); + if (LastKeyBeforeMedian->NextKey == NULL) + { + DPRINT1("Error: Couldn't allocate dummy key!\n"); + LastKeyBeforeMedian->NextKey = *MedianKey; + ExFreePoolWithTag(*NewRightHandSibling, TAG_NTFS); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Did the median key have a child node? + if ((*MedianKey)->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) + { + // Set the child of the new dummy key + LastKeyBeforeMedian->NextKey->LesserChild = (*MedianKey)->LesserChild; + + // Give the dummy key's index entry the same sub-node VCN the median + SetIndexEntryVCN(LastKeyBeforeMedian->NextKey->IndexEntry, GetIndexEntryVCN((*MedianKey)->IndexEntry)); + } + else + { + // Median key didn't have a child node, but it will. Create a new index entry large enough to store a VCN. + PINDEX_ENTRY_ATTRIBUTE NewIndexEntry = ExAllocatePoolWithTag(NonPagedPool, + (*MedianKey)->IndexEntry->Length + sizeof(ULONGLONG), + TAG_NTFS); + if (!NewIndexEntry) + { + DPRINT1("Unable to allocate memory for new index entry!\n"); + LastKeyBeforeMedian->NextKey = *MedianKey; + ExFreePoolWithTag(*NewRightHandSibling, TAG_NTFS); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Copy the old index entry to the new one + RtlCopyMemory(NewIndexEntry, (*MedianKey)->IndexEntry, (*MedianKey)->IndexEntry->Length); + + // Use the new index entry after freeing the old one + ExFreePoolWithTag((*MedianKey)->IndexEntry, TAG_NTFS); + (*MedianKey)->IndexEntry = NewIndexEntry; + + // Update the length for the VCN + (*MedianKey)->IndexEntry->Length += sizeof(ULONGLONG); + + // Set the node flag + (*MedianKey)->IndexEntry->Flags |= NTFS_INDEX_ENTRY_NODE; + } + + // "Node" will become the child of the median key + (*MedianKey)->LesserChild = Node; + SetIndexEntryVCN((*MedianKey)->IndexEntry, Node->VCN); + + // Update Node's KeyCount (remember to add 1 for the new dummy key) + Node->KeyCount = MedianKeyIndex + 2; + + KeyCount = CountBTreeKeys(Node->FirstKey); + ASSERT(Node->KeyCount == KeyCount); + + // everything to the right of MedianKey becomes the right hand sibling of Node + (*NewRightHandSibling)->FirstKey = FirstKeyAfterMedian; + (*NewRightHandSibling)->KeyCount = CountBTreeKeys(FirstKeyAfterMedian); + +#ifndef NDEBUG + DPRINT1("Left-hand node after split:\n"); + DumpBTreeNode(Node, 0, 0); + + DPRINT1("Right-hand sibling node after split:\n"); + DumpBTreeNode(*NewRightHandSibling, 0, 0); +#endif + + return STATUS_SUCCESS; +} \ No newline at end of file diff --git a/drivers/filesystems/ntfs/create.c b/drivers/filesystems/ntfs/create.c index 4a4a6a6a8df..b591b79073f 100644 --- a/drivers/filesystems/ntfs/create.c +++ b/drivers/filesystems/ntfs/create.c @@ -185,7 +185,7 @@ NtfsOpenFileById(PDEVICE_EXTENSION DeviceExt, DPRINT1("NtfsOpenFileById(%p, %p, %I64x, %p)\n", DeviceExt, FileObject, MftId, FoundFCB); - ASSERT(MftId < 0x10); + ASSERT(MftId < NTFS_FILE_FIRST_USER_FILE); if (MftId > 0xb) /* No entries are used yet beyond this */ { return STATUS_OBJECT_NAME_NOT_FOUND; @@ -246,6 +246,7 @@ NTSTATUS NtfsOpenFile(PDEVICE_EXTENSION DeviceExt, PFILE_OBJECT FileObject, PWSTR FileName, + BOOLEAN CaseSensitive, PNTFS_FCB * FoundFCB) { PNTFS_FCB ParentFcb; @@ -253,7 +254,12 @@ NtfsOpenFile(PDEVICE_EXTENSION DeviceExt, NTSTATUS Status; PWSTR AbsFileName = NULL; - DPRINT1("NtfsOpenFile(%p, %p, %S, %p)\n", DeviceExt, FileObject, FileName, FoundFCB); + DPRINT1("NtfsOpenFile(%p, %p, %S, %s, %p)\n", + DeviceExt, + FileObject, + FileName, + CaseSensitive ? "TRUE" : "FALSE", + FoundFCB); *FoundFCB = NULL; @@ -285,19 +291,20 @@ NtfsOpenFile(PDEVICE_EXTENSION DeviceExt, Status = NtfsGetFCBForFile(DeviceExt, &ParentFcb, &Fcb, - FileName); + FileName, + CaseSensitive); if (ParentFcb != NULL) { NtfsReleaseFCB(DeviceExt, ParentFcb); } - if (!NT_SUCCESS (Status)) + if (!NT_SUCCESS(Status)) { DPRINT("Could not make a new FCB, status: %x\n", Status); if (AbsFileName) - ExFreePool(AbsFileName); + ExFreePoolWithTag(AbsFileName, TAG_NTFS); return Status; } @@ -323,7 +330,7 @@ NtfsOpenFile(PDEVICE_EXTENSION DeviceExt, static NTSTATUS NtfsCreateFile(PDEVICE_OBJECT DeviceObject, - PIRP Irp) + PNTFS_IRP_CONTEXT IrpContext) { PDEVICE_EXTENSION DeviceExt; PIO_STACK_LOCATION Stack; @@ -334,12 +341,13 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject, // PWSTR FileName; NTSTATUS Status; UNICODE_STRING FullPath; + PIRP Irp = IrpContext->Irp; - DPRINT1("NtfsCreateFile(%p, %p) called\n", DeviceObject, Irp); + DPRINT1("NtfsCreateFile(%p, %p) called\n", DeviceObject, IrpContext); DeviceExt = DeviceObject->DeviceExtension; ASSERT(DeviceExt); - Stack = IoGetCurrentIrpStackLocation (Irp); + Stack = IoGetCurrentIrpStackLocation(Irp); ASSERT(Stack); RequestedDisposition = ((Stack->Parameters.Create.Options >> 24) & 0xff); @@ -367,7 +375,7 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject, return STATUS_INVALID_PARAMETER; MFTId = (*(PULONGLONG)FileObject->FileName.Buffer) & NTFS_MFT_MASK; - if (MFTId < 0x10) + if (MFTId < NTFS_FILE_FIRST_USER_FILE) { Status = NtfsOpenFileById(DeviceExt, FileObject, MFTId, &Fcb); } @@ -411,6 +419,7 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject, Status = NtfsOpenFile(DeviceExt, FileObject, ((RequestedOptions & FILE_OPEN_BY_FILE_ID) ? FullPath.Buffer : FileObject->FileName.Buffer), + BooleanFlagOn(Stack->Flags, SL_CASE_SENSITIVE), &Fcb); if (RequestedOptions & FILE_OPEN_BY_FILE_ID) @@ -476,26 +485,126 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject, return Status; } - /* HUGLY HACK: remain RO so far... */ if (RequestedDisposition == FILE_OVERWRITE || RequestedDisposition == FILE_OVERWRITE_IF || RequestedDisposition == FILE_SUPERSEDE) { - DPRINT1("Denying write request on NTFS volume\n"); - NtfsCloseFile(DeviceExt, FileObject); - return STATUS_ACCESS_DENIED; + PFILE_RECORD_HEADER fileRecord = NULL; + PNTFS_ATTR_CONTEXT dataContext = NULL; + ULONG DataAttributeOffset; + LARGE_INTEGER Zero; + Zero.QuadPart = 0; + + if (!NtfsGlobalData->EnableWriteSupport) + { + DPRINT1("NTFS write-support is EXPERIMENTAL and is disabled by default!\n"); + NtfsCloseFile(DeviceExt, FileObject); + return STATUS_ACCESS_DENIED; + } + + // TODO: check for appropriate access + + ExAcquireResourceExclusiveLite(&(Fcb->MainResource), TRUE); + + fileRecord = ExAllocatePoolWithTag(NonPagedPool, + Fcb->Vcb->NtfsInfo.BytesPerFileRecord, + TAG_NTFS); + if (fileRecord) + { + + Status = ReadFileRecord(Fcb->Vcb, + Fcb->MFTIndex, + fileRecord); + if (!NT_SUCCESS(Status)) + goto DoneOverwriting; + + // find the data attribute and set it's length to 0 (TODO: Handle Alternate Data Streams) + Status = FindAttribute(Fcb->Vcb, fileRecord, AttributeData, L"", 0, &dataContext, &DataAttributeOffset); + if (!NT_SUCCESS(Status)) + goto DoneOverwriting; + + Status = SetAttributeDataLength(FileObject, Fcb, dataContext, DataAttributeOffset, fileRecord, &Zero); + } + else + { + Status = STATUS_NO_MEMORY; + } + + DoneOverwriting: + if (fileRecord) + ExFreePoolWithTag(fileRecord, TAG_NTFS); + if (dataContext) + ReleaseAttributeContext(dataContext); + + ExReleaseResourceLite(&(Fcb->MainResource)); + + if (!NT_SUCCESS(Status)) + { + NtfsCloseFile(DeviceExt, FileObject); + return Status; + } + + if (RequestedDisposition == FILE_SUPERSEDE) + { + Irp->IoStatus.Information = FILE_SUPERSEDED; + } + else + { + Irp->IoStatus.Information = FILE_OVERWRITTEN; + } } } else { - /* HUGLY HACK: remain RO so far... */ + /* HUGLY HACK: Can't create new files yet... */ if (RequestedDisposition == FILE_CREATE || RequestedDisposition == FILE_OPEN_IF || RequestedDisposition == FILE_OVERWRITE_IF || RequestedDisposition == FILE_SUPERSEDE) { - DPRINT1("Denying write request on NTFS volume\n"); - return STATUS_ACCESS_DENIED; + if (!NtfsGlobalData->EnableWriteSupport) + { + DPRINT1("NTFS write-support is EXPERIMENTAL and is disabled by default!\n"); + NtfsCloseFile(DeviceExt, FileObject); + return STATUS_ACCESS_DENIED; + } + + // Was the user trying to create a directory? + if (RequestedOptions & FILE_DIRECTORY_FILE) + { + // 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)); + } + + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Couldn't create file record!\n"); + return Status; + } + + // 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; + + // Now we should be able to open the file using NtfsCreateFile() + Status = NtfsCreateFile(DeviceObject, IrpContext); + if (NT_SUCCESS(Status)) + { + // We need to change Irp->IoStatus.Information to reflect creation + Irp->IoStatus.Information = FILE_CREATED; + } + return Status; } } @@ -541,10 +650,321 @@ NtfsCreate(PNTFS_IRP_CONTEXT IrpContext) ExAcquireResourceExclusiveLite(&DeviceExt->DirResource, TRUE); Status = NtfsCreateFile(DeviceObject, - IrpContext->Irp); + IrpContext); ExReleaseResourceLite(&DeviceExt->DirResource); return Status; } +/** +* @name NtfsCreateDirectory() +* @implemented +* +* 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 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_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 +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 MaxIndexRootSize; + ULONG RootLength; + + DPRINT1("NtfsCreateFileRecord(%p, %p, %s, %s)\n", + DeviceExt, + FileObject, + 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 + 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, + TAG_NTFS); + if (!FileRecord) + { + DPRINT1("ERROR: Unable to allocate memory for file record!\n"); + return NULL; + } + + RtlZeroMemory(FileRecord, DeviceExt->NtfsInfo.BytesPerFileRecord); + + FileRecord->Ntfs.Type = NRH_FILE_TYPE; + + // calculate USA offset and count + FileRecord->Ntfs.UsaOffset = FIELD_OFFSET(FILE_RECORD_HEADER, MFTRecordNumber) + sizeof(ULONG); + + // size of USA (in ULONG's) will be 1 (for USA number) + 1 for every sector the file record uses + FileRecord->BytesAllocated = DeviceExt->NtfsInfo.BytesPerFileRecord; + FileRecord->Ntfs.UsaCount = (FileRecord->BytesAllocated / DeviceExt->NtfsInfo.BytesPerSector) + 1; + + // setup other file record fields + FileRecord->SequenceNumber = 1; + FileRecord->AttributeOffset = FileRecord->Ntfs.UsaOffset + (2 * FileRecord->Ntfs.UsaCount); + 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); + + // mark the (temporary) end of the file-record + 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); + + // 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); + + // 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); + 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(FileRecord, TAG_NTFS); + + return Status; +} + /* EOF */ diff --git a/drivers/filesystems/ntfs/dirctl.c b/drivers/filesystems/ntfs/dirctl.c index 82b8a7cb1b0..ab784fce3d5 100644 --- a/drivers/filesystems/ntfs/dirctl.c +++ b/drivers/filesystems/ntfs/dirctl.c @@ -34,7 +34,6 @@ /* FUNCTIONS ****************************************************************/ - ULONGLONG NtfsGetFileSize(PDEVICE_EXTENSION DeviceExt, PFILE_RECORD_HEADER FileRecord, @@ -47,11 +46,11 @@ NtfsGetFileSize(PDEVICE_EXTENSION DeviceExt, NTSTATUS Status; PNTFS_ATTR_CONTEXT DataContext; - Status = FindAttribute(DeviceExt, FileRecord, AttributeData, Stream, StreamLength, &DataContext); + Status = FindAttribute(DeviceExt, FileRecord, AttributeData, Stream, StreamLength, &DataContext, NULL); if (NT_SUCCESS(Status)) { - Size = AttributeDataLength(&DataContext->Record); - Allocated = AttributeAllocatedLength(&DataContext->Record); + Size = AttributeDataLength(DataContext->pRecord); + Allocated = AttributeAllocatedLength(DataContext->pRecord); ReleaseAttributeContext(DataContext); } @@ -62,16 +61,16 @@ NtfsGetFileSize(PDEVICE_EXTENSION DeviceExt, static NTSTATUS -NtfsGetNameInformation(PDEVICE_EXTENSION DeviceExt, - PFILE_RECORD_HEADER FileRecord, - ULONGLONG MFTIndex, - PFILE_NAMES_INFORMATION Info, - ULONG BufferLength) +NtfsGetNamesInformation(PDEVICE_EXTENSION DeviceExt, + PFILE_RECORD_HEADER FileRecord, + ULONGLONG MFTIndex, + PFILE_NAMES_INFORMATION Info, + ULONG BufferLength) { ULONG Length; PFILENAME_ATTRIBUTE FileName; - DPRINT("NtfsGetNameInformation() called\n"); + DPRINT("NtfsGetNamesInformation() called\n"); FileName = GetBestFileNameFromRecord(DeviceExt, FileRecord); if (FileName == NULL) @@ -377,7 +376,8 @@ NtfsQueryDirectory(PNTFS_IRP_CONTEXT IrpContext) &Ccb->Entry, &FileRecord, &MFTRecord, - Fcb->MFTIndex); + Fcb->MFTIndex, + BooleanFlagOn(Stack->Flags, SL_CASE_SENSITIVE)); if (NT_SUCCESS(Status)) { @@ -395,12 +395,12 @@ NtfsQueryDirectory(PNTFS_IRP_CONTEXT IrpContext) switch (FileInformationClass) { - case FileNameInformation: - Status = NtfsGetNameInformation(DeviceExtension, - FileRecord, - MFTRecord, - (PFILE_NAMES_INFORMATION)Buffer, - BufferLength); + case FileNamesInformation: + Status = NtfsGetNamesInformation(DeviceExtension, + FileRecord, + MFTRecord, + (PFILE_NAMES_INFORMATION)Buffer, + BufferLength); break; case FileDirectoryInformation: diff --git a/drivers/filesystems/ntfs/dispatch.c b/drivers/filesystems/ntfs/dispatch.c index 09140f2f320..53d79303ed1 100644 --- a/drivers/filesystems/ntfs/dispatch.c +++ b/drivers/filesystems/ntfs/dispatch.c @@ -81,6 +81,18 @@ NtfsDispatch(PNTFS_IRP_CONTEXT IrpContext) Status = NtfsQueryInformation(IrpContext); break; + case IRP_MJ_SET_INFORMATION: + if (!NtfsGlobalData->EnableWriteSupport) + { + DPRINT1("NTFS write-support is EXPERIMENTAL and is disabled by default!\n"); + Status = STATUS_ACCESS_DENIED; + } + else + { + Status = NtfsSetInformation(IrpContext); + } + break; + case IRP_MJ_DIRECTORY_CONTROL: Status = NtfsDirectoryControl(IrpContext); break; @@ -94,7 +106,15 @@ NtfsDispatch(PNTFS_IRP_CONTEXT IrpContext) break; case IRP_MJ_WRITE: - Status = NtfsWrite(IrpContext); + if (!NtfsGlobalData->EnableWriteSupport) + { + DPRINT1("NTFS write-support is EXPERIMENTAL and is disabled by default!\n"); + Status = STATUS_ACCESS_DENIED; + } + else + { + Status = NtfsWrite(IrpContext); + } break; case IRP_MJ_CLOSE: diff --git a/drivers/filesystems/ntfs/fcb.c b/drivers/filesystems/ntfs/fcb.c index e88944c3347..f8057ee2ff0 100644 --- a/drivers/filesystems/ntfs/fcb.c +++ b/drivers/filesystems/ntfs/fcb.c @@ -35,8 +35,8 @@ /* FUNCTIONS ****************************************************************/ static -PWCHAR -NtfsGetNextPathElement(PWCHAR FileName) +PCWSTR +NtfsGetNextPathElement(PCWSTR FileName) { if (*FileName == L'\0') { @@ -55,7 +55,7 @@ NtfsGetNextPathElement(PWCHAR FileName) static VOID NtfsWSubString(PWCHAR pTarget, - const PWCHAR pSource, + PCWSTR pSource, size_t pLength) { wcsncpy(pTarget, pSource, pLength); @@ -506,6 +506,7 @@ static NTSTATUS NtfsDirFindFile(PNTFS_VCB Vcb, PNTFS_FCB DirectoryFcb, PWSTR FileToFind, + BOOLEAN CaseSensitive, PNTFS_FCB *FoundFCB) { NTSTATUS Status; @@ -517,7 +518,12 @@ NtfsDirFindFile(PNTFS_VCB Vcb, PNTFS_ATTR_CONTEXT DataContext; USHORT Length = 0; - DPRINT1("NtfsDirFindFile(%p, %p, %S, %p)\n", Vcb, DirectoryFcb, FileToFind, FoundFCB); + DPRINT1("NtfsDirFindFile(%p, %p, %S, %s, %p)\n", + Vcb, + DirectoryFcb, + FileToFind, + CaseSensitive ? "TRUE" : "FALSE", + FoundFCB); *FoundFCB = NULL; RtlInitUnicodeString(&File, FileToFind); @@ -551,7 +557,7 @@ NtfsDirFindFile(PNTFS_VCB Vcb, DPRINT1("Will now look for file '%wZ' with stream '%S'\n", &File, Colon); } - Status = NtfsLookupFileAt(Vcb, &File, &FileRecord, &MFTIndex, CurrentDir); + Status = NtfsLookupFileAt(Vcb, &File, CaseSensitive, &FileRecord, &MFTIndex, CurrentDir); if (!NT_SUCCESS(Status)) { return Status; @@ -568,7 +574,7 @@ NtfsDirFindFile(PNTFS_VCB Vcb, } else if (Colon != 0) { - Status = FindAttribute(Vcb, FileRecord, AttributeData, Colon, wcslen(Colon), &DataContext); + Status = FindAttribute(Vcb, FileRecord, AttributeData, Colon, wcslen(Colon), &DataContext, NULL); if (!NT_SUCCESS(Status)) { return STATUS_OBJECT_NAME_NOT_FOUND; @@ -587,20 +593,22 @@ NTSTATUS NtfsGetFCBForFile(PNTFS_VCB Vcb, PNTFS_FCB *pParentFCB, PNTFS_FCB *pFCB, - const PWSTR pFileName) + PCWSTR pFileName, + BOOLEAN CaseSensitive) { NTSTATUS Status; WCHAR pathName [MAX_PATH]; WCHAR elementName [MAX_PATH]; - PWCHAR currentElement; + PCWSTR currentElement; PNTFS_FCB FCB; PNTFS_FCB parentFCB; - DPRINT("NtfsGetFCBForFile(%p, %p, %p, '%S')\n", + DPRINT("NtfsGetFCBForFile(%p, %p, %p, '%S', %s)\n", Vcb, pParentFCB, pFCB, - pFileName); + pFileName, + CaseSensitive ? "TRUE" : "FALSE"); /* Dummy code */ // FCB = NtfsOpenRootFCB(Vcb); @@ -609,7 +617,7 @@ NtfsGetFCBForFile(PNTFS_VCB Vcb, #if 1 /* Trivial case, open of the root directory on volume */ - if (pFileName [0] == L'\0' || wcscmp(pFileName, L"\\") == 0) + if (pFileName[0] == L'\0' || wcscmp(pFileName, L"\\") == 0) { DPRINT("returning root FCB\n"); @@ -677,7 +685,7 @@ NtfsGetFCBForFile(PNTFS_VCB Vcb, NtfsGetNextPathElement(currentElement) - currentElement); DPRINT(" elementName:%S\n", elementName); - Status = NtfsDirFindFile(Vcb, parentFCB, elementName, &FCB); + Status = NtfsDirFindFile(Vcb, parentFCB, elementName, CaseSensitive, &FCB); if (Status == STATUS_OBJECT_NAME_NOT_FOUND) { *pParentFCB = parentFCB; @@ -741,14 +749,14 @@ NtfsReadFCBAttribute(PNTFS_VCB Vcb, return Status; } - Status = FindAttribute(Vcb, FileRecord, Type, Name, NameLength, &AttrCtxt); + Status = FindAttribute(Vcb, FileRecord, Type, Name, NameLength, &AttrCtxt, NULL); if (!NT_SUCCESS(Status)) { ExFreePoolWithTag(FileRecord, TAG_NTFS); return Status; } - AttrLength = AttributeDataLength(&AttrCtxt->Record); + AttrLength = AttributeDataLength(AttrCtxt->pRecord); *Data = ExAllocatePoolWithTag(NonPagedPool, AttrLength, TAG_NTFS); if (*Data == NULL) { diff --git a/drivers/filesystems/ntfs/finfo.c b/drivers/filesystems/ntfs/finfo.c index 50604f847df..fb7df5f4d4c 100644 --- a/drivers/filesystems/ntfs/finfo.c +++ b/drivers/filesystems/ntfs/finfo.c @@ -289,6 +289,128 @@ NtfsGetSteamInformation(PNTFS_FCB Fcb, return Status; } +// Convert enum value to friendly name +const PCSTR +GetInfoClassName(FILE_INFORMATION_CLASS infoClass) +{ + const PCSTR fileInfoClassNames[] = { "???????", + "FileDirectoryInformation", + "FileFullDirectoryInformation", + "FileBothDirectoryInformation", + "FileBasicInformation", + "FileStandardInformation", + "FileInternalInformation", + "FileEaInformation", + "FileAccessInformation", + "FileNameInformation", + "FileRenameInformation", + "FileLinkInformation", + "FileNamesInformation", + "FileDispositionInformation", + "FilePositionInformation", + "FileFullEaInformation", + "FileModeInformation", + "FileAlignmentInformation", + "FileAllInformation", + "FileAllocationInformation", + "FileEndOfFileInformation", + "FileAlternateNameInformation", + "FileStreamInformation", + "FilePipeInformation", + "FilePipeLocalInformation", + "FilePipeRemoteInformation", + "FileMailslotQueryInformation", + "FileMailslotSetInformation", + "FileCompressionInformation", + "FileObjectIdInformation", + "FileCompletionInformation", + "FileMoveClusterInformation", + "FileQuotaInformation", + "FileReparsePointInformation", + "FileNetworkOpenInformation", + "FileAttributeTagInformation", + "FileTrackingInformation", + "FileIdBothDirectoryInformation", + "FileIdFullDirectoryInformation", + "FileValidDataLengthInformation", + "FileShortNameInformation", + "FileIoCompletionNotificationInformation", + "FileIoStatusBlockRangeInformation", + "FileIoPriorityHintInformation", + "FileSfioReserveInformation", + "FileSfioVolumeInformation", + "FileHardLinkInformation", + "FileProcessIdsUsingFileInformation", + "FileNormalizedNameInformation", + "FileNetworkPhysicalNameInformation", + "FileIdGlobalTxDirectoryInformation", + "FileIsRemoteDeviceInformation", + "FileAttributeCacheInformation", + "FileNumaNodeInformation", + "FileStandardLinkInformation", + "FileRemoteProtocolInformation", + "FileReplaceCompletionInformation", + "FileMaximumInformation", + "FileDirectoryInformation", + "FileFullDirectoryInformation", + "FileBothDirectoryInformation", + "FileBasicInformation", + "FileStandardInformation", + "FileInternalInformation", + "FileEaInformation", + "FileAccessInformation", + "FileNameInformation", + "FileRenameInformation", + "FileLinkInformation", + "FileNamesInformation", + "FileDispositionInformation", + "FilePositionInformation", + "FileFullEaInformation", + "FileModeInformation", + "FileAlignmentInformation", + "FileAllInformation", + "FileAllocationInformation", + "FileEndOfFileInformation", + "FileAlternateNameInformation", + "FileStreamInformation", + "FilePipeInformation", + "FilePipeLocalInformation", + "FilePipeRemoteInformation", + "FileMailslotQueryInformation", + "FileMailslotSetInformation", + "FileCompressionInformation", + "FileObjectIdInformation", + "FileCompletionInformation", + "FileMoveClusterInformation", + "FileQuotaInformation", + "FileReparsePointInformation", + "FileNetworkOpenInformation", + "FileAttributeTagInformation", + "FileTrackingInformation", + "FileIdBothDirectoryInformation", + "FileIdFullDirectoryInformation", + "FileValidDataLengthInformation", + "FileShortNameInformation", + "FileIoCompletionNotificationInformation", + "FileIoStatusBlockRangeInformation", + "FileIoPriorityHintInformation", + "FileSfioReserveInformation", + "FileSfioVolumeInformation", + "FileHardLinkInformation", + "FileProcessIdsUsingFileInformation", + "FileNormalizedNameInformation", + "FileNetworkPhysicalNameInformation", + "FileIdGlobalTxDirectoryInformation", + "FileIsRemoteDeviceInformation", + "FileAttributeCacheInformation", + "FileNumaNodeInformation", + "FileStandardLinkInformation", + "FileRemoteProtocolInformation", + "FileReplaceCompletionInformation", + "FileMaximumInformation" }; + return fileInfoClassNames[infoClass]; +} + /* * FUNCTION: Retrieve the specified file information */ @@ -376,12 +498,12 @@ NtfsQueryInformation(PNTFS_IRP_CONTEXT IrpContext) case FileAlternateNameInformation: case FileAllInformation: - DPRINT1("Unimplemented information class %u\n", FileInformationClass); + DPRINT1("Unimplemented information class: %s\n", GetInfoClassName(FileInformationClass)); Status = STATUS_NOT_IMPLEMENTED; break; default: - DPRINT1("Unimplemented information class %u\n", FileInformationClass); + DPRINT1("Unimplemented information class: %s\n", GetInfoClassName(FileInformationClass)); Status = STATUS_INVALID_PARAMETER; } @@ -396,4 +518,267 @@ NtfsQueryInformation(PNTFS_IRP_CONTEXT IrpContext) return Status; } +/** +* @name NtfsSetEndOfFile +* @implemented +* +* Sets the end of file (file size) for a given file. +* +* @param Fcb +* Pointer to an NTFS_FCB which describes the target file. Fcb->MainResource should have been +* acquired with ExAcquireResourceSharedLite(). +* +* @param FileObject +* Pointer to a FILE_OBJECT describing the target file. +* +* @param DeviceExt +* Points to the target disk's DEVICE_EXTENSION +* +* @param IrpFlags +* ULONG describing the flags of the original IRP request (Irp->Flags). +* +* @param CaseSensitive +* Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE +* if an application opened the file with the FILE_FLAG_POSIX_SEMANTICS flag. +* +* @param NewFileSize +* Pointer to a LARGE_INTEGER which indicates the new end of file (file size). +* +* @return +* STATUS_SUCCESS if successful, +* STATUS_USER_MAPPED_FILE if trying to truncate a file but MmCanFileBeTruncated() returned false, +* STATUS_OBJECT_NAME_NOT_FOUND if there was no $DATA attribute associated with the target file, +* STATUS_INVALID_PARAMETER if there was no $FILENAME attribute associated with the target file, +* STATUS_INSUFFICIENT_RESOURCES if an allocation failed, +* STATUS_ACCESS_DENIED if target file is a volume or if paging is involved. +* +* @remarks As this function sets the size of a file at the file-level +* (and not at the attribute level) it's not recommended to use this +* function alongside functions that operate on the data attribute directly. +* +*/ +NTSTATUS +NtfsSetEndOfFile(PNTFS_FCB Fcb, + PFILE_OBJECT FileObject, + PDEVICE_EXTENSION DeviceExt, + ULONG IrpFlags, + BOOLEAN CaseSensitive, + PLARGE_INTEGER NewFileSize) +{ + LARGE_INTEGER CurrentFileSize; + PFILE_RECORD_HEADER FileRecord; + PNTFS_ATTR_CONTEXT DataContext; + ULONG AttributeOffset; + NTSTATUS Status = STATUS_SUCCESS; + ULONGLONG AllocationSize; + PFILENAME_ATTRIBUTE FileNameAttribute; + ULONGLONG ParentMFTId; + UNICODE_STRING FileName; + + + // Allocate non-paged memory for the file record + FileRecord = ExAllocatePoolWithTag(NonPagedPool, DeviceExt->NtfsInfo.BytesPerFileRecord, TAG_NTFS); + if (FileRecord == NULL) + { + DPRINT1("Couldn't allocate memory for file record!"); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // read the file record + DPRINT("Reading file record...\n"); + Status = ReadFileRecord(DeviceExt, Fcb->MFTIndex, FileRecord); + if (!NT_SUCCESS(Status)) + { + // We couldn't get the file's record. Free the memory and return the error + DPRINT1("Can't find record for %wS!\n", Fcb->ObjectName); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + return Status; + } + + DPRINT("Found record for %wS\n", Fcb->ObjectName); + + CurrentFileSize.QuadPart = NtfsGetFileSize(DeviceExt, FileRecord, L"", 0, NULL); + + // Are we trying to decrease the file size? + if (NewFileSize->QuadPart < CurrentFileSize.QuadPart) + { + // Is the file mapped? + if (!MmCanFileBeTruncated(FileObject->SectionObjectPointer, + NewFileSize)) + { + DPRINT1("Couldn't decrease file size!\n"); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + return STATUS_USER_MAPPED_FILE; + } + } + + // Find the attribute with the data stream for our file + DPRINT("Finding Data Attribute...\n"); + Status = FindAttribute(DeviceExt, + FileRecord, + AttributeData, + Fcb->Stream, + wcslen(Fcb->Stream), + &DataContext, + &AttributeOffset); + + // Did we fail to find the attribute? + if (!NT_SUCCESS(Status)) + { + DPRINT1("No '%S' data stream associated with file!\n", Fcb->Stream); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + return Status; + } + + // Get the size of the data attribute + CurrentFileSize.QuadPart = AttributeDataLength(DataContext->pRecord); + + // Are we enlarging the attribute? + if (NewFileSize->QuadPart > CurrentFileSize.QuadPart) + { + // is increasing the stream size not allowed? + if ((Fcb->Flags & FCB_IS_VOLUME) || + (IrpFlags & IRP_PAGING_IO)) + { + // TODO - just fail for now + ReleaseAttributeContext(DataContext); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + return STATUS_ACCESS_DENIED; + } + } + + // set the attribute data length + Status = SetAttributeDataLength(FileObject, Fcb, DataContext, AttributeOffset, FileRecord, NewFileSize); + if (!NT_SUCCESS(Status)) + { + ReleaseAttributeContext(DataContext); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + return Status; + } + + // now we need to update this file's size in every directory index entry that references it + // TODO: expand to work with every filename / hardlink stored in the file record. + FileNameAttribute = GetBestFileNameFromRecord(Fcb->Vcb, FileRecord); + if (FileNameAttribute == NULL) + { + DPRINT1("Unable to find FileName attribute associated with file!\n"); + ReleaseAttributeContext(DataContext); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + return STATUS_INVALID_PARAMETER; + } + + ParentMFTId = FileNameAttribute->DirectoryFileReferenceNumber & NTFS_MFT_MASK; + + FileName.Buffer = FileNameAttribute->Name; + FileName.Length = FileNameAttribute->NameLength * sizeof(WCHAR); + FileName.MaximumLength = FileName.Length; + + AllocationSize = AttributeAllocatedLength(DataContext->pRecord); + + Status = UpdateFileNameRecord(Fcb->Vcb, + ParentMFTId, + &FileName, + FALSE, + NewFileSize->QuadPart, + AllocationSize, + CaseSensitive); + + ReleaseAttributeContext(DataContext); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + + return Status; +} + +/** +* @name NtfsSetInformation +* @implemented +* +* Sets the specified file information. +* +* @param IrpContext +* Points to an NTFS_IRP_CONTEXT which describes the set operation +* +* @return +* STATUS_SUCCESS if successful, +* STATUS_NOT_IMPLEMENTED if trying to set an unimplemented information class, +* STATUS_USER_MAPPED_FILE if trying to truncate a file but MmCanFileBeTruncated() returned false, +* STATUS_OBJECT_NAME_NOT_FOUND if there was no $DATA attribute associated with the target file, +* STATUS_INVALID_PARAMETER if there was no $FILENAME attribute associated with the target file, +* STATUS_INSUFFICIENT_RESOURCES if an allocation failed, +* STATUS_ACCESS_DENIED if target file is a volume or if paging is involved. +* +* @remarks Called by NtfsDispatch() in response to an IRP_MJ_SET_INFORMATION request. +* Only the FileEndOfFileInformation InformationClass is fully implemented. FileAllocationInformation +* is a hack and not a true implementation, but it's enough to make SetEndOfFile() work. +* All other information classes are TODO. +* +*/ +NTSTATUS +NtfsSetInformation(PNTFS_IRP_CONTEXT IrpContext) +{ + FILE_INFORMATION_CLASS FileInformationClass; + PIO_STACK_LOCATION Stack; + PDEVICE_EXTENSION DeviceExt; + PFILE_OBJECT FileObject; + PNTFS_FCB Fcb; + PVOID SystemBuffer; + ULONG BufferLength; + PIRP Irp; + PDEVICE_OBJECT DeviceObject; + NTSTATUS Status = STATUS_NOT_IMPLEMENTED; + + DPRINT1("NtfsSetInformation(%p)\n", IrpContext); + + Irp = IrpContext->Irp; + Stack = IrpContext->Stack; + DeviceObject = IrpContext->DeviceObject; + DeviceExt = DeviceObject->DeviceExtension; + FileInformationClass = Stack->Parameters.QueryFile.FileInformationClass; + FileObject = IrpContext->FileObject; + Fcb = FileObject->FsContext; + + SystemBuffer = Irp->AssociatedIrp.SystemBuffer; + BufferLength = Stack->Parameters.QueryFile.Length; + + if (!ExAcquireResourceSharedLite(&Fcb->MainResource, + BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT))) + { + return NtfsMarkIrpContextForQueue(IrpContext); + } + + switch (FileInformationClass) + { + PFILE_END_OF_FILE_INFORMATION EndOfFileInfo; + + /* TODO: Allocation size is not actually the same as file end for NTFS, + however, few applications are likely to make the distinction. */ + case FileAllocationInformation: + DPRINT1("FIXME: Using hacky method of setting FileAllocationInformation.\n"); + case FileEndOfFileInformation: + EndOfFileInfo = (PFILE_END_OF_FILE_INFORMATION)SystemBuffer; + Status = NtfsSetEndOfFile(Fcb, + FileObject, + DeviceExt, + Irp->Flags, + BooleanFlagOn(Stack->Flags, SL_CASE_SENSITIVE), + &EndOfFileInfo->EndOfFile); + break; + + // TODO: all other information classes + + default: + DPRINT1("FIXME: Unimplemented information class: %s\n", GetInfoClassName(FileInformationClass)); + Status = STATUS_NOT_IMPLEMENTED; + } + + ExReleaseResourceLite(&Fcb->MainResource); + + if (NT_SUCCESS(Status)) + Irp->IoStatus.Information = + Stack->Parameters.QueryFile.Length - BufferLength; + else + Irp->IoStatus.Information = 0; + + return Status; +} /* EOF */ diff --git a/drivers/filesystems/ntfs/fsctl.c b/drivers/filesystems/ntfs/fsctl.c index 0ce93a58eaa..76337ec4482 100644 --- a/drivers/filesystems/ntfs/fsctl.c +++ b/drivers/filesystems/ntfs/fsctl.c @@ -295,7 +295,13 @@ NtfsGetVolumeData(PDEVICE_OBJECT DeviceObject, return Status; } - Status = FindAttribute(DeviceExt, DeviceExt->MasterFileTable, AttributeData, L"", 0, &DeviceExt->MFTContext); + Status = FindAttribute(DeviceExt, + DeviceExt->MasterFileTable, + AttributeData, + L"", + 0, + &DeviceExt->MFTContext, + &DeviceExt->MftDataOffset); if (!NT_SUCCESS(Status)) { DPRINT1("Can't find data attribute for Master File Table.\n"); @@ -333,11 +339,11 @@ NtfsGetVolumeData(PDEVICE_OBJECT DeviceObject, NtfsDumpFileAttributes(DeviceExt, VolumeRecord); /* Get volume name */ - Status = FindAttribute(DeviceExt, VolumeRecord, AttributeVolumeName, L"", 0, &AttrCtxt); + Status = FindAttribute(DeviceExt, VolumeRecord, AttributeVolumeName, L"", 0, &AttrCtxt, NULL); - if (NT_SUCCESS(Status) && AttrCtxt->Record.Resident.ValueLength != 0) + if (NT_SUCCESS(Status) && AttrCtxt->pRecord->Resident.ValueLength != 0) { - Attribute = &AttrCtxt->Record; + Attribute = AttrCtxt->pRecord; DPRINT("Data length %lu\n", AttributeDataLength(Attribute)); NtfsInfo->VolumeLabelLength = min (Attribute->Resident.ValueLength, MAXIMUM_VOLUME_LABEL_LENGTH); @@ -374,11 +380,11 @@ NtfsGetVolumeData(PDEVICE_OBJECT DeviceObject, DeviceExt->VolumeFcb = VolumeFcb; /* Get volume information */ - Status = FindAttribute(DeviceExt, VolumeRecord, AttributeVolumeInformation, L"", 0, &AttrCtxt); + Status = FindAttribute(DeviceExt, VolumeRecord, AttributeVolumeInformation, L"", 0, &AttrCtxt, NULL); - if (NT_SUCCESS(Status) && AttrCtxt->Record.Resident.ValueLength != 0) + if (NT_SUCCESS(Status) && AttrCtxt->pRecord->Resident.ValueLength != 0) { - Attribute = &AttrCtxt->Record; + Attribute = AttrCtxt->pRecord; DPRINT("Data length %lu\n", AttributeDataLength (Attribute)); VolumeInfo = (PVOID)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset); @@ -804,7 +810,7 @@ GetVolumeBitmap(PDEVICE_EXTENSION DeviceExt, return Status; } - Status = FindAttribute(DeviceExt, BitmapRecord, AttributeData, L"", 0, &DataContext); + Status = FindAttribute(DeviceExt, BitmapRecord, AttributeData, L"", 0, &DataContext, NULL); if (!NT_SUCCESS(Status)) { DPRINT1("Failed find $DATA for bitmap: %lx\n", Status); diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index d02a32dc23c..4896d6d5500 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -24,11 +24,13 @@ * Valentin Verkhovsky * Pierre Schweitzer (pierre@reactos.org) * Hervé Poussineau (hpoussin@reactos.org) + * Trevor Thompson */ /* INCLUDES *****************************************************************/ #include "ntfs.h" +#include #define NDEBUG #include @@ -41,15 +43,34 @@ PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord) PNTFS_ATTR_CONTEXT Context; Context = ExAllocatePoolWithTag(NonPagedPool, - FIELD_OFFSET(NTFS_ATTR_CONTEXT, Record) + AttrRecord->Length, + sizeof(NTFS_ATTR_CONTEXT), TAG_NTFS); - RtlCopyMemory(&Context->Record, AttrRecord, AttrRecord->Length); + if(!Context) + { + DPRINT1("Error: Unable to allocate memory for context!\n"); + return NULL; + } + + // Allocate memory for a copy of the attribute + Context->pRecord = ExAllocatePoolWithTag(NonPagedPool, AttrRecord->Length, TAG_NTFS); + if(!Context->pRecord) + { + DPRINT1("Error: Unable to allocate memory for attribute record!\n"); + ExFreePoolWithTag(Context, TAG_NTFS); + return NULL; + } + + // Copy the attribute + RtlCopyMemory(Context->pRecord, AttrRecord, AttrRecord->Length); + if (AttrRecord->IsNonResident) { LONGLONG DataRunOffset; ULONGLONG DataRunLength; + ULONGLONG NextVBN = 0; + PUCHAR DataRun = (PUCHAR)((ULONG_PTR)Context->pRecord + Context->pRecord->NonResident.MappingPairsOffset); - Context->CacheRun = (PUCHAR)&Context->Record + Context->Record.NonResident.MappingPairsOffset; + Context->CacheRun = DataRun; Context->CacheRunOffset = 0; Context->CacheRun = DecodeRun(Context->CacheRun, &DataRunOffset, &DataRunLength); Context->CacheRunLength = DataRunLength; @@ -66,6 +87,15 @@ PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord) Context->CacheRunLastLCN = 0; } Context->CacheRunCurrentOffset = 0; + + // Convert the data runs to a map control block + if (!NT_SUCCESS(ConvertDataRunsToLargeMCB(DataRun, &Context->DataRunsMCB, &NextVBN))) + { + DPRINT1("Unable to convert data runs to MCB!\n"); + ExFreePoolWithTag(Context->pRecord, TAG_NTFS); + ExFreePoolWithTag(Context, TAG_NTFS); + return NULL; + } } return Context; @@ -75,24 +105,43 @@ PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord) VOID ReleaseAttributeContext(PNTFS_ATTR_CONTEXT Context) { + if (Context->pRecord->IsNonResident) + { + FsRtlUninitializeLargeMcb(&Context->DataRunsMCB); + } + + if(Context->pRecord) + ExFreePoolWithTag(Context->pRecord, TAG_NTFS); + ExFreePoolWithTag(Context, TAG_NTFS); } +/** +* @name FindAttribute +* @implemented +* +* Searches a file record for an attribute matching the given type and name. +* +* @param Offset +* Optional pointer to a ULONG that will receive the offset of the found attribute +* from the beginning of the record. Can be set to NULL. +*/ NTSTATUS FindAttribute(PDEVICE_EXTENSION Vcb, PFILE_RECORD_HEADER MftRecord, ULONG Type, PCWSTR Name, ULONG NameLength, - PNTFS_ATTR_CONTEXT * AttrCtx) + PNTFS_ATTR_CONTEXT * AttrCtx, + PULONG Offset) { BOOLEAN Found; NTSTATUS Status; FIND_ATTR_CONTXT Context; PNTFS_ATTR_RECORD Attribute; - DPRINT("FindAttribute(%p, %p, 0x%x, %S, %u, %p)\n", Vcb, MftRecord, Type, Name, NameLength, AttrCtx); + DPRINT("FindAttribute(%p, %p, 0x%x, %S, %lu, %p, %p)\n", Vcb, MftRecord, Type, Name, NameLength, AttrCtx, Offset); Found = FALSE; Status = FindFirstAttribute(&Context, Vcb, MftRecord, FALSE, &Attribute); @@ -106,7 +155,7 @@ FindAttribute(PDEVICE_EXTENSION Vcb, AttrName = (PWCHAR)((PCHAR)Attribute + Attribute->NameOffset); DPRINT("%.*S, %.*S\n", Attribute->NameLength, AttrName, NameLength, Name); - if (RtlCompareMemory(AttrName, Name, NameLength << 1) == (NameLength << 1)) + if (RtlCompareMemory(AttrName, Name, NameLength * sizeof(WCHAR)) == (NameLength * sizeof(WCHAR))) { Found = TRUE; } @@ -121,6 +170,12 @@ FindAttribute(PDEVICE_EXTENSION Vcb, /* Found it, fill up the context and return. */ DPRINT("Found context\n"); *AttrCtx = PrepareAttributeContext(Attribute); + + (*AttrCtx)->FileMFTIndex = MftRecord->MFTRecordNumber; + + if (Offset != NULL) + *Offset = Context.Offset; + FindCloseAttribute(&Context); return STATUS_SUCCESS; } @@ -134,13 +189,13 @@ FindAttribute(PDEVICE_EXTENSION Vcb, } -ULONG +ULONGLONG AttributeAllocatedLength(PNTFS_ATTR_RECORD AttrRecord) { if (AttrRecord->IsNonResident) return AttrRecord->NonResident.AllocatedSize; else - return AttrRecord->Resident.ValueLength; + return ALIGN_UP_BY(AttrRecord->Resident.ValueLength, ATTR_RECORD_ALIGNMENT); } @@ -153,6 +208,800 @@ AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord) return AttrRecord->Resident.ValueLength; } +/** +* @name IncreaseMftSize +* @implemented +* +* Increases the size of the master file table on a volume, increasing the space available for file records. +* +* @param Vcb +* Pointer to the VCB (DEVICE_EXTENSION) of the target volume. +* +* +* @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 an allocation fails. +* STATUS_INVALID_PARAMETER if there was an error reading the Mft's bitmap. +* STATUS_CANT_WAIT if CanWait was FALSE and the function could not get immediate, exclusive access to the MFT. +* +* @remarks +* Increases the size of the Master File Table by 64 records. Bitmap entries for the new records are cleared, +* and the bitmap is also enlarged if needed. Mimicking Windows' behavior when enlarging the mft is still TODO. +* This function will wait for exlusive access to the volume fcb. +*/ +NTSTATUS +IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait) +{ + PNTFS_ATTR_CONTEXT BitmapContext; + LARGE_INTEGER BitmapSize; + LARGE_INTEGER DataSize; + LONGLONG BitmapSizeDifference; + ULONG NewRecords = ATTR_RECORD_ALIGNMENT * 8; // Allocate one new record for every bit of every byte we'll be adding to the bitmap + ULONG DataSizeDifference = Vcb->NtfsInfo.BytesPerFileRecord * NewRecords; + ULONG BitmapOffset; + PUCHAR BitmapBuffer; + ULONGLONG BitmapBytes; + ULONGLONG NewBitmapSize; + ULONGLONG FirstNewMftIndex; + ULONG BytesRead; + ULONG LengthWritten; + PFILE_RECORD_HEADER BlankFileRecord; + ULONG i; + NTSTATUS Status; + + DPRINT1("IncreaseMftSize(%p, %s)\n", Vcb, CanWait ? "TRUE" : "FALSE"); + + // We need exclusive access to the mft while we change its size + if (!ExAcquireResourceExclusiveLite(&(Vcb->DirResource), CanWait)) + { + return STATUS_CANT_WAIT; + } + + // Create a blank file record that will be used later + BlankFileRecord = NtfsCreateEmptyFileRecord(Vcb); + if (!BlankFileRecord) + { + DPRINT1("Error: Unable to create empty file record!\n"); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Clear the flags (file record is not in use) + BlankFileRecord->Flags = 0; + + // Find the bitmap attribute of master file table + Status = FindAttribute(Vcb, Vcb->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Couldn't find $BITMAP attribute of Mft!\n"); + ExFreePoolWithTag(BlankFileRecord, TAG_NTFS); + ExReleaseResourceLite(&(Vcb->DirResource)); + return Status; + } + + // Get size of Bitmap Attribute + BitmapSize.QuadPart = AttributeDataLength(BitmapContext->pRecord); + + // Calculate the new mft size + DataSize.QuadPart = AttributeDataLength(Vcb->MFTContext->pRecord) + DataSizeDifference; + + // Find the index of the first Mft entry that will be created + FirstNewMftIndex = AttributeDataLength(Vcb->MFTContext->pRecord) / Vcb->NtfsInfo.BytesPerFileRecord; + + // Determine how many bytes will make up the bitmap + BitmapBytes = DataSize.QuadPart / Vcb->NtfsInfo.BytesPerFileRecord / 8; + if ((DataSize.QuadPart / Vcb->NtfsInfo.BytesPerFileRecord) % 8 != 0) + BitmapBytes++; + + // Windows will always keep the number of bytes in a bitmap as a multiple of 8, so no bytes are wasted on slack + BitmapBytes = ALIGN_UP_BY(BitmapBytes, ATTR_RECORD_ALIGNMENT); + + // Determine how much we need to adjust the bitmap size (it's possible we don't) + BitmapSizeDifference = BitmapBytes - BitmapSize.QuadPart; + NewBitmapSize = max(BitmapSize.QuadPart + BitmapSizeDifference, BitmapSize.QuadPart); + + // Allocate memory for the bitmap + BitmapBuffer = ExAllocatePoolWithTag(NonPagedPool, NewBitmapSize, TAG_NTFS); + if (!BitmapBuffer) + { + DPRINT1("ERROR: Unable to allocate memory for bitmap attribute!\n"); + ExFreePoolWithTag(BlankFileRecord, TAG_NTFS); + ExReleaseResourceLite(&(Vcb->DirResource)); + ReleaseAttributeContext(BitmapContext); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Zero the bytes we'll be adding + RtlZeroMemory(BitmapBuffer, NewBitmapSize); + + // Read the bitmap attribute + BytesRead = ReadAttribute(Vcb, + BitmapContext, + 0, + (PCHAR)BitmapBuffer, + BitmapSize.LowPart); + if (BytesRead != BitmapSize.LowPart) + { + DPRINT1("ERROR: Bytes read != Bitmap size!\n"); + ExFreePoolWithTag(BlankFileRecord, TAG_NTFS); + ExReleaseResourceLite(&(Vcb->DirResource)); + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + return STATUS_INVALID_PARAMETER; + } + + // Increase the mft size + Status = SetNonResidentAttributeDataLength(Vcb, Vcb->MFTContext, Vcb->MftDataOffset, Vcb->MasterFileTable, &DataSize); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to set size of $MFT data attribute!\n"); + ExFreePoolWithTag(BlankFileRecord, TAG_NTFS); + ExReleaseResourceLite(&(Vcb->DirResource)); + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + return Status; + } + + // We'll need to find the bitmap again, because its offset will have changed after resizing the data attribute + ReleaseAttributeContext(BitmapContext); + Status = FindAttribute(Vcb, Vcb->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, &BitmapOffset); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Couldn't find $BITMAP attribute of Mft!\n"); + ExFreePoolWithTag(BlankFileRecord, TAG_NTFS); + ExReleaseResourceLite(&(Vcb->DirResource)); + return Status; + } + + // If the bitmap grew + if (BitmapSizeDifference > 0) + { + // Set the new bitmap size + BitmapSize.QuadPart = NewBitmapSize; + if (BitmapContext->pRecord->IsNonResident) + Status = SetNonResidentAttributeDataLength(Vcb, BitmapContext, BitmapOffset, Vcb->MasterFileTable, &BitmapSize); + else + Status = SetResidentAttributeDataLength(Vcb, BitmapContext, BitmapOffset, Vcb->MasterFileTable, &BitmapSize); + + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to set size of bitmap attribute!\n"); + ExFreePoolWithTag(BlankFileRecord, TAG_NTFS); + ExReleaseResourceLite(&(Vcb->DirResource)); + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + return Status; + } + } + + NtfsDumpFileAttributes(Vcb, Vcb->MasterFileTable); + + // Update the file record with the new attribute sizes + Status = UpdateFileRecord(Vcb, Vcb->VolumeFcb->MFTIndex, Vcb->MasterFileTable); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to update $MFT file record!\n"); + ExFreePoolWithTag(BlankFileRecord, TAG_NTFS); + ExReleaseResourceLite(&(Vcb->DirResource)); + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + return Status; + } + + // Write out the new bitmap + Status = WriteAttribute(Vcb, BitmapContext, 0, BitmapBuffer, NewBitmapSize, &LengthWritten, Vcb->MasterFileTable); + if (!NT_SUCCESS(Status)) + { + ExFreePoolWithTag(BlankFileRecord, TAG_NTFS); + ExReleaseResourceLite(&(Vcb->DirResource)); + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + DPRINT1("ERROR: Couldn't write to bitmap attribute of $MFT!\n"); + return Status; + } + + // Create blank records for the new file record entries. + for (i = 0; i < NewRecords; i++) + { + Status = UpdateFileRecord(Vcb, FirstNewMftIndex + i, BlankFileRecord); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to write blank file record!\n"); + ExFreePoolWithTag(BlankFileRecord, TAG_NTFS); + ExReleaseResourceLite(&(Vcb->DirResource)); + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + return Status; + } + } + + // Update the mft mirror + Status = UpdateMftMirror(Vcb); + + // Cleanup + ExFreePoolWithTag(BlankFileRecord, TAG_NTFS); + ExReleaseResourceLite(&(Vcb->DirResource)); + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + + return Status; +} + +/** +* @name MoveAttributes +* @implemented +* +* Moves a block of attributes to a new location in the file Record. The attribute at FirstAttributeToMove +* and every attribute after that will be moved to MoveTo. +* +* @param DeviceExt +* Pointer to the DEVICE_EXTENSION (VCB) of the target volume. +* +* @param FirstAttributeToMove +* Pointer to the first NTFS_ATTR_RECORD that needs to be moved. This pointer must reside within a file record. +* +* @param FirstAttributeOffset +* Offset of FirstAttributeToMove relative to the beginning of the file record. +* +* @param MoveTo +* ULONG_PTR with the memory location that will be the new location of the first attribute being moved. +* +* @return +* The new location of the final attribute (i.e. AttributeEnd marker). +*/ +PNTFS_ATTR_RECORD +MoveAttributes(PDEVICE_EXTENSION DeviceExt, + PNTFS_ATTR_RECORD FirstAttributeToMove, + ULONG FirstAttributeOffset, + ULONG_PTR MoveTo) +{ + // Get the size of all attributes after this one + ULONG MemBlockSize = 0; + PNTFS_ATTR_RECORD CurrentAttribute = FirstAttributeToMove; + ULONG CurrentOffset = FirstAttributeOffset; + PNTFS_ATTR_RECORD FinalAttribute; + + while (CurrentAttribute->Type != AttributeEnd && CurrentOffset < DeviceExt->NtfsInfo.BytesPerFileRecord) + { + CurrentOffset += CurrentAttribute->Length; + MemBlockSize += CurrentAttribute->Length; + CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length); + } + + FinalAttribute = (PNTFS_ATTR_RECORD)(MoveTo + MemBlockSize); + MemBlockSize += sizeof(ULONG) * 2; // Add the AttributeEnd and file record end + + ASSERT(MemBlockSize % ATTR_RECORD_ALIGNMENT == 0); + + // Move the attributes after this one + RtlMoveMemory((PCHAR)MoveTo, FirstAttributeToMove, MemBlockSize); + + return FinalAttribute; +} + +NTSTATUS +InternalSetResidentAttributeLength(PDEVICE_EXTENSION DeviceExt, + PNTFS_ATTR_CONTEXT AttrContext, + PFILE_RECORD_HEADER FileRecord, + ULONG AttrOffset, + ULONG DataSize) +{ + PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset); + PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Destination + Destination->Length); + PNTFS_ATTR_RECORD FinalAttribute; + ULONG OldAttributeLength = Destination->Length; + ULONG NextAttributeOffset; + + DPRINT1("InternalSetResidentAttributeLength( %p, %p, %p, %lu, %lu )\n", DeviceExt, AttrContext, FileRecord, AttrOffset, DataSize); + + ASSERT(!AttrContext->pRecord->IsNonResident); + + // Update ValueLength Field + Destination->Resident.ValueLength = DataSize; + + // Calculate the record length and end marker offset + Destination->Length = ALIGN_UP_BY(DataSize + AttrContext->pRecord->Resident.ValueOffset, ATTR_RECORD_ALIGNMENT); + NextAttributeOffset = AttrOffset + Destination->Length; + + // Ensure NextAttributeOffset is aligned to an 8-byte boundary + ASSERT(NextAttributeOffset % ATTR_RECORD_ALIGNMENT == 0); + + // Will the new attribute be larger than the old one? + if (Destination->Length > OldAttributeLength) + { + // Free the old copy of the attribute in the context, as it will be the wrong length + ExFreePoolWithTag(AttrContext->pRecord, TAG_NTFS); + + // Create a new copy of the attribute record for the context + AttrContext->pRecord = ExAllocatePoolWithTag(NonPagedPool, Destination->Length, TAG_NTFS); + if (!AttrContext->pRecord) + { + DPRINT1("Unable to allocate memory for attribute!\n"); + return STATUS_INSUFFICIENT_RESOURCES; + } + RtlZeroMemory((PVOID)((ULONG_PTR)AttrContext->pRecord + OldAttributeLength), Destination->Length - OldAttributeLength); + RtlCopyMemory(AttrContext->pRecord, Destination, OldAttributeLength); + } + + // Are there attributes after this one that need to be moved? + if (NextAttribute->Type != AttributeEnd) + { + // Move the attributes after this one + FinalAttribute = MoveAttributes(DeviceExt, NextAttribute, NextAttributeOffset, (ULONG_PTR)Destination + Destination->Length); + } + else + { + // advance to the final "attribute," adjust for the changed length of the attribute we're resizing + FinalAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)NextAttribute - OldAttributeLength + Destination->Length); + } + + // Update pRecord's length + AttrContext->pRecord->Length = Destination->Length; + AttrContext->pRecord->Resident.ValueLength = DataSize; + + // set the file record end + SetFileRecordEnd(FileRecord, FinalAttribute, FILE_RECORD_END); + + //NtfsDumpFileRecord(DeviceExt, FileRecord); + + return STATUS_SUCCESS; +} + +/** +* @parameter FileRecord +* Pointer to a file record. Must be a full record at least +* Fcb->Vcb->NtfsInfo.BytesPerFileRecord bytes large, not just the header. +*/ +NTSTATUS +SetAttributeDataLength(PFILE_OBJECT FileObject, + PNTFS_FCB Fcb, + PNTFS_ATTR_CONTEXT AttrContext, + ULONG AttrOffset, + PFILE_RECORD_HEADER FileRecord, + PLARGE_INTEGER DataSize) +{ + NTSTATUS Status = STATUS_SUCCESS; + + DPRINT1("SetAttributeDataLength(%p, %p, %p, %lu, %p, %I64u)\n", + FileObject, + Fcb, + AttrContext, + AttrOffset, + FileRecord, + DataSize->QuadPart); + + // are we truncating the file? + if (DataSize->QuadPart < AttributeDataLength(AttrContext->pRecord)) + { + if (!MmCanFileBeTruncated(FileObject->SectionObjectPointer, DataSize)) + { + DPRINT1("Can't truncate a memory-mapped file!\n"); + return STATUS_USER_MAPPED_FILE; + } + } + + if (AttrContext->pRecord->IsNonResident) + { + Status = SetNonResidentAttributeDataLength(Fcb->Vcb, + AttrContext, + AttrOffset, + FileRecord, + DataSize); + } + else + { + // resident attribute + Status = SetResidentAttributeDataLength(Fcb->Vcb, + AttrContext, + AttrOffset, + FileRecord, + DataSize); + } + + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to set size of attribute!\n"); + return Status; + } + + //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord); + + // write the updated file record back to disk + Status = UpdateFileRecord(Fcb->Vcb, Fcb->MFTIndex, FileRecord); + + if (NT_SUCCESS(Status)) + { + if (AttrContext->pRecord->IsNonResident) + Fcb->RFCB.AllocationSize.QuadPart = AttrContext->pRecord->NonResident.AllocatedSize; + else + Fcb->RFCB.AllocationSize = *DataSize; + Fcb->RFCB.FileSize = *DataSize; + Fcb->RFCB.ValidDataLength = *DataSize; + CcSetFileSizes(FileObject, (PCC_FILE_SIZES)&Fcb->RFCB.AllocationSize); + } + + return STATUS_SUCCESS; +} + +/** +* @name SetFileRecordEnd +* @implemented +* +* This small function sets a new endpoint for the file record. It set's the final +* AttrEnd->Type to AttributeEnd and recalculates the bytes used by the file record. +* +* @param FileRecord +* Pointer to the file record whose endpoint (length) will be set. +* +* @param AttrEnd +* Pointer to section of memory that will receive the AttributeEnd marker. This must point +* to memory allocated for the FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord). +* +* @param EndMarker +* This value will be written after AttributeEnd but isn't critical at all. When Windows resizes +* a file record, it preserves the final ULONG that previously ended the record, even though this +* value is (to my knowledge) never used. We emulate this behavior. +* +*/ +VOID +SetFileRecordEnd(PFILE_RECORD_HEADER FileRecord, + PNTFS_ATTR_RECORD AttrEnd, + ULONG EndMarker) +{ + // Ensure AttrEnd is aligned on an 8-byte boundary, relative to FileRecord + ASSERT(((ULONG_PTR)AttrEnd - (ULONG_PTR)FileRecord) % ATTR_RECORD_ALIGNMENT == 0); + + // mark the end of attributes + AttrEnd->Type = AttributeEnd; + + // Restore the "file-record-end marker." The value is never checked but this behavior is consistent with Win2k3. + AttrEnd->Length = EndMarker; + + // recalculate bytes in use + FileRecord->BytesInUse = (ULONG_PTR)AttrEnd - (ULONG_PTR)FileRecord + sizeof(ULONG) * 2; +} + +/** +* @name SetNonResidentAttributeDataLength +* @implemented +* +* Called by SetAttributeDataLength() to set the size of a non-resident attribute. Doesn't update the file record. +* +* @param Vcb +* Pointer to a DEVICE_EXTENSION describing the target disk. +* +* @param AttrContext +* PNTFS_ATTR_CONTEXT describing the location of the attribute whose size is being set. +* +* @param AttrOffset +* Offset, from the beginning of the record, of the attribute being sized. +* +* @param FileRecord +* Pointer to a file record containing the attribute to be resized. Must be a complete file record, +* not just the header. +* +* @param DataSize +* Pointer to a LARGE_INTEGER describing the new size of the attribute's data. +* +* @return +* STATUS_SUCCESS on success; +* STATUS_INSUFFICIENT_RESOURCES if an allocation fails. +* STATUS_INVALID_PARAMETER if we can't find the last cluster in the data run. +* +* @remarks +* Called by SetAttributeDataLength() and IncreaseMftSize(). Use SetAttributeDataLength() unless you have a good +* reason to use this. Doesn't update the file record on disk. Doesn't inform the cache controller of changes with +* any associated files. Synchronization is the callers responsibility. +*/ +NTSTATUS +SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, + PNTFS_ATTR_CONTEXT AttrContext, + ULONG AttrOffset, + PFILE_RECORD_HEADER FileRecord, + PLARGE_INTEGER DataSize) +{ + NTSTATUS Status = STATUS_SUCCESS; + ULONG BytesPerCluster = Vcb->NtfsInfo.BytesPerCluster; + ULONGLONG AllocationSize = ROUND_UP(DataSize->QuadPart, BytesPerCluster); + PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset); + ULONG ExistingClusters = AttrContext->pRecord->NonResident.AllocatedSize / BytesPerCluster; + + ASSERT(AttrContext->pRecord->IsNonResident); + + // do we need to increase the allocation size? + if (AttrContext->pRecord->NonResident.AllocatedSize < AllocationSize) + { + ULONG ClustersNeeded = (AllocationSize / BytesPerCluster) - ExistingClusters; + LARGE_INTEGER LastClusterInDataRun; + ULONG NextAssignedCluster; + ULONG AssignedClusters; + + if (ExistingClusters == 0) + { + LastClusterInDataRun.QuadPart = 0; + } + else + { + if (!FsRtlLookupLargeMcbEntry(&AttrContext->DataRunsMCB, + (LONGLONG)AttrContext->pRecord->NonResident.HighestVCN, + (PLONGLONG)&LastClusterInDataRun.QuadPart, + NULL, + NULL, + NULL, + NULL)) + { + DPRINT1("Error looking up final large MCB entry!\n"); + + // Most likely, HighestVCN went above the largest mapping + DPRINT1("Highest VCN of record: %I64u\n", AttrContext->pRecord->NonResident.HighestVCN); + return STATUS_INVALID_PARAMETER; + } + } + + DPRINT("LastClusterInDataRun: %I64u\n", LastClusterInDataRun.QuadPart); + DPRINT("Highest VCN of record: %I64u\n", AttrContext->pRecord->NonResident.HighestVCN); + + while (ClustersNeeded > 0) + { + Status = NtfsAllocateClusters(Vcb, + LastClusterInDataRun.LowPart + 1, + ClustersNeeded, + &NextAssignedCluster, + &AssignedClusters); + + if (!NT_SUCCESS(Status)) + { + DPRINT1("Error: Unable to allocate requested clusters!\n"); + return Status; + } + + // now we need to add the clusters we allocated to the data run + Status = AddRun(Vcb, AttrContext, AttrOffset, FileRecord, NextAssignedCluster, AssignedClusters); + if (!NT_SUCCESS(Status)) + { + DPRINT1("Error: Unable to add data run!\n"); + return Status; + } + + ClustersNeeded -= AssignedClusters; + LastClusterInDataRun.LowPart = NextAssignedCluster + AssignedClusters - 1; + } + } + else if (AttrContext->pRecord->NonResident.AllocatedSize > AllocationSize) + { + // shrink allocation size + ULONG ClustersToFree = ExistingClusters - (AllocationSize / BytesPerCluster); + Status = FreeClusters(Vcb, AttrContext, AttrOffset, FileRecord, ClustersToFree); + } + + // TODO: is the file compressed, encrypted, or sparse? + + AttrContext->pRecord->NonResident.AllocatedSize = AllocationSize; + AttrContext->pRecord->NonResident.DataSize = DataSize->QuadPart; + AttrContext->pRecord->NonResident.InitializedSize = DataSize->QuadPart; + + DestinationAttribute->NonResident.AllocatedSize = AllocationSize; + DestinationAttribute->NonResident.DataSize = DataSize->QuadPart; + DestinationAttribute->NonResident.InitializedSize = DataSize->QuadPart; + + // HighestVCN seems to be set incorrectly somewhere. Apply a hack-fix to reset it. + // HACKHACK FIXME: Fix for sparse files; this math won't work in that case. + AttrContext->pRecord->NonResident.HighestVCN = ((ULONGLONG)AllocationSize / Vcb->NtfsInfo.BytesPerCluster) - 1; + DestinationAttribute->NonResident.HighestVCN = AttrContext->pRecord->NonResident.HighestVCN; + + DPRINT("Allocated Size: %I64u\n", DestinationAttribute->NonResident.AllocatedSize); + + return Status; +} + +/** +* @name SetResidentAttributeDataLength +* @implemented +* +* Called by SetAttributeDataLength() to set the size of a non-resident attribute. Doesn't update the file record. +* +* @param Vcb +* Pointer to a DEVICE_EXTENSION describing the target disk. +* +* @param AttrContext +* PNTFS_ATTR_CONTEXT describing the location of the attribute whose size is being set. +* +* @param AttrOffset +* Offset, from the beginning of the record, of the attribute being sized. +* +* @param FileRecord +* Pointer to a file record containing the attribute to be resized. Must be a complete file record, +* not just the header. +* +* @param DataSize +* Pointer to a LARGE_INTEGER describing the new size of the attribute's data. +* +* @return +* STATUS_SUCCESS on success; +* STATUS_INSUFFICIENT_RESOURCES if an allocation fails. +* STATUS_INVALID_PARAMETER if AttrContext describes a non-resident attribute. +* STATUS_NOT_IMPLEMENTED if requested to decrease the size of an attribute that isn't the +* last attribute listed in the file record. +* +* @remarks +* Called by SetAttributeDataLength() and IncreaseMftSize(). Use SetAttributeDataLength() unless you have a good +* reason to use this. Doesn't update the file record on disk. Doesn't inform the cache controller of changes with +* any associated files. Synchronization is the callers responsibility. +*/ +NTSTATUS +SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, + PNTFS_ATTR_CONTEXT AttrContext, + ULONG AttrOffset, + PFILE_RECORD_HEADER FileRecord, + PLARGE_INTEGER DataSize) +{ + NTSTATUS Status; + + // find the next attribute + ULONG NextAttributeOffset = AttrOffset + AttrContext->pRecord->Length; + PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((PCHAR)FileRecord + NextAttributeOffset); + + ASSERT(!AttrContext->pRecord->IsNonResident); + + //NtfsDumpFileAttributes(Vcb, FileRecord); + + // Do we need to increase the data length? + if (DataSize->QuadPart > AttrContext->pRecord->Resident.ValueLength) + { + // There's usually padding at the end of a record. Do we need to extend past it? + ULONG MaxValueLength = AttrContext->pRecord->Length - AttrContext->pRecord->Resident.ValueOffset; + if (MaxValueLength < DataSize->LowPart) + { + // If this is the last attribute, we could move the end marker to the very end of the file record + MaxValueLength += Vcb->NtfsInfo.BytesPerFileRecord - NextAttributeOffset - (sizeof(ULONG) * 2); + + if (MaxValueLength < DataSize->LowPart || NextAttribute->Type != AttributeEnd) + { + // convert attribute to non-resident + PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset); + PNTFS_ATTR_RECORD NewRecord; + LARGE_INTEGER AttribDataSize; + PVOID AttribData; + ULONG NewRecordLength; + ULONG EndAttributeOffset; + ULONG LengthWritten; + + DPRINT1("Converting attribute to non-resident.\n"); + + AttribDataSize.QuadPart = AttrContext->pRecord->Resident.ValueLength; + + // Is there existing data we need to back-up? + if (AttribDataSize.QuadPart > 0) + { + AttribData = ExAllocatePoolWithTag(NonPagedPool, AttribDataSize.QuadPart, TAG_NTFS); + if (AttribData == NULL) + { + DPRINT1("ERROR: Couldn't allocate memory for attribute data. Can't migrate to non-resident!\n"); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // read data to temp buffer + Status = ReadAttribute(Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Unable to read attribute before migrating!\n"); + ExFreePoolWithTag(AttribData, TAG_NTFS); + return Status; + } + } + + // Start by turning this attribute into a 0-length, non-resident attribute, then enlarge it. + + // The size of a 0-length, non-resident attribute will be 0x41 + the size of the attribute name, aligned to an 8-byte boundary + NewRecordLength = ALIGN_UP_BY(0x41 + (AttrContext->pRecord->NameLength * sizeof(WCHAR)), ATTR_RECORD_ALIGNMENT); + + // Create a new attribute record that will store the 0-length, non-resident attribute + NewRecord = ExAllocatePoolWithTag(NonPagedPool, NewRecordLength, TAG_NTFS); + + // Zero out the NonResident structure + RtlZeroMemory(NewRecord, NewRecordLength); + + // Copy the data that's common to both non-resident and resident attributes + RtlCopyMemory(NewRecord, AttrContext->pRecord, FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.ValueLength)); + + // if there's a name + if (AttrContext->pRecord->NameLength != 0) + { + // copy the name + // An attribute name will be located at offset 0x18 for a resident attribute, 0x40 for non-resident + RtlCopyMemory((PCHAR)((ULONG_PTR)NewRecord + 0x40), + (PCHAR)((ULONG_PTR)AttrContext->pRecord + 0x18), + AttrContext->pRecord->NameLength * sizeof(WCHAR)); + } + + // update the mapping pairs offset, which will be 0x40 (size of a non-resident header) + length in bytes of the name + NewRecord->NonResident.MappingPairsOffset = 0x40 + (AttrContext->pRecord->NameLength * sizeof(WCHAR)); + + // update the end of the file record + // calculate position of end markers (1 byte for empty data run) + EndAttributeOffset = AttrOffset + NewRecord->NonResident.MappingPairsOffset + 1; + EndAttributeOffset = ALIGN_UP_BY(EndAttributeOffset, ATTR_RECORD_ALIGNMENT); + + // Update the length + NewRecord->Length = EndAttributeOffset - AttrOffset; + + ASSERT(NewRecord->Length == NewRecordLength); + + // Copy the new attribute record into the file record + RtlCopyMemory(Destination, NewRecord, NewRecord->Length); + + // Update the file record end + SetFileRecordEnd(FileRecord, + (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + EndAttributeOffset), + FILE_RECORD_END); + + // Initialize the MCB, potentially catch an exception + _SEH2_TRY + { + FsRtlInitializeLargeMcb(&AttrContext->DataRunsMCB, NonPagedPool); + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + DPRINT1("Unable to create LargeMcb!\n"); + if (AttribDataSize.QuadPart > 0) + ExFreePoolWithTag(AttribData, TAG_NTFS); + _SEH2_YIELD(return _SEH2_GetExceptionCode()); + } _SEH2_END; + + // Mark the attribute as non-resident (we wait until after we know the LargeMcb was initialized) + NewRecord->IsNonResident = Destination->IsNonResident = 1; + + // Update file record on disk + Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Couldn't update file record to continue migration!\n"); + if (AttribDataSize.QuadPart > 0) + ExFreePoolWithTag(AttribData, TAG_NTFS); + return Status; + } + + // Now we need to free the old copy of the attribute record in the context and replace it with the new one + ExFreePoolWithTag(AttrContext->pRecord, TAG_NTFS); + AttrContext->pRecord = NewRecord; + + // Now we can treat the attribute as non-resident and enlarge it normally + Status = SetNonResidentAttributeDataLength(Vcb, AttrContext, AttrOffset, FileRecord, DataSize); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Unable to migrate resident attribute!\n"); + if (AttribDataSize.QuadPart > 0) + ExFreePoolWithTag(AttribData, TAG_NTFS); + return Status; + } + + // restore the back-up attribute, if we made one + if (AttribDataSize.QuadPart > 0) + { + Status = WriteAttribute(Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart, &LengthWritten, FileRecord); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Unable to write attribute data to non-resident clusters during migration!\n"); + // TODO: Reverse migration so no data is lost + ExFreePoolWithTag(AttribData, TAG_NTFS); + return Status; + } + + ExFreePoolWithTag(AttribData, TAG_NTFS); + } + } + } + } + + // set the new length of the resident attribute (if we didn't migrate it) + if (!AttrContext->pRecord->IsNonResident) + return InternalSetResidentAttributeLength(Vcb, AttrContext, FileRecord, AttrOffset, DataSize->LowPart); + + return STATUS_SUCCESS; +} ULONG ReadAttribute(PDEVICE_EXTENSION Vcb, @@ -170,14 +1019,23 @@ ReadAttribute(PDEVICE_EXTENSION Vcb, ULONG ReadLength; ULONG AlreadyRead; NTSTATUS Status; + + //TEMPTEMP + PUCHAR TempBuffer; - if (!Context->Record.IsNonResident) + if (!Context->pRecord->IsNonResident) { - if (Offset > Context->Record.Resident.ValueLength) + // We need to truncate Offset to a ULONG for pointer arithmetic + // The check below should ensure that Offset is well within the range of 32 bits + ULONG LittleOffset = (ULONG)Offset; + + // Ensure that offset isn't beyond the end of the attribute + if (Offset > Context->pRecord->Resident.ValueLength) return 0; - if (Offset + Length > Context->Record.Resident.ValueLength) - Length = (ULONG)(Context->Record.Resident.ValueLength - Offset); - RtlCopyMemory(Buffer, (PCHAR)&Context->Record + Context->Record.Resident.ValueOffset + Offset, Length); + if (Offset + Length > Context->pRecord->Resident.ValueLength) + Length = (ULONG)(Context->pRecord->Resident.ValueLength - Offset); + + RtlCopyMemory(Buffer, (PVOID)((ULONG_PTR)Context->pRecord + Context->pRecord->Resident.ValueOffset + LittleOffset), Length); return Length; } @@ -203,10 +1061,21 @@ ReadAttribute(PDEVICE_EXTENSION Vcb, } else { + //TEMPTEMP + ULONG UsedBufferSize; + TempBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS); + LastLCN = 0; - DataRun = (PUCHAR)&Context->Record + Context->Record.NonResident.MappingPairsOffset; CurrentOffset = 0; + // This will be rewritten in the next iteration to just use the DataRuns MCB directly + ConvertLargeMCBToDataRuns(&Context->DataRunsMCB, + TempBuffer, + Vcb->NtfsInfo.BytesPerFileRecord, + &UsedBufferSize); + + DataRun = TempBuffer; + while (1) { DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength); @@ -323,6 +1192,10 @@ ReadAttribute(PDEVICE_EXTENSION Vcb, } /* if Disk */ + // TEMPTEMP + if (Context->pRecord->IsNonResident) + ExFreePoolWithTag(TempBuffer, TAG_NTFS); + Context->CacheRun = DataRun; Context->CacheRunOffset = Offset + AlreadyRead; Context->CacheRunStartLCN = DataRunStartLCN; @@ -334,6 +1207,354 @@ ReadAttribute(PDEVICE_EXTENSION Vcb, } +/** +* @name WriteAttribute +* @implemented +* +* Writes an NTFS attribute to the disk. It presently borrows a lot of code from ReadAttribute(), +* and it still needs more documentation / cleaning up. +* +* @param Vcb +* Volume Control Block indicating which volume to write the attribute to +* +* @param Context +* Pointer to an NTFS_ATTR_CONTEXT that has information about the attribute +* +* @param Offset +* Offset, in bytes, from the beginning of the attribute indicating where to start +* writing data +* +* @param Buffer +* The data that's being written to the device +* +* @param Length +* How much data will be written, in bytes +* +* @param RealLengthWritten +* Pointer to a ULONG which will receive how much data was written, in bytes +* +* @param FileRecord +* Optional pointer to a FILE_RECORD_HEADER that contains a copy of the file record +* being written to. Can be NULL, in which case the file record will be read from disk. +* If not-null, WriteAttribute() will skip reading from disk, and FileRecord +* will be updated with the newly-written attribute before the function returns. +* +* @return +* STATUS_SUCCESS if successful, an error code otherwise. STATUS_NOT_IMPLEMENTED if +* writing to a sparse file. +* +* @remarks Note that in this context the word "attribute" isn't referring read-only, hidden, +* etc. - the file's data is actually stored in an attribute in NTFS parlance. +* +*/ + +NTSTATUS +WriteAttribute(PDEVICE_EXTENSION Vcb, + PNTFS_ATTR_CONTEXT Context, + ULONGLONG Offset, + const PUCHAR Buffer, + ULONG Length, + PULONG RealLengthWritten, + PFILE_RECORD_HEADER FileRecord) +{ + ULONGLONG LastLCN; + PUCHAR DataRun; + LONGLONG DataRunOffset; + ULONGLONG DataRunLength; + LONGLONG DataRunStartLCN; + ULONGLONG CurrentOffset; + ULONG WriteLength; + NTSTATUS Status; + PUCHAR SourceBuffer = Buffer; + LONGLONG StartingOffset; + BOOLEAN FileRecordAllocated = FALSE; + + //TEMPTEMP + PUCHAR TempBuffer; + + + DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p, %p)\n", Vcb, Context, Offset, Buffer, Length, RealLengthWritten, FileRecord); + + *RealLengthWritten = 0; + + // is this a resident attribute? + if (!Context->pRecord->IsNonResident) + { + ULONG AttributeOffset; + PNTFS_ATTR_CONTEXT FoundContext; + PNTFS_ATTR_RECORD Destination; + + // Ensure requested data is within the bounds of the attribute + ASSERT(Offset + Length <= Context->pRecord->Resident.ValueLength); + + if (Offset + Length > Context->pRecord->Resident.ValueLength) + { + DPRINT1("DRIVER ERROR: Attribute is too small!\n"); + return STATUS_INVALID_PARAMETER; + } + + // Do we need to read the file record? + if (FileRecord == NULL) + { + FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS); + if (!FileRecord) + { + DPRINT1("Error: Couldn't allocate file record!\n"); + return STATUS_NO_MEMORY; + } + + FileRecordAllocated = TRUE; + + // read the file record + ReadFileRecord(Vcb, Context->FileMFTIndex, FileRecord); + } + + // find where to write the attribute data to + Status = FindAttribute(Vcb, FileRecord, + Context->pRecord->Type, + (PCWSTR)((ULONG_PTR)Context->pRecord + Context->pRecord->NameOffset), + Context->pRecord->NameLength, + &FoundContext, + &AttributeOffset); + + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Couldn't find matching attribute!\n"); + if(FileRecordAllocated) + ExFreePoolWithTag(FileRecord, TAG_NTFS); + return Status; + } + + Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttributeOffset); + + DPRINT("Offset: %I64u, AttributeOffset: %u, ValueOffset: %u\n", Offset, AttributeOffset, Context->pRecord->Resident.ValueLength); + + // Will we be writing past the end of the allocated file record? + if (Offset + Length + AttributeOffset + Context->pRecord->Resident.ValueOffset > Vcb->NtfsInfo.BytesPerFileRecord) + { + DPRINT1("DRIVER ERROR: Data being written extends past end of file record!\n"); + ReleaseAttributeContext(FoundContext); + if (FileRecordAllocated) + ExFreePoolWithTag(FileRecord, TAG_NTFS); + return STATUS_INVALID_PARAMETER; + } + + // copy the data being written into the file record. We cast Offset to ULONG, which is safe because it's range has been verified. + RtlCopyMemory((PCHAR)((ULONG_PTR)Destination + Context->pRecord->Resident.ValueOffset + (ULONG)Offset), Buffer, Length); + + Status = UpdateFileRecord(Vcb, Context->FileMFTIndex, FileRecord); + + // Update the context's copy of the resident attribute + ASSERT(Context->pRecord->Length == Destination->Length); + RtlCopyMemory((PVOID)Context->pRecord, Destination, Context->pRecord->Length); + + ReleaseAttributeContext(FoundContext); + if (FileRecordAllocated) + ExFreePoolWithTag(FileRecord, TAG_NTFS); + + if (NT_SUCCESS(Status)) + *RealLengthWritten = Length; + + return Status; + } + + // This is a non-resident attribute. + + // I. Find the corresponding start data run. + + // FIXME: Cache seems to be non-working. Disable it for now + //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize) + /*if (0) + { + DataRun = Context->CacheRun; + LastLCN = Context->CacheRunLastLCN; + DataRunStartLCN = Context->CacheRunStartLCN; + DataRunLength = Context->CacheRunLength; + CurrentOffset = Context->CacheRunCurrentOffset; + } + else*/ + { + ULONG UsedBufferSize; + LastLCN = 0; + CurrentOffset = 0; + + // This will be rewritten in the next iteration to just use the DataRuns MCB directly + TempBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS); + + ConvertLargeMCBToDataRuns(&Context->DataRunsMCB, + TempBuffer, + Vcb->NtfsInfo.BytesPerFileRecord, + &UsedBufferSize); + + DataRun = TempBuffer; + + while (1) + { + DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength); + if (DataRunOffset != -1) + { + // Normal data run. + // DPRINT1("Writing to normal data run, LastLCN %I64u DataRunOffset %I64d\n", LastLCN, DataRunOffset); + DataRunStartLCN = LastLCN + DataRunOffset; + LastLCN = DataRunStartLCN; + } + else + { + // Sparse data run. We can't support writing to sparse files yet + // (it may require increasing the allocation size). + DataRunStartLCN = -1; + DPRINT1("FIXME: Writing to sparse files is not supported yet!\n"); + return STATUS_NOT_IMPLEMENTED; + } + + // Have we reached the data run we're trying to write to? + if (Offset >= CurrentOffset && + Offset < CurrentOffset + (DataRunLength * Vcb->NtfsInfo.BytesPerCluster)) + { + break; + } + + if (*DataRun == 0) + { + // We reached the last assigned cluster + // TODO: assign new clusters to the end of the file. + // (Presently, this code will rarely be reached, the write will usually have already failed by now) + // [We can reach here by creating a new file record when the MFT isn't large enough] + DPRINT1("FIXME: Master File Table needs to be enlarged.\n"); + return STATUS_END_OF_FILE; + } + + CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster; + } + } + + // II. Go through the run list and write the data + + /* REVIEWME -- As adapted from NtfsReadAttribute(): + We seem to be making a special case for the first applicable data run, but I'm not sure why. + Does it have something to do with (not) caching? Is this strategy equally applicable to writing? */ + + WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset), Length); + + StartingOffset = DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset; + + // Write the data to the disk + Status = NtfsWriteDisk(Vcb->StorageDevice, + StartingOffset, + WriteLength, + Vcb->NtfsInfo.BytesPerSector, + (PVOID)SourceBuffer); + + // Did the write fail? + if (!NT_SUCCESS(Status)) + { + Context->CacheRun = DataRun; + Context->CacheRunOffset = Offset; + Context->CacheRunStartLCN = DataRunStartLCN; + Context->CacheRunLength = DataRunLength; + Context->CacheRunLastLCN = LastLCN; + Context->CacheRunCurrentOffset = CurrentOffset; + + return Status; + } + + Length -= WriteLength; + SourceBuffer += WriteLength; + *RealLengthWritten += WriteLength; + + // Did we write to the end of the data run? + if (WriteLength == DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset)) + { + // Advance to the next data run + CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster; + DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength); + + if (DataRunOffset != (ULONGLONG)-1) + { + DataRunStartLCN = LastLCN + DataRunOffset; + LastLCN = DataRunStartLCN; + } + else + DataRunStartLCN = -1; + } + + // Do we have more data to write? + while (Length > 0) + { + // Make sure we don't write past the end of the current data run + WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster, Length); + + // Are we dealing with a sparse data run? + if (DataRunStartLCN == -1) + { + DPRINT1("FIXME: Don't know how to write to sparse files yet! (DataRunStartLCN == -1)\n"); + return STATUS_NOT_IMPLEMENTED; + } + else + { + // write the data to the disk + Status = NtfsWriteDisk(Vcb->StorageDevice, + DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster, + WriteLength, + Vcb->NtfsInfo.BytesPerSector, + (PVOID)SourceBuffer); + if (!NT_SUCCESS(Status)) + break; + } + + Length -= WriteLength; + SourceBuffer += WriteLength; + *RealLengthWritten += WriteLength; + + // We finished this request, but there's still data in this data run. + if (Length == 0 && WriteLength != DataRunLength * Vcb->NtfsInfo.BytesPerCluster) + break; + + // Go to next run in the list. + + if (*DataRun == 0) + { + // that was the last run + if (Length > 0) + { + // Failed sanity check. + DPRINT1("Encountered EOF before expected!\n"); + return STATUS_END_OF_FILE; + } + + break; + } + + // Advance to the next data run + CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster; + DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength); + if (DataRunOffset != -1) + { + // Normal data run. + DataRunStartLCN = LastLCN + DataRunOffset; + LastLCN = DataRunStartLCN; + } + else + { + // Sparse data run. + DataRunStartLCN = -1; + } + } // end while (Length > 0) [more data to write] + + // TEMPTEMP + if (Context->pRecord->IsNonResident) + ExFreePoolWithTag(TempBuffer, TAG_NTFS); + + Context->CacheRun = DataRun; + Context->CacheRunOffset = Offset + *RealLengthWritten; + Context->CacheRunStartLCN = DataRunStartLCN; + Context->CacheRunLength = DataRunLength; + Context->CacheRunLastLCN = LastLCN; + Context->CacheRunCurrentOffset = CurrentOffset; + + return Status; +} + NTSTATUS ReadFileRecord(PDEVICE_EXTENSION Vcb, ULONGLONG index, @@ -346,16 +1567,330 @@ ReadFileRecord(PDEVICE_EXTENSION Vcb, BytesRead = ReadAttribute(Vcb, Vcb->MFTContext, index * Vcb->NtfsInfo.BytesPerFileRecord, (PCHAR)file, Vcb->NtfsInfo.BytesPerFileRecord); if (BytesRead != Vcb->NtfsInfo.BytesPerFileRecord) { - DPRINT1("ReadFileRecord failed: %I64u read, %u expected\n", BytesRead, Vcb->NtfsInfo.BytesPerFileRecord); + DPRINT1("ReadFileRecord failed: %I64u read, %lu expected\n", BytesRead, Vcb->NtfsInfo.BytesPerFileRecord); return STATUS_PARTIAL_COPY; } /* Apply update sequence array fixups. */ + DPRINT("Sequence number: %u\n", file->SequenceNumber); return FixupUpdateSequenceArray(Vcb, &file->Ntfs); } -NTSTATUS +/** +* Searches a file's parent directory (given the parent's index in the mft) +* for the given file. Upon finding an index entry for that file, updates +* Data Size and Allocated Size values in the $FILE_NAME attribute of that entry. +* +* (Most of this code was copied from NtfsFindMftRecord) +*/ +NTSTATUS +UpdateFileNameRecord(PDEVICE_EXTENSION Vcb, + ULONGLONG ParentMFTIndex, + PUNICODE_STRING FileName, + BOOLEAN DirSearch, + ULONGLONG NewDataSize, + ULONGLONG NewAllocationSize, + BOOLEAN CaseSensitive) +{ + PFILE_RECORD_HEADER MftRecord; + PNTFS_ATTR_CONTEXT IndexRootCtx; + PINDEX_ROOT_ATTRIBUTE IndexRoot; + PCHAR IndexRecord; + PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd; + NTSTATUS Status; + ULONG CurrentEntry = 0; + + DPRINT("UpdateFileNameRecord(%p, %I64d, %wZ, %s, %I64u, %I64u, %s)\n", + Vcb, + ParentMFTIndex, + FileName, + DirSearch ? "TRUE" : "FALSE", + NewDataSize, + NewAllocationSize, + CaseSensitive ? "TRUE" : "FALSE"); + + MftRecord = ExAllocatePoolWithTag(NonPagedPool, + Vcb->NtfsInfo.BytesPerFileRecord, + TAG_NTFS); + if (MftRecord == NULL) + { + return STATUS_INSUFFICIENT_RESOURCES; + } + + Status = ReadFileRecord(Vcb, ParentMFTIndex, MftRecord); + if (!NT_SUCCESS(Status)) + { + ExFreePoolWithTag(MftRecord, TAG_NTFS); + return Status; + } + + ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE); + Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL); + if (!NT_SUCCESS(Status)) + { + ExFreePoolWithTag(MftRecord, TAG_NTFS); + return Status; + } + + IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS); + if (IndexRecord == NULL) + { + ReleaseAttributeContext(IndexRootCtx); + ExFreePoolWithTag(MftRecord, TAG_NTFS); + return STATUS_INSUFFICIENT_RESOURCES; + } + + Status = ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, AttributeDataLength(IndexRootCtx->pRecord)); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to read Index Root!\n"); + ExFreePoolWithTag(IndexRecord, TAG_NTFS); + ReleaseAttributeContext(IndexRootCtx); + ExFreePoolWithTag(MftRecord, TAG_NTFS); + } + + IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord; + IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset); + // Index root is always resident. + IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries); + + DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry); + + Status = UpdateIndexEntryFileNameSize(Vcb, + MftRecord, + IndexRecord, + IndexRoot->SizeOfEntry, + IndexEntry, + IndexEntryEnd, + FileName, + &CurrentEntry, + &CurrentEntry, + DirSearch, + NewDataSize, + NewAllocationSize, + CaseSensitive); + + if (Status == STATUS_PENDING) + { + // we need to write the index root attribute back to disk + ULONG LengthWritten; + Status = WriteAttribute(Vcb, IndexRootCtx, 0, (PUCHAR)IndexRecord, AttributeDataLength(IndexRootCtx->pRecord), &LengthWritten, MftRecord); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Couldn't update Index Root!\n"); + } + + } + + ReleaseAttributeContext(IndexRootCtx); + ExFreePoolWithTag(IndexRecord, TAG_NTFS); + ExFreePoolWithTag(MftRecord, TAG_NTFS); + + return Status; +} + +/** +* Recursively searches directory index and applies the size update to the $FILE_NAME attribute of the +* proper index entry. +* (Heavily based on BrowseIndexEntries) +*/ +NTSTATUS +UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb, + PFILE_RECORD_HEADER MftRecord, + PCHAR IndexRecord, + ULONG IndexBlockSize, + PINDEX_ENTRY_ATTRIBUTE FirstEntry, + PINDEX_ENTRY_ATTRIBUTE LastEntry, + PUNICODE_STRING FileName, + PULONG StartEntry, + PULONG CurrentEntry, + BOOLEAN DirSearch, + ULONGLONG NewDataSize, + ULONGLONG NewAllocatedSize, + BOOLEAN CaseSensitive) +{ + NTSTATUS Status; + ULONG RecordOffset; + PINDEX_ENTRY_ATTRIBUTE IndexEntry; + PNTFS_ATTR_CONTEXT IndexAllocationCtx; + ULONGLONG IndexAllocationSize; + PINDEX_BUFFER IndexBuffer; + + DPRINT("UpdateIndexEntrySize(%p, %p, %p, %lu, %p, %p, %wZ, %lu, %lu, %s, %I64u, %I64u, %s)\n", + Vcb, + MftRecord, + IndexRecord, + IndexBlockSize, + FirstEntry, + LastEntry, + FileName, + *StartEntry, + *CurrentEntry, + DirSearch ? "TRUE" : "FALSE", + NewDataSize, + NewAllocatedSize, + CaseSensitive ? "TRUE" : "FALSE"); + + // find the index entry responsible for the file we're trying to update + IndexEntry = FirstEntry; + while (IndexEntry < LastEntry && + !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END)) + { + if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) > NTFS_FILE_FIRST_USER_FILE && + *CurrentEntry >= *StartEntry && + IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS && + CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive)) + { + *StartEntry = *CurrentEntry; + IndexEntry->FileName.DataSize = NewDataSize; + IndexEntry->FileName.AllocatedSize = NewAllocatedSize; + // indicate that the caller will still need to write the structure to the disk + return STATUS_PENDING; + } + + (*CurrentEntry) += 1; + ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE)); + IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length); + } + + /* If we're already browsing a subnode */ + if (IndexRecord == NULL) + { + return STATUS_OBJECT_PATH_NOT_FOUND; + } + + /* If there's no subnode */ + if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)) + { + return STATUS_OBJECT_PATH_NOT_FOUND; + } + + Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL); + if (!NT_SUCCESS(Status)) + { + DPRINT("Corrupted filesystem!\n"); + return Status; + } + + IndexAllocationSize = AttributeDataLength(IndexAllocationCtx->pRecord); + Status = STATUS_OBJECT_PATH_NOT_FOUND; + for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize) + { + ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize); + Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs); + if (!NT_SUCCESS(Status)) + { + break; + } + + IndexBuffer = (PINDEX_BUFFER)IndexRecord; + ASSERT(IndexBuffer->Ntfs.Type == NRH_INDX_TYPE); + ASSERT(IndexBuffer->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize); + FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.FirstEntryOffset); + LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.TotalSizeOfEntries); + ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexBuffer + IndexBlockSize)); + + Status = UpdateIndexEntryFileNameSize(NULL, + NULL, + NULL, + 0, + FirstEntry, + LastEntry, + FileName, + StartEntry, + CurrentEntry, + DirSearch, + NewDataSize, + NewAllocatedSize, + CaseSensitive); + if (Status == STATUS_PENDING) + { + // write the index record back to disk + ULONG Written; + + // first we need to update the fixup values for the index block + Status = AddFixupArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs); + if (!NT_SUCCESS(Status)) + { + DPRINT1("Error: Failed to update fixup sequence array!\n"); + break; + } + + Status = WriteAttribute(Vcb, IndexAllocationCtx, RecordOffset, (const PUCHAR)IndexRecord, IndexBlockSize, &Written, MftRecord); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR Performing write!\n"); + break; + } + + Status = STATUS_SUCCESS; + break; + } + if (NT_SUCCESS(Status)) + { + break; + } + } + + ReleaseAttributeContext(IndexAllocationCtx); + return Status; +} + +/** +* @name UpdateFileRecord +* @implemented +* +* Writes a file record to the master file table, at a given index. +* +* @param Vcb +* Pointer to the DEVICE_EXTENSION of the target drive being written to. +* +* @param MftIndex +* Target index in the master file table to store the file record. +* +* @param FileRecord +* Pointer to the complete file record which will be written to the master file table. +* +* @return +* STATUS_SUCCESSFUL on success. An error passed from WriteAttribute() otherwise. +* +*/ +NTSTATUS +UpdateFileRecord(PDEVICE_EXTENSION Vcb, + ULONGLONG MftIndex, + PFILE_RECORD_HEADER FileRecord) +{ + ULONG BytesWritten; + NTSTATUS Status = STATUS_SUCCESS; + + DPRINT("UpdateFileRecord(%p, 0x%I64x, %p)\n", Vcb, MftIndex, FileRecord); + + // Add the fixup array to prepare the data for writing to disk + AddFixupArray(Vcb, &FileRecord->Ntfs); + + // write the file record to the master file table + Status = WriteAttribute(Vcb, + Vcb->MFTContext, + MftIndex * Vcb->NtfsInfo.BytesPerFileRecord, + (const PUCHAR)FileRecord, + Vcb->NtfsInfo.BytesPerFileRecord, + &BytesWritten, + FileRecord); + + if (!NT_SUCCESS(Status)) + { + DPRINT1("UpdateFileRecord failed: %lu written, %lu expected\n", BytesWritten, Vcb->NtfsInfo.BytesPerFileRecord); + } + + // remove the fixup array (so the file record pointer can still be used) + FixupUpdateSequenceArray(Vcb, &FileRecord->Ntfs); + + return Status; +} + + +NTSTATUS FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb, PNTFS_RECORD_HEADER Record) { @@ -369,6 +1904,8 @@ FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb, USACount = Record->UsaCount - 1; /* Exclude the USA Number. */ Block = (USHORT*)((PCHAR)Record + Vcb->NtfsInfo.BytesPerSector - 2); + DPRINT("FixupUpdateSequenceArray(%p, %p)\nUSANumber: %u\tUSACount: %u\n", Vcb, Record, USANumber, USACount); + while (USACount) { if (*Block != USANumber) @@ -384,6 +1921,642 @@ FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb, return STATUS_SUCCESS; } +/** +* @name AddNewMftEntry +* @implemented +* +* Adds a file record to the master file table of a given device. +* +* @param FileRecord +* Pointer to a complete file record which will be saved to disk. +* +* @param DeviceExt +* Pointer to the DEVICE_EXTENSION of the target drive. +* +* @param DestinationIndex +* Pointer to a ULONGLONG which will receive the MFT index where the file record was stored. +* +* @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_OBJECT_NAME_NOT_FOUND if we can't find the MFT's $Bitmap or if we weren't able +* to read the attribute. +* STATUS_INSUFFICIENT_RESOURCES if we can't allocate enough memory for a copy of $Bitmap. +* STATUS_CANT_WAIT if CanWait was FALSE and the function could not get immediate, exclusive access to the MFT. +*/ +NTSTATUS +AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, + PDEVICE_EXTENSION DeviceExt, + PULONGLONG DestinationIndex, + BOOLEAN CanWait) +{ + NTSTATUS Status = STATUS_SUCCESS; + ULONGLONG MftIndex; + RTL_BITMAP Bitmap; + ULONGLONG BitmapDataSize; + ULONGLONG AttrBytesRead; + PUCHAR BitmapData; + PUCHAR BitmapBuffer; + ULONG LengthWritten; + PNTFS_ATTR_CONTEXT BitmapContext; + LARGE_INTEGER BitmapBits; + UCHAR SystemReservedBits; + + 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)) + { + DPRINT1("ERROR: Couldn't find $Bitmap attribute of master file table!\n"); + return Status; + } + + // Get size of bitmap + BitmapDataSize = AttributeDataLength(BitmapContext->pRecord); + + // 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; + } + RtlZeroMemory(BitmapBuffer, BitmapDataSize + sizeof(ULONG)); + + // 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 != BitmapDataSize) + { + DPRINT1("ERROR: Unable to read $Bitmap attribute of master file table!\n"); + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + return STATUS_OBJECT_NAME_NOT_FOUND; + } + + // We need to backup the bits for records 0x10 - 0x17 (3rd byte of bitmap) and mark these records + // as in-use so we don't assign files to those indices. They're reserved for the system (e.g. ChkDsk). + SystemReservedBits = BitmapData[2]; + BitmapData[2] = 0xff; + + // Calculate bit count + BitmapBits.QuadPart = AttributeDataLength(DeviceExt->MFTContext->pRecord) / + DeviceExt->NtfsInfo.BytesPerFileRecord; + if (BitmapBits.HighPart != 0) + { + DPRINT1("\tFIXME: bitmap sizes beyond 32bits are not yet supported! (Your NTFS volume is too large)\n"); + NtfsGlobalData->EnableWriteSupport = FALSE; + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + return STATUS_NOT_IMPLEMENTED; + } + + // convert buffer into bitmap + RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, BitmapBits.LowPart); + + // set next available bit, preferrably after 23rd bit + MftIndex = RtlFindClearBitsAndSet(&Bitmap, 1, 24); + if ((LONG)MftIndex == -1) + { + DPRINT1("Couldn't find free space in MFT for file record, increasing MFT size.\n"); + + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + + // Couldn't find a free record in the MFT, add some blank records and try again + Status = IncreaseMftSize(DeviceExt, CanWait); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Couldn't find space in MFT for file or increase MFT size!\n"); + return Status; + } + + return AddNewMftEntry(FileRecord, DeviceExt, DestinationIndex, CanWait); + } + + DPRINT1("Creating file record at MFT index: %I64u\n", MftIndex); + + // update file record with index + FileRecord->MFTRecordNumber = MftIndex; + + // [BitmapData should have been updated via RtlFindClearBitsAndSet()] + + // Restore the system reserved bits + BitmapData[2] = SystemReservedBits; + + // write the bitmap back to the MFT's $Bitmap attribute + Status = WriteAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize, &LengthWritten, FileRecord); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR encountered when writing $Bitmap attribute!\n"); + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + return Status; + } + + // update the file record (write it to disk) + Status = UpdateFileRecord(DeviceExt, MftIndex, FileRecord); + + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Unable to write file record!\n"); + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + return Status; + } + + *DestinationIndex = MftIndex; + + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + + return Status; +} + +/** +* @name NtfsAddFilenameToDirectory +* @implemented +* +* Adds a $FILE_NAME attribute to a given directory index. +* +* @param DeviceExt +* Points to the target disk's DEVICE_EXTENSION. +* +* @param DirectoryMftIndex +* Mft index of the parent directory which will receive the file. +* +* @param FileReferenceNumber +* File reference of the file to be added to the directory. This is a combination of the +* Mft index and sequence number. +* +* @param FilenameAttribute +* Pointer to the FILENAME_ATTRIBUTE of the file being added to the directory. +* +* @param CaseSensitive +* Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE +* if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag. +* +* @return +* STATUS_SUCCESS on success. +* STATUS_INSUFFICIENT_RESOURCES if an allocation fails. +* STATUS_NOT_IMPLEMENTED if target address isn't at the end of the given file record. +* +* @remarks +* WIP - Can only support a few files in a directory. +* One FILENAME_ATTRIBUTE is added to the directory's index for each link to that file. So, each +* file which contains one FILENAME_ATTRIBUTE for a long name and another for the 8.3 name, will +* get both attributes added to its parent directory. +*/ +NTSTATUS +NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, + ULONGLONG DirectoryMftIndex, + ULONGLONG FileReferenceNumber, + PFILENAME_ATTRIBUTE FilenameAttribute, + BOOLEAN CaseSensitive) +{ + NTSTATUS Status = STATUS_SUCCESS; + PFILE_RECORD_HEADER ParentFileRecord; + PNTFS_ATTR_CONTEXT IndexRootContext; + PINDEX_ROOT_ATTRIBUTE I30IndexRoot; + ULONG IndexRootOffset; + ULONGLONG I30IndexRootLength; + ULONG LengthWritten; + PINDEX_ROOT_ATTRIBUTE NewIndexRoot; + ULONG AttributeLength; + PNTFS_ATTR_RECORD NextAttribute; + PB_TREE NewTree; + ULONG BtreeIndexLength; + ULONG MaxIndexRootSize; + PB_TREE_KEY NewLeftKey; + PB_TREE_FILENAME_NODE NewRightHandNode; + LARGE_INTEGER MinIndexRootSize; + ULONG NewMaxIndexRootSize; + ULONG NodeSize; + + // Allocate memory for the parent directory + ParentFileRecord = ExAllocatePoolWithTag(NonPagedPool, + DeviceExt->NtfsInfo.BytesPerFileRecord, + TAG_NTFS); + if (!ParentFileRecord) + { + DPRINT1("ERROR: Couldn't allocate memory for file record!\n"); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Open the parent directory + Status = ReadFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord); + if (!NT_SUCCESS(Status)) + { + ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); + DPRINT1("ERROR: Couldn't read parent directory with index %I64u\n", + DirectoryMftIndex); + return Status; + } + +#ifndef NDEBUG + DPRINT1("Dumping old parent file record:\n"); + NtfsDumpFileRecord(DeviceExt, ParentFileRecord); +#endif + + // Find the index root attribute for the directory + Status = FindAttribute(DeviceExt, + ParentFileRecord, + AttributeIndexRoot, + L"$I30", + 4, + &IndexRootContext, + &IndexRootOffset); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Couldn't find $I30 $INDEX_ROOT attribute for parent directory with MFT #: %I64u!\n", + DirectoryMftIndex); + ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); + return Status; + } + + // Find the maximum index size given what the file record can hold + // First, find the max index size assuming index root is the last attribute + MaxIndexRootSize = DeviceExt->NtfsInfo.BytesPerFileRecord // Start with the size of a file record + - IndexRootOffset // Subtract the length of everything that comes before index root + - IndexRootContext->pRecord->Resident.ValueOffset // Subtract the length of the attribute header for index root + - sizeof(INDEX_ROOT_ATTRIBUTE) // Subtract the length of the index root header + - (sizeof(ULONG) * 2); // Subtract the length of the file record end marker and padding + + // Are there attributes after this one? + NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset + IndexRootContext->pRecord->Length); + if (NextAttribute->Type != AttributeEnd) + { + // Find the length of all attributes after this one, not counting the end marker + ULONG LengthOfAttributes = 0; + PNTFS_ATTR_RECORD CurrentAttribute = NextAttribute; + while (CurrentAttribute->Type != AttributeEnd) + { + LengthOfAttributes += CurrentAttribute->Length; + CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length); + } + + // Leave room for the existing attributes + MaxIndexRootSize -= LengthOfAttributes; + } + + // Allocate memory for the index root data + I30IndexRootLength = AttributeDataLength(IndexRootContext->pRecord); + I30IndexRoot = ExAllocatePoolWithTag(NonPagedPool, I30IndexRootLength, TAG_NTFS); + if (!I30IndexRoot) + { + DPRINT1("ERROR: Couldn't allocate memory for index root attribute!\n"); + ReleaseAttributeContext(IndexRootContext); + ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Read the Index Root + Status = ReadAttribute(DeviceExt, IndexRootContext, 0, (PCHAR)I30IndexRoot, I30IndexRootLength); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Couln't read index root attribute for Mft index #%I64u\n", DirectoryMftIndex); + ReleaseAttributeContext(IndexRootContext); + ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); + ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); + return Status; + } + + // Convert the index to a B*Tree + Status = CreateBTreeFromIndex(DeviceExt, + ParentFileRecord, + IndexRootContext, + I30IndexRoot, + &NewTree); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to create B-Tree from Index!\n"); + ReleaseAttributeContext(IndexRootContext); + ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); + ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); + return Status; + } + +#ifndef NDEBUG + DumpBTree(NewTree); +#endif + + // Insert the key for the file we're adding + Status = NtfsInsertKey(NewTree, + FileReferenceNumber, + FilenameAttribute, + NewTree->RootNode, + CaseSensitive, + MaxIndexRootSize, + I30IndexRoot->SizeOfEntry, + &NewLeftKey, + &NewRightHandNode); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to insert key into B-Tree!\n"); + DestroyBTree(NewTree); + ReleaseAttributeContext(IndexRootContext); + ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); + ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); + return Status; + } + +#ifndef NDEBUG + DumpBTree(NewTree); +#endif + + // The root node can't be split + ASSERT(NewLeftKey == NULL); + ASSERT(NewRightHandNode == NULL); + + // Convert B*Tree back to Index + + // Updating the index allocation can change the size available for the index root, + // And if the index root is demoted, the index allocation will need to be updated again, + // which may change the size available for index root... etc. + // My solution is to decrease index root to the size it would be if it was demoted, + // then UpdateIndexAllocation will have an accurate representation of the maximum space + // it can use in the file record. There's still a chance that the act of allocating an + // index node after demoting the index root will increase the size of the file record beyond + // it's limit, but if that happens, an attribute-list will most definitely be needed. + // This a bit hacky, but it seems to be functional. + + // Calculate the minimum size of the index root attribute, considering one dummy key and one VCN + MinIndexRootSize.QuadPart = sizeof(INDEX_ROOT_ATTRIBUTE) // size of the index root headers + + 0x18; // Size of dummy key with a VCN for a subnode + ASSERT(MinIndexRootSize.QuadPart % ATTR_RECORD_ALIGNMENT == 0); + + // Temporarily shrink the index root to it's minimal size + AttributeLength = MinIndexRootSize.LowPart; + AttributeLength += sizeof(INDEX_ROOT_ATTRIBUTE); + + + // FIXME: IndexRoot will probably be invalid until we're finished. If we fail before we finish, the directory will probably be toast. + // The potential for catastrophic data-loss exists!!! :) + + // Update the length of the attribute in the file record of the parent directory + Status = InternalSetResidentAttributeLength(DeviceExt, + IndexRootContext, + ParentFileRecord, + IndexRootOffset, + AttributeLength); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Unable to set length of index root!\n"); + DestroyBTree(NewTree); + ReleaseAttributeContext(IndexRootContext); + ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); + ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); + return Status; + } + + // Update the index allocation + Status = UpdateIndexAllocation(DeviceExt, NewTree, I30IndexRoot->SizeOfEntry, ParentFileRecord); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to update index allocation from B-Tree!\n"); + DestroyBTree(NewTree); + ReleaseAttributeContext(IndexRootContext); + ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); + ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); + return Status; + } + +#ifndef NDEBUG + DPRINT1("Index Allocation updated\n"); + DumpBTree(NewTree); +#endif + + // Find the maximum index root size given what the file record can hold + // First, find the max index size assuming index root is the last attribute + NewMaxIndexRootSize = + DeviceExt->NtfsInfo.BytesPerFileRecord // Start with the size of a file record + - IndexRootOffset // Subtract the length of everything that comes before index root + - IndexRootContext->pRecord->Resident.ValueOffset // Subtract the length of the attribute header for index root + - sizeof(INDEX_ROOT_ATTRIBUTE) // Subtract the length of the index root header + - (sizeof(ULONG) * 2); // Subtract the length of the file record end marker and padding + + // Are there attributes after this one? + NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset + IndexRootContext->pRecord->Length); + if (NextAttribute->Type != AttributeEnd) + { + // Find the length of all attributes after this one, not counting the end marker + ULONG LengthOfAttributes = 0; + PNTFS_ATTR_RECORD CurrentAttribute = NextAttribute; + while (CurrentAttribute->Type != AttributeEnd) + { + LengthOfAttributes += CurrentAttribute->Length; + CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length); + } + + // Leave room for the existing attributes + NewMaxIndexRootSize -= LengthOfAttributes; + } + + // The index allocation and index bitmap may have grown, leaving less room for the index root, + // so now we need to double-check that index root isn't too large + NodeSize = GetSizeOfIndexEntries(NewTree->RootNode); + if (NodeSize > NewMaxIndexRootSize) + { + DPRINT1("Demoting index root.\nNodeSize: 0x%lx\nNewMaxIndexRootSize: 0x%lx\n", NodeSize, NewMaxIndexRootSize); + + Status = DemoteBTreeRoot(NewTree); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to demote index root!\n"); + DestroyBTree(NewTree); + ReleaseAttributeContext(IndexRootContext); + ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); + ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); + return Status; + } + + // We need to update the index allocation once more + Status = UpdateIndexAllocation(DeviceExt, NewTree, I30IndexRoot->SizeOfEntry, ParentFileRecord); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to update index allocation from B-Tree!\n"); + DestroyBTree(NewTree); + ReleaseAttributeContext(IndexRootContext); + ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); + ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); + return Status; + } + + // re-recalculate max size of index root + NewMaxIndexRootSize = + // Find the maximum index size given what the file record can hold + // First, find the max index size assuming index root is the last attribute + DeviceExt->NtfsInfo.BytesPerFileRecord // Start with the size of a file record + - IndexRootOffset // Subtract the length of everything that comes before index root + - IndexRootContext->pRecord->Resident.ValueOffset // Subtract the length of the attribute header for index root + - sizeof(INDEX_ROOT_ATTRIBUTE) // Subtract the length of the index root header + - (sizeof(ULONG) * 2); // Subtract the length of the file record end marker and padding + + // Are there attributes after this one? + NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset + IndexRootContext->pRecord->Length); + if (NextAttribute->Type != AttributeEnd) + { + // Find the length of all attributes after this one, not counting the end marker + ULONG LengthOfAttributes = 0; + PNTFS_ATTR_RECORD CurrentAttribute = NextAttribute; + while (CurrentAttribute->Type != AttributeEnd) + { + LengthOfAttributes += CurrentAttribute->Length; + CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length); + } + + // Leave room for the existing attributes + NewMaxIndexRootSize -= LengthOfAttributes; + } + + + } + + // Create the Index Root from the B*Tree + Status = CreateIndexRootFromBTree(DeviceExt, NewTree, NewMaxIndexRootSize, &NewIndexRoot, &BtreeIndexLength); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to create Index root from B-Tree!\n"); + DestroyBTree(NewTree); + ReleaseAttributeContext(IndexRootContext); + ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); + ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); + return Status; + } + + // We're done with the B-Tree now + DestroyBTree(NewTree); + + // Write back the new index root attribute to the parent directory file record + + // First, we need to resize the attribute. + // CreateIndexRootFromBTree() should have verified that the index root fits within MaxIndexSize. + // We can't set the size as we normally would, because $INDEX_ROOT must always be resident. + AttributeLength = NewIndexRoot->Header.AllocatedSize + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header); + + if (AttributeLength != IndexRootContext->pRecord->Resident.ValueLength) + { + // Update the length of the attribute in the file record of the parent directory + Status = InternalSetResidentAttributeLength(DeviceExt, + IndexRootContext, + ParentFileRecord, + IndexRootOffset, + AttributeLength); + if (!NT_SUCCESS(Status)) + { + ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); + ReleaseAttributeContext(IndexRootContext); + ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); + ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); + DPRINT1("ERROR: Unable to set resident attribute length!\n"); + return Status; + } + + } + + NT_ASSERT(ParentFileRecord->BytesInUse <= DeviceExt->NtfsInfo.BytesPerFileRecord); + + Status = UpdateFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to update file record of directory with index: %llx\n", DirectoryMftIndex); + ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); + ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); + ReleaseAttributeContext(IndexRootContext); + ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); + return Status; + } + + // Write the new index root to disk + Status = WriteAttribute(DeviceExt, + IndexRootContext, + 0, + (PUCHAR)NewIndexRoot, + AttributeLength, + &LengthWritten, + ParentFileRecord); + if (!NT_SUCCESS(Status) || LengthWritten != AttributeLength) + { + DPRINT1("ERROR: Unable to write new index root attribute to parent directory!\n"); + ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); + ReleaseAttributeContext(IndexRootContext); + ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); + ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); + return Status; + } + + // re-read the parent file record, so we can dump it + Status = ReadFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Couldn't read parent directory after messing with it!\n"); + } + else + { +#ifndef NDEBUG + DPRINT1("Dumping new B-Tree:\n"); + + Status = CreateBTreeFromIndex(DeviceExt, ParentFileRecord, IndexRootContext, NewIndexRoot, &NewTree); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Couldn't re-create b-tree\n"); + return Status; + } + + DumpBTree(NewTree); + + DestroyBTree(NewTree); + + NtfsDumpFileRecord(DeviceExt, ParentFileRecord); +#endif + } + + // Cleanup + ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); + ReleaseAttributeContext(IndexRootContext); + ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); + ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); + + return Status; +} + +NTSTATUS +AddFixupArray(PDEVICE_EXTENSION Vcb, + PNTFS_RECORD_HEADER Record) +{ + USHORT *pShortToFixUp; + ULONG ArrayEntryCount = Record->UsaCount - 1; + ULONG Offset = Vcb->NtfsInfo.BytesPerSector - 2; + ULONG i; + + PFIXUP_ARRAY fixupArray = (PFIXUP_ARRAY)((UCHAR*)Record + Record->UsaOffset); + + DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb, Record, fixupArray->USN, ArrayEntryCount); + + fixupArray->USN++; + + for (i = 0; i < ArrayEntryCount; i++) + { + DPRINT("USN: %u\tOffset: %u\n", fixupArray->USN, Offset); + + pShortToFixUp = (USHORT*)((PCHAR)Record + Offset); + fixupArray->Array[i] = *pShortToFixUp; + *pShortToFixUp = fixupArray->USN; + Offset += Vcb->NtfsInfo.BytesPerSector; + } + + return STATUS_SUCCESS; +} NTSTATUS ReadLCN(PDEVICE_EXTENSION Vcb, @@ -407,7 +2580,8 @@ ReadLCN(PDEVICE_EXTENSION Vcb, BOOLEAN CompareFileName(PUNICODE_STRING FileName, PINDEX_ENTRY_ATTRIBUTE IndexEntry, - BOOLEAN DirSearch) + BOOLEAN DirSearch, + BOOLEAN CaseSensitive) { BOOLEAN Ret, Alloc = FALSE; UNICODE_STRING EntryName; @@ -419,7 +2593,7 @@ CompareFileName(PUNICODE_STRING FileName, if (DirSearch) { UNICODE_STRING IntFileName; - if (IndexEntry->FileName.NameType != NTFS_FILE_NAME_POSIX) + if (!CaseSensitive) { NT_VERIFY(NT_SUCCESS(RtlUpcaseUnicodeString(&IntFileName, FileName, TRUE))); Alloc = TRUE; @@ -429,7 +2603,7 @@ CompareFileName(PUNICODE_STRING FileName, IntFileName = *FileName; } - Ret = FsRtlIsNameInExpression(&IntFileName, &EntryName, (IndexEntry->FileName.NameType != NTFS_FILE_NAME_POSIX), NULL); + Ret = FsRtlIsNameInExpression(&IntFileName, &EntryName, !CaseSensitive, NULL); if (Alloc) { @@ -440,10 +2614,133 @@ CompareFileName(PUNICODE_STRING FileName, } else { - return (RtlCompareUnicodeString(FileName, &EntryName, (IndexEntry->FileName.NameType != NTFS_FILE_NAME_POSIX)) == 0); + return (RtlCompareUnicodeString(FileName, &EntryName, !CaseSensitive) == 0); } } +/** +* @name UpdateMftMirror +* @implemented +* +* Backs-up the first ~4 master file table entries to the $MFTMirr file. +* +* @param Vcb +* Pointer to an NTFS_VCB for the volume whose Mft mirror is being updated. +* +* @return + +* STATUS_SUCCESS on success. +* STATUS_INSUFFICIENT_RESOURCES if an allocation failed. +* STATUS_UNSUCCESSFUL if we couldn't read the master file table. +* +* @remarks +* NTFS maintains up-to-date copies of the first several mft entries in the $MFTMirr file. Usually, the first 4 file +* records from the mft are stored. The exact number of entries is determined by the size of $MFTMirr's $DATA. +* If $MFTMirr is not up-to-date, chkdsk will reject every change it can find prior to when $MFTMirr was last updated. +* Therefore, it's recommended to call this function if the volume changes considerably. For instance, IncreaseMftSize() +* relies on this function to keep chkdsk from deleting the mft entries it creates. Note that under most instances, creating +* or deleting a file will not affect the first ~four mft entries, and so will not require updating the mft mirror. +*/ +NTSTATUS +UpdateMftMirror(PNTFS_VCB Vcb) +{ + PFILE_RECORD_HEADER MirrorFileRecord; + PNTFS_ATTR_CONTEXT MirrDataContext; + PNTFS_ATTR_CONTEXT MftDataContext; + PCHAR DataBuffer; + ULONGLONG DataLength; + NTSTATUS Status; + ULONG BytesRead; + ULONG LengthWritten; + + // Allocate memory for the Mft mirror file record + MirrorFileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS); + if (!MirrorFileRecord) + { + DPRINT1("Error: Failed to allocate memory for $MFTMirr!\n"); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Read the Mft Mirror file record + Status = ReadFileRecord(Vcb, NTFS_FILE_MFTMIRR, MirrorFileRecord); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to read $MFTMirr!\n"); + ExFreePoolWithTag(MirrorFileRecord, TAG_NTFS); + return Status; + } + + // Find the $DATA attribute of $MFTMirr + Status = FindAttribute(Vcb, MirrorFileRecord, AttributeData, L"", 0, &MirrDataContext, NULL); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Couldn't find $DATA attribute!\n"); + ExFreePoolWithTag(MirrorFileRecord, TAG_NTFS); + return Status; + } + + // Find the $DATA attribute of $MFT + Status = FindAttribute(Vcb, Vcb->MasterFileTable, AttributeData, L"", 0, &MftDataContext, NULL); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Couldn't find $DATA attribute!\n"); + ReleaseAttributeContext(MirrDataContext); + ExFreePoolWithTag(MirrorFileRecord, TAG_NTFS); + return Status; + } + + // Get the size of the mirror's $DATA attribute + DataLength = AttributeDataLength(MirrDataContext->pRecord); + + ASSERT(DataLength % Vcb->NtfsInfo.BytesPerFileRecord == 0); + + // Create buffer for the mirror's $DATA attribute + DataBuffer = ExAllocatePoolWithTag(NonPagedPool, DataLength, TAG_NTFS); + if (!DataBuffer) + { + DPRINT1("Error: Couldn't allocate memory for $DATA buffer!\n"); + ReleaseAttributeContext(MftDataContext); + ReleaseAttributeContext(MirrDataContext); + ExFreePoolWithTag(MirrorFileRecord, TAG_NTFS); + return STATUS_INSUFFICIENT_RESOURCES; + } + + ASSERT(DataLength < ULONG_MAX); + + // Back up the first several entries of the Mft's $DATA Attribute + BytesRead = ReadAttribute(Vcb, MftDataContext, 0, DataBuffer, (ULONG)DataLength); + if (BytesRead != (ULONG)DataLength) + { + DPRINT1("Error: Failed to read $DATA for $MFTMirr!\n"); + ReleaseAttributeContext(MftDataContext); + ReleaseAttributeContext(MirrDataContext); + ExFreePoolWithTag(DataBuffer, TAG_NTFS); + ExFreePoolWithTag(MirrorFileRecord, TAG_NTFS); + return STATUS_UNSUCCESSFUL; + } + + // Write the mirror's $DATA attribute + Status = WriteAttribute(Vcb, + MirrDataContext, + 0, + (PUCHAR)DataBuffer, + DataLength, + &LengthWritten, + MirrorFileRecord); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to write $DATA attribute of $MFTMirr!\n"); + } + + // Cleanup + ReleaseAttributeContext(MftDataContext); + ReleaseAttributeContext(MirrDataContext); + ExFreePoolWithTag(DataBuffer, TAG_NTFS); + ExFreePoolWithTag(MirrorFileRecord, TAG_NTFS); + + return Status; +} + #if 0 static VOID @@ -469,10 +2766,158 @@ DumpIndexEntry(PINDEX_ENTRY_ATTRIBUTE IndexEntry) } #endif +NTSTATUS +BrowseSubNodeIndexEntries(PNTFS_VCB Vcb, + PFILE_RECORD_HEADER MftRecord, + ULONG IndexBlockSize, + PUNICODE_STRING FileName, + PNTFS_ATTR_CONTEXT IndexAllocationContext, + PRTL_BITMAP Bitmap, + ULONGLONG VCN, + PULONG StartEntry, + PULONG CurrentEntry, + BOOLEAN DirSearch, + BOOLEAN CaseSensitive, + ULONGLONG *OutMFTIndex) +{ + PINDEX_BUFFER IndexRecord; + ULONGLONG Offset; + ULONG BytesRead; + PINDEX_ENTRY_ATTRIBUTE FirstEntry; + PINDEX_ENTRY_ATTRIBUTE LastEntry; + PINDEX_ENTRY_ATTRIBUTE IndexEntry; + ULONG NodeNumber; + NTSTATUS Status; + + DPRINT("BrowseSubNodeIndexEntries(%p, %p, %lu, %wZ, %p, %p, %I64d, %lu, %lu, %s, %s, %p)\n", + Vcb, + MftRecord, + IndexBlockSize, + FileName, + IndexAllocationContext, + Bitmap, + VCN, + *StartEntry, + *CurrentEntry, + "FALSE", + DirSearch ? "TRUE" : "FALSE", + CaseSensitive ? "TRUE" : "FALSE", + OutMFTIndex); + + // Calculate node number as VCN / Clusters per index record + NodeNumber = VCN / (Vcb->NtfsInfo.BytesPerIndexRecord / Vcb->NtfsInfo.BytesPerCluster); + + // Is the bit for this node clear in the bitmap? + if (!RtlCheckBit(Bitmap, NodeNumber)) + { + DPRINT1("File system corruption detected, node with VCN %I64u is being reused or is marked as deleted.\n", VCN); + return STATUS_DATA_ERROR; + } + + // Clear the bit for this node so it can't be recursively referenced + RtlClearBits(Bitmap, NodeNumber, 1); + + // Allocate memory for the index record + IndexRecord = ExAllocatePoolWithTag(NonPagedPool, IndexBlockSize, TAG_NTFS); + if (!IndexRecord) + { + DPRINT1("Unable to allocate memory for index record!\n"); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Calculate offset of index record + Offset = VCN * Vcb->NtfsInfo.BytesPerCluster; + + // Read the index record + BytesRead = ReadAttribute(Vcb, IndexAllocationContext, Offset, (PCHAR)IndexRecord, IndexBlockSize); + if (BytesRead != IndexBlockSize) + { + DPRINT1("Unable to read index record!\n"); + ExFreePoolWithTag(IndexRecord, TAG_NTFS); + return STATUS_UNSUCCESSFUL; + } + + // Assert that we're dealing with an index record here + ASSERT(IndexRecord->Ntfs.Type == NRH_INDX_TYPE); + + // Apply the fixup array to the index record + Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs); + if (!NT_SUCCESS(Status)) + { + ExFreePoolWithTag(IndexRecord, TAG_NTFS); + DPRINT1("Failed to apply fixup array!\n"); + return Status; + } + + ASSERT(IndexRecord->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize); + FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexRecord->Header + IndexRecord->Header.FirstEntryOffset); + LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexRecord->Header + IndexRecord->Header.TotalSizeOfEntries); + ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexRecord + IndexBlockSize)); + + // Loop through all Index Entries of index, starting with FirstEntry + IndexEntry = FirstEntry; + while (IndexEntry <= LastEntry) + { + // Does IndexEntry have a sub-node? + if (IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) + { + if (!(IndexRecord->Header.Flags & INDEX_NODE_LARGE) || !IndexAllocationContext) + { + DPRINT1("Filesystem corruption detected!\n"); + } + else + { + Status = BrowseSubNodeIndexEntries(Vcb, + MftRecord, + IndexBlockSize, + FileName, + IndexAllocationContext, + Bitmap, + GetIndexEntryVCN(IndexEntry), + StartEntry, + CurrentEntry, + DirSearch, + CaseSensitive, + OutMFTIndex); + if (NT_SUCCESS(Status)) + { + ExFreePoolWithTag(IndexRecord, TAG_NTFS); + return Status; + } + } + } + + // Are we done? + if (IndexEntry->Flags & NTFS_INDEX_ENTRY_END) + break; + + // If we've found a file whose index is greater than or equal to StartEntry that matches the search criteria + if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) >= NTFS_FILE_FIRST_USER_FILE && + *CurrentEntry >= *StartEntry && + IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS && + CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive)) + { + *StartEntry = *CurrentEntry; + *OutMFTIndex = (IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK); + ExFreePoolWithTag(IndexRecord, TAG_NTFS); + return STATUS_SUCCESS; + } + + // Advance to the next index entry + (*CurrentEntry) += 1; + ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE)); + IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length); + } + + ExFreePoolWithTag(IndexRecord, TAG_NTFS); + + return STATUS_OBJECT_PATH_NOT_FOUND; +} + NTSTATUS BrowseIndexEntries(PDEVICE_EXTENSION Vcb, PFILE_RECORD_HEADER MftRecord, - PCHAR IndexRecord, + PINDEX_ROOT_ATTRIBUTE IndexRecord, ULONG IndexBlockSize, PINDEX_ENTRY_ATTRIBUTE FirstEntry, PINDEX_ENTRY_ATTRIBUTE LastEntry, @@ -480,82 +2925,155 @@ BrowseIndexEntries(PDEVICE_EXTENSION Vcb, PULONG StartEntry, PULONG CurrentEntry, BOOLEAN DirSearch, + BOOLEAN CaseSensitive, ULONGLONG *OutMFTIndex) { NTSTATUS Status; - ULONG RecordOffset; PINDEX_ENTRY_ATTRIBUTE IndexEntry; - PNTFS_ATTR_CONTEXT IndexAllocationCtx; - ULONGLONG IndexAllocationSize; - PINDEX_BUFFER IndexBuffer; + PNTFS_ATTR_CONTEXT IndexAllocationContext; + PNTFS_ATTR_CONTEXT BitmapContext; + PCHAR *BitmapMem; + ULONG *BitmapPtr; + RTL_BITMAP Bitmap; - DPRINT("BrowseIndexEntries(%p, %p, %p, %u, %p, %p, %wZ, %u, %u, %u, %p)\n", Vcb, MftRecord, IndexRecord, IndexBlockSize, FirstEntry, LastEntry, FileName, *StartEntry, *CurrentEntry, DirSearch, OutMFTIndex); + DPRINT("BrowseIndexEntries(%p, %p, %p, %lu, %p, %p, %wZ, %lu, %lu, %s, %s, %p)\n", + Vcb, + MftRecord, + IndexRecord, + IndexBlockSize, + FirstEntry, + LastEntry, + FileName, + *StartEntry, + *CurrentEntry, + DirSearch ? "TRUE" : "FALSE", + CaseSensitive ? "TRUE" : "FALSE", + OutMFTIndex); - IndexEntry = FirstEntry; - while (IndexEntry < LastEntry && - !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END)) + // Find the $I30 index allocation, if there is one + Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationContext, NULL); + if (NT_SUCCESS(Status)) { - if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) > 0x10 && + ULONGLONG BitmapLength; + // Find the bitmap attribute for the index + Status = FindAttribute(Vcb, MftRecord, AttributeBitmap, L"$I30", 4, &BitmapContext, NULL); + if (!NT_SUCCESS(Status)) + { + DPRINT1("Potential file system corruption detected!\n"); + ReleaseAttributeContext(IndexAllocationContext); + return Status; + } + + // Get the length of the bitmap attribute + BitmapLength = AttributeDataLength(BitmapContext->pRecord); + + // 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, BitmapLength + sizeof(ULONG), TAG_NTFS); + if (!BitmapMem) + { + DPRINT1("Error: failed to allocate bitmap!"); + ReleaseAttributeContext(BitmapContext); + ReleaseAttributeContext(IndexAllocationContext); + return STATUS_INSUFFICIENT_RESOURCES; + } + + RtlZeroMemory(BitmapMem, BitmapLength + sizeof(ULONG)); + + // RtlInitializeBitmap() wants a pointer that's ULONG-aligned. + BitmapPtr = (PULONG)ALIGN_UP_BY((ULONG_PTR)BitmapMem, sizeof(ULONG)); + + // Read the existing bitmap data + Status = ReadAttribute(Vcb, BitmapContext, 0, (PCHAR)BitmapPtr, BitmapLength); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to read bitmap attribute!\n"); + ExFreePoolWithTag(BitmapMem, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + ReleaseAttributeContext(IndexAllocationContext); + return Status; + } + + // Initialize bitmap + RtlInitializeBitMap(&Bitmap, BitmapPtr, BitmapLength * 8); + } + else + { + // Couldn't find an index allocation + IndexAllocationContext = NULL; + } + + + // Loop through all Index Entries of index, starting with FirstEntry + IndexEntry = FirstEntry; + while (IndexEntry <= LastEntry) + { + // Does IndexEntry have a sub-node? + if (IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) + { + if (!(IndexRecord->Header.Flags & INDEX_ROOT_LARGE) || !IndexAllocationContext) + { + DPRINT1("Filesystem corruption detected!\n"); + } + else + { + Status = BrowseSubNodeIndexEntries(Vcb, + MftRecord, + IndexBlockSize, + FileName, + IndexAllocationContext, + &Bitmap, + GetIndexEntryVCN(IndexEntry), + StartEntry, + CurrentEntry, + DirSearch, + CaseSensitive, + OutMFTIndex); + if (NT_SUCCESS(Status)) + { + ExFreePoolWithTag(BitmapMem, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + ReleaseAttributeContext(IndexAllocationContext); + return Status; + } + } + } + + // Are we done? + if (IndexEntry->Flags & NTFS_INDEX_ENTRY_END) + break; + + // If we've found a file whose index is greater than or equal to StartEntry that matches the search criteria + if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) >= NTFS_FILE_FIRST_USER_FILE && *CurrentEntry >= *StartEntry && IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS && - CompareFileName(FileName, IndexEntry, DirSearch)) + CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive)) { *StartEntry = *CurrentEntry; *OutMFTIndex = (IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK); + if (IndexAllocationContext) + { + ExFreePoolWithTag(BitmapMem, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + ReleaseAttributeContext(IndexAllocationContext); + } return STATUS_SUCCESS; } + // Advance to the next index entry (*CurrentEntry) += 1; ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE)); IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length); } - /* If we're already browsing a subnode */ - if (IndexRecord == NULL) + if (IndexAllocationContext) { - return STATUS_OBJECT_PATH_NOT_FOUND; + ExFreePoolWithTag(BitmapMem, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + ReleaseAttributeContext(IndexAllocationContext); } - /* If there's no subnode */ - if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)) - { - return STATUS_OBJECT_PATH_NOT_FOUND; - } - - Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx); - if (!NT_SUCCESS(Status)) - { - DPRINT("Corrupted filesystem!\n"); - return Status; - } - - IndexAllocationSize = AttributeDataLength(&IndexAllocationCtx->Record); - Status = STATUS_OBJECT_PATH_NOT_FOUND; - for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize) - { - ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize); - Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs); - if (!NT_SUCCESS(Status)) - { - break; - } - - IndexBuffer = (PINDEX_BUFFER)IndexRecord; - ASSERT(IndexBuffer->Ntfs.Type == NRH_INDX_TYPE); - ASSERT(IndexBuffer->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize); - FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.FirstEntryOffset); - LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.TotalSizeOfEntries); - ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexBuffer + IndexBlockSize)); - - Status = BrowseIndexEntries(NULL, NULL, NULL, 0, FirstEntry, LastEntry, FileName, StartEntry, CurrentEntry, DirSearch, OutMFTIndex); - if (NT_SUCCESS(Status)) - { - break; - } - } - - ReleaseAttributeContext(IndexAllocationCtx); - return Status; + return STATUS_OBJECT_PATH_NOT_FOUND; } NTSTATUS @@ -564,6 +3082,7 @@ NtfsFindMftRecord(PDEVICE_EXTENSION Vcb, PUNICODE_STRING FileName, PULONG FirstEntry, BOOLEAN DirSearch, + BOOLEAN CaseSensitive, ULONGLONG *OutMFTIndex) { PFILE_RECORD_HEADER MftRecord; @@ -574,7 +3093,14 @@ NtfsFindMftRecord(PDEVICE_EXTENSION Vcb, NTSTATUS Status; ULONG CurrentEntry = 0; - DPRINT("NtfsFindMftRecord(%p, %I64d, %wZ, %u, %u, %p)\n", Vcb, MFTIndex, FileName, *FirstEntry, DirSearch, OutMFTIndex); + DPRINT("NtfsFindMftRecord(%p, %I64d, %wZ, %lu, %s, %s, %p)\n", + Vcb, + MFTIndex, + FileName, + *FirstEntry, + DirSearch ? "TRUE" : "FALSE", + CaseSensitive ? "TRUE" : "FALSE", + OutMFTIndex); MftRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, @@ -592,7 +3118,7 @@ NtfsFindMftRecord(PDEVICE_EXTENSION Vcb, } ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE); - Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx); + Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL); if (!NT_SUCCESS(Status)) { ExFreePoolWithTag(MftRecord, TAG_NTFS); @@ -616,7 +3142,18 @@ NtfsFindMftRecord(PDEVICE_EXTENSION Vcb, DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry); - Status = BrowseIndexEntries(Vcb, MftRecord, IndexRecord, IndexRoot->SizeOfEntry, IndexEntry, IndexEntryEnd, FileName, FirstEntry, &CurrentEntry, DirSearch, OutMFTIndex); + Status = BrowseIndexEntries(Vcb, + MftRecord, + (PINDEX_ROOT_ATTRIBUTE)IndexRecord, + IndexRoot->SizeOfEntry, + IndexEntry, + IndexEntryEnd, + FileName, + FirstEntry, + &CurrentEntry, + DirSearch, + CaseSensitive, + OutMFTIndex); ExFreePoolWithTag(IndexRecord, TAG_NTFS); ExFreePoolWithTag(MftRecord, TAG_NTFS); @@ -627,6 +3164,7 @@ NtfsFindMftRecord(PDEVICE_EXTENSION Vcb, NTSTATUS NtfsLookupFileAt(PDEVICE_EXTENSION Vcb, PUNICODE_STRING PathName, + BOOLEAN CaseSensitive, PFILE_RECORD_HEADER *FileRecord, PULONGLONG MFTIndex, ULONGLONG CurrentMFTIndex) @@ -635,7 +3173,13 @@ NtfsLookupFileAt(PDEVICE_EXTENSION Vcb, NTSTATUS Status; ULONG FirstEntry = 0; - DPRINT("NtfsLookupFileAt(%p, %wZ, %p, %I64x)\n", Vcb, PathName, FileRecord, CurrentMFTIndex); + DPRINT("NtfsLookupFileAt(%p, %wZ, %s, %p, %p, %I64x)\n", + Vcb, + PathName, + CaseSensitive ? "TRUE" : "FALSE", + FileRecord, + MFTIndex, + CurrentMFTIndex); FsRtlDissectName(*PathName, &Current, &Remaining); @@ -643,7 +3187,7 @@ NtfsLookupFileAt(PDEVICE_EXTENSION Vcb, { DPRINT("Current: %wZ\n", &Current); - Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, &Current, &FirstEntry, FALSE, &CurrentMFTIndex); + Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, &Current, &FirstEntry, FALSE, CaseSensitive, &CurrentMFTIndex); if (!NT_SUCCESS(Status)) { return Status; @@ -678,10 +3222,69 @@ NtfsLookupFileAt(PDEVICE_EXTENSION Vcb, NTSTATUS NtfsLookupFile(PDEVICE_EXTENSION Vcb, PUNICODE_STRING PathName, + BOOLEAN CaseSensitive, PFILE_RECORD_HEADER *FileRecord, PULONGLONG MFTIndex) { - return NtfsLookupFileAt(Vcb, PathName, FileRecord, MFTIndex, NTFS_FILE_ROOT); + return NtfsLookupFileAt(Vcb, PathName, CaseSensitive, FileRecord, MFTIndex, NTFS_FILE_ROOT); +} + +void +NtfsDumpData(ULONG_PTR Buffer, ULONG Length) +{ + ULONG i, j; + + // dump binary data, 8 bytes at a time + for (i = 0; i < Length; i += 8) + { + // display current offset, in hex + DbgPrint("\t%03x\t", i); + + // display hex value of each of the next 8 bytes + for (j = 0; j < 8; j++) + DbgPrint("%02x ", *(PUCHAR)(Buffer + i + j)); + DbgPrint("\n"); + } +} + +/** +* @name NtfsDumpFileRecord +* @implemented +* +* Provides diagnostic information about a file record. Prints a hex dump +* of the entire record (based on the size reported by FileRecord->ByesInUse), +* then prints a dump of each attribute. +* +* @param Vcb +* Pointer to a DEVICE_EXTENSION describing the volume. +* +* @param FileRecord +* Pointer to the file record to be analyzed. +* +* @remarks +* FileRecord must be a complete file record at least FileRecord->BytesAllocated +* in size, and not just the header. +* +*/ +VOID +NtfsDumpFileRecord(PDEVICE_EXTENSION Vcb, + PFILE_RECORD_HEADER FileRecord) +{ + ULONG i, j; + + // dump binary data, 8 bytes at a time + for (i = 0; i < FileRecord->BytesInUse; i += 8) + { + // display current offset, in hex + DbgPrint("\t%03x\t", i); + + // display hex value of each of the next 8 bytes + for (j = 0; j < 8; j++) + DbgPrint("%02x ", *(PUCHAR)((ULONG_PTR)FileRecord + i + j)); + DbgPrint("\n"); + } + + NtfsDumpFileAttributes(Vcb, FileRecord); } NTSTATUS @@ -690,13 +3293,21 @@ NtfsFindFileAt(PDEVICE_EXTENSION Vcb, PULONG FirstEntry, PFILE_RECORD_HEADER *FileRecord, PULONGLONG MFTIndex, - ULONGLONG CurrentMFTIndex) + ULONGLONG CurrentMFTIndex, + BOOLEAN CaseSensitive) { NTSTATUS Status; - DPRINT("NtfsFindFileAt(%p, %wZ, %u, %p, %p, %I64x)\n", Vcb, SearchPattern, *FirstEntry, FileRecord, MFTIndex, CurrentMFTIndex); + DPRINT("NtfsFindFileAt(%p, %wZ, %lu, %p, %p, %I64x, %s)\n", + Vcb, + SearchPattern, + *FirstEntry, + FileRecord, + MFTIndex, + CurrentMFTIndex, + (CaseSensitive ? "TRUE" : "FALSE")); - Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, SearchPattern, FirstEntry, TRUE, &CurrentMFTIndex); + Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, SearchPattern, FirstEntry, TRUE, CaseSensitive, &CurrentMFTIndex); if (!NT_SUCCESS(Status)) { DPRINT("NtfsFindFileAt: NtfsFindMftRecord() failed with status 0x%08lx\n", Status); diff --git a/drivers/filesystems/ntfs/misc.c b/drivers/filesystems/ntfs/misc.c index ba461b04a80..76b77134c9d 100644 --- a/drivers/filesystems/ntfs/misc.c +++ b/drivers/filesystems/ntfs/misc.c @@ -130,4 +130,62 @@ NtfsGetUserBuffer(PIRP Irp, } } +/** +* @name NtfsLockUserBuffer +* @implemented +* +* Ensures the IRP has an MDL Address. +* +* @param Irp +* Irp with the UserBuffer that needs locking +* +* @param Length +* Size of the Irp->UserBuffer, in bytes +* +* @param Operation +* What kind of access does the driver need to the buffer. Set to +* IoReadAccess, IoWriteAccess, or IoModifyAccess. +* +* @return +* STATUS_SUCCESS in case of success, STATUS_INSUFFICIENT_RESOURCES +* or an exception code otherwise. +* +* @remarks Trevor Thompson shamelessly ripped this from +* VfatLockUserBuffer(). Only the name was changed. +* +*/ +NTSTATUS +NtfsLockUserBuffer(IN PIRP Irp, + IN ULONG Length, + IN LOCK_OPERATION Operation) +{ + ASSERT(Irp); + + if (Irp->MdlAddress) + { + return STATUS_SUCCESS; + } + + IoAllocateMdl(Irp->UserBuffer, Length, FALSE, FALSE, Irp); + + if (!Irp->MdlAddress) + { + return STATUS_INSUFFICIENT_RESOURCES; + } + + _SEH2_TRY + { + MmProbeAndLockPages(Irp->MdlAddress, Irp->RequestorMode, Operation); + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + IoFreeMdl(Irp->MdlAddress); + Irp->MdlAddress = NULL; + _SEH2_YIELD(return _SEH2_GetExceptionCode()); + } + _SEH2_END; + + return STATUS_SUCCESS; +} + /* EOF */ diff --git a/drivers/filesystems/ntfs/ntfs.c b/drivers/filesystems/ntfs/ntfs.c index 94ba4f8f6a9..c9bbc490bda 100644 --- a/drivers/filesystems/ntfs/ntfs.c +++ b/drivers/filesystems/ntfs/ntfs.c @@ -58,6 +58,8 @@ DriverEntry(PDRIVER_OBJECT DriverObject, UNICODE_STRING DeviceName = RTL_CONSTANT_STRING(DEVICE_NAME); NTSTATUS Status; PDEVICE_OBJECT DeviceObject; + OBJECT_ATTRIBUTES Attributes; + HANDLE DriverKey = NULL; TRACE_(NTFS, "DriverEntry(%p, '%wZ')\n", DriverObject, RegistryPath); @@ -84,6 +86,42 @@ DriverEntry(PDRIVER_OBJECT DriverObject, ExInitializeResourceLite(&NtfsGlobalData->Resource); + NtfsGlobalData->EnableWriteSupport = FALSE; + + // Read registry to determine if write support should be enabled + InitializeObjectAttributes(&Attributes, + RegistryPath, + OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, + NULL, + NULL); + + Status = ZwOpenKey(&DriverKey, KEY_READ, &Attributes); + if (NT_SUCCESS(Status)) + { + UNICODE_STRING ValueName; + UCHAR Buffer[sizeof(KEY_VALUE_PARTIAL_INFORMATION) + sizeof(ULONG)]; + PKEY_VALUE_PARTIAL_INFORMATION Value = (PKEY_VALUE_PARTIAL_INFORMATION)Buffer; + ULONG ValueLength = sizeof(Buffer); + ULONG ResultLength; + + RtlInitUnicodeString(&ValueName, L"MyDataDoesNotMatterSoEnableExperimentalWriteSupportForEveryNTFSVolume"); + + Status = ZwQueryValueKey(DriverKey, + &ValueName, + KeyValuePartialInformation, + Value, + ValueLength, + &ResultLength); + + if (NT_SUCCESS(Status) && Value->Data[0] == TRUE) + { + DPRINT1("\tEnabling write support on ALL NTFS volumes!\n"); + NtfsGlobalData->EnableWriteSupport = TRUE; + } + + ZwClose(DriverKey); + } + /* Keep trace of Driver Object */ NtfsGlobalData->DriverObject = DriverObject; @@ -118,7 +156,7 @@ DriverEntry(PDRIVER_OBJECT DriverObject, IoRegisterFileSystem(NtfsGlobalData->DeviceObject); ObReferenceObject(NtfsGlobalData->DeviceObject); - return Status; + return STATUS_SUCCESS; } @@ -139,6 +177,7 @@ NtfsInitializeFunctionPointers(PDRIVER_OBJECT DriverObject) DriverObject->MajorFunction[IRP_MJ_READ] = NtfsFsdDispatch; DriverObject->MajorFunction[IRP_MJ_WRITE] = NtfsFsdDispatch; DriverObject->MajorFunction[IRP_MJ_QUERY_INFORMATION] = NtfsFsdDispatch; + DriverObject->MajorFunction[IRP_MJ_SET_INFORMATION] = NtfsFsdDispatch; DriverObject->MajorFunction[IRP_MJ_QUERY_VOLUME_INFORMATION] = NtfsFsdDispatch; DriverObject->MajorFunction[IRP_MJ_SET_VOLUME_INFORMATION] = NtfsFsdDispatch; DriverObject->MajorFunction[IRP_MJ_DIRECTORY_CONTROL] = NtfsFsdDispatch; diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index 9e987c923f7..97f4acd1d63 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -116,6 +116,7 @@ typedef struct NTFS_INFO NtfsInfo; + ULONG MftDataOffset; ULONG Flags; ULONG OpenHandleCount; @@ -151,6 +152,7 @@ typedef struct FAST_IO_DISPATCH FastIoDispatch; NPAGED_LOOKASIDE_LIST IrpContextLookasideList; NPAGED_LOOKASIDE_LIST FcbLookasideList; + BOOLEAN EnableWriteSupport; } NTFS_GLOBAL_DATA, *PNTFS_GLOBAL_DATA; @@ -175,6 +177,10 @@ typedef enum AttributeEnd = 0xFFFFFFFF } ATTRIBUTE_TYPE, *PATTRIBUTE_TYPE; +// FILE_RECORD_END seems to follow AttributeEnd in every file record starting with $Quota. +// No clue what data is being represented here. +#define FILE_RECORD_END 0x11477982 + #define NTFS_FILE_MFT 0 #define NTFS_FILE_MFTMIRR 1 #define NTFS_FILE_LOGFILE 2 @@ -187,6 +193,7 @@ typedef enum #define NTFS_FILE_QUOTA 9 #define NTFS_FILE_UPCASE 10 #define NTFS_FILE_EXTEND 11 +#define NTFS_FILE_FIRST_USER_FILE 16 #define NTFS_MFT_MASK 0x0000FFFFFFFFFFFFULL @@ -201,6 +208,9 @@ typedef enum #define INDEX_ROOT_SMALL 0x0 #define INDEX_ROOT_LARGE 0x1 +#define INDEX_NODE_SMALL 0x0 +#define INDEX_NODE_LARGE 0x1 + #define NTFS_INDEX_ENTRY_NODE 1 #define NTFS_INDEX_ENTRY_END 2 @@ -217,6 +227,9 @@ typedef enum #define NTFS_FILE_TYPE_COMPRESSED 0x800 #define NTFS_FILE_TYPE_DIRECTORY 0x10000000 +/* Indexed Flag in Resident attributes - still somewhat speculative */ +#define RA_INDEXED 0x01 + typedef struct { ULONG Type; /* Magic number 'FILE' */ @@ -287,6 +300,16 @@ typedef struct }; } NTFS_ATTR_RECORD, *PNTFS_ATTR_RECORD; +// The beginning and length of an attribute record are always aligned to an 8-byte boundary, +// 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; @@ -389,6 +412,34 @@ typedef struct FILENAME_ATTRIBUTE FileName; } INDEX_ENTRY_ATTRIBUTE, *PINDEX_ENTRY_ATTRIBUTE; +struct _B_TREE_FILENAME_NODE; +typedef struct _B_TREE_FILENAME_NODE B_TREE_FILENAME_NODE; + +// Keys are arranged in nodes as an ordered, linked list +typedef struct _B_TREE_KEY +{ + struct _B_TREE_KEY *NextKey; + B_TREE_FILENAME_NODE *LesserChild; // Child-Node. All the keys in this node will be sorted before IndexEntry + PINDEX_ENTRY_ATTRIBUTE IndexEntry; // must be last member for FIELD_OFFSET +}B_TREE_KEY, *PB_TREE_KEY; + +// Every Node is just an ordered list of keys. +// Sub-nodes can be found attached to a key (if they exist). +// A key's sub-node precedes that key in the ordered list. +typedef struct _B_TREE_FILENAME_NODE +{ + ULONG KeyCount; + BOOLEAN HasValidVCN; + BOOLEAN DiskNeedsUpdating; + ULONGLONG VCN; + PB_TREE_KEY FirstKey; +} B_TREE_FILENAME_NODE, *PB_TREE_FILENAME_NODE; + +typedef struct +{ + PB_TREE_FILENAME_NODE RootNode; +} B_TREE, *PB_TREE; + typedef struct { ULONGLONG Unknown1; @@ -433,7 +484,9 @@ typedef struct _NTFS_ATTR_CONTEXT ULONGLONG CacheRunLength; LONGLONG CacheRunLastLCN; ULONGLONG CacheRunCurrentOffset; - NTFS_ATTR_RECORD Record; + LARGE_MCB DataRunsMCB; + ULONGLONG FileMFTIndex; + PNTFS_ATTR_RECORD pRecord; } NTFS_ATTR_CONTEXT, *PNTFS_ATTR_CONTEXT; #define FCB_CACHE_INITIALIZED 0x0001 @@ -483,8 +536,15 @@ typedef struct _FIND_ATTR_CONTXT PNTFS_ATTR_RECORD LastAttr; PNTFS_ATTR_RECORD NonResidentStart; PNTFS_ATTR_RECORD NonResidentEnd; + ULONG Offset; } FIND_ATTR_CONTXT, *PFIND_ATTR_CONTXT; +typedef struct +{ + USHORT USN; + USHORT Array[]; +} FIXUP_ARRAY, *PFIXUP_ARRAY; + extern PNTFS_GLOBAL_DATA NtfsGlobalData; FORCEINLINE @@ -504,11 +564,75 @@ NtfsMarkIrpContextForQueue(PNTFS_IRP_CONTEXT IrpContext) //VOID //NtfsDumpAttribute(PATTRIBUTE Attribute); +NTSTATUS +AddBitmap(PNTFS_VCB Vcb, + PFILE_RECORD_HEADER FileRecord, + PNTFS_ATTR_RECORD AttributeAddress, + PCWSTR Name, + USHORT NameLength); + +NTSTATUS +AddData(PFILE_RECORD_HEADER FileRecord, + PNTFS_ATTR_RECORD AttributeAddress); + +NTSTATUS +AddRun(PNTFS_VCB Vcb, + PNTFS_ATTR_CONTEXT AttrContext, + ULONG AttrOffset, + PFILE_RECORD_HEADER FileRecord, + ULONGLONG NextAssignedCluster, + ULONG RunLength); + +NTSTATUS +AddIndexAllocation(PNTFS_VCB Vcb, + PFILE_RECORD_HEADER FileRecord, + PNTFS_ATTR_RECORD AttributeAddress, + PCWSTR Name, + USHORT NameLength); + +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, + PDEVICE_EXTENSION DeviceExt, + PFILE_OBJECT FileObject, + BOOLEAN CaseSensitive, + PULONGLONG ParentMftIndex); + +NTSTATUS +AddStandardInformation(PFILE_RECORD_HEADER FileRecord, + PNTFS_ATTR_RECORD AttributeAddress); + +NTSTATUS +ConvertDataRunsToLargeMCB(PUCHAR DataRun, + PLARGE_MCB DataRunsMCB, + PULONGLONG pNextVBN); + +NTSTATUS +ConvertLargeMCBToDataRuns(PLARGE_MCB DataRunsMCB, + PUCHAR RunBuffer, + ULONG MaxBufferSize, + PULONG UsedBufferSize); + PUCHAR DecodeRun(PUCHAR DataRun, LONGLONG *DataRunOffset, ULONGLONG *DataRunLength); +ULONG GetFileNameAttributeLength(PFILENAME_ATTRIBUTE FileNameAttribute); + +VOID +NtfsDumpDataRuns(PVOID StartOfRun, + ULONGLONG CurrentLCN); + VOID NtfsDumpFileAttributes(PDEVICE_EXTENSION Vcb, PFILE_RECORD_HEADER FileRecord); @@ -522,6 +646,15 @@ GetFileNameFromRecord(PDEVICE_EXTENSION Vcb, PFILE_RECORD_HEADER FileRecord, UCHAR NameType); +UCHAR +GetPackedByteCount(LONGLONG NumberToPack, + BOOLEAN IsSigned); + +NTSTATUS +GetLastClusterInDataRun(PDEVICE_EXTENSION Vcb, + PNTFS_ATTR_RECORD Attribute, + PULONGLONG LastCluster); + PFILENAME_ATTRIBUTE GetBestFileNameFromRecord(PDEVICE_EXTENSION Vcb, PFILE_RECORD_HEADER FileRecord); @@ -540,6 +673,13 @@ FindNextAttribute(PFIND_ATTR_CONTXT Context, VOID FindCloseAttribute(PFIND_ATTR_CONTXT Context); +NTSTATUS +FreeClusters(PNTFS_VCB Vcb, + PNTFS_ATTR_CONTEXT AttrContext, + ULONG AttrOffset, + PFILE_RECORD_HEADER FileRecord, + ULONG ClustersToFree); + /* blockdev.c */ NTSTATUS @@ -550,6 +690,13 @@ NtfsReadDisk(IN PDEVICE_OBJECT DeviceObject, IN OUT PUCHAR Buffer, IN BOOLEAN Override); +NTSTATUS +NtfsWriteDisk(IN PDEVICE_OBJECT DeviceObject, + IN LONGLONG StartingOffset, + IN ULONG Length, + IN ULONG SectorSize, + IN const PUCHAR Buffer); + NTSTATUS NtfsReadSectors(IN PDEVICE_OBJECT DeviceObject, IN ULONG DiskSector, @@ -568,6 +715,98 @@ NtfsDeviceIoControl(IN PDEVICE_OBJECT DeviceObject, IN BOOLEAN Override); +/* btree.c */ + +LONG +CompareTreeKeys(PB_TREE_KEY Key1, + PB_TREE_KEY Key2, + BOOLEAN CaseSensitive); + +NTSTATUS +CreateBTreeFromIndex(PDEVICE_EXTENSION Vcb, + PFILE_RECORD_HEADER FileRecordWithIndex, + /*PCWSTR IndexName,*/ + PNTFS_ATTR_CONTEXT IndexRootContext, + PINDEX_ROOT_ATTRIBUTE IndexRoot, + PB_TREE *NewTree); + +NTSTATUS +CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt, + PB_TREE Tree, + ULONG MaxIndexSize, + PINDEX_ROOT_ATTRIBUTE *IndexRoot, + ULONG *Length); + +NTSTATUS +DemoteBTreeRoot(PB_TREE Tree); + +VOID +DestroyBTree(PB_TREE Tree); + +VOID +DestroyBTreeNode(PB_TREE_FILENAME_NODE Node); + +VOID +DumpBTree(PB_TREE Tree); + +VOID +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, + ULONGLONG Vcn); + +ULONGLONG +GetIndexEntryVCN(PINDEX_ENTRY_ATTRIBUTE IndexEntry); + +ULONG +GetSizeOfIndexEntries(PB_TREE_FILENAME_NODE Node); + +NTSTATUS +NtfsInsertKey(PB_TREE Tree, + ULONGLONG FileReference, + PFILENAME_ATTRIBUTE FileNameAttribute, + PB_TREE_FILENAME_NODE Node, + BOOLEAN CaseSensitive, + ULONG MaxIndexRootSize, + ULONG IndexRecordSize, + PB_TREE_KEY *MedianKey, + PB_TREE_FILENAME_NODE *NewRightHandSibling); + +NTSTATUS +SplitBTreeNode(PB_TREE Tree, + PB_TREE_FILENAME_NODE Node, + PB_TREE_KEY *MedianKey, + PB_TREE_FILENAME_NODE *NewRightHandSibling, + BOOLEAN CaseSensitive); + +NTSTATUS +UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt, + PB_TREE Tree, + ULONG IndexBufferSize, + PFILE_RECORD_HEADER FileRecord); + +NTSTATUS +UpdateIndexNode(PDEVICE_EXTENSION DeviceExt, + PFILE_RECORD_HEADER FileRecord, + PB_TREE_FILENAME_NODE Node, + ULONG IndexBufferSize, + PNTFS_ATTR_CONTEXT IndexAllocationContext, + ULONG IndexAllocationOffset); + /* close.c */ NTSTATUS @@ -589,6 +828,20 @@ 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, + BOOLEAN CaseSensitive, + BOOLEAN CanWait); /* devctl.c */ @@ -695,7 +948,8 @@ NTSTATUS NtfsGetFCBForFile(PNTFS_VCB Vcb, PNTFS_FCB *pParentFCB, PNTFS_FCB *pFCB, - const PWSTR pFileName); + PCWSTR pFileName, + BOOLEAN CaseSensitive); NTSTATUS NtfsReadFCBAttribute(PNTFS_VCB Vcb, @@ -720,6 +974,16 @@ NtfsMakeFCBFromDirEntry(PNTFS_VCB Vcb, NTSTATUS NtfsQueryInformation(PNTFS_IRP_CONTEXT IrpContext); +NTSTATUS +NtfsSetEndOfFile(PNTFS_FCB Fcb, + PFILE_OBJECT FileObject, + PDEVICE_EXTENSION DeviceExt, + ULONG IrpFlags, + BOOLEAN CaseSensitive, + PLARGE_INTEGER NewFileSize); + +NTSTATUS +NtfsSetInformation(PNTFS_IRP_CONTEXT IrpContext); /* fsctl.c */ @@ -728,6 +992,22 @@ NtfsFileSystemControl(PNTFS_IRP_CONTEXT IrpContext); /* mft.c */ +NTSTATUS +NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, + ULONGLONG DirectoryMftIndex, + ULONGLONG FileReferenceNumber, + PFILENAME_ATTRIBUTE FilenameAttribute, + BOOLEAN CaseSensitive); + +NTSTATUS +AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, + PDEVICE_EXTENSION DeviceExt, + PULONGLONG DestinationIndex, + BOOLEAN CanWait); + +VOID +NtfsDumpData(ULONG_PTR Buffer, ULONG Length); + PNTFS_ATTR_CONTEXT PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord); @@ -741,24 +1021,112 @@ ReadAttribute(PDEVICE_EXTENSION Vcb, PCHAR Buffer, ULONG Length); +NTSTATUS +WriteAttribute(PDEVICE_EXTENSION Vcb, + PNTFS_ATTR_CONTEXT Context, + ULONGLONG Offset, + const PUCHAR Buffer, + ULONG Length, + PULONG LengthWritten, + PFILE_RECORD_HEADER FileRecord); + ULONGLONG AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord); -ULONG +NTSTATUS +InternalSetResidentAttributeLength(PDEVICE_EXTENSION DeviceExt, + PNTFS_ATTR_CONTEXT AttrContext, + PFILE_RECORD_HEADER FileRecord, + ULONG AttrOffset, + ULONG DataSize); + +PNTFS_ATTR_RECORD +MoveAttributes(PDEVICE_EXTENSION DeviceExt, + PNTFS_ATTR_RECORD FirstAttributeToMove, + ULONG FirstAttributeOffset, + ULONG_PTR MoveTo); + +NTSTATUS +SetAttributeDataLength(PFILE_OBJECT FileObject, + PNTFS_FCB Fcb, + PNTFS_ATTR_CONTEXT AttrContext, + ULONG AttrOffset, + PFILE_RECORD_HEADER FileRecord, + PLARGE_INTEGER DataSize); + +VOID +SetFileRecordEnd(PFILE_RECORD_HEADER FileRecord, + PNTFS_ATTR_RECORD AttrEnd, + ULONG EndMarker); + +NTSTATUS +SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, + PNTFS_ATTR_CONTEXT AttrContext, + ULONG AttrOffset, + PFILE_RECORD_HEADER FileRecord, + PLARGE_INTEGER DataSize); + +NTSTATUS +SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, + PNTFS_ATTR_CONTEXT AttrContext, + ULONG AttrOffset, + PFILE_RECORD_HEADER FileRecord, + PLARGE_INTEGER DataSize); + +ULONGLONG AttributeAllocatedLength(PNTFS_ATTR_RECORD AttrRecord); +BOOLEAN +CompareFileName(PUNICODE_STRING FileName, + PINDEX_ENTRY_ATTRIBUTE IndexEntry, + BOOLEAN DirSearch, + BOOLEAN CaseSensitive); + +NTSTATUS +UpdateMftMirror(PNTFS_VCB Vcb); + NTSTATUS ReadFileRecord(PDEVICE_EXTENSION Vcb, ULONGLONG index, PFILE_RECORD_HEADER file); +NTSTATUS +UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb, + PFILE_RECORD_HEADER MftRecord, + PCHAR IndexRecord, + ULONG IndexBlockSize, + PINDEX_ENTRY_ATTRIBUTE FirstEntry, + PINDEX_ENTRY_ATTRIBUTE LastEntry, + PUNICODE_STRING FileName, + PULONG StartEntry, + PULONG CurrentEntry, + BOOLEAN DirSearch, + ULONGLONG NewDataSize, + ULONGLONG NewAllocatedSize, + BOOLEAN CaseSensitive); + +NTSTATUS +UpdateFileNameRecord(PDEVICE_EXTENSION Vcb, + ULONGLONG ParentMFTIndex, + PUNICODE_STRING FileName, + BOOLEAN DirSearch, + ULONGLONG NewDataSize, + ULONGLONG NewAllocationSize, + BOOLEAN CaseSensitive); + +NTSTATUS +UpdateFileRecord(PDEVICE_EXTENSION Vcb, + ULONGLONG MftIndex, + PFILE_RECORD_HEADER FileRecord); + NTSTATUS FindAttribute(PDEVICE_EXTENSION Vcb, PFILE_RECORD_HEADER MftRecord, ULONG Type, PCWSTR Name, ULONG NameLength, - PNTFS_ATTR_CONTEXT * AttrCtx); + PNTFS_ATTR_CONTEXT * AttrCtx, + PULONG Offset); VOID ReadVCN(PDEVICE_EXTENSION Vcb, @@ -772,6 +1140,10 @@ NTSTATUS FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb, PNTFS_RECORD_HEADER Record); +NTSTATUS +AddFixupArray(PDEVICE_EXTENSION Vcb, + PNTFS_RECORD_HEADER Record); + NTSTATUS ReadLCN(PDEVICE_EXTENSION Vcb, ULONGLONG lcn, @@ -786,23 +1158,39 @@ EnumerAttribute(PFILE_RECORD_HEADER file, NTSTATUS NtfsLookupFile(PDEVICE_EXTENSION Vcb, PUNICODE_STRING PathName, + BOOLEAN CaseSensitive, PFILE_RECORD_HEADER *FileRecord, PULONGLONG MFTIndex); NTSTATUS NtfsLookupFileAt(PDEVICE_EXTENSION Vcb, PUNICODE_STRING PathName, + BOOLEAN CaseSensitive, PFILE_RECORD_HEADER *FileRecord, PULONGLONG MFTIndex, ULONGLONG CurrentMFTIndex); +VOID +NtfsDumpFileRecord(PDEVICE_EXTENSION Vcb, + PFILE_RECORD_HEADER FileRecord); + NTSTATUS NtfsFindFileAt(PDEVICE_EXTENSION Vcb, PUNICODE_STRING SearchPattern, PULONG FirstEntry, PFILE_RECORD_HEADER *FileRecord, PULONGLONG MFTIndex, - ULONGLONG CurrentMFTIndex); + ULONGLONG CurrentMFTIndex, + BOOLEAN CaseSensitive); + +NTSTATUS +NtfsFindMftRecord(PDEVICE_EXTENSION Vcb, + ULONGLONG MFTIndex, + PUNICODE_STRING FileName, + PULONG FirstEntry, + BOOLEAN DirSearch, + BOOLEAN CaseSensitive, + ULONGLONG *OutMFTIndex); /* misc.c */ @@ -817,6 +1205,21 @@ PVOID NtfsGetUserBuffer(PIRP Irp, BOOLEAN Paging); +NTSTATUS +NtfsLockUserBuffer(IN PIRP Irp, + IN ULONG Length, + IN LOCK_OPERATION Operation); + +#if 0 +BOOLEAN +wstrcmpjoki(PWSTR s1, PWSTR s2); + +VOID +CdfsSwapString(PWCHAR Out, + PUCHAR In, + ULONG Count); +#endif + VOID NtfsFileFlagsToAttributes(ULONG NtfsAttributes, PULONG FileAttributes); @@ -833,6 +1236,13 @@ NtfsWrite(PNTFS_IRP_CONTEXT IrpContext); /* volinfo.c */ +NTSTATUS +NtfsAllocateClusters(PDEVICE_EXTENSION DeviceExt, + ULONG FirstDesiredCluster, + ULONG DesiredClusters, + PULONG FirstAssignedCluster, + PULONG AssignedClusters); + ULONGLONG NtfsGetFreeClusters(PDEVICE_EXTENSION DeviceExt); diff --git a/drivers/filesystems/ntfs/rw.c b/drivers/filesystems/ntfs/rw.c index 0437b61d997..94d05de9ae1 100644 --- a/drivers/filesystems/ntfs/rw.c +++ b/drivers/filesystems/ntfs/rw.c @@ -22,6 +22,7 @@ * PURPOSE: NTFS filesystem driver * PROGRAMMERS: Art Yerkes * Pierre Schweitzer (pierre@reactos.org) + * Trevor Thompson */ /* INCLUDES *****************************************************************/ @@ -59,7 +60,7 @@ NtfsReadFile(PDEVICE_EXTENSION DeviceExt, PCHAR ReadBuffer = (PCHAR)Buffer; ULONGLONG StreamSize; - DPRINT1("NtfsReadFile(%p, %p, %p, %u, %u, %x, %p)\n", DeviceExt, FileObject, Buffer, Length, ReadOffset, IrpFlags, LengthRead); + DPRINT1("NtfsReadFile(%p, %p, %p, %lu, %lu, %lx, %p)\n", DeviceExt, FileObject, Buffer, Length, ReadOffset, IrpFlags, LengthRead); *LengthRead = 0; @@ -94,7 +95,7 @@ NtfsReadFile(PDEVICE_EXTENSION DeviceExt, } - Status = FindAttribute(DeviceExt, FileRecord, AttributeData, Fcb->Stream, wcslen(Fcb->Stream), &DataContext); + Status = FindAttribute(DeviceExt, FileRecord, AttributeData, Fcb->Stream, wcslen(Fcb->Stream), &DataContext, NULL); if (!NT_SUCCESS(Status)) { NTSTATUS BrowseStatus; @@ -125,7 +126,7 @@ NtfsReadFile(PDEVICE_EXTENSION DeviceExt, return Status; } - StreamSize = AttributeDataLength(&DataContext->Record); + StreamSize = AttributeDataLength(DataContext->pRecord); if (ReadOffset >= StreamSize) { DPRINT1("Reading beyond stream end!\n"); @@ -148,7 +149,7 @@ NtfsReadFile(PDEVICE_EXTENSION DeviceExt, /* do we need to extend RealLength by one sector? */ if (RealLength + RealReadOffset < ReadOffset + Length) { - if (RealReadOffset + RealLength + DeviceExt->NtfsInfo.BytesPerSector <= AttributeAllocatedLength(&DataContext->Record)) + if (RealReadOffset + RealLength + DeviceExt->NtfsInfo.BytesPerSector <= AttributeAllocatedLength(DataContext->pRecord)) RealLength += DeviceExt->NtfsInfo.BytesPerSector; } @@ -164,7 +165,7 @@ NtfsReadFile(PDEVICE_EXTENSION DeviceExt, AllocatedBuffer = TRUE; } - DPRINT1("Effective read: %lu at %lu for stream '%S'\n", RealLength, RealReadOffset, Fcb->Stream); + DPRINT("Effective read: %lu at %lu for stream '%S'\n", RealLength, RealReadOffset, Fcb->Stream); RealLengthRead = ReadAttribute(DeviceExt, DataContext, RealReadOffset, (PCHAR)ReadBuffer, RealLength); if (RealLengthRead == 0) { @@ -183,7 +184,7 @@ NtfsReadFile(PDEVICE_EXTENSION DeviceExt, *LengthRead = ToRead; - DPRINT1("%lu got read\n", *LengthRead); + DPRINT("%lu got read\n", *LengthRead); if (AllocatedBuffer) { @@ -255,14 +256,465 @@ NtfsRead(PNTFS_IRP_CONTEXT IrpContext) return Status; } +/** +* @name NtfsWriteFile +* @implemented +* +* Writes a file to the disk. It presently borrows a lot of code from NtfsReadFile() and +* VFatWriteFileData(). It needs some more work before it will be complete; it won't handle +* page files, asnyc io, cached writes, etc. +* +* @param DeviceExt +* Points to the target disk's DEVICE_EXTENSION +* +* @param FileObject +* Pointer to a FILE_OBJECT describing the target file +* +* @param Buffer +* The data that's being written to the file +* +* @Param Length +* The size of the data buffer being written, in bytes +* +* @param WriteOffset +* Offset, in bytes, from the beginning of the file. Indicates where to start +* writing data. +* +* @param IrpFlags +* TODO: flags are presently ignored in code. +* +* @param CaseSensitive +* Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE +* if an application opened the file with the FILE_FLAG_POSIX_SEMANTICS flag. +* +* @param LengthWritten +* Pointer to a ULONG. This ULONG will be set to the number of bytes successfully written. +* +* @return +* STATUS_SUCCESS if successful, STATUS_NOT_IMPLEMENTED if a required feature isn't implemented, +* STATUS_INSUFFICIENT_RESOURCES if an allocation failed, STATUS_ACCESS_DENIED if the write itself fails, +* STATUS_PARTIAL_COPY or STATUS_UNSUCCESSFUL if ReadFileRecord() fails, or +* STATUS_OBJECT_NAME_NOT_FOUND if the file's data stream could not be found. +* +* @remarks Called by NtfsWrite(). It may perform a read-modify-write operation if the requested write is +* not sector-aligned. LengthWritten only refers to how much of the requested data has been written; +* extra data that needs to be written to make the write sector-aligned will not affect it. +* +*/ +NTSTATUS NtfsWriteFile(PDEVICE_EXTENSION DeviceExt, + PFILE_OBJECT FileObject, + const PUCHAR Buffer, + ULONG Length, + ULONG WriteOffset, + ULONG IrpFlags, + BOOLEAN CaseSensitive, + PULONG LengthWritten) +{ + NTSTATUS Status = STATUS_NOT_IMPLEMENTED; + PNTFS_FCB Fcb; + PFILE_RECORD_HEADER FileRecord; + PNTFS_ATTR_CONTEXT DataContext; + ULONG AttributeOffset; + ULONGLONG StreamSize; + DPRINT("NtfsWriteFile(%p, %p, %p, %lu, %lu, %x, %s, %p)\n", + DeviceExt, + FileObject, + Buffer, + Length, + WriteOffset, + IrpFlags, + (CaseSensitive ? "TRUE" : "FALSE"), + LengthWritten); + + *LengthWritten = 0; + + ASSERT(DeviceExt); + + if (Length == 0) + { + if (Buffer == NULL) + return STATUS_SUCCESS; + else + return STATUS_INVALID_PARAMETER; + } + + // get the File control block + Fcb = (PNTFS_FCB)FileObject->FsContext; + ASSERT(Fcb); + + DPRINT("Fcb->PathName: %wS\n", Fcb->PathName); + DPRINT("Fcb->ObjectName: %wS\n", Fcb->ObjectName); + + // we don't yet handle compression + if (NtfsFCBIsCompressed(Fcb)) + { + DPRINT("Compressed file!\n"); + UNIMPLEMENTED; + return STATUS_NOT_IMPLEMENTED; + } + + // allocate non-paged memory for the FILE_RECORD_HEADER + FileRecord = ExAllocatePoolWithTag(NonPagedPool, DeviceExt->NtfsInfo.BytesPerFileRecord, TAG_NTFS); + if (FileRecord == NULL) + { + DPRINT1("Not enough memory! Can't write %wS!\n", Fcb->PathName); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // read the FILE_RECORD_HEADER from the drive (or cache) + DPRINT("Reading file record...\n"); + Status = ReadFileRecord(DeviceExt, Fcb->MFTIndex, FileRecord); + if (!NT_SUCCESS(Status)) + { + // We couldn't get the file's record. Free the memory and return the error + DPRINT1("Can't find record for %wS!\n", Fcb->ObjectName); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + return Status; + } + + DPRINT("Found record for %wS\n", Fcb->ObjectName); + + // Find the attribute with the data stream for our file + DPRINT("Finding Data Attribute...\n"); + Status = FindAttribute(DeviceExt, FileRecord, AttributeData, Fcb->Stream, wcslen(Fcb->Stream), &DataContext, + &AttributeOffset); + + // Did we fail to find the attribute? + if (!NT_SUCCESS(Status)) + { + NTSTATUS BrowseStatus; + FIND_ATTR_CONTXT Context; + PNTFS_ATTR_RECORD Attribute; + + DPRINT1("No '%S' data stream associated with file!\n", Fcb->Stream); + + // Couldn't find the requested data stream; print a list of streams available + BrowseStatus = FindFirstAttribute(&Context, DeviceExt, FileRecord, FALSE, &Attribute); + while (NT_SUCCESS(BrowseStatus)) + { + if (Attribute->Type == AttributeData) + { + UNICODE_STRING Name; + + Name.Length = Attribute->NameLength * sizeof(WCHAR); + Name.MaximumLength = Name.Length; + Name.Buffer = (PWCHAR)((ULONG_PTR)Attribute + Attribute->NameOffset); + DPRINT1("Data stream: '%wZ' available\n", &Name); + } + + BrowseStatus = FindNextAttribute(&Context, &Attribute); + } + FindCloseAttribute(&Context); + + ReleaseAttributeContext(DataContext); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + return Status; + } + + // Get the size of the stream on disk + StreamSize = AttributeDataLength(DataContext->pRecord); + + DPRINT("WriteOffset: %lu\tStreamSize: %I64u\n", WriteOffset, StreamSize); + + // Are we trying to write beyond the end of the stream? + if (WriteOffset + Length > StreamSize) + { + // is increasing the stream size allowed? + if (!(Fcb->Flags & FCB_IS_VOLUME) && + !(IrpFlags & IRP_PAGING_IO)) + { + LARGE_INTEGER DataSize; + ULONGLONG AllocationSize; + PFILENAME_ATTRIBUTE fileNameAttribute; + ULONGLONG ParentMFTId; + UNICODE_STRING filename; + + DataSize.QuadPart = WriteOffset + Length; + + // set the attribute data length + Status = SetAttributeDataLength(FileObject, Fcb, DataContext, AttributeOffset, FileRecord, &DataSize); + if (!NT_SUCCESS(Status)) + { + ReleaseAttributeContext(DataContext); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + *LengthWritten = 0; + return Status; + } + + AllocationSize = AttributeAllocatedLength(DataContext->pRecord); + + // now we need to update this file's size in every directory index entry that references it + // TODO: put this code in its own function and adapt it to work with every filename / hardlink + // stored in the file record. + fileNameAttribute = GetBestFileNameFromRecord(Fcb->Vcb, FileRecord); + ASSERT(fileNameAttribute); + + ParentMFTId = fileNameAttribute->DirectoryFileReferenceNumber & NTFS_MFT_MASK; + + filename.Buffer = fileNameAttribute->Name; + filename.Length = fileNameAttribute->NameLength * sizeof(WCHAR); + filename.MaximumLength = filename.Length; + + Status = UpdateFileNameRecord(Fcb->Vcb, + ParentMFTId, + &filename, + FALSE, + DataSize.QuadPart, + AllocationSize, + CaseSensitive); + + } + else + { + // TODO - just fail for now + ReleaseAttributeContext(DataContext); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + *LengthWritten = 0; + return STATUS_ACCESS_DENIED; + } + } + + DPRINT("Length: %lu\tWriteOffset: %lu\tStreamSize: %I64u\n", Length, WriteOffset, StreamSize); + + // Write the data to the attribute + Status = WriteAttribute(DeviceExt, DataContext, WriteOffset, Buffer, Length, LengthWritten, FileRecord); + + // Did the write fail? + if (!NT_SUCCESS(Status)) + { + DPRINT1("Write failure!\n"); + ReleaseAttributeContext(DataContext); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + + return Status; + } + + // This should never happen: + if (*LengthWritten != Length) + { + DPRINT1("\a\tNTFS DRIVER ERROR: length written (%lu) differs from requested (%lu), but no error was indicated!\n", + *LengthWritten, Length); + Status = STATUS_UNEXPECTED_IO_ERROR; + } + + ReleaseAttributeContext(DataContext); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + + return Status; +} + +/** +* @name NtfsWrite +* @implemented +* +* Handles IRP_MJ_WRITE I/O Request Packets for NTFS. This code borrows a lot from +* VfatWrite, and needs a lot of cleaning up. It also needs a lot more of the code +* from VfatWrite integrated. +* +* @param IrpContext +* Points to an NTFS_IRP_CONTEXT which describes the write +* +* @return +* STATUS_SUCCESS if successful, +* STATUS_INSUFFICIENT_RESOURCES if an allocation failed, +* STATUS_INVALID_DEVICE_REQUEST if called on the main device object, +* STATUS_NOT_IMPLEMENTED or STATUS_ACCESS_DENIED if a required feature isn't implemented. +* STATUS_PARTIAL_COPY, STATUS_UNSUCCESSFUL, or STATUS_OBJECT_NAME_NOT_FOUND if NtfsWriteFile() fails. +* +* @remarks Called by NtfsDispatch() in response to an IRP_MJ_WRITE request. Page files are not implemented. +* Support for large files (>4gb) is not implemented. Cached writes, file locks, transactions, etc - not implemented. +* +*/ NTSTATUS NtfsWrite(PNTFS_IRP_CONTEXT IrpContext) { - DPRINT("NtfsWrite(IrpContext %p)\n",IrpContext); + PNTFS_FCB Fcb; + PERESOURCE Resource = NULL; + LARGE_INTEGER ByteOffset; + PUCHAR Buffer; + NTSTATUS Status = STATUS_SUCCESS; + ULONG Length = 0; + ULONG ReturnedWriteLength = 0; + PDEVICE_OBJECT DeviceObject = NULL; + PDEVICE_EXTENSION DeviceExt = NULL; + PFILE_OBJECT FileObject = NULL; + PIRP Irp = NULL; + ULONG BytesPerSector; - IrpContext->Irp->IoStatus.Information = 0; - return STATUS_NOT_SUPPORTED; + DPRINT("NtfsWrite(IrpContext %p)\n", IrpContext); + ASSERT(IrpContext); + + // This request is not allowed on the main device object + if (IrpContext->DeviceObject == NtfsGlobalData->DeviceObject) + { + DPRINT1("\t\t\t\tNtfsWrite is called with the main device object.\n"); + + Irp->IoStatus.Information = 0; + return STATUS_INVALID_DEVICE_REQUEST; + } + + // get the I/O request packet + Irp = IrpContext->Irp; + + // get the File control block + Fcb = (PNTFS_FCB)IrpContext->FileObject->FsContext; + ASSERT(Fcb); + + DPRINT("About to write %wS\n", Fcb->ObjectName); + DPRINT("NTFS Version: %d.%d\n", Fcb->Vcb->NtfsInfo.MajorVersion, Fcb->Vcb->NtfsInfo.MinorVersion); + + // setup some more locals + FileObject = IrpContext->FileObject; + DeviceObject = IrpContext->DeviceObject; + DeviceExt = DeviceObject->DeviceExtension; + BytesPerSector = DeviceExt->StorageDevice->SectorSize; + Length = IrpContext->Stack->Parameters.Write.Length; + + // get the file offset we'll be writing to + ByteOffset = IrpContext->Stack->Parameters.Write.ByteOffset; + if (ByteOffset.u.LowPart == FILE_WRITE_TO_END_OF_FILE && + ByteOffset.u.HighPart == -1) + { + ByteOffset.QuadPart = Fcb->RFCB.FileSize.QuadPart; + } + + DPRINT("ByteOffset: %I64u\tLength: %lu\tBytes per sector: %lu\n", ByteOffset.QuadPart, + Length, BytesPerSector); + + if (ByteOffset.u.HighPart && !(Fcb->Flags & FCB_IS_VOLUME)) + { + // TODO: Support large files + DPRINT1("FIXME: Writing to large files is not yet supported at this time.\n"); + return STATUS_INVALID_PARAMETER; + } + + // Is this a non-cached write? A non-buffered write? + if (IrpContext->Irp->Flags & (IRP_PAGING_IO | IRP_NOCACHE) || (Fcb->Flags & FCB_IS_VOLUME) || + IrpContext->FileObject->Flags & FILE_NO_INTERMEDIATE_BUFFERING) + { + // non-cached and non-buffered writes must be sector aligned + if (ByteOffset.u.LowPart % BytesPerSector != 0 || Length % BytesPerSector != 0) + { + DPRINT1("Non-cached writes and non-buffered writes must be sector aligned!\n"); + return STATUS_INVALID_PARAMETER; + } + } + + if (Length == 0) + { + DPRINT1("Null write!\n"); + + IrpContext->Irp->IoStatus.Information = 0; + + // FIXME: Doesn't accurately detect when a user passes NULL to WriteFile() for the buffer + if (Irp->UserBuffer == NULL && Irp->MdlAddress == NULL) + { + // FIXME: Update last write time + return STATUS_SUCCESS; + } + + return STATUS_INVALID_PARAMETER; + } + + // get the Resource + if (Fcb->Flags & FCB_IS_VOLUME) + { + Resource = &DeviceExt->DirResource; + } + else if (IrpContext->Irp->Flags & IRP_PAGING_IO) + { + Resource = &Fcb->PagingIoResource; + } + else + { + Resource = &Fcb->MainResource; + } + + // acquire exclusive access to the Resource + if (!ExAcquireResourceExclusiveLite(Resource, BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT))) + { + return STATUS_CANT_WAIT; + } + + /* From VfatWrite(). Todo: Handle file locks + if (!(IrpContext->Irp->Flags & IRP_PAGING_IO) && + FsRtlAreThereCurrentFileLocks(&Fcb->FileLock)) + { + if (!FsRtlCheckLockForWriteAccess(&Fcb->FileLock, IrpContext->Irp)) + { + Status = STATUS_FILE_LOCK_CONFLICT; + goto ByeBye; + } + }*/ + + // Is this an async request to a file? + if (!(IrpContext->Flags & IRPCONTEXT_CANWAIT) && !(Fcb->Flags & FCB_IS_VOLUME)) + { + DPRINT1("FIXME: Async writes not supported in NTFS!\n"); + + ExReleaseResourceLite(Resource); + return STATUS_NOT_IMPLEMENTED; + } + + // get the buffer of data the user is trying to write + Buffer = NtfsGetUserBuffer(Irp, BooleanFlagOn(Irp->Flags, IRP_PAGING_IO)); + ASSERT(Buffer); + + // lock the buffer + Status = NtfsLockUserBuffer(Irp, Length, IoReadAccess); + + // were we unable to lock the buffer? + if (!NT_SUCCESS(Status)) + { + DPRINT1("Unable to lock user buffer!\n"); + + ExReleaseResourceLite(Resource); + return Status; + } + + DPRINT("Existing File Size(Fcb->RFCB.FileSize.QuadPart): %I64u\n", Fcb->RFCB.FileSize.QuadPart); + DPRINT("About to write the data. Length: %lu\n", Length); + + // TODO: handle HighPart of ByteOffset (large files) + + // write the file + Status = NtfsWriteFile(DeviceExt, + FileObject, + Buffer, + Length, + ByteOffset.LowPart, + Irp->Flags, + BooleanFlagOn(IrpContext->Stack->Flags, SL_CASE_SENSITIVE), + &ReturnedWriteLength); + + IrpContext->Irp->IoStatus.Status = Status; + + // was the write successful? + if (NT_SUCCESS(Status)) + { + // TODO: Update timestamps + + if (FileObject->Flags & FO_SYNCHRONOUS_IO) + { + // advance the file pointer + FileObject->CurrentByteOffset.QuadPart = ByteOffset.QuadPart + ReturnedWriteLength; + } + + IrpContext->PriorityBoost = IO_DISK_INCREMENT; + } + else + { + DPRINT1("Write not Succesful!\tReturned length: %lu\n", ReturnedWriteLength); + } + + Irp->IoStatus.Information = ReturnedWriteLength; + + // Note: We leave the user buffer that we locked alone, it's up to the I/O manager to unlock and free it + + ExReleaseResourceLite(Resource); + + return Status; } /* EOF */ diff --git a/drivers/filesystems/ntfs/volinfo.c b/drivers/filesystems/ntfs/volinfo.c index ddf7f6c58dd..6b7e598819a 100644 --- a/drivers/filesystems/ntfs/volinfo.c +++ b/drivers/filesystems/ntfs/volinfo.c @@ -62,14 +62,14 @@ NtfsGetFreeClusters(PDEVICE_EXTENSION DeviceExt) return 0; } - Status = FindAttribute(DeviceExt, BitmapRecord, AttributeData, L"", 0, &DataContext); + Status = FindAttribute(DeviceExt, BitmapRecord, AttributeData, L"", 0, &DataContext, NULL); if (!NT_SUCCESS(Status)) { ExFreePoolWithTag(BitmapRecord, TAG_NTFS); return 0; } - BitmapDataSize = AttributeDataLength(&DataContext->Record); + BitmapDataSize = AttributeDataLength(DataContext->pRecord); ASSERT((BitmapDataSize * 8) >= DeviceExt->NtfsInfo.ClusterCount); BitmapData = ExAllocatePoolWithTag(NonPagedPool, ROUND_UP(BitmapDataSize, DeviceExt->NtfsInfo.BytesPerSector), TAG_NTFS); if (BitmapData == NULL) @@ -99,6 +99,113 @@ NtfsGetFreeClusters(PDEVICE_EXTENSION DeviceExt) return FreeClusters; } +/** +* NtfsAllocateClusters +* Allocates a run of clusters. The run allocated might be smaller than DesiredClusters. +*/ +NTSTATUS +NtfsAllocateClusters(PDEVICE_EXTENSION DeviceExt, + ULONG FirstDesiredCluster, + ULONG DesiredClusters, + PULONG FirstAssignedCluster, + PULONG AssignedClusters) +{ + NTSTATUS Status; + PFILE_RECORD_HEADER BitmapRecord; + PNTFS_ATTR_CONTEXT DataContext; + ULONGLONG BitmapDataSize; + PUCHAR BitmapData; + ULONGLONG FreeClusters = 0; + RTL_BITMAP Bitmap; + ULONG AssignedRun; + ULONG LengthWritten; + + DPRINT1("NtfsAllocateClusters(%p, %lu, %lu, %p, %p)\n", DeviceExt, FirstDesiredCluster, DesiredClusters, FirstAssignedCluster, AssignedClusters); + + BitmapRecord = ExAllocatePoolWithTag(NonPagedPool, + DeviceExt->NtfsInfo.BytesPerFileRecord, + TAG_NTFS); + if (BitmapRecord == NULL) + { + return STATUS_INSUFFICIENT_RESOURCES; + } + + Status = ReadFileRecord(DeviceExt, NTFS_FILE_BITMAP, BitmapRecord); + if (!NT_SUCCESS(Status)) + { + ExFreePoolWithTag(BitmapRecord, TAG_NTFS); + return Status; + } + + Status = FindAttribute(DeviceExt, BitmapRecord, AttributeData, L"", 0, &DataContext, NULL); + if (!NT_SUCCESS(Status)) + { + ExFreePoolWithTag(BitmapRecord, TAG_NTFS); + return Status; + } + + BitmapDataSize = AttributeDataLength(DataContext->pRecord); + BitmapDataSize = min(BitmapDataSize, 0xffffffff); + ASSERT((BitmapDataSize * 8) >= DeviceExt->NtfsInfo.ClusterCount); + BitmapData = ExAllocatePoolWithTag(NonPagedPool, ROUND_UP(BitmapDataSize, DeviceExt->NtfsInfo.BytesPerSector), TAG_NTFS); + if (BitmapData == NULL) + { + ReleaseAttributeContext(DataContext); + ExFreePoolWithTag(BitmapRecord, TAG_NTFS); + return STATUS_INSUFFICIENT_RESOURCES; + } + + DPRINT1("Total clusters: %I64x\n", DeviceExt->NtfsInfo.ClusterCount); + DPRINT1("Total clusters in bitmap: %I64x\n", BitmapDataSize * 8); + DPRINT1("Diff in size: %I64d B\n", ((BitmapDataSize * 8) - DeviceExt->NtfsInfo.ClusterCount) * DeviceExt->NtfsInfo.SectorsPerCluster * DeviceExt->NtfsInfo.BytesPerSector); + + ReadAttribute(DeviceExt, DataContext, 0, (PCHAR)BitmapData, (ULONG)BitmapDataSize); + + RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, DeviceExt->NtfsInfo.ClusterCount); + FreeClusters = RtlNumberOfClearBits(&Bitmap); + + if (FreeClusters < DesiredClusters) + { + ReleaseAttributeContext(DataContext); + + ExFreePoolWithTag(BitmapData, TAG_NTFS); + ExFreePoolWithTag(BitmapRecord, TAG_NTFS); + return STATUS_DISK_FULL; + } + + // TODO: Observe MFT reservation zone + + // Can we get one contiguous run? + AssignedRun = RtlFindClearBitsAndSet(&Bitmap, DesiredClusters, FirstDesiredCluster); + + if (AssignedRun != 0xFFFFFFFF) + { + *FirstAssignedCluster = AssignedRun; + *AssignedClusters = DesiredClusters; + } + else + { + // we can't get one contiguous run + *AssignedClusters = RtlFindNextForwardRunClear(&Bitmap, FirstDesiredCluster, FirstAssignedCluster); + + if (*AssignedClusters == 0) + { + // we couldn't find any runs starting at DesiredFirstCluster + *AssignedClusters = RtlFindLongestRunClear(&Bitmap, FirstAssignedCluster); + } + + } + + Status = WriteAttribute(DeviceExt, DataContext, 0, BitmapData, (ULONG)BitmapDataSize, &LengthWritten, BitmapRecord); + + ReleaseAttributeContext(DataContext); + + ExFreePoolWithTag(BitmapData, TAG_NTFS); + ExFreePoolWithTag(BitmapRecord, TAG_NTFS); + + return Status; +} + static NTSTATUS NtfsGetFsVolumeInformation(PDEVICE_OBJECT DeviceObject,