From 760cdfb5aa5043c49c30bec6066f682df9e12d28 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Tue, 5 Jul 2016 07:00:43 +0000 Subject: [PATCH 1/1] [NTFS] Add ability to write to resident attributes. SetAttributeDataLength() - Check if the file is memory mapped before truncating +InternalSetResidentAttributeLength() - Used by SetAttributeDataLength() svn path=/branches/GSoC_2016/NTFS/; revision=71820 --- drivers/filesystems/ntfs/attrib.c | 2 + drivers/filesystems/ntfs/mft.c | 189 ++++++++++++++++++++++++----- drivers/filesystems/ntfs/ntfs.h | 5 + drivers/filesystems/ntfs/volinfo.c | 3 +- 4 files changed, 170 insertions(+), 29 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index 9608d1d17be..e8625903bc4 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -509,6 +509,8 @@ NtfsDumpAttribute(PDEVICE_EXTENSION Vcb, DbgPrint(" logical clusters: %I64u - %I64u\n", lcn, lcn + runcount - 1); } + else + DbgPrint(" %u bytes of data\n", Attribute->Resident.ValueLength); } } diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index e1725f9a50e..4e10584ce0d 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -135,6 +135,8 @@ FindAttribute(PDEVICE_EXTENSION Vcb, DPRINT("Found context\n"); *AttrCtx = PrepareAttributeContext(Attribute); + (*AttrCtx)->FileMFTIndex = MftRecord->MFTRecordNumber; + if (Offset != NULL) *Offset = Context.Offset; @@ -170,6 +172,50 @@ AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord) return AttrRecord->Resident.ValueLength; } +void +InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext, + PFILE_RECORD_HEADER FileRecord, + ULONG AttrOffset, + ULONG DataSize) +{ + ULONG EndMarker = AttributeEnd; + ULONG FinalMarker = FILE_RECORD_END; + ULONG NextAttributeOffset; + ULONG Offset; + USHORT Padding; + + DPRINT("InternalSetResidentAttributeLength( %p, %p, %lu, %lu )\n", AttrContext, FileRecord, AttrOffset, DataSize); + + // update ValueLength Field + AttrContext->Record.Resident.ValueLength = DataSize; + Offset = AttrOffset + FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.ValueLength); + RtlCopyMemory((PCHAR)FileRecord + Offset, &DataSize, sizeof(ULONG)); + + // calculate the record length and end marker offset + AttrContext->Record.Length = DataSize + AttrContext->Record.Resident.ValueOffset; + NextAttributeOffset = AttrOffset + AttrContext->Record.Length; + + // Ensure NextAttributeOffset is aligned to an 8-byte boundary + if (NextAttributeOffset % 8 != 0) + { + Padding = 8 - (NextAttributeOffset % 8); + NextAttributeOffset += Padding; + AttrContext->Record.Length += Padding; + } + + // update the record length + Offset = AttrOffset + FIELD_OFFSET(NTFS_ATTR_RECORD, Length); + RtlCopyMemory((PCHAR)FileRecord + Offset, &AttrContext->Record.Length, sizeof(ULONG)); + + // write the end marker + RtlCopyMemory((PCHAR)FileRecord + NextAttributeOffset, &EndMarker, sizeof(ULONG)); + + // write the final marker + Offset = NextAttributeOffset + sizeof(ULONG); + RtlCopyMemory((PCHAR)FileRecord + Offset, &FinalMarker, sizeof(ULONG)); + + FileRecord->BytesInUse = Offset + sizeof(ULONG); +} NTSTATUS SetAttributeDataLength(PFILE_OBJECT FileObject, @@ -179,6 +225,18 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, PFILE_RECORD_HEADER FileRecord, PLARGE_INTEGER DataSize) { + NTSTATUS Status = STATUS_SUCCESS; + + // are we truncating the file? + if (DataSize->QuadPart < AttributeDataLength(&AttrContext->Record) + { + if (!MmCanFileBeTruncated(FileObject->SectionObjectPointer, (PLARGE_INTEGER)&AllocationSize)) + { + DPRINT1("Can't truncate a memory-mapped file!\n"); + return STATUS_USER_MAPPED_FILE; + } + } + if (AttrContext->Record.IsNonResident) { ULONG BytesPerCluster = Fcb->Vcb->NtfsInfo.BytesPerCluster; @@ -238,28 +296,59 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, AttrContext->Record.NonResident.DataSize = DataSize->QuadPart; AttrContext->Record.NonResident.InitializedSize = DataSize->QuadPart; - Fcb->RFCB.FileSize = *DataSize; - Fcb->RFCB.ValidDataLength = *DataSize; + // copy the attribute record back into the FileRecord + RtlCopyMemory((PCHAR)FileRecord + AttrOffset, &AttrContext->Record, AttrContext->Record.Length); + } + else + { + // resident attribute - DPRINT("Data Size: %I64u\n", Fcb->RFCB.FileSize.QuadPart); + // find the next attribute + ULONG NextAttributeOffset = AttrOffset + AttrContext->Record.Length; + PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((PCHAR)FileRecord + NextAttributeOffset); //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord); - // copy the attribute back into the FileRecord - RtlCopyMemory((PCHAR)FileRecord + AttrOffset, &AttrContext->Record, AttrContext->Record.Length); - - //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord); + // Do we need to increase the data length? + if (DataSize->QuadPart > AttrContext->Record.Resident.ValueLength) + { + // There's usually padding at the end of a record. Do we need to extend past it? + ULONG MaxValueLength = AttrContext->Record.Length - AttrContext->Record.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 += Fcb->Vcb->NtfsInfo.BytesPerFileRecord - NextAttributeOffset - (sizeof(ULONG) * 2); - // write the updated file record back to disk - UpdateFileRecord(Fcb->Vcb, Fcb->MFTIndex, FileRecord); + if (MaxValueLength < DataSize->LowPart || NextAttribute->Type != AttributeEnd) + { + DPRINT1("FIXME: Need to convert attribute to non-resident!\n"); + return STATUS_NOT_IMPLEMENTED; + } + } + } + else if (DataSize->LowPart < AttrContext->Record.Resident.ValueLength) + { + // we need to decrease the length + if (NextAttribute->Type != AttributeEnd) + { + DPRINT1("FIXME: Don't know how to decrease length of resident attribute unless it's the final attribute!\n"); + return STATUS_NOT_IMPLEMENTED; + } + } - CcSetFileSizes(FileObject, (PCC_FILE_SIZES)&Fcb->RFCB.AllocationSize); + InternalSetResidentAttributeLength(AttrContext, FileRecord, AttrOffset, DataSize->LowPart); } - else + + //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord); + + // write the updated file record back to disk + Status = UpdateFileRecord(Fcb->Vcb, Fcb->MFTIndex, FileRecord); + + if (NT_SUCCESS(Status)) { - // we can't yet handle resident attributes - DPRINT1("FixMe: Can't handle increasing length of resident attribute\n"); - return STATUS_NOT_IMPLEMENTED; + Fcb->RFCB.FileSize = *DataSize; + Fcb->RFCB.ValidDataLength = *DataSize; + CcSetFileSizes(FileObject, (PCC_FILE_SIZES)&Fcb->RFCB.AllocationSize); } return STATUS_SUCCESS; @@ -501,29 +590,76 @@ WriteAttribute(PDEVICE_EXTENSION Vcb, DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p)\n", Vcb, Context, Offset, Buffer, Length, RealLengthWritten); + *RealLengthWritten = 0; + // is this a resident attribute? if (!Context->Record.IsNonResident) { - DPRINT1("FIXME: Writing to resident NTFS records (small files) is not supported at this time.\n"); - // (TODO: This should be really easy to implement) + ULONG AttributeOffset; + PNTFS_ATTR_CONTEXT FoundContext; + PFILE_RECORD_HEADER FileRecord; - /* LeftOver code from ReadAttribute(), may be helpful: - if (Offset > Context->Record.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); - return Length;*/ + { + DPRINT1("DRIVER ERROR: Attribute is too small!\n"); + return STATUS_INVALID_PARAMETER; + } + + FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS); + + if (!FileRecord) + { + DPRINT1("Error: Couldn't allocate file record!\n"); + return STATUS_NO_MEMORY; + } + + // read the file record + ReadFileRecord(Vcb, Context->FileMFTIndex, FileRecord); - return STATUS_NOT_IMPLEMENTED; // until we implement it + // find where to write the attribute data to + Status = FindAttribute(Vcb, FileRecord, + Context->Record.Type, + (PCWSTR)((PCHAR)&Context->Record + Context->Record.NameOffset), + Context->Record.NameLength, + &FoundContext, + &AttributeOffset); + + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Couldn't find matching attribute!\n"); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + return Status; + } + + DPRINT("Offset: %I64u, AttributeOffset: %u, ValueOffset: %u\n", Offset, AttributeOffset, Context->Record.Resident.ValueLength); + Offset += AttributeOffset + Context->Record.Resident.ValueOffset; + + if (Offset + Length > Vcb->NtfsInfo.BytesPerFileRecord) + { + DPRINT1("DRIVER ERROR: Data being written extends past end of file record!\n"); + ReleaseAttributeContext(FoundContext); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + return STATUS_INVALID_PARAMETER; + } + + // copy the data being written into the file record + RtlCopyMemory((PCHAR)FileRecord + Offset, Buffer, Length); + + Status = UpdateFileRecord(Vcb, Context->FileMFTIndex, FileRecord); + + ReleaseAttributeContext(FoundContext); + 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. - *RealLengthWritten = 0; - // FIXME: Cache seems to be non-working. Disable it for now //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize) /*if (0) @@ -718,6 +854,7 @@ ReadFileRecord(PDEVICE_EXTENSION Vcb, } /* Apply update sequence array fixups. */ + DPRINT("Sequence number: %u\n", file->SequenceNumber); return FixupUpdateSequenceArray(Vcb, &file->Ntfs); } @@ -948,8 +1085,6 @@ UpdateFileRecord(PDEVICE_EXTENSION Vcb, // write the file record to the master file table Status = WriteAttribute(Vcb, Vcb->MFTContext, index * Vcb->NtfsInfo.BytesPerFileRecord, (const PUCHAR)file, Vcb->NtfsInfo.BytesPerFileRecord, &BytesWritten); - // TODO: Update MFT mirror - if (!NT_SUCCESS(Status)) { DPRINT1("UpdateFileRecord failed: %I64u written, %u expected\n", BytesWritten, Vcb->NtfsInfo.BytesPerFileRecord); diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index 66cb37f4bd1..238e6d5b2d4 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -175,6 +175,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 @@ -433,6 +437,7 @@ typedef struct _NTFS_ATTR_CONTEXT ULONGLONG CacheRunLength; LONGLONG CacheRunLastLCN; ULONGLONG CacheRunCurrentOffset; + ULONGLONG FileMFTIndex; NTFS_ATTR_RECORD Record; } NTFS_ATTR_CONTEXT, *PNTFS_ATTR_CONTEXT; diff --git a/drivers/filesystems/ntfs/volinfo.c b/drivers/filesystems/ntfs/volinfo.c index df3af4989a0..0a54cf73b83 100644 --- a/drivers/filesystems/ntfs/volinfo.c +++ b/drivers/filesystems/ntfs/volinfo.c @@ -116,7 +116,6 @@ NtfsAllocateClusters(PDEVICE_EXTENSION DeviceExt, ULONGLONG BitmapDataSize; PCHAR BitmapData; ULONGLONG FreeClusters = 0; - ULONG Read = 0; RTL_BITMAP Bitmap; DPRINT1("NtfsAllocateClusters(%p, %lu, %lu, %p, %p)\n", DeviceExt, FirstDesiredCluster, DesiredClusters, FirstAssignedCluster, AssignedClusters); @@ -158,7 +157,7 @@ NtfsAllocateClusters(PDEVICE_EXTENSION DeviceExt, 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, Read, (PCHAR)((ULONG_PTR)BitmapData + Read), (ULONG)BitmapDataSize); + ReadAttribute(DeviceExt, DataContext, 0, (PCHAR)BitmapData, (ULONG)BitmapDataSize); RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, DeviceExt->NtfsInfo.ClusterCount); FreeClusters = RtlNumberOfClearBits(&Bitmap); -- 2.17.1