https://git.reactos.org/?p=reactos.git;a=commitdiff;h=afe40eb054267a365cc6ca...
commit afe40eb054267a365cc6ca8142f48870a49839ea Author: Trevor Thompson tmt256@email.vccs.edu AuthorDate: Thu Jul 14 15:20:48 2016 +0000
[NTFS] Implement AddRun(). Add support functions and documentation. +ConvertDataRunsToLargeMCB() +ConvertLargeMCBToDataRuns() *SetAttributeDataLength(), *NtfsWriteFile() - Update for AddRun() implementation. Add hack to SetAttributeDataLength() to allow notepad.exe to save files until freeing clusters is implemented.
svn path=/branches/GSoC_2016/NTFS/; revision=71942 --- drivers/filesystems/ntfs/attrib.c | 298 +++++++++++++++++++++++++++++++++++++- drivers/filesystems/ntfs/mft.c | 35 +++-- drivers/filesystems/ntfs/ntfs.h | 16 +- drivers/filesystems/ntfs/rw.c | 16 ++ 4 files changed, 352 insertions(+), 13 deletions(-)
diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index f34072d8a5..3cf40d8341 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -35,17 +35,309 @@
/* FUNCTIONS ****************************************************************/
+/** +* @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. +* 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_ATTR_CONTEXT AttrContext, +AddRun(PNTFS_VCB Vcb, + PNTFS_ATTR_CONTEXT AttrContext, + ULONG AttrOffset, + PFILE_RECORD_HEADER FileRecord, ULONGLONG NextAssignedCluster, ULONG RunLength) { - UNIMPLEMENTED; + NTSTATUS Status; + PUCHAR DataRun = (PUCHAR)&AttrContext->Record + AttrContext->Record.NonResident.MappingPairsOffset; + int DataRunMaxLength; + PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset); + LARGE_MCB DataRunsMCB; + ULONG NextAttributeOffset = AttrOffset + AttrContext->Record.Length; + ULONGLONG NextVBN = AttrContext->Record.NonResident.LowestVCN; + + // Allocate some memory for the RunBuffer + PUCHAR RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS); + int RunBufferOffset = 0;
if (!AttrContext->Record.IsNonResident) return STATUS_INVALID_PARAMETER;
- return STATUS_NOT_IMPLEMENTED; + // Convert the data runs to a map control block + Status = ConvertDataRunsToLargeMCB(DataRun, &DataRunsMCB, &NextVBN); + if (!NT_SUCCESS(Status)) + { + DPRINT1("Unable to convert data runs to MCB (probably ran out of memory)!\n"); + return Status; + } + + // Add newly-assigned clusters to mcb + FsRtlAddLargeMcbEntry(&DataRunsMCB, + NextVBN, + NextAssignedCluster, + RunLength); + + // Convert the map control block back to encoded data runs + ConvertLargeMCBToDataRuns(&DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferOffset); + + // Get the amount of free space between the start of the of the first data run and the attribute end + DataRunMaxLength = AttrContext->Record.Length - AttrContext->Record.NonResident.MappingPairsOffset; + + // Do we need to extend the attribute (or convert to attribute list)? + if (DataRunMaxLength < RunBufferOffset) + { + PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset); + DataRunMaxLength += Vcb->NtfsInfo.BytesPerFileRecord - NextAttributeOffset - (sizeof(ULONG) * 2); + + // Can we move the end of the attribute? + if (NextAttribute->Type != AttributeEnd || DataRunMaxLength < RunBufferOffset - 1) + { + DPRINT1("FIXME: Need to create attribute list! Max Data Run Length available: %d\n", DataRunMaxLength); + if (NextAttribute->Type != AttributeEnd) + DPRINT1("There's another attribute after this one with type %0xlx\n", NextAttribute->Type); + ExFreePoolWithTag(RunBuffer, TAG_NTFS); + FsRtlUninitializeLargeMcb(&DataRunsMCB); + return STATUS_NOT_IMPLEMENTED; + } + + // calculate position of end markers + NextAttributeOffset = AttrOffset + AttrContext->Record.NonResident.MappingPairsOffset + RunBufferOffset; + NextAttributeOffset = ALIGN_UP_BY(NextAttributeOffset, 8); + + // Write the end markers + NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset); + NextAttribute->Type = AttributeEnd; + NextAttribute->Length = FILE_RECORD_END; + + // Update the length + DestinationAttribute->Length = NextAttributeOffset - AttrOffset; + AttrContext->Record.Length = DestinationAttribute->Length; + + // We need to increase the FileRecord size + FileRecord->BytesInUse = NextAttributeOffset + (sizeof(ULONG) * 2); + } + + // NOTE: from this point on the original attribute record will contain invalid data in it's runbuffer + // TODO: Elegant fix? Could we free the old Record and allocate a new one without issue? + + // Update HighestVCN + DestinationAttribute->NonResident.HighestVCN = + AttrContext->Record.NonResident.HighestVCN = max(NextVBN - 1 + RunLength, + AttrContext->Record.NonResident.HighestVCN); + + // Write data runs to destination attribute + RtlCopyMemory((PVOID)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), + RunBuffer, + RunBufferOffset); + + // Update the file record + Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord); + + ExFreePoolWithTag(RunBuffer, TAG_NTFS); + FsRtlUninitializeLargeMcb(&DataRunsMCB); + + NtfsDumpDataRuns((PUCHAR)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), 0); + + return Status; +} + +/** +* @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 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 NextCluster; + + ULONGLONG LastLCN = 0; + + // Initialize the MCB, potentially catch an exception + _SEH2_TRY{ + FsRtlInitializeLargeMcb(DataRunsMCB, NonPagedPool); + } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + _SEH2_YIELD(return STATUS_INSUFFICIENT_RESOURCES); + } _SEH2_END; + + while (*DataRun != 0) + { + DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength); + + if (DataRunOffset != -1) + { + // Normal data run. + DataRunStartLCN = LastLCN + DataRunOffset; + LastLCN = DataRunStartLCN; + NextCluster = LastLCN + DataRunLength; + + + _SEH2_TRY{ + if (!FsRtlAddLargeMcbEntry(DataRunsMCB, + *pNextVBN, + DataRunStartLCN, + DataRunLength)) + { + FsRtlUninitializeLargeMcb(DataRunsMCB); + return STATUS_INSUFFICIENT_RESOURCES; + } + } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + FsRtlUninitializeLargeMcb(DataRunsMCB); + _SEH2_YIELD(return STATUS_INSUFFICIENT_RESOURCES); + } _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; + + + DPRINT("\t[Vbn, Lbn, Count]\n"); + + // convert each mcb entry to a data run + for (int 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 diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 6263263dc8..7b3dcb8d37 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -211,6 +211,11 @@ InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext, FileRecord->BytesInUse = NextAttributeOffset + (sizeof(ULONG) * 2); }
+/** +* @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, @@ -235,6 +240,7 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, { ULONG BytesPerCluster = Fcb->Vcb->NtfsInfo.BytesPerCluster; ULONGLONG AllocationSize = ROUND_UP(DataSize->QuadPart, BytesPerCluster); + PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
// do we need to increase the allocation size? if (AttrContext->Record.NonResident.AllocatedSize < AllocationSize) @@ -265,7 +271,7 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, }
// now we need to add the clusters we allocated to the data run - Status = AddRun(AttrContext, NextAssignedCluster, AssignedClusters); + Status = AddRun(Fcb->Vcb, AttrContext, AttrOffset, FileRecord, NextAssignedCluster, AssignedClusters); if (!NT_SUCCESS(Status)) { DPRINT1("Error: Unable to add data run!\n"); @@ -275,23 +281,34 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, ClustersNeeded -= AssignedClusters; LastClusterInDataRun.LowPart = NextAssignedCluster + AssignedClusters - 1; } - - DPRINT1("FixMe: Increasing allocation size is unimplemented!\n"); - return STATUS_NOT_IMPLEMENTED; + } + else if (AttrContext->Record.NonResident.AllocatedSize > AllocationSize) + { + // shrink allocation size (TODO) + if (AllocationSize == 0) + { + // hack for notepad.exe + PUCHAR DataRuns = (PUCHAR)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset); + *DataRuns = 0; + DestinationAttribute->NonResident.HighestVCN = + AttrContext->Record.NonResident.HighestVCN = 0; + } }
// TODO: is the file compressed, encrypted, or sparse?
// NOTE: we need to have acquired the main resource exclusively, as well as(?) the PagingIoResource
- // TODO: update the allocated size on-disk - DPRINT("Allocated Size: %I64u\n", AttrContext->Record.NonResident.AllocatedSize); - + Fcb->RFCB.AllocationSize.QuadPart = AllocationSize; + AttrContext->Record.NonResident.AllocatedSize = AllocationSize; AttrContext->Record.NonResident.DataSize = DataSize->QuadPart; AttrContext->Record.NonResident.InitializedSize = DataSize->QuadPart;
- // copy the attribute record back into the FileRecord - RtlCopyMemory((PCHAR)FileRecord + AttrOffset, &AttrContext->Record, AttrContext->Record.Length); + DestinationAttribute->NonResident.AllocatedSize = AllocationSize; + DestinationAttribute->NonResident.DataSize = DataSize->QuadPart; + DestinationAttribute->NonResident.InitializedSize = DataSize->QuadPart; + + DPRINT("Allocated Size: %I64u\n", DestinationAttribute->NonResident.AllocatedSize); } else { diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index f16153012e..bc6a8d9089 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -517,10 +517,24 @@ NtfsMarkIrpContextForQueue(PNTFS_IRP_CONTEXT IrpContext) //NtfsDumpAttribute(PATTRIBUTE Attribute);
NTSTATUS -AddRun(PNTFS_ATTR_CONTEXT AttrContext, +AddRun(PNTFS_VCB Vcb, + PNTFS_ATTR_CONTEXT AttrContext, + ULONG AttrOffset, + PFILE_RECORD_HEADER FileRecord, ULONGLONG NextAssignedCluster, ULONG RunLength);
+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, diff --git a/drivers/filesystems/ntfs/rw.c b/drivers/filesystems/ntfs/rw.c index 5afb87e9e6..4db1c38815 100644 --- a/drivers/filesystems/ntfs/rw.c +++ b/drivers/filesystems/ntfs/rw.c @@ -432,6 +432,22 @@ NTSTATUS NtfsWriteFile(PDEVICE_EXTENSION DeviceExt, return Status; }
+ // at this point the record in DataContext may be stale, so we need to refresh it + ReleaseAttributeContext(DataContext); + + Status = FindAttribute(DeviceExt, + FileRecord, + AttributeData, + Fcb->Stream, + wcslen(Fcb->Stream), + &DataContext, + &AttributeOffset); + if (!NT_SUCCESS(Status)) + { + DPRINT1("DRIVER ERROR: Couldn't find $DATA attribute after setting size!\n"); + return Status; + } + // 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.