From 58a13831ef75324ed2b3101c1c98ecffeabe942b Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Sun, 1 May 2016 16:21:53 +0000 Subject: [PATCH 01/71] [NTFS] Added minimal write support from CORE-10998 along with updates as suggested by CR-90. svn path=/branches/GSoC_2016/NTFS/; revision=71224 --- drivers/filesystems/ntfs/blockdev.c | 177 ++++++++++++- drivers/filesystems/ntfs/create.c | 10 +- drivers/filesystems/ntfs/mft.c | 269 +++++++++++++++++++ drivers/filesystems/ntfs/misc.c | 58 ++++ drivers/filesystems/ntfs/ntfs.h | 30 +++ drivers/filesystems/ntfs/rw.c | 396 +++++++++++++++++++++++++++- 6 files changed, 931 insertions(+), 9 deletions(-) diff --git a/drivers/filesystems/ntfs/blockdev.c b/drivers/filesystems/ntfs/blockdev.c index 6c90131b19f..1ae67e52d7f 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 *****************************************************************/ @@ -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, %u, %u, %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/create.c b/drivers/filesystems/ntfs/create.c index 4a4a6a6a8df..5662806880c 100644 --- a/drivers/filesystems/ntfs/create.c +++ b/drivers/filesystems/ntfs/create.c @@ -476,26 +476,26 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject, return Status; } - /* HUGLY HACK: remain RO so far... */ + /* HUGLY HACK: Can't overwrite or supersede a file yet... */ if (RequestedDisposition == FILE_OVERWRITE || RequestedDisposition == FILE_OVERWRITE_IF || RequestedDisposition == FILE_SUPERSEDE) { - DPRINT1("Denying write request on NTFS volume\n"); + DPRINT1("Cannot yet perform an overwrite or supersede request on NTFS volume\n"); NtfsCloseFile(DeviceExt, FileObject); return STATUS_ACCESS_DENIED; } } 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; + DPRINT1("Denying file creation request on NTFS volume\n"); + return STATUS_CANNOT_MAKE; } } diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index d02a32dc23c..43b38ef2ab1 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -24,6 +24,7 @@ * Valentin Verkhovsky * Pierre Schweitzer (pierre@reactos.org) * Hervé Poussineau (hpoussin@reactos.org) + * Trevor Thompson */ /* INCLUDES *****************************************************************/ @@ -31,6 +32,7 @@ #include "ntfs.h" #define NDEBUG +#undef NDEBUG #include /* FUNCTIONS ****************************************************************/ @@ -334,6 +336,273 @@ 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 +* +* @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) +{ + ULONGLONG LastLCN; + PUCHAR DataRun; + LONGLONG DataRunOffset; + ULONGLONG DataRunLength; + LONGLONG DataRunStartLCN; + ULONGLONG CurrentOffset; + ULONG WriteLength; + NTSTATUS Status; + PUCHAR SourceBuffer = Buffer; + LONGLONG StartingOffset; + + DPRINT("WriteAttribute(%p, %p, %I64U, %p, %lu)\n", Vcb, Context, Offset, Buffer, Length); + + // 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) + + /* 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;*/ + + return STATUS_NOT_IMPLEMENTED; // until we implement it + } + + // 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) + { + DataRun = Context->CacheRun; + LastLCN = Context->CacheRunLastLCN; + DataRunStartLCN = Context->CacheRunStartLCN; + DataRunLength = Context->CacheRunLength; + CurrentOffset = Context->CacheRunCurrentOffset; + } + else*/ + { + LastLCN = 0; + DataRun = (PUCHAR)&Context->Record + Context->Record.NonResident.MappingPairsOffset; + CurrentOffset = 0; + + 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 never be reached, the write should have already failed by now) + 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; + + if (*DataRun == 0) + { + if (Length == 0) + return STATUS_SUCCESS; + + // This code shouldn't execute, because we should have extended the allocation size + // or failed the request by now. It's just a sanity check. + DPRINT1("Encountered EOF before expected!\n"); + return STATUS_END_OF_FILE; + } + } + + // 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] + + 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, 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.h b/drivers/filesystems/ntfs/ntfs.h index 9e987c923f7..f80bb973fe6 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -550,6 +550,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 PUCHAR Buffer); + NTSTATUS NtfsReadSectors(IN PDEVICE_OBJECT DeviceObject, IN ULONG DiskSector, @@ -741,6 +748,14 @@ 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); + ULONGLONG AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord); @@ -817,6 +832,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); diff --git a/drivers/filesystems/ntfs/rw.c b/drivers/filesystems/ntfs/rw.c index 0437b61d997..076a3e85781 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 *****************************************************************/ @@ -255,14 +256,403 @@ 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 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, + PULONG LengthWritten) +{ + NTSTATUS Status = STATUS_NOT_IMPLEMENTED; + PNTFS_FCB Fcb; + PFILE_RECORD_HEADER FileRecord; + PNTFS_ATTR_CONTEXT DataContext; + ULONGLONG StreamSize; + DPRINT("NtfsWriteFile(%p, %p, %p, %u, %u, %x, %p)\n", DeviceExt, FileObject, Buffer, Length, WriteOffset, IrpFlags, 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 (in the NTFS sense of the word) with the data stream for our file + DPRINT("Finding Data Attribute...\n"); + Status = FindAttribute(DeviceExt, FileRecord, AttributeData, Fcb->Stream, wcslen(Fcb->Stream), &DataContext); + + // 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->Record); + + DPRINT("WriteOffset: %lu\tStreamSize: %I64u\n", WriteOffset, StreamSize); + + // Are we trying to write beyond the end of the stream? + if (WriteOffset + Length > StreamSize) + { + // TODO: allocate additional clusters as needed and expand stream + DPRINT1("WriteOffset: %lu\tLength: %lu\tStreamSize: %I64u\n", WriteOffset, Length, StreamSize); + DPRINT1("TODO: Stream embiggening (appending files) is not yet supported!\n"); + ReleaseAttributeContext(DataContext); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + *LengthWritten = 0; // We didn't write anything + return STATUS_ACCESS_DENIED; // temporarily; we don't change file sizes yet + } + + 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); + + // 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, + &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 */ From ea6b9622c4328b1d3c51196edf004febb12dd68b Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Sun, 12 Jun 2016 04:02:52 +0000 Subject: [PATCH 02/71] [NTFS] Add fixes to WriteAttribute(): -Remove erroneous check for end of run (before writing to the last run returned) -Properly dereference RealLengthWritten pointer, as reported in CR-90 svn path=/branches/GSoC_2016/NTFS/; revision=71616 --- drivers/filesystems/ntfs/mft.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 43b38ef2ab1..1294264e696 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -517,17 +517,6 @@ WriteAttribute(PDEVICE_EXTENSION Vcb, } else DataRunStartLCN = -1; - - if (*DataRun == 0) - { - if (Length == 0) - return STATUS_SUCCESS; - - // This code shouldn't execute, because we should have extended the allocation size - // or failed the request by now. It's just a sanity check. - DPRINT1("Encountered EOF before expected!\n"); - return STATUS_END_OF_FILE; - } } // Do we have more data to write? @@ -556,7 +545,7 @@ WriteAttribute(PDEVICE_EXTENSION Vcb, Length -= WriteLength; SourceBuffer += WriteLength; - RealLengthWritten += WriteLength; + *RealLengthWritten += WriteLength; // We finished this request, but there's still data in this data run. if (Length == 0 && WriteLength != DataRunLength * Vcb->NtfsInfo.BytesPerCluster) From ba33b9faac9457359efac140b28cc17c7f664eb4 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Wed, 22 Jun 2016 21:20:50 +0000 Subject: [PATCH 03/71] [NTFS] When writing to a file, increase the file size if trying to write past the end. *FindAttribute() has been given an optional pointer to a ULONG that will receive the offset of the found attribute from the beginning of the record. This is to allow for found attributes to be written back into their file records. +SetAttributeDataLength() +UpdateFileRecord() - Updates a file record in the master file table at a given index. +AddFixupArray() - Prepares a file record or directory index for writing to the disk. svn path=/branches/GSoC_2016/NTFS/; revision=71660 --- drivers/filesystems/ntfs/attrib.c | 20 +++- drivers/filesystems/ntfs/dirctl.c | 2 +- drivers/filesystems/ntfs/fcb.c | 4 +- drivers/filesystems/ntfs/fsctl.c | 8 +- drivers/filesystems/ntfs/mft.c | 141 ++++++++++++++++++++++++++++- drivers/filesystems/ntfs/ntfs.h | 30 +++++- drivers/filesystems/ntfs/rw.c | 48 ++++++++-- drivers/filesystems/ntfs/volinfo.c | 2 +- 8 files changed, 226 insertions(+), 29 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index 1883951c444..12474f6c813 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -150,6 +150,8 @@ static PNTFS_ATTR_RECORD InternalGetNextAttribute(PFIND_ATTR_CONTXT Context) { + PNTFS_ATTR_RECORD NextAttribute; + if (Context->CurrAttr == (PVOID)-1) { return NULL; @@ -165,7 +167,10 @@ 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); + Context->Offset += ((ULONG_PTR)NextAttribute - (ULONG_PTR)Context->CurrAttr); + Context->CurrAttr = NextAttribute; + if (Context->CurrAttr < Context->LastAttr && Context->CurrAttr->Type != AttributeEnd) { @@ -186,7 +191,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 +230,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 +254,7 @@ FindFirstAttribute(PFIND_ATTR_CONTXT Context, else { *Attribute = Context->CurrAttr; + Context->Offset = (UCHAR*)Context->CurrAttr - (UCHAR*)FileRecord; } return STATUS_SUCCESS; @@ -307,7 +316,8 @@ 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); } @@ -481,8 +491,8 @@ 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); } diff --git a/drivers/filesystems/ntfs/dirctl.c b/drivers/filesystems/ntfs/dirctl.c index 82b8a7cb1b0..aaa6dfcb87f 100644 --- a/drivers/filesystems/ntfs/dirctl.c +++ b/drivers/filesystems/ntfs/dirctl.c @@ -47,7 +47,7 @@ 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); diff --git a/drivers/filesystems/ntfs/fcb.c b/drivers/filesystems/ntfs/fcb.c index e88944c3347..111660288ab 100644 --- a/drivers/filesystems/ntfs/fcb.c +++ b/drivers/filesystems/ntfs/fcb.c @@ -568,7 +568,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; @@ -741,7 +741,7 @@ 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); diff --git a/drivers/filesystems/ntfs/fsctl.c b/drivers/filesystems/ntfs/fsctl.c index 0ce93a58eaa..a1f3048cd06 100644 --- a/drivers/filesystems/ntfs/fsctl.c +++ b/drivers/filesystems/ntfs/fsctl.c @@ -295,7 +295,7 @@ 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, NULL); if (!NT_SUCCESS(Status)) { DPRINT1("Can't find data attribute for Master File Table.\n"); @@ -333,7 +333,7 @@ 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) { @@ -374,7 +374,7 @@ 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) { @@ -804,7 +804,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 1294264e696..a53b37fb87e 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -81,13 +81,24 @@ ReleaseAttributeContext(PNTFS_ATTR_CONTEXT Context) } +/** +* @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; @@ -123,6 +134,10 @@ FindAttribute(PDEVICE_EXTENSION Vcb, /* Found it, fill up the context and return. */ DPRINT("Found context\n"); *AttrCtx = PrepareAttributeContext(Attribute); + + if (Offset != NULL) + *Offset = Context.Offset; + FindCloseAttribute(&Context); return STATUS_SUCCESS; } @@ -156,6 +171,61 @@ AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord) } +NTSTATUS +SetAttributeDataLength(PFILE_OBJECT FileObject, + PNTFS_FCB Fcb, + PNTFS_ATTR_CONTEXT AttrContext, + ULONG AttrOffset, + PFILE_RECORD_HEADER FileRecord, + PDEVICE_EXTENSION DeviceExt, + PLARGE_INTEGER DataSize) +{ + if (AttrContext->Record.IsNonResident) + { + // do we need to increase the allocation size? + if (AttrContext->Record.NonResident.AllocatedSize < DataSize->QuadPart) + { + DPRINT1("FixMe: Increasing allocation size is unimplemented!\n"); + return STATUS_NOT_IMPLEMENTED; + } + + // 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); + + AttrContext->Record.NonResident.DataSize = DataSize->QuadPart; + AttrContext->Record.NonResident.InitializedSize = DataSize->QuadPart; + + Fcb->RFCB.FileSize = *DataSize; + Fcb->RFCB.ValidDataLength = *DataSize; + + DPRINT("Data Size: %I64u\n", Fcb->RFCB.FileSize.QuadPart); + + //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord); + + // copy the attribute back into the FileRecord + RtlCopyMemory((PCHAR)FileRecord + AttrOffset, &AttrContext->Record, AttrContext->Record.Length); + + //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord); + + // write the updated file record back to disk + UpdateFileRecord(Fcb->Vcb, Fcb->MFTIndex, FileRecord); + + CcSetFileSizes(FileObject, (PCC_FILE_SIZES)&Fcb->RFCB.AllocationSize); + } + else + { + // we can't yet handle resident attributes + DPRINT1("FixMe: Can't handle increasing length of resident attribute\n"); + return STATUS_NOT_IMPLEMENTED; + } + + return STATUS_SUCCESS; +} + ULONG ReadAttribute(PDEVICE_EXTENSION Vcb, PNTFS_ATTR_CONTEXT Context, @@ -612,8 +682,39 @@ ReadFileRecord(PDEVICE_EXTENSION Vcb, return FixupUpdateSequenceArray(Vcb, &file->Ntfs); } +/** +* UpdateFileRecord +* @implemented +* Writes a file record to the master file table, at a given index. +*/ +NTSTATUS +UpdateFileRecord(PDEVICE_EXTENSION Vcb, + ULONGLONG index, + PFILE_RECORD_HEADER file) +{ + ULONG BytesWritten; + NTSTATUS Status = STATUS_SUCCESS; -NTSTATUS + DPRINT("UpdateFileRecord(%p, %I64x, %p)\n", Vcb, index, file); + + // Add the fixup array to prepare the data for writing to disk + AddFixupArray(Vcb, file); + + // 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); + } + + return Status; +} + + +NTSTATUS FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb, PNTFS_RECORD_HEADER Record) { @@ -627,6 +728,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) @@ -642,6 +745,36 @@ FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb, return STATUS_SUCCESS; } +NTSTATUS +AddFixupArray(PDEVICE_EXTENSION Vcb, + PFILE_RECORD_HEADER Record) +{ + USHORT *pShortToFixUp; + unsigned int ArrayEntryCount = Record->BytesAllocated / Vcb->NtfsInfo.BytesPerSector; + unsigned int Offset = Vcb->NtfsInfo.BytesPerSector - 2; + int i; + + PFIXUP_ARRAY fixupArray = (PFIXUP_ARRAY)((UCHAR*)Record + Record->Ntfs.UsaOffset); + + DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb, Record, fixupArray->USN, ArrayEntryCount); + + if (Record->BytesAllocated % Vcb->NtfsInfo.BytesPerSector != 0) + ArrayEntryCount++; + + fixupArray->USN++; + + for (i = 0; i < ArrayEntryCount; i++) + { + DPRINT("USN: %u\tOffset: %u\n", fixupArray->USN, Offset); + + pShortToFixUp = (USHORT*)((UCHAR*)Record + Offset); + fixupArray->Array[i] = *pShortToFixUp; + *pShortToFixUp = fixupArray->USN; + Offset += Vcb->NtfsInfo.BytesPerSector; + } + + return STATUS_SUCCESS; +} NTSTATUS ReadLCN(PDEVICE_EXTENSION Vcb, @@ -780,7 +913,7 @@ BrowseIndexEntries(PDEVICE_EXTENSION Vcb, return STATUS_OBJECT_PATH_NOT_FOUND; } - Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx); + Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL); if (!NT_SUCCESS(Status)) { DPRINT("Corrupted filesystem!\n"); @@ -850,7 +983,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); diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index f80bb973fe6..6b9a86ed07a 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -483,8 +483,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 @@ -555,7 +562,7 @@ NtfsWriteDisk(IN PDEVICE_OBJECT DeviceObject, IN LONGLONG StartingOffset, IN ULONG Length, IN ULONG SectorSize, - IN PUCHAR Buffer); + IN const PUCHAR Buffer); NTSTATUS NtfsReadSectors(IN PDEVICE_OBJECT DeviceObject, @@ -759,6 +766,15 @@ WriteAttribute(PDEVICE_EXTENSION Vcb, ULONGLONG AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord); +NTSTATUS +SetAttributeDataLength(PFILE_OBJECT FileObject, + PNTFS_FCB Fcb, + PNTFS_ATTR_CONTEXT AttrContext, + ULONG AttrOffset, + PFILE_RECORD_HEADER FileRecord, + PDEVICE_EXTENSION DeviceExt, + PLARGE_INTEGER DataSize); + ULONG AttributeAllocatedLength(PNTFS_ATTR_RECORD AttrRecord); @@ -767,13 +783,19 @@ ReadFileRecord(PDEVICE_EXTENSION Vcb, ULONGLONG index, PFILE_RECORD_HEADER file); +NTSTATUS +UpdateFileRecord(PDEVICE_EXTENSION Vcb, + ULONGLONG index, + PFILE_RECORD_HEADER file); + 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, @@ -787,6 +809,10 @@ NTSTATUS FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb, PNTFS_RECORD_HEADER Record); +NTSTATUS +AddFixupArray(PDEVICE_EXTENSION Vcb, + PFILE_RECORD_HEADER Record); + NTSTATUS ReadLCN(PDEVICE_EXTENSION Vcb, ULONGLONG lcn, diff --git a/drivers/filesystems/ntfs/rw.c b/drivers/filesystems/ntfs/rw.c index 076a3e85781..5d95300ae2f 100644 --- a/drivers/filesystems/ntfs/rw.c +++ b/drivers/filesystems/ntfs/rw.c @@ -95,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; @@ -309,6 +309,7 @@ NTSTATUS NtfsWriteFile(PDEVICE_EXTENSION DeviceExt, PNTFS_FCB Fcb; PFILE_RECORD_HEADER FileRecord; PNTFS_ATTR_CONTEXT DataContext; + ULONG AttributeOffset; ULONGLONG StreamSize; DPRINT("NtfsWriteFile(%p, %p, %p, %u, %u, %x, %p)\n", DeviceExt, FileObject, Buffer, Length, WriteOffset, IrpFlags, LengthWritten); @@ -361,9 +362,10 @@ NTSTATUS NtfsWriteFile(PDEVICE_EXTENSION DeviceExt, DPRINT("Found record for %wS\n", Fcb->ObjectName); - // Find the attribute (in the NTFS sense of the word) with the data stream for our 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); + Status = FindAttribute(DeviceExt, FileRecord, AttributeData, Fcb->Stream, wcslen(Fcb->Stream), &DataContext, + &AttributeOffset); // Did we fail to find the attribute? if (!NT_SUCCESS(Status)) @@ -405,13 +407,39 @@ NTSTATUS NtfsWriteFile(PDEVICE_EXTENSION DeviceExt, // Are we trying to write beyond the end of the stream? if (WriteOffset + Length > StreamSize) { - // TODO: allocate additional clusters as needed and expand stream - DPRINT1("WriteOffset: %lu\tLength: %lu\tStreamSize: %I64u\n", WriteOffset, Length, StreamSize); - DPRINT1("TODO: Stream embiggening (appending files) is not yet supported!\n"); - ReleaseAttributeContext(DataContext); - ExFreePoolWithTag(FileRecord, TAG_NTFS); - *LengthWritten = 0; // We didn't write anything - return STATUS_ACCESS_DENIED; // temporarily; we don't change file sizes yet + // is increasing the stream size allowed? + if (!(Fcb->Flags & FCB_IS_VOLUME) && + !(IrpFlags & IRP_PAGING_IO)) + { + LARGE_INTEGER DataSize; + ULONGLONG AllocationSize; + + DataSize.QuadPart = WriteOffset + Length; + + AllocationSize = ROUND_UP(DataSize.QuadPart, Fcb->Vcb->NtfsInfo.BytesPerCluster); + + // set the attribute data length + Status = SetAttributeDataLength(FileObject, Fcb, DataContext, AttributeOffset, FileRecord, DeviceExt, &DataSize); + + if (!NT_SUCCESS(Status)) + { + ReleaseAttributeContext(DataContext); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + *LengthWritten = 0; + return Status; + } + + // now we need to update this file's size in every directory index entry that references it + // (saved for a later commit) + } + 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); diff --git a/drivers/filesystems/ntfs/volinfo.c b/drivers/filesystems/ntfs/volinfo.c index ddf7f6c58dd..13a3b7af7ff 100644 --- a/drivers/filesystems/ntfs/volinfo.c +++ b/drivers/filesystems/ntfs/volinfo.c @@ -62,7 +62,7 @@ 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); From 4f8133f44b49a9caef0aecd0baa1394dce0dfc0e Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Thu, 23 Jun 2016 15:37:19 +0000 Subject: [PATCH 04/71] [NTFS] Fix Up AddFixupArray - It needs to accept a PNTFS_RECORD_HEADER for parameter 2, not a PFILE_RECORD_HEADER. svn path=/branches/GSoC_2016/NTFS/; revision=71662 --- drivers/filesystems/ntfs/mft.c | 13 +++++-------- drivers/filesystems/ntfs/ntfs.h | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index a53b37fb87e..7017ab76957 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -698,7 +698,7 @@ UpdateFileRecord(PDEVICE_EXTENSION Vcb, DPRINT("UpdateFileRecord(%p, %I64x, %p)\n", Vcb, index, file); // Add the fixup array to prepare the data for writing to disk - AddFixupArray(Vcb, file); + AddFixupArray(Vcb, &file->Ntfs); // 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); @@ -747,27 +747,24 @@ FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb, NTSTATUS AddFixupArray(PDEVICE_EXTENSION Vcb, - PFILE_RECORD_HEADER Record) + PNTFS_RECORD_HEADER Record) { USHORT *pShortToFixUp; - unsigned int ArrayEntryCount = Record->BytesAllocated / Vcb->NtfsInfo.BytesPerSector; + unsigned int ArrayEntryCount = Record->UsaCount - 1; unsigned int Offset = Vcb->NtfsInfo.BytesPerSector - 2; int i; - PFIXUP_ARRAY fixupArray = (PFIXUP_ARRAY)((UCHAR*)Record + Record->Ntfs.UsaOffset); + 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); - if (Record->BytesAllocated % Vcb->NtfsInfo.BytesPerSector != 0) - ArrayEntryCount++; - fixupArray->USN++; for (i = 0; i < ArrayEntryCount; i++) { DPRINT("USN: %u\tOffset: %u\n", fixupArray->USN, Offset); - pShortToFixUp = (USHORT*)((UCHAR*)Record + Offset); + pShortToFixUp = (USHORT*)((PCHAR)Record + Offset); fixupArray->Array[i] = *pShortToFixUp; *pShortToFixUp = fixupArray->USN; Offset += Vcb->NtfsInfo.BytesPerSector; diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index 6b9a86ed07a..9b12a60de84 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -811,7 +811,7 @@ FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb, NTSTATUS AddFixupArray(PDEVICE_EXTENSION Vcb, - PFILE_RECORD_HEADER Record); + PNTFS_RECORD_HEADER Record); NTSTATUS ReadLCN(PDEVICE_EXTENSION Vcb, From a7a2c0d734a447b66d9cc5daadb46845dd08327b Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Thu, 23 Jun 2016 18:02:03 +0000 Subject: [PATCH 05/71] [NTFS] Update a file's size in the relevant $FILE_NAME attribute of the index entry in the parent directory. +UpdateFileNameRecord() - Searches a parent directory for the proper index entry, then updates the file sizes in that entry. +UpdateIndexEntryFileNameSize() - Recursively searches directory index and applies the size update. svn path=/branches/GSoC_2016/NTFS/; revision=71664 --- drivers/filesystems/ntfs/mft.c | 206 ++++++++++++++++++++++++++++++++ drivers/filesystems/ntfs/ntfs.h | 27 +++++ drivers/filesystems/ntfs/rw.c | 17 ++- 3 files changed, 249 insertions(+), 1 deletion(-) diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 7017ab76957..5d61f534ddf 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -682,6 +682,212 @@ ReadFileRecord(PDEVICE_EXTENSION Vcb, return FixupUpdateSequenceArray(Vcb, &file->Ntfs); } + +/** +* 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) +{ + 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, %u, %I64u, %I64u)\n", Vcb, ParentMFTIndex, FileName, DirSearch, NewDataSize, NewAllocationSize); + + 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; + } + + ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, Vcb->NtfsInfo.BytesPerIndexRecord); + 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); + + 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) +{ + NTSTATUS Status; + ULONG RecordOffset; + PINDEX_ENTRY_ATTRIBUTE IndexEntry; + PNTFS_ATTR_CONTEXT IndexAllocationCtx; + ULONGLONG IndexAllocationSize; + PINDEX_BUFFER IndexBuffer; + + DPRINT("UpdateIndexEntrySize(%p, %p, %p, %u, %p, %p, %wZ, %u, %u, %u, %I64u, %I64u)\n", Vcb, MftRecord, IndexRecord, IndexBlockSize, FirstEntry, LastEntry, FileName, *StartEntry, *CurrentEntry, DirSearch, NewDataSize, NewAllocatedSize); + + // 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) > 0x10 && + *CurrentEntry >= *StartEntry && + IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS && + CompareFileName(FileName, IndexEntry, DirSearch)) + { + *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->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 = UpdateIndexEntryFileNameSize(NULL, NULL, NULL, 0, FirstEntry, LastEntry, FileName, StartEntry, CurrentEntry, DirSearch, NewDataSize, NewAllocatedSize); + 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); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR Performing write!\n"); + break; + } + + Status = STATUS_SUCCESS; + break; + } + if (NT_SUCCESS(Status)) + { + break; + } + } + + ReleaseAttributeContext(IndexAllocationCtx); + return Status; +} + /** * UpdateFileRecord * @implemented diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index 9b12a60de84..6ff5b838003 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -778,11 +778,38 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, ULONG AttributeAllocatedLength(PNTFS_ATTR_RECORD AttrRecord); +BOOLEAN +CompareFileName(PUNICODE_STRING FileName, + PINDEX_ENTRY_ATTRIBUTE IndexEntry, + BOOLEAN DirSearch); + 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); + +NTSTATUS +UpdateFileNameRecord(PDEVICE_EXTENSION Vcb, + ULONGLONG ParentMFTIndex, + PUNICODE_STRING FileName, + BOOLEAN DirSearch, + ULONGLONG NewDataSize, + ULONGLONG NewAllocationSize); + NTSTATUS UpdateFileRecord(PDEVICE_EXTENSION Vcb, ULONGLONG index, diff --git a/drivers/filesystems/ntfs/rw.c b/drivers/filesystems/ntfs/rw.c index 5d95300ae2f..9d111896314 100644 --- a/drivers/filesystems/ntfs/rw.c +++ b/drivers/filesystems/ntfs/rw.c @@ -413,6 +413,9 @@ NTSTATUS NtfsWriteFile(PDEVICE_EXTENSION DeviceExt, { LARGE_INTEGER DataSize; ULONGLONG AllocationSize; + PFILENAME_ATTRIBUTE fileNameAttribute; + ULONGLONG ParentMFTId; + UNICODE_STRING filename; DataSize.QuadPart = WriteOffset + Length; @@ -430,7 +433,19 @@ NTSTATUS NtfsWriteFile(PDEVICE_EXTENSION DeviceExt, } // now we need to update this file's size in every directory index entry that references it - // (saved for a later commit) + // 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); + } else { From f47efca89f47655cf845bfe7f8a84faba40f4b05 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Sun, 26 Jun 2016 17:03:31 +0000 Subject: [PATCH 06/71] [NTFS] Remove unused parameter from SetAttributeDataLength. svn path=/branches/GSoC_2016/NTFS/; revision=71677 --- drivers/filesystems/ntfs/mft.c | 1 - drivers/filesystems/ntfs/ntfs.h | 1 - drivers/filesystems/ntfs/rw.c | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 5d61f534ddf..ae2a6cc2baf 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -177,7 +177,6 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, PNTFS_ATTR_CONTEXT AttrContext, ULONG AttrOffset, PFILE_RECORD_HEADER FileRecord, - PDEVICE_EXTENSION DeviceExt, PLARGE_INTEGER DataSize) { if (AttrContext->Record.IsNonResident) diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index 6ff5b838003..4435b4866e6 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -772,7 +772,6 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, PNTFS_ATTR_CONTEXT AttrContext, ULONG AttrOffset, PFILE_RECORD_HEADER FileRecord, - PDEVICE_EXTENSION DeviceExt, PLARGE_INTEGER DataSize); ULONG diff --git a/drivers/filesystems/ntfs/rw.c b/drivers/filesystems/ntfs/rw.c index 9d111896314..5afb87e9e60 100644 --- a/drivers/filesystems/ntfs/rw.c +++ b/drivers/filesystems/ntfs/rw.c @@ -422,7 +422,7 @@ NTSTATUS NtfsWriteFile(PDEVICE_EXTENSION DeviceExt, AllocationSize = ROUND_UP(DataSize.QuadPart, Fcb->Vcb->NtfsInfo.BytesPerCluster); // set the attribute data length - Status = SetAttributeDataLength(FileObject, Fcb, DataContext, AttributeOffset, FileRecord, DeviceExt, &DataSize); + Status = SetAttributeDataLength(FileObject, Fcb, DataContext, AttributeOffset, FileRecord, &DataSize); if (!NT_SUCCESS(Status)) { From 84a1280fd6a4572894bbb70fac70c455958e6d02 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Sun, 26 Jun 2016 21:06:02 +0000 Subject: [PATCH 07/71] [NTFS] Allow for an existing file to be opened with FILE_OVERWRITE, FILE_OVERWRITE_IF, or FILE_SUPERSEDE dispositions, and truncate that file. This allows for a file to be opened and saved in Notepad.exe [provided that file is non-resident and its allocation size doesn't need to change]. svn path=/branches/GSoC_2016/NTFS/; revision=71680 --- drivers/filesystems/ntfs/create.c | 60 ++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/drivers/filesystems/ntfs/create.c b/drivers/filesystems/ntfs/create.c index 5662806880c..344c16464ff 100644 --- a/drivers/filesystems/ntfs/create.c +++ b/drivers/filesystems/ntfs/create.c @@ -476,14 +476,66 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject, return Status; } - /* HUGLY HACK: Can't overwrite or supersede a file yet... */ if (RequestedDisposition == FILE_OVERWRITE || RequestedDisposition == FILE_OVERWRITE_IF || RequestedDisposition == FILE_SUPERSEDE) { - DPRINT1("Cannot yet perform an overwrite or supersede 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; + + // 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) + ExFreePool(fileRecord); + 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 From 77fc65dc0e4f7b08cec056bb3f017c70036748f6 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Wed, 29 Jun 2016 16:35:36 +0000 Subject: [PATCH 08/71] [NTFS] Lay some groundwork for extending allocation size. +AddRun() - Unimplemented +GetLastClusterInDataRun() +NtfsAllocateClusters() svn path=/branches/GSoC_2016/NTFS/; revision=71696 --- drivers/filesystems/ntfs/attrib.c | 57 ++++++++++++++++ drivers/filesystems/ntfs/mft.c | 48 +++++++++++-- drivers/filesystems/ntfs/ntfs.h | 19 +++++- drivers/filesystems/ntfs/volinfo.c | 104 +++++++++++++++++++++++++++++ 4 files changed, 223 insertions(+), 5 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index 12474f6c813..9608d1d17be 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -35,6 +35,19 @@ /* FUNCTIONS ****************************************************************/ +NTSTATUS +AddRun(PNTFS_ATTR_CONTEXT AttrContext, + ULONGLONG NextAssignedCluster, + ULONG RunLength) +{ + UNIMPLEMENTED; + + if (!AttrContext->Record.IsNonResident) + return STATUS_INVALID_PARAMETER; + + return STATUS_NOT_IMPLEMENTED; +} + PUCHAR DecodeRun(PUCHAR DataRun, LONGLONG *DataRunOffset, @@ -551,6 +564,50 @@ GetFileNameFromRecord(PDEVICE_EXTENSION Vcb, return NULL; } +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) + { + // sparse run + if (*DataRun == 0) + { + // if it's the last run, return the last cluster of the last run + *LastCluster = LastLCN + DataRunLength - 1; + break; + } + } + else + { + // Normal data run. + DataRunStartLCN = LastLCN + DataRunOffset; + LastLCN = DataRunStartLCN; + } + + if (*DataRun == 0) + { + *LastCluster = LastLCN + DataRunLength - 1; + break; + } + } + + return STATUS_SUCCESS; +} + PSTANDARD_INFORMATION GetStandardInformationFromRecord(PDEVICE_EXTENSION Vcb, PFILE_RECORD_HEADER FileRecord) diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index ae2a6cc2baf..e1725f9a50e 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -151,7 +151,7 @@ FindAttribute(PDEVICE_EXTENSION Vcb, } -ULONG +ULONGLONG AttributeAllocatedLength(PNTFS_ATTR_RECORD AttrRecord) { if (AttrRecord->IsNonResident) @@ -181,9 +181,49 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, { if (AttrContext->Record.IsNonResident) { + ULONG BytesPerCluster = Fcb->Vcb->NtfsInfo.BytesPerCluster; + ULONGLONG AllocationSize = ROUND_UP(DataSize->QuadPart, BytesPerCluster); + // do we need to increase the allocation size? - if (AttrContext->Record.NonResident.AllocatedSize < DataSize->QuadPart) - { + if (AttrContext->Record.NonResident.AllocatedSize < AllocationSize) + { + ULONG ExistingClusters = AttrContext->Record.NonResident.AllocatedSize / BytesPerCluster; + ULONG ClustersNeeded = (AllocationSize / BytesPerCluster) - ExistingClusters; + LARGE_INTEGER LastClusterInDataRun; + ULONG NextAssignedCluster; + ULONG AssignedClusters; + + NTSTATUS Status = GetLastClusterInDataRun(Fcb->Vcb, &AttrContext->Record, &LastClusterInDataRun.QuadPart); + + DPRINT1("GetLastClusterInDataRun returned: %I64u\n", LastClusterInDataRun.QuadPart); + DPRINT1("Highest VCN of record: %I64u\n", AttrContext->Record.NonResident.HighestVCN); + + while (ClustersNeeded > 0) + { + Status = NtfsAllocateClusters(Fcb->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(AttrContext, NextAssignedCluster, AssignedClusters); + if (!NT_SUCCESS(Status)) + { + DPRINT1("Error: Unable to add data run!\n"); + return Status; + } + + ClustersNeeded -= AssignedClusters; + LastClusterInDataRun.LowPart = NextAssignedCluster + AssignedClusters - 1; + } + DPRINT1("FixMe: Increasing allocation size is unimplemented!\n"); return STATUS_NOT_IMPLEMENTED; } @@ -459,7 +499,7 @@ WriteAttribute(PDEVICE_EXTENSION Vcb, PUCHAR SourceBuffer = Buffer; LONGLONG StartingOffset; - DPRINT("WriteAttribute(%p, %p, %I64U, %p, %lu)\n", Vcb, Context, Offset, Buffer, Length); + DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p)\n", Vcb, Context, Offset, Buffer, Length, RealLengthWritten); // is this a resident attribute? if (!Context->Record.IsNonResident) diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index 4435b4866e6..66cb37f4bd1 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -511,6 +511,11 @@ NtfsMarkIrpContextForQueue(PNTFS_IRP_CONTEXT IrpContext) //VOID //NtfsDumpAttribute(PATTRIBUTE Attribute); +NTSTATUS +AddRun(PNTFS_ATTR_CONTEXT AttrContext, + ULONGLONG NextAssignedCluster, + ULONG RunLength); + PUCHAR DecodeRun(PUCHAR DataRun, LONGLONG *DataRunOffset, @@ -529,6 +534,11 @@ GetFileNameFromRecord(PDEVICE_EXTENSION Vcb, PFILE_RECORD_HEADER FileRecord, UCHAR NameType); +NTSTATUS +GetLastClusterInDataRun(PDEVICE_EXTENSION Vcb, + PNTFS_ATTR_RECORD Attribute, + PULONGLONG LastCluster); + PFILENAME_ATTRIBUTE GetBestFileNameFromRecord(PDEVICE_EXTENSION Vcb, PFILE_RECORD_HEADER FileRecord); @@ -774,7 +784,7 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, PFILE_RECORD_HEADER FileRecord, PLARGE_INTEGER DataSize); -ULONG +ULONGLONG AttributeAllocatedLength(PNTFS_ATTR_RECORD AttrRecord); BOOLEAN @@ -915,6 +925,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/volinfo.c b/drivers/filesystems/ntfs/volinfo.c index 13a3b7af7ff..c8935621894 100644 --- a/drivers/filesystems/ntfs/volinfo.c +++ b/drivers/filesystems/ntfs/volinfo.c @@ -99,6 +99,110 @@ 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; + PCHAR BitmapData; + ULONGLONG FreeClusters = 0; + ULONG Read = 0; + RTL_BITMAP Bitmap; + + DPRINT1("NtfsAllocateClusters(%p, %lu, %lu, %p)\n", DeviceExt, DesiredClusters, FirstDesiredCluster, FirstAssignedCluster, AssignedClusters); + + BitmapRecord = ExAllocatePoolWithTag(NonPagedPool, + DeviceExt->NtfsInfo.BytesPerFileRecord, + TAG_NTFS); + if (BitmapRecord == NULL) + { + return 0; + } + + Status = ReadFileRecord(DeviceExt, NTFS_FILE_BITMAP, BitmapRecord); + if (!NT_SUCCESS(Status)) + { + ExFreePoolWithTag(BitmapRecord, TAG_NTFS); + return 0; + } + + Status = FindAttribute(DeviceExt, BitmapRecord, AttributeData, L"", 0, &DataContext, NULL); + if (!NT_SUCCESS(Status)) + { + ExFreePoolWithTag(BitmapRecord, TAG_NTFS); + return 0; + } + + BitmapDataSize = AttributeDataLength(&DataContext->Record); + 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 0; + } + + 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, Read, (PCHAR)((ULONG_PTR)BitmapData + Read), (ULONG)BitmapDataSize); + + RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, DeviceExt->NtfsInfo.ClusterCount); + FreeClusters = RtlNumberOfClearBits(&Bitmap); + + if (FreeClusters >= DesiredClusters) + { + // TODO: Observe MFT reservation zone + + // Can we get one contiguous run? + ULONG AssignedRun = RtlFindClearBitsAndSet(&Bitmap, DesiredClusters, FirstDesiredCluster); + ULONG LengthWritten; + + 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); + } + else + Status = STATUS_DISK_FULL; + + + ReleaseAttributeContext(DataContext); + + ExFreePoolWithTag(BitmapData, TAG_NTFS); + ExFreePoolWithTag(BitmapRecord, TAG_NTFS); + + return Status; +} + static NTSTATUS NtfsGetFsVolumeInformation(PDEVICE_OBJECT DeviceObject, From 25fdabd8ca59cde07d3b873183d817db8f17ed30 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Wed, 29 Jun 2016 17:49:36 +0000 Subject: [PATCH 09/71] [NTFS] Fix typo in NtfsAllocateClusters(), spotted by ThFabba. svn path=/branches/GSoC_2016/NTFS/; revision=71697 --- drivers/filesystems/ntfs/volinfo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/filesystems/ntfs/volinfo.c b/drivers/filesystems/ntfs/volinfo.c index c8935621894..df3af4989a0 100644 --- a/drivers/filesystems/ntfs/volinfo.c +++ b/drivers/filesystems/ntfs/volinfo.c @@ -119,7 +119,7 @@ NtfsAllocateClusters(PDEVICE_EXTENSION DeviceExt, ULONG Read = 0; RTL_BITMAP Bitmap; - DPRINT1("NtfsAllocateClusters(%p, %lu, %lu, %p)\n", DeviceExt, DesiredClusters, FirstDesiredCluster, FirstAssignedCluster, AssignedClusters); + DPRINT1("NtfsAllocateClusters(%p, %lu, %lu, %p, %p)\n", DeviceExt, FirstDesiredCluster, DesiredClusters, FirstAssignedCluster, AssignedClusters); BitmapRecord = ExAllocatePoolWithTag(NonPagedPool, DeviceExt->NtfsInfo.BytesPerFileRecord, From 760cdfb5aa5043c49c30bec6066f682df9e12d28 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Tue, 5 Jul 2016 07:00:43 +0000 Subject: [PATCH 10/71] [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 | 197 ++++++++++++++++++++++++----- drivers/filesystems/ntfs/ntfs.h | 5 + drivers/filesystems/ntfs/volinfo.c | 3 +- 4 files changed, 174 insertions(+), 33 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; - - DPRINT("Data Size: %I64u\n", Fcb->RFCB.FileSize.QuadPart); - - //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord); - - // copy the attribute back into the FileRecord + // copy the attribute record back into the FileRecord RtlCopyMemory((PCHAR)FileRecord + AttrOffset, &AttrContext->Record, AttrContext->Record.Length); - - //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord); - - // write the updated file record back to disk - UpdateFileRecord(Fcb->Vcb, Fcb->MFTIndex, FileRecord); - - CcSetFileSizes(FileObject, (PCC_FILE_SIZES)&Fcb->RFCB.AllocationSize); } else { - // we can't yet handle resident attributes - DPRINT1("FixMe: Can't handle increasing length of resident attribute\n"); - return STATUS_NOT_IMPLEMENTED; + // resident attribute + + // find the next attribute + ULONG NextAttributeOffset = AttrOffset + AttrContext->Record.Length; + PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((PCHAR)FileRecord + NextAttributeOffset); + + //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); + + 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; + } + } + + InternalSetResidentAttributeLength(AttrContext, FileRecord, AttrOffset, DataSize->LowPart); + } + + //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord); + + // write the updated file record back to disk + Status = UpdateFileRecord(Fcb->Vcb, Fcb->MFTIndex, FileRecord); + + if (NT_SUCCESS(Status)) + { + 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; + } - return STATUS_NOT_IMPLEMENTED; // until we implement it + 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); + + // 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); From 7eb1264f5f184623f230832cf6aecfa5f362e0bd Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Wed, 6 Jul 2016 07:57:57 +0000 Subject: [PATCH 11/71] [NTFS] Fix copy-paste error in SetAttributeDataLength() svn path=/branches/GSoC_2016/NTFS/; revision=71832 --- drivers/filesystems/ntfs/mft.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 4e10584ce0d..a5111c18ee4 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -228,9 +228,9 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, NTSTATUS Status = STATUS_SUCCESS; // are we truncating the file? - if (DataSize->QuadPart < AttributeDataLength(&AttrContext->Record) + if (DataSize->QuadPart < AttributeDataLength(&AttrContext->Record)) { - if (!MmCanFileBeTruncated(FileObject->SectionObjectPointer, (PLARGE_INTEGER)&AllocationSize)) + if (!MmCanFileBeTruncated(FileObject->SectionObjectPointer, DataSize)) { DPRINT1("Can't truncate a memory-mapped file!\n"); return STATUS_USER_MAPPED_FILE; From c08d37d182408f69533644257ee8cc10e6a16f6b Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Thu, 7 Jul 2016 08:17:07 +0000 Subject: [PATCH 12/71] [NTFS] Simplify and optimize InternalSetResidentAttributeLength(), as suggested by Pierre. svn path=/branches/GSoC_2016/NTFS/; revision=71837 --- drivers/filesystems/ntfs/mft.c | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index a5111c18ee4..aa203bbe9d2 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -178,43 +178,37 @@ InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext, ULONG AttrOffset, ULONG DataSize) { - ULONG EndMarker = AttributeEnd; - ULONG FinalMarker = FILE_RECORD_END; + PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset); 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)); + AttrContext->Record.Resident.ValueLength = + Destination->Resident.ValueLength = DataSize; // calculate the record length and end marker offset - AttrContext->Record.Length = DataSize + AttrContext->Record.Resident.ValueOffset; + AttrContext->Record.Length = + Destination->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); + USHORT Padding = 8 - (NextAttributeOffset % 8); NextAttributeOffset += Padding; AttrContext->Record.Length += Padding; + Destination->Length += Padding; } + + // advance Destination to the final "attribute" and write the end type + (ULONG_PTR)Destination += Destination->Length; + Destination->Type = AttributeEnd; - // update the record length - Offset = AttrOffset + FIELD_OFFSET(NTFS_ATTR_RECORD, Length); - RtlCopyMemory((PCHAR)FileRecord + Offset, &AttrContext->Record.Length, sizeof(ULONG)); + // write the final marker (which shares the same offset and type as the Length field) + Destination->Length = FILE_RECORD_END; - // 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); + FileRecord->BytesInUse = NextAttributeOffset + (sizeof(ULONG) * 2); } NTSTATUS From 7a6e9bcdf62e24411780c03d565097c5051fc06b Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Fri, 8 Jul 2016 11:59:25 +0000 Subject: [PATCH 13/71] [NTFS] Simplify GetLastClusterInDataRun() and clean up NtfsAllocateClusters(). No functional changes. svn path=/branches/GSoC_2016/NTFS/; revision=71857 --- drivers/filesystems/ntfs/attrib.c | 18 ++-------- drivers/filesystems/ntfs/volinfo.c | 55 ++++++++++++++---------------- 2 files changed, 29 insertions(+), 44 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index e8625903bc4..80022f59573 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -583,28 +583,16 @@ GetLastClusterInDataRun(PDEVICE_EXTENSION Vcb, PNTFS_ATTR_RECORD Attribute, PULO { DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength); - if (DataRunOffset == -1) - { - // sparse run - if (*DataRun == 0) - { - // if it's the last run, return the last cluster of the last run - *LastCluster = LastLCN + DataRunLength - 1; - break; - } - } - else + if (DataRunOffset != -1) { // Normal data run. DataRunStartLCN = LastLCN + DataRunOffset; LastLCN = DataRunStartLCN; + *LastCluster = LastLCN + DataRunLength - 1; } - if (*DataRun == 0) - { - *LastCluster = LastLCN + DataRunLength - 1; + if (*DataRun == 0) break; - } } return STATUS_SUCCESS; diff --git a/drivers/filesystems/ntfs/volinfo.c b/drivers/filesystems/ntfs/volinfo.c index 0a54cf73b83..68a10506207 100644 --- a/drivers/filesystems/ntfs/volinfo.c +++ b/drivers/filesystems/ntfs/volinfo.c @@ -162,38 +162,35 @@ NtfsAllocateClusters(PDEVICE_EXTENSION DeviceExt, RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, DeviceExt->NtfsInfo.ClusterCount); FreeClusters = RtlNumberOfClearBits(&Bitmap); - if (FreeClusters >= DesiredClusters) + if( FreeClusters < DesiredClusters ) + Status = STATUS_DISK_FULL; + + // TODO: Observe MFT reservation zone + + // Can we get one contiguous run? + ULONG AssignedRun = RtlFindClearBitsAndSet(&Bitmap, DesiredClusters, FirstDesiredCluster); + ULONG LengthWritten; + + if (AssignedRun != 0xFFFFFFFF) { - // TODO: Observe MFT reservation zone - - // Can we get one contiguous run? - ULONG AssignedRun = RtlFindClearBitsAndSet(&Bitmap, DesiredClusters, FirstDesiredCluster); - ULONG LengthWritten; - - 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); + *FirstAssignedCluster = AssignedRun; + *AssignedClusters = DesiredClusters; } else - Status = STATUS_DISK_FULL; - - + { + // 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); + ReleaseAttributeContext(DataContext); ExFreePoolWithTag(BitmapData, TAG_NTFS); From de5cff4b3753f1e3db60595b0b757638a904d7c2 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Fri, 8 Jul 2016 12:05:19 +0000 Subject: [PATCH 14/71] [NTFS] Fix broken check for free disk space in NtfsAllocateClusters(). svn path=/branches/GSoC_2016/NTFS/; revision=71858 --- drivers/filesystems/ntfs/volinfo.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/filesystems/ntfs/volinfo.c b/drivers/filesystems/ntfs/volinfo.c index 68a10506207..d1505a5fb24 100644 --- a/drivers/filesystems/ntfs/volinfo.c +++ b/drivers/filesystems/ntfs/volinfo.c @@ -162,8 +162,14 @@ NtfsAllocateClusters(PDEVICE_EXTENSION DeviceExt, RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, DeviceExt->NtfsInfo.ClusterCount); FreeClusters = RtlNumberOfClearBits(&Bitmap); - if( FreeClusters < DesiredClusters ) - Status = STATUS_DISK_FULL; + if (FreeClusters < DesiredClusters) + { + ReleaseAttributeContext(DataContext); + + ExFreePoolWithTag(BitmapData, TAG_NTFS); + ExFreePoolWithTag(BitmapRecord, TAG_NTFS); + return STATUS_DISK_FULL; + } // TODO: Observe MFT reservation zone From 7c576a836f635bc1a07fc7f310690a0424051382 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Tue, 12 Jul 2016 09:57:16 +0000 Subject: [PATCH 15/71] [NTFS] Add error-checking to InternalGetNextAttribute(); don't crash if CurrAttr->Length is invalid. svn path=/branches/GSoC_2016/NTFS/; revision=71897 --- drivers/filesystems/ntfs/attrib.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index 80022f59573..fc2463fee57 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -181,6 +181,14 @@ InternalGetNextAttribute(PFIND_ATTR_CONTXT Context) } 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; From 268a139e6386a6c38302d30d9a9289e07dd4ebc6 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Wed, 13 Jul 2016 09:26:04 +0000 Subject: [PATCH 16/71] [NTFS] UpdateFileRecord() - Remove fixup array before returning. svn path=/branches/GSoC_2016/NTFS/; revision=71920 --- drivers/filesystems/ntfs/mft.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index aa203bbe9d2..3ef8291a738 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -1084,6 +1084,9 @@ UpdateFileRecord(PDEVICE_EXTENSION Vcb, DPRINT1("UpdateFileRecord failed: %I64u written, %u expected\n", BytesWritten, Vcb->NtfsInfo.BytesPerFileRecord); } + // remove the fixup array (so the file record pointer can still be used) + FixupUpdateSequenceArray(Vcb, file); + return Status; } From 63e83c7ffc1f9cbde675cb2165861451e055598f Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Wed, 13 Jul 2016 11:51:16 +0000 Subject: [PATCH 17/71] [NTFS] Add support functions, fix warning from previous commit. +NtfsDumpDataRuns(), +NtfsDumpDataRunData() - Provide diagnostic output. +GetPackedByteCount() - Used to encode data runs. svn path=/branches/GSoC_2016/NTFS/; revision=71921 --- drivers/filesystems/ntfs/attrib.c | 136 ++++++++++++++++++++++++++++++ drivers/filesystems/ntfs/mft.c | 2 +- drivers/filesystems/ntfs/ntfs.h | 8 ++ 3 files changed, 145 insertions(+), 1 deletion(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index fc2463fee57..f34072d8a5c 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -523,6 +523,72 @@ NtfsDumpAttribute(PDEVICE_EXTENSION Vcb, } +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) @@ -574,6 +640,76 @@ 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) +{ + int bytes = 0; + 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; + return 1; + } + 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; + } + return bytes; +} + NTSTATUS GetLastClusterInDataRun(PDEVICE_EXTENSION Vcb, PNTFS_ATTR_RECORD Attribute, PULONGLONG LastCluster) { diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 3ef8291a738..c7096cc5d8f 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -1085,7 +1085,7 @@ UpdateFileRecord(PDEVICE_EXTENSION Vcb, } // remove the fixup array (so the file record pointer can still be used) - FixupUpdateSequenceArray(Vcb, file); + FixupUpdateSequenceArray(Vcb, &file->Ntfs); return Status; } diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index 238e6d5b2d4..f16153012e0 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -526,6 +526,10 @@ DecodeRun(PUCHAR DataRun, LONGLONG *DataRunOffset, ULONGLONG *DataRunLength); +VOID +NtfsDumpDataRuns(PVOID StartOfRun, + ULONGLONG CurrentLCN); + VOID NtfsDumpFileAttributes(PDEVICE_EXTENSION Vcb, PFILE_RECORD_HEADER FileRecord); @@ -539,6 +543,10 @@ 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, From a135ef5864a205c8f75fa367cf3d0f0964a380ba Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Wed, 13 Jul 2016 14:49:46 +0000 Subject: [PATCH 18/71] [NTFS] Various minor changes to fix GCC build. svn path=/branches/GSoC_2016/NTFS/; revision=71922 --- drivers/filesystems/ntfs/mft.c | 4 ++-- drivers/filesystems/ntfs/volinfo.c | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index c7096cc5d8f..6263263dc8c 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -202,7 +202,7 @@ InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext, } // advance Destination to the final "attribute" and write the end type - (ULONG_PTR)Destination += Destination->Length; + Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)Destination + Destination->Length); Destination->Type = AttributeEnd; // write the final marker (which shares the same offset and type as the Length field) @@ -245,7 +245,7 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, ULONG NextAssignedCluster; ULONG AssignedClusters; - NTSTATUS Status = GetLastClusterInDataRun(Fcb->Vcb, &AttrContext->Record, &LastClusterInDataRun.QuadPart); + NTSTATUS Status = GetLastClusterInDataRun(Fcb->Vcb, &AttrContext->Record, (PULONGLONG)&LastClusterInDataRun.QuadPart); DPRINT1("GetLastClusterInDataRun returned: %I64u\n", LastClusterInDataRun.QuadPart); DPRINT1("Highest VCN of record: %I64u\n", AttrContext->Record.NonResident.HighestVCN); diff --git a/drivers/filesystems/ntfs/volinfo.c b/drivers/filesystems/ntfs/volinfo.c index d1505a5fb24..338ea847509 100644 --- a/drivers/filesystems/ntfs/volinfo.c +++ b/drivers/filesystems/ntfs/volinfo.c @@ -114,9 +114,11 @@ NtfsAllocateClusters(PDEVICE_EXTENSION DeviceExt, PFILE_RECORD_HEADER BitmapRecord; PNTFS_ATTR_CONTEXT DataContext; ULONGLONG BitmapDataSize; - PCHAR BitmapData; + 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); @@ -174,8 +176,7 @@ NtfsAllocateClusters(PDEVICE_EXTENSION DeviceExt, // TODO: Observe MFT reservation zone // Can we get one contiguous run? - ULONG AssignedRun = RtlFindClearBitsAndSet(&Bitmap, DesiredClusters, FirstDesiredCluster); - ULONG LengthWritten; + AssignedRun = RtlFindClearBitsAndSet(&Bitmap, DesiredClusters, FirstDesiredCluster); if (AssignedRun != 0xFFFFFFFF) { From afe40eb054267a365cc6ca8142f48870a49839ea Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Thu, 14 Jul 2016 15:20:48 +0000 Subject: [PATCH 19/71] [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 f34072d8a5c..3cf40d83411 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 6263263dc8c..7b3dcb8d379 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 f16153012e0..bc6a8d90897 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 5afb87e9e60..4db1c388153 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. From d09e1398f83d71a7940ed02ee68e63fff2756cbc Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Fri, 15 Jul 2016 15:27:04 +0000 Subject: [PATCH 20/71] [NTFS] *AddRun() - Don't leak RunBuffer when encountering errors. Handle exception from FsRtlAddLargeMcbEntry(). svn path=/branches/GSoC_2016/NTFS/; revision=71945 --- drivers/filesystems/ntfs/attrib.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index 3cf40d83411..61d8c8ab8f2 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -88,25 +88,40 @@ AddRun(PNTFS_VCB Vcb, ULONGLONG NextVBN = AttrContext->Record.NonResident.LowestVCN; // Allocate some memory for the RunBuffer - PUCHAR RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS); + PUCHAR RunBuffer; int RunBufferOffset = 0; if (!AttrContext->Record.IsNonResident) return STATUS_INVALID_PARAMETER; + RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS); + // 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"); + ExFreePoolWithTag(RunBuffer, TAG_NTFS); return Status; } // Add newly-assigned clusters to mcb - FsRtlAddLargeMcbEntry(&DataRunsMCB, - NextVBN, - NextAssignedCluster, - RunLength); + _SEH2_TRY{ + if (!FsRtlAddLargeMcbEntry(&DataRunsMCB, + NextVBN, + NextAssignedCluster, + RunLength)) + { + FsRtlUninitializeLargeMcb(&DataRunsMCB); + ExFreePoolWithTag(RunBuffer, TAG_NTFS); + return STATUS_INSUFFICIENT_RESOURCES; + } + } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + FsRtlUninitializeLargeMcb(&DataRunsMCB); + ExFreePoolWithTag(RunBuffer, TAG_NTFS); + _SEH2_YIELD(return STATUS_INSUFFICIENT_RESOURCES); + } _SEH2_END; + // Convert the map control block back to encoded data runs ConvertLargeMCBToDataRuns(&DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferOffset); From ed1b4bf9bc62b44408fda66f3cbfb427b2e5cdab Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Sun, 17 Jul 2016 14:04:01 +0000 Subject: [PATCH 21/71] [NTFS] Address some minor issues with attrib.c: Fix gcc build. Fix formatting. svn path=/branches/GSoC_2016/NTFS/; revision=71957 --- drivers/filesystems/ntfs/attrib.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index 61d8c8ab8f2..e0c41fa0e49 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -89,7 +89,7 @@ AddRun(PNTFS_VCB Vcb, // Allocate some memory for the RunBuffer PUCHAR RunBuffer; - int RunBufferOffset = 0; + ULONG RunBufferOffset = 0; if (!AttrContext->Record.IsNonResident) return STATUS_INVALID_PARAMETER; @@ -108,7 +108,7 @@ AddRun(PNTFS_VCB Vcb, // Add newly-assigned clusters to mcb _SEH2_TRY{ if (!FsRtlAddLargeMcbEntry(&DataRunsMCB, - NextVBN, + NextVBN, NextAssignedCluster, RunLength)) { @@ -217,8 +217,6 @@ ConvertDataRunsToLargeMCB(PUCHAR DataRun, LONGLONG DataRunOffset; ULONGLONG DataRunLength; LONGLONG DataRunStartLCN; - ULONGLONG NextCluster; - ULONGLONG LastLCN = 0; // Initialize the MCB, potentially catch an exception @@ -237,8 +235,6 @@ ConvertDataRunsToLargeMCB(PUCHAR DataRun, // Normal data run. DataRunStartLCN = LastLCN + DataRunOffset; LastLCN = DataRunStartLCN; - NextCluster = LastLCN + DataRunLength; - _SEH2_TRY{ if (!FsRtlAddLargeMcbEntry(DataRunsMCB, @@ -295,14 +291,14 @@ ConvertLargeMCBToDataRuns(PLARGE_MCB DataRunsMCB, ULONG RunBufferOffset = 0; LONGLONG DataRunOffset; ULONGLONG LastLCN = 0; - LONGLONG Vbn, Lbn, Count; + int i; 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++) + for (i = 0; FsRtlGetNextLargeMcbEntry(DataRunsMCB, i, &Vbn, &Lbn, &Count); i++) { UCHAR DataRunOffsetSize = 0; UCHAR DataRunLengthSize = 0; From 920e2f02161f08274a27186b3dfa2e27e16203ad Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Tue, 19 Jul 2016 15:31:22 +0000 Subject: [PATCH 22/71] [NTFS] +FreeClusters(). Fix a DPRINT. svn path=/branches/GSoC_2016/NTFS/; revision=71968 --- drivers/filesystems/ntfs/attrib.c | 202 ++++++++++++++++++++++++++++++ drivers/filesystems/ntfs/mft.c | 18 +-- drivers/filesystems/ntfs/ntfs.h | 7 ++ 3 files changed, 215 insertions(+), 12 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index e0c41fa0e49..61f2d6fa7ac 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -409,6 +409,208 @@ 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 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; + + // convert data runs to mcb + PUCHAR DataRun = (PUCHAR)&AttrContext->Record + AttrContext->Record.NonResident.MappingPairsOffset; + PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset); + LARGE_MCB DataRunsMCB; + ULONG NextAttributeOffset = AttrOffset + AttrContext->Record.Length; + PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset); + ULONGLONG NextVBN = AttrContext->Record.NonResident.LowestVCN; + + // Allocate some memory for the RunBuffer + PUCHAR RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS); + ULONG RunBufferOffset = 0; + + PFILE_RECORD_HEADER BitmapRecord; + PNTFS_ATTR_CONTEXT DataContext; + ULONGLONG BitmapDataSize; + PUCHAR BitmapData; + RTL_BITMAP Bitmap; + ULONG LengthWritten; + + if (!AttrContext->Record.IsNonResident) + { + ExFreePoolWithTag(RunBuffer, TAG_NTFS); + return STATUS_INVALID_PARAMETER; + } + + // 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"); + ExFreePoolWithTag(RunBuffer, TAG_NTFS); + return Status; + } + + BitmapRecord = ExAllocatePoolWithTag(NonPagedPool, + Vcb->NtfsInfo.BytesPerFileRecord, + TAG_NTFS); + if (BitmapRecord == NULL) + { + DPRINT1("Error: Unable to allocate memory for bitmap file record!\n"); + FsRtlUninitializeLargeMcb(&DataRunsMCB); + ExFreePoolWithTag(RunBuffer, TAG_NTFS); + 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"); + FsRtlUninitializeLargeMcb(&DataRunsMCB); + ExFreePoolWithTag(BitmapRecord, TAG_NTFS); + ExFreePoolWithTag(RunBuffer, 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"); + FsRtlUninitializeLargeMcb(&DataRunsMCB); + ExFreePoolWithTag(BitmapRecord, TAG_NTFS); + ExFreePoolWithTag(RunBuffer, TAG_NTFS); + return 0; + } + + BitmapDataSize = AttributeDataLength(&DataContext->Record); + BitmapDataSize = min(BitmapDataSize, 0xffffffff); + 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); + FsRtlUninitializeLargeMcb(&DataRunsMCB); + ExFreePoolWithTag(BitmapRecord, TAG_NTFS); + ExFreePoolWithTag(RunBuffer, 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(&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(&DataRunsMCB, AttrContext->Record.NonResident.HighestVCN); + AttrContext->Record.NonResident.HighestVCN = min(AttrContext->Record.NonResident.HighestVCN, AttrContext->Record.NonResident.HighestVCN - 1); + ClustersLeftToFree--; + } + + // update $BITMAP file on disk + Status = WriteAttribute(Vcb, DataContext, 0, BitmapData, (ULONG)BitmapDataSize, &LengthWritten); + if (!NT_SUCCESS(Status)) + { + ReleaseAttributeContext(DataContext); + FsRtlUninitializeLargeMcb(&DataRunsMCB); + ExFreePoolWithTag(BitmapData, TAG_NTFS); + ExFreePoolWithTag(BitmapRecord, TAG_NTFS); + ExFreePoolWithTag(RunBuffer, TAG_NTFS); + return Status; + } + + ReleaseAttributeContext(DataContext); + ExFreePoolWithTag(BitmapData, TAG_NTFS); + ExFreePoolWithTag(BitmapRecord, TAG_NTFS); + + // Convert the map control block back to encoded data runs + ConvertLargeMCBToDataRuns(&DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferOffset); + + // Update HighestVCN + DestinationAttribute->NonResident.HighestVCN = AttrContext->Record.NonResident.HighestVCN; + + // Write data runs to destination attribute + RtlCopyMemory((PVOID)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), + RunBuffer, + RunBufferOffset); + + if (NextAttribute->Type == AttributeEnd) + { + // update attribute length + AttrContext->Record.Length = ALIGN_UP_BY(AttrContext->Record.NonResident.MappingPairsOffset + RunBufferOffset, 8); + DestinationAttribute->Length = AttrContext->Record.Length; + + // write end markers + NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)DestinationAttribute + DestinationAttribute->Length); + NextAttribute->Type = AttributeEnd; + NextAttribute->Length = FILE_RECORD_END; + + // update file record length + FileRecord->BytesInUse = AttrOffset + DestinationAttribute->Length + (sizeof(ULONG) * 2); + } + + // Update the file record + Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord); + + FsRtlUninitializeLargeMcb(&DataRunsMCB); + ExFreePoolWithTag(RunBuffer, TAG_NTFS); + + NtfsDumpDataRuns((PUCHAR)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), 0); + + return Status; +} + static NTSTATUS InternalReadNonResidentAttributes(PFIND_ATTR_CONTXT Context) diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 7b3dcb8d379..f3ac3182dc6 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -225,6 +225,7 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, PLARGE_INTEGER DataSize) { NTSTATUS Status = STATUS_SUCCESS; + ULONG BytesPerCluster = Fcb->Vcb->NtfsInfo.BytesPerCluster; // are we truncating the file? if (DataSize->QuadPart < AttributeDataLength(&AttrContext->Record)) @@ -238,14 +239,13 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, if (AttrContext->Record.IsNonResident) { - ULONG BytesPerCluster = Fcb->Vcb->NtfsInfo.BytesPerCluster; ULONGLONG AllocationSize = ROUND_UP(DataSize->QuadPart, BytesPerCluster); PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset); + ULONG ExistingClusters = AttrContext->Record.NonResident.AllocatedSize / BytesPerCluster; // do we need to increase the allocation size? if (AttrContext->Record.NonResident.AllocatedSize < AllocationSize) { - ULONG ExistingClusters = AttrContext->Record.NonResident.AllocatedSize / BytesPerCluster; ULONG ClustersNeeded = (AllocationSize / BytesPerCluster) - ExistingClusters; LARGE_INTEGER LastClusterInDataRun; ULONG NextAssignedCluster; @@ -284,15 +284,9 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, } 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; - } + // shrink allocation size + ULONG ClustersToFree = ExistingClusters - (AllocationSize / BytesPerCluster); + Status = FreeClusters(Fcb->Vcb, AttrContext, AttrOffset, FileRecord, ClustersToFree); } // TODO: is the file compressed, encrypted, or sparse? @@ -1098,7 +1092,7 @@ UpdateFileRecord(PDEVICE_EXTENSION Vcb, if (!NT_SUCCESS(Status)) { - DPRINT1("UpdateFileRecord failed: %I64u written, %u expected\n", BytesWritten, Vcb->NtfsInfo.BytesPerFileRecord); + DPRINT1("UpdateFileRecord failed: %I64u written, %lu expected\n", BytesWritten, Vcb->NtfsInfo.BytesPerFileRecord); } // remove the fixup array (so the file record pointer can still be used) diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index bc6a8d90897..29a6c2af004 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -584,6 +584,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 From 8b893c8e30a6ea0fa46f745810f6c6d2eb6f6feb Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Mon, 22 Aug 2016 08:11:12 +0000 Subject: [PATCH 23/71] [NTFS] Add some fixes to attrib.c, as suggested by Pierre Schweitzer: *ConvertLargeMCBToDataRuns() - Use MS portable type, ULONG, for variable i. *FreeClusters(), AddRun() - Check for invalid parameter before allocating memory, and confirm the memory is allocated. *ConvertDataRunsToLargeMCB(), AddRun() - Avoid code duplication by using ExRaiseStatus() in try block, and return accurate status via _SEH2_GetExceptionCode(). svn path=/branches/GSoC_2016/NTFS/; revision=72422 --- drivers/filesystems/ntfs/attrib.c | 39 ++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index 61f2d6fa7ac..b65a6b8b50e 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -62,7 +62,9 @@ * * @return * STATUS_SUCCESS on success. STATUS_INVALID_PARAMETER if AttrContext describes a resident attribute. -* STATUS_INSUFFICIENT_RESOURCES if ConvertDataRunsToLargeMCB() fails. +* 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). * @@ -95,6 +97,11 @@ AddRun(PNTFS_VCB Vcb, return STATUS_INVALID_PARAMETER; 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 data runs to a map control block Status = ConvertDataRunsToLargeMCB(DataRun, &DataRunsMCB, &NextVBN); @@ -112,14 +119,12 @@ AddRun(PNTFS_VCB Vcb, NextAssignedCluster, RunLength)) { - FsRtlUninitializeLargeMcb(&DataRunsMCB); - ExFreePoolWithTag(RunBuffer, TAG_NTFS); - return STATUS_INSUFFICIENT_RESOURCES; + ExRaiseStatus(STATUS_UNSUCCESSFUL); } } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { FsRtlUninitializeLargeMcb(&DataRunsMCB); ExFreePoolWithTag(RunBuffer, TAG_NTFS); - _SEH2_YIELD(return STATUS_INSUFFICIENT_RESOURCES); + _SEH2_YIELD(_SEH2_GetExceptionCode()); } _SEH2_END; @@ -200,7 +205,7 @@ AddRun(PNTFS_VCB Vcb, * Pointer to an unitialized LARGE_MCB structure. * * @return -* STATUS_SUCCESS on success, STATUS_INSUFFICIENT_RESOURCES if we fail to +* STATUS_SUCCESS on success, STATUS_INSUFFICIENT_RESOURCES or STATUS_UNSUCCESSFUL if we fail to * initialize the mcb or add an entry. * * @remarks @@ -223,7 +228,7 @@ ConvertDataRunsToLargeMCB(PUCHAR DataRun, _SEH2_TRY{ FsRtlInitializeLargeMcb(DataRunsMCB, NonPagedPool); } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { - _SEH2_YIELD(return STATUS_INSUFFICIENT_RESOURCES); + _SEH2_YIELD(return _SEH2_GetExceptionCode()); } _SEH2_END; while (*DataRun != 0) @@ -242,12 +247,11 @@ ConvertDataRunsToLargeMCB(PUCHAR DataRun, DataRunStartLCN, DataRunLength)) { - FsRtlUninitializeLargeMcb(DataRunsMCB); - return STATUS_INSUFFICIENT_RESOURCES; + ExRaiseStatus(STATUS_UNSUCCESSFUL); } } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { FsRtlUninitializeLargeMcb(DataRunsMCB); - _SEH2_YIELD(return STATUS_INSUFFICIENT_RESOURCES); + _SEH2_YIELD(return _SEH2_GetExceptionCode()); } _SEH2_END; } @@ -292,7 +296,7 @@ ConvertLargeMCBToDataRuns(PLARGE_MCB DataRunsMCB, LONGLONG DataRunOffset; ULONGLONG LastLCN = 0; LONGLONG Vbn, Lbn, Count; - int i; + ULONG i; DPRINT("\t[Vbn, Lbn, Count]\n"); @@ -436,7 +440,8 @@ FindRun(PNTFS_ATTR_RECORD NresAttr, * @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 ConvertDataRunsToLargeMCB() fails. +* STATUS_INSUFFICIENT_RESOURCES if allocating a buffer for the data runs fails or +* if ConvertDataRunsToLargeMCB() fails. * STATUS_BUFFER_TOO_SMALL if ConvertLargeMCBToDataRuns() fails. * * @@ -460,7 +465,7 @@ FreeClusters(PNTFS_VCB Vcb, ULONGLONG NextVBN = AttrContext->Record.NonResident.LowestVCN; // Allocate some memory for the RunBuffer - PUCHAR RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS); + PUCHAR RunBuffer; ULONG RunBufferOffset = 0; PFILE_RECORD_HEADER BitmapRecord; @@ -472,10 +477,16 @@ FreeClusters(PNTFS_VCB Vcb, if (!AttrContext->Record.IsNonResident) { - ExFreePoolWithTag(RunBuffer, TAG_NTFS); return STATUS_INVALID_PARAMETER; } + 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 data runs to a map control block Status = ConvertDataRunsToLargeMCB(DataRun, &DataRunsMCB, &NextVBN); if (!NT_SUCCESS(Status)) From 6ab3072098ec3a28292471ac6ec6a7310dff04a5 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Mon, 22 Aug 2016 08:27:35 +0000 Subject: [PATCH 24/71] [NTFS] *UpdateFileRecord() - Fix a DPRINT svn path=/branches/GSoC_2016/NTFS/; revision=72423 --- drivers/filesystems/ntfs/mft.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index f3ac3182dc6..4b0ded49f34 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -1092,7 +1092,7 @@ UpdateFileRecord(PDEVICE_EXTENSION Vcb, if (!NT_SUCCESS(Status)) { - DPRINT1("UpdateFileRecord failed: %I64u written, %lu expected\n", BytesWritten, Vcb->NtfsInfo.BytesPerFileRecord); + 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) From 7643831d569d693c73858bfdd0f9d63cc6d1b6af Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Mon, 22 Aug 2016 11:08:22 +0000 Subject: [PATCH 25/71] [NTFS] +NtfsDumpFileRecord() - Provides diagnostic information about a file record. svn path=/branches/GSoC_2016/NTFS/; revision=72424 --- drivers/filesystems/ntfs/mft.c | 40 +++++++++++++++++++++++++++++++++ drivers/filesystems/ntfs/ntfs.h | 4 ++++ 2 files changed, 44 insertions(+) diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 4b0ded49f34..30ef5f0e3ea 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -1460,6 +1460,46 @@ NtfsLookupFile(PDEVICE_EXTENSION Vcb, return NtfsLookupFileAt(Vcb, PathName, FileRecord, MFTIndex, NTFS_FILE_ROOT); } +/** +* @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 NtfsFindFileAt(PDEVICE_EXTENSION Vcb, PUNICODE_STRING SearchPattern, diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index 29a6c2af004..ce8a7df2de6 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -907,6 +907,10 @@ NtfsLookupFileAt(PDEVICE_EXTENSION Vcb, PULONGLONG MFTIndex, ULONGLONG CurrentMFTIndex); +VOID +NtfsDumpFileRecord(PDEVICE_EXTENSION Vcb, + PFILE_RECORD_HEADER FileRecord); + NTSTATUS NtfsFindFileAt(PDEVICE_EXTENSION Vcb, PUNICODE_STRING SearchPattern, From 0409b3161e8789a4c9aa606616626e1237427766 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Sun, 16 Apr 2017 00:17:07 +0000 Subject: [PATCH 26/71] [NTFS] Add support for creating new MFT entries: +AddStandardInformation(), +AddData(), +AddFileName() - Add attributes to a file record +NtfsCreateFileRecord() - Creates a new file record and saves it to the MFT. +AddNewMftEntry() - Adds a file record to the MFT. NtfsCreateFile() - Modified to create a file record on a file-creation request (file creation is still unsupported; the created file needs to be added to the parent's directory index). +SetFileRecordEnd() - Establishes a new file record size UpdateFileRecord() - Improved documentation InternalSetResidentAttributeLength() - Updated to use SetFileRecordEnd(). svn path=/branches/GSoC_2016/NTFS/; revision=74321 --- drivers/filesystems/ntfs/attrib.c | 242 ++++++++++++++++++++++++++++++ drivers/filesystems/ntfs/create.c | 96 +++++++++++- drivers/filesystems/ntfs/mft.c | 189 +++++++++++++++++++++-- drivers/filesystems/ntfs/ntfs.h | 38 ++++- 4 files changed, 545 insertions(+), 20 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index b65a6b8b50e..2a70e8c3c8f 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -35,6 +35,185 @@ /* FUNCTIONS ****************************************************************/ +/** +* @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, 8); + 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. +* +* @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) +{ + 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; + NTSTATUS Status = STATUS_SUCCESS; + ULONG FirstEntry = 0; + + 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; + + 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); + + while (Current.Length != 0) + { + DPRINT1("Current: %wZ\n", &Current); + + Status = NtfsFindMftRecord(DeviceExt, CurrentMFTIndex, &Current, &FirstEntry, FALSE, &CurrentMFTIndex); + if (!NT_SUCCESS(Status)) + { + break; + } + + if (Remaining.Length == 0) + break; + + FsRtlDissectName(Current, &Current, &Remaining); + } + + DPRINT1("MFT Index of parent: %I64u\n", CurrentMFTIndex); + + // set reference to parent directory + FileNameAttribute->DirectoryFileReferenceNumber = CurrentMFTIndex; + + // 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%I64x\n", FileNameAttribute->DirectoryFileReferenceNumber); + + FileNameAttribute->NameLength = Current.Length / 2; + // TODO: Get proper nametype, add DOS links as needed + FileNameAttribute->NameType = NTFS_FILE_NAME_WIN32_AND_DOS; + RtlCopyMemory(FileNameAttribute->Name, Current.Buffer, Current.Length); + FileRecord->LinkCount++; + + AttributeAddress->Length = ResidentHeaderLength + + FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + Current.Length; + AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, 8); + + AttributeAddress->Resident.ValueLength = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + Current.Length; + AttributeAddress->Resident.ValueOffset = ResidentHeaderLength; + AttributeAddress->Resident.Flags = 1; // 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 AddRun * @implemented @@ -192,6 +371,69 @@ AddRun(PNTFS_VCB Vcb, 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, 8); + 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 diff --git a/drivers/filesystems/ntfs/create.c b/drivers/filesystems/ntfs/create.c index 344c16464ff..30312d61043 100644 --- a/drivers/filesystems/ntfs/create.c +++ b/drivers/filesystems/ntfs/create.c @@ -545,8 +545,15 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject, RequestedDisposition == FILE_OPEN_IF || RequestedDisposition == FILE_OVERWRITE_IF || RequestedDisposition == FILE_SUPERSEDE) - { - DPRINT1("Denying file creation request on NTFS volume\n"); + { + // Create the file record on disk + Status = NtfsCreateFileRecord(DeviceExt, FileObject); + + // Update the parent directory index + // Still TODO + + // Call NtfsOpenFile() + return STATUS_CANNOT_MAKE; } } @@ -599,4 +606,89 @@ NtfsCreate(PNTFS_IRP_CONTEXT IrpContext) return Status; } +/** +* @name NtfsCreateFileRecord() +* @implemented +* +* Creates a file record and saves it to the MFT. +* +* @param DeviceExt +* Points to the target disk's DEVICE_EXTENSION +* +* @param FileObject +* Pointer to a FILE_OBJECT describing the file to be created +* +* @return +* STATUS_SUCCESS on success. +* STATUS_INSUFFICIENT_RESOURCES if unable to allocate memory for the file record. +*/ +NTSTATUS +NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, + PFILE_OBJECT FileObject) +{ + NTSTATUS Status = STATUS_SUCCESS; + PFILE_RECORD_HEADER FileRecord; + PNTFS_ATTR_RECORD NextAttribute; + + // 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 STATUS_INSUFFICIENT_RESOURCES; + } + + 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, 8); + 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; + + // 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); + + // 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); + + // dump file record in memory (for debugging) + NtfsDumpFileRecord(DeviceExt, FileRecord); + + // Now that we've built the file record in memory, we need to store it in the MFT. + Status = AddNewMftEntry(FileRecord, DeviceExt); + + ExFreePoolWithTag(FileRecord, TAG_NTFS); + + return Status; +} + /* EOF */ diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 30ef5f0e3ea..8b588d760b6 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -172,7 +172,7 @@ AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord) return AttrRecord->Resident.ValueLength; } -void +VOID InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext, PFILE_RECORD_HEADER FileRecord, ULONG AttrOffset, @@ -201,14 +201,9 @@ InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext, Destination->Length += Padding; } - // advance Destination to the final "attribute" and write the end type + // advance Destination to the final "attribute" and set the file record end Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)Destination + Destination->Length); - Destination->Type = AttributeEnd; - - // write the final marker (which shares the same offset and type as the Length field) - Destination->Length = FILE_RECORD_END; - - FileRecord->BytesInUse = NextAttributeOffset + (sizeof(ULONG) * 2); + SetFileRecordEnd(FileRecord, Destination, FILE_RECORD_END); } /** @@ -359,6 +354,41 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, 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) +{ + // 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; +} + ULONG ReadAttribute(PDEVICE_EXTENSION Vcb, PNTFS_ATTR_CONTEXT Context, @@ -711,7 +741,9 @@ WriteAttribute(PDEVICE_EXTENSION Vcb, { // We reached the last assigned cluster // TODO: assign new clusters to the end of the file. - // (Presently, this code will never be reached, the write should have already failed by now) + // (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; } @@ -1070,25 +1102,39 @@ UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb, } /** -* UpdateFileRecord +* @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 index, - PFILE_RECORD_HEADER file) + ULONGLONG MftIndex, + PFILE_RECORD_HEADER FileRecord) { ULONG BytesWritten; NTSTATUS Status = STATUS_SUCCESS; - DPRINT("UpdateFileRecord(%p, %I64x, %p)\n", Vcb, index, file); + DPRINT("UpdateFileRecord(%p, 0x%I64x, %p)\n", Vcb, MftIndex, FileRecord); // Add the fixup array to prepare the data for writing to disk - AddFixupArray(Vcb, &file->Ntfs); + AddFixupArray(Vcb, &FileRecord->Ntfs); // 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); + Status = WriteAttribute(Vcb, Vcb->MFTContext, MftIndex * Vcb->NtfsInfo.BytesPerFileRecord, (const PUCHAR)FileRecord, Vcb->NtfsInfo.BytesPerFileRecord, &BytesWritten); if (!NT_SUCCESS(Status)) { @@ -1096,7 +1142,7 @@ UpdateFileRecord(PDEVICE_EXTENSION Vcb, } // remove the fixup array (so the file record pointer can still be used) - FixupUpdateSequenceArray(Vcb, &file->Ntfs); + FixupUpdateSequenceArray(Vcb, &FileRecord->Ntfs); return Status; } @@ -1133,6 +1179,117 @@ 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. +* +* @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_NOT_IMPLEMENTED if we need to increase the size of the MFT. +* +*/ +NTSTATUS +AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, + PDEVICE_EXTENSION DeviceExt) +{ + NTSTATUS Status = STATUS_SUCCESS; + ULONGLONG MftIndex; + RTL_BITMAP Bitmap; + ULONGLONG BitmapDataSize; + ULONGLONG AttrBytesRead; + PVOID BitmapData; + ULONG LengthWritten; + + // First, we have to read the mft's $Bitmap attribute + PNTFS_ATTR_CONTEXT BitmapContext; + 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; + } + + // allocate a buffer for the $Bitmap attribute + BitmapDataSize = AttributeDataLength(&BitmapContext->Record); + BitmapData = ExAllocatePoolWithTag(NonPagedPool, BitmapDataSize, TAG_NTFS); + if (!BitmapData) + { + ReleaseAttributeContext(BitmapContext); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // read $Bitmap attribute + AttrBytesRead = ReadAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize); + + if (AttrBytesRead == 0) + { + DPRINT1("ERROR: Unable to read $Bitmap attribute of master file table!\n"); + ExFreePoolWithTag(BitmapData, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + return STATUS_OBJECT_NAME_NOT_FOUND; + } + + // convert buffer into bitmap + RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, BitmapDataSize * 8); + + // set next available bit, preferrably after 23rd bit + MftIndex = RtlFindClearBitsAndSet(&Bitmap, 1, 24); + if ((LONG)MftIndex == -1) + { + DPRINT1("ERROR: Couldn't find free space in MFT for file record!\n"); + + ExFreePoolWithTag(BitmapData, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + + // TODO: increase mft size + return STATUS_NOT_IMPLEMENTED; + } + + 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()] + + // write the bitmap back to the MFT's $Bitmap attribute + Status = WriteAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize, &LengthWritten); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR encountered when writing $Bitmap attribute!\n"); + ExFreePoolWithTag(BitmapData, 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(BitmapData, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + return Status; + } + + ExFreePoolWithTag(BitmapData, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + + return Status; +} + NTSTATUS AddFixupArray(PDEVICE_EXTENSION Vcb, PNTFS_RECORD_HEADER Record) diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index ce8a7df2de6..5a36bcc9fe9 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -516,6 +516,10 @@ NtfsMarkIrpContextForQueue(PNTFS_IRP_CONTEXT IrpContext) //VOID //NtfsDumpAttribute(PATTRIBUTE Attribute); +NTSTATUS +AddData(PFILE_RECORD_HEADER FileRecord, + PNTFS_ATTR_RECORD AttributeAddress); + NTSTATUS AddRun(PNTFS_VCB Vcb, PNTFS_ATTR_CONTEXT AttrContext, @@ -524,6 +528,16 @@ AddRun(PNTFS_VCB Vcb, ULONGLONG NextAssignedCluster, ULONG RunLength); +NTSTATUS +AddFileName(PFILE_RECORD_HEADER FileRecord, + PNTFS_ATTR_RECORD AttributeAddress, + PDEVICE_EXTENSION DeviceExt, + PFILE_OBJECT FileObject); + +NTSTATUS +AddStandardInformation(PFILE_RECORD_HEADER FileRecord, + PNTFS_ATTR_RECORD AttributeAddress); + NTSTATUS ConvertDataRunsToLargeMCB(PUCHAR DataRun, PLARGE_MCB DataRunsMCB, @@ -647,6 +661,9 @@ NtfsClose(PNTFS_IRP_CONTEXT IrpContext); NTSTATUS NtfsCreate(PNTFS_IRP_CONTEXT IrpContext); +NTSTATUS +NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, + PFILE_OBJECT FileObject); /* devctl.c */ @@ -786,6 +803,10 @@ NtfsFileSystemControl(PNTFS_IRP_CONTEXT IrpContext); /* mft.c */ +NTSTATUS +AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, + PDEVICE_EXTENSION DeviceExt); + PNTFS_ATTR_CONTEXT PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord); @@ -818,6 +839,11 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, PFILE_RECORD_HEADER FileRecord, PLARGE_INTEGER DataSize); +VOID +SetFileRecordEnd(PFILE_RECORD_HEADER FileRecord, + PNTFS_ATTR_RECORD AttrEnd, + ULONG EndMarker); + ULONGLONG AttributeAllocatedLength(PNTFS_ATTR_RECORD AttrRecord); @@ -855,8 +881,8 @@ UpdateFileNameRecord(PDEVICE_EXTENSION Vcb, NTSTATUS UpdateFileRecord(PDEVICE_EXTENSION Vcb, - ULONGLONG index, - PFILE_RECORD_HEADER file); + ULONGLONG MftIndex, + PFILE_RECORD_HEADER FileRecord); NTSTATUS FindAttribute(PDEVICE_EXTENSION Vcb, @@ -919,6 +945,14 @@ NtfsFindFileAt(PDEVICE_EXTENSION Vcb, PULONGLONG MFTIndex, ULONGLONG CurrentMFTIndex); +NTSTATUS +NtfsFindMftRecord(PDEVICE_EXTENSION Vcb, + ULONGLONG MFTIndex, + PUNICODE_STRING FileName, + PULONG FirstEntry, + BOOLEAN DirSearch, + ULONGLONG *OutMFTIndex); + /* misc.c */ BOOLEAN From 52b9f46776d9850c49976c949513fdd34a6d935f Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Fri, 12 May 2017 22:16:20 +0000 Subject: [PATCH 27/71] [NTFS] - Commit early results of a small restructuring effort: -Add a new member to the NTFS_ATTR_CONTEXT struct, a LARGE_MCB. This allows an attribute context to describe the cluster mapping of a non-resident file while allowing that mapping to change dynamically, without the context itself needing to be resized. This fixes problems which sometimes arose from resizing files. -Remove hacky code from NtfsWriteFile() for dealing with "stale" contexts. This fixes that issue. -Update SetDataAttributeLength(), PrepareAttributeContext(), ReleaseAttributeContext(), FreeClusters(), and AddRun() for the new member. -Update ReadAttribute() and WriteAttribute() to work with the changed structure. A very-soon-to-come commit will overhaul these functions so they'll operate directly on the LARGE_MCB, instead of converting to and from a packed list of data runs. (Sparse files are broken until then.) -Rename "RunBufferOffset" to "RunBufferSize" in several places where appropriate. -Fix, improve, and add some comments. svn path=/branches/GSoC_2016/NTFS/; revision=74523 --- drivers/filesystems/ntfs/attrib.c | 141 +++++++++++------------------- drivers/filesystems/ntfs/mft.c | 85 ++++++++++++++++-- drivers/filesystems/ntfs/ntfs.h | 1 + drivers/filesystems/ntfs/rw.c | 16 ---- 4 files changed, 128 insertions(+), 115 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index 2a70e8c3c8f..bb678e3ef8f 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -261,20 +261,33 @@ AddRun(PNTFS_VCB Vcb, ULONG RunLength) { 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; + ULONGLONG NextVBN = 0; - // Allocate some memory for the RunBuffer PUCHAR RunBuffer; - ULONG RunBufferOffset = 0; + ULONG RunBufferSize; if (!AttrContext->Record.IsNonResident) return STATUS_INVALID_PARAMETER; + if (AttrContext->Record.NonResident.AllocatedSize != 0) + NextVBN = AttrContext->Record.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) { + _SEH2_YIELD(_SEH2_GetExceptionCode()); + } _SEH2_END; + RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS); if (!RunBuffer) { @@ -282,89 +295,55 @@ AddRun(PNTFS_VCB Vcb, return STATUS_INSUFFICIENT_RESOURCES; } - // 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"); - ExFreePoolWithTag(RunBuffer, TAG_NTFS); - return Status; - } - - // Add newly-assigned clusters to mcb - _SEH2_TRY{ - if (!FsRtlAddLargeMcbEntry(&DataRunsMCB, - NextVBN, - NextAssignedCluster, - RunLength)) - { - ExRaiseStatus(STATUS_UNSUCCESSFUL); - } - } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { - FsRtlUninitializeLargeMcb(&DataRunsMCB); - ExFreePoolWithTag(RunBuffer, TAG_NTFS); - _SEH2_YIELD(_SEH2_GetExceptionCode()); - } _SEH2_END; - - // Convert the map control block back to encoded data runs - ConvertLargeMCBToDataRuns(&DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferOffset); + 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->Record.Length - AttrContext->Record.NonResident.MappingPairsOffset; // Do we need to extend the attribute (or convert to attribute list)? - if (DataRunMaxLength < RunBufferOffset) + if (DataRunMaxLength < RunBufferSize) { 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) + if (NextAttribute->Type != AttributeEnd || DataRunMaxLength < RunBufferSize - 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 = AttrOffset + AttrContext->Record.NonResident.MappingPairsOffset + RunBufferSize; 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); + // End the file record + NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset); + SetFileRecordEnd(FileRecord, NextAttribute, FILE_RECORD_END); } - // 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 = max(NextVBN - 1 + RunLength, AttrContext->Record.NonResident.HighestVCN); // Write data runs to destination attribute RtlCopyMemory((PVOID)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), RunBuffer, - RunBufferOffset); + RunBufferSize); // 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); @@ -567,7 +546,7 @@ ConvertLargeMCBToDataRuns(PLARGE_MCB DataRunsMCB, DataRunLengthSize = GetPackedByteCount(Count, TRUE); DPRINT("%d bytes needed.\n", DataRunLengthSize); - // ensure the next data run + end marker would be > Max buffer size + // ensure the next data run + end marker would be <= Max buffer size if (RunBufferOffset + 2 + DataRunLengthSize + DataRunOffsetSize > MaxBufferSize) { Status = STATUS_BUFFER_TOO_SMALL; @@ -698,17 +677,12 @@ FreeClusters(PNTFS_VCB Vcb, NTSTATUS Status = STATUS_SUCCESS; ULONG ClustersLeftToFree = ClustersToFree; - // convert data runs to mcb - PUCHAR DataRun = (PUCHAR)&AttrContext->Record + AttrContext->Record.NonResident.MappingPairsOffset; PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset); - LARGE_MCB DataRunsMCB; ULONG NextAttributeOffset = AttrOffset + AttrContext->Record.Length; PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset); - ULONGLONG NextVBN = AttrContext->Record.NonResident.LowestVCN; - // Allocate some memory for the RunBuffer PUCHAR RunBuffer; - ULONG RunBufferOffset = 0; + ULONG RunBufferSize = 0; PFILE_RECORD_HEADER BitmapRecord; PNTFS_ATTR_CONTEXT DataContext; @@ -722,30 +696,13 @@ FreeClusters(PNTFS_VCB Vcb, return STATUS_INVALID_PARAMETER; } - 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 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"); - ExFreePoolWithTag(RunBuffer, TAG_NTFS); - return Status; - } - + // 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"); - FsRtlUninitializeLargeMcb(&DataRunsMCB); - ExFreePoolWithTag(RunBuffer, TAG_NTFS); return STATUS_NO_MEMORY; } @@ -753,9 +710,7 @@ FreeClusters(PNTFS_VCB Vcb, if (!NT_SUCCESS(Status)) { DPRINT1("Error: Unable to read file record for bitmap!\n"); - FsRtlUninitializeLargeMcb(&DataRunsMCB); ExFreePoolWithTag(BitmapRecord, TAG_NTFS); - ExFreePoolWithTag(RunBuffer, TAG_NTFS); return 0; } @@ -763,9 +718,7 @@ FreeClusters(PNTFS_VCB Vcb, if (!NT_SUCCESS(Status)) { DPRINT1("Error: Unable to find data attribute for bitmap file!\n"); - FsRtlUninitializeLargeMcb(&DataRunsMCB); ExFreePoolWithTag(BitmapRecord, TAG_NTFS); - ExFreePoolWithTag(RunBuffer, TAG_NTFS); return 0; } @@ -777,9 +730,7 @@ FreeClusters(PNTFS_VCB Vcb, { DPRINT1("Error: Unable to allocate memory for bitmap file data!\n"); ReleaseAttributeContext(DataContext); - FsRtlUninitializeLargeMcb(&DataRunsMCB); ExFreePoolWithTag(BitmapRecord, TAG_NTFS); - ExFreePoolWithTag(RunBuffer, TAG_NTFS); return 0; } @@ -792,7 +743,7 @@ FreeClusters(PNTFS_VCB Vcb, { LONGLONG LargeVbn, LargeLbn; - if (!FsRtlLookupLastLargeMcbEntry(&DataRunsMCB, &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!", @@ -806,7 +757,9 @@ FreeClusters(PNTFS_VCB Vcb, // deallocate this cluster RtlClearBits(&Bitmap, LargeLbn, 1); } - FsRtlTruncateLargeMcb(&DataRunsMCB, AttrContext->Record.NonResident.HighestVCN); + FsRtlTruncateLargeMcb(&AttrContext->DataRunsMCB, AttrContext->Record.NonResident.HighestVCN); + + // decrement HighestVCN, but don't let it go below 0 AttrContext->Record.NonResident.HighestVCN = min(AttrContext->Record.NonResident.HighestVCN, AttrContext->Record.NonResident.HighestVCN - 1); ClustersLeftToFree--; } @@ -816,19 +769,27 @@ FreeClusters(PNTFS_VCB Vcb, if (!NT_SUCCESS(Status)) { ReleaseAttributeContext(DataContext); - FsRtlUninitializeLargeMcb(&DataRunsMCB); ExFreePoolWithTag(BitmapData, TAG_NTFS); ExFreePoolWithTag(BitmapRecord, TAG_NTFS); - ExFreePoolWithTag(RunBuffer, 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(&DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferOffset); + ConvertLargeMCBToDataRuns(&AttrContext->DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferSize); // Update HighestVCN DestinationAttribute->NonResident.HighestVCN = AttrContext->Record.NonResident.HighestVCN; @@ -836,27 +797,23 @@ FreeClusters(PNTFS_VCB Vcb, // Write data runs to destination attribute RtlCopyMemory((PVOID)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), RunBuffer, - RunBufferOffset); + RunBufferSize); + // Is DestinationAttribute the last attribute in the file record? if (NextAttribute->Type == AttributeEnd) { // update attribute length - AttrContext->Record.Length = ALIGN_UP_BY(AttrContext->Record.NonResident.MappingPairsOffset + RunBufferOffset, 8); + AttrContext->Record.Length = ALIGN_UP_BY(AttrContext->Record.NonResident.MappingPairsOffset + RunBufferSize, 8); DestinationAttribute->Length = AttrContext->Record.Length; // write end markers NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)DestinationAttribute + DestinationAttribute->Length); - NextAttribute->Type = AttributeEnd; - NextAttribute->Length = FILE_RECORD_END; - - // update file record length - FileRecord->BytesInUse = AttrOffset + DestinationAttribute->Length + (sizeof(ULONG) * 2); + SetFileRecordEnd(FileRecord, NextAttribute, FILE_RECORD_END); } // Update the file record Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord); - FsRtlUninitializeLargeMcb(&DataRunsMCB); ExFreePoolWithTag(RunBuffer, TAG_NTFS); NtfsDumpDataRuns((PUCHAR)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), 0); diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 8b588d760b6..e78be291844 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -50,8 +50,10 @@ PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord) { LONGLONG DataRunOffset; ULONGLONG DataRunLength; + ULONGLONG NextVBN = 0; + PUCHAR DataRun = (PUCHAR)&Context->Record + Context->Record.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; @@ -68,6 +70,14 @@ 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, TAG_NTFS); + return NULL; + } } return Context; @@ -77,6 +87,11 @@ PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord) VOID ReleaseAttributeContext(PNTFS_ATTR_CONTEXT Context) { + if (Context->Record.IsNonResident) + { + FsRtlUninitializeLargeMcb(&Context->DataRunsMCB); + } + ExFreePoolWithTag(Context, TAG_NTFS); } @@ -246,10 +261,30 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, ULONG NextAssignedCluster; ULONG AssignedClusters; - NTSTATUS Status = GetLastClusterInDataRun(Fcb->Vcb, &AttrContext->Record, (PULONGLONG)&LastClusterInDataRun.QuadPart); + if (ExistingClusters == 0) + { + LastClusterInDataRun.QuadPart = 0; + } + else + { + if (!FsRtlLookupLargeMcbEntry(&AttrContext->DataRunsMCB, + (LONGLONG)AttrContext->Record.NonResident.HighestVCN, + (PLONGLONG)&LastClusterInDataRun.QuadPart, + NULL, + NULL, + NULL, + NULL)) + { + DPRINT1("Error looking up final large MCB entry!\n"); - DPRINT1("GetLastClusterInDataRun returned: %I64u\n", LastClusterInDataRun.QuadPart); - DPRINT1("Highest VCN of record: %I64u\n", AttrContext->Record.NonResident.HighestVCN); + // Most likely, HighestVCN went above the largest mapping + DPRINT1("Highest VCN of record: %I64u\n", AttrContext->Record.NonResident.HighestVCN); + return STATUS_INVALID_PARAMETER; + } + } + + DPRINT("LastClusterInDataRun: %I64u\n", LastClusterInDataRun.QuadPart); + DPRINT("Highest VCN of record: %I64u\n", AttrContext->Record.NonResident.HighestVCN); while (ClustersNeeded > 0) { @@ -405,6 +440,9 @@ ReadAttribute(PDEVICE_EXTENSION Vcb, ULONG ReadLength; ULONG AlreadyRead; NTSTATUS Status; + + //TEMPTEMP + PUCHAR TempBuffer; if (!Context->Record.IsNonResident) { @@ -438,10 +476,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); @@ -558,6 +607,10 @@ ReadAttribute(PDEVICE_EXTENSION Vcb, } /* if Disk */ + // TEMPTEMP + if (Context->Record.IsNonResident) + ExFreePoolWithTag(TempBuffer, TAG_NTFS); + Context->CacheRun = DataRun; Context->CacheRunOffset = Offset + AlreadyRead; Context->CacheRunStartLCN = DataRunStartLCN; @@ -622,6 +675,10 @@ WriteAttribute(PDEVICE_EXTENSION Vcb, NTSTATUS Status; PUCHAR SourceBuffer = Buffer; LONGLONG StartingOffset; + + //TEMPTEMP + PUCHAR TempBuffer; + DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p)\n", Vcb, Context, Offset, Buffer, Length, RealLengthWritten); @@ -707,9 +764,19 @@ WriteAttribute(PDEVICE_EXTENSION Vcb, } else*/ { + ULONG UsedBufferSize; LastLCN = 0; - DataRun = (PUCHAR)&Context->Record + Context->Record.NonResident.MappingPairsOffset; - CurrentOffset = 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) { @@ -864,6 +931,10 @@ WriteAttribute(PDEVICE_EXTENSION Vcb, } } // end while (Length > 0) [more data to write] + // TEMPTEMP + if(Context->Record.IsNonResident) + ExFreePoolWithTag(TempBuffer, TAG_NTFS); + Context->CacheRun = DataRun; Context->CacheRunOffset = Offset + *RealLengthWritten; Context->CacheRunStartLCN = DataRunStartLCN; diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index 5a36bcc9fe9..462980d45df 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -437,6 +437,7 @@ typedef struct _NTFS_ATTR_CONTEXT ULONGLONG CacheRunLength; LONGLONG CacheRunLastLCN; ULONGLONG CacheRunCurrentOffset; + LARGE_MCB DataRunsMCB; ULONGLONG FileMFTIndex; NTFS_ATTR_RECORD Record; } NTFS_ATTR_CONTEXT, *PNTFS_ATTR_CONTEXT; diff --git a/drivers/filesystems/ntfs/rw.c b/drivers/filesystems/ntfs/rw.c index 4db1c388153..5afb87e9e60 100644 --- a/drivers/filesystems/ntfs/rw.c +++ b/drivers/filesystems/ntfs/rw.c @@ -432,22 +432,6 @@ 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. From c25b9747a168c659c76e1b41a53655dad53f8007 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Sat, 13 May 2017 08:56:54 +0000 Subject: [PATCH 28/71] [NTFS] - Expand support for resizing resident attributes and fix NtfsAllocateClusters(). -Modify SetAttributeDataLength() to allow a resident attribute to migrate to non-resident if the attribute grows too large to remain resident. -Fix values returned by NtfsAllocateClusters() in case of error; return error codes, not 0. svn path=/branches/GSoC_2016/NTFS/; revision=74524 --- drivers/filesystems/ntfs/mft.c | 106 ++++++++++++++++++++++++++++- drivers/filesystems/ntfs/volinfo.c | 8 +-- 2 files changed, 107 insertions(+), 7 deletions(-) diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index e78be291844..1e23675a0cc 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -356,8 +356,106 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, if (MaxValueLength < DataSize->LowPart || NextAttribute->Type != AttributeEnd) { - DPRINT1("FIXME: Need to convert attribute to non-resident!\n"); - return STATUS_NOT_IMPLEMENTED; + // convert attribute to non-resident + PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset); + LARGE_INTEGER AttribDataSize; + PVOID AttribData; + ULONG EndAttributeOffset; + ULONG LengthWritten; + + DPRINT1("Converting attribute to non-resident.\n"); + + AttribDataSize.QuadPart = AttrContext->Record.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(Fcb->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. + + // Zero out the NonResident structure + RtlZeroMemory(&AttrContext->Record.NonResident.LowestVCN, + FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize) - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.LowestVCN)); + RtlZeroMemory(&Destination->NonResident.LowestVCN, + FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize) - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.LowestVCN)); + + // update the mapping pairs offset, which will be 0x40 + length in bytes of the name + AttrContext->Record.NonResident.MappingPairsOffset = Destination->NonResident.MappingPairsOffset = 0x40 + (Destination->NameLength * 2); + + // mark the attribute as non-resident + AttrContext->Record.IsNonResident = Destination->IsNonResident = 1; + + // update the end of the file record + // calculate position of end markers (1 byte for empty data run) + EndAttributeOffset = AttrOffset + AttrContext->Record.NonResident.MappingPairsOffset + 1; + EndAttributeOffset = ALIGN_UP_BY(EndAttributeOffset, 8); + + // Update the length + Destination->Length = EndAttributeOffset - AttrOffset; + AttrContext->Record.Length = Destination->Length; + + // Update the file record end + SetFileRecordEnd(FileRecord, + (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + EndAttributeOffset), + FILE_RECORD_END); + + // update file record on disk + Status = UpdateFileRecord(Fcb->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; + } + + // Initialize the MCB, potentially catch an exception + _SEH2_TRY{ + FsRtlInitializeLargeMcb(&AttrContext->DataRunsMCB, NonPagedPool); + } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + _SEH2_YIELD(return _SEH2_GetExceptionCode()); + } _SEH2_END; + + // Now we can treat the attribute as non-resident and enlarge it normally + Status = SetAttributeDataLength(FileObject, Fcb, AttrContext, AttrOffset, FileRecord, DataSize); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Unable to migrate resident attribute!\n"); + if(AttribData != NULL) + ExFreePoolWithTag(AttribData, TAG_NTFS); + return Status; + } + + // restore the back-up attribute, if we made one + if (AttribDataSize.QuadPart > 0) + { + Status = WriteAttribute(Fcb->Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart, &LengthWritten); + 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); + } } } } @@ -371,7 +469,9 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, } } - InternalSetResidentAttributeLength(AttrContext, FileRecord, AttrOffset, DataSize->LowPart); + // set the new length of the resident attribute (if we didn't migrate it) + if(!AttrContext->Record.IsNonResident) + InternalSetResidentAttributeLength(AttrContext, FileRecord, AttrOffset, DataSize->LowPart); } //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord); diff --git a/drivers/filesystems/ntfs/volinfo.c b/drivers/filesystems/ntfs/volinfo.c index 338ea847509..1a577d45e23 100644 --- a/drivers/filesystems/ntfs/volinfo.c +++ b/drivers/filesystems/ntfs/volinfo.c @@ -127,21 +127,21 @@ NtfsAllocateClusters(PDEVICE_EXTENSION DeviceExt, TAG_NTFS); if (BitmapRecord == NULL) { - return 0; + return STATUS_INSUFFICIENT_RESOURCES; } Status = ReadFileRecord(DeviceExt, NTFS_FILE_BITMAP, BitmapRecord); if (!NT_SUCCESS(Status)) { ExFreePoolWithTag(BitmapRecord, TAG_NTFS); - return 0; + return Status; } Status = FindAttribute(DeviceExt, BitmapRecord, AttributeData, L"", 0, &DataContext, NULL); if (!NT_SUCCESS(Status)) { ExFreePoolWithTag(BitmapRecord, TAG_NTFS); - return 0; + return Status; } BitmapDataSize = AttributeDataLength(&DataContext->Record); @@ -152,7 +152,7 @@ NtfsAllocateClusters(PDEVICE_EXTENSION DeviceExt, { ReleaseAttributeContext(DataContext); ExFreePoolWithTag(BitmapRecord, TAG_NTFS); - return 0; + return STATUS_INSUFFICIENT_RESOURCES; } DPRINT1("Total clusters: %I64x\n", DeviceExt->NtfsInfo.ClusterCount); From 58402b67300e24e8822f7a1dd1f3997014ed25cf Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Sat, 13 May 2017 09:17:06 +0000 Subject: [PATCH 29/71] [NTFS] - Fix a bug with last commit, as spotted by Pierre. svn path=/branches/GSoC_2016/NTFS/; revision=74525 --- drivers/filesystems/ntfs/mft.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 1e23675a0cc..4dba9c66cdf 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -437,7 +437,7 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, if (!NT_SUCCESS(Status)) { DPRINT1("ERROR: Unable to migrate resident attribute!\n"); - if(AttribData != NULL) + if (AttribDataSize.QuadPart > 0) ExFreePoolWithTag(AttribData, TAG_NTFS); return Status; } From 7f762aac01103bffbbdd23807cef9731fdcf9936 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Sat, 27 May 2017 03:20:31 +0000 Subject: [PATCH 30/71] [NTFS] - Add support for changing a file's size via SetEndOfFile(): -Handle IRP_MJ_SET_INFORMATION IRP requests. +NtfsSetEndOfFile() - Sets the end of file (file size) for a given file. +NtfsSetInformation() - Sets the specified file information. At this point, only FileEndOfFileInformation is fully implemented. FileAllocationInformation is handled the same way and not truly implemented, but this works well enough for SetEndOfFile(). Overwriting a file in NTFS should now work in the majority of use cases. svn path=/branches/GSoC_2016/NTFS/; revision=74675 --- drivers/filesystems/ntfs/dispatch.c | 4 + drivers/filesystems/ntfs/finfo.c | 244 ++++++++++++++++++++++++++++ drivers/filesystems/ntfs/ntfs.c | 1 + drivers/filesystems/ntfs/ntfs.h | 9 + 4 files changed, 258 insertions(+) diff --git a/drivers/filesystems/ntfs/dispatch.c b/drivers/filesystems/ntfs/dispatch.c index 09140f2f320..bb67de73aac 100644 --- a/drivers/filesystems/ntfs/dispatch.c +++ b/drivers/filesystems/ntfs/dispatch.c @@ -81,6 +81,10 @@ NtfsDispatch(PNTFS_IRP_CONTEXT IrpContext) Status = NtfsQueryInformation(IrpContext); break; + case IRP_MJ_SET_INFORMATION: + Status = NtfsSetInformation(IrpContext); + break; + case IRP_MJ_DIRECTORY_CONTROL: Status = NtfsDirectoryControl(IrpContext); break; diff --git a/drivers/filesystems/ntfs/finfo.c b/drivers/filesystems/ntfs/finfo.c index 50604f847df..76cc78f4d9a 100644 --- a/drivers/filesystems/ntfs/finfo.c +++ b/drivers/filesystems/ntfs/finfo.c @@ -396,4 +396,248 @@ 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 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, + 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, (PULONGLONG)&CurrentFileSize); + + // 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"); + 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->Record); + + // 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"); + 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 = ROUND_UP(NewFileSize->QuadPart, Fcb->Vcb->NtfsInfo.BytesPerCluster); + + Status = UpdateFileNameRecord(Fcb->Vcb, ParentMFTId, &filename, FALSE, NewFileSize->QuadPart, AllocationSize); + + 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, &EndOfFileInfo->EndOfFile); + break; + + // TODO: all other information classes + + default: + DPRINT1("FIXME: Unimplemented information class %u\n", 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/ntfs.c b/drivers/filesystems/ntfs/ntfs.c index 94ba4f8f6a9..de36a653284 100644 --- a/drivers/filesystems/ntfs/ntfs.c +++ b/drivers/filesystems/ntfs/ntfs.c @@ -139,6 +139,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 462980d45df..68259177eec 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -796,6 +796,15 @@ NtfsMakeFCBFromDirEntry(PNTFS_VCB Vcb, NTSTATUS NtfsQueryInformation(PNTFS_IRP_CONTEXT IrpContext); +NTSTATUS +NtfsSetEndOfFile(PNTFS_FCB Fcb, + PFILE_OBJECT FileObject, + PDEVICE_EXTENSION DeviceExt, + ULONG IrpFlags, + PLARGE_INTEGER NewFileSize); + +NTSTATUS +NtfsSetInformation(PNTFS_IRP_CONTEXT IrpContext); /* fsctl.c */ From 037d88201d4312d6a4c1936124f76ed48af83f6d Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Sat, 27 May 2017 19:32:43 +0000 Subject: [PATCH 31/71] [NTFS] - Disable write support by default. Enable it via the registry. [BOOTDATA] - Add a commented-out section to hivesys.inf which can add the required key to enable NTFS write support. svn path=/branches/GSoC_2016/NTFS/; revision=74685 --- boot/bootdata/hivesys.inf | 2 ++ drivers/filesystems/ntfs/create.c | 16 +++++++++++- drivers/filesystems/ntfs/dispatch.c | 20 +++++++++++++-- drivers/filesystems/ntfs/ntfs.c | 40 ++++++++++++++++++++++++++++- drivers/filesystems/ntfs/ntfs.h | 1 + 5 files changed, 75 insertions(+), 4 deletions(-) diff --git a/boot/bootdata/hivesys.inf b/boot/bootdata/hivesys.inf index b0ece8594ab..2fdb5e30972 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/create.c b/drivers/filesystems/ntfs/create.c index 30312d61043..6513827c627 100644 --- a/drivers/filesystems/ntfs/create.c +++ b/drivers/filesystems/ntfs/create.c @@ -486,6 +486,13 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject, 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); @@ -545,7 +552,14 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject, RequestedDisposition == FILE_OPEN_IF || RequestedDisposition == FILE_OVERWRITE_IF || RequestedDisposition == FILE_SUPERSEDE) - { + { + if (!NtfsGlobalData->EnableWriteSupport) + { + DPRINT1("NTFS write-support is EXPERIMENTAL and is disabled by default!\n"); + NtfsCloseFile(DeviceExt, FileObject); + return STATUS_ACCESS_DENIED; + } + // Create the file record on disk Status = NtfsCreateFileRecord(DeviceExt, FileObject); diff --git a/drivers/filesystems/ntfs/dispatch.c b/drivers/filesystems/ntfs/dispatch.c index bb67de73aac..53d79303ed1 100644 --- a/drivers/filesystems/ntfs/dispatch.c +++ b/drivers/filesystems/ntfs/dispatch.c @@ -82,7 +82,15 @@ NtfsDispatch(PNTFS_IRP_CONTEXT IrpContext) break; case IRP_MJ_SET_INFORMATION: - Status = NtfsSetInformation(IrpContext); + 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: @@ -98,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/ntfs.c b/drivers/filesystems/ntfs/ntfs.c index de36a653284..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; } diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index 68259177eec..2177f1db0e3 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -151,6 +151,7 @@ typedef struct FAST_IO_DISPATCH FastIoDispatch; NPAGED_LOOKASIDE_LIST IrpContextLookasideList; NPAGED_LOOKASIDE_LIST FcbLookasideList; + BOOLEAN EnableWriteSupport; } NTFS_GLOBAL_DATA, *PNTFS_GLOBAL_DATA; From 4ca91513004b2f698941566b50fb1b81796b614f Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Sun, 28 May 2017 20:51:31 +0000 Subject: [PATCH 32/71] [NTFS] - Decrease debug spam svn path=/branches/GSoC_2016/NTFS/; revision=74694 --- drivers/filesystems/ntfs/mft.c | 1 - drivers/filesystems/ntfs/rw.c | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 4dba9c66cdf..19ae0d15c17 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -32,7 +32,6 @@ #include "ntfs.h" #define NDEBUG -#undef NDEBUG #include /* FUNCTIONS ****************************************************************/ diff --git a/drivers/filesystems/ntfs/rw.c b/drivers/filesystems/ntfs/rw.c index 5afb87e9e60..7f119d706b1 100644 --- a/drivers/filesystems/ntfs/rw.c +++ b/drivers/filesystems/ntfs/rw.c @@ -165,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) { @@ -184,7 +184,7 @@ NtfsReadFile(PDEVICE_EXTENSION DeviceExt, *LengthRead = ToRead; - DPRINT1("%lu got read\n", *LengthRead); + DPRINT("%lu got read\n", *LengthRead); if (AllocatedBuffer) { From e0048b1362c812705d326bf902b3712a258b4611 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Fri, 9 Jun 2017 03:14:30 +0000 Subject: [PATCH 33/71] [NTFS] - Add the most basic support for file creation. Expand diagnostic output, especially in NtfsDumpIndexRootAttribute(). Replace an ExFreePool() with ExFreePoolWithTag(). AddFileName() - Add a parameter to receive the Mft index of the parent directory. Fix so the name of the file will be stored in the attribute, not the name of the directory. NtfsCreateFile() - Open a file that was successfully created, instead of assuming failure. NtfsCreateFileRecord() - Add the filename attribute of the created file to the parent directory's index. +NtfsAddFilenameToDirectory() - Adds a $FILE_NAME attribute to a given directory index. Presently, a file can be created in an empty directory only. AddNewMftEntry() - Add a parameter to receive the mft index where the new entry was stored. svn path=/branches/GSoC_2016/NTFS/; revision=74970 --- drivers/filesystems/ntfs/attrib.c | 94 +++++++++-- drivers/filesystems/ntfs/create.c | 44 ++++-- drivers/filesystems/ntfs/dirctl.c | 250 ++++++++++++++++++++++++++++++ drivers/filesystems/ntfs/mft.c | 8 +- drivers/filesystems/ntfs/ntfs.h | 18 ++- 5 files changed, 388 insertions(+), 26 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index bb678e3ef8f..eeed58104e6 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -112,6 +112,9 @@ AddData(PFILE_RECORD_HEADER FileRecord, * Pointer to the FILE_OBJECT which represents the new name. * This parameter is used to determine the filename and parent directory. * +* @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. @@ -128,16 +131,18 @@ NTSTATUS AddFileName(PFILE_RECORD_HEADER FileRecord, PNTFS_ATTR_RECORD AttributeAddress, PDEVICE_EXTENSION DeviceExt, - PFILE_OBJECT FileObject) + PFILE_OBJECT FileObject, + 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; + UNICODE_STRING Current, Remaining, FilenameNoPath; NTSTATUS Status = STATUS_SUCCESS; ULONG FirstEntry = 0; + WCHAR Buffer[MAX_PATH]; if (AttributeAddress->Type != AttributeEnd) { @@ -162,21 +167,30 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, // we need to extract the filename from the path DPRINT1("Pathname: %wZ\n", &FileObject->FileName); + RtlZeroMemory(&FilenameNoPath, sizeof(UNICODE_STRING)); + FilenameNoPath.Buffer = Buffer; + FilenameNoPath.MaximumLength = MAX_PATH; + FsRtlDissectName(FileObject->FileName, &Current, &Remaining); while (Current.Length != 0) { DPRINT1("Current: %wZ\n", &Current); + if(Remaining.Length != 0) + RtlCopyUnicodeString(&FilenameNoPath, &Remaining); + Status = NtfsFindMftRecord(DeviceExt, CurrentMFTIndex, &Current, &FirstEntry, FALSE, &CurrentMFTIndex); if (!NT_SUCCESS(Status)) + break; + + if (Remaining.Length == 0 ) { + if(Current.Length != 0) + RtlCopyUnicodeString(&FilenameNoPath, &Current); break; } - if (Remaining.Length == 0) - break; - FsRtlDissectName(Current, &Current, &Remaining); } @@ -184,6 +198,9 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, // 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) @@ -191,19 +208,19 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, else FileNameAttribute->DirectoryFileReferenceNumber |= (ULONGLONG)FileRecord->SequenceNumber << 48; - DPRINT1("FileNameAttribute->DirectoryFileReferenceNumber: 0x%I64x\n", FileNameAttribute->DirectoryFileReferenceNumber); + DPRINT1("FileNameAttribute->DirectoryFileReferenceNumber: 0x%016I64x\n", FileNameAttribute->DirectoryFileReferenceNumber); - FileNameAttribute->NameLength = Current.Length / 2; + FileNameAttribute->NameLength = FilenameNoPath.Length / 2; // TODO: Get proper nametype, add DOS links as needed FileNameAttribute->NameType = NTFS_FILE_NAME_WIN32_AND_DOS; - RtlCopyMemory(FileNameAttribute->Name, Current.Buffer, Current.Length); + RtlCopyMemory(FileNameAttribute->Name, FilenameNoPath.Buffer, FilenameNoPath.Length); FileRecord->LinkCount++; AttributeAddress->Length = ResidentHeaderLength + - FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + Current.Length; + FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + FilenameNoPath.Length; AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, 8); - AttributeAddress->Resident.ValueLength = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + Current.Length; + AttributeAddress->Resident.ValueLength = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + FilenameNoPath.Length; AttributeAddress->Resident.ValueOffset = ResidentHeaderLength; AttributeAddress->Resident.Flags = 1; // indexed @@ -1054,6 +1071,7 @@ NtfsDumpFileNameAttribute(PNTFS_ATTR_RECORD Attribute) DbgPrint(" (%x) '%.*S' ", FileNameAttr->NameType, FileNameAttr->NameLength, FileNameAttr->Name); DbgPrint(" '%x' \n", FileNameAttr->FileAttributes); DbgPrint(" AllocatedSize: %I64u\nDataSize: %I64u\n", FileNameAttr->AllocatedSize, FileNameAttr->DataSize); + DbgPrint(" File reference: 0x%016I64x\n", FileNameAttr->DirectoryFileReferenceNumber); } @@ -1110,23 +1128,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 %u", currentNode++); + if (currentIndexExtry->Flags & NTFS_INDEX_ENTRY_NODE) + DbgPrint(" (Branch)"); + else + DbgPrint(" (Leaf)"); + if((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 - 8); + DbgPrint(" VCN of sub-node: 0x%llx\n", *SubNodeVCN); + } + + currentOffset += currentIndexExtry->Length; + ASSERT(currentIndexExtry->Length); + } + } diff --git a/drivers/filesystems/ntfs/create.c b/drivers/filesystems/ntfs/create.c index 6513827c627..ef39b44373d 100644 --- a/drivers/filesystems/ntfs/create.c +++ b/drivers/filesystems/ntfs/create.c @@ -523,7 +523,7 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject, DoneOverwriting: if (fileRecord) - ExFreePool(fileRecord); + ExFreePoolWithTag(fileRecord, TAG_NTFS); if (dataContext) ReleaseAttributeContext(dataContext); @@ -562,13 +562,14 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject, // Create the file record on disk Status = NtfsCreateFileRecord(DeviceExt, FileObject); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Couldn't create file record!\n"); + return Status; + } - // Update the parent directory index - // Still TODO - - // Call NtfsOpenFile() - - return STATUS_CANNOT_MAKE; + // Now we should be able to open the file + return NtfsCreateFile(DeviceObject, Irp); } } @@ -624,7 +625,8 @@ NtfsCreate(PNTFS_IRP_CONTEXT IrpContext) * @name NtfsCreateFileRecord() * @implemented * -* Creates a file record and saves it to the MFT. +* 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 @@ -643,6 +645,9 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, NTSTATUS Status = STATUS_SUCCESS; PFILE_RECORD_HEADER FileRecord; PNTFS_ATTR_RECORD NextAttribute; + PFILENAME_ATTRIBUTE FilenameAttribute; + ULONGLONG ParentMftIndex; + ULONGLONG FileMftIndex; // allocate memory for file record FileRecord = ExAllocatePoolWithTag(NonPagedPool, @@ -686,7 +691,10 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)NextAttribute + (ULONG_PTR)NextAttribute->Length); // Add the $FILE_NAME attribute - AddFileName(FileRecord, NextAttribute, DeviceExt, FileObject); + AddFileName(FileRecord, NextAttribute, DeviceExt, FileObject, &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); @@ -698,7 +706,23 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, NtfsDumpFileRecord(DeviceExt, FileRecord); // Now that we've built the file record in memory, we need to store it in the MFT. - Status = AddNewMftEntry(FileRecord, DeviceExt); + Status = AddNewMftEntry(FileRecord, DeviceExt, &FileMftIndex); + 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); + } ExFreePoolWithTag(FileRecord, TAG_NTFS); diff --git a/drivers/filesystems/ntfs/dirctl.c b/drivers/filesystems/ntfs/dirctl.c index aaa6dfcb87f..91f0bc5db70 100644 --- a/drivers/filesystems/ntfs/dirctl.c +++ b/drivers/filesystems/ntfs/dirctl.c @@ -34,6 +34,256 @@ /* FUNCTIONS ****************************************************************/ +/** +* @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. +* +* @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 an empty 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) +{ + NTSTATUS Status = STATUS_SUCCESS; + PFILE_RECORD_HEADER ParentFileRecord; + PNTFS_ATTR_CONTEXT DirectoryContext; + PINDEX_ROOT_ATTRIBUTE I30IndexRoot; + ULONG IndexRootOffset; + ULONGLONG I30IndexRootLength; + PINDEX_ENTRY_ATTRIBUTE IndexNodeEntry; + ULONG LengthWritten; + PNTFS_ATTR_RECORD DestinationAttribute; + PINDEX_ROOT_ATTRIBUTE NewIndexRoot; + ULONG AttributeLength; + PNTFS_ATTR_RECORD NextAttribute; + + // 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; + } + + DPRINT1("Dumping old parent file record:\n"); + NtfsDumpFileRecord(DeviceExt, ParentFileRecord); + + // Find the index root attribute for the directory + Status = FindAttribute(DeviceExt, + ParentFileRecord, + AttributeIndexRoot, + L"$I30", + 4, + &DirectoryContext, + &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; + } + + I30IndexRootLength = AttributeDataLength(&DirectoryContext->Record); + + // Allocate memory for the index root data + I30IndexRoot = (PINDEX_ROOT_ATTRIBUTE)ExAllocatePoolWithTag(NonPagedPool, I30IndexRootLength, TAG_NTFS); + if (!I30IndexRoot) + { + DPRINT1("ERROR: Couldn't allocate memory for index root attribute!\n"); + ReleaseAttributeContext(DirectoryContext); + ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); + } + + // Read the Index Root + Status = ReadAttribute(DeviceExt, DirectoryContext, 0, (PCHAR)I30IndexRoot, I30IndexRootLength); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Couln't read index root attribute for Mft index #%I64u\n", DirectoryMftIndex); + ReleaseAttributeContext(DirectoryContext); + ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); + ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); + return Status; + } + + // Make sure it's empty (temporarily) + IndexNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)I30IndexRoot + I30IndexRoot->Header.FirstEntryOffset + 0x10); + if (IndexNodeEntry->Data.Directory.IndexedFile != 0 || IndexNodeEntry->Flags != 2) + { + DPRINT1("FIXME: File-creation is only supported in empty directories right now! Be patient! :)\n"); + ReleaseAttributeContext(DirectoryContext); + ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); + ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); + return STATUS_NOT_IMPLEMENTED; + } + + // Now we need to setup a new index root attribute to replace the old one + NewIndexRoot = ExAllocatePoolWithTag(NonPagedPool, DeviceExt->NtfsInfo.BytesPerIndexRecord, TAG_NTFS); + if (!NewIndexRoot) + { + DPRINT1("ERROR: Unable to allocate memory for new index root attribute!\n"); + ReleaseAttributeContext(DirectoryContext); + ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); + ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Setup the new index record + RtlZeroMemory(NewIndexRoot, DeviceExt->NtfsInfo.BytesPerIndexRecord); // shouldn't be necessary but aids in debugging + + 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 = 0x10; + NewIndexRoot->Header.Flags = INDEX_ROOT_SMALL; + // still need to calculate sizes + + // The first index node entry will be for the filename we're adding + IndexNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)NewIndexRoot + NewIndexRoot->Header.FirstEntryOffset + 0x10); + IndexNodeEntry->Data.Directory.IndexedFile = FileReferenceNumber; + IndexNodeEntry->Flags = INDEX_ROOT_SMALL; + IndexNodeEntry->KeyLength = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + (2 * FilenameAttribute->NameLength); + IndexNodeEntry->Length = ALIGN_UP_BY(IndexNodeEntry->KeyLength, 8) + FIELD_OFFSET(INDEX_ENTRY_ATTRIBUTE, FileName); + + // Now we can calculate the Node length (temp logic) + NewIndexRoot->Header.TotalSizeOfEntries = NewIndexRoot->Header.FirstEntryOffset + IndexNodeEntry->Length + 0x10; + NewIndexRoot->Header.AllocatedSize = NewIndexRoot->Header.TotalSizeOfEntries; + + DPRINT1("New Index Node Entry Stream Length: %u\nNew Inde Node Entry Length: %u\n", + IndexNodeEntry->KeyLength, + IndexNodeEntry->Length); + + // copy over the attribute proper + RtlCopyMemory(&IndexNodeEntry->FileName, FilenameAttribute, IndexNodeEntry->KeyLength); + + // Now setup the dummy key + IndexNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexNodeEntry + IndexNodeEntry->Length); + + IndexNodeEntry->Data.Directory.IndexedFile = 0; + IndexNodeEntry->Length = 0x10; + IndexNodeEntry->KeyLength = 0; + IndexNodeEntry->Flags = NTFS_INDEX_ENTRY_END; + + // This is when we'd normally setup the length (already done above) + + // Write back the new index root attribute to the parent directory file record + + // First, we need to make sure the attribute is large enough. + // We can't set the size as we normally would, because if we extend past the file record, + // we must create an index allocation and index bitmap (TODO). Also TODO: support file records with + // $ATTRIBUTE_LIST's. + AttributeLength = NewIndexRoot->Header.AllocatedSize + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header); + DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset); + + NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)DestinationAttribute + DestinationAttribute->Length); + if (NextAttribute->Type != AttributeEnd) + { + DPRINT1("FIXME: For now, only resizing index root at the end of a file record is supported!\n"); + ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); + ReleaseAttributeContext(DirectoryContext); + ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); + ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); + return STATUS_NOT_IMPLEMENTED; + } + + // Update the length of the attribute in the file record of the parent directory + InternalSetResidentAttributeLength(DirectoryContext, + ParentFileRecord, + IndexRootOffset, + AttributeLength); + + 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); + ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); + return Status; + } + + // Update the parent directory with the new index root + Status = WriteAttribute(DeviceExt, + DirectoryContext, + 0, + (PUCHAR)NewIndexRoot, + AttributeLength, + &LengthWritten); + if (!NT_SUCCESS(Status) ) + { + DPRINT1("ERROR: Unable to write new index root attribute to parent directory!\n"); + ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); + ReleaseAttributeContext(DirectoryContext); + 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 + { + DPRINT1("Dumping new parent file record:\n"); + NtfsDumpFileRecord(DeviceExt, ParentFileRecord); + } + + // Cleanup + ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); + ReleaseAttributeContext(DirectoryContext); + ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); + ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); + + return Status; +} ULONGLONG NtfsGetFileSize(PDEVICE_EXTENSION DeviceExt, diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 19ae0d15c17..5b9b291e4ef 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -1361,6 +1361,9 @@ FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb, * @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. +* * @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 @@ -1371,7 +1374,8 @@ FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb, */ NTSTATUS AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, - PDEVICE_EXTENSION DeviceExt) + PDEVICE_EXTENSION DeviceExt, + PULONGLONG DestinationIndex) { NTSTATUS Status = STATUS_SUCCESS; ULONGLONG MftIndex; @@ -1454,6 +1458,8 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, return Status; } + *DestinationIndex = MftIndex; + ExFreePoolWithTag(BitmapData, TAG_NTFS); ReleaseAttributeContext(BitmapContext); diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index 2177f1db0e3..a3265db9da6 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -534,7 +534,8 @@ NTSTATUS AddFileName(PFILE_RECORD_HEADER FileRecord, PNTFS_ATTR_RECORD AttributeAddress, PDEVICE_EXTENSION DeviceExt, - PFILE_OBJECT FileObject); + PFILE_OBJECT FileObject, + PULONGLONG ParentMftIndex); NTSTATUS AddStandardInformation(PFILE_RECORD_HEADER FileRecord, @@ -675,6 +676,12 @@ NtfsDeviceControl(PNTFS_IRP_CONTEXT IrpContext); /* dirctl.c */ +NTSTATUS +NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, + ULONGLONG DirectoryMftIndex, + ULONGLONG FileMftIndex, + PFILENAME_ATTRIBUTE FilenameAttribute); + ULONGLONG NtfsGetFileSize(PDEVICE_EXTENSION DeviceExt, PFILE_RECORD_HEADER FileRecord, @@ -816,7 +823,8 @@ NtfsFileSystemControl(PNTFS_IRP_CONTEXT IrpContext); /* mft.c */ NTSTATUS AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, - PDEVICE_EXTENSION DeviceExt); + PDEVICE_EXTENSION DeviceExt, + PULONGLONG DestinationIndex); PNTFS_ATTR_CONTEXT PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord); @@ -842,6 +850,12 @@ WriteAttribute(PDEVICE_EXTENSION Vcb, ULONGLONG AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord); +VOID +InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext, + PFILE_RECORD_HEADER FileRecord, + ULONG AttrOffset, + ULONG DataSize); + NTSTATUS SetAttributeDataLength(PFILE_OBJECT FileObject, PNTFS_FCB Fcb, From 9dce6f4db1ec015588ccf7d5657693d5a0697390 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Fri, 16 Jun 2017 05:42:01 +0000 Subject: [PATCH 34/71] [NTFS] - Add some improvements to the previous commit, as suggested by Thomas. svn path=/branches/GSoC_2016/NTFS/; revision=75054 --- drivers/filesystems/ntfs/attrib.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index eeed58104e6..78be3ecf3ce 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -167,9 +167,7 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, // we need to extract the filename from the path DPRINT1("Pathname: %wZ\n", &FileObject->FileName); - RtlZeroMemory(&FilenameNoPath, sizeof(UNICODE_STRING)); - FilenameNoPath.Buffer = Buffer; - FilenameNoPath.MaximumLength = MAX_PATH; + RtlInitEmptyUnicodeString(&FilenameNoPath, Buffer, MAX_PATH); FsRtlDissectName(FileObject->FileName, &Current, &Remaining); @@ -210,7 +208,7 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, DPRINT1("FileNameAttribute->DirectoryFileReferenceNumber: 0x%016I64x\n", FileNameAttribute->DirectoryFileReferenceNumber); - FileNameAttribute->NameLength = FilenameNoPath.Length / 2; + FileNameAttribute->NameLength = FilenameNoPath.Length / sizeof(WCHAR); // TODO: Get proper nametype, add DOS links as needed FileNameAttribute->NameType = NTFS_FILE_NAME_WIN32_AND_DOS; RtlCopyMemory(FileNameAttribute->Name, FilenameNoPath.Buffer, FilenameNoPath.Length); From 1417f286e03dbb76ad9ddd99929fbfef5b789a68 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Fri, 16 Jun 2017 05:43:52 +0000 Subject: [PATCH 35/71] [NTFS] - Restructure some code in preparation for the next commit: -SetAttributeDataLength() has been split into two functions, SetNonResidentAttributeDataLength() and SetResidentAttributeDataLength(). This should improve code readibility and allows for resizing an attribute when there's no FileObject associated with it. -Added "MftDataOffset" member to DEVICE_EXTENSION, which stores the offset of the Mft's $DATA attribute. (I'm starting to think it's better to add a member for offset to NTFS_ATTR_CONTEXT directly, but I'll save that level of restructuring for a future commit.) svn path=/branches/GSoC_2016/NTFS/; revision=75055 --- drivers/filesystems/ntfs/fsctl.c | 8 +- drivers/filesystems/ntfs/mft.c | 557 +++++++++++++++++++------------ drivers/filesystems/ntfs/ntfs.h | 15 + 3 files changed, 360 insertions(+), 220 deletions(-) diff --git a/drivers/filesystems/ntfs/fsctl.c b/drivers/filesystems/ntfs/fsctl.c index a1f3048cd06..8acdd64e169 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, NULL); + 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"); diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 5b9b291e4ef..52a7caaa43d 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -234,7 +234,6 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, PLARGE_INTEGER DataSize) { NTSTATUS Status = STATUS_SUCCESS; - ULONG BytesPerCluster = Fcb->Vcb->NtfsInfo.BytesPerCluster; // are we truncating the file? if (DataSize->QuadPart < AttributeDataLength(&AttrContext->Record)) @@ -248,229 +247,26 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, if (AttrContext->Record.IsNonResident) { - ULONGLONG AllocationSize = ROUND_UP(DataSize->QuadPart, BytesPerCluster); - PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset); - ULONG ExistingClusters = AttrContext->Record.NonResident.AllocatedSize / BytesPerCluster; - - // do we need to increase the allocation size? - if (AttrContext->Record.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->Record.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->Record.NonResident.HighestVCN); - return STATUS_INVALID_PARAMETER; - } - } - - DPRINT("LastClusterInDataRun: %I64u\n", LastClusterInDataRun.QuadPart); - DPRINT("Highest VCN of record: %I64u\n", AttrContext->Record.NonResident.HighestVCN); - - while (ClustersNeeded > 0) - { - Status = NtfsAllocateClusters(Fcb->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(Fcb->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->Record.NonResident.AllocatedSize > AllocationSize) - { - // shrink allocation size - ULONG ClustersToFree = ExistingClusters - (AllocationSize / BytesPerCluster); - Status = FreeClusters(Fcb->Vcb, AttrContext, AttrOffset, FileRecord, ClustersToFree); - } - - // TODO: is the file compressed, encrypted, or sparse? - - // NOTE: we need to have acquired the main resource exclusively, as well as(?) the PagingIoResource - - Fcb->RFCB.AllocationSize.QuadPart = AllocationSize; - AttrContext->Record.NonResident.AllocatedSize = AllocationSize; - AttrContext->Record.NonResident.DataSize = DataSize->QuadPart; - AttrContext->Record.NonResident.InitializedSize = DataSize->QuadPart; - - DestinationAttribute->NonResident.AllocatedSize = AllocationSize; - DestinationAttribute->NonResident.DataSize = DataSize->QuadPart; - DestinationAttribute->NonResident.InitializedSize = DataSize->QuadPart; - - DPRINT("Allocated Size: %I64u\n", DestinationAttribute->NonResident.AllocatedSize); + Status = SetNonResidentAttributeDataLength(Fcb->Vcb, + AttrContext, + AttrOffset, + FileRecord, + DataSize); } else { // resident attribute + Status = SetResidentAttributeDataLength(Fcb->Vcb, + AttrContext, + AttrOffset, + FileRecord, + DataSize); + } - // find the next attribute - ULONG NextAttributeOffset = AttrOffset + AttrContext->Record.Length; - PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((PCHAR)FileRecord + NextAttributeOffset); - - //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); - - if (MaxValueLength < DataSize->LowPart || NextAttribute->Type != AttributeEnd) - { - // convert attribute to non-resident - PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset); - LARGE_INTEGER AttribDataSize; - PVOID AttribData; - ULONG EndAttributeOffset; - ULONG LengthWritten; - - DPRINT1("Converting attribute to non-resident.\n"); - - AttribDataSize.QuadPart = AttrContext->Record.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(Fcb->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. - - // Zero out the NonResident structure - RtlZeroMemory(&AttrContext->Record.NonResident.LowestVCN, - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize) - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.LowestVCN)); - RtlZeroMemory(&Destination->NonResident.LowestVCN, - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize) - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.LowestVCN)); - - // update the mapping pairs offset, which will be 0x40 + length in bytes of the name - AttrContext->Record.NonResident.MappingPairsOffset = Destination->NonResident.MappingPairsOffset = 0x40 + (Destination->NameLength * 2); - - // mark the attribute as non-resident - AttrContext->Record.IsNonResident = Destination->IsNonResident = 1; - - // update the end of the file record - // calculate position of end markers (1 byte for empty data run) - EndAttributeOffset = AttrOffset + AttrContext->Record.NonResident.MappingPairsOffset + 1; - EndAttributeOffset = ALIGN_UP_BY(EndAttributeOffset, 8); - - // Update the length - Destination->Length = EndAttributeOffset - AttrOffset; - AttrContext->Record.Length = Destination->Length; - - // Update the file record end - SetFileRecordEnd(FileRecord, - (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + EndAttributeOffset), - FILE_RECORD_END); - - // update file record on disk - Status = UpdateFileRecord(Fcb->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; - } - - // Initialize the MCB, potentially catch an exception - _SEH2_TRY{ - FsRtlInitializeLargeMcb(&AttrContext->DataRunsMCB, NonPagedPool); - } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { - _SEH2_YIELD(return _SEH2_GetExceptionCode()); - } _SEH2_END; - - // Now we can treat the attribute as non-resident and enlarge it normally - Status = SetAttributeDataLength(FileObject, Fcb, 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(Fcb->Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart, &LengthWritten); - 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); - } - } - } - } - 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; - } - } - - // set the new length of the resident attribute (if we didn't migrate it) - if(!AttrContext->Record.IsNonResident) - InternalSetResidentAttributeLength(AttrContext, FileRecord, AttrOffset, DataSize->LowPart); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to set size of attribute!\n"); + return Status; } //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord); @@ -480,6 +276,10 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, if (NT_SUCCESS(Status)) { + if(AttrContext->Record.IsNonResident) + Fcb->RFCB.AllocationSize.QuadPart = AttrContext->Record.NonResident.AllocatedSize; + else + Fcb->RFCB.AllocationSize = *DataSize; Fcb->RFCB.FileSize = *DataSize; Fcb->RFCB.ValidDataLength = *DataSize; CcSetFileSizes(FileObject, (PCC_FILE_SIZES)&Fcb->RFCB.AllocationSize); @@ -523,6 +323,325 @@ SetFileRecordEnd(PFILE_RECORD_HEADER FileRecord, 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(). 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->Record.NonResident.AllocatedSize / BytesPerCluster; + + if (!AttrContext->Record.IsNonResident) + { + DPRINT1("ERROR: SetNonResidentAttributeDataLength() called for resident attribute!\n"); + return STATUS_INVALID_PARAMETER; + } + + // do we need to increase the allocation size? + if (AttrContext->Record.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->Record.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->Record.NonResident.HighestVCN); + return STATUS_INVALID_PARAMETER; + } + } + + DPRINT("LastClusterInDataRun: %I64u\n", LastClusterInDataRun.QuadPart); + DPRINT("Highest VCN of record: %I64u\n", AttrContext->Record.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->Record.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->Record.NonResident.AllocatedSize = AllocationSize; + AttrContext->Record.NonResident.DataSize = DataSize->QuadPart; + AttrContext->Record.NonResident.InitializedSize = DataSize->QuadPart; + + DestinationAttribute->NonResident.AllocatedSize = AllocationSize; + DestinationAttribute->NonResident.DataSize = DataSize->QuadPart; + DestinationAttribute->NonResident.InitializedSize = DataSize->QuadPart; + + 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(). 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->Record.Length; + PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((PCHAR)FileRecord + NextAttributeOffset); + + if (AttrContext->Record.IsNonResident) + { + DPRINT1("ERROR: SetResidentAttributeDataLength() called for non-resident attribute!\n"); + return STATUS_INVALID_PARAMETER; + } + + //NtfsDumpFileAttributes(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 += 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); + LARGE_INTEGER AttribDataSize; + PVOID AttribData; + ULONG EndAttributeOffset; + ULONG LengthWritten; + + DPRINT1("Converting attribute to non-resident.\n"); + + AttribDataSize.QuadPart = AttrContext->Record.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. + + // Zero out the NonResident structure + RtlZeroMemory(&AttrContext->Record.NonResident.LowestVCN, + FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize) - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.LowestVCN)); + RtlZeroMemory(&Destination->NonResident.LowestVCN, + FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize) - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.LowestVCN)); + + // update the mapping pairs offset, which will be 0x40 + length in bytes of the name + AttrContext->Record.NonResident.MappingPairsOffset = Destination->NonResident.MappingPairsOffset = 0x40 + (Destination->NameLength * 2); + + // mark the attribute as non-resident + AttrContext->Record.IsNonResident = Destination->IsNonResident = 1; + + // update the end of the file record + // calculate position of end markers (1 byte for empty data run) + EndAttributeOffset = AttrOffset + AttrContext->Record.NonResident.MappingPairsOffset + 1; + EndAttributeOffset = ALIGN_UP_BY(EndAttributeOffset, 8); + + // Update the length + Destination->Length = EndAttributeOffset - AttrOffset; + AttrContext->Record.Length = Destination->Length; + + // Update the file record end + SetFileRecordEnd(FileRecord, + (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + EndAttributeOffset), + FILE_RECORD_END); + + // 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; + } + + // Initialize the MCB, potentially catch an exception + _SEH2_TRY{ + FsRtlInitializeLargeMcb(&AttrContext->DataRunsMCB, NonPagedPool); + } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + _SEH2_YIELD(return _SEH2_GetExceptionCode()); + } _SEH2_END; + + // 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); + 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); + } + } + } + } + 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; + } + } + + // set the new length of the resident attribute (if we didn't migrate it) + if (!AttrContext->Record.IsNonResident) + InternalSetResidentAttributeLength(AttrContext, FileRecord, AttrOffset, DataSize->LowPart); + + return STATUS_SUCCESS; +} + ULONG ReadAttribute(PDEVICE_EXTENSION Vcb, PNTFS_ATTR_CONTEXT Context, diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index a3265db9da6..b9336758efb 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; @@ -869,6 +870,20 @@ 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); From 9ab86116a9cdddac4ced4ea66dc6d228dc751aa9 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Fri, 16 Jun 2017 06:00:09 +0000 Subject: [PATCH 36/71] [NTFS] - Add support for expanding the master file table. Fix a bug with BrowseIndexEntries(). Improve diagnostic output. -AddNewMftEntry() - Increase size of MFT as needed. Fix math for bitmap length. Don't assign file records to MFT indices 0x10 - 0x17; In Windows, these records aren't used unless they have to be, even though they are marked as unused in the bitmap. +IncreaseMftSize() - Adds room for additional file records in the master file table. -BrowseIndexEntries() - allow for the rare situation when a non-system file has an MFT index of 0x10. svn path=/branches/GSoC_2016/NTFS/; revision=75056 --- drivers/filesystems/ntfs/create.c | 2 + drivers/filesystems/ntfs/mft.c | 199 ++++++++++++++++++++++++++++-- 2 files changed, 192 insertions(+), 9 deletions(-) diff --git a/drivers/filesystems/ntfs/create.c b/drivers/filesystems/ntfs/create.c index ef39b44373d..07a9f5ac3e1 100644 --- a/drivers/filesystems/ntfs/create.c +++ b/drivers/filesystems/ntfs/create.c @@ -649,6 +649,8 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, ULONGLONG ParentMftIndex; ULONGLONG FileMftIndex; + DPRINT1("NtfsCreateFileRecord(%p, %p)\n", DeviceExt, FileObject); + // allocate memory for file record FileRecord = ExAllocatePoolWithTag(NonPagedPool, DeviceExt->NtfsInfo.BytesPerFileRecord, diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 52a7caaa43d..62b29790314 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -186,6 +186,161 @@ 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. +* +* @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. +* +* @remarks +* Increases the size of the Master File Table by 8 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) +{ + PNTFS_ATTR_CONTEXT BitmapContext; + LARGE_INTEGER BitmapSize; + LARGE_INTEGER DataSize; + LONGLONG BitmapSizeDifference; + ULONG DataSizeDifference = Vcb->NtfsInfo.BytesPerFileRecord * 8; + ULONG BitmapOffset; + PUCHAR BitmapBuffer; + ULONGLONG BitmapBytes; + ULONGLONG NewBitmapSize; + ULONG BytesRead; + ULONG LengthWritten; + NTSTATUS Status; + + DPRINT1("IncreaseMftSize(%p)\n", Vcb); + + // We need exclusive access to the mft while we change its size + if (!ExAcquireResourceExclusiveLite(&(Vcb->DirResource), TRUE)) + { + return STATUS_CANT_WAIT; + } + + // Find the bitmap attribute of master file table + Status = FindAttribute(Vcb, Vcb->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, &BitmapOffset); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Couldn't find $BITMAP attribute of Mft!\n"); + ExReleaseResourceLite(&(Vcb->DirResource)); + return Status; + } + + // Get size of Bitmap Attribute + BitmapSize.QuadPart = AttributeDataLength(&BitmapContext->Record); + + // Calculate the new mft size + DataSize.QuadPart = AttributeDataLength(&(Vcb->MFTContext->Record)) + DataSizeDifference; + + // Determine how many bytes will make up the bitmap + BitmapBytes = DataSize.QuadPart / Vcb->NtfsInfo.BytesPerFileRecord / 8; + + // 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"); + ExReleaseResourceLite(&(Vcb->DirResource)); + ReleaseAttributeContext(BitmapContext); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Zero the bytes we'll be adding + RtlZeroMemory((PUCHAR)((ULONG_PTR)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"); + 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"); + ExReleaseResourceLite(&(Vcb->DirResource)); + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + return Status; + } + + // If the bitmap grew + if (BitmapSizeDifference > 0) + { + // Set the new bitmap size + BitmapSize.QuadPart += BitmapSizeDifference; + if (BitmapContext->Record.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"); + ExReleaseResourceLite(&(Vcb->DirResource)); + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + return Status; + } + } + + //NtfsDumpFileAttributes(Vcb, FileRecord); + + // 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"); + ExReleaseResourceLite(&(Vcb->DirResource)); + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + return Status; + } + + // Write out the new bitmap + Status = WriteAttribute(Vcb, BitmapContext, BitmapOffset, BitmapBuffer, BitmapSize.LowPart, &LengthWritten); + if (!NT_SUCCESS(Status)) + { + ExReleaseResourceLite(&(Vcb->DirResource)); + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + DPRINT1("ERROR: Couldn't write to bitmap attribute of $MFT!\n"); + } + + // Cleanup + ExReleaseResourceLite(&(Vcb->DirResource)); + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + + return STATUS_SUCCESS; +} + VOID InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext, PFILE_RECORD_HEADER FileRecord, @@ -351,7 +506,7 @@ SetFileRecordEnd(PFILE_RECORD_HEADER FileRecord, * STATUS_INVALID_PARAMETER if we can't find the last cluster in the data run. * * @remarks -* Called by SetAttributeDataLength(). Use SetAttributeDataLength() unless you have a good +* 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. */ @@ -485,7 +640,7 @@ SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, * last attribute listed in the file record. * * @remarks -* Called by SetAttributeDataLength(). Use SetAttributeDataLength() unless you have a good +* 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. */ @@ -1488,7 +1643,6 @@ FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb, * 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_NOT_IMPLEMENTED if we need to increase the size of the MFT. * */ NTSTATUS @@ -1503,9 +1657,13 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, ULONGLONG AttrBytesRead; PVOID BitmapData; ULONG LengthWritten; + PNTFS_ATTR_CONTEXT BitmapContext; + LARGE_INTEGER BitmapBits; + UCHAR SystemReservedBits; + + DPRINT1("AddNewMftEntry(%p, %p, %p)\n", FileRecord, DeviceExt, DestinationIndex); // First, we have to read the mft's $Bitmap attribute - PNTFS_ATTR_CONTEXT BitmapContext; Status = FindAttribute(DeviceExt, DeviceExt->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL); if (!NT_SUCCESS(Status)) { @@ -1533,20 +1691,40 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, return STATUS_OBJECT_NAME_NOT_FOUND; } + // we need to backup the bits for records 0x10 - 0x17 and leave them unassigned if they aren't assigned + RtlCopyMemory(&SystemReservedBits, (PVOID)((ULONG_PTR)BitmapData + 2), 1); + RtlFillMemory((PVOID)((ULONG_PTR)BitmapData + 2), 1, (UCHAR)0xFF); + + // Calculate bit count + BitmapBits.QuadPart = AttributeDataLength(&(DeviceExt->MFTContext->Record)) / + DeviceExt->NtfsInfo.BytesPerFileRecord; + if (BitmapBits.HighPart != 0) + { + DPRINT1("\tFIXME: bitmap sizes beyond 32bits are not yet supported!\n"); + BitmapBits.LowPart = 0xFFFFFFFF; + } + // convert buffer into bitmap - RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, BitmapDataSize * 8); + RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, BitmapBits.LowPart); // set next available bit, preferrably after 23rd bit MftIndex = RtlFindClearBitsAndSet(&Bitmap, 1, 24); if ((LONG)MftIndex == -1) { - DPRINT1("ERROR: Couldn't find free space in MFT for file record!\n"); + DPRINT1("Couldn't find free space in MFT for file record, increasing MFT size.\n"); ExFreePoolWithTag(BitmapData, TAG_NTFS); ReleaseAttributeContext(BitmapContext); - // TODO: increase mft size - return STATUS_NOT_IMPLEMENTED; + // Couldn't find a free record in the MFT, add some blank records and try again + Status = IncreaseMftSize(DeviceExt); + 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); } DPRINT1("Creating file record at MFT index: %I64u\n", MftIndex); @@ -1556,6 +1734,9 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, // [BitmapData should have been updated via RtlFindClearBitsAndSet()] + // Restore the system reserved bits + RtlCopyMemory((PVOID)((ULONG_PTR)BitmapData + 2), &SystemReservedBits, 1); + // write the bitmap back to the MFT's $Bitmap attribute Status = WriteAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize, &LengthWritten); if (!NT_SUCCESS(Status)) @@ -1723,7 +1904,7 @@ BrowseIndexEntries(PDEVICE_EXTENSION Vcb, while (IndexEntry < LastEntry && !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END)) { - if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) > 0x10 && + if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) >= 0x10 && *CurrentEntry >= *StartEntry && IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS && CompareFileName(FileName, IndexEntry, DirSearch)) From 98ddf610bcd804bebd056f1916b59e46dc5388eb Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Fri, 23 Jun 2017 17:30:13 +0000 Subject: [PATCH 37/71] [NTFS] - Fix IncreaseMftSize(); check IrpContext to see if waiting for exclusive access to the MFT is allowed. As pointed out by Pierre. svn path=/branches/GSoC_2016/NTFS/; revision=75170 --- drivers/filesystems/ntfs/create.c | 24 ++++++++++++++++-------- drivers/filesystems/ntfs/mft.c | 27 +++++++++++++++++++-------- drivers/filesystems/ntfs/ntfs.h | 6 ++++-- 3 files changed, 39 insertions(+), 18 deletions(-) diff --git a/drivers/filesystems/ntfs/create.c b/drivers/filesystems/ntfs/create.c index 07a9f5ac3e1..96f563e75dd 100644 --- a/drivers/filesystems/ntfs/create.c +++ b/drivers/filesystems/ntfs/create.c @@ -323,7 +323,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,8 +334,9 @@ 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); @@ -561,7 +562,7 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject, } // Create the file record on disk - Status = NtfsCreateFileRecord(DeviceExt, FileObject); + Status = NtfsCreateFileRecord(DeviceExt, FileObject, BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT)); if (!NT_SUCCESS(Status)) { DPRINT1("ERROR: Couldn't create file record!\n"); @@ -569,7 +570,7 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject, } // Now we should be able to open the file - return NtfsCreateFile(DeviceObject, Irp); + return NtfsCreateFile(DeviceObject, IrpContext); } } @@ -615,7 +616,7 @@ NtfsCreate(PNTFS_IRP_CONTEXT IrpContext) ExAcquireResourceExclusiveLite(&DeviceExt->DirResource, TRUE); Status = NtfsCreateFile(DeviceObject, - IrpContext->Irp); + IrpContext); ExReleaseResourceLite(&DeviceExt->DirResource); return Status; @@ -634,13 +635,20 @@ NtfsCreate(PNTFS_IRP_CONTEXT IrpContext) * @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) + PFILE_OBJECT FileObject, + BOOLEAN CanWait) { NTSTATUS Status = STATUS_SUCCESS; PFILE_RECORD_HEADER FileRecord; @@ -649,7 +657,7 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, ULONGLONG ParentMftIndex; ULONGLONG FileMftIndex; - DPRINT1("NtfsCreateFileRecord(%p, %p)\n", DeviceExt, FileObject); + DPRINT1("NtfsCreateFileRecord(%p, %p, %s)\n", DeviceExt, FileObject, CanWait ? "TRUE" : "FALSE"); // allocate memory for file record FileRecord = ExAllocatePoolWithTag(NonPagedPool, @@ -708,7 +716,7 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, NtfsDumpFileRecord(DeviceExt, FileRecord); // Now that we've built the file record in memory, we need to store it in the MFT. - Status = AddNewMftEntry(FileRecord, DeviceExt, &FileMftIndex); + 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 diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 62b29790314..8df62ed5420 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -195,10 +195,16 @@ AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord) * @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 8 records. Bitmap entries for the new records are cleared, @@ -206,7 +212,7 @@ AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord) * This function will wait for exlusive access to the volume fcb. */ NTSTATUS -IncreaseMftSize(PDEVICE_EXTENSION Vcb) +IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait) { PNTFS_ATTR_CONTEXT BitmapContext; LARGE_INTEGER BitmapSize; @@ -221,10 +227,10 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb) ULONG LengthWritten; NTSTATUS Status; - DPRINT1("IncreaseMftSize(%p)\n", Vcb); + 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), TRUE)) + if (!ExAcquireResourceExclusiveLite(&(Vcb->DirResource), CanWait)) { return STATUS_CANT_WAIT; } @@ -1638,17 +1644,22 @@ FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb, * @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) + PULONGLONG DestinationIndex, + BOOLEAN CanWait) { NTSTATUS Status = STATUS_SUCCESS; ULONGLONG MftIndex; @@ -1661,7 +1672,7 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, LARGE_INTEGER BitmapBits; UCHAR SystemReservedBits; - DPRINT1("AddNewMftEntry(%p, %p, %p)\n", FileRecord, DeviceExt, DestinationIndex); + DPRINT1("AddNewMftEntry(%p, %p, %p, %s)\n", FileRecord, DeviceExt, DestinationIndex, CanWait ? "TRUE" : "FALSE"); // First, we have to read the mft's $Bitmap attribute Status = FindAttribute(DeviceExt, DeviceExt->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL); @@ -1717,14 +1728,14 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, ReleaseAttributeContext(BitmapContext); // Couldn't find a free record in the MFT, add some blank records and try again - Status = IncreaseMftSize(DeviceExt); + 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); + return AddNewMftEntry(FileRecord, DeviceExt, DestinationIndex, CanWait); } DPRINT1("Creating file record at MFT index: %I64u\n", MftIndex); diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index b9336758efb..799f8e2a4f0 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -667,7 +667,8 @@ NtfsCreate(PNTFS_IRP_CONTEXT IrpContext); NTSTATUS NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, - PFILE_OBJECT FileObject); + PFILE_OBJECT FileObject, + BOOLEAN CanWait); /* devctl.c */ @@ -825,7 +826,8 @@ NtfsFileSystemControl(PNTFS_IRP_CONTEXT IrpContext); NTSTATUS AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, PDEVICE_EXTENSION DeviceExt, - PULONGLONG DestinationIndex); + PULONGLONG DestinationIndex, + BOOLEAN CanWait); PNTFS_ATTR_CONTEXT PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord); From 032be0295481aeed18a5ced204f4b83d247b0232 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Sat, 24 Jun 2017 04:36:28 +0000 Subject: [PATCH 38/71] [NTFS] - Fix POSIX rules. Fix accessing long filenames created in Windows when 8dot3 name creation is disabled. Whether or not a filename should be interpreted as case-sensitive is dependent on a flag passed to the driver when a file is created (opened); it's separate from the namespace associated with the file being accessed. svn path=/branches/GSoC_2016/NTFS/; revision=75178 --- drivers/filesystems/ntfs/attrib.c | 15 +++- drivers/filesystems/ntfs/create.c | 12 +++- drivers/filesystems/ntfs/dirctl.c | 3 +- drivers/filesystems/ntfs/fcb.c | 20 ++++-- drivers/filesystems/ntfs/finfo.c | 20 +++++- drivers/filesystems/ntfs/mft.c | 113 ++++++++++++++++++++++++------ drivers/filesystems/ntfs/ntfs.h | 22 ++++-- drivers/filesystems/ntfs/rw.c | 24 ++++++- 8 files changed, 185 insertions(+), 44 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index 78be3ecf3ce..85f7f880476 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -115,6 +115,10 @@ AddData(PFILE_RECORD_HEADER FileRecord, * @param ParentMftIndex * Pointer to a ULONGLONG which will receive the index of the 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. +* * @return * STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end * of the given file record. @@ -132,7 +136,8 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, PNTFS_ATTR_RECORD AttributeAddress, PDEVICE_EXTENSION DeviceExt, PFILE_OBJECT FileObject, - PULONGLONG ParentMftIndex) + PULONGLONG ParentMftIndex, + BOOLEAN CaseSensitive) { ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR); PFILENAME_ATTRIBUTE FileNameAttribute; @@ -178,7 +183,13 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, if(Remaining.Length != 0) RtlCopyUnicodeString(&FilenameNoPath, &Remaining); - Status = NtfsFindMftRecord(DeviceExt, CurrentMFTIndex, &Current, &FirstEntry, FALSE, &CurrentMFTIndex); + Status = NtfsFindMftRecord(DeviceExt, + CurrentMFTIndex, + &Current, + &FirstEntry, + FALSE, + &CurrentMFTIndex, + CaseSensitive); if (!NT_SUCCESS(Status)) break; diff --git a/drivers/filesystems/ntfs/create.c b/drivers/filesystems/ntfs/create.c index 96f563e75dd..ddafb14d678 100644 --- a/drivers/filesystems/ntfs/create.c +++ b/drivers/filesystems/ntfs/create.c @@ -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,7 +291,8 @@ NtfsOpenFile(PDEVICE_EXTENSION DeviceExt, Status = NtfsGetFCBForFile(DeviceExt, &ParentFcb, &Fcb, - FileName); + FileName, + CaseSensitive); if (ParentFcb != NULL) { NtfsReleaseFCB(DeviceExt, @@ -412,6 +419,7 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject, Status = NtfsOpenFile(DeviceExt, FileObject, ((RequestedOptions & FILE_OPEN_BY_FILE_ID) ? FullPath.Buffer : FileObject->FileName.Buffer), + (Stack->Flags & SL_CASE_SENSITIVE), &Fcb); if (RequestedOptions & FILE_OPEN_BY_FILE_ID) diff --git a/drivers/filesystems/ntfs/dirctl.c b/drivers/filesystems/ntfs/dirctl.c index 91f0bc5db70..7c59e88204d 100644 --- a/drivers/filesystems/ntfs/dirctl.c +++ b/drivers/filesystems/ntfs/dirctl.c @@ -627,7 +627,8 @@ NtfsQueryDirectory(PNTFS_IRP_CONTEXT IrpContext) &Ccb->Entry, &FileRecord, &MFTRecord, - Fcb->MFTIndex); + Fcb->MFTIndex, + (Stack->Flags & SL_CASE_SENSITIVE)); if (NT_SUCCESS(Status)) { diff --git a/drivers/filesystems/ntfs/fcb.c b/drivers/filesystems/ntfs/fcb.c index 111660288ab..531749d4d28 100644 --- a/drivers/filesystems/ntfs/fcb.c +++ b/drivers/filesystems/ntfs/fcb.c @@ -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, &FileRecord, &MFTIndex, CurrentDir, CaseSensitive); if (!NT_SUCCESS(Status)) { return Status; @@ -587,7 +593,8 @@ NTSTATUS NtfsGetFCBForFile(PNTFS_VCB Vcb, PNTFS_FCB *pParentFCB, PNTFS_FCB *pFCB, - const PWSTR pFileName) + const PWSTR pFileName, + BOOLEAN CaseSensitive) { NTSTATUS Status; WCHAR pathName [MAX_PATH]; @@ -596,11 +603,12 @@ NtfsGetFCBForFile(PNTFS_VCB Vcb, 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); @@ -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; diff --git a/drivers/filesystems/ntfs/finfo.c b/drivers/filesystems/ntfs/finfo.c index 76cc78f4d9a..1cb7dbc0fe9 100644 --- a/drivers/filesystems/ntfs/finfo.c +++ b/drivers/filesystems/ntfs/finfo.c @@ -415,6 +415,10 @@ NtfsQueryInformation(PNTFS_IRP_CONTEXT IrpContext) * @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). * @@ -436,6 +440,7 @@ NtfsSetEndOfFile(PNTFS_FCB Fcb, PFILE_OBJECT FileObject, PDEVICE_EXTENSION DeviceExt, ULONG IrpFlags, + BOOLEAN CaseSensitive, PLARGE_INTEGER NewFileSize) { LARGE_INTEGER CurrentFileSize; @@ -545,7 +550,13 @@ NtfsSetEndOfFile(PNTFS_FCB Fcb, AllocationSize = ROUND_UP(NewFileSize->QuadPart, Fcb->Vcb->NtfsInfo.BytesPerCluster); - Status = UpdateFileNameRecord(Fcb->Vcb, ParentMFTId, &filename, FALSE, NewFileSize->QuadPart, AllocationSize); + Status = UpdateFileNameRecord(Fcb->Vcb, + ParentMFTId, + &filename, + FALSE, + NewFileSize->QuadPart, + AllocationSize, + CaseSensitive); ReleaseAttributeContext(DataContext); ExFreePoolWithTag(FileRecord, TAG_NTFS); @@ -620,7 +631,12 @@ NtfsSetInformation(PNTFS_IRP_CONTEXT IrpContext) 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, &EndOfFileInfo->EndOfFile); + Status = NtfsSetEndOfFile(Fcb, + FileObject, + DeviceExt, + Irp->Flags, + (Stack->Flags & SL_CASE_SENSITIVE), + &EndOfFileInfo->EndOfFile); break; // TODO: all other information classes diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 8df62ed5420..2c0dab65864 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -1359,7 +1359,8 @@ UpdateFileNameRecord(PDEVICE_EXTENSION Vcb, PUNICODE_STRING FileName, BOOLEAN DirSearch, ULONGLONG NewDataSize, - ULONGLONG NewAllocationSize) + ULONGLONG NewAllocationSize, + BOOLEAN CaseSensitive) { PFILE_RECORD_HEADER MftRecord; PNTFS_ATTR_CONTEXT IndexRootCtx; @@ -1369,7 +1370,14 @@ UpdateFileNameRecord(PDEVICE_EXTENSION Vcb, NTSTATUS Status; ULONG CurrentEntry = 0; - DPRINT("UpdateFileNameRecord(%p, %I64d, %wZ, %u, %I64u, %I64u)\n", Vcb, ParentMFTIndex, FileName, DirSearch, NewDataSize, NewAllocationSize); + DPRINT("UpdateFileNameRecord(%p, %I64d, %wZ, %u, %I64u, %I64u, %s)\n", + Vcb, + ParentMFTIndex, + FileName, + DirSearch, + NewDataSize, + NewAllocationSize, + CaseSensitive ? "TRUE" : "FALSE"); MftRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, @@ -1421,7 +1429,8 @@ UpdateFileNameRecord(PDEVICE_EXTENSION Vcb, &CurrentEntry, DirSearch, NewDataSize, - NewAllocationSize); + NewAllocationSize, + CaseSensitive); ReleaseAttributeContext(IndexRootCtx); ExFreePoolWithTag(IndexRecord, TAG_NTFS); @@ -1447,7 +1456,8 @@ UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb, PULONG CurrentEntry, BOOLEAN DirSearch, ULONGLONG NewDataSize, - ULONGLONG NewAllocatedSize) + ULONGLONG NewAllocatedSize, + BOOLEAN CaseSensitive) { NTSTATUS Status; ULONG RecordOffset; @@ -1466,7 +1476,7 @@ UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb, if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) > 0x10 && *CurrentEntry >= *StartEntry && IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS && - CompareFileName(FileName, IndexEntry, DirSearch)) + CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive)) { *StartEntry = *CurrentEntry; IndexEntry->FileName.DataSize = NewDataSize; @@ -1517,7 +1527,19 @@ UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb, 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); + 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 @@ -1827,7 +1849,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; @@ -1839,7 +1862,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; @@ -1849,7 +1872,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) { @@ -1860,7 +1883,7 @@ CompareFileName(PUNICODE_STRING FileName, } else { - return (RtlCompareUnicodeString(FileName, &EntryName, (IndexEntry->FileName.NameType != NTFS_FILE_NAME_POSIX)) == 0); + return (RtlCompareUnicodeString(FileName, &EntryName, !CaseSensitive) == 0); } } @@ -1900,6 +1923,7 @@ BrowseIndexEntries(PDEVICE_EXTENSION Vcb, PULONG StartEntry, PULONG CurrentEntry, BOOLEAN DirSearch, + BOOLEAN CaseSensitive, ULONGLONG *OutMFTIndex) { NTSTATUS Status; @@ -1909,7 +1933,19 @@ BrowseIndexEntries(PDEVICE_EXTENSION Vcb, ULONGLONG IndexAllocationSize; PINDEX_BUFFER IndexBuffer; - 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, %u, %p, %p, %wZ, %u, %u, %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 && @@ -1918,7 +1954,7 @@ BrowseIndexEntries(PDEVICE_EXTENSION Vcb, if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) >= 0x10 && *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); @@ -1967,7 +2003,18 @@ BrowseIndexEntries(PDEVICE_EXTENSION Vcb, 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); + Status = BrowseIndexEntries(NULL, + NULL, + NULL, + 0, + FirstEntry, + LastEntry, + FileName, + StartEntry, + CurrentEntry, + DirSearch, + CaseSensitive, + OutMFTIndex); if (NT_SUCCESS(Status)) { break; @@ -1984,7 +2031,8 @@ NtfsFindMftRecord(PDEVICE_EXTENSION Vcb, PUNICODE_STRING FileName, PULONG FirstEntry, BOOLEAN DirSearch, - ULONGLONG *OutMFTIndex) + ULONGLONG *OutMFTIndex, + BOOLEAN CaseSensitive) { PFILE_RECORD_HEADER MftRecord; PNTFS_ATTR_CONTEXT IndexRootCtx; @@ -2036,7 +2084,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, + IndexRecord, + IndexRoot->SizeOfEntry, + IndexEntry, + IndexEntryEnd, + FileName, + FirstEntry, + &CurrentEntry, + DirSearch, + CaseSensitive, + OutMFTIndex); ExFreePoolWithTag(IndexRecord, TAG_NTFS); ExFreePoolWithTag(MftRecord, TAG_NTFS); @@ -2049,7 +2108,8 @@ NtfsLookupFileAt(PDEVICE_EXTENSION Vcb, PUNICODE_STRING PathName, PFILE_RECORD_HEADER *FileRecord, PULONGLONG MFTIndex, - ULONGLONG CurrentMFTIndex) + ULONGLONG CurrentMFTIndex, + BOOLEAN CaseSensitive) { UNICODE_STRING Current, Remaining; NTSTATUS Status; @@ -2063,7 +2123,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, &CurrentMFTIndex, CaseSensitive); if (!NT_SUCCESS(Status)) { return Status; @@ -2099,9 +2159,10 @@ NTSTATUS NtfsLookupFile(PDEVICE_EXTENSION Vcb, PUNICODE_STRING PathName, PFILE_RECORD_HEADER *FileRecord, - PULONGLONG MFTIndex) + PULONGLONG MFTIndex, + BOOLEAN CaseSensitive) { - return NtfsLookupFileAt(Vcb, PathName, FileRecord, MFTIndex, NTFS_FILE_ROOT); + return NtfsLookupFileAt(Vcb, PathName, FileRecord, MFTIndex, NTFS_FILE_ROOT, CaseSensitive); } /** @@ -2150,13 +2211,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, %u, %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, &CurrentMFTIndex, CaseSensitive); if (!NT_SUCCESS(Status)) { DPRINT("NtfsFindFileAt: NtfsFindMftRecord() failed with status 0x%08lx\n", Status); diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index 799f8e2a4f0..f54a14d2545 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -781,7 +781,8 @@ NTSTATUS NtfsGetFCBForFile(PNTFS_VCB Vcb, PNTFS_FCB *pParentFCB, PNTFS_FCB *pFCB, - const PWSTR pFileName); + const PWSTR pFileName, + BOOLEAN CaseSensitive); NTSTATUS NtfsReadFCBAttribute(PNTFS_VCB Vcb, @@ -811,6 +812,7 @@ NtfsSetEndOfFile(PNTFS_FCB Fcb, PFILE_OBJECT FileObject, PDEVICE_EXTENSION DeviceExt, ULONG IrpFlags, + BOOLEAN CaseSensitive, PLARGE_INTEGER NewFileSize); NTSTATUS @@ -892,7 +894,8 @@ AttributeAllocatedLength(PNTFS_ATTR_RECORD AttrRecord); BOOLEAN CompareFileName(PUNICODE_STRING FileName, PINDEX_ENTRY_ATTRIBUTE IndexEntry, - BOOLEAN DirSearch); + BOOLEAN DirSearch, + BOOLEAN CaseSensitive); NTSTATUS ReadFileRecord(PDEVICE_EXTENSION Vcb, @@ -911,7 +914,8 @@ UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb, PULONG CurrentEntry, BOOLEAN DirSearch, ULONGLONG NewDataSize, - ULONGLONG NewAllocatedSize); + ULONGLONG NewAllocatedSize, + BOOLEAN CaseSensitive); NTSTATUS UpdateFileNameRecord(PDEVICE_EXTENSION Vcb, @@ -919,7 +923,8 @@ UpdateFileNameRecord(PDEVICE_EXTENSION Vcb, PUNICODE_STRING FileName, BOOLEAN DirSearch, ULONGLONG NewDataSize, - ULONGLONG NewAllocationSize); + ULONGLONG NewAllocationSize, + BOOLEAN CaseSensitive); NTSTATUS UpdateFileRecord(PDEVICE_EXTENSION Vcb, @@ -973,7 +978,8 @@ NtfsLookupFileAt(PDEVICE_EXTENSION Vcb, PUNICODE_STRING PathName, PFILE_RECORD_HEADER *FileRecord, PULONGLONG MFTIndex, - ULONGLONG CurrentMFTIndex); + ULONGLONG CurrentMFTIndex, + BOOLEAN CaseSensitive); VOID NtfsDumpFileRecord(PDEVICE_EXTENSION Vcb, @@ -985,7 +991,8 @@ NtfsFindFileAt(PDEVICE_EXTENSION Vcb, PULONG FirstEntry, PFILE_RECORD_HEADER *FileRecord, PULONGLONG MFTIndex, - ULONGLONG CurrentMFTIndex); + ULONGLONG CurrentMFTIndex, + BOOLEAN CaseSensitive); NTSTATUS NtfsFindMftRecord(PDEVICE_EXTENSION Vcb, @@ -993,7 +1000,8 @@ NtfsFindMftRecord(PDEVICE_EXTENSION Vcb, PUNICODE_STRING FileName, PULONG FirstEntry, BOOLEAN DirSearch, - ULONGLONG *OutMFTIndex); + ULONGLONG *OutMFTIndex, + BOOLEAN CaseSensitive); /* misc.c */ diff --git a/drivers/filesystems/ntfs/rw.c b/drivers/filesystems/ntfs/rw.c index 7f119d706b1..1cfdd7fde20 100644 --- a/drivers/filesystems/ntfs/rw.c +++ b/drivers/filesystems/ntfs/rw.c @@ -283,6 +283,10 @@ NtfsRead(PNTFS_IRP_CONTEXT IrpContext) * @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. * @@ -303,6 +307,7 @@ NTSTATUS NtfsWriteFile(PDEVICE_EXTENSION DeviceExt, ULONG Length, ULONG WriteOffset, ULONG IrpFlags, + BOOLEAN CaseSensitive, PULONG LengthWritten) { NTSTATUS Status = STATUS_NOT_IMPLEMENTED; @@ -312,7 +317,15 @@ NTSTATUS NtfsWriteFile(PDEVICE_EXTENSION DeviceExt, ULONG AttributeOffset; ULONGLONG StreamSize; - DPRINT("NtfsWriteFile(%p, %p, %p, %u, %u, %x, %p)\n", DeviceExt, FileObject, Buffer, Length, WriteOffset, IrpFlags, LengthWritten); + DPRINT("NtfsWriteFile(%p, %p, %p, %u, %u, %x, %s, %p)\n", + DeviceExt, + FileObject, + Buffer, + Length, + WriteOffset, + IrpFlags, + (CaseSensitive ? "TRUE" : "FALSE"), + LengthWritten); *LengthWritten = 0; @@ -444,7 +457,13 @@ NTSTATUS NtfsWriteFile(PDEVICE_EXTENSION DeviceExt, filename.Length = fileNameAttribute->NameLength * sizeof(WCHAR); filename.MaximumLength = filename.Length; - Status = UpdateFileNameRecord(Fcb->Vcb, ParentMFTId, &filename, FALSE, DataSize.QuadPart, AllocationSize); + Status = UpdateFileNameRecord(Fcb->Vcb, + ParentMFTId, + &filename, + FALSE, + DataSize.QuadPart, + AllocationSize, + CaseSensitive); } else @@ -667,6 +686,7 @@ NtfsWrite(PNTFS_IRP_CONTEXT IrpContext) Length, ByteOffset.LowPart, Irp->Flags, + (IrpContext->Stack->Flags & SL_CASE_SENSITIVE), &ReturnedWriteLength); IrpContext->Irp->IoStatus.Status = Status; From 948e91907a3a545d9c0027e7e3bcd4e28507b110 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Sun, 25 Jun 2017 02:38:15 +0000 Subject: [PATCH 39/71] [NTFS] - Fix a mistake with AddFileName() from my last commit. Also, move CaseSensitive parameter before output parameters in the parameter list of several functions. svn path=/branches/GSoC_2016/NTFS/; revision=75191 --- drivers/filesystems/ntfs/attrib.c | 10 +++++----- drivers/filesystems/ntfs/create.c | 14 +++++++++++--- drivers/filesystems/ntfs/fcb.c | 2 +- drivers/filesystems/ntfs/mft.c | 18 ++++++++++++------ drivers/filesystems/ntfs/ntfs.h | 7 +++++-- 5 files changed, 34 insertions(+), 17 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index 85f7f880476..120cf901853 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -112,13 +112,13 @@ AddData(PFILE_RECORD_HEADER FileRecord, * Pointer to the FILE_OBJECT which represents the new name. * This parameter is used to determine the filename and parent directory. * -* @param ParentMftIndex -* Pointer to a ULONGLONG which will receive the index of the 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. @@ -136,8 +136,8 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, PNTFS_ATTR_RECORD AttributeAddress, PDEVICE_EXTENSION DeviceExt, PFILE_OBJECT FileObject, - PULONGLONG ParentMftIndex, - BOOLEAN CaseSensitive) + BOOLEAN CaseSensitive, + PULONGLONG ParentMftIndex) { ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR); PFILENAME_ATTRIBUTE FileNameAttribute; diff --git a/drivers/filesystems/ntfs/create.c b/drivers/filesystems/ntfs/create.c index ddafb14d678..f52cd7d187d 100644 --- a/drivers/filesystems/ntfs/create.c +++ b/drivers/filesystems/ntfs/create.c @@ -570,7 +570,10 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject, } // Create the file record on disk - Status = NtfsCreateFileRecord(DeviceExt, FileObject, BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT)); + Status = NtfsCreateFileRecord(DeviceExt, + FileObject, + (Stack->Flags & SL_CASE_SENSITIVE), + BooleanFlagOn(IrpContext->Flags,IRPCONTEXT_CANWAIT)); if (!NT_SUCCESS(Status)) { DPRINT1("ERROR: Couldn't create file record!\n"); @@ -656,6 +659,7 @@ NtfsCreate(PNTFS_IRP_CONTEXT IrpContext) NTSTATUS NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, PFILE_OBJECT FileObject, + BOOLEAN CaseSensitive, BOOLEAN CanWait) { NTSTATUS Status = STATUS_SUCCESS; @@ -665,7 +669,11 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, ULONGLONG ParentMftIndex; ULONGLONG FileMftIndex; - DPRINT1("NtfsCreateFileRecord(%p, %p, %s)\n", DeviceExt, FileObject, CanWait ? "TRUE" : "FALSE"); + DPRINT1("NtfsCreateFileRecord(%p, %p, %s, %s)\n", + DeviceExt, + FileObject, + CaseSensitive ? "TRUE" : "FALSE", + CanWait ? "TRUE" : "FALSE"); // allocate memory for file record FileRecord = ExAllocatePoolWithTag(NonPagedPool, @@ -709,7 +717,7 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)NextAttribute + (ULONG_PTR)NextAttribute->Length); // Add the $FILE_NAME attribute - AddFileName(FileRecord, NextAttribute, DeviceExt, FileObject, &ParentMftIndex); + AddFileName(FileRecord, NextAttribute, DeviceExt, FileObject, CaseSensitive, &ParentMftIndex); // save a pointer to the filename attribute FilenameAttribute = (PFILENAME_ATTRIBUTE)((ULONG_PTR)NextAttribute + NextAttribute->Resident.ValueOffset); diff --git a/drivers/filesystems/ntfs/fcb.c b/drivers/filesystems/ntfs/fcb.c index 531749d4d28..851234c4fec 100644 --- a/drivers/filesystems/ntfs/fcb.c +++ b/drivers/filesystems/ntfs/fcb.c @@ -557,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, CaseSensitive); + Status = NtfsLookupFileAt(Vcb, &File, CaseSensitive, &FileRecord, &MFTIndex, CurrentDir); if (!NT_SUCCESS(Status)) { return Status; diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 2c0dab65864..e83c33e6de7 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -2106,16 +2106,22 @@ NtfsFindMftRecord(PDEVICE_EXTENSION Vcb, NTSTATUS NtfsLookupFileAt(PDEVICE_EXTENSION Vcb, PUNICODE_STRING PathName, + BOOLEAN CaseSensitive, PFILE_RECORD_HEADER *FileRecord, PULONGLONG MFTIndex, - ULONGLONG CurrentMFTIndex, - BOOLEAN CaseSensitive) + ULONGLONG CurrentMFTIndex) { UNICODE_STRING Current, Remaining; 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); @@ -2158,11 +2164,11 @@ NtfsLookupFileAt(PDEVICE_EXTENSION Vcb, NTSTATUS NtfsLookupFile(PDEVICE_EXTENSION Vcb, PUNICODE_STRING PathName, + BOOLEAN CaseSensitive, PFILE_RECORD_HEADER *FileRecord, - PULONGLONG MFTIndex, - BOOLEAN CaseSensitive) + PULONGLONG MFTIndex) { - return NtfsLookupFileAt(Vcb, PathName, FileRecord, MFTIndex, NTFS_FILE_ROOT, CaseSensitive); + return NtfsLookupFileAt(Vcb, PathName, CaseSensitive, FileRecord, MFTIndex, NTFS_FILE_ROOT); } /** diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index f54a14d2545..b2633c0dd89 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -536,6 +536,7 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, PNTFS_ATTR_RECORD AttributeAddress, PDEVICE_EXTENSION DeviceExt, PFILE_OBJECT FileObject, + BOOLEAN CaseSensitive, PULONGLONG ParentMftIndex); NTSTATUS @@ -668,6 +669,7 @@ NtfsCreate(PNTFS_IRP_CONTEXT IrpContext); NTSTATUS NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, PFILE_OBJECT FileObject, + BOOLEAN CaseSensitive, BOOLEAN CanWait); /* devctl.c */ @@ -970,16 +972,17 @@ 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, - BOOLEAN CaseSensitive); + ULONGLONG CurrentMFTIndex); VOID NtfsDumpFileRecord(PDEVICE_EXTENSION Vcb, From 8bb62e20d333fb058ac5cffd31600ad684cd6363 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Sun, 25 Jun 2017 02:56:40 +0000 Subject: [PATCH 40/71] [NTFS] - In the NtfsAddFilenameToDirectory() function, rename DirectoryContext parameter to the more descriptive, and accurate, IndexRootContext (to simplify the next commit). svn path=/branches/GSoC_2016/NTFS/; revision=75192 --- drivers/filesystems/ntfs/dirctl.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/drivers/filesystems/ntfs/dirctl.c b/drivers/filesystems/ntfs/dirctl.c index 7c59e88204d..4d19052ac9c 100644 --- a/drivers/filesystems/ntfs/dirctl.c +++ b/drivers/filesystems/ntfs/dirctl.c @@ -72,7 +72,7 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, { NTSTATUS Status = STATUS_SUCCESS; PFILE_RECORD_HEADER ParentFileRecord; - PNTFS_ATTR_CONTEXT DirectoryContext; + PNTFS_ATTR_CONTEXT IndexRootContext; PINDEX_ROOT_ATTRIBUTE I30IndexRoot; ULONG IndexRootOffset; ULONGLONG I30IndexRootLength; @@ -112,7 +112,7 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, AttributeIndexRoot, L"$I30", 4, - &DirectoryContext, + &IndexRootContext, &IndexRootOffset); if (!NT_SUCCESS(Status)) { @@ -122,23 +122,23 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, return Status; } - I30IndexRootLength = AttributeDataLength(&DirectoryContext->Record); + I30IndexRootLength = AttributeDataLength(&IndexRootContext->Record); // Allocate memory for the index root data I30IndexRoot = (PINDEX_ROOT_ATTRIBUTE)ExAllocatePoolWithTag(NonPagedPool, I30IndexRootLength, TAG_NTFS); if (!I30IndexRoot) { DPRINT1("ERROR: Couldn't allocate memory for index root attribute!\n"); - ReleaseAttributeContext(DirectoryContext); + ReleaseAttributeContext(IndexRootContext); ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); } // Read the Index Root - Status = ReadAttribute(DeviceExt, DirectoryContext, 0, (PCHAR)I30IndexRoot, I30IndexRootLength); + 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(DirectoryContext); + ReleaseAttributeContext(IndexRootContext); ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); return Status; @@ -149,7 +149,7 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, if (IndexNodeEntry->Data.Directory.IndexedFile != 0 || IndexNodeEntry->Flags != 2) { DPRINT1("FIXME: File-creation is only supported in empty directories right now! Be patient! :)\n"); - ReleaseAttributeContext(DirectoryContext); + ReleaseAttributeContext(IndexRootContext); ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); return STATUS_NOT_IMPLEMENTED; @@ -160,7 +160,7 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, if (!NewIndexRoot) { DPRINT1("ERROR: Unable to allocate memory for new index root attribute!\n"); - ReleaseAttributeContext(DirectoryContext); + ReleaseAttributeContext(IndexRootContext); ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); return STATUS_INSUFFICIENT_RESOURCES; @@ -225,14 +225,14 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, { DPRINT1("FIXME: For now, only resizing index root at the end of a file record is supported!\n"); ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); - ReleaseAttributeContext(DirectoryContext); + ReleaseAttributeContext(IndexRootContext); ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); return STATUS_NOT_IMPLEMENTED; } // Update the length of the attribute in the file record of the parent directory - InternalSetResidentAttributeLength(DirectoryContext, + InternalSetResidentAttributeLength(IndexRootContext, ParentFileRecord, IndexRootOffset, AttributeLength); @@ -249,7 +249,7 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, // Update the parent directory with the new index root Status = WriteAttribute(DeviceExt, - DirectoryContext, + IndexRootContext, 0, (PUCHAR)NewIndexRoot, AttributeLength, @@ -258,7 +258,7 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, { DPRINT1("ERROR: Unable to write new index root attribute to parent directory!\n"); ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); - ReleaseAttributeContext(DirectoryContext); + ReleaseAttributeContext(IndexRootContext); ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); return Status; @@ -278,7 +278,7 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, // Cleanup ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); - ReleaseAttributeContext(DirectoryContext); + ReleaseAttributeContext(IndexRootContext); ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); From e4aab2678175b4e0533f8c86e00ed370db501f1e Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Sun, 25 Jun 2017 04:29:04 +0000 Subject: [PATCH 41/71] [NTFS] - Fix creation of files with long filenames. svn path=/branches/GSoC_2016/NTFS/; revision=75193 --- drivers/filesystems/ntfs/attrib.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index 120cf901853..7669008f998 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -220,9 +220,15 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, DPRINT1("FileNameAttribute->DirectoryFileReferenceNumber: 0x%016I64x\n", FileNameAttribute->DirectoryFileReferenceNumber); FileNameAttribute->NameLength = FilenameNoPath.Length / sizeof(WCHAR); - // TODO: Get proper nametype, add DOS links as needed - FileNameAttribute->NameType = NTFS_FILE_NAME_WIN32_AND_DOS; 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 (RtlIsNameLegalDOS8Dot3(&FilenameNoPath, NULL, NULL)) + FileNameAttribute->NameType = NTFS_FILE_NAME_WIN32_AND_DOS; + else + FileNameAttribute->NameType = NTFS_FILE_NAME_POSIX; + FileRecord->LinkCount++; AttributeAddress->Length = ResidentHeaderLength + From 38c947b7abde91ce68d27e5f18553ca2f1747274 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Mon, 26 Jun 2017 05:17:08 +0000 Subject: [PATCH 42/71] [NTFS] - Add some minor fixes and improvements: Improve, add, or fix some DPRINTs. In particular, ULONG's should use %lu, not %u. Also, don't be silent about filesystem corruption. NtfsFindMftRecord() - move CaseSensitive parameter before output parameter in parameter list. svn path=/branches/GSoC_2016/NTFS/; revision=75199 --- drivers/filesystems/ntfs/attrib.c | 6 ++-- drivers/filesystems/ntfs/blockdev.c | 4 +-- drivers/filesystems/ntfs/mft.c | 54 ++++++++++++++++++++++------- drivers/filesystems/ntfs/ntfs.h | 4 +-- drivers/filesystems/ntfs/rw.c | 4 +-- 5 files changed, 50 insertions(+), 22 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index 7669008f998..90e20d38192 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -188,8 +188,8 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, &Current, &FirstEntry, FALSE, - &CurrentMFTIndex, - CaseSensitive); + CaseSensitive, + &CurrentMFTIndex); if (!NT_SUCCESS(Status)) break; @@ -1173,7 +1173,7 @@ NtfsDumpIndexRootAttribute(PNTFS_ATTR_RECORD Attribute) while (currentOffset < IndexRootAttr->Header.TotalSizeOfEntries) { PINDEX_ENTRY_ATTRIBUTE currentIndexExtry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexRootAttr + 0x10 + currentOffset); - DbgPrint(" Index Node Entry %u", currentNode++); + DbgPrint(" Index Node Entry %lu", currentNode++); if (currentIndexExtry->Flags & NTFS_INDEX_ENTRY_NODE) DbgPrint(" (Branch)"); else diff --git a/drivers/filesystems/ntfs/blockdev.c b/drivers/filesystems/ntfs/blockdev.c index 1ae67e52d7f..70658b03c3f 100644 --- a/drivers/filesystems/ntfs/blockdev.c +++ b/drivers/filesystems/ntfs/blockdev.c @@ -52,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, @@ -176,7 +176,7 @@ NtfsWriteDisk(IN PDEVICE_OBJECT DeviceObject, BOOLEAN AllocatedBuffer = FALSE; PUCHAR TempBuffer = NULL; - DPRINT("NtfsWriteDisk(%p, %I64x, %u, %u, %p)\n", DeviceObject, StartingOffset, Length, SectorSize, Buffer); + DPRINT("NtfsWriteDisk(%p, %I64x, %lu, %lu, %p)\n", DeviceObject, StartingOffset, Length, SectorSize, Buffer); if (Length == 0) return STATUS_SUCCESS; diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index e83c33e6de7..6ffe8d53ad2 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -119,7 +119,7 @@ FindAttribute(PDEVICE_EXTENSION Vcb, 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); @@ -396,6 +396,14 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, { NTSTATUS Status = STATUS_SUCCESS; + DPRINT1("SetAttributeDataLenth(%p, %p, %p, %lu, %p, %I64u)\n", + FileObject, + Fcb, + AttrContext, + AttrOffset, + FileRecord, + DataSize->QuadPart); + // are we truncating the file? if (DataSize->QuadPart < AttributeDataLength(&AttrContext->Record)) { @@ -1336,7 +1344,7 @@ 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; } @@ -1370,11 +1378,11 @@ UpdateFileNameRecord(PDEVICE_EXTENSION Vcb, NTSTATUS Status; ULONG CurrentEntry = 0; - DPRINT("UpdateFileNameRecord(%p, %I64d, %wZ, %u, %I64u, %I64u, %s)\n", + DPRINT("UpdateFileNameRecord(%p, %I64d, %wZ, %s, %I64u, %I64u, %s)\n", Vcb, ParentMFTIndex, FileName, - DirSearch, + DirSearch ? "TRUE" : "FALSE", NewDataSize, NewAllocationSize, CaseSensitive ? "TRUE" : "FALSE"); @@ -1466,7 +1474,20 @@ UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb, ULONGLONG IndexAllocationSize; PINDEX_BUFFER IndexBuffer; - DPRINT("UpdateIndexEntrySize(%p, %p, %p, %u, %p, %p, %wZ, %u, %u, %u, %I64u, %I64u)\n", Vcb, MftRecord, IndexRecord, IndexBlockSize, FirstEntry, LastEntry, FileName, *StartEntry, *CurrentEntry, DirSearch, NewDataSize, NewAllocatedSize); + 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; @@ -1933,7 +1954,7 @@ BrowseIndexEntries(PDEVICE_EXTENSION Vcb, ULONGLONG IndexAllocationSize; PINDEX_BUFFER IndexBuffer; - DPRINT("BrowseIndexEntries(%p, %p, %p, %u, %p, %p, %wZ, %u, %u, %s, %s, %p)\n", + DPRINT("BrowseIndexEntries(%p, %p, %p, %lu, %p, %p, %wZ, %lu, %lu, %s, %s, %p)\n", Vcb, MftRecord, IndexRecord, @@ -1981,7 +2002,7 @@ BrowseIndexEntries(PDEVICE_EXTENSION Vcb, Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL); if (!NT_SUCCESS(Status)) { - DPRINT("Corrupted filesystem!\n"); + DPRINT1("Corrupted filesystem!\n"); return Status; } @@ -2031,8 +2052,8 @@ NtfsFindMftRecord(PDEVICE_EXTENSION Vcb, PUNICODE_STRING FileName, PULONG FirstEntry, BOOLEAN DirSearch, - ULONGLONG *OutMFTIndex, - BOOLEAN CaseSensitive) + BOOLEAN CaseSensitive, + ULONGLONG *OutMFTIndex) { PFILE_RECORD_HEADER MftRecord; PNTFS_ATTR_CONTEXT IndexRootCtx; @@ -2042,7 +2063,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, @@ -2129,7 +2157,7 @@ NtfsLookupFileAt(PDEVICE_EXTENSION Vcb, { DPRINT("Current: %wZ\n", &Current); - Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, &Current, &FirstEntry, FALSE, &CurrentMFTIndex, CaseSensitive); + Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, &Current, &FirstEntry, FALSE, CaseSensitive, &CurrentMFTIndex); if (!NT_SUCCESS(Status)) { return Status; @@ -2222,7 +2250,7 @@ NtfsFindFileAt(PDEVICE_EXTENSION Vcb, { NTSTATUS Status; - DPRINT("NtfsFindFileAt(%p, %wZ, %u, %p, %p, %I64x, %s)\n", + DPRINT("NtfsFindFileAt(%p, %wZ, %lu, %p, %p, %I64x, %s)\n", Vcb, SearchPattern, *FirstEntry, @@ -2231,7 +2259,7 @@ NtfsFindFileAt(PDEVICE_EXTENSION Vcb, CurrentMFTIndex, (CaseSensitive ? "TRUE" : "FALSE")); - Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, SearchPattern, FirstEntry, TRUE, &CurrentMFTIndex, CaseSensitive); + 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/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index b2633c0dd89..d738ef5fe2c 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -1003,8 +1003,8 @@ NtfsFindMftRecord(PDEVICE_EXTENSION Vcb, PUNICODE_STRING FileName, PULONG FirstEntry, BOOLEAN DirSearch, - ULONGLONG *OutMFTIndex, - BOOLEAN CaseSensitive); + BOOLEAN CaseSensitive, + ULONGLONG *OutMFTIndex); /* misc.c */ diff --git a/drivers/filesystems/ntfs/rw.c b/drivers/filesystems/ntfs/rw.c index 1cfdd7fde20..215269a25c2 100644 --- a/drivers/filesystems/ntfs/rw.c +++ b/drivers/filesystems/ntfs/rw.c @@ -60,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; @@ -317,7 +317,7 @@ NTSTATUS NtfsWriteFile(PDEVICE_EXTENSION DeviceExt, ULONG AttributeOffset; ULONGLONG StreamSize; - DPRINT("NtfsWriteFile(%p, %p, %p, %u, %u, %x, %s, %p)\n", + DPRINT("NtfsWriteFile(%p, %p, %p, %lu, %lu, %x, %s, %p)\n", DeviceExt, FileObject, Buffer, From 54f5c3b6ec26f1758b878b901f4a2f3f9a1a47cc Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Wed, 28 Jun 2017 03:45:52 +0000 Subject: [PATCH 43/71] [NTFS] - Begin to implement B-Trees. Allow for creating several new files in a directory. NtfsAddFilenameToDirectory() - Add CaseSensitive parameter. Update to use new B-Tree code: First, the index is read and converted to a B-Tree in memory. Next, a key for the new file is inserted into the tree. Finally, the tree is converted back to an index root attribute which is written to disk. +btree.c - Includes functions related to B-Trees (AKA B*Trees). ntfs.h - Added several structures for representing B-Trees in memory. Known limitations: For simplicity, only trees with a depth of one are currently supported (i.e. an ordered list of filenames). Directories that have or will require an index allocation to store all their filenames are still TODO. As a consequence, the user will only be able to create about 6 files in a directory. svn path=/branches/GSoC_2016/NTFS/; revision=75223 --- drivers/filesystems/ntfs/CMakeLists.txt | 1 + drivers/filesystems/ntfs/attrib.c | 6 + drivers/filesystems/ntfs/btree.c | 520 ++++++++++++++++++++++++ drivers/filesystems/ntfs/create.c | 3 +- drivers/filesystems/ntfs/dirctl.c | 115 +++--- drivers/filesystems/ntfs/ntfs.h | 61 ++- 6 files changed, 642 insertions(+), 64 deletions(-) create mode 100644 drivers/filesystems/ntfs/btree.c diff --git a/drivers/filesystems/ntfs/CMakeLists.txt b/drivers/filesystems/ntfs/CMakeLists.txt index ff3b35f647f..b9c72a72e7d 100644 --- a/drivers/filesystems/ntfs/CMakeLists.txt +++ b/drivers/filesystems/ntfs/CMakeLists.txt @@ -1,6 +1,7 @@ list(APPEND SOURCE attrib.c + btree.c blockdev.c cleanup.c close.c diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index 90e20d38192..239e1c04001 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -1566,6 +1566,12 @@ GetStandardInformationFromRecord(PDEVICE_EXTENSION Vcb, return NULL; } +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/btree.c b/drivers/filesystems/ntfs/btree.c new file mode 100644 index 00000000000..604c02c2906 --- /dev/null +++ b/drivers/filesystems/ntfs/btree.c @@ -0,0 +1,520 @@ +/* +* 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 ****************************************************************/ + +/** +* @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. +*/ +LONG +CompareTreeKeys(PB_TREE_KEY Key1, PB_TREE_KEY Key2, BOOLEAN CaseSensitive) +{ + UNICODE_STRING Key1Name, Key2Name; + + // If Key2 is the "dummy key", key 1 will always come first + if (Key2->NextKey == NULL) + return -1; + + // If Key1 is the "dummy key", key 2 will always come first + if (Key1->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); + + return RtlCompareUnicodeString(&Key1Name, &Key2Name, !CaseSensitive); +} + +/** +* @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(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); + + DPRINT1("CreateBTreeFromIndex(%p, %p, %p)\n", IndexRootContext, 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)); + + // Setup the Tree + RootNode->FirstKey = CurrentKey; + Tree->RootNode = RootNode; + + // Create a key for each entry in the node + CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexRoot + + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header) + + IndexRoot->Header.FirstEntryOffset); + while (TRUE) + { + // 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(PB_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(PB_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); + + // Make sure this B-Tree is only one level deep (flat list) + if (CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) + { + DPRINT1("TODO: Only directories with single-level B-Trees are supported right now!\n"); + DestroyBTree(Tree); + return STATUS_NOT_IMPLEMENTED; + } + + // Advance to the next entry + 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; + + // Make sure this B-Tree is only one level deep (flat list) + if (CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) + { + DPRINT1("TODO: Only directories with single-level B-Trees are supported right now!\n"); + DestroyBTree(Tree); + return STATUS_NOT_IMPLEMENTED; + } + + break; + } + } + + *NewTree = Tree; + + return STATUS_SUCCESS; +} + +/** +* @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. +* 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) +{ + 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); + + 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 + PB_TREE_KEY CurrentKey = Tree->RootNode->FirstKey; + CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)NewIndexRoot + + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header) + + NewIndexRoot->Header.FirstEntryOffset); + for (int 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 = FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header) + + NewIndexRoot->Header.FirstEntryOffset + + NewIndexRoot->Header.TotalSizeOfEntries + + CurrentNodeEntry->Length; + if (IndexSize > MaxIndexSize) + { + DPRINT1("TODO: Adding file would require creating an index allocation!\n"); + ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); + return STATUS_NOT_IMPLEMENTED; + } + + // Copy the index entry + if (CurrentKey->IndexEntry->Length > 0) + RtlCopyMemory(CurrentNodeEntry, CurrentKey->IndexEntry, CurrentKey->IndexEntry->Length); + else + DPRINT1("DRIVER ERROR: CurrentKey->IndexEntry->Length <= 0 !\n"); + + DPRINT1("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 + NewIndexRoot->Header.TotalSizeOfEntries += CurrentNodeEntry->Length; + + // Go to the next node + 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; +} + +VOID +DestroyBTreeKey(PB_TREE_KEY Key) +{ + if (Key->IndexEntry) + ExFreePoolWithTag(Key->IndexEntry, TAG_NTFS); + + // We'll destroy Key->LesserChild here after we start using it + + ExFreePoolWithTag(Key, TAG_NTFS); +} + +VOID +DestroyBTreeNode(PB_TREE_FILENAME_NODE Node) +{ + PB_TREE_KEY NextKey; + PB_TREE_KEY CurrentKey = Node->FirstKey; + for (int 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_KEY Key, int Number, int Depth) +{ + for (int 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 * 2; + FileName.MaximumLength = FileName.Length; + FileName.Buffer = Key->IndexEntry->FileName.Name; + DbgPrint(" '%wZ'\n", &FileName); + } + else + DbgPrint(" (Dummy Key)\n"); +} + +DumpBTreeNode(PB_TREE_FILENAME_NODE Node, int Number, int Depth) +{ + for (int i = 0; i < Depth; i++) + DbgPrint(" "); + DbgPrint("Node #%d, Depth %d\n", Number, Depth); + + PB_TREE_KEY CurrentKey = Node->FirstKey; + for (int i = 0; i < Node->KeyCount; i++) + { + DumpBTreeKey(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->RootNode, 0, 0); +} + +/** +* @name NtfsInsertKey +* @implemented +* +* Inserts a FILENAME_ATTRIBUTE into a B-Tree node. +* +* @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. +* +* @remarks +* A node is always sorted, with the least comparable filename stored first and a dummy key to mark the end. +*/ +NTSTATUS +NtfsInsertKey(ULONGLONG FileReference, + PFILENAME_ATTRIBUTE FileNameAttribute, + PB_TREE_FILENAME_NODE Node, + BOOLEAN CaseSensitive) +{ + // Calculate size of Attribute and Index Entry + ULONG AttributeSize = GetFileNameAttributeLength(FileNameAttribute); + ULONG EntrySize = ALIGN_UP_BY(AttributeSize + FIELD_OFFSET(INDEX_ENTRY_ATTRIBUTE, FileName), 8); + PINDEX_ENTRY_ATTRIBUTE NewEntry; + + DPRINT1("NtfsInsertKey(0x%02I64, %p, %p, %s)\n", + FileReference, + FileNameAttribute, + Node, + CaseSensitive ? "TRUE" : "FALSE"); + + // Create a new Index Entry for the file + NewEntry = ExAllocatePoolWithTag(NonPagedPool, EntrySize, TAG_NTFS); + if (!NewEntry) + { + DPRINT1("ERROR: Failed to allocate memory for Index Entry!\n"); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // 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 + PB_TREE_KEY NewKey = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_KEY), TAG_NTFS); + NewKey->IndexEntry = NewEntry; + NewKey->NextKey = NULL; + + // Find where to insert the key + PB_TREE_KEY CurrentKey = Node->FirstKey; + PB_TREE_KEY PreviousKey = NULL; + for (int 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("DRIVER ERROR: Asked to insert key into tree that already has it!\n"); + ExFreePoolWithTag(NewKey, TAG_NTFS); + ExFreePoolWithTag(NewEntry, TAG_NTFS); + return STATUS_INVALID_PARAMETER; + } + if (Comparison < 0) + { + // NewKey is < CurrentKey + // Insert New Key before Current Key + NewKey->NextKey = CurrentKey; + + // was CurrentKey the first key? + if (CurrentKey == Node->FirstKey) + Node->FirstKey = NewKey; + else + PreviousKey->NextKey = NewKey; + break; + } + + PreviousKey = CurrentKey; + CurrentKey = CurrentKey->NextKey; + } + + Node->KeyCount++; + + // NewEntry and NewKey will be destroyed later by DestroyBTree() + + return STATUS_SUCCESS; +} \ No newline at end of file diff --git a/drivers/filesystems/ntfs/create.c b/drivers/filesystems/ntfs/create.c index f52cd7d187d..586da89b36a 100644 --- a/drivers/filesystems/ntfs/create.c +++ b/drivers/filesystems/ntfs/create.c @@ -747,7 +747,8 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, Status = NtfsAddFilenameToDirectory(DeviceExt, ParentMftIndex, FileMftIndex, - FilenameAttribute); + FilenameAttribute, + CaseSensitive); } ExFreePoolWithTag(FileRecord, TAG_NTFS); diff --git a/drivers/filesystems/ntfs/dirctl.c b/drivers/filesystems/ntfs/dirctl.c index 4d19052ac9c..ba7231dc807 100644 --- a/drivers/filesystems/ntfs/dirctl.c +++ b/drivers/filesystems/ntfs/dirctl.c @@ -53,13 +53,17 @@ * @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 an empty directory. +* 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. @@ -68,7 +72,8 @@ NTSTATUS NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, ULONGLONG DirectoryMftIndex, ULONGLONG FileReferenceNumber, - PFILENAME_ATTRIBUTE FilenameAttribute) + PFILENAME_ATTRIBUTE FilenameAttribute, + BOOLEAN CaseSensitive) { NTSTATUS Status = STATUS_SUCCESS; PFILE_RECORD_HEADER ParentFileRecord; @@ -76,12 +81,14 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, PINDEX_ROOT_ATTRIBUTE I30IndexRoot; ULONG IndexRootOffset; ULONGLONG I30IndexRootLength; - PINDEX_ENTRY_ATTRIBUTE IndexNodeEntry; ULONG LengthWritten; PNTFS_ATTR_RECORD DestinationAttribute; PINDEX_ROOT_ATTRIBUTE NewIndexRoot; ULONG AttributeLength; PNTFS_ATTR_RECORD NextAttribute; + PB_TREE NewTree; + ULONG BtreeIndexLength; + ULONG MaxIndexSize; // Allocate memory for the parent directory ParentFileRecord = ExAllocatePoolWithTag(NonPagedPool, @@ -122,9 +129,15 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, return Status; } - I30IndexRootLength = AttributeDataLength(&IndexRootContext->Record); + // Find the maximum index size given what the file record can hold + MaxIndexSize = DeviceExt->NtfsInfo.BytesPerFileRecord + - IndexRootOffset + - IndexRootContext->Record.Resident.ValueOffset + - FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header) + - (sizeof(ULONG) * 2); // Allocate memory for the index root data + I30IndexRootLength = AttributeDataLength(&IndexRootContext->Record); I30IndexRoot = (PINDEX_ROOT_ATTRIBUTE)ExAllocatePoolWithTag(NonPagedPool, I30IndexRootLength, TAG_NTFS); if (!I30IndexRoot) { @@ -144,82 +157,59 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, return Status; } - // Make sure it's empty (temporarily) - IndexNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)I30IndexRoot + I30IndexRoot->Header.FirstEntryOffset + 0x10); - if (IndexNodeEntry->Data.Directory.IndexedFile != 0 || IndexNodeEntry->Flags != 2) + // Convert the index to a B*Tree + Status = CreateBTreeFromIndex(IndexRootContext, I30IndexRoot, &NewTree); + if (!NT_SUCCESS(Status)) { - DPRINT1("FIXME: File-creation is only supported in empty directories right now! Be patient! :)\n"); + DPRINT1("ERROR: Failed to create B-Tree from Index!\n"); ReleaseAttributeContext(IndexRootContext); ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); - return STATUS_NOT_IMPLEMENTED; + return Status; } - - // Now we need to setup a new index root attribute to replace the old one - NewIndexRoot = ExAllocatePoolWithTag(NonPagedPool, DeviceExt->NtfsInfo.BytesPerIndexRecord, TAG_NTFS); - if (!NewIndexRoot) + + DumpBTree(NewTree); + + // Insert the key for the file we're adding + Status = NtfsInsertKey(FileReferenceNumber, FilenameAttribute, NewTree->RootNode, CaseSensitive); + if (!NT_SUCCESS(Status)) { - DPRINT1("ERROR: Unable to allocate memory for new index root attribute!\n"); + DPRINT1("ERROR: Failed to insert key into B-Tree!\n"); + DestroyBTree(NewTree); ReleaseAttributeContext(IndexRootContext); ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); - return STATUS_INSUFFICIENT_RESOURCES; + return Status; } + + DumpBTree(NewTree); - // Setup the new index record - RtlZeroMemory(NewIndexRoot, DeviceExt->NtfsInfo.BytesPerIndexRecord); // shouldn't be necessary but aids in debugging + // Convert B*Tree back to Index Root + Status = CreateIndexRootFromBTree(DeviceExt, NewTree, MaxIndexSize, &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; + } - 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 = 0x10; - NewIndexRoot->Header.Flags = INDEX_ROOT_SMALL; - // still need to calculate sizes - - // The first index node entry will be for the filename we're adding - IndexNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)NewIndexRoot + NewIndexRoot->Header.FirstEntryOffset + 0x10); - IndexNodeEntry->Data.Directory.IndexedFile = FileReferenceNumber; - IndexNodeEntry->Flags = INDEX_ROOT_SMALL; - IndexNodeEntry->KeyLength = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + (2 * FilenameAttribute->NameLength); - IndexNodeEntry->Length = ALIGN_UP_BY(IndexNodeEntry->KeyLength, 8) + FIELD_OFFSET(INDEX_ENTRY_ATTRIBUTE, FileName); - - // Now we can calculate the Node length (temp logic) - NewIndexRoot->Header.TotalSizeOfEntries = NewIndexRoot->Header.FirstEntryOffset + IndexNodeEntry->Length + 0x10; - NewIndexRoot->Header.AllocatedSize = NewIndexRoot->Header.TotalSizeOfEntries; - - DPRINT1("New Index Node Entry Stream Length: %u\nNew Inde Node Entry Length: %u\n", - IndexNodeEntry->KeyLength, - IndexNodeEntry->Length); - - // copy over the attribute proper - RtlCopyMemory(&IndexNodeEntry->FileName, FilenameAttribute, IndexNodeEntry->KeyLength); - - // Now setup the dummy key - IndexNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexNodeEntry + IndexNodeEntry->Length); - - IndexNodeEntry->Data.Directory.IndexedFile = 0; - IndexNodeEntry->Length = 0x10; - IndexNodeEntry->KeyLength = 0; - IndexNodeEntry->Flags = NTFS_INDEX_ENTRY_END; - - // This is when we'd normally setup the length (already done above) + // 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 make sure the attribute is large enough. + // 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 if we extend past the file record, // we must create an index allocation and index bitmap (TODO). Also TODO: support file records with // $ATTRIBUTE_LIST's. AttributeLength = NewIndexRoot->Header.AllocatedSize + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header); DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset); + // Find the attribute (or attribute-end marker) after the index root NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)DestinationAttribute + DestinationAttribute->Length); if (NextAttribute->Type != AttributeEnd) { @@ -230,24 +220,27 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); return STATUS_NOT_IMPLEMENTED; } - + // Update the length of the attribute in the file record of the parent directory InternalSetResidentAttributeLength(IndexRootContext, ParentFileRecord, IndexRootOffset, AttributeLength); + 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; } - // Update the parent directory with the new index root + // Write the new index root to disk Status = WriteAttribute(DeviceExt, IndexRootContext, 0, diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index d738ef5fe2c..3e01f469a48 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -395,6 +395,28 @@ typedef struct FILENAME_ATTRIBUTE FileName; } INDEX_ENTRY_ATTRIBUTE, *PINDEX_ENTRY_ATTRIBUTE; +// Keys are arranged in nodes as an ordered, linked list +typedef struct _B_TREE_KEY +{ + struct _B_TREE_KEY *NextKey; + // PB_TREE_FILENAME_NODE LesserChild; // we aren't worried about multi-level trees yet + 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 +{ + int KeyCount; + 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; @@ -559,6 +581,8 @@ DecodeRun(PUCHAR DataRun, LONGLONG *DataRunOffset, ULONGLONG *DataRunLength); +ULONG GetFileNameAttributeLength(PFILENAME_ATTRIBUTE FileNameAttribute); + VOID NtfsDumpDataRuns(PVOID StartOfRun, ULONGLONG CurrentLCN); @@ -645,6 +669,38 @@ 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,*/ + 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); + +VOID +DestroyBTree(PB_TREE Tree); + +VOID +DumpBTree(PB_TREE Tree); + +NTSTATUS +NtfsInsertKey(ULONGLONG FileReference, + PFILENAME_ATTRIBUTE FileNameAttribute, + PB_TREE_FILENAME_NODE Node, + BOOLEAN CaseSensitive); + /* close.c */ NTSTATUS @@ -683,8 +739,9 @@ NtfsDeviceControl(PNTFS_IRP_CONTEXT IrpContext); NTSTATUS NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, ULONGLONG DirectoryMftIndex, - ULONGLONG FileMftIndex, - PFILENAME_ATTRIBUTE FilenameAttribute); + ULONGLONG FileReferenceNumber, + PFILENAME_ATTRIBUTE FilenameAttribute, + BOOLEAN CaseSensitive); ULONGLONG NtfsGetFileSize(PDEVICE_EXTENSION DeviceExt, From 2dc4938549ef2315a2f301eb452e9b9ad6f14835 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Wed, 28 Jun 2017 18:22:42 +0000 Subject: [PATCH 44/71] [NTFS] - Fix a couple of issues spotted by Thomas: Maintain alphabetical ordering of CMakeLists.txt, fix casts to BOOLEAN with BooleanFlagOn macro. svn path=/branches/GSoC_2016/NTFS/; revision=75224 --- drivers/filesystems/ntfs/CMakeLists.txt | 2 +- drivers/filesystems/ntfs/create.c | 2 +- drivers/filesystems/ntfs/dirctl.c | 2 +- drivers/filesystems/ntfs/finfo.c | 2 +- drivers/filesystems/ntfs/rw.c | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/filesystems/ntfs/CMakeLists.txt b/drivers/filesystems/ntfs/CMakeLists.txt index b9c72a72e7d..0b1af78947f 100644 --- a/drivers/filesystems/ntfs/CMakeLists.txt +++ b/drivers/filesystems/ntfs/CMakeLists.txt @@ -1,8 +1,8 @@ list(APPEND SOURCE attrib.c - btree.c blockdev.c + btree.c cleanup.c close.c create.c diff --git a/drivers/filesystems/ntfs/create.c b/drivers/filesystems/ntfs/create.c index 586da89b36a..4da9470a6b4 100644 --- a/drivers/filesystems/ntfs/create.c +++ b/drivers/filesystems/ntfs/create.c @@ -419,7 +419,7 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject, Status = NtfsOpenFile(DeviceExt, FileObject, ((RequestedOptions & FILE_OPEN_BY_FILE_ID) ? FullPath.Buffer : FileObject->FileName.Buffer), - (Stack->Flags & SL_CASE_SENSITIVE), + BooleanFlagOn(Stack->Flags, SL_CASE_SENSITIVE), &Fcb); if (RequestedOptions & FILE_OPEN_BY_FILE_ID) diff --git a/drivers/filesystems/ntfs/dirctl.c b/drivers/filesystems/ntfs/dirctl.c index ba7231dc807..c3bcdb43cb7 100644 --- a/drivers/filesystems/ntfs/dirctl.c +++ b/drivers/filesystems/ntfs/dirctl.c @@ -621,7 +621,7 @@ NtfsQueryDirectory(PNTFS_IRP_CONTEXT IrpContext) &FileRecord, &MFTRecord, Fcb->MFTIndex, - (Stack->Flags & SL_CASE_SENSITIVE)); + BooleanFlagOn(Stack->Flags, SL_CASE_SENSITIVE)); if (NT_SUCCESS(Status)) { diff --git a/drivers/filesystems/ntfs/finfo.c b/drivers/filesystems/ntfs/finfo.c index 1cb7dbc0fe9..ed1515c8d6b 100644 --- a/drivers/filesystems/ntfs/finfo.c +++ b/drivers/filesystems/ntfs/finfo.c @@ -635,7 +635,7 @@ NtfsSetInformation(PNTFS_IRP_CONTEXT IrpContext) FileObject, DeviceExt, Irp->Flags, - (Stack->Flags & SL_CASE_SENSITIVE), + BooleanFlagOn(Stack->Flags, SL_CASE_SENSITIVE), &EndOfFileInfo->EndOfFile); break; diff --git a/drivers/filesystems/ntfs/rw.c b/drivers/filesystems/ntfs/rw.c index 215269a25c2..7ef808f8c2b 100644 --- a/drivers/filesystems/ntfs/rw.c +++ b/drivers/filesystems/ntfs/rw.c @@ -686,7 +686,7 @@ NtfsWrite(PNTFS_IRP_CONTEXT IrpContext) Length, ByteOffset.LowPart, Irp->Flags, - (IrpContext->Stack->Flags & SL_CASE_SENSITIVE), + BooleanFlagOn(IrpContext->Stack->Flags, SL_CASE_SENSITIVE), &ReturnedWriteLength); IrpContext->Irp->IoStatus.Status = Status; From 16204ed3a703aded3eda9f8aaa9835570c893894 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Thu, 29 Jun 2017 02:36:00 +0000 Subject: [PATCH 45/71] [NTFS] - Fix gcc build. Fix CompareTreeKeys(): Don't consider Key1 a possible dummy key. Don't assume filenames are the same length. svn path=/branches/GSoC_2016/NTFS/; revision=75228 --- drivers/filesystems/ntfs/btree.c | 71 ++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 17 deletions(-) diff --git a/drivers/filesystems/ntfs/btree.c b/drivers/filesystems/ntfs/btree.c index 604c02c2906..d23871d3ff6 100644 --- a/drivers/filesystems/ntfs/btree.c +++ b/drivers/filesystems/ntfs/btree.c @@ -54,21 +54,18 @@ * > 0 if key1 is greater than key2 * * @remarks -* Any other key is always less than the final (dummy) key in a node. +* 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; // If Key2 is the "dummy key", key 1 will always come first if (Key2->NextKey == NULL) return -1; - // If Key1 is the "dummy key", key 2 will always come first - if (Key1->NextKey == NULL) - return 1; - Key1Name.Buffer = Key1->IndexEntry->FileName.Name; Key1Name.Length = Key1Name.MaximumLength = Key1->IndexEntry->FileName.NameLength * sizeof(WCHAR); @@ -77,7 +74,38 @@ CompareTreeKeys(PB_TREE_KEY Key1, PB_TREE_KEY Key2, BOOLEAN CaseSensitive) Key2Name.Length = Key2Name.MaximumLength = Key2->IndexEntry->FileName.NameLength * sizeof(WCHAR); - return RtlCompareUnicodeString(&Key1Name, &Key2Name, !CaseSensitive); + // 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 files 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 files are the same length, the shorter one comes first + if (Comparison == 0) + return 1; + } + + return Comparison; } /** @@ -241,6 +269,8 @@ CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt, PINDEX_ROOT_ATTRIBUTE *IndexRoot, ULONG *Length) { + int i; + PB_TREE_KEY CurrentKey; PINDEX_ENTRY_ATTRIBUTE CurrentNodeEntry; PINDEX_ROOT_ATTRIBUTE NewIndexRoot = ExAllocatePoolWithTag(NonPagedPool, DeviceExt->NtfsInfo.BytesPerFileRecord, @@ -274,11 +304,11 @@ CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt, NewIndexRoot->Header.TotalSizeOfEntries = NewIndexRoot->Header.FirstEntryOffset; // Setup each Node Entry - PB_TREE_KEY CurrentKey = Tree->RootNode->FirstKey; + CurrentKey = Tree->RootNode->FirstKey; CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)NewIndexRoot + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header) + NewIndexRoot->Header.FirstEntryOffset); - for (int i = 0; i < Tree->RootNode->KeyCount; i++) + 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 = FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header) @@ -335,7 +365,8 @@ DestroyBTreeNode(PB_TREE_FILENAME_NODE Node) { PB_TREE_KEY NextKey; PB_TREE_KEY CurrentKey = Node->FirstKey; - for (int i = 0; i < Node->KeyCount; i++) + int i; + for (i = 0; i < Node->KeyCount; i++) { NT_ASSERT(CurrentKey); NextKey = CurrentKey->NextKey; @@ -370,7 +401,8 @@ DestroyBTree(PB_TREE Tree) VOID DumpBTreeKey(PB_TREE_KEY Key, int Number, int Depth) { - for (int i = 0; i < Depth; i++) + int i; + for (i = 0; i < Depth; i++) DbgPrint(" "); DbgPrint(" Key #%d", Number); @@ -386,14 +418,17 @@ DumpBTreeKey(PB_TREE_KEY Key, int Number, int Depth) DbgPrint(" (Dummy Key)\n"); } +VOID DumpBTreeNode(PB_TREE_FILENAME_NODE Node, int Number, int Depth) { - for (int i = 0; i < Depth; i++) + PB_TREE_KEY CurrentKey; + int i; + for (i = 0; i < Depth; i++) DbgPrint(" "); DbgPrint("Node #%d, Depth %d\n", Number, Depth); - PB_TREE_KEY CurrentKey = Node->FirstKey; - for (int i = 0; i < Node->KeyCount; i++) + CurrentKey = Node->FirstKey; + for (i = 0; i < Node->KeyCount; i++) { DumpBTreeKey(CurrentKey, i, Depth); CurrentKey = CurrentKey->NextKey; @@ -451,6 +486,8 @@ NtfsInsertKey(ULONGLONG FileReference, ULONG AttributeSize = GetFileNameAttributeLength(FileNameAttribute); ULONG EntrySize = ALIGN_UP_BY(AttributeSize + FIELD_OFFSET(INDEX_ENTRY_ATTRIBUTE, FileName), 8); PINDEX_ENTRY_ATTRIBUTE NewEntry; + PB_TREE_KEY NewKey, CurrentKey, PreviousKey; + int i; DPRINT1("NtfsInsertKey(0x%02I64, %p, %p, %s)\n", FileReference, @@ -476,14 +513,14 @@ NtfsInsertKey(ULONGLONG FileReference, RtlCopyMemory(&NewEntry->FileName, FileNameAttribute, AttributeSize); // Setup the New Key - PB_TREE_KEY NewKey = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_KEY), TAG_NTFS); + NewKey = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_KEY), TAG_NTFS); NewKey->IndexEntry = NewEntry; NewKey->NextKey = NULL; // Find where to insert the key - PB_TREE_KEY CurrentKey = Node->FirstKey; - PB_TREE_KEY PreviousKey = NULL; - for (int i = 0; i < Node->KeyCount; i++) + 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); From 612facc28fb9e0bc15cb759abda871b8f7478477 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Tue, 4 Jul 2017 20:47:06 +0000 Subject: [PATCH 46/71] [NTFS] - Fix AddFileName() when the file path is two or more directories deep. svn path=/branches/GSoC_2016/NTFS/; revision=75277 --- drivers/filesystems/ntfs/attrib.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index 239e1c04001..4fece45c6c5 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -146,7 +146,7 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, ULONGLONG CurrentMFTIndex = NTFS_FILE_ROOT; UNICODE_STRING Current, Remaining, FilenameNoPath; NTSTATUS Status = STATUS_SUCCESS; - ULONG FirstEntry = 0; + ULONG FirstEntry; WCHAR Buffer[MAX_PATH]; if (AttributeAddress->Type != AttributeEnd) @@ -183,6 +183,7 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, if(Remaining.Length != 0) RtlCopyUnicodeString(&FilenameNoPath, &Remaining); + FirstEntry = 0; Status = NtfsFindMftRecord(DeviceExt, CurrentMFTIndex, &Current, @@ -200,7 +201,7 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, break; } - FsRtlDissectName(Current, &Current, &Remaining); + FsRtlDissectName(Remaining, &Current, &Remaining); } DPRINT1("MFT Index of parent: %I64u\n", CurrentMFTIndex); From f5b7f90f237d6d21249c203753d9c8c1e65ab84f Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Tue, 4 Jul 2017 21:16:46 +0000 Subject: [PATCH 47/71] [NTFS] - Fix UpdateFileNameRecord() when the file being updated resides in $INDEX_ROOT. Also, don't attempt to read beyond size of index root attribute, and check return value after reading. svn path=/branches/GSoC_2016/NTFS/; revision=75278 --- drivers/filesystems/ntfs/mft.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 6ffe8d53ad2..f8aff9ccd2a 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -1418,7 +1418,15 @@ UpdateFileNameRecord(PDEVICE_EXTENSION Vcb, return STATUS_INSUFFICIENT_RESOURCES; } - ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, Vcb->NtfsInfo.BytesPerIndexRecord); + Status = ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, AttributeDataLength(&IndexRootCtx->Record)); + 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. @@ -1440,6 +1448,18 @@ UpdateFileNameRecord(PDEVICE_EXTENSION Vcb, NewAllocationSize, CaseSensitive); + if (Status == STATUS_PENDING) + { + // we need to write the index root attribute back to disk + ULONG LengthWritten; + Status = WriteAttribute(Vcb, IndexRootCtx, 0, IndexRecord, AttributeDataLength(&IndexRootCtx->Record), &LengthWritten); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Couldn't update Index Root!\n"); + } + + } + ReleaseAttributeContext(IndexRootCtx); ExFreePoolWithTag(IndexRecord, TAG_NTFS); ExFreePoolWithTag(MftRecord, TAG_NTFS); From e5cc846555486cd27c68d51ad1c68417b9c6b771 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Tue, 4 Jul 2017 21:40:02 +0000 Subject: [PATCH 48/71] [NTFS] - Add some fixes and improvements to attribute.c from CR-123: -AddFileName() - Don't use dedicated buffer for FileNameNoPath, just point into FileObject->FileName's buffer. Don't use RtlCopyUnicodeString(), just update pointers. -AddRun() - Fix an SEH2_YIELD with missing return statement. -FreeClusters() - Use ULONG_MAX in place of 0xffffffff. -NtfsDumpIndexRootAttribute() - Use consistent UpperCase naming convention. Use BooleanFlagOn() macro where appropriate. Replace magic 8 with sizeof(ULONGLONG). -GetFileNameAttributeLength() - Add documentation. svn path=/branches/GSoC_2016/NTFS/; revision=75279 --- drivers/filesystems/ntfs/attrib.c | 66 +++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 21 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index 4fece45c6c5..018c69b20f5 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -29,6 +29,7 @@ /* INCLUDES *****************************************************************/ #include "ntfs.h" +#include #define NDEBUG #include @@ -147,7 +148,6 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, UNICODE_STRING Current, Remaining, FilenameNoPath; NTSTATUS Status = STATUS_SUCCESS; ULONG FirstEntry; - WCHAR Buffer[MAX_PATH]; if (AttributeAddress->Type != AttributeEnd) { @@ -172,7 +172,8 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, // we need to extract the filename from the path DPRINT1("Pathname: %wZ\n", &FileObject->FileName); - RtlInitEmptyUnicodeString(&FilenameNoPath, Buffer, MAX_PATH); + FilenameNoPath.Buffer = FileObject->FileName.Buffer; + FilenameNoPath.MaximumLength = FilenameNoPath.Length = FileObject->FileName.Length; FsRtlDissectName(FileObject->FileName, &Current, &Remaining); @@ -180,8 +181,11 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, { DPRINT1("Current: %wZ\n", &Current); - if(Remaining.Length != 0) - RtlCopyUnicodeString(&FilenameNoPath, &Remaining); + if (Remaining.Length != 0) + { + FilenameNoPath.Buffer = Remaining.Buffer; + FilenameNoPath.Length = FilenameNoPath.MaximumLength = Remaining.Length; + } FirstEntry = 0; Status = NtfsFindMftRecord(DeviceExt, @@ -196,8 +200,11 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, if (Remaining.Length == 0 ) { - if(Current.Length != 0) - RtlCopyUnicodeString(&FilenameNoPath, &Current); + if (Current.Length != 0) + { + FilenameNoPath.Buffer = Current.Buffer; + FilenameNoPath.Length = FilenameNoPath.MaximumLength = Current.Length; + } break; } @@ -317,9 +324,13 @@ AddRun(PNTFS_VCB Vcb, { ExRaiseStatus(STATUS_UNSUCCESSFUL); } - } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { - _SEH2_YIELD(_SEH2_GetExceptionCode()); - } _SEH2_END; + } + _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) @@ -756,7 +767,7 @@ FreeClusters(PNTFS_VCB Vcb, } BitmapDataSize = AttributeDataLength(&DataContext->Record); - BitmapDataSize = min(BitmapDataSize, 0xffffffff); + 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) @@ -1144,8 +1155,8 @@ VOID NtfsDumpIndexRootAttribute(PNTFS_ATTR_RECORD Attribute) { PINDEX_ROOT_ATTRIBUTE IndexRootAttr; - ULONG currentOffset; - ULONG currentNode; + ULONG CurrentOffset; + ULONG CurrentNode; IndexRootAttr = (PINDEX_ROOT_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset); @@ -1168,18 +1179,18 @@ NtfsDumpIndexRootAttribute(PNTFS_ATTR_RECORD Attribute) IndexRootAttr->Header.FirstEntryOffset, IndexRootAttr->Header.TotalSizeOfEntries, IndexRootAttr->Header.AllocatedSize); - currentOffset = IndexRootAttr->Header.FirstEntryOffset; - currentNode = 0; + CurrentOffset = IndexRootAttr->Header.FirstEntryOffset; + CurrentNode = 0; // print details of every node in the index - while (currentOffset < IndexRootAttr->Header.TotalSizeOfEntries) + while (CurrentOffset < IndexRootAttr->Header.TotalSizeOfEntries) { - PINDEX_ENTRY_ATTRIBUTE currentIndexExtry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexRootAttr + 0x10 + currentOffset); - DbgPrint(" Index Node Entry %lu", currentNode++); - if (currentIndexExtry->Flags & NTFS_INDEX_ENTRY_NODE) + 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((currentIndexExtry->Flags & NTFS_INDEX_ENTRY_END)) + if (BooleanFlagOn(currentIndexExtry->Flags, NTFS_INDEX_ENTRY_END)) { DbgPrint(" (Dummy Key)"); } @@ -1203,11 +1214,11 @@ NtfsDumpIndexRootAttribute(PNTFS_ATTR_RECORD Attribute) if (currentIndexExtry->Flags & NTFS_INDEX_ENTRY_NODE) { // Print the VCN of the sub-node - PULONGLONG SubNodeVCN = (PULONGLONG)((ULONG_PTR)currentIndexExtry + currentIndexExtry->Length - 8); + PULONGLONG SubNodeVCN = (PULONGLONG)((ULONG_PTR)currentIndexExtry + currentIndexExtry->Length - sizeof(ULONGLONG)); DbgPrint(" VCN of sub-node: 0x%llx\n", *SubNodeVCN); } - currentOffset += currentIndexExtry->Length; + CurrentOffset += currentIndexExtry->Length; ASSERT(currentIndexExtry->Length); } @@ -1567,6 +1578,19 @@ 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)); From 7e9acb7ddae095b06d573d4d5bb19a93624c739a Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Tue, 4 Jul 2017 21:47:43 +0000 Subject: [PATCH 49/71] [NTFS] - Add some fixes and improvements to btree.c from CR-123: -CompareTreeKeys() - Assert that the first key isn't the dummy key. -CreateIndexRootFromBTree() - Assert that CurrentKey->IndexEntry->Length isn't 0. -DumpBTreeKey() - Use sizeof(WCHAR) in place of magic 2. -NtfsInsertKey() - Check for allocation failure of NewKey. svn path=/branches/GSoC_2016/NTFS/; revision=75280 --- drivers/filesystems/ntfs/btree.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/drivers/filesystems/ntfs/btree.c b/drivers/filesystems/ntfs/btree.c index d23871d3ff6..f4641d005c4 100644 --- a/drivers/filesystems/ntfs/btree.c +++ b/drivers/filesystems/ntfs/btree.c @@ -62,6 +62,8 @@ CompareTreeKeys(PB_TREE_KEY Key1, PB_TREE_KEY Key2, BOOLEAN CaseSensitive) UNICODE_STRING Key1Name, Key2Name; LONG Comparison; + 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; @@ -322,11 +324,10 @@ CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt, return STATUS_NOT_IMPLEMENTED; } + ASSERT(CurrentKey->IndexEntry->Length != 0); + // Copy the index entry - if (CurrentKey->IndexEntry->Length > 0) - RtlCopyMemory(CurrentNodeEntry, CurrentKey->IndexEntry, CurrentKey->IndexEntry->Length); - else - DPRINT1("DRIVER ERROR: CurrentKey->IndexEntry->Length <= 0 !\n"); + RtlCopyMemory(CurrentNodeEntry, CurrentKey->IndexEntry, CurrentKey->IndexEntry->Length); DPRINT1("Index Node Entry Stream Length: %u\nIndex Node Entry Length: %u\n", CurrentNodeEntry->KeyLength, @@ -409,7 +410,7 @@ DumpBTreeKey(PB_TREE_KEY Key, int Number, int Depth) if (!(Key->IndexEntry->Flags & NTFS_INDEX_ENTRY_END)) { UNICODE_STRING FileName; - FileName.Length = Key->IndexEntry->FileName.NameLength * 2; + FileName.Length = Key->IndexEntry->FileName.NameLength * sizeof(WCHAR); FileName.MaximumLength = FileName.Length; FileName.Buffer = Key->IndexEntry->FileName.Name; DbgPrint(" '%wZ'\n", &FileName); @@ -514,6 +515,12 @@ NtfsInsertKey(ULONGLONG FileReference, // 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 STATUS_INSUFFICIENT_RESOURCES; + } NewKey->IndexEntry = NewEntry; NewKey->NextKey = NULL; From 39a06fae0dcc928341e104ccbac7cacc5d7b4298 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Tue, 4 Jul 2017 22:34:17 +0000 Subject: [PATCH 50/71] [NTFS] - Add some fixes and improvements to create.c, dirctl.c and fcb.c from CR-123: -NtfsOpenFile() - Replace an ExFreePool() with ExFreePoolWithTag(). -NtfsCreateFile() - Fix broken cast with BooleanFlagOn() macro. -NtfsAddFilenameToDirectory() - Remove an extra cast. Return an error if we fail to allocate I30IndexRoot. -NtfsGetNextPathElement(), NtfsWSubString(), NtfsGetFCBForFile() - Use PCWSTR in place of const PWCHAR or PWCHAR where it makes sense. svn path=/branches/GSoC_2016/NTFS/; revision=75281 --- drivers/filesystems/ntfs/create.c | 4 ++-- drivers/filesystems/ntfs/dirctl.c | 3 ++- drivers/filesystems/ntfs/fcb.c | 10 +++++----- drivers/filesystems/ntfs/ntfs.h | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/filesystems/ntfs/create.c b/drivers/filesystems/ntfs/create.c index 4da9470a6b4..3c8ba81f021 100644 --- a/drivers/filesystems/ntfs/create.c +++ b/drivers/filesystems/ntfs/create.c @@ -304,7 +304,7 @@ NtfsOpenFile(PDEVICE_EXTENSION DeviceExt, DPRINT("Could not make a new FCB, status: %x\n", Status); if (AbsFileName) - ExFreePool(AbsFileName); + ExFreePoolWithTag(AbsFileName, TAG_NTFS); return Status; } @@ -572,7 +572,7 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject, // Create the file record on disk Status = NtfsCreateFileRecord(DeviceExt, FileObject, - (Stack->Flags & SL_CASE_SENSITIVE), + BooleanFlagOn(Stack->Flags, SL_CASE_SENSITIVE), BooleanFlagOn(IrpContext->Flags,IRPCONTEXT_CANWAIT)); if (!NT_SUCCESS(Status)) { diff --git a/drivers/filesystems/ntfs/dirctl.c b/drivers/filesystems/ntfs/dirctl.c index c3bcdb43cb7..c41688ea2b3 100644 --- a/drivers/filesystems/ntfs/dirctl.c +++ b/drivers/filesystems/ntfs/dirctl.c @@ -138,12 +138,13 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, // Allocate memory for the index root data I30IndexRootLength = AttributeDataLength(&IndexRootContext->Record); - I30IndexRoot = (PINDEX_ROOT_ATTRIBUTE)ExAllocatePoolWithTag(NonPagedPool, I30IndexRootLength, TAG_NTFS); + 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 diff --git a/drivers/filesystems/ntfs/fcb.c b/drivers/filesystems/ntfs/fcb.c index 851234c4fec..5497b8001de 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); @@ -593,13 +593,13 @@ 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; diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index 3e01f469a48..eb4d6f3f9f5 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -840,7 +840,7 @@ NTSTATUS NtfsGetFCBForFile(PNTFS_VCB Vcb, PNTFS_FCB *pParentFCB, PNTFS_FCB *pFCB, - const PWSTR pFileName, + PCWSTR pFileName, BOOLEAN CaseSensitive); NTSTATUS From 34696e49fc9b6ebcde32d9e6792ac4bae48fd184 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Wed, 5 Jul 2017 03:11:13 +0000 Subject: [PATCH 51/71] [NTFS] - Add some fixes and improvements to finfo.c from CR-123: NtfsSetEndOfFile() - Make fileNameAttribute and filename variables uppercase. Don't leak FileRecord if we can't truncate the file. Don't leak memory if there's no FileName attribute. svn path=/branches/GSoC_2016/NTFS/; revision=75283 --- drivers/filesystems/ntfs/finfo.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/drivers/filesystems/ntfs/finfo.c b/drivers/filesystems/ntfs/finfo.c index ed1515c8d6b..323b85cf45d 100644 --- a/drivers/filesystems/ntfs/finfo.c +++ b/drivers/filesystems/ntfs/finfo.c @@ -449,9 +449,9 @@ NtfsSetEndOfFile(PNTFS_FCB Fcb, ULONG AttributeOffset; NTSTATUS Status = STATUS_SUCCESS; ULONGLONG AllocationSize; - PFILENAME_ATTRIBUTE fileNameAttribute; + PFILENAME_ATTRIBUTE FileNameAttribute; ULONGLONG ParentMFTId; - UNICODE_STRING filename; + UNICODE_STRING FileName; // Allocate non-paged memory for the file record @@ -485,6 +485,7 @@ NtfsSetEndOfFile(PNTFS_FCB Fcb, NewFileSize)) { DPRINT1("Couldn't decrease file size!\n"); + ExFreePoolWithTag(FileRecord, TAG_NTFS); return STATUS_USER_MAPPED_FILE; } } @@ -535,24 +536,26 @@ NtfsSetEndOfFile(PNTFS_FCB Fcb, // 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) + 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; + ParentMFTId = FileNameAttribute->DirectoryFileReferenceNumber & NTFS_MFT_MASK; - filename.Buffer = fileNameAttribute->Name; - filename.Length = fileNameAttribute->NameLength * sizeof(WCHAR); - filename.MaximumLength = filename.Length; + FileName.Buffer = FileNameAttribute->Name; + FileName.Length = FileNameAttribute->NameLength * sizeof(WCHAR); + FileName.MaximumLength = FileName.Length; AllocationSize = ROUND_UP(NewFileSize->QuadPart, Fcb->Vcb->NtfsInfo.BytesPerCluster); Status = UpdateFileNameRecord(Fcb->Vcb, ParentMFTId, - &filename, + &FileName, FALSE, NewFileSize->QuadPart, AllocationSize, From 68a48b2758c64bf1e8f4b7885020567441d14879 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Wed, 5 Jul 2017 05:09:51 +0000 Subject: [PATCH 52/71] [NTFS] - Add some fixes and improvements to mft.c from CR-123: FindAttribute() - Use * sizeof(WCHAR) in place bit-shifting wizardry. IncreaseMftSize() - Remove two useless casts. Return error code if WriteAttribute() fails. SetNonResidentAttributeDataLength() - Assert that AttrContext parameter describes a NonResident attribute. When migrating a resident attribute to non-resident, don't leak memory if FsRtlInitializeLargeMcb() throws an exception, and don't mark the attribute as non-resident until after the Mcb is initialized. InternalSetResidentAttributeLength() and SetResidentAttributeLength() - Assert that AttrContext parameter describes a resident attribute. AddNewMftEntry() - Simplify math of backing up and restoring reserved bits by changing BitmapData to PUCHAR type. Don't attempt to modify a volume with more than 2^32 clusters. AddFixupArray() - Use ULONG in place of int. svn path=/branches/GSoC_2016/NTFS/; revision=75284 --- drivers/filesystems/ntfs/mft.c | 69 ++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index f8aff9ccd2a..539b8f225c6 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -133,7 +133,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; } @@ -268,7 +268,7 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait) } // Zero the bytes we'll be adding - RtlZeroMemory((PUCHAR)((ULONG_PTR)BitmapBuffer), NewBitmapSize); + RtlZeroMemory(BitmapBuffer, NewBitmapSize); // Read the bitmap attribute BytesRead = ReadAttribute(Vcb, @@ -337,6 +337,7 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait) ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); ReleaseAttributeContext(BitmapContext); DPRINT1("ERROR: Couldn't write to bitmap attribute of $MFT!\n"); + return Status; } // Cleanup @@ -358,6 +359,8 @@ InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext, DPRINT("InternalSetResidentAttributeLength( %p, %p, %lu, %lu )\n", AttrContext, FileRecord, AttrOffset, DataSize); + ASSERT(!AttrContext->Record.IsNonResident); + // update ValueLength Field AttrContext->Record.Resident.ValueLength = Destination->Resident.ValueLength = DataSize; @@ -537,11 +540,7 @@ SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset); ULONG ExistingClusters = AttrContext->Record.NonResident.AllocatedSize / BytesPerCluster; - if (!AttrContext->Record.IsNonResident) - { - DPRINT1("ERROR: SetNonResidentAttributeDataLength() called for resident attribute!\n"); - return STATUS_INVALID_PARAMETER; - } + ASSERT(AttrContext->Record.IsNonResident); // do we need to increase the allocation size? if (AttrContext->Record.NonResident.AllocatedSize < AllocationSize) @@ -671,11 +670,7 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, ULONG NextAttributeOffset = AttrOffset + AttrContext->Record.Length; PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((PCHAR)FileRecord + NextAttributeOffset); - if (AttrContext->Record.IsNonResident) - { - DPRINT1("ERROR: SetResidentAttributeDataLength() called for non-resident attribute!\n"); - return STATUS_INVALID_PARAMETER; - } + ASSERT(!AttrContext->Record.IsNonResident); //NtfsDumpFileAttributes(Vcb, FileRecord); @@ -733,9 +728,6 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, // update the mapping pairs offset, which will be 0x40 + length in bytes of the name AttrContext->Record.NonResident.MappingPairsOffset = Destination->NonResident.MappingPairsOffset = 0x40 + (Destination->NameLength * 2); - // mark the attribute as non-resident - AttrContext->Record.IsNonResident = Destination->IsNonResident = 1; - // update the end of the file record // calculate position of end markers (1 byte for empty data run) EndAttributeOffset = AttrOffset + AttrContext->Record.NonResident.MappingPairsOffset + 1; @@ -750,7 +742,22 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + EndAttributeOffset), FILE_RECORD_END); - // update file record on disk + // Initialize the MCB, potentially catch an exception + _SEH2_TRY + { + FsRtlInitializeLargeMcb(&AttrContext->DataRunsMCB, NonPagedPool); + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + DPRINT1("Unable to create LargeMcb!\n"); + 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) + AttrContext->Record.IsNonResident = Destination->IsNonResident = 1; + + // Update file record on disk Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord); if (!NT_SUCCESS(Status)) { @@ -760,13 +767,6 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, return Status; } - // Initialize the MCB, potentially catch an exception - _SEH2_TRY{ - FsRtlInitializeLargeMcb(&AttrContext->DataRunsMCB, NonPagedPool); - } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { - _SEH2_YIELD(return _SEH2_GetExceptionCode()); - } _SEH2_END; - // Now we can treat the attribute as non-resident and enlarge it normally Status = SetNonResidentAttributeDataLength(Vcb, AttrContext, AttrOffset, FileRecord, DataSize); if (!NT_SUCCESS(Status)) @@ -1729,7 +1729,7 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, RTL_BITMAP Bitmap; ULONGLONG BitmapDataSize; ULONGLONG AttrBytesRead; - PVOID BitmapData; + PUCHAR BitmapData; ULONG LengthWritten; PNTFS_ATTR_CONTEXT BitmapContext; LARGE_INTEGER BitmapBits; @@ -1765,17 +1765,20 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, return STATUS_OBJECT_NAME_NOT_FOUND; } - // we need to backup the bits for records 0x10 - 0x17 and leave them unassigned if they aren't assigned - RtlCopyMemory(&SystemReservedBits, (PVOID)((ULONG_PTR)BitmapData + 2), 1); - RtlFillMemory((PVOID)((ULONG_PTR)BitmapData + 2), 1, (UCHAR)0xFF); + // 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->Record)) / DeviceExt->NtfsInfo.BytesPerFileRecord; if (BitmapBits.HighPart != 0) { - DPRINT1("\tFIXME: bitmap sizes beyond 32bits are not yet supported!\n"); - BitmapBits.LowPart = 0xFFFFFFFF; + DPRINT1("\tFIXME: bitmap sizes beyond 32bits are not yet supported! (Your NTFS volume is too large)\n"); + ExFreePoolWithTag(BitmapData, TAG_NTFS); + ReleaseAttributeContext(BitmapContext); + return STATUS_NOT_IMPLEMENTED; } // convert buffer into bitmap @@ -1809,7 +1812,7 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, // [BitmapData should have been updated via RtlFindClearBitsAndSet()] // Restore the system reserved bits - RtlCopyMemory((PVOID)((ULONG_PTR)BitmapData + 2), &SystemReservedBits, 1); + BitmapData[2] = SystemReservedBits; // write the bitmap back to the MFT's $Bitmap attribute Status = WriteAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize, &LengthWritten); @@ -1845,9 +1848,9 @@ AddFixupArray(PDEVICE_EXTENSION Vcb, PNTFS_RECORD_HEADER Record) { USHORT *pShortToFixUp; - unsigned int ArrayEntryCount = Record->UsaCount - 1; - unsigned int Offset = Vcb->NtfsInfo.BytesPerSector - 2; - int i; + ULONG ArrayEntryCount = Record->UsaCount - 1; + ULONG Offset = Vcb->NtfsInfo.BytesPerSector - 2; + ULONG i; PFIXUP_ARRAY fixupArray = (PFIXUP_ARRAY)((UCHAR*)Record + Record->UsaOffset); From 5ab24a5aaed797d556384e81047e9f75cb903e49 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Wed, 5 Jul 2017 16:29:16 +0000 Subject: [PATCH 53/71] [NTFS] - Add a few fixes and improvements, most from CR-123: -Add ATTR_RECORD_ALIGNMENT define to replace magic value of 8 when we need to adjust an attribute's beginning or length. -Don't use int's. -GetPackedByteCount() - Remove unused "bytes" variable. svn path=/branches/GSoC_2016/NTFS/; revision=75288 --- drivers/filesystems/ntfs/attrib.c | 16 +++++++--------- drivers/filesystems/ntfs/btree.c | 14 +++++++------- drivers/filesystems/ntfs/create.c | 2 +- drivers/filesystems/ntfs/mft.c | 4 ++-- drivers/filesystems/ntfs/ntfs.h | 6 +++++- 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index 018c69b20f5..7dcdabbea58 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -77,7 +77,7 @@ AddData(PFILE_RECORD_HEADER FileRecord, AttributeAddress->Type = AttributeData; AttributeAddress->Length = ResidentHeaderLength; - AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, 8); + AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, ATTR_RECORD_ALIGNMENT); AttributeAddress->Resident.ValueLength = 0; AttributeAddress->Resident.ValueOffset = ResidentHeaderLength; @@ -241,7 +241,7 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, AttributeAddress->Length = ResidentHeaderLength + FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + FilenameNoPath.Length; - AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, 8); + AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, ATTR_RECORD_ALIGNMENT); AttributeAddress->Resident.ValueLength = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + FilenameNoPath.Length; AttributeAddress->Resident.ValueOffset = ResidentHeaderLength; @@ -363,7 +363,7 @@ AddRun(PNTFS_VCB Vcb, // calculate position of end markers NextAttributeOffset = AttrOffset + AttrContext->Record.NonResident.MappingPairsOffset + RunBufferSize; - NextAttributeOffset = ALIGN_UP_BY(NextAttributeOffset, 8); + NextAttributeOffset = ALIGN_UP_BY(NextAttributeOffset, ATTR_RECORD_ALIGNMENT); // Update the length DestinationAttribute->Length = NextAttributeOffset - AttrOffset; @@ -437,7 +437,7 @@ AddStandardInformation(PFILE_RECORD_HEADER FileRecord, AttributeAddress->Type = AttributeStandardInformation; AttributeAddress->Length = sizeof(STANDARD_INFORMATION) + ResidentHeaderLength; - AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, 8); + AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, ATTR_RECORD_ALIGNMENT); AttributeAddress->Resident.ValueLength = sizeof(STANDARD_INFORMATION); AttributeAddress->Resident.ValueOffset = ResidentHeaderLength; AttributeAddress->Instance = FileRecord->NextAttributeNumber++; @@ -847,7 +847,8 @@ FreeClusters(PNTFS_VCB Vcb, if (NextAttribute->Type == AttributeEnd) { // update attribute length - AttrContext->Record.Length = ALIGN_UP_BY(AttrContext->Record.NonResident.MappingPairsOffset + RunBufferSize, 8); + AttrContext->Record.Length = ALIGN_UP_BY(AttrContext->Record.NonResident.MappingPairsOffset + RunBufferSize, + ATTR_RECORD_ALIGNMENT); DestinationAttribute->Length = AttrContext->Record.Length; // write end markers @@ -1459,7 +1460,6 @@ UCHAR GetPackedByteCount(LONGLONG NumberToPack, BOOLEAN IsSigned) { - int bytes = 0; if (!IsSigned) { if (NumberToPack >= 0x0100000000000000) @@ -1496,7 +1496,6 @@ GetPackedByteCount(LONGLONG NumberToPack, return 3; if (NumberToPack >= 0x0000000000000080) return 2; - return 1; } else { @@ -1515,9 +1514,8 @@ GetPackedByteCount(LONGLONG NumberToPack, return 3; if (NumberToPack <= 0xffffffffffffff80) return 2; - return 1; } - return bytes; + return 1; } NTSTATUS diff --git a/drivers/filesystems/ntfs/btree.c b/drivers/filesystems/ntfs/btree.c index f4641d005c4..eff2b157ade 100644 --- a/drivers/filesystems/ntfs/btree.c +++ b/drivers/filesystems/ntfs/btree.c @@ -271,7 +271,7 @@ CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt, PINDEX_ROOT_ATTRIBUTE *IndexRoot, ULONG *Length) { - int i; + ULONG i; PB_TREE_KEY CurrentKey; PINDEX_ENTRY_ATTRIBUTE CurrentNodeEntry; PINDEX_ROOT_ATTRIBUTE NewIndexRoot = ExAllocatePoolWithTag(NonPagedPool, @@ -366,7 +366,7 @@ DestroyBTreeNode(PB_TREE_FILENAME_NODE Node) { PB_TREE_KEY NextKey; PB_TREE_KEY CurrentKey = Node->FirstKey; - int i; + ULONG i; for (i = 0; i < Node->KeyCount; i++) { NT_ASSERT(CurrentKey); @@ -400,9 +400,9 @@ DestroyBTree(PB_TREE Tree) } VOID -DumpBTreeKey(PB_TREE_KEY Key, int Number, int Depth) +DumpBTreeKey(PB_TREE_KEY Key, ULONG Number, ULONG Depth) { - int i; + ULONG i; for (i = 0; i < Depth; i++) DbgPrint(" "); DbgPrint(" Key #%d", Number); @@ -420,10 +420,10 @@ DumpBTreeKey(PB_TREE_KEY Key, int Number, int Depth) } VOID -DumpBTreeNode(PB_TREE_FILENAME_NODE Node, int Number, int Depth) +DumpBTreeNode(PB_TREE_FILENAME_NODE Node, ULONG Number, ULONG Depth) { PB_TREE_KEY CurrentKey; - int i; + ULONG i; for (i = 0; i < Depth; i++) DbgPrint(" "); DbgPrint("Node #%d, Depth %d\n", Number, Depth); @@ -488,7 +488,7 @@ NtfsInsertKey(ULONGLONG FileReference, ULONG EntrySize = ALIGN_UP_BY(AttributeSize + FIELD_OFFSET(INDEX_ENTRY_ATTRIBUTE, FileName), 8); PINDEX_ENTRY_ATTRIBUTE NewEntry; PB_TREE_KEY NewKey, CurrentKey, PreviousKey; - int i; + ULONG i; DPRINT1("NtfsInsertKey(0x%02I64, %p, %p, %s)\n", FileReference, diff --git a/drivers/filesystems/ntfs/create.c b/drivers/filesystems/ntfs/create.c index 3c8ba81f021..8274d979922 100644 --- a/drivers/filesystems/ntfs/create.c +++ b/drivers/filesystems/ntfs/create.c @@ -699,7 +699,7 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, // setup other file record fields FileRecord->SequenceNumber = 1; FileRecord->AttributeOffset = FileRecord->Ntfs.UsaOffset + (2 * FileRecord->Ntfs.UsaCount); - FileRecord->AttributeOffset = ALIGN_UP_BY(FileRecord->AttributeOffset, 8); + FileRecord->AttributeOffset = ALIGN_UP_BY(FileRecord->AttributeOffset, ATTR_RECORD_ALIGNMENT); FileRecord->Flags = FRH_IN_USE; FileRecord->BytesInUse = FileRecord->AttributeOffset + sizeof(ULONG) * 2; diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 539b8f225c6..87820e64578 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -373,7 +373,7 @@ InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext, // Ensure NextAttributeOffset is aligned to an 8-byte boundary if (NextAttributeOffset % 8 != 0) { - USHORT Padding = 8 - (NextAttributeOffset % 8); + USHORT Padding = ATTR_RECORD_ALIGNMENT - (NextAttributeOffset % ATTR_RECORD_ALIGNMENT); NextAttributeOffset += Padding; AttrContext->Record.Length += Padding; Destination->Length += Padding; @@ -731,7 +731,7 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, // update the end of the file record // calculate position of end markers (1 byte for empty data run) EndAttributeOffset = AttrOffset + AttrContext->Record.NonResident.MappingPairsOffset + 1; - EndAttributeOffset = ALIGN_UP_BY(EndAttributeOffset, 8); + EndAttributeOffset = ALIGN_UP_BY(EndAttributeOffset, ATTR_RECORD_ALIGNMENT); // Update the length Destination->Length = EndAttributeOffset - AttrOffset; diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index eb4d6f3f9f5..ddb876daf9c 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -293,6 +293,10 @@ 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 + typedef struct { ULONGLONG CreationTime; @@ -408,7 +412,7 @@ typedef struct _B_TREE_KEY // A key's sub-node precedes that key in the ordered list. typedef struct { - int KeyCount; + ULONG KeyCount; PB_TREE_KEY FirstKey; } B_TREE_FILENAME_NODE, *PB_TREE_FILENAME_NODE; From 430ce0a9e395ba1f98e4417bd14286dd9eeac552 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Wed, 5 Jul 2017 22:10:22 +0000 Subject: [PATCH 54/71] [NTFS] - Fix some formatting / style issues, per CR-123. svn path=/branches/GSoC_2016/NTFS/; revision=75290 --- drivers/filesystems/ntfs/attrib.c | 5 +++-- drivers/filesystems/ntfs/btree.c | 4 +++- drivers/filesystems/ntfs/create.c | 4 ++-- drivers/filesystems/ntfs/fcb.c | 2 +- drivers/filesystems/ntfs/mft.c | 4 ++-- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index 7dcdabbea58..bd070c97ad9 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -316,7 +316,8 @@ AddRun(PNTFS_VCB Vcb, NextVBN = AttrContext->Record.NonResident.HighestVCN + 1; // Add newly-assigned clusters to mcb - _SEH2_TRY{ + _SEH2_TRY + { if (!FsRtlAddLargeMcbEntry(&AttrContext->DataRunsMCB, NextVBN, NextAssignedCluster, @@ -796,7 +797,7 @@ FreeClusters(PNTFS_VCB Vcb, break; } - if( LargeLbn != -1) + if (LargeLbn != -1) { // deallocate this cluster RtlClearBits(&Bitmap, LargeLbn, 1); diff --git a/drivers/filesystems/ntfs/btree.c b/drivers/filesystems/ntfs/btree.c index eff2b157ade..179107272bc 100644 --- a/drivers/filesystems/ntfs/btree.c +++ b/drivers/filesystems/ntfs/btree.c @@ -77,7 +77,7 @@ CompareTreeKeys(PB_TREE_KEY Key1, PB_TREE_KEY Key2, BOOLEAN CaseSensitive) = Key2->IndexEntry->FileName.NameLength * sizeof(WCHAR); // Are the two keys the same length? - if(Key1Name.Length == Key2Name.Length) + if (Key1Name.Length == Key2Name.Length) return RtlCompareUnicodeString(&Key1Name, &Key2Name, !CaseSensitive); // Is Key1 shorter? @@ -416,7 +416,9 @@ DumpBTreeKey(PB_TREE_KEY Key, ULONG Number, ULONG Depth) DbgPrint(" '%wZ'\n", &FileName); } else + { DbgPrint(" (Dummy Key)\n"); + } } VOID diff --git a/drivers/filesystems/ntfs/create.c b/drivers/filesystems/ntfs/create.c index 8274d979922..30cc9210959 100644 --- a/drivers/filesystems/ntfs/create.c +++ b/drivers/filesystems/ntfs/create.c @@ -299,7 +299,7 @@ NtfsOpenFile(PDEVICE_EXTENSION DeviceExt, ParentFcb); } - if (!NT_SUCCESS (Status)) + if (!NT_SUCCESS(Status)) { DPRINT("Could not make a new FCB, status: %x\n", Status); @@ -347,7 +347,7 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject, DeviceExt = DeviceObject->DeviceExtension; ASSERT(DeviceExt); - Stack = IoGetCurrentIrpStackLocation (Irp); + Stack = IoGetCurrentIrpStackLocation(Irp); ASSERT(Stack); RequestedDisposition = ((Stack->Parameters.Create.Options >> 24) & 0xff); diff --git a/drivers/filesystems/ntfs/fcb.c b/drivers/filesystems/ntfs/fcb.c index 5497b8001de..fe79511bb32 100644 --- a/drivers/filesystems/ntfs/fcb.c +++ b/drivers/filesystems/ntfs/fcb.c @@ -617,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"); diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 87820e64578..ed531df173e 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -448,7 +448,7 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, if (NT_SUCCESS(Status)) { - if(AttrContext->Record.IsNonResident) + if (AttrContext->Record.IsNonResident) Fcb->RFCB.AllocationSize.QuadPart = AttrContext->Record.NonResident.AllocatedSize; else Fcb->RFCB.AllocationSize = *DataSize; @@ -1319,7 +1319,7 @@ WriteAttribute(PDEVICE_EXTENSION Vcb, } // end while (Length > 0) [more data to write] // TEMPTEMP - if(Context->Record.IsNonResident) + if (Context->Record.IsNonResident) ExFreePoolWithTag(TempBuffer, TAG_NTFS); Context->CacheRun = DataRun; From 935fcd1b353a65d3af01e3f9061d3784f759cdbc Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Tue, 18 Jul 2017 19:59:36 +0000 Subject: [PATCH 55/71] [NTFS] - Fix some more issues, including remaining issues marked as "unresolved" from CR-123: -Add define for indexed flag for resident attributes, RA_INDEXED. -CreateBTreeFromIndex() - Don't try to read index entries beyond attribute length. -Move NtfsAddFileNameToDirectory() from dirctl.c to mft.c (no changes to function). -SetResidentAttributeDataLength() - Don't try to free AttribData if it hasn't been allocated. Cast IndexRecord to PUCHAR for WriteAttribute(), and cast BitmapData to PCHAR for ReadAttribute() (make gcc happy). -Replace magic number 16 with a define, NTFS_FILE_FIRST_USER_FILE. svn path=/branches/GSoC_2016/NTFS/; revision=75371 --- drivers/filesystems/ntfs/attrib.c | 2 +- drivers/filesystems/ntfs/btree.c | 30 ++-- drivers/filesystems/ntfs/create.c | 4 +- drivers/filesystems/ntfs/dirctl.c | 245 ---------------------------- drivers/filesystems/ntfs/mft.c | 256 +++++++++++++++++++++++++++++- drivers/filesystems/ntfs/ntfs.h | 18 ++- 6 files changed, 284 insertions(+), 271 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index bd070c97ad9..a6bc9a8aca1 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -245,7 +245,7 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, AttributeAddress->Resident.ValueLength = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + FilenameNoPath.Length; AttributeAddress->Resident.ValueOffset = ResidentHeaderLength; - AttributeAddress->Resident.Flags = 1; // indexed + 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); diff --git a/drivers/filesystems/ntfs/btree.c b/drivers/filesystems/ntfs/btree.c index 179107272bc..a7eacb3173c 100644 --- a/drivers/filesystems/ntfs/btree.c +++ b/drivers/filesystems/ntfs/btree.c @@ -138,6 +138,7 @@ CreateBTreeFromIndex(PNTFS_ATTR_CONTEXT IndexRootContext, 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; DPRINT1("CreateBTreeFromIndex(%p, %p, %p)\n", IndexRootContext, IndexRoot, NewTree); @@ -161,11 +162,21 @@ CreateBTreeFromIndex(PNTFS_ATTR_CONTEXT IndexRootContext, RootNode->FirstKey = CurrentKey; Tree->RootNode = RootNode; - // Create a key for each entry in the node + // Make sure we won't try reading past the attribute-end + if (FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header) + IndexRoot->Header.TotalSizeOfEntries > IndexRootContext->Record.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); - while (TRUE) + + // 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); @@ -207,6 +218,7 @@ CreateBTreeFromIndex(PNTFS_ATTR_CONTEXT IndexRootContext, } // Advance to the next entry + CurrentOffset += CurrentNodeEntry->Length; CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)CurrentNodeEntry + CurrentNodeEntry->Length); CurrentKey = NextKey; } @@ -336,7 +348,7 @@ CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt, // Add Length of Current Entry to Total Size of Entries NewIndexRoot->Header.TotalSizeOfEntries += CurrentNodeEntry->Length; - // Go to the next node + // Go to the next node entry CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)CurrentNodeEntry + CurrentNodeEntry->Length); CurrentKey = CurrentKey->NextKey; @@ -533,16 +545,12 @@ NtfsInsertKey(ULONGLONG FileReference, { // Should the New Key go before the current key? LONG Comparison = CompareTreeKeys(NewKey, CurrentKey, CaseSensitive); - if (Comparison == 0) - { - DPRINT1("DRIVER ERROR: Asked to insert key into tree that already has it!\n"); - ExFreePoolWithTag(NewKey, TAG_NTFS); - ExFreePoolWithTag(NewEntry, TAG_NTFS); - return STATUS_INVALID_PARAMETER; - } + + ASSERT(Comparison != 0); + + // Is NewKey < CurrentKey? if (Comparison < 0) { - // NewKey is < CurrentKey // Insert New Key before Current Key NewKey->NextKey = CurrentKey; diff --git a/drivers/filesystems/ntfs/create.c b/drivers/filesystems/ntfs/create.c index 30cc9210959..f5ca1027fc0 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; @@ -375,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); } diff --git a/drivers/filesystems/ntfs/dirctl.c b/drivers/filesystems/ntfs/dirctl.c index c41688ea2b3..98294b47913 100644 --- a/drivers/filesystems/ntfs/dirctl.c +++ b/drivers/filesystems/ntfs/dirctl.c @@ -34,251 +34,6 @@ /* FUNCTIONS ****************************************************************/ -/** -* @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; - PNTFS_ATTR_RECORD DestinationAttribute; - PINDEX_ROOT_ATTRIBUTE NewIndexRoot; - ULONG AttributeLength; - PNTFS_ATTR_RECORD NextAttribute; - PB_TREE NewTree; - ULONG BtreeIndexLength; - ULONG MaxIndexSize; - - // 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; - } - - DPRINT1("Dumping old parent file record:\n"); - NtfsDumpFileRecord(DeviceExt, ParentFileRecord); - - // 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 - MaxIndexSize = DeviceExt->NtfsInfo.BytesPerFileRecord - - IndexRootOffset - - IndexRootContext->Record.Resident.ValueOffset - - FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header) - - (sizeof(ULONG) * 2); - - // Allocate memory for the index root data - I30IndexRootLength = AttributeDataLength(&IndexRootContext->Record); - 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(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; - } - - DumpBTree(NewTree); - - // Insert the key for the file we're adding - Status = NtfsInsertKey(FileReferenceNumber, FilenameAttribute, NewTree->RootNode, CaseSensitive); - 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; - } - - DumpBTree(NewTree); - - // Convert B*Tree back to Index Root - Status = CreateIndexRootFromBTree(DeviceExt, NewTree, MaxIndexSize, &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 if we extend past the file record, - // we must create an index allocation and index bitmap (TODO). Also TODO: support file records with - // $ATTRIBUTE_LIST's. - AttributeLength = NewIndexRoot->Header.AllocatedSize + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header); - DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset); - - // Find the attribute (or attribute-end marker) after the index root - NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)DestinationAttribute + DestinationAttribute->Length); - if (NextAttribute->Type != AttributeEnd) - { - DPRINT1("FIXME: For now, only resizing index root at the end of a file record is supported!\n"); - ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); - ReleaseAttributeContext(IndexRootContext); - ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); - ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); - return STATUS_NOT_IMPLEMENTED; - } - - // Update the length of the attribute in the file record of the parent directory - InternalSetResidentAttributeLength(IndexRootContext, - ParentFileRecord, - IndexRootOffset, - AttributeLength); - - 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); - if (!NT_SUCCESS(Status) ) - { - 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 - { - DPRINT1("Dumping new parent file record:\n"); - NtfsDumpFileRecord(DeviceExt, ParentFileRecord); - } - - // Cleanup - ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); - ReleaseAttributeContext(IndexRootContext); - ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); - ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); - - return Status; -} - ULONGLONG NtfsGetFileSize(PDEVICE_EXTENSION DeviceExt, PFILE_RECORD_HEADER FileRecord, diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index ed531df173e..72953c00868 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -750,7 +750,8 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { DPRINT1("Unable to create LargeMcb!\n"); - ExFreePoolWithTag(AttribData, TAG_NTFS); + if (AttribDataSize.QuadPart > 0) + ExFreePoolWithTag(AttribData, TAG_NTFS); _SEH2_YIELD(return _SEH2_GetExceptionCode()); } _SEH2_END; @@ -1452,7 +1453,7 @@ UpdateFileNameRecord(PDEVICE_EXTENSION Vcb, { // we need to write the index root attribute back to disk ULONG LengthWritten; - Status = WriteAttribute(Vcb, IndexRootCtx, 0, IndexRecord, AttributeDataLength(&IndexRootCtx->Record), &LengthWritten); + Status = WriteAttribute(Vcb, IndexRootCtx, 0, (PUCHAR)IndexRecord, AttributeDataLength(&IndexRootCtx->Record), &LengthWritten); if (!NT_SUCCESS(Status)) { DPRINT1("ERROR: Couldn't update Index Root!\n"); @@ -1514,7 +1515,7 @@ UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb, while (IndexEntry < LastEntry && !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END)) { - if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) > 0x10 && + 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)) @@ -1755,7 +1756,7 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, } // read $Bitmap attribute - AttrBytesRead = ReadAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize); + AttrBytesRead = ReadAttribute(DeviceExt, BitmapContext, 0, (PCHAR)BitmapData, BitmapDataSize); if (AttrBytesRead == 0) { @@ -1843,6 +1844,251 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, 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; + PNTFS_ATTR_RECORD DestinationAttribute; + PINDEX_ROOT_ATTRIBUTE NewIndexRoot; + ULONG AttributeLength; + PNTFS_ATTR_RECORD NextAttribute; + PB_TREE NewTree; + ULONG BtreeIndexLength; + ULONG MaxIndexSize; + + // 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; + } + + DPRINT1("Dumping old parent file record:\n"); + NtfsDumpFileRecord(DeviceExt, ParentFileRecord); + + // 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 + MaxIndexSize = DeviceExt->NtfsInfo.BytesPerFileRecord + - IndexRootOffset + - IndexRootContext->Record.Resident.ValueOffset + - FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header) + - (sizeof(ULONG) * 2); + + // Allocate memory for the index root data + I30IndexRootLength = AttributeDataLength(&IndexRootContext->Record); + 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(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; + } + + DumpBTree(NewTree); + + // Insert the key for the file we're adding + Status = NtfsInsertKey(FileReferenceNumber, FilenameAttribute, NewTree->RootNode, CaseSensitive); + 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; + } + + DumpBTree(NewTree); + + // Convert B*Tree back to Index Root + Status = CreateIndexRootFromBTree(DeviceExt, NewTree, MaxIndexSize, &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 if we extend past the file record, + // we must create an index allocation and index bitmap (TODO). Also TODO: support file records with + // $ATTRIBUTE_LIST's. + AttributeLength = NewIndexRoot->Header.AllocatedSize + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header); + DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset); + + // Find the attribute (or attribute-end marker) after the index root + NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)DestinationAttribute + DestinationAttribute->Length); + if (NextAttribute->Type != AttributeEnd) + { + DPRINT1("FIXME: For now, only resizing index root at the end of a file record is supported!\n"); + ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); + ReleaseAttributeContext(IndexRootContext); + ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); + ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); + return STATUS_NOT_IMPLEMENTED; + } + + // Update the length of the attribute in the file record of the parent directory + InternalSetResidentAttributeLength(IndexRootContext, + ParentFileRecord, + IndexRootOffset, + AttributeLength); + + 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); + if (!NT_SUCCESS(Status)) + { + 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 + { + DPRINT1("Dumping new parent file record:\n"); + NtfsDumpFileRecord(DeviceExt, ParentFileRecord); + } + + // 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) @@ -1995,7 +2241,7 @@ BrowseIndexEntries(PDEVICE_EXTENSION Vcb, while (IndexEntry < LastEntry && !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END)) { - if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) >= 0x10 && + 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)) diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index ddb876daf9c..9cbc4d54858 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -193,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 @@ -223,6 +224,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' */ @@ -740,13 +744,6 @@ NtfsDeviceControl(PNTFS_IRP_CONTEXT IrpContext); /* dirctl.c */ -NTSTATUS -NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, - ULONGLONG DirectoryMftIndex, - ULONGLONG FileReferenceNumber, - PFILENAME_ATTRIBUTE FilenameAttribute, - BOOLEAN CaseSensitive); - ULONGLONG NtfsGetFileSize(PDEVICE_EXTENSION DeviceExt, PFILE_RECORD_HEADER FileRecord, @@ -888,6 +885,13 @@ 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, From 9cef425464df506c5475a47e3723f9de507f1341 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Thu, 27 Jul 2017 11:35:50 +0000 Subject: [PATCH 56/71] [NTFS] - After creating a new file, update creation disposition before calling NtfsCreateFile() recursively. This fixes creating a file via right-clicking in a folder. svn path=/branches/GSoC_2016/NTFS/; revision=75423 --- drivers/filesystems/ntfs/create.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/filesystems/ntfs/create.c b/drivers/filesystems/ntfs/create.c index f5ca1027fc0..a98afc67b7f 100644 --- a/drivers/filesystems/ntfs/create.c +++ b/drivers/filesystems/ntfs/create.c @@ -580,8 +580,18 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject, return Status; } - // Now we should be able to open the file - return NtfsCreateFile(DeviceObject, IrpContext); + // Before we open the file 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; } } From 5579428b4f1361481dae3ea0b901c0a915401148 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Thu, 27 Jul 2017 18:22:24 +0000 Subject: [PATCH 57/71] [NTFS] - Add some utility functions and improve some comments. Improve NtfsAddFilenameToDirectory(). +PrintAllVCNs() - Diagnostic function which prints VCN of every node in an index allocation. +GetAllocationOffsetFromVCN() - Calculates location of an index buffer from the node's VCN. +GetInfoClassName() - Gets a string representation of an info class enumeration, to speed up development of unimplemented classes. -NtfsSetInformation() & NtfsQueryInformation() - Use GetInfoClassName to report unhandled information classes. -CompareTreeKeys() - Add a comment and clarify some comments. -NtfsAddFilenameToDirectory() - Don't try to update the size of Index Root on disk if the attribute length hasn't changed. svn path=/branches/GSoC_2016/NTFS/; revision=75424 --- drivers/filesystems/ntfs/btree.c | 46 ++++++++++- drivers/filesystems/ntfs/finfo.c | 128 ++++++++++++++++++++++++++++++- drivers/filesystems/ntfs/mft.c | 38 +++++---- drivers/filesystems/ntfs/ntfs.h | 16 ++++ 4 files changed, 206 insertions(+), 22 deletions(-) diff --git a/drivers/filesystems/ntfs/btree.c b/drivers/filesystems/ntfs/btree.c index a7eacb3173c..38fa9aefee7 100644 --- a/drivers/filesystems/ntfs/btree.c +++ b/drivers/filesystems/ntfs/btree.c @@ -32,6 +32,47 @@ /* 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->Record); + ULONGLONG i; + int Count = 0; + + Buffer = ExAllocatePoolWithTag(NonPagedPool, BufferSize, TAG_NTFS); + + ULONG 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 CompareTreeKeys * @implemented @@ -62,6 +103,7 @@ 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 @@ -89,7 +131,7 @@ CompareTreeKeys(PB_TREE_KEY Key1, PB_TREE_KEY Key2, BOOLEAN CaseSensitive) // Compare the names of the same length Comparison = RtlCompareUnicodeString(&Key1Name, &Key2Name, !CaseSensitive); - // If the truncated files are the same length, the shorter one comes first + // If the truncated names are the same length, the shorter one comes first if (Comparison == 0) return -1; } @@ -102,7 +144,7 @@ CompareTreeKeys(PB_TREE_KEY Key1, PB_TREE_KEY Key2, BOOLEAN CaseSensitive) // Compare the names of the same length Comparison = RtlCompareUnicodeString(&Key1Name, &Key2Name, !CaseSensitive); - // If the truncated files are the same length, the shorter one comes first + // If the truncated names are the same length, the shorter one comes first if (Comparison == 0) return 1; } diff --git a/drivers/filesystems/ntfs/finfo.c b/drivers/filesystems/ntfs/finfo.c index 323b85cf45d..aee8aa51e2d 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; } @@ -645,7 +767,7 @@ NtfsSetInformation(PNTFS_IRP_CONTEXT IrpContext) // TODO: all other information classes default: - DPRINT1("FIXME: Unimplemented information class %u\n", FileInformationClass); + DPRINT1("FIXME: Unimplemented information class: %s\n", GetInfoClassName(FileInformationClass)); Status = STATUS_NOT_IMPLEMENTED; } diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 72953c00868..928839edd78 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -2018,25 +2018,29 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, // we must create an index allocation and index bitmap (TODO). Also TODO: support file records with // $ATTRIBUTE_LIST's. AttributeLength = NewIndexRoot->Header.AllocatedSize + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header); - DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset); - - // Find the attribute (or attribute-end marker) after the index root - NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)DestinationAttribute + DestinationAttribute->Length); - if (NextAttribute->Type != AttributeEnd) + + if (AttributeLength != IndexRootContext->Record.Resident.ValueLength) { - DPRINT1("FIXME: For now, only resizing index root at the end of a file record is supported!\n"); - ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); - ReleaseAttributeContext(IndexRootContext); - ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); - ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); - return STATUS_NOT_IMPLEMENTED; - } + DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset); - // Update the length of the attribute in the file record of the parent directory - InternalSetResidentAttributeLength(IndexRootContext, - ParentFileRecord, - IndexRootOffset, - AttributeLength); + // Find the attribute (or attribute-end marker) after the index root + NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)DestinationAttribute + DestinationAttribute->Length); + if (NextAttribute->Type != AttributeEnd) + { + DPRINT1("FIXME: For now, only resizing index root at the end of a file record is supported!\n"); + ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); + ReleaseAttributeContext(IndexRootContext); + ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); + ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); + return STATUS_NOT_IMPLEMENTED; + } + + // Update the length of the attribute in the file record of the parent directory + InternalSetResidentAttributeLength(IndexRootContext, + ParentFileRecord, + IndexRootOffset, + AttributeLength); + } NT_ASSERT(ParentFileRecord->BytesInUse <= DeviceExt->NtfsInfo.BytesPerFileRecord); diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index 9cbc4d54858..39c33210fcf 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -208,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 @@ -700,9 +703,22 @@ CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt, VOID DestroyBTree(PB_TREE Tree); +VOID +DestroyBTreeNode(PB_TREE_FILENAME_NODE Node); + VOID DumpBTree(PB_TREE Tree); +VOID +DumpBTreeNode(PB_TREE_FILENAME_NODE Node, + ULONG Number, + ULONG Depth); + +ULONGLONG +GetAllocationOffsetFromVCN(PDEVICE_EXTENSION DeviceExt, + ULONG IndexBufferSize, + ULONGLONG Vcn); + NTSTATUS NtfsInsertKey(ULONGLONG FileReference, PFILENAME_ATTRIBUTE FileNameAttribute, From b1eab36f6bed95fd08de770d07645e933c29f729 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Thu, 27 Jul 2017 18:34:23 +0000 Subject: [PATCH 58/71] [NTFS] - Add GetAllocationOffsetFromVCN() function, which was mistakenly left out of the last commit. svn path=/branches/GSoC_2016/NTFS/; revision=75425 --- drivers/filesystems/ntfs/btree.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/filesystems/ntfs/btree.c b/drivers/filesystems/ntfs/btree.c index 38fa9aefee7..f4bdf909a1f 100644 --- a/drivers/filesystems/ntfs/btree.c +++ b/drivers/filesystems/ntfs/btree.c @@ -511,6 +511,18 @@ DumpBTree(PB_TREE Tree) DumpBTreeNode(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; +} + /** * @name NtfsInsertKey * @implemented From 92d1f92a6134c70c80e87d10dd79f3a80da70ffa Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Thu, 27 Jul 2017 19:52:33 +0000 Subject: [PATCH 59/71] [NTFS] - Add some improvements to B-Tree support. Add preliminary support for trees of arbitrary depth. Add support for creating files in directories which have an index allocation, provided there aren't too many files and the node doesn't need to be split. -CreateBTreeFromIndex() - Fix memory allocation; allocate sizeof(B_TREE_KEY) bytes for the key, not the size of the pointer. Add support for child nodes. Update parameter list. -CreateIndexRootFromBTree() - Update Header->Flags if any of index root's keys have sub-nodes. +CreateIndexBufferFromBTreeNode() - Converts a B-Tree node to an index buffer to be written to the index allocation. +UpdateIndexAllocation() - Updates all of the stale nodes in an index allocation based on a PB_TREE. +UpdateIndexNode() - Writes a B-Tree node into the index allocation. +CreateBTreeKeyFromFilename() - Creates a PB_TREE_KEY based on a FILENAME_ATTRIBUTE. -DestroyBTreeKey() - Destroy a child node, if present. -DumpBTreeKey() - Dump a child node, if present. -DumpBTreeNode() - Include Node->KeyCount in output. -NtfsAddFilenameToDirectory() - Call UpdateIndexAllocation() prior to CreateIndexRootFromBTree(). -Update B_TREE_FILENAME_NODE with members to keep track of its existence on disk. svn path=/branches/GSoC_2016/NTFS/; revision=75428 --- drivers/filesystems/ntfs/btree.c | 581 ++++++++++++++++++++++++++++--- drivers/filesystems/ntfs/mft.c | 20 +- drivers/filesystems/ntfs/ntfs.h | 29 +- 3 files changed, 570 insertions(+), 60 deletions(-) diff --git a/drivers/filesystems/ntfs/btree.c b/drivers/filesystems/ntfs/btree.c index f4bdf909a1f..c1a907e8fea 100644 --- a/drivers/filesystems/ntfs/btree.c +++ b/drivers/filesystems/ntfs/btree.c @@ -42,12 +42,13 @@ PrintAllVCNs(PDEVICE_EXTENSION Vcb, ULONGLONG CurrentOffset = 0; PINDEX_BUFFER CurrentNode, Buffer; ULONGLONG BufferSize = AttributeDataLength(&IndexAllocationContext->Record); + ULONG BytesRead; ULONGLONG i; int Count = 0; Buffer = ExAllocatePoolWithTag(NonPagedPool, BufferSize, TAG_NTFS); - ULONG BytesRead = ReadAttribute(Vcb, IndexAllocationContext, 0, (PCHAR)Buffer, BufferSize); + BytesRead = ReadAttribute(Vcb, IndexAllocationContext, 0, (PCHAR)Buffer, BufferSize); ASSERT(BytesRead = BufferSize); @@ -152,6 +153,175 @@ CompareTreeKeys(PB_TREE_KEY Key1, PB_TREE_KEY Key2, BOOLEAN CaseSensitive) return Comparison; } +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 NodeNumber; + 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 + NodeNumber = (PULONGLONG)((ULONG_PTR)NodeEntry + NodeEntry->Length - sizeof(ULONGLONG)); + + // Create the new tree node + DPRINT1("About to allocate %ld for NewNode\n", sizeof(B_TREE_FILENAME_NODE)); + NewNode = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_FILENAME_NODE), TAG_NTFS); + if (!NewNode) + { + 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, *NodeNumber); + + // 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 == *NodeNumber); + + // 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) + { + DPRINT1("TODO: Only a node with a single-level is supported right now!\n"); + // Needs debugging: + /*CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb, + IndexRoot, + IndexAllocationAttributeCtx, + CurrentNodeEntry);*/ + } + + 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) + { + DPRINT1("TODO: Only a node with a single-level is supported right now!\n"); + // Needs debugging: + /*CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb, + IndexRoot, + IndexAllocationAttributeCtx, + CurrentNodeEntry);*/ + } + + break; + } + + // Advance to the next entry + CurrentEntryOffset += CurrentNodeEntry->Length; + CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)CurrentNodeEntry + CurrentNodeEntry->Length); + } + + NewNode->NodeNumber = *NodeNumber; + NewNode->ExistsOnDisk = TRUE; + + ExFreePoolWithTag(NodeBuffer, TAG_NTFS); + + return NewNode; +} + /** * @name CreateBTreeFromIndex * @implemented @@ -172,7 +342,10 @@ CompareTreeKeys(PB_TREE_KEY Key1, PB_TREE_KEY Key2, BOOLEAN CaseSensitive) * Allocates memory for the entire tree. Caller is responsible for destroying the tree with DestroyBTree(). */ NTSTATUS -CreateBTreeFromIndex(PNTFS_ATTR_CONTEXT IndexRootContext, +CreateBTreeFromIndex(PDEVICE_EXTENSION Vcb, + PFILE_RECORD_HEADER FileRecordWithIndex, + /*PCWSTR IndexName,*/ + PNTFS_ATTR_CONTEXT IndexRootContext, PINDEX_ROOT_ATTRIBUTE IndexRoot, PB_TREE *NewTree) { @@ -181,8 +354,10 @@ CreateBTreeFromIndex(PNTFS_ATTR_CONTEXT IndexRootContext, 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, %p)\n", IndexRootContext, IndexRoot, NewTree); + DPRINT1("CreateBTreeFromIndex(%p, %p)\n", IndexRoot, NewTree); if (!Tree || !RootNode || !CurrentKey) { @@ -200,6 +375,19 @@ CreateBTreeFromIndex(PNTFS_ATTR_CONTEXT IndexRootContext, 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; + else + PrintAllVCNs(Vcb, IndexAllocationContext, IndexRoot->SizeOfEntry); + // Setup the Tree RootNode->FirstKey = CurrentKey; Tree->RootNode = RootNode; @@ -235,7 +423,7 @@ CreateBTreeFromIndex(PNTFS_ATTR_CONTEXT IndexRootContext, if (!(CurrentNodeEntry->Flags & NTFS_INDEX_ENTRY_END)) { // Create the next key - PB_TREE_KEY NextKey = ExAllocatePoolWithTag(NonPagedPool, sizeof(PB_TREE_KEY), TAG_NTFS); + PB_TREE_KEY NextKey = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_KEY), TAG_NTFS); if (!NextKey) { DPRINT1("ERROR: Couldn't allocate memory for next key!\n"); @@ -243,7 +431,7 @@ CreateBTreeFromIndex(PNTFS_ATTR_CONTEXT IndexRootContext, return STATUS_INSUFFICIENT_RESOURCES; } - RtlZeroMemory(NextKey, sizeof(PB_TREE_KEY)); + RtlZeroMemory(NextKey, sizeof(B_TREE_KEY)); // Add NextKey to the end of the list CurrentKey->NextKey = NextKey; @@ -251,12 +439,20 @@ CreateBTreeFromIndex(PNTFS_ATTR_CONTEXT IndexRootContext, // Copy the current entry to its key RtlCopyMemory(CurrentKey->IndexEntry, CurrentNodeEntry, CurrentNodeEntry->Length); - // Make sure this B-Tree is only one level deep (flat list) + // Does this key have a sub-node? if (CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) { - DPRINT1("TODO: Only directories with single-level B-Trees are supported right now!\n"); - DestroyBTree(Tree); - return STATUS_NOT_IMPLEMENTED; + // 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 @@ -270,12 +466,20 @@ CreateBTreeFromIndex(PNTFS_ATTR_CONTEXT IndexRootContext, RtlCopyMemory(CurrentKey->IndexEntry, CurrentNodeEntry, CurrentNodeEntry->Length); CurrentKey->NextKey = NULL; - // Make sure this B-Tree is only one level deep (flat list) + // Does this key have a sub-node? if (CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) { - DPRINT1("TODO: Only directories with single-level B-Trees are supported right now!\n"); - DestroyBTree(Tree); - return STATUS_NOT_IMPLEMENTED; + // 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; @@ -284,6 +488,9 @@ CreateBTreeFromIndex(PNTFS_ATTR_CONTEXT IndexRootContext, *NewTree = Tree; + if (IndexAllocationContext) + ReleaseAttributeContext(IndexAllocationContext); + return STATUS_SUCCESS; } @@ -387,6 +594,10 @@ CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt, 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 += CurrentNodeEntry->Length; @@ -404,13 +615,253 @@ CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt, return STATUS_SUCCESS; } +NTSTATUS +CreateIndexBufferFromBTreeNode(PDEVICE_EXTENSION DeviceExt, + PB_TREE_FILENAME_NODE Node, + ULONG BufferSize, + 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->ExistsOnDisk); + IndexBuffer->VCN = Node->NodeNumber; + + 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.FirstEntryOffset + + 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); + + DPRINT1("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; + + // TODO: Check for child nodes + + // 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; +} + +NTSTATUS +UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt, + PB_TREE Tree, + ULONG IndexBufferSize, + PFILE_RECORD_HEADER FileRecord) +{ + // Find the index allocation and bitmap + PNTFS_ATTR_CONTEXT IndexAllocationContext, BitmapContext; + PB_TREE_KEY CurrentKey; + NTSTATUS Status; + BOOLEAN HasIndexAllocation = FALSE; + ULONG i; + + DPRINT1("UpdateIndexAllocations() called.\n"); + + Status = FindAttribute(DeviceExt, FileRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationContext, NULL); + if (NT_SUCCESS(Status)) + HasIndexAllocation = TRUE; + + // TODO: Handle bitmap + BitmapContext = NULL; + + // 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) + { + DPRINT1("FIXME: Need to add index allocation\n"); + return STATUS_NOT_IMPLEMENTED; + } + else + { + Status = UpdateIndexNode(DeviceExt, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, BitmapContext); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to update index node!\n"); + ReleaseAttributeContext(IndexAllocationContext); + return Status; + } + } + + } + CurrentKey = CurrentKey->NextKey; + } + + if(HasIndexAllocation) + ReleaseAttributeContext(IndexAllocationContext); + + return STATUS_SUCCESS; +} + +NTSTATUS +UpdateIndexNode(PDEVICE_EXTENSION DeviceExt, + PB_TREE_FILENAME_NODE Node, + ULONG IndexBufferSize, + PNTFS_ATTR_CONTEXT IndexAllocationContext, + PNTFS_ATTR_CONTEXT BitmapContext) +{ + ULONG i; + PB_TREE_KEY CurrentKey = Node->FirstKey; + NTSTATUS Status; + + DPRINT1("UpdateIndexNode(%p, %p, %lu, %p, %p) called for index node with VCN %I64u\n", DeviceExt, Node, IndexBufferSize, IndexAllocationContext, BitmapContext, Node->NodeNumber); + + // Do we need to write this node to disk? + if (Node->DiskNeedsUpdating) + { + ULONGLONG NodeOffset; + ULONG LengthWritten; + + // Allocate memory for an index buffer + PINDEX_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, 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->NodeNumber); + + // Write the buffer to the index allocation + Status = WriteAttribute(DeviceExt, IndexAllocationContext, NodeOffset, (const PUCHAR)IndexBuffer, IndexBufferSize, &LengthWritten); + 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); + } + + // 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) + { + // Update the child node on disk + Status = UpdateIndexNode(DeviceExt, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, BitmapContext); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to update child node!\n"); + return Status; + } + } + + CurrentKey = CurrentKey->NextKey; + } + + 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); - // We'll destroy Key->LesserChild here after we start using it + if (Key->LesserChild) + DestroyBTreeNode(Key->LesserChild); ExFreePoolWithTag(Key, TAG_NTFS); } @@ -473,6 +924,18 @@ DumpBTreeKey(PB_TREE_KEY Key, ULONG Number, ULONG Depth) { DbgPrint(" (Dummy Key)\n"); } + + // Is there a child node? + if (Key->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) + { + if (Key->LesserChild) + DumpBTreeNode(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 @@ -482,7 +945,7 @@ DumpBTreeNode(PB_TREE_FILENAME_NODE Node, ULONG Number, ULONG Depth) ULONG i; for (i = 0; i < Depth; i++) DbgPrint(" "); - DbgPrint("Node #%d, Depth %d\n", Number, Depth); + DbgPrint("Node #%d, Depth %d, has %d keys\n", Number, Depth, Node->KeyCount); CurrentKey = Node->FirstKey; for (i = 0; i < Node->KeyCount; i++) @@ -551,46 +1014,23 @@ NtfsInsertKey(ULONGLONG FileReference, PB_TREE_FILENAME_NODE Node, BOOLEAN CaseSensitive) { - // Calculate size of Attribute and Index Entry - ULONG AttributeSize = GetFileNameAttributeLength(FileNameAttribute); - ULONG EntrySize = ALIGN_UP_BY(AttributeSize + FIELD_OFFSET(INDEX_ENTRY_ATTRIBUTE, FileName), 8); - PINDEX_ENTRY_ATTRIBUTE NewEntry; PB_TREE_KEY NewKey, CurrentKey, PreviousKey; + NTSTATUS Status = STATUS_SUCCESS; + ULONG NodeSize; + ULONG AllocatedNodeSize; + ULONG MaxNodeSizeWithoutHeader; ULONG i; - DPRINT1("NtfsInsertKey(0x%02I64, %p, %p, %s)\n", + DPRINT1("NtfsInsertKey(0x%I64x, %p, %p, %s)\n", FileReference, FileNameAttribute, Node, CaseSensitive ? "TRUE" : "FALSE"); - // Create a new Index Entry for the file - NewEntry = ExAllocatePoolWithTag(NonPagedPool, EntrySize, TAG_NTFS); - if (!NewEntry) - { - DPRINT1("ERROR: Failed to allocate memory for Index Entry!\n"); - return STATUS_INSUFFICIENT_RESOURCES; - } - - // 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); + // Create the key for the filename attribute + NewKey = CreateBTreeKeyFromFilename(FileReference, FileNameAttribute); if (!NewKey) - { - DPRINT1("ERROR: Failed to allocate memory for new key!\n"); - ExFreePoolWithTag(NewEntry, TAG_NTFS); return STATUS_INSUFFICIENT_RESOURCES; - } - NewKey->IndexEntry = NewEntry; - NewKey->NextKey = NULL; // Find where to insert the key CurrentKey = Node->FirstKey; @@ -605,24 +1045,57 @@ NtfsInsertKey(ULONGLONG FileReference, // Is NewKey < CurrentKey? if (Comparison < 0) { - // Insert New Key before Current Key - NewKey->NextKey = CurrentKey; - // was CurrentKey the first key? - if (CurrentKey == Node->FirstKey) - Node->FirstKey = NewKey; + // Does CurrentKey have a sub-node? + if (CurrentKey->LesserChild) + { + // Insert the key into the child node + Status = NtfsInsertKey(FileReference, FileNameAttribute, CurrentKey->LesserChild, CaseSensitive); + } else - PreviousKey->NextKey = NewKey; - break; + { + // 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; } - Node->KeyCount++; + // Is the node larger than its allocated size? + NodeSize = 0; + CurrentKey = Node->FirstKey; + for (i = 0; i < Node->KeyCount; i++) + { + NodeSize += CurrentKey->IndexEntry->Length; + CurrentKey = CurrentKey->NextKey; + } + + // TEMPTEMP: TODO: MATH + AllocatedNodeSize = 0xfe8; + MaxNodeSizeWithoutHeader = AllocatedNodeSize - 0x28; + + if (NodeSize > MaxNodeSizeWithoutHeader) + { + DPRINT1("FIXME: Splitting a node is still a WIP!\n"); + //SplitBTreeNode(NULL, Node); + //DumpBTree(Tree); + return STATUS_NOT_IMPLEMENTED; + } // NewEntry and NewKey will be destroyed later by DestroyBTree() - return STATUS_SUCCESS; + return Status; } \ No newline at end of file diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 928839edd78..1df31aee0ce 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -1969,7 +1969,11 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, } // Convert the index to a B*Tree - Status = CreateBTreeFromIndex(IndexRootContext, I30IndexRoot, &NewTree); + Status = CreateBTreeFromIndex(DeviceExt, + ParentFileRecord, + IndexRootContext, + I30IndexRoot, + &NewTree); if (!NT_SUCCESS(Status)) { DPRINT1("ERROR: Failed to create B-Tree from Index!\n"); @@ -1995,7 +1999,19 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, DumpBTree(NewTree); - // Convert B*Tree back to Index Root + // Convert B*Tree back to Index + 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; + } + + // Create the Index Root from the B*Tree Status = CreateIndexRootFromBTree(DeviceExt, NewTree, MaxIndexSize, &NewIndexRoot, &BtreeIndexLength); if (!NT_SUCCESS(Status)) { diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index 39c33210fcf..42168be1abe 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -406,20 +406,26 @@ 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; - // PB_TREE_FILENAME_NODE LesserChild; // we aren't worried about multi-level trees yet - PINDEX_ENTRY_ATTRIBUTE IndexEntry; // must be last member for FIELD_OFFSET + 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 +typedef struct _B_TREE_FILENAME_NODE { ULONG KeyCount; + BOOLEAN ExistsOnDisk; + BOOLEAN DiskNeedsUpdating; + ULONGLONG NodeNumber; PB_TREE_KEY FirstKey; } B_TREE_FILENAME_NODE, *PB_TREE_FILENAME_NODE; @@ -688,7 +694,9 @@ CompareTreeKeys(PB_TREE_KEY Key1, BOOLEAN CaseSensitive); NTSTATUS -CreateBTreeFromIndex(/*PDEVICE_EXTENSION Vcb,*/ +CreateBTreeFromIndex(PDEVICE_EXTENSION Vcb, + PFILE_RECORD_HEADER FileRecordWithIndex, + /*PCWSTR IndexName,*/ PNTFS_ATTR_CONTEXT IndexRootContext, PINDEX_ROOT_ATTRIBUTE IndexRoot, PB_TREE *NewTree); @@ -725,6 +733,19 @@ NtfsInsertKey(ULONGLONG FileReference, PB_TREE_FILENAME_NODE Node, BOOLEAN CaseSensitive); +NTSTATUS +UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt, + PB_TREE Tree, + ULONG IndexBufferSize, + PFILE_RECORD_HEADER FileRecord); + +NTSTATUS +UpdateIndexNode(PDEVICE_EXTENSION DeviceExt, + PB_TREE_FILENAME_NODE Node, + ULONG IndexBufferSize, + PNTFS_ATTR_CONTEXT IndexAllocationContext, + PNTFS_ATTR_CONTEXT BitmapContext); + /* close.c */ NTSTATUS From f2e47474f0cb3cd2d60ee8068a5040b998c82325 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Fri, 4 Aug 2017 17:58:06 +0000 Subject: [PATCH 60/71] [NTFS] - Fix index entries storing the wrong allocated file size when the file is resident. Fix a typo in a DPRINT. svn path=/branches/GSoC_2016/NTFS/; revision=75479 --- drivers/filesystems/ntfs/finfo.c | 4 ++-- drivers/filesystems/ntfs/mft.c | 4 ++-- drivers/filesystems/ntfs/rw.c | 5 ++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/filesystems/ntfs/finfo.c b/drivers/filesystems/ntfs/finfo.c index aee8aa51e2d..37debba2cad 100644 --- a/drivers/filesystems/ntfs/finfo.c +++ b/drivers/filesystems/ntfs/finfo.c @@ -597,7 +597,7 @@ NtfsSetEndOfFile(PNTFS_FCB Fcb, DPRINT("Found record for %wS\n", Fcb->ObjectName); - CurrentFileSize.QuadPart = NtfsGetFileSize(DeviceExt, FileRecord, L"", 0, (PULONGLONG)&CurrentFileSize); + CurrentFileSize.QuadPart = NtfsGetFileSize(DeviceExt, FileRecord, L"", 0, NULL); // Are we trying to decrease the file size? if (NewFileSize->QuadPart < CurrentFileSize.QuadPart) @@ -673,7 +673,7 @@ NtfsSetEndOfFile(PNTFS_FCB Fcb, FileName.Length = FileNameAttribute->NameLength * sizeof(WCHAR); FileName.MaximumLength = FileName.Length; - AllocationSize = ROUND_UP(NewFileSize->QuadPart, Fcb->Vcb->NtfsInfo.BytesPerCluster); + AllocationSize = AttributeAllocatedLength(&DataContext->Record); Status = UpdateFileNameRecord(Fcb->Vcb, ParentMFTId, diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 1df31aee0ce..83c966972ac 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -173,7 +173,7 @@ 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); } @@ -399,7 +399,7 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, { NTSTATUS Status = STATUS_SUCCESS; - DPRINT1("SetAttributeDataLenth(%p, %p, %p, %lu, %p, %I64u)\n", + DPRINT1("SetAttributeDataLength(%p, %p, %p, %lu, %p, %I64u)\n", FileObject, Fcb, AttrContext, diff --git a/drivers/filesystems/ntfs/rw.c b/drivers/filesystems/ntfs/rw.c index 7ef808f8c2b..cb4646495f8 100644 --- a/drivers/filesystems/ntfs/rw.c +++ b/drivers/filesystems/ntfs/rw.c @@ -432,11 +432,8 @@ NTSTATUS NtfsWriteFile(PDEVICE_EXTENSION DeviceExt, DataSize.QuadPart = WriteOffset + Length; - AllocationSize = ROUND_UP(DataSize.QuadPart, Fcb->Vcb->NtfsInfo.BytesPerCluster); - // set the attribute data length Status = SetAttributeDataLength(FileObject, Fcb, DataContext, AttributeOffset, FileRecord, &DataSize); - if (!NT_SUCCESS(Status)) { ReleaseAttributeContext(DataContext); @@ -445,6 +442,8 @@ NTSTATUS NtfsWriteFile(PDEVICE_EXTENSION DeviceExt, return Status; } + AllocationSize = AttributeAllocatedLength(&DataContext->Record); + // 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. From 4dfcd1d58295e72486625ab6063a3d5bf3e59a2c Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Sun, 6 Aug 2017 02:54:15 +0000 Subject: [PATCH 61/71] [NTFS] - Refactor to allow the copy of the attribute stored in NTFS_ATTR_CONTEXT to have a dynamic length; change Record member from an NTFS_ATTR_RECORD to a PNTFS_ATTR_RECORD. Rename it pRecord to reinforce the change. Fix some bugs related to the record size changing. -PrepareAttributeContext() - update to allocate memory for pRecord. Don't assume allocations are succeeding. -ReleaseAttributeContext() - update to free memory for pRecord. -InternalSetResidentAttributeLength() - Increase size of AttrContext->pRecord as needed. Update to return an NTSTATUS. -SetResidentAttributeDataLength() - Fix bug that could occur when migrating resident attributes to non-resident if AttrContext->pRecord is too small for the new attribute. -AddRun() - Fix a bug by reallocating AttrContext->pRecord if the record needs to be enlarged. svn path=/branches/GSoC_2016/NTFS/; revision=75493 --- drivers/filesystems/ntfs/attrib.c | 58 ++++--- drivers/filesystems/ntfs/btree.c | 4 +- drivers/filesystems/ntfs/dirctl.c | 4 +- drivers/filesystems/ntfs/fcb.c | 2 +- drivers/filesystems/ntfs/finfo.c | 4 +- drivers/filesystems/ntfs/fsctl.c | 8 +- drivers/filesystems/ntfs/mft.c | 240 +++++++++++++++++++---------- drivers/filesystems/ntfs/ntfs.h | 4 +- drivers/filesystems/ntfs/rw.c | 8 +- drivers/filesystems/ntfs/volinfo.c | 4 +- 10 files changed, 216 insertions(+), 120 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index a6bc9a8aca1..8618862344f 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -303,17 +303,17 @@ AddRun(PNTFS_VCB Vcb, NTSTATUS Status; int DataRunMaxLength; PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset); - ULONG NextAttributeOffset = AttrOffset + AttrContext->Record.Length; + ULONG NextAttributeOffset = AttrOffset + AttrContext->pRecord->Length; ULONGLONG NextVBN = 0; PUCHAR RunBuffer; ULONG RunBufferSize; - if (!AttrContext->Record.IsNonResident) + if (!AttrContext->pRecord->IsNonResident) return STATUS_INVALID_PARAMETER; - if (AttrContext->Record.NonResident.AllocatedSize != 0) - NextVBN = AttrContext->Record.NonResident.HighestVCN + 1; + if (AttrContext->pRecord->NonResident.AllocatedSize != 0) + NextVBN = AttrContext->pRecord->NonResident.HighestVCN + 1; // Add newly-assigned clusters to mcb _SEH2_TRY @@ -344,12 +344,14 @@ AddRun(PNTFS_VCB Vcb, 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->Record.Length - AttrContext->Record.NonResident.MappingPairsOffset; + 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; + DataRunMaxLength += Vcb->NtfsInfo.BytesPerFileRecord - NextAttributeOffset - (sizeof(ULONG) * 2); // Can we move the end of the attribute? @@ -363,12 +365,22 @@ AddRun(PNTFS_VCB Vcb, } // calculate position of end markers - NextAttributeOffset = AttrOffset + AttrContext->Record.NonResident.MappingPairsOffset + RunBufferSize; + NextAttributeOffset = AttrOffset + AttrContext->pRecord->NonResident.MappingPairsOffset + RunBufferSize; NextAttributeOffset = ALIGN_UP_BY(NextAttributeOffset, ATTR_RECORD_ALIGNMENT); - // Update the length + // Update the length of the destination attribute DestinationAttribute->Length = NextAttributeOffset - AttrOffset; - AttrContext->Record.Length = DestinationAttribute->Length; + + // Create a new copy of the attribute + 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, which won't be large enough + ExFreePoolWithTag(AttrContext->pRecord, TAG_NTFS); + + // Set the attribute context's record to the new copy + AttrContext->pRecord = NewRecord; // End the file record NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset); @@ -377,14 +389,19 @@ AddRun(PNTFS_VCB Vcb, // Update HighestVCN DestinationAttribute->NonResident.HighestVCN = - AttrContext->Record.NonResident.HighestVCN = max(NextVBN - 1 + RunLength, - AttrContext->Record.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 copy 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); @@ -723,7 +740,7 @@ FreeClusters(PNTFS_VCB Vcb, ULONG ClustersLeftToFree = ClustersToFree; PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset); - ULONG NextAttributeOffset = AttrOffset + AttrContext->Record.Length; + ULONG NextAttributeOffset = AttrOffset + AttrContext->pRecord->Length; PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset); PUCHAR RunBuffer; @@ -736,7 +753,7 @@ FreeClusters(PNTFS_VCB Vcb, RTL_BITMAP Bitmap; ULONG LengthWritten; - if (!AttrContext->Record.IsNonResident) + if (!AttrContext->pRecord->IsNonResident) { return STATUS_INVALID_PARAMETER; } @@ -767,7 +784,7 @@ FreeClusters(PNTFS_VCB Vcb, return 0; } - BitmapDataSize = AttributeDataLength(&DataContext->Record); + 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); @@ -802,10 +819,10 @@ FreeClusters(PNTFS_VCB Vcb, // deallocate this cluster RtlClearBits(&Bitmap, LargeLbn, 1); } - FsRtlTruncateLargeMcb(&AttrContext->DataRunsMCB, AttrContext->Record.NonResident.HighestVCN); + FsRtlTruncateLargeMcb(&AttrContext->DataRunsMCB, AttrContext->pRecord->NonResident.HighestVCN); // decrement HighestVCN, but don't let it go below 0 - AttrContext->Record.NonResident.HighestVCN = min(AttrContext->Record.NonResident.HighestVCN, AttrContext->Record.NonResident.HighestVCN - 1); + AttrContext->pRecord->NonResident.HighestVCN = min(AttrContext->pRecord->NonResident.HighestVCN, AttrContext->pRecord->NonResident.HighestVCN - 1); ClustersLeftToFree--; } @@ -837,7 +854,7 @@ FreeClusters(PNTFS_VCB Vcb, ConvertLargeMCBToDataRuns(&AttrContext->DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferSize); // Update HighestVCN - DestinationAttribute->NonResident.HighestVCN = AttrContext->Record.NonResident.HighestVCN; + DestinationAttribute->NonResident.HighestVCN = AttrContext->pRecord->NonResident.HighestVCN; // Write data runs to destination attribute RtlCopyMemory((PVOID)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), @@ -848,9 +865,12 @@ FreeClusters(PNTFS_VCB Vcb, if (NextAttribute->Type == AttributeEnd) { // update attribute length - AttrContext->Record.Length = ALIGN_UP_BY(AttrContext->Record.NonResident.MappingPairsOffset + RunBufferSize, + DestinationAttribute->Length = ALIGN_UP_BY(AttrContext->pRecord->NonResident.MappingPairsOffset + RunBufferSize, ATTR_RECORD_ALIGNMENT); - DestinationAttribute->Length = AttrContext->Record.Length; + + ASSERT(DestinationAttribute->Length <= AttrContext->pRecord->Length); + + AttrContext->pRecord->Length = DestinationAttribute->Length; // write end markers NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)DestinationAttribute + DestinationAttribute->Length); @@ -893,7 +913,7 @@ InternalReadNonResidentAttributes(PFIND_ATTR_CONTXT Context) } ListContext = PrepareAttributeContext(Attribute); - ListSize = AttributeDataLength(&ListContext->Record); + ListSize = AttributeDataLength(ListContext->pRecord); if (ListSize > 0xFFFFFFFF) { ReleaseAttributeContext(ListContext); diff --git a/drivers/filesystems/ntfs/btree.c b/drivers/filesystems/ntfs/btree.c index c1a907e8fea..dbbf3f9f870 100644 --- a/drivers/filesystems/ntfs/btree.c +++ b/drivers/filesystems/ntfs/btree.c @@ -41,7 +41,7 @@ PrintAllVCNs(PDEVICE_EXTENSION Vcb, { ULONGLONG CurrentOffset = 0; PINDEX_BUFFER CurrentNode, Buffer; - ULONGLONG BufferSize = AttributeDataLength(&IndexAllocationContext->Record); + ULONGLONG BufferSize = AttributeDataLength(IndexAllocationContext->pRecord); ULONG BytesRead; ULONGLONG i; int Count = 0; @@ -393,7 +393,7 @@ CreateBTreeFromIndex(PDEVICE_EXTENSION Vcb, 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->Record.Resident.ValueLength) + if (FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header) + IndexRoot->Header.TotalSizeOfEntries > IndexRootContext->pRecord->Resident.ValueLength) { DPRINT1("Filesystem corruption detected!\n"); DestroyBTree(Tree); diff --git a/drivers/filesystems/ntfs/dirctl.c b/drivers/filesystems/ntfs/dirctl.c index 98294b47913..61316cb77d9 100644 --- a/drivers/filesystems/ntfs/dirctl.c +++ b/drivers/filesystems/ntfs/dirctl.c @@ -49,8 +49,8 @@ NtfsGetFileSize(PDEVICE_EXTENSION DeviceExt, 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); } diff --git a/drivers/filesystems/ntfs/fcb.c b/drivers/filesystems/ntfs/fcb.c index fe79511bb32..f8057ee2ff0 100644 --- a/drivers/filesystems/ntfs/fcb.c +++ b/drivers/filesystems/ntfs/fcb.c @@ -756,7 +756,7 @@ NtfsReadFCBAttribute(PNTFS_VCB Vcb, 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 37debba2cad..fb7df5f4d4c 100644 --- a/drivers/filesystems/ntfs/finfo.c +++ b/drivers/filesystems/ntfs/finfo.c @@ -631,7 +631,7 @@ NtfsSetEndOfFile(PNTFS_FCB Fcb, } // Get the size of the data attribute - CurrentFileSize.QuadPart = AttributeDataLength(&DataContext->Record); + CurrentFileSize.QuadPart = AttributeDataLength(DataContext->pRecord); // Are we enlarging the attribute? if (NewFileSize->QuadPart > CurrentFileSize.QuadPart) @@ -673,7 +673,7 @@ NtfsSetEndOfFile(PNTFS_FCB Fcb, FileName.Length = FileNameAttribute->NameLength * sizeof(WCHAR); FileName.MaximumLength = FileName.Length; - AllocationSize = AttributeAllocatedLength(&DataContext->Record); + AllocationSize = AttributeAllocatedLength(DataContext->pRecord); Status = UpdateFileNameRecord(Fcb->Vcb, ParentMFTId, diff --git a/drivers/filesystems/ntfs/fsctl.c b/drivers/filesystems/ntfs/fsctl.c index 8acdd64e169..76337ec4482 100644 --- a/drivers/filesystems/ntfs/fsctl.c +++ b/drivers/filesystems/ntfs/fsctl.c @@ -341,9 +341,9 @@ NtfsGetVolumeData(PDEVICE_OBJECT DeviceObject, /* Get volume name */ 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); @@ -382,9 +382,9 @@ NtfsGetVolumeData(PDEVICE_OBJECT DeviceObject, /* Get volume information */ 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); diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 83c966972ac..23f802dfcae 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -42,15 +42,32 @@ 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)&Context->Record + Context->Record.NonResident.MappingPairsOffset; + PUCHAR DataRun = (PUCHAR)((ULONG_PTR)Context->pRecord + Context->pRecord->NonResident.MappingPairsOffset); Context->CacheRun = DataRun; Context->CacheRunOffset = 0; @@ -74,6 +91,7 @@ PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord) 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; } @@ -86,11 +104,14 @@ PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord) VOID ReleaseAttributeContext(PNTFS_ATTR_CONTEXT Context) { - if (Context->Record.IsNonResident) + if (Context->pRecord->IsNonResident) { FsRtlUninitializeLargeMcb(&Context->DataRunsMCB); } + if(Context->pRecord) + ExFreePoolWithTag(Context->pRecord, TAG_NTFS); + ExFreePoolWithTag(Context, TAG_NTFS); } @@ -245,10 +266,10 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait) } // Get size of Bitmap Attribute - BitmapSize.QuadPart = AttributeDataLength(&BitmapContext->Record); + BitmapSize.QuadPart = AttributeDataLength(BitmapContext->pRecord); // Calculate the new mft size - DataSize.QuadPart = AttributeDataLength(&(Vcb->MFTContext->Record)) + DataSizeDifference; + DataSize.QuadPart = AttributeDataLength(Vcb->MFTContext->pRecord) + DataSizeDifference; // Determine how many bytes will make up the bitmap BitmapBytes = DataSize.QuadPart / Vcb->NtfsInfo.BytesPerFileRecord / 8; @@ -301,7 +322,7 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait) { // Set the new bitmap size BitmapSize.QuadPart += BitmapSizeDifference; - if (BitmapContext->Record.IsNonResident) + if (BitmapContext->pRecord->IsNonResident) Status = SetNonResidentAttributeDataLength(Vcb, BitmapContext, BitmapOffset, Vcb->MasterFileTable, &BitmapSize); else Status = SetResidentAttributeDataLength(Vcb, BitmapContext, BitmapOffset, Vcb->MasterFileTable, &BitmapSize); @@ -348,7 +369,7 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait) return STATUS_SUCCESS; } -VOID +NTSTATUS InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext, PFILE_RECORD_HEADER FileRecord, ULONG AttrOffset, @@ -356,32 +377,45 @@ InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext, { PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset); ULONG NextAttributeOffset; + USHORT ValueOffset; DPRINT("InternalSetResidentAttributeLength( %p, %p, %lu, %lu )\n", AttrContext, FileRecord, AttrOffset, DataSize); - ASSERT(!AttrContext->Record.IsNonResident); + ASSERT(!AttrContext->pRecord->IsNonResident); - // update ValueLength Field - AttrContext->Record.Resident.ValueLength = + // Update ValueLength Field Destination->Resident.ValueLength = DataSize; - // calculate the record length and end marker offset - AttrContext->Record.Length = - Destination->Length = DataSize + AttrContext->Record.Resident.ValueOffset; - NextAttributeOffset = AttrOffset + AttrContext->Record.Length; + // 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 FileRecord has an up-to-date copy of the attribute + ValueOffset = AttrContext->pRecord->Resident.ValueOffset; + RtlCopyMemory((PCHAR)((ULONG_PTR)FileRecord + AttrOffset + ValueOffset), + (PCHAR)((ULONG_PTR)AttrContext->pRecord + ValueOffset), + min(DataSize, AttrContext->pRecord->Resident.ValueLength)); + + // 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 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; + } + RtlCopyMemory(AttrContext->pRecord, Destination, Destination->Length); // Ensure NextAttributeOffset is aligned to an 8-byte boundary - if (NextAttributeOffset % 8 != 0) - { - USHORT Padding = ATTR_RECORD_ALIGNMENT - (NextAttributeOffset % ATTR_RECORD_ALIGNMENT); - NextAttributeOffset += Padding; - AttrContext->Record.Length += Padding; - Destination->Length += Padding; - } + ASSERT(NextAttributeOffset % ATTR_RECORD_ALIGNMENT == 0); // advance Destination to the final "attribute" and set the file record end Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)Destination + Destination->Length); SetFileRecordEnd(FileRecord, Destination, FILE_RECORD_END); + + return STATUS_SUCCESS; } /** @@ -408,7 +442,7 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, DataSize->QuadPart); // are we truncating the file? - if (DataSize->QuadPart < AttributeDataLength(&AttrContext->Record)) + if (DataSize->QuadPart < AttributeDataLength(AttrContext->pRecord)) { if (!MmCanFileBeTruncated(FileObject->SectionObjectPointer, DataSize)) { @@ -417,7 +451,7 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, } } - if (AttrContext->Record.IsNonResident) + if (AttrContext->pRecord->IsNonResident) { Status = SetNonResidentAttributeDataLength(Fcb->Vcb, AttrContext, @@ -448,8 +482,8 @@ SetAttributeDataLength(PFILE_OBJECT FileObject, if (NT_SUCCESS(Status)) { - if (AttrContext->Record.IsNonResident) - Fcb->RFCB.AllocationSize.QuadPart = AttrContext->Record.NonResident.AllocatedSize; + if (AttrContext->pRecord->IsNonResident) + Fcb->RFCB.AllocationSize.QuadPart = AttrContext->pRecord->NonResident.AllocatedSize; else Fcb->RFCB.AllocationSize = *DataSize; Fcb->RFCB.FileSize = *DataSize; @@ -538,12 +572,12 @@ SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, 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->Record.NonResident.AllocatedSize / BytesPerCluster; + ULONG ExistingClusters = AttrContext->pRecord->NonResident.AllocatedSize / BytesPerCluster; - ASSERT(AttrContext->Record.IsNonResident); + ASSERT(AttrContext->pRecord->IsNonResident); // do we need to increase the allocation size? - if (AttrContext->Record.NonResident.AllocatedSize < AllocationSize) + if (AttrContext->pRecord->NonResident.AllocatedSize < AllocationSize) { ULONG ClustersNeeded = (AllocationSize / BytesPerCluster) - ExistingClusters; LARGE_INTEGER LastClusterInDataRun; @@ -557,7 +591,7 @@ SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, else { if (!FsRtlLookupLargeMcbEntry(&AttrContext->DataRunsMCB, - (LONGLONG)AttrContext->Record.NonResident.HighestVCN, + (LONGLONG)AttrContext->pRecord->NonResident.HighestVCN, (PLONGLONG)&LastClusterInDataRun.QuadPart, NULL, NULL, @@ -567,13 +601,13 @@ SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, 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->Record.NonResident.HighestVCN); + 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->Record.NonResident.HighestVCN); + DPRINT("Highest VCN of record: %I64u\n", AttrContext->pRecord->NonResident.HighestVCN); while (ClustersNeeded > 0) { @@ -601,7 +635,7 @@ SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, LastClusterInDataRun.LowPart = NextAssignedCluster + AssignedClusters - 1; } } - else if (AttrContext->Record.NonResident.AllocatedSize > AllocationSize) + else if (AttrContext->pRecord->NonResident.AllocatedSize > AllocationSize) { // shrink allocation size ULONG ClustersToFree = ExistingClusters - (AllocationSize / BytesPerCluster); @@ -610,9 +644,9 @@ SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, // TODO: is the file compressed, encrypted, or sparse? - AttrContext->Record.NonResident.AllocatedSize = AllocationSize; - AttrContext->Record.NonResident.DataSize = DataSize->QuadPart; - AttrContext->Record.NonResident.InitializedSize = DataSize->QuadPart; + 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; @@ -667,18 +701,18 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, NTSTATUS Status; // find the next attribute - ULONG NextAttributeOffset = AttrOffset + AttrContext->Record.Length; + ULONG NextAttributeOffset = AttrOffset + AttrContext->pRecord->Length; PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((PCHAR)FileRecord + NextAttributeOffset); - ASSERT(!AttrContext->Record.IsNonResident); + ASSERT(!AttrContext->pRecord->IsNonResident); //NtfsDumpFileAttributes(Vcb, FileRecord); // Do we need to increase the data length? - if (DataSize->QuadPart > AttrContext->Record.Resident.ValueLength) + 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->Record.Length - AttrContext->Record.Resident.ValueOffset; + 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 @@ -688,14 +722,16 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, { // 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->Record.Resident.ValueLength; + AttribDataSize.QuadPart = AttrContext->pRecord->Resident.ValueLength; // Is there existing data we need to back-up? if (AttribDataSize.QuadPart > 0) @@ -719,23 +755,43 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, // Start by turning this attribute into a 0-length, non-resident attribute, then enlarge it. - // Zero out the NonResident structure - RtlZeroMemory(&AttrContext->Record.NonResident.LowestVCN, - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize) - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.LowestVCN)); - RtlZeroMemory(&Destination->NonResident.LowestVCN, - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize) - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.LowestVCN)); + // 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); - // update the mapping pairs offset, which will be 0x40 + length in bytes of the name - AttrContext->Record.NonResident.MappingPairsOffset = Destination->NonResident.MappingPairsOffset = 0x40 + (Destination->NameLength * 2); + // 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 + AttrContext->Record.NonResident.MappingPairsOffset + 1; + EndAttributeOffset = AttrOffset + NewRecord->NonResident.MappingPairsOffset + 1; EndAttributeOffset = ALIGN_UP_BY(EndAttributeOffset, ATTR_RECORD_ALIGNMENT); // Update the length - Destination->Length = EndAttributeOffset - AttrOffset; - AttrContext->Record.Length = Destination->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, @@ -756,7 +812,7 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, } _SEH2_END; // Mark the attribute as non-resident (we wait until after we know the LargeMcb was initialized) - AttrContext->Record.IsNonResident = Destination->IsNonResident = 1; + NewRecord->IsNonResident = Destination->IsNonResident = 1; // Update file record on disk Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord); @@ -768,6 +824,10 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, 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)) @@ -795,7 +855,7 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, } } } - else if (DataSize->LowPart < AttrContext->Record.Resident.ValueLength) + else if (DataSize->LowPart < AttrContext->pRecord->Resident.ValueLength) { // we need to decrease the length if (NextAttribute->Type != AttributeEnd) @@ -806,8 +866,8 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, } // set the new length of the resident attribute (if we didn't migrate it) - if (!AttrContext->Record.IsNonResident) - InternalSetResidentAttributeLength(AttrContext, FileRecord, AttrOffset, DataSize->LowPart); + if (!AttrContext->pRecord->IsNonResident) + return InternalSetResidentAttributeLength(AttrContext, FileRecord, AttrOffset, DataSize->LowPart); return STATUS_SUCCESS; } @@ -832,13 +892,19 @@ ReadAttribute(PDEVICE_EXTENSION Vcb, //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; } @@ -996,7 +1062,7 @@ ReadAttribute(PDEVICE_EXTENSION Vcb, } /* if Disk */ // TEMPTEMP - if (Context->Record.IsNonResident) + if (Context->pRecord->IsNonResident) ExFreePoolWithTag(TempBuffer, TAG_NTFS); Context->CacheRun = DataRun; @@ -1073,13 +1139,13 @@ WriteAttribute(PDEVICE_EXTENSION Vcb, *RealLengthWritten = 0; // is this a resident attribute? - if (!Context->Record.IsNonResident) + if (!Context->pRecord->IsNonResident) { ULONG AttributeOffset; PNTFS_ATTR_CONTEXT FoundContext; PFILE_RECORD_HEADER FileRecord; - if (Offset + Length > Context->Record.Resident.ValueLength) + if (Offset + Length > Context->pRecord->Resident.ValueLength) { DPRINT1("DRIVER ERROR: Attribute is too small!\n"); return STATUS_INVALID_PARAMETER; @@ -1098,9 +1164,9 @@ WriteAttribute(PDEVICE_EXTENSION Vcb, // 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, + Context->pRecord->Type, + (PCWSTR)((ULONG_PTR)Context->pRecord + Context->pRecord->NameOffset), + Context->pRecord->NameLength, &FoundContext, &AttributeOffset); @@ -1111,8 +1177,8 @@ WriteAttribute(PDEVICE_EXTENSION Vcb, return Status; } - DPRINT("Offset: %I64u, AttributeOffset: %u, ValueOffset: %u\n", Offset, AttributeOffset, Context->Record.Resident.ValueLength); - Offset += AttributeOffset + Context->Record.Resident.ValueOffset; + DPRINT("Offset: %I64u, AttributeOffset: %u, ValueOffset: %u\n", Offset, AttributeOffset, Context->pRecord->Resident.ValueLength); + Offset += AttributeOffset + Context->pRecord->Resident.ValueOffset; if (Offset + Length > Vcb->NtfsInfo.BytesPerFileRecord) { @@ -1320,7 +1386,7 @@ WriteAttribute(PDEVICE_EXTENSION Vcb, } // end while (Length > 0) [more data to write] // TEMPTEMP - if (Context->Record.IsNonResident) + if (Context->pRecord->IsNonResident) ExFreePoolWithTag(TempBuffer, TAG_NTFS); Context->CacheRun = DataRun; @@ -1419,7 +1485,7 @@ UpdateFileNameRecord(PDEVICE_EXTENSION Vcb, return STATUS_INSUFFICIENT_RESOURCES; } - Status = ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, AttributeDataLength(&IndexRootCtx->Record)); + Status = ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, AttributeDataLength(IndexRootCtx->pRecord)); if (!NT_SUCCESS(Status)) { DPRINT1("ERROR: Failed to read Index Root!\n"); @@ -1453,7 +1519,7 @@ UpdateFileNameRecord(PDEVICE_EXTENSION Vcb, { // we need to write the index root attribute back to disk ULONG LengthWritten; - Status = WriteAttribute(Vcb, IndexRootCtx, 0, (PUCHAR)IndexRecord, AttributeDataLength(&IndexRootCtx->Record), &LengthWritten); + Status = WriteAttribute(Vcb, IndexRootCtx, 0, (PUCHAR)IndexRecord, AttributeDataLength(IndexRootCtx->pRecord), &LengthWritten); if (!NT_SUCCESS(Status)) { DPRINT1("ERROR: Couldn't update Index Root!\n"); @@ -1551,7 +1617,7 @@ UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb, return Status; } - IndexAllocationSize = AttributeDataLength(&IndexAllocationCtx->Record); + IndexAllocationSize = AttributeDataLength(IndexAllocationCtx->pRecord); Status = STATUS_OBJECT_PATH_NOT_FOUND; for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize) { @@ -1747,7 +1813,7 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, } // allocate a buffer for the $Bitmap attribute - BitmapDataSize = AttributeDataLength(&BitmapContext->Record); + BitmapDataSize = AttributeDataLength(BitmapContext->pRecord); BitmapData = ExAllocatePoolWithTag(NonPagedPool, BitmapDataSize, TAG_NTFS); if (!BitmapData) { @@ -1772,7 +1838,7 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, BitmapData[2] = 0xff; // Calculate bit count - BitmapBits.QuadPart = AttributeDataLength(&(DeviceExt->MFTContext->Record)) / + BitmapBits.QuadPart = AttributeDataLength(DeviceExt->MFTContext->pRecord) / DeviceExt->NtfsInfo.BytesPerFileRecord; if (BitmapBits.HighPart != 0) { @@ -1942,12 +2008,12 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, // Find the maximum index size given what the file record can hold MaxIndexSize = DeviceExt->NtfsInfo.BytesPerFileRecord - IndexRootOffset - - IndexRootContext->Record.Resident.ValueOffset + - IndexRootContext->pRecord->Resident.ValueOffset - FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header) - (sizeof(ULONG) * 2); // Allocate memory for the index root data - I30IndexRootLength = AttributeDataLength(&IndexRootContext->Record); + I30IndexRootLength = AttributeDataLength(IndexRootContext->pRecord); I30IndexRoot = ExAllocatePoolWithTag(NonPagedPool, I30IndexRootLength, TAG_NTFS); if (!I30IndexRoot) { @@ -2035,7 +2101,7 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, // $ATTRIBUTE_LIST's. AttributeLength = NewIndexRoot->Header.AllocatedSize + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header); - if (AttributeLength != IndexRootContext->Record.Resident.ValueLength) + if (AttributeLength != IndexRootContext->pRecord->Resident.ValueLength) { DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset); @@ -2052,10 +2118,20 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, } // Update the length of the attribute in the file record of the parent directory - InternalSetResidentAttributeLength(IndexRootContext, - ParentFileRecord, - IndexRootOffset, - AttributeLength); + Status = InternalSetResidentAttributeLength(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); @@ -2295,7 +2371,7 @@ BrowseIndexEntries(PDEVICE_EXTENSION Vcb, return Status; } - IndexAllocationSize = AttributeDataLength(&IndexAllocationCtx->Record); + IndexAllocationSize = AttributeDataLength(IndexAllocationCtx->pRecord); Status = STATUS_OBJECT_PATH_NOT_FOUND; for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize) { diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index 42168be1abe..668815ddd58 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -480,7 +480,7 @@ typedef struct _NTFS_ATTR_CONTEXT ULONGLONG CacheRunCurrentOffset; LARGE_MCB DataRunsMCB; ULONGLONG FileMFTIndex; - NTFS_ATTR_RECORD Record; + PNTFS_ATTR_RECORD pRecord; } NTFS_ATTR_CONTEXT, *PNTFS_ATTR_CONTEXT; #define FCB_CACHE_INITIALIZED 0x0001 @@ -959,7 +959,7 @@ WriteAttribute(PDEVICE_EXTENSION Vcb, ULONGLONG AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord); -VOID +NTSTATUS InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext, PFILE_RECORD_HEADER FileRecord, ULONG AttrOffset, diff --git a/drivers/filesystems/ntfs/rw.c b/drivers/filesystems/ntfs/rw.c index cb4646495f8..2c21f3a449e 100644 --- a/drivers/filesystems/ntfs/rw.c +++ b/drivers/filesystems/ntfs/rw.c @@ -126,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"); @@ -149,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; } @@ -413,7 +413,7 @@ NTSTATUS NtfsWriteFile(PDEVICE_EXTENSION DeviceExt, } // Get the size of the stream on disk - StreamSize = AttributeDataLength(&DataContext->Record); + StreamSize = AttributeDataLength(DataContext->pRecord); DPRINT("WriteOffset: %lu\tStreamSize: %I64u\n", WriteOffset, StreamSize); @@ -442,7 +442,7 @@ NTSTATUS NtfsWriteFile(PDEVICE_EXTENSION DeviceExt, return Status; } - AllocationSize = AttributeAllocatedLength(&DataContext->Record); + 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 diff --git a/drivers/filesystems/ntfs/volinfo.c b/drivers/filesystems/ntfs/volinfo.c index 1a577d45e23..d1efa4c2695 100644 --- a/drivers/filesystems/ntfs/volinfo.c +++ b/drivers/filesystems/ntfs/volinfo.c @@ -69,7 +69,7 @@ NtfsGetFreeClusters(PDEVICE_EXTENSION DeviceExt) 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) @@ -144,7 +144,7 @@ NtfsAllocateClusters(PDEVICE_EXTENSION DeviceExt, return Status; } - BitmapDataSize = AttributeDataLength(&DataContext->Record); + 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); From d484d91eba5724f885cf6662e9e8bd87c99d561b Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Tue, 15 Aug 2017 19:32:20 +0000 Subject: [PATCH 62/71] [NTFS] - Allow for resizing an attribute in the middle of a file record. Add a helper function and minor improvements: AddRun() - Allow for resizing the size of the data runs when the attribute isn't the last in the file record. Fix some comments. CreateIndexBufferFromBTreeNode(), CreateIndexRootFromBTree - Fix math of IndexSize when checking if the index buffer is too large. InternalSetResidentAttributeLength() - Allow changing the length of an attribute in the middle of a file record. Adjust the position of every attribute after the one being resized. +MoveAttributes() - Moves a block of attributes to a new location in the file Record. PrintAllVCNs() - Add consideration for an index allocation with a size of 0. WriteAttribute() - Add optional parameter for a pointer to the file record being written to. If passed a file record, WriteAttribute() will skip reading the file record from disk, and will update the file record in memory before returning. This helps callers that use the file record after writing an attribute to stay in-sync with what's on disk. svn path=/branches/GSoC_2016/NTFS/; revision=75554 --- drivers/filesystems/ntfs/attrib.c | 47 ++++-- drivers/filesystems/ntfs/btree.c | 26 ++-- drivers/filesystems/ntfs/mft.c | 240 +++++++++++++++++++---------- drivers/filesystems/ntfs/ntfs.h | 12 +- drivers/filesystems/ntfs/rw.c | 2 +- drivers/filesystems/ntfs/volinfo.c | 2 +- 6 files changed, 222 insertions(+), 107 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index 8618862344f..e6d3483553b 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -352,18 +352,35 @@ AddRun(PNTFS_VCB Vcb, PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset); PNTFS_ATTR_RECORD NewRecord; - DataRunMaxLength += Vcb->NtfsInfo.BytesPerFileRecord - NextAttributeOffset - (sizeof(ULONG) * 2); + // Add free space at the end of the file record to DataRunMaxLength + DataRunMaxLength += Vcb->NtfsInfo.BytesPerFileRecord - FileRecord->BytesInUse; - // Can we move the end of the attribute? - if (NextAttribute->Type != AttributeEnd || DataRunMaxLength < RunBufferSize - 1) + // Can we resize the attribute? + if (DataRunMaxLength < RunBufferSize) { - 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); + 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); @@ -371,20 +388,24 @@ AddRun(PNTFS_VCB Vcb, // Update the length of the destination attribute DestinationAttribute->Length = NextAttributeOffset - AttrOffset; - // Create a new copy of the attribute + // 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, which won't be large enough + // 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; - // End the file record - NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset); - SetFileRecordEnd(FileRecord, NextAttribute, FILE_RECORD_END); + // 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 @@ -397,7 +418,7 @@ AddRun(PNTFS_VCB Vcb, RunBuffer, RunBufferSize); - // Update the attribute copy in the attribute context + // Update the attribute record in the attribute context RtlCopyMemory((PVOID)((ULONG_PTR)AttrContext->pRecord + AttrContext->pRecord->NonResident.MappingPairsOffset), RunBuffer, RunBufferSize); @@ -827,7 +848,7 @@ FreeClusters(PNTFS_VCB Vcb, } // update $BITMAP file on disk - Status = WriteAttribute(Vcb, DataContext, 0, BitmapData, (ULONG)BitmapDataSize, &LengthWritten); + Status = WriteAttribute(Vcb, DataContext, 0, BitmapData, (ULONG)BitmapDataSize, &LengthWritten, FileRecord); if (!NT_SUCCESS(Status)) { ReleaseAttributeContext(DataContext); diff --git a/drivers/filesystems/ntfs/btree.c b/drivers/filesystems/ntfs/btree.c index dbbf3f9f870..648111d9c05 100644 --- a/drivers/filesystems/ntfs/btree.c +++ b/drivers/filesystems/ntfs/btree.c @@ -46,6 +46,12 @@ PrintAllVCNs(PDEVICE_EXTENSION Vcb, 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); @@ -281,10 +287,10 @@ CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb, { DPRINT1("TODO: Only a node with a single-level is supported right now!\n"); // Needs debugging: - /*CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb, + CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb, IndexRoot, IndexAllocationAttributeCtx, - CurrentNodeEntry);*/ + CurrentKey->IndexEntry); } CurrentKey = NextKey; @@ -300,10 +306,10 @@ CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb, { DPRINT1("TODO: Only a node with a single-level is supported right now!\n"); // Needs debugging: - /*CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb, - IndexRoot, - IndexAllocationAttributeCtx, - CurrentNodeEntry);*/ + CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb, + IndexRoot, + IndexAllocationAttributeCtx, + CurrentKey->IndexEntry); } break; @@ -575,7 +581,6 @@ CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt, { // Would adding the current entry to the index increase the index size beyond the limit we've set? ULONG IndexSize = FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header) - + NewIndexRoot->Header.FirstEntryOffset + NewIndexRoot->Header.TotalSizeOfEntries + CurrentNodeEntry->Length; if (IndexSize > MaxIndexSize) @@ -649,7 +654,6 @@ CreateIndexBufferFromBTreeNode(PDEVICE_EXTENSION DeviceExt, { // 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.FirstEntryOffset + IndexBuffer->Header.TotalSizeOfEntries + CurrentNodeEntry->Length; if (IndexSize > BufferSize) @@ -776,7 +780,7 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt, NodeOffset = GetAllocationOffsetFromVCN(DeviceExt, IndexBufferSize, Node->NodeNumber); // Write the buffer to the index allocation - Status = WriteAttribute(DeviceExt, IndexAllocationContext, NodeOffset, (const PUCHAR)IndexBuffer, IndexBufferSize, &LengthWritten); + Status = WriteAttribute(DeviceExt, IndexAllocationContext, NodeOffset, (const PUCHAR)IndexBuffer, IndexBufferSize, &LengthWritten, NULL); if (!NT_SUCCESS(Status) || LengthWritten != IndexBufferSize) { DPRINT1("ERROR: Failed to update index allocation!\n"); @@ -945,10 +949,10 @@ DumpBTreeNode(PB_TREE_FILENAME_NODE Node, ULONG Number, ULONG Depth) ULONG i; for (i = 0; i < Depth; i++) DbgPrint(" "); - DbgPrint("Node #%d, Depth %d, has %d keys\n", Number, Depth, Node->KeyCount); + DbgPrint("Node #%d, Depth %d, has %d key%s\n", Number, Depth, Node->KeyCount, Node->KeyCount == 1 ? "" : "s"); CurrentKey = Node->FirstKey; - for (i = 0; i < Node->KeyCount; i++) + for (i = 1; i <= Node->KeyCount; i++) { DumpBTreeKey(CurrentKey, i, Depth); CurrentKey = CurrentKey->NextKey; diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 23f802dfcae..aeee447e33b 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -351,7 +351,7 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait) } // Write out the new bitmap - Status = WriteAttribute(Vcb, BitmapContext, BitmapOffset, BitmapBuffer, BitmapSize.LowPart, &LengthWritten); + Status = WriteAttribute(Vcb, BitmapContext, BitmapOffset, BitmapBuffer, BitmapSize.LowPart, &LengthWritten, Vcb->MasterFileTable); if (!NT_SUCCESS(Status)) { ExReleaseResourceLite(&(Vcb->DirResource)); @@ -369,17 +369,72 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait) return STATUS_SUCCESS; } +/** +* @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(PNTFS_ATTR_CONTEXT AttrContext, +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; - USHORT ValueOffset; - DPRINT("InternalSetResidentAttributeLength( %p, %p, %lu, %lu )\n", AttrContext, FileRecord, AttrOffset, DataSize); + DPRINT1("InternalSetResidentAttributeLength( %p, %p, %p, %lu, %lu )\n", DeviceExt, AttrContext, FileRecord, AttrOffset, DataSize); ASSERT(!AttrContext->pRecord->IsNonResident); @@ -390,30 +445,46 @@ InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext, Destination->Length = ALIGN_UP_BY(DataSize + AttrContext->pRecord->Resident.ValueOffset, ATTR_RECORD_ALIGNMENT); NextAttributeOffset = AttrOffset + Destination->Length; - // Ensure FileRecord has an up-to-date copy of the attribute - ValueOffset = AttrContext->pRecord->Resident.ValueOffset; - RtlCopyMemory((PCHAR)((ULONG_PTR)FileRecord + AttrOffset + ValueOffset), - (PCHAR)((ULONG_PTR)AttrContext->pRecord + ValueOffset), - min(DataSize, AttrContext->pRecord->Resident.ValueLength)); - - // 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 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; - } - RtlCopyMemory(AttrContext->pRecord, Destination, Destination->Length); - // Ensure NextAttributeOffset is aligned to an 8-byte boundary ASSERT(NextAttributeOffset % ATTR_RECORD_ALIGNMENT == 0); - - // advance Destination to the final "attribute" and set the file record end - Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)Destination + Destination->Length); - SetFileRecordEnd(FileRecord, Destination, FILE_RECORD_END); + + // 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; } @@ -519,6 +590,9 @@ 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; @@ -841,7 +915,7 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, // restore the back-up attribute, if we made one if (AttribDataSize.QuadPart > 0) { - Status = WriteAttribute(Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart, &LengthWritten); + 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"); @@ -855,19 +929,10 @@ SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, } } } - else if (DataSize->LowPart < AttrContext->pRecord->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; - } - } // set the new length of the resident attribute (if we didn't migrate it) if (!AttrContext->pRecord->IsNonResident) - return InternalSetResidentAttributeLength(AttrContext, FileRecord, AttrOffset, DataSize->LowPart); + return InternalSetResidentAttributeLength(Vcb, AttrContext, FileRecord, AttrOffset, DataSize->LowPart); return STATUS_SUCCESS; } @@ -1102,6 +1167,12 @@ ReadAttribute(PDEVICE_EXTENSION Vcb, * @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. @@ -1117,7 +1188,8 @@ WriteAttribute(PDEVICE_EXTENSION Vcb, ULONGLONG Offset, const PUCHAR Buffer, ULONG Length, - PULONG RealLengthWritten) + PULONG RealLengthWritten, + PFILE_RECORD_HEADER FileRecord) { ULONGLONG LastLCN; PUCHAR DataRun; @@ -1129,12 +1201,13 @@ WriteAttribute(PDEVICE_EXTENSION Vcb, NTSTATUS Status; PUCHAR SourceBuffer = Buffer; LONGLONG StartingOffset; + BOOLEAN FileRecordAllocated = FALSE; //TEMPTEMP PUCHAR TempBuffer; - DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p)\n", Vcb, Context, Offset, Buffer, Length, RealLengthWritten); + DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p, %p)\n", Vcb, Context, Offset, Buffer, Length, RealLengthWritten, FileRecord); *RealLengthWritten = 0; @@ -1143,7 +1216,10 @@ WriteAttribute(PDEVICE_EXTENSION Vcb, { ULONG AttributeOffset; PNTFS_ATTR_CONTEXT FoundContext; - PFILE_RECORD_HEADER FileRecord; + 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) { @@ -1151,16 +1227,21 @@ WriteAttribute(PDEVICE_EXTENSION Vcb, return STATUS_INVALID_PARAMETER; } - FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS); - - if (!FileRecord) + // Do we need to read the file record? + if (FileRecord == NULL) { - DPRINT1("Error: Couldn't allocate file record!\n"); - return STATUS_NO_MEMORY; - } + 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); + FileRecordAllocated = TRUE; + + // read the file record + ReadFileRecord(Vcb, Context->FileMFTIndex, FileRecord); + } // find where to write the attribute data to Status = FindAttribute(Vcb, FileRecord, @@ -1173,28 +1254,37 @@ WriteAttribute(PDEVICE_EXTENSION Vcb, if (!NT_SUCCESS(Status)) { DPRINT1("ERROR: Couldn't find matching attribute!\n"); - ExFreePoolWithTag(FileRecord, TAG_NTFS); + 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); - Offset += AttributeOffset + Context->pRecord->Resident.ValueOffset; - - if (Offset + Length > Vcb->NtfsInfo.BytesPerFileRecord) + + // 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); - ExFreePoolWithTag(FileRecord, TAG_NTFS); + if (FileRecordAllocated) + ExFreePoolWithTag(FileRecord, TAG_NTFS); return STATUS_INVALID_PARAMETER; } - // copy the data being written into the file record - RtlCopyMemory((PCHAR)FileRecord + Offset, Buffer, Length); + // 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); - ExFreePoolWithTag(FileRecord, TAG_NTFS); + if (FileRecordAllocated) + ExFreePoolWithTag(FileRecord, TAG_NTFS); if (NT_SUCCESS(Status)) *RealLengthWritten = Length; @@ -1519,7 +1609,7 @@ UpdateFileNameRecord(PDEVICE_EXTENSION Vcb, { // we need to write the index root attribute back to disk ULONG LengthWritten; - Status = WriteAttribute(Vcb, IndexRootCtx, 0, (PUCHAR)IndexRecord, AttributeDataLength(IndexRootCtx->pRecord), &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"); @@ -1661,7 +1751,7 @@ UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb, break; } - Status = WriteAttribute(Vcb, IndexAllocationCtx, RecordOffset, (const PUCHAR)IndexRecord, IndexBlockSize, &Written); + Status = WriteAttribute(Vcb, IndexAllocationCtx, RecordOffset, (const PUCHAR)IndexRecord, IndexBlockSize, &Written, MftRecord); if (!NT_SUCCESS(Status)) { DPRINT1("ERROR Performing write!\n"); @@ -1714,7 +1804,13 @@ UpdateFileRecord(PDEVICE_EXTENSION Vcb, 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); + Status = WriteAttribute(Vcb, + Vcb->MFTContext, + MftIndex * Vcb->NtfsInfo.BytesPerFileRecord, + (const PUCHAR)FileRecord, + Vcb->NtfsInfo.BytesPerFileRecord, + &BytesWritten, + FileRecord); if (!NT_SUCCESS(Status)) { @@ -1882,7 +1978,7 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, BitmapData[2] = SystemReservedBits; // write the bitmap back to the MFT's $Bitmap attribute - Status = WriteAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize, &LengthWritten); + Status = WriteAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize, &LengthWritten, FileRecord); if (!NT_SUCCESS(Status)) { DPRINT1("ERROR encountered when writing $Bitmap attribute!\n"); @@ -1958,10 +2054,8 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, ULONG IndexRootOffset; ULONGLONG I30IndexRootLength; ULONG LengthWritten; - PNTFS_ATTR_RECORD DestinationAttribute; PINDEX_ROOT_ATTRIBUTE NewIndexRoot; ULONG AttributeLength; - PNTFS_ATTR_RECORD NextAttribute; PB_TREE NewTree; ULONG BtreeIndexLength; ULONG MaxIndexSize; @@ -2065,7 +2159,7 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, DumpBTree(NewTree); - // Convert B*Tree back to Index + // Convert B*Tree back to Index, starting with the index allocation Status = UpdateIndexAllocation(DeviceExt, NewTree, I30IndexRoot->SizeOfEntry, ParentFileRecord); if (!NT_SUCCESS(Status)) { @@ -2103,22 +2197,9 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, if (AttributeLength != IndexRootContext->pRecord->Resident.ValueLength) { - DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset); - - // Find the attribute (or attribute-end marker) after the index root - NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)DestinationAttribute + DestinationAttribute->Length); - if (NextAttribute->Type != AttributeEnd) - { - DPRINT1("FIXME: For now, only resizing index root at the end of a file record is supported!\n"); - ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); - ReleaseAttributeContext(IndexRootContext); - ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); - ExFreePoolWithTag(ParentFileRecord, TAG_NTFS); - return STATUS_NOT_IMPLEMENTED; - } - // Update the length of the attribute in the file record of the parent directory - Status = InternalSetResidentAttributeLength(IndexRootContext, + Status = InternalSetResidentAttributeLength(DeviceExt, + IndexRootContext, ParentFileRecord, IndexRootOffset, AttributeLength); @@ -2153,7 +2234,8 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, 0, (PUCHAR)NewIndexRoot, AttributeLength, - &LengthWritten); + &LengthWritten, + ParentFileRecord); if (!NT_SUCCESS(Status)) { DPRINT1("ERROR: Unable to write new index root attribute to parent directory!\n"); diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index 668815ddd58..4d436a699e1 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -954,17 +954,25 @@ WriteAttribute(PDEVICE_EXTENSION Vcb, ULONGLONG Offset, const PUCHAR Buffer, ULONG Length, - PULONG LengthWritten); + PULONG LengthWritten, + PFILE_RECORD_HEADER FileRecord); ULONGLONG AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord); NTSTATUS -InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext, +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, diff --git a/drivers/filesystems/ntfs/rw.c b/drivers/filesystems/ntfs/rw.c index 2c21f3a449e..94d05de9ae1 100644 --- a/drivers/filesystems/ntfs/rw.c +++ b/drivers/filesystems/ntfs/rw.c @@ -478,7 +478,7 @@ NTSTATUS NtfsWriteFile(PDEVICE_EXTENSION DeviceExt, 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); + Status = WriteAttribute(DeviceExt, DataContext, WriteOffset, Buffer, Length, LengthWritten, FileRecord); // Did the write fail? if (!NT_SUCCESS(Status)) diff --git a/drivers/filesystems/ntfs/volinfo.c b/drivers/filesystems/ntfs/volinfo.c index d1efa4c2695..6b7e598819a 100644 --- a/drivers/filesystems/ntfs/volinfo.c +++ b/drivers/filesystems/ntfs/volinfo.c @@ -196,7 +196,7 @@ NtfsAllocateClusters(PDEVICE_EXTENSION DeviceExt, } - Status = WriteAttribute(DeviceExt, DataContext, 0, BitmapData, (ULONG)BitmapDataSize, &LengthWritten); + Status = WriteAttribute(DeviceExt, DataContext, 0, BitmapData, (ULONG)BitmapDataSize, &LengthWritten, BitmapRecord); ReleaseAttributeContext(DataContext); From 88a7c3d14b5f74300baacff3486b377901c2467f Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Tue, 15 Aug 2017 19:57:55 +0000 Subject: [PATCH 63/71] [NTFS] - Allow for creating a file when the index root gets too large and needs to be moved into an index record. Add some helper functions. +AllocateIndexNode() - Allocates a new index record in an index allocation. +CreateDummyKey() - Creates the final B_TREE_KEY for a B_TREE_FILENAME_NODE. Also creates the associated index entry. GetSizeOfIndexEntries() - Sums the size of each index entry in every key in a B-Tree node. +SetIndexEntryVCN() - Sets the VCN of a given IndexEntry. NtfsInsertKey() - Handle instance when the index root grows too large. If it does, add its contents to a new sub-node, and replace contents with a dummy-key whose child is the new node. UpdateIndexAllocation() - Update index entry if a key has just been assigned a child allocation. UpdateIndexNode() - Make sure the node exists on disk, and allocate an index record for it if it doesn't. svn path=/branches/GSoC_2016/NTFS/; revision=75557 --- drivers/filesystems/ntfs/btree.c | 526 ++++++++++++++++++++++++++++--- drivers/filesystems/ntfs/mft.c | 35 +- drivers/filesystems/ntfs/ntfs.h | 8 +- 3 files changed, 522 insertions(+), 47 deletions(-) diff --git a/drivers/filesystems/ntfs/btree.c b/drivers/filesystems/ntfs/btree.c index 648111d9c05..727c147a2a4 100644 --- a/drivers/filesystems/ntfs/btree.c +++ b/drivers/filesystems/ntfs/btree.c @@ -80,6 +80,237 @@ PrintAllVCNs(PDEVICE_EXTENSION Vcb, 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. +*/ +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; + if (NextNodeNumber % 8 != 0) + BytesNeeded++; + + // Windows seems to allocate the bitmap in 8-byte chunks to keep any bytes from being wasted on padding + ALIGN_UP(BytesNeeded, ATTR_RECORD_ALIGNMENT); + + // 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; + 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; + } + + // Allocate memory for the bitmap. RtlInitializeBitmap() wants a pointer that's ULONG-aligned + BitmapMem = ExAllocatePoolWithTag(NonPagedPool, BytesNeeded + sizeof(ULONG) - 1, TAG_NTFS); + BitmapPtr = (PULONG)ALIGN_UP_BY((ULONG_PTR)BitmapMem, sizeof(ULONG)); + + RtlZeroMemory(BitmapPtr, BytesNeeded); + + // Read the existing bitmap data + Status = ReadAttribute(DeviceExt, BitmapCtx, 0, (PCHAR)BitmapPtr, BitmapLength); + + // Initialize bitmap + RtlInitializeBitMap(&Bitmap, BitmapPtr, BytesNeeded); + + // Set the bit for the new index record + RtlSetBits(&Bitmap, NextNodeNumber, 1); + + // 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); + + 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 CompareTreeKeys * @implemented @@ -500,6 +731,42 @@ CreateBTreeFromIndex(PDEVICE_EXTENSION Vcb, 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 @@ -686,6 +953,33 @@ CreateIndexBufferFromBTreeNode(PDEVICE_EXTENSION DeviceExt, return Status; } +/** +* @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, @@ -698,13 +992,20 @@ UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt, NTSTATUS Status; BOOLEAN HasIndexAllocation = FALSE; ULONG i; + ULONG IndexAllocationOffset; - DPRINT1("UpdateIndexAllocations() called.\n"); + DPRINT1("UpdateIndexAllocation() called.\n"); - Status = FindAttribute(DeviceExt, FileRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationContext, NULL); + Status = FindAttribute(DeviceExt, FileRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationContext, &IndexAllocationOffset); if (NT_SUCCESS(Status)) + { HasIndexAllocation = TRUE; + PrintAllVCNs(DeviceExt, + IndexAllocationContext, + IndexBufferSize); + } + // TODO: Handle bitmap BitmapContext = NULL; @@ -719,48 +1020,112 @@ UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt, DPRINT1("FIXME: Need to add index allocation\n"); return STATUS_NOT_IMPLEMENTED; } - else + + Status = UpdateIndexNode(DeviceExt, FileRecord, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, IndexAllocationOffset, BitmapContext); + if (!NT_SUCCESS(Status)) { - Status = UpdateIndexNode(DeviceExt, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, BitmapContext); - if (!NT_SUCCESS(Status)) - { - DPRINT1("ERROR: Failed to update index node!\n"); - ReleaseAttributeContext(IndexAllocationContext); - return Status; - } + DPRINT1("ERROR: Failed to update index node!\n"); + ReleaseAttributeContext(IndexAllocationContext); + return Status; } + // Is the Index Entry large enough to store the VCN? + if (!CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) + { + // 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 VCN stored in the index entry of CurrentKey + SetIndexEntryVCN(CurrentKey->IndexEntry, CurrentKey->LesserChild->NodeNumber); + } CurrentKey = CurrentKey->NextKey; } - if(HasIndexAllocation) + if (HasIndexAllocation) + { + PrintAllVCNs(DeviceExt, + IndexAllocationContext, + IndexBufferSize); + 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, PNTFS_ATTR_CONTEXT BitmapContext) { ULONG i; PB_TREE_KEY CurrentKey = Node->FirstKey; NTSTATUS Status; - DPRINT1("UpdateIndexNode(%p, %p, %lu, %p, %p) called for index node with VCN %I64u\n", DeviceExt, Node, IndexBufferSize, IndexAllocationContext, BitmapContext, Node->NodeNumber); + DPRINT1("UpdateIndexNode(%p, %p, %p, %lu, %p, %lu, %p) called for index node with VCN %I64u\n", + DeviceExt, + FileRecord, + Node, + IndexBufferSize, + IndexAllocationContext, + IndexAllocationOffset, + BitmapContext, + Node->NodeNumber); // 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->ExistsOnDisk) + { + // Allocate the node + Status = AllocateIndexNode(DeviceExt, + FileRecord, + IndexBufferSize, + IndexAllocationContext, + IndexAllocationOffset, + &Node->NodeNumber); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to allocate index record in index allocation!\n"); + return Status; + } + + Node->ExistsOnDisk = TRUE; + } // Allocate memory for an index buffer - PINDEX_BUFFER IndexBuffer = ExAllocatePoolWithTag(NonPagedPool, IndexBufferSize, TAG_NTFS); + IndexBuffer = ExAllocatePoolWithTag(NonPagedPool, IndexBufferSize, TAG_NTFS); if (!IndexBuffer) { DPRINT1("ERROR: Failed to allocate %lu bytes for index buffer!\n", IndexBufferSize); @@ -780,7 +1145,7 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt, NodeOffset = GetAllocationOffsetFromVCN(DeviceExt, IndexBufferSize, Node->NodeNumber); // Write the buffer to the index allocation - Status = WriteAttribute(DeviceExt, IndexAllocationContext, NodeOffset, (const PUCHAR)IndexBuffer, IndexBufferSize, &LengthWritten, NULL); + 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"); @@ -806,7 +1171,7 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt, if (CurrentKey->LesserChild) { // Update the child node on disk - Status = UpdateIndexNode(DeviceExt, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, BitmapContext); + Status = UpdateIndexNode(DeviceExt, FileRecord, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, IndexAllocationOffset, BitmapContext); if (!NT_SUCCESS(Status)) { DPRINT1("ERROR: Failed to update child node!\n"); @@ -996,6 +1361,9 @@ GetAllocationOffsetFromVCN(PDEVICE_EXTENSION DeviceExt, * * 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. * @@ -1009,27 +1377,35 @@ GetAllocationOffsetFromVCN(PDEVICE_EXTENSION DeviceExt, * 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. +* * @remarks * A node is always sorted, with the least comparable filename stored first and a dummy key to mark the end. */ NTSTATUS -NtfsInsertKey(ULONGLONG FileReference, +NtfsInsertKey(PB_TREE Tree, + ULONGLONG FileReference, PFILENAME_ATTRIBUTE FileNameAttribute, PB_TREE_FILENAME_NODE Node, - BOOLEAN CaseSensitive) + BOOLEAN CaseSensitive, + ULONG MaxIndexRootSize) { PB_TREE_KEY NewKey, CurrentKey, PreviousKey; - NTSTATUS Status = STATUS_SUCCESS; + NTSTATUS Status = STATUS_SUCCESS; ULONG NodeSize; ULONG AllocatedNodeSize; ULONG MaxNodeSizeWithoutHeader; ULONG i; - DPRINT1("NtfsInsertKey(0x%I64x, %p, %p, %s)\n", + DPRINT1("NtfsInsertKey(%p, 0x%I64x, %p, %p, %s, %lu)\n", + Tree, FileReference, FileNameAttribute, Node, - CaseSensitive ? "TRUE" : "FALSE"); + CaseSensitive ? "TRUE" : "FALSE", + MaxIndexRootSize); // Create the key for the filename attribute NewKey = CreateBTreeKeyFromFilename(FileReference, FileNameAttribute); @@ -1044,6 +1420,11 @@ NtfsInsertKey(ULONGLONG FileReference, // 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? @@ -1054,7 +1435,14 @@ NtfsInsertKey(ULONGLONG FileReference, if (CurrentKey->LesserChild) { // Insert the key into the child node - Status = NtfsInsertKey(FileReference, FileNameAttribute, CurrentKey->LesserChild, CaseSensitive); + Status = NtfsInsertKey(Tree, FileReference, FileNameAttribute, CurrentKey->LesserChild, CaseSensitive, 0); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to insert key.\n"); + ExFreePoolWithTag(NewKey, TAG_NTFS); + return Status; + } + } else { @@ -1078,25 +1466,89 @@ NtfsInsertKey(ULONGLONG FileReference, CurrentKey = CurrentKey->NextKey; } - // Is the node larger than its allocated size? - NodeSize = 0; - CurrentKey = Node->FirstKey; - for (i = 0; i < Node->KeyCount; i++) + // Determine how much space the index entries will need + NodeSize = GetSizeOfIndexEntries(Node); + + // Is Node the root node? + if (Node == Tree->RootNode) { - NodeSize += CurrentKey->IndexEntry->Length; - CurrentKey = CurrentKey->NextKey; + // Is the index root too large for the file record? + if (NodeSize > MaxIndexRootSize) + { + PB_TREE_FILENAME_NODE NewSubNode, NewIndexRoot; + PB_TREE_KEY DummyKey; + + DPRINT1("Collapsing Index Root into sub-node.\n") ; + + DumpBTree(Tree); + + // 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 = Node->KeyCount; + NewSubNode->FirstKey = Node->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; + NewIndexRoot->ExistsOnDisk = TRUE; + + // Make the new node the Tree's root node + Tree->RootNode = NewIndexRoot; + + DumpBTree(Tree); + + return STATUS_SUCCESS; + } } - - // TEMPTEMP: TODO: MATH - AllocatedNodeSize = 0xfe8; - MaxNodeSizeWithoutHeader = AllocatedNodeSize - 0x28; - - if (NodeSize > MaxNodeSizeWithoutHeader) + else { - DPRINT1("FIXME: Splitting a node is still a WIP!\n"); - //SplitBTreeNode(NULL, Node); - //DumpBTree(Tree); - return STATUS_NOT_IMPLEMENTED; + // TEMPTEMP: TODO: MATH + AllocatedNodeSize = 0xfe8; + MaxNodeSizeWithoutHeader = AllocatedNodeSize - 0x28; + + // Has the node grown larger than its allocated size? + if (NodeSize > MaxNodeSizeWithoutHeader) + { + DPRINT1("FIXME: Splitting a node is still a WIP!\n"); + //SplitBTreeNode(NULL, Node); + //DumpBTree(Tree); + return STATUS_NOT_IMPLEMENTED; + } } // NewEntry and NewKey will be destroyed later by DestroyBTree() diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index aeee447e33b..0cd2767efb9 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -2056,9 +2056,10 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, ULONG LengthWritten; PINDEX_ROOT_ATTRIBUTE NewIndexRoot; ULONG AttributeLength; + PNTFS_ATTR_RECORD NextAttribute; PB_TREE NewTree; ULONG BtreeIndexLength; - ULONG MaxIndexSize; + ULONG MaxIndexRootSize; // Allocate memory for the parent directory ParentFileRecord = ExAllocatePoolWithTag(NonPagedPool, @@ -2100,11 +2101,29 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, } // Find the maximum index size given what the file record can hold - MaxIndexSize = DeviceExt->NtfsInfo.BytesPerFileRecord - - IndexRootOffset - - IndexRootContext->pRecord->Resident.ValueOffset - - FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header) - - (sizeof(ULONG) * 2); + // 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 + - FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header) // Subtract the length of the index header for index root + - (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); @@ -2146,7 +2165,7 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, DumpBTree(NewTree); // Insert the key for the file we're adding - Status = NtfsInsertKey(FileReferenceNumber, FilenameAttribute, NewTree->RootNode, CaseSensitive); + Status = NtfsInsertKey(NewTree, FileReferenceNumber, FilenameAttribute, NewTree->RootNode, CaseSensitive, MaxIndexRootSize); if (!NT_SUCCESS(Status)) { DPRINT1("ERROR: Failed to insert key into B-Tree!\n"); @@ -2172,7 +2191,7 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, } // Create the Index Root from the B*Tree - Status = CreateIndexRootFromBTree(DeviceExt, NewTree, MaxIndexSize, &NewIndexRoot, &BtreeIndexLength); + Status = CreateIndexRootFromBTree(DeviceExt, NewTree, MaxIndexRootSize, &NewIndexRoot, &BtreeIndexLength); if (!NT_SUCCESS(Status)) { DPRINT1("ERROR: Failed to create Index root from B-Tree!\n"); diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index 4d436a699e1..030b777c22d 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -728,10 +728,12 @@ GetAllocationOffsetFromVCN(PDEVICE_EXTENSION DeviceExt, ULONGLONG Vcn); NTSTATUS -NtfsInsertKey(ULONGLONG FileReference, +NtfsInsertKey(PB_TREE Tree, + ULONGLONG FileReference, PFILENAME_ATTRIBUTE FileNameAttribute, PB_TREE_FILENAME_NODE Node, - BOOLEAN CaseSensitive); + BOOLEAN CaseSensitive, + ULONG MaxIndexRootSize); NTSTATUS UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt, @@ -741,9 +743,11 @@ UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt, NTSTATUS UpdateIndexNode(PDEVICE_EXTENSION DeviceExt, + PFILE_RECORD_HEADER FileRecord, PB_TREE_FILENAME_NODE Node, ULONG IndexBufferSize, PNTFS_ATTR_CONTEXT IndexAllocationContext, + ULONG IndexAllocationOffset, PNTFS_ATTR_CONTEXT BitmapContext); /* close.c */ From c63e7e54b5a1808d7f126911d75ac996dbb8093c Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Fri, 25 Aug 2017 17:16:04 +0000 Subject: [PATCH 64/71] [NTFS] - When creating files: -Don't add a preceding backslash when creating files on root. -Use NTFS_FILE_NAME_POSIX name type if CaseSensitive option is specified. -Don't try to create a file when a folder is requested (folder creation is still TODO). svn path=/branches/GSoC_2016/NTFS/; revision=75671 --- drivers/filesystems/ntfs/attrib.c | 8 ++++---- drivers/filesystems/ntfs/create.c | 7 +++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index e6d3483553b..e830264ad68 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -172,11 +172,11 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, // we need to extract the filename from the path DPRINT1("Pathname: %wZ\n", &FileObject->FileName); - FilenameNoPath.Buffer = FileObject->FileName.Buffer; - FilenameNoPath.MaximumLength = FilenameNoPath.Length = FileObject->FileName.Length; - FsRtlDissectName(FileObject->FileName, &Current, &Remaining); + FilenameNoPath.Buffer = Current.Buffer; + FilenameNoPath.MaximumLength = FilenameNoPath.Length = Current.Length; + while (Current.Length != 0) { DPRINT1("Current: %wZ\n", &Current); @@ -232,7 +232,7 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, // For now, we're emulating the way Windows behaves when 8.3 name generation is disabled // TODO: add DOS Filename as needed - if (RtlIsNameLegalDOS8Dot3(&FilenameNoPath, NULL, NULL)) + if (!CaseSensitive && RtlIsNameLegalDOS8Dot3(&FilenameNoPath, NULL, NULL)) FileNameAttribute->NameType = NTFS_FILE_NAME_WIN32_AND_DOS; else FileNameAttribute->NameType = NTFS_FILE_NAME_POSIX; diff --git a/drivers/filesystems/ntfs/create.c b/drivers/filesystems/ntfs/create.c index a98afc67b7f..e8d0b1eb141 100644 --- a/drivers/filesystems/ntfs/create.c +++ b/drivers/filesystems/ntfs/create.c @@ -569,6 +569,13 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject, return STATUS_ACCESS_DENIED; } + // We can't create directories yet + if (RequestedOptions & FILE_DIRECTORY_FILE) + { + DPRINT1("FIXME: Folder creation is still TODO!\n"); + return STATUS_NOT_IMPLEMENTED; + } + // Create the file record on disk Status = NtfsCreateFileRecord(DeviceExt, FileObject, From b033f00f58c48d139a86688818d5c3a7692037aa Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Sun, 27 Aug 2017 14:37:17 +0000 Subject: [PATCH 65/71] [NTFS] - Add support for directory creation. Add some helper functions, some comments, and some fixes. +AddIndexRoot() - Creates an $INDEX_ROOT attribute and adds it to a file record. AddNewMftEntry() - Make sure the buffer used by RtlInitializeBitmap() is ULONG-aligned, and a ULONG-multiple in size, per MSDN. AllocateIndexNode() - Calculate BytesNeeded correctly. Read $BITMAP attribute before increasing its length, in anticipation of a future commit that will check for a free bit before assigning a new index record to the end of the allocation. Use appropriate Set*AttributeDataLength() function, as $BITMAP can be resident or non-resident. B_TREE_FILENAME_NODE - Give two members more accurate names: change "ExistsOnDisk" member to "HasValidVCN" and rename "NodeNumber" member "VCN." +CreateEmptyBTree() - Creates a B-Tree to represent an empty directory (for AddIndexRoot). +NtfsCreateEmptyFileRecord() - Creates an empty file record in memory, with no attributes. CreateIndexRootFromBTree() - Fix TotalSizeOfEntries calculation. +NtfsCreateDirectory() - Creates a file record for an empty directory and adds it to the mft. svn path=/branches/GSoC_2016/NTFS/; revision=75692 --- drivers/filesystems/ntfs/attrib.c | 112 ++++++++++++- drivers/filesystems/ntfs/btree.c | 208 ++++++++++++++++------- drivers/filesystems/ntfs/create.c | 263 ++++++++++++++++++++++++++---- drivers/filesystems/ntfs/mft.c | 32 ++-- drivers/filesystems/ntfs/ntfs.h | 40 ++++- 5 files changed, 542 insertions(+), 113 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index e830264ad68..34bee2a0605 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -167,7 +167,11 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, FileNameAttribute->LastWriteTime = SystemTime.QuadPart; FileNameAttribute->LastAccessTime = SystemTime.QuadPart; - FileNameAttribute->FileAttributes = NTFS_FILE_TYPE_ARCHIVE; + // Is this a directory? + if(FileRecord->Flags & FRH_DIRECTORY) + FileNameAttribute->FileAttributes = NTFS_FILE_TYPE_DIRECTORY; + else + FileNameAttribute->FileAttributes = NTFS_FILE_TYPE_ARCHIVE; // we need to extract the filename from the path DPRINT1("Pathname: %wZ\n", &FileObject->FileName); @@ -254,6 +258,112 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, return Status; } +/** +* @name AddIndexRoot +* @implemented +* +* Adds an $INDEX_ROOT attribute to a given FileRecord. +* +* @param Vcb +* Pointer to an NTFS_VCB for the destination volume. +* +* @param FileRecord +* Pointer to a complete file record to add the attribute to. Caller is responsible for +* ensuring FileRecord is large enough to contain $INDEX_ROOT. +* +* @param AttributeAddress +* Pointer to the region of memory that will receive the $INDEX_ROOT attribute. +* This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord). +* +* @param NewIndexRoot +* Pointer to an INDEX_ROOT_ATTRIBUTE containing the index root that will be copied to the new attribute. +* +* @param RootLength +* The length of NewIndexRoot, in bytes. +* +* @param Name +* Pointer to a string of 16-bit Unicode characters naming the attribute. Most often, this will be L"$I30". +* +* @param NameLength +* The number of wide-characters in the name. L"$I30" Would use 4 here. +* +* @return +* STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end +* of the given file record. +* +* @remarks +* This function is intended to assist in creating new folders. +* Only adding the attribute to the end of the file record is supported; AttributeAddress must +* be of type AttributeEnd. +* It's the caller's responsibility to ensure the given file record has enough memory allocated +* for the attribute, and this memory must have been zeroed. +*/ +NTSTATUS +AddIndexRoot(PNTFS_VCB Vcb, + PFILE_RECORD_HEADER FileRecord, + PNTFS_ATTR_RECORD AttributeAddress, + PINDEX_ROOT_ATTRIBUTE NewIndexRoot, + ULONG RootLength, + PCWSTR Name, + USHORT NameLength) +{ + ULONG AttributeLength; + // Calculate the header length + ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR); + // Back up the file record's final ULONG (even though it doesn't matter) + ULONG FileRecordEnd = AttributeAddress->Length; + ULONG NameOffset; + ULONG ValueOffset; + ULONG BytesAvailable; + + if (AttributeAddress->Type != AttributeEnd) + { + DPRINT1("FIXME: Can only add $DATA attribute to the end of a file record.\n"); + return STATUS_NOT_IMPLEMENTED; + } + + NameOffset = ResidentHeaderLength; + + // Calculate ValueOffset, which will be aligned to a 4-byte boundary + ValueOffset = ALIGN_UP_BY(NameOffset + (sizeof(WCHAR) * NameLength), VALUE_OFFSET_ALIGNMENT); + + // Calculate length of attribute + AttributeLength = ValueOffset + RootLength; + AttributeLength = ALIGN_UP_BY(AttributeLength, ATTR_RECORD_ALIGNMENT); + + // Make sure the file record is large enough for the new attribute + BytesAvailable = Vcb->NtfsInfo.BytesPerFileRecord - FileRecord->BytesInUse; + if (BytesAvailable < AttributeLength) + { + DPRINT1("FIXME: Not enough room in file record for index allocation attribute!\n"); + return STATUS_NOT_IMPLEMENTED; + } + + // Set Attribute fields + RtlZeroMemory(AttributeAddress, AttributeLength); + + AttributeAddress->Type = AttributeIndexRoot; + AttributeAddress->Length = AttributeLength; + AttributeAddress->NameLength = NameLength; + AttributeAddress->NameOffset = NameOffset; + AttributeAddress->Instance = FileRecord->NextAttributeNumber++; + + AttributeAddress->Resident.ValueLength = RootLength; + AttributeAddress->Resident.ValueOffset = ValueOffset; + + // Set the name + RtlCopyMemory((PCHAR)((ULONG_PTR)AttributeAddress + NameOffset), Name, NameLength * sizeof(WCHAR)); + + // Copy the index root attribute + RtlCopyMemory((PCHAR)((ULONG_PTR)AttributeAddress + ValueOffset), NewIndexRoot, RootLength); + + // move the attribute-end and file-record-end markers to the end of the file record + AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length); + SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd); + + return STATUS_SUCCESS; +} + /** * @name AddRun * @implemented diff --git a/drivers/filesystems/ntfs/btree.c b/drivers/filesystems/ntfs/btree.c index 727c147a2a4..cd9cf350698 100644 --- a/drivers/filesystems/ntfs/btree.c +++ b/drivers/filesystems/ntfs/btree.c @@ -111,6 +111,7 @@ PrintAllVCNs(PDEVICE_EXTENSION Vcb, * @remarks * AllocateIndexNode() doesn't write any data to the index record it creates. Called by UpdateIndexNode(). * Don't call PrintAllVCNs() or NtfsDumpFileRecord() after calling AllocateIndexNode() before UpdateIndexNode() finishes. +* Possible TODO: Create an empty node and write it to the allocated index node, so the index allocation is always valid. */ NTSTATUS AllocateIndexNode(PDEVICE_EXTENSION DeviceExt, @@ -167,11 +168,30 @@ AllocateIndexNode(PDEVICE_EXTENSION DeviceExt, // See how many bytes we need to store the amount of bits we'll have BytesNeeded = NextNodeNumber / 8; - if (NextNodeNumber % 8 != 0) - BytesNeeded++; + BytesNeeded++; // Windows seems to allocate the bitmap in 8-byte chunks to keep any bytes from being wasted on padding - ALIGN_UP(BytesNeeded, ATTR_RECORD_ALIGNMENT); + BytesNeeded = ALIGN_UP(BytesNeeded, ATTR_RECORD_ALIGNMENT); + + // Allocate memory for the bitmap, including some padding; RtlInitializeBitmap() wants a pointer + // that's ULONG-aligned, and it wants the size of the memory allocated for it to be a ULONG-multiple. + BitmapMem = ExAllocatePoolWithTag(NonPagedPool, BytesNeeded + sizeof(ULONG), TAG_NTFS); + if (!BitmapMem) + { + DPRINT1("Error: failed to allocate bitmap!"); + ReleaseAttributeContext(BitmapCtx); + return STATUS_INSUFFICIENT_RESOURCES; + } + // RtlInitializeBitmap() wants a pointer that's ULONG-aligned. + BitmapPtr = (PULONG)ALIGN_UP_BY((ULONG_PTR)BitmapMem, sizeof(ULONG)); + + RtlZeroMemory(BitmapPtr, BytesNeeded); + + // Read the existing bitmap data + Status = ReadAttribute(DeviceExt, BitmapCtx, 0, (PCHAR)BitmapPtr, BitmapLength); + + // Initialize bitmap + RtlInitializeBitMap(&Bitmap, BitmapPtr, NextNodeNumber); // Do we need to enlarge the bitmap? if (BytesNeeded > BitmapLength) @@ -179,11 +199,22 @@ AllocateIndexNode(PDEVICE_EXTENSION DeviceExt, // TODO: handle synchronization issues that could occur from changing the directory's file record // Change bitmap size DataSize.QuadPart = BytesNeeded; - Status = SetResidentAttributeDataLength(DeviceExt, - BitmapCtx, - BitmapOffset, - FileRecord, - &DataSize); + if (BitmapCtx->pRecord->IsNonResident) + { + Status = SetNonResidentAttributeDataLength(DeviceExt, + BitmapCtx, + BitmapOffset, + FileRecord, + &DataSize); + } + else + { + Status = SetResidentAttributeDataLength(DeviceExt, + BitmapCtx, + BitmapOffset, + FileRecord, + &DataSize); + } if (!NT_SUCCESS(Status)) { DPRINT1("ERROR: Failed to set length of bitmap attribute!\n"); @@ -215,18 +246,6 @@ AllocateIndexNode(PDEVICE_EXTENSION DeviceExt, return Status; } - // Allocate memory for the bitmap. RtlInitializeBitmap() wants a pointer that's ULONG-aligned - BitmapMem = ExAllocatePoolWithTag(NonPagedPool, BytesNeeded + sizeof(ULONG) - 1, TAG_NTFS); - BitmapPtr = (PULONG)ALIGN_UP_BY((ULONG_PTR)BitmapMem, sizeof(ULONG)); - - RtlZeroMemory(BitmapPtr, BytesNeeded); - - // Read the existing bitmap data - Status = ReadAttribute(DeviceExt, BitmapCtx, 0, (PCHAR)BitmapPtr, BitmapLength); - - // Initialize bitmap - RtlInitializeBitMap(&Bitmap, BitmapPtr, BytesNeeded); - // Set the bit for the new index record RtlSetBits(&Bitmap, NextNodeNumber, 1); @@ -246,6 +265,8 @@ AllocateIndexNode(PDEVICE_EXTENSION DeviceExt, // Calculate VCN of new node number *NewVCN = NextNodeNumber * (IndexBufferSize / DeviceExt->NtfsInfo.BytesPerCluster); + DPRINT("New VCN: %I64u\n", *NewVCN); + ExFreePoolWithTag(BitmapMem, TAG_NTFS); ReleaseAttributeContext(BitmapCtx); @@ -311,6 +332,63 @@ CreateDummyKey(BOOLEAN HasChildNode) return NewDummyKey; } +/** +* @name CreateEmptyBTree +* @implemented +* +* Creates an empty B-Tree, which will contain a single root node which will contain a single dummy key. +* +* @param NewTree +* Pointer to a PB_TREE that will receive the pointer of the newly-created B-Tree. +* +* @return +* STATUS_SUCCESS on success. STATUS_INSUFFICIENT_RESOURCES if an allocation fails. +*/ +NTSTATUS +CreateEmptyBTree(PB_TREE *NewTree) +{ + PB_TREE Tree = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE), TAG_NTFS); + PB_TREE_FILENAME_NODE RootNode = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_FILENAME_NODE), TAG_NTFS); + PB_TREE_KEY DummyKey; + + DPRINT1("CreateEmptyBTree(%p) called\n", NewTree); + + if (!Tree || !RootNode) + { + DPRINT1("Couldn't allocate enough memory for B-Tree!\n"); + if (Tree) + ExFreePoolWithTag(Tree, TAG_NTFS); + if (RootNode) + ExFreePoolWithTag(RootNode, TAG_NTFS); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // Create the dummy key + DummyKey = CreateDummyKey(FALSE); + if (!DummyKey) + { + DPRINT1("ERROR: Failed to create dummy key!\n"); + ExFreePoolWithTag(Tree, TAG_NTFS); + ExFreePoolWithTag(RootNode, TAG_NTFS); + return STATUS_INSUFFICIENT_RESOURCES; + } + + RtlZeroMemory(Tree, sizeof(B_TREE)); + RtlZeroMemory(RootNode, sizeof(B_TREE_FILENAME_NODE)); + + // Setup the Tree + RootNode->FirstKey = DummyKey; + RootNode->KeyCount = 1; + RootNode->DiskNeedsUpdating = TRUE; + Tree->RootNode = RootNode; + + *NewTree = Tree; + + // Memory will be freed when DestroyBTree() is called + + return STATUS_SUCCESS; +} + /** * @name CompareTreeKeys * @implemented @@ -402,7 +480,7 @@ CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb, ULONG CurrentEntryOffset = 0; PINDEX_BUFFER NodeBuffer; ULONG IndexBufferSize = Vcb->NtfsInfo.BytesPerIndexRecord; - PULONGLONG NodeNumber; + PULONGLONG VCN; PB_TREE_KEY CurrentKey; NTSTATUS Status; ULONGLONG IndexNodeOffset; @@ -415,10 +493,9 @@ CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb, } // Get the node number from the end of the node entry - NodeNumber = (PULONGLONG)((ULONG_PTR)NodeEntry + NodeEntry->Length - sizeof(ULONGLONG)); + VCN = (PULONGLONG)((ULONG_PTR)NodeEntry + NodeEntry->Length - sizeof(ULONGLONG)); // Create the new tree node - DPRINT1("About to allocate %ld for NewNode\n", sizeof(B_TREE_FILENAME_NODE)); NewNode = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_FILENAME_NODE), TAG_NTFS); if (!NewNode) { @@ -449,7 +526,7 @@ CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb, } // Calculate offset into index allocation - IndexNodeOffset = GetAllocationOffsetFromVCN(Vcb, IndexBufferSize, *NodeNumber); + IndexNodeOffset = GetAllocationOffsetFromVCN(Vcb, IndexBufferSize, *VCN); // TODO: Confirm index bitmap has this node marked as in-use @@ -462,7 +539,7 @@ CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb, ASSERT(BytesRead == IndexBufferSize); NT_ASSERT(NodeBuffer->Ntfs.Type == NRH_INDX_TYPE); - NT_ASSERT(NodeBuffer->VCN == *NodeNumber); + NT_ASSERT(NodeBuffer->VCN == *VCN); // Apply the fixup array to the node buffer Status = FixupUpdateSequenceArray(Vcb, &NodeBuffer->Ntfs); @@ -516,8 +593,6 @@ CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb, // See if the current key has a sub-node if (CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) { - DPRINT1("TODO: Only a node with a single-level is supported right now!\n"); - // Needs debugging: CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb, IndexRoot, IndexAllocationAttributeCtx, @@ -535,8 +610,6 @@ CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb, // See if the current key has a sub-node if (CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) { - DPRINT1("TODO: Only a node with a single-level is supported right now!\n"); - // Needs debugging: CurrentKey->LesserChild = CreateBTreeNodeFromIndexNode(Vcb, IndexRoot, IndexAllocationAttributeCtx, @@ -551,8 +624,8 @@ CreateBTreeNodeFromIndexNode(PDEVICE_EXTENSION Vcb, CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)CurrentNodeEntry + CurrentNodeEntry->Length); } - NewNode->NodeNumber = *NodeNumber; - NewNode->ExistsOnDisk = TRUE; + NewNode->VCN = *VCN; + NewNode->HasValidVCN = TRUE; ExFreePoolWithTag(NodeBuffer, TAG_NTFS); @@ -622,8 +695,6 @@ CreateBTreeFromIndex(PDEVICE_EXTENSION Vcb, NULL); if (!NT_SUCCESS(Status)) IndexAllocationContext = NULL; - else - PrintAllVCNs(Vcb, IndexAllocationContext, IndexRoot->SizeOfEntry); // Setup the Tree RootNode->FirstKey = CurrentKey; @@ -871,7 +942,7 @@ CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt, NewIndexRoot->Header.Flags = INDEX_ROOT_LARGE; // Add Length of Current Entry to Total Size of Entries - NewIndexRoot->Header.TotalSizeOfEntries += CurrentNodeEntry->Length; + NewIndexRoot->Header.TotalSizeOfEntries += CurrentKey->IndexEntry->Length; // Go to the next node entry CurrentNodeEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)CurrentNodeEntry + CurrentNodeEntry->Length); @@ -905,10 +976,12 @@ CreateIndexBufferFromBTreeNode(PDEVICE_EXTENSION DeviceExt, IndexBuffer->Ntfs.UsaCount = 9; // TODO: Check bitmap for VCN - ASSERT(Node->ExistsOnDisk); - IndexBuffer->VCN = Node->NodeNumber; + ASSERT(Node->HasValidVCN); + IndexBuffer->VCN = Node->VCN; - IndexBuffer->Header.FirstEntryOffset = 0x28; + // Windows seems to alternate between using 0x28 and 0x40 for the first entry offset of each index buffer. + // Interestingly, neither Windows nor chkdsk seem to mind if we just use 0x28 for every index record. + IndexBuffer->Header.FirstEntryOffset = 0x28; IndexBuffer->Header.AllocatedSize = BufferSize - FIELD_OFFSET(INDEX_BUFFER, Header); // Start summing the total size of this node's entries @@ -934,9 +1007,9 @@ CreateIndexBufferFromBTreeNode(PDEVICE_EXTENSION DeviceExt, // Copy the index entry RtlCopyMemory(CurrentNodeEntry, CurrentKey->IndexEntry, CurrentKey->IndexEntry->Length); - DPRINT1("Index Node Entry Stream Length: %u\nIndex Node Entry Length: %u\n", - CurrentNodeEntry->KeyLength, - CurrentNodeEntry->Length); + DPRINT("Index Node Entry Stream Length: %u\nIndex Node Entry Length: %u\n", + CurrentNodeEntry->KeyLength, + CurrentNodeEntry->Length); // Add Length of Current Entry to Total Size of Entries IndexBuffer->Header.TotalSizeOfEntries += CurrentNodeEntry->Length; @@ -1020,14 +1093,6 @@ UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt, DPRINT1("FIXME: Need to add index allocation\n"); return STATUS_NOT_IMPLEMENTED; } - - Status = UpdateIndexNode(DeviceExt, FileRecord, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, IndexAllocationOffset, BitmapContext); - if (!NT_SUCCESS(Status)) - { - DPRINT1("ERROR: Failed to update index node!\n"); - ReleaseAttributeContext(IndexAllocationContext); - return Status; - } // Is the Index Entry large enough to store the VCN? if (!CurrentKey->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) @@ -1056,9 +1121,17 @@ UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt, CurrentKey->IndexEntry->Flags |= NTFS_INDEX_ENTRY_NODE; } - // Update the VCN stored in the index entry of CurrentKey - SetIndexEntryVCN(CurrentKey->IndexEntry, CurrentKey->LesserChild->NodeNumber); + // Update the sub-node + Status = UpdateIndexNode(DeviceExt, FileRecord, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, IndexAllocationOffset, BitmapContext); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to update index node!\n"); + ReleaseAttributeContext(IndexAllocationContext); + return Status; + } + // Update the VCN stored in the index entry of CurrentKey + SetIndexEntryVCN(CurrentKey->IndexEntry, CurrentKey->LesserChild->VCN); } CurrentKey = CurrentKey->NextKey; } @@ -1096,7 +1169,7 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt, IndexAllocationContext, IndexAllocationOffset, BitmapContext, - Node->NodeNumber); + Node->VCN); // Do we need to write this node to disk? if (Node->DiskNeedsUpdating) @@ -1106,7 +1179,7 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt, PINDEX_BUFFER IndexBuffer; // Does the node need to be assigned a VCN? - if (!Node->ExistsOnDisk) + if (!Node->HasValidVCN) { // Allocate the node Status = AllocateIndexNode(DeviceExt, @@ -1114,14 +1187,14 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt, IndexBufferSize, IndexAllocationContext, IndexAllocationOffset, - &Node->NodeNumber); + &Node->VCN); if (!NT_SUCCESS(Status)) { DPRINT1("ERROR: Failed to allocate index record in index allocation!\n"); return Status; } - Node->ExistsOnDisk = TRUE; + Node->HasValidVCN = TRUE; } // Allocate memory for an index buffer @@ -1142,7 +1215,7 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt, } // Get Offset of index buffer in index allocation - NodeOffset = GetAllocationOffsetFromVCN(DeviceExt, IndexBufferSize, Node->NodeNumber); + NodeOffset = GetAllocationOffsetFromVCN(DeviceExt, IndexBufferSize, Node->VCN); // Write the buffer to the index allocation Status = WriteAttribute(DeviceExt, IndexAllocationContext, NodeOffset, (const PUCHAR)IndexBuffer, IndexBufferSize, &LengthWritten, FileRecord); @@ -1274,7 +1347,7 @@ DestroyBTree(PB_TREE Tree) } VOID -DumpBTreeKey(PB_TREE_KEY Key, ULONG Number, ULONG Depth) +DumpBTreeKey(PB_TREE Tree, PB_TREE_KEY Key, ULONG Number, ULONG Depth) { ULONG i; for (i = 0; i < Depth; i++) @@ -1298,7 +1371,7 @@ DumpBTreeKey(PB_TREE_KEY Key, ULONG Number, ULONG Depth) if (Key->IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) { if (Key->LesserChild) - DumpBTreeNode(Key->LesserChild, Number, Depth + 1); + DumpBTreeNode(Tree, Key->LesserChild, Number, Depth + 1); else { // This will be an assert once nodes with arbitrary depth are debugged @@ -1308,18 +1381,28 @@ DumpBTreeKey(PB_TREE_KEY Key, ULONG Number, ULONG Depth) } VOID -DumpBTreeNode(PB_TREE_FILENAME_NODE Node, ULONG Number, ULONG Depth) +DumpBTreeNode(PB_TREE Tree, + PB_TREE_FILENAME_NODE Node, + ULONG Number, + ULONG Depth) { PB_TREE_KEY CurrentKey; ULONG i; for (i = 0; i < Depth; i++) DbgPrint(" "); - DbgPrint("Node #%d, Depth %d, has %d key%s\n", Number, Depth, Node->KeyCount, Node->KeyCount == 1 ? "" : "s"); + DbgPrint("Node #%d, Depth %d, has %d key%s", Number, Depth, Node->KeyCount, Node->KeyCount == 1 ? "" : "s"); + + if (Node->HasValidVCN) + DbgPrint(" VCN: %I64u\n", Node->VCN); + else if (Tree->RootNode == Node) + DbgPrint(" Index Root"); + else + DbgPrint(" NOT ASSIGNED VCN YET\n"); CurrentKey = Node->FirstKey; - for (i = 1; i <= Node->KeyCount; i++) + for (i = 0; i < Node->KeyCount; i++) { - DumpBTreeKey(CurrentKey, i, Depth); + DumpBTreeKey(Tree, CurrentKey, i, Depth); CurrentKey = CurrentKey->NextKey; } } @@ -1340,7 +1423,7 @@ VOID DumpBTree(PB_TREE Tree) { DbgPrint("B_TREE @ %p\n", Tree); - DumpBTreeNode(Tree->RootNode, 0, 0); + DumpBTreeNode(Tree, Tree->RootNode, 0, 0); } // Calculates start of Index Buffer relative to the index allocation, given the node's VCN @@ -1525,7 +1608,6 @@ NtfsInsertKey(PB_TREE Tree, NewIndexRoot->FirstKey = DummyKey; NewIndexRoot->KeyCount = 1; NewIndexRoot->DiskNeedsUpdating = TRUE; - NewIndexRoot->ExistsOnDisk = TRUE; // Make the new node the Tree's root node Tree->RootNode = NewIndexRoot; diff --git a/drivers/filesystems/ntfs/create.c b/drivers/filesystems/ntfs/create.c index e8d0b1eb141..55e1e95a38b 100644 --- a/drivers/filesystems/ntfs/create.c +++ b/drivers/filesystems/ntfs/create.c @@ -569,25 +569,31 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject, return STATUS_ACCESS_DENIED; } - // We can't create directories yet + // Was the user trying to create a directory? if (RequestedOptions & FILE_DIRECTORY_FILE) { - DPRINT1("FIXME: Folder creation is still TODO!\n"); - return STATUS_NOT_IMPLEMENTED; + // Create the directory on disk + Status = NtfsCreateDirectory(DeviceExt, + FileObject, + BooleanFlagOn(Stack->Flags, SL_CASE_SENSITIVE), + BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT)); + } + else + { + // Create the file record on disk + Status = NtfsCreateFileRecord(DeviceExt, + FileObject, + BooleanFlagOn(Stack->Flags, SL_CASE_SENSITIVE), + BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT)); } - // Create the file record on disk - Status = NtfsCreateFileRecord(DeviceExt, - FileObject, - BooleanFlagOn(Stack->Flags, SL_CASE_SENSITIVE), - BooleanFlagOn(IrpContext->Flags,IRPCONTEXT_CANWAIT)); if (!NT_SUCCESS(Status)) { DPRINT1("ERROR: Couldn't create file record!\n"); return Status; } - // Before we open the file we just created, we need to change the disposition (upper 8 bits of ULONG) + // Before we open the file/directory we just created, we need to change the disposition (upper 8 bits of ULONG) // from create to open, since we already created the file Stack->Parameters.Create.Options = (ULONG)FILE_OPEN << 24 | RequestedOptions; @@ -650,6 +656,215 @@ NtfsCreate(PNTFS_IRP_CONTEXT IrpContext) 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 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 + ULONG MaxIndexRootSize = DeviceExt->NtfsInfo.BytesPerFileRecord + - ((ULONG_PTR)NextAttribute - (ULONG_PTR)FileRecord) + - sizeof(ULONG) * 2; + + // Create a new index record from the tree + Status = CreateIndexRootFromBTree(DeviceExt, + Tree, + MaxIndexRootSize, + &NewIndexRoot, + &RootLength); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Unable to create empty index root!\n"); + DestroyBTree(Tree); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + return Status; + } + + // We're done with the B-Tree + DestroyBTree(Tree); + + // add the $INDEX_ROOT attribute + Status = AddIndexRoot(DeviceExt, FileRecord, NextAttribute, NewIndexRoot, RootLength, L"$I30", 4); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ERROR: Failed to add index root to new file record!\n"); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + return Status; + } + + +#ifndef NDEBUG + NtfsDumpFileRecord(DeviceExt, FileRecord); +#endif + + // Now that we've built the file record in memory, we need to store it in the MFT. + Status = AddNewMftEntry(FileRecord, DeviceExt, &FileMftIndex, CanWait); + if (NT_SUCCESS(Status)) + { + // The highest 2 bytes should be the sequence number, unless the parent happens to be root + if (FileMftIndex == NTFS_FILE_ROOT) + FileMftIndex = FileMftIndex + ((ULONGLONG)NTFS_FILE_ROOT << 48); + else + FileMftIndex = FileMftIndex + ((ULONGLONG)FileRecord->SequenceNumber << 48); + + DPRINT1("New File Reference: 0x%016I64x\n", FileMftIndex); + + // Add the filename attribute to the filename-index of the parent directory + Status = NtfsAddFilenameToDirectory(DeviceExt, + ParentMftIndex, + FileMftIndex, + FilenameAttribute, + CaseSensitive); + } + + ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + + return Status; +} + +/** +* @name NtfsCreateEmptyFileRecord +* @implemented +* +* Creates a new, empty file record, with no attributes. +* +* @param DeviceExt +* Pointer to the DEVICE_EXTENSION of the target volume the file record will be stored on. +* +* @return +* A pointer to the newly-created FILE_RECORD_HEADER if the function succeeds, NULL otherwise. +*/ +PFILE_RECORD_HEADER +NtfsCreateEmptyFileRecord(PDEVICE_EXTENSION DeviceExt) +{ + PFILE_RECORD_HEADER FileRecord; + PNTFS_ATTR_RECORD NextAttribute; + + DPRINT1("NtfsCreateEmptyFileRecord(%p)\n", DeviceExt); + + // allocate memory for file record + FileRecord = ExAllocatePoolWithTag(NonPagedPool, + DeviceExt->NtfsInfo.BytesPerFileRecord, + 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 @@ -693,43 +908,19 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, CanWait ? "TRUE" : "FALSE"); // allocate memory for file record - FileRecord = ExAllocatePoolWithTag(NonPagedPool, - DeviceExt->NtfsInfo.BytesPerFileRecord, - TAG_NTFS); + FileRecord = NtfsCreateEmptyFileRecord(DeviceExt); if (!FileRecord) { DPRINT1("ERROR: Unable to allocate memory for file record!\n"); return STATUS_INSUFFICIENT_RESOURCES; } - 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; - // add first attribute, $STANDARD_INFORMATION AddStandardInformation(FileRecord, NextAttribute); - + // advance NextAttribute pointer to the next attribute NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)NextAttribute + (ULONG_PTR)NextAttribute->Length); @@ -745,8 +936,10 @@ NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, // add the $DATA attribute AddData(FileRecord, NextAttribute); +#ifndef NDEBUG // dump file record in memory (for debugging) NtfsDumpFileRecord(DeviceExt, FileRecord); +#endif // Now that we've built the file record in memory, we need to store it in the MFT. Status = AddNewMftEntry(FileRecord, DeviceExt, &FileMftIndex, CanWait); diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 0cd2767efb9..7d0157a7611 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -1893,6 +1893,7 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, ULONGLONG BitmapDataSize; ULONGLONG AttrBytesRead; PUCHAR BitmapData; + PUCHAR BitmapBuffer; ULONG LengthWritten; PNTFS_ATTR_CONTEXT BitmapContext; LARGE_INTEGER BitmapBits; @@ -1901,6 +1902,8 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, DPRINT1("AddNewMftEntry(%p, %p, %p, %s)\n", FileRecord, DeviceExt, DestinationIndex, CanWait ? "TRUE" : "FALSE"); // First, we have to read the mft's $Bitmap attribute + + // Find the attribute Status = FindAttribute(DeviceExt, DeviceExt->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL); if (!NT_SUCCESS(Status)) { @@ -1908,22 +1911,28 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, return Status; } - // allocate a buffer for the $Bitmap attribute + // Get size of bitmap BitmapDataSize = AttributeDataLength(BitmapContext->pRecord); - BitmapData = ExAllocatePoolWithTag(NonPagedPool, BitmapDataSize, TAG_NTFS); - if (!BitmapData) + + // RtlInitializeBitmap wants a ULONG-aligned pointer, and wants the memory passed to it to be a ULONG-multiple + // Allocate a buffer for the $Bitmap attribute plus enough to ensure we can get a ULONG-aligned pointer + BitmapBuffer = ExAllocatePoolWithTag(NonPagedPool, BitmapDataSize + sizeof(ULONG), TAG_NTFS); + if (!BitmapBuffer) { ReleaseAttributeContext(BitmapContext); return STATUS_INSUFFICIENT_RESOURCES; } + // Get a ULONG-aligned pointer for the bitmap itself + BitmapData = (PUCHAR)ALIGN_UP_BY((ULONG_PTR)BitmapBuffer, sizeof(ULONG)); + // read $Bitmap attribute AttrBytesRead = ReadAttribute(DeviceExt, BitmapContext, 0, (PCHAR)BitmapData, BitmapDataSize); - if (AttrBytesRead == 0) + if (AttrBytesRead != BitmapDataSize) { DPRINT1("ERROR: Unable to read $Bitmap attribute of master file table!\n"); - ExFreePoolWithTag(BitmapData, TAG_NTFS); + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); ReleaseAttributeContext(BitmapContext); return STATUS_OBJECT_NAME_NOT_FOUND; } @@ -1939,7 +1948,8 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, if (BitmapBits.HighPart != 0) { DPRINT1("\tFIXME: bitmap sizes beyond 32bits are not yet supported! (Your NTFS volume is too large)\n"); - ExFreePoolWithTag(BitmapData, TAG_NTFS); + NtfsGlobalData->EnableWriteSupport = FALSE; + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); ReleaseAttributeContext(BitmapContext); return STATUS_NOT_IMPLEMENTED; } @@ -1953,7 +1963,7 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, { DPRINT1("Couldn't find free space in MFT for file record, increasing MFT size.\n"); - ExFreePoolWithTag(BitmapData, TAG_NTFS); + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); ReleaseAttributeContext(BitmapContext); // Couldn't find a free record in the MFT, add some blank records and try again @@ -1982,7 +1992,7 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, if (!NT_SUCCESS(Status)) { DPRINT1("ERROR encountered when writing $Bitmap attribute!\n"); - ExFreePoolWithTag(BitmapData, TAG_NTFS); + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); ReleaseAttributeContext(BitmapContext); return Status; } @@ -1993,14 +2003,14 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, if (!NT_SUCCESS(Status)) { DPRINT1("ERROR: Unable to write file record!\n"); - ExFreePoolWithTag(BitmapData, TAG_NTFS); + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); ReleaseAttributeContext(BitmapContext); return Status; } *DestinationIndex = MftIndex; - ExFreePoolWithTag(BitmapData, TAG_NTFS); + ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); ReleaseAttributeContext(BitmapContext); return Status; @@ -2255,7 +2265,7 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, AttributeLength, &LengthWritten, ParentFileRecord); - if (!NT_SUCCESS(Status)) + if (!NT_SUCCESS(Status) || LengthWritten != AttributeLength) { DPRINT1("ERROR: Unable to write new index root attribute to parent directory!\n"); ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index 030b777c22d..abd2d2791d9 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -304,6 +304,12 @@ typedef struct // relative to the beginning of the file record. #define ATTR_RECORD_ALIGNMENT 8 +// Data runs are aligned to a 4-byte boundary, relative to the start of the attribute record +#define DATA_RUN_ALIGNMENT 4 + +// Value offset is aligned to a 4-byte boundary, relative to the start of the attribute record +#define VALUE_OFFSET_ALIGNMENT 4 + typedef struct { ULONGLONG CreationTime; @@ -423,9 +429,9 @@ typedef struct _B_TREE_KEY typedef struct _B_TREE_FILENAME_NODE { ULONG KeyCount; - BOOLEAN ExistsOnDisk; + BOOLEAN HasValidVCN; BOOLEAN DiskNeedsUpdating; - ULONGLONG NodeNumber; + ULONGLONG VCN; PB_TREE_KEY FirstKey; } B_TREE_FILENAME_NODE, *PB_TREE_FILENAME_NODE; @@ -570,6 +576,15 @@ AddRun(PNTFS_VCB Vcb, ULONGLONG NextAssignedCluster, ULONG RunLength); +NTSTATUS +AddIndexRoot(PNTFS_VCB Vcb, + PFILE_RECORD_HEADER FileRecord, + PNTFS_ATTR_RECORD AttributeAddress, + PINDEX_ROOT_ATTRIBUTE NewIndexRoot, + ULONG RootLength, + PCWSTR Name, + USHORT NameLength); + NTSTATUS AddFileName(PFILE_RECORD_HEADER FileRecord, PNTFS_ATTR_RECORD AttributeAddress, @@ -718,10 +733,20 @@ VOID DumpBTree(PB_TREE Tree); VOID -DumpBTreeNode(PB_TREE_FILENAME_NODE Node, +DumpBTreeKey(PB_TREE Tree, + PB_TREE_KEY Key, + ULONG Number, + ULONG Depth); + +VOID +DumpBTreeNode(PB_TREE Tree, + PB_TREE_FILENAME_NODE Node, ULONG Number, ULONG Depth); +NTSTATUS +CreateEmptyBTree(PB_TREE *NewTree); + ULONGLONG GetAllocationOffsetFromVCN(PDEVICE_EXTENSION DeviceExt, ULONG IndexBufferSize, @@ -771,6 +796,15 @@ NtfsClose(PNTFS_IRP_CONTEXT IrpContext); NTSTATUS NtfsCreate(PNTFS_IRP_CONTEXT IrpContext); +NTSTATUS +NtfsCreateDirectory(PDEVICE_EXTENSION DeviceExt, + PFILE_OBJECT FileObject, + BOOLEAN CaseSensitive, + BOOLEAN CanWait); + +PFILE_RECORD_HEADER +NtfsCreateEmptyFileRecord(PDEVICE_EXTENSION DeviceExt); + NTSTATUS NtfsCreateFileRecord(PDEVICE_EXTENSION DeviceExt, PFILE_OBJECT FileObject, From 9a91a51f17c3baf5c510effb869e4d6fb4097e6c Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Sun, 27 Aug 2017 14:43:35 +0000 Subject: [PATCH 66/71] [NTFS] - Fix for C89 compliance. svn path=/branches/GSoC_2016/NTFS/; revision=75693 --- drivers/filesystems/ntfs/create.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/filesystems/ntfs/create.c b/drivers/filesystems/ntfs/create.c index 55e1e95a38b..b591b79073f 100644 --- a/drivers/filesystems/ntfs/create.c +++ b/drivers/filesystems/ntfs/create.c @@ -698,6 +698,7 @@ NtfsCreateDirectory(PDEVICE_EXTENSION DeviceExt, ULONGLONG FileMftIndex; PB_TREE Tree; PINDEX_ROOT_ATTRIBUTE NewIndexRoot; + ULONG MaxIndexRootSize; ULONG RootLength; DPRINT1("NtfsCreateFileRecord(%p, %p, %s, %s)\n", @@ -745,9 +746,9 @@ NtfsCreateDirectory(PDEVICE_EXTENSION DeviceExt, } // Calculate maximum size of index root - ULONG MaxIndexRootSize = DeviceExt->NtfsInfo.BytesPerFileRecord - - ((ULONG_PTR)NextAttribute - (ULONG_PTR)FileRecord) - - sizeof(ULONG) * 2; + MaxIndexRootSize = DeviceExt->NtfsInfo.BytesPerFileRecord + - ((ULONG_PTR)NextAttribute - (ULONG_PTR)FileRecord) + - sizeof(ULONG) * 2; // Create a new index record from the tree Status = CreateIndexRootFromBTree(DeviceExt, From 5e7c11842ad0b24a9a7bc847e938a9cda0b2748e Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Mon, 28 Aug 2017 03:11:38 +0000 Subject: [PATCH 67/71] [NTFS] - Fix increasing the mft size, to keep chkdsk happy. IncreaseMftSize() - Add some fixes. Write blank records to newly-allocated mft entries, and update $MFTMirr when finished; these changes are needed for chkdsk. Increase size by 64 records instead of 8. +UpdateMftMirror() - Backs up the first ~4 master file table entries to the $MFTMirr file. svn path=/branches/GSoC_2016/NTFS/; revision=75694 --- drivers/filesystems/ntfs/mft.c | 200 ++++++++++++++++++++++++++++++-- drivers/filesystems/ntfs/ntfs.h | 3 + 2 files changed, 195 insertions(+), 8 deletions(-) diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 7d0157a7611..547ab64a2b1 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -30,6 +30,7 @@ /* INCLUDES *****************************************************************/ #include "ntfs.h" +#include #define NDEBUG #include @@ -228,7 +229,7 @@ AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord) * 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 8 records. Bitmap entries for the new records are cleared, +* 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. */ @@ -239,13 +240,17 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait) LARGE_INTEGER BitmapSize; LARGE_INTEGER DataSize; LONGLONG BitmapSizeDifference; - ULONG DataSizeDifference = Vcb->NtfsInfo.BytesPerFileRecord * 8; + 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"); @@ -256,11 +261,23 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN 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, &BitmapOffset); + 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; } @@ -271,9 +288,17 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait) // 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); @@ -283,6 +308,7 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait) 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; @@ -300,6 +326,7 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait) if (BytesRead != BitmapSize.LowPart) { DPRINT1("ERROR: Bytes read != Bitmap size!\n"); + ExFreePoolWithTag(BlankFileRecord, TAG_NTFS); ExReleaseResourceLite(&(Vcb->DirResource)); ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); ReleaseAttributeContext(BitmapContext); @@ -311,17 +338,29 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait) 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 += BitmapSizeDifference; + BitmapSize.QuadPart = NewBitmapSize; if (BitmapContext->pRecord->IsNonResident) Status = SetNonResidentAttributeDataLength(Vcb, BitmapContext, BitmapOffset, Vcb->MasterFileTable, &BitmapSize); else @@ -330,6 +369,7 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait) 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); @@ -337,13 +377,14 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait) } } - //NtfsDumpFileAttributes(Vcb, FileRecord); + 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); @@ -351,9 +392,10 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait) } // Write out the new bitmap - Status = WriteAttribute(Vcb, BitmapContext, BitmapOffset, BitmapBuffer, BitmapSize.LowPart, &LengthWritten, Vcb->MasterFileTable); + 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); @@ -361,12 +403,31 @@ IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait) 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_SUCCESS; + return Status; } /** @@ -2384,6 +2445,129 @@ CompareFileName(PUNICODE_STRING FileName, } } +/** +* @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. +* +* @returninja livecd + +* 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 diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index abd2d2791d9..aba8b291969 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -1047,6 +1047,9 @@ CompareFileName(PUNICODE_STRING FileName, BOOLEAN DirSearch, BOOLEAN CaseSensitive); +NTSTATUS +UpdateMftMirror(PNTFS_VCB Vcb); + NTSTATUS ReadFileRecord(PDEVICE_EXTENSION Vcb, ULONGLONG index, From 52c30fdf37d3fd9a1583d4052f6b11365099613c Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Tue, 29 Aug 2017 15:51:14 +0000 Subject: [PATCH 68/71] [NTFS] - Add some helper functions for new features. Add some fixes. Add support for creating an index allocation, splitting a b-tree node, or "demoting" the index root. This allows for file creation without functional limitations. +AddBitmap() - adds a $BITMAP attribute to a file record. +AddIndexAllocation() - adds an $INDEX_ALLOCATION attribute to a file record. +CountBTreeKeys() - Counts the number of linked B-Tree keys. CreateIndexBufferFromBTreeNode() - Set INDEX_NODE_LARGE if the node has sub-nodes. CreateIndexRootFromBTree() - Simplify the usage and math of MaxIndexSize; make it only account for the cumulative size of the index entries. +DemoteBTreeRoot() - Replaces the contents of an index root with a dummy key, and puts those contents in a new node, which is made a child of the dummy key. This is done when an index root grows too large. +GetIndexEntryVCN() - Retrieves the VCN from an index entry. NtfsAddFilenameToDirectory() - Fix math for MaxIndexRootSize. NtfsInsertKey() - Add support for splitting a B-Tree node. Don't check size of index root (that will be handled later). +SplitBTreeNode() - Called when a B-Tree node grows too large. UpdateIndexAllocation() - Create an $I30 index allocation attribute and bitmap attribute if needed. UpdateIndexNode() - Update children before updating the current node. Store VCN of child nodes in the index entries of their respective keys. svn path=/branches/GSoC_2016/NTFS/; revision=75707 --- drivers/filesystems/ntfs/attrib.c | 193 ++++++++++ drivers/filesystems/ntfs/btree.c | 615 ++++++++++++++++++++++++------ drivers/filesystems/ntfs/mft.c | 186 ++++++++- drivers/filesystems/ntfs/ntfs.h | 35 +- 4 files changed, 895 insertions(+), 134 deletions(-) diff --git a/drivers/filesystems/ntfs/attrib.c b/drivers/filesystems/ntfs/attrib.c index 34bee2a0605..846c16da808 100644 --- a/drivers/filesystems/ntfs/attrib.c +++ b/drivers/filesystems/ntfs/attrib.c @@ -36,6 +36,100 @@ /* 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 @@ -258,6 +352,105 @@ AddFileName(PFILE_RECORD_HEADER FileRecord, 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 diff --git a/drivers/filesystems/ntfs/btree.c b/drivers/filesystems/ntfs/btree.c index cd9cf350698..bb745c5e9e2 100644 --- a/drivers/filesystems/ntfs/btree.c +++ b/drivers/filesystems/ntfs/btree.c @@ -468,6 +468,33 @@ CompareTreeKeys(PB_TREE_KEY Key1, PB_TREE_KEY Key2, BOOLEAN CaseSensitive) 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, @@ -852,6 +879,9 @@ GetSizeOfIndexEntries(PB_TREE_FILENAME_NODE Node) * * @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). * @@ -885,6 +915,10 @@ CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt, 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"); @@ -918,12 +952,10 @@ CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt, 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 = FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header) - + NewIndexRoot->Header.TotalSizeOfEntries - + CurrentNodeEntry->Length; + ULONG IndexSize = NewIndexRoot->Header.TotalSizeOfEntries - NewIndexRoot->Header.FirstEntryOffset + CurrentKey->IndexEntry->Length; if (IndexSize > MaxIndexSize) { - DPRINT1("TODO: Adding file would require creating an index allocation!\n"); + DPRINT1("TODO: Adding file would require creating an attribute list!\n"); ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); return STATUS_NOT_IMPLEMENTED; } @@ -962,6 +994,7 @@ NTSTATUS CreateIndexBufferFromBTreeNode(PDEVICE_EXTENSION DeviceExt, PB_TREE_FILENAME_NODE Node, ULONG BufferSize, + BOOLEAN HasChildren, PINDEX_BUFFER IndexBuffer) { ULONG i; @@ -1014,7 +1047,9 @@ CreateIndexBufferFromBTreeNode(PDEVICE_EXTENSION DeviceExt, // Add Length of Current Entry to Total Size of Entries IndexBuffer->Header.TotalSizeOfEntries += CurrentNodeEntry->Length; - // TODO: Check for child nodes + // 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); @@ -1026,6 +1061,89 @@ CreateIndexBufferFromBTreeNode(PDEVICE_EXTENSION DeviceExt, 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 @@ -1060,7 +1178,7 @@ UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt, PFILE_RECORD_HEADER FileRecord) { // Find the index allocation and bitmap - PNTFS_ATTR_CONTEXT IndexAllocationContext, BitmapContext; + PNTFS_ATTR_CONTEXT IndexAllocationContext; PB_TREE_KEY CurrentKey; NTSTATUS Status; BOOLEAN HasIndexAllocation = FALSE; @@ -1074,14 +1192,12 @@ UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt, { HasIndexAllocation = TRUE; +#ifndef NDEBUG PrintAllVCNs(DeviceExt, IndexAllocationContext, IndexBufferSize); +#endif } - - // TODO: Handle bitmap - BitmapContext = NULL; - // Walk through the root node and update all the sub-nodes CurrentKey = Tree->RootNode->FirstKey; for (i = 0; i < Tree->RootNode->KeyCount; i++) @@ -1090,8 +1206,46 @@ UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt, { if (!HasIndexAllocation) { - DPRINT1("FIXME: Need to add index allocation\n"); - return STATUS_NOT_IMPLEMENTED; + // 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? @@ -1122,7 +1276,7 @@ UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt, } // Update the sub-node - Status = UpdateIndexNode(DeviceExt, FileRecord, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, IndexAllocationOffset, BitmapContext); + Status = UpdateIndexNode(DeviceExt, FileRecord, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, IndexAllocationOffset); if (!NT_SUCCESS(Status)) { DPRINT1("ERROR: Failed to update index node!\n"); @@ -1136,12 +1290,17 @@ UpdateIndexAllocation(PDEVICE_EXTENSION DeviceExt, CurrentKey = CurrentKey->NextKey; } +#ifndef NDEBUG + DumpBTree(Tree); +#endif + if (HasIndexAllocation) { +#ifndef NDEBUG PrintAllVCNs(DeviceExt, IndexAllocationContext, IndexBufferSize); - +#endif ReleaseAttributeContext(IndexAllocationContext); } @@ -1154,22 +1313,74 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt, PB_TREE_FILENAME_NODE Node, ULONG IndexBufferSize, PNTFS_ATTR_CONTEXT IndexAllocationContext, - ULONG IndexAllocationOffset, - PNTFS_ATTR_CONTEXT BitmapContext) + ULONG IndexAllocationOffset) { ULONG i; PB_TREE_KEY CurrentKey = Node->FirstKey; + BOOLEAN HasChildren = FALSE; NTSTATUS Status; - DPRINT1("UpdateIndexNode(%p, %p, %p, %lu, %p, %lu, %p) called for index node with VCN %I64u\n", - DeviceExt, - FileRecord, - Node, - IndexBufferSize, - IndexAllocationContext, - IndexAllocationOffset, - BitmapContext, - Node->VCN); + + 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) @@ -1206,7 +1417,7 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt, } // Create the index buffer we'll be writing to disk to represent this node - Status = CreateIndexBufferFromBTreeNode(DeviceExt, Node, IndexBufferSize, IndexBuffer); + Status = CreateIndexBufferFromBTreeNode(DeviceExt, Node, IndexBufferSize, HasChildren, IndexBuffer); if (!NT_SUCCESS(Status)) { DPRINT1("ERROR: Failed to create index buffer from node!\n"); @@ -1235,26 +1446,6 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt, ExFreePoolWithTag(IndexBuffer, TAG_NTFS); } - // 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) - { - // Update the child node on disk - Status = UpdateIndexNode(DeviceExt, FileRecord, CurrentKey->LesserChild, IndexBufferSize, IndexAllocationContext, IndexAllocationOffset, BitmapContext); - if (!NT_SUCCESS(Status)) - { - DPRINT1("ERROR: Failed to update child node!\n"); - return Status; - } - } - - CurrentKey = CurrentKey->NextKey; - } - return STATUS_SUCCESS; } @@ -1438,6 +1629,16 @@ GetAllocationOffsetFromVCN(PDEVICE_EXTENSION DeviceExt, 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 @@ -1464,6 +1665,17 @@ GetAllocationOffsetFromVCN(PDEVICE_EXTENSION DeviceExt, * 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. */ @@ -1473,7 +1685,10 @@ NtfsInsertKey(PB_TREE Tree, PFILENAME_ATTRIBUTE FileNameAttribute, PB_TREE_FILENAME_NODE Node, BOOLEAN CaseSensitive, - ULONG MaxIndexRootSize) + ULONG MaxIndexRootSize, + ULONG IndexRecordSize, + PB_TREE_KEY *MedianKey, + PB_TREE_FILENAME_NODE *NewRightHandSibling) { PB_TREE_KEY NewKey, CurrentKey, PreviousKey; NTSTATUS Status = STATUS_SUCCESS; @@ -1482,13 +1697,19 @@ NtfsInsertKey(PB_TREE Tree, ULONG MaxNodeSizeWithoutHeader; ULONG i; - DPRINT1("NtfsInsertKey(%p, 0x%I64x, %p, %p, %s, %lu)\n", + *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); + MaxIndexRootSize, + IndexRecordSize, + MedianKey, + NewRightHandSibling); // Create the key for the filename attribute NewKey = CreateBTreeKeyFromFilename(FileReference, FileNameAttribute); @@ -1513,12 +1734,22 @@ NtfsInsertKey(PB_TREE Tree, // 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, 0); + Status = NtfsInsertKey(Tree, + FileReference, + FileNameAttribute, + CurrentKey->LesserChild, + CaseSensitive, + MaxIndexRootSize, + IndexRecordSize, + &NewLeftKey, + &NewChild); if (!NT_SUCCESS(Status)) { DPRINT1("ERROR: Failed to insert key.\n"); @@ -1526,6 +1757,30 @@ NtfsInsertKey(PB_TREE Tree, 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 NDEBUG + } } else { @@ -1541,8 +1796,8 @@ NtfsInsertKey(PB_TREE Tree, Node->FirstKey = NewKey; else PreviousKey->NextKey = NewKey; - break; } + break; } PreviousKey = CurrentKey; @@ -1552,88 +1807,202 @@ NtfsInsertKey(PB_TREE Tree, // Determine how much space the index entries will need NodeSize = GetSizeOfIndexEntries(Node); - // Is Node the root node? - if (Node == Tree->RootNode) + // Is Node not the root node? + if (Node != Tree->RootNode) { - // Is the index root too large for the file record? - if (NodeSize > MaxIndexRootSize) - { - PB_TREE_FILENAME_NODE NewSubNode, NewIndexRoot; - PB_TREE_KEY DummyKey; + // Calculate maximum size of index entries without any headers + AllocatedNodeSize = IndexRecordSize - FIELD_OFFSET(INDEX_BUFFER, Header); - DPRINT1("Collapsing Index Root into sub-node.\n") ; - - DumpBTree(Tree); - - // 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 = Node->KeyCount; - NewSubNode->FirstKey = Node->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; - - DumpBTree(Tree); - - return STATUS_SUCCESS; - } - } - else - { - // TEMPTEMP: TODO: MATH - AllocatedNodeSize = 0xfe8; + // TODO: Replace magic with math MaxNodeSizeWithoutHeader = AllocatedNodeSize - 0x28; // Has the node grown larger than its allocated size? if (NodeSize > MaxNodeSizeWithoutHeader) { - DPRINT1("FIXME: Splitting a node is still a WIP!\n"); - //SplitBTreeNode(NULL, Node); - //DumpBTree(Tree); - return STATUS_NOT_IMPLEMENTED; + 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 i; + + DPRINT1("SplitBTreeNode(%p, %p, %p, %p, %s) called\n", + Tree, + Node, + MedianKey, + NewRightHandSibling, + CaseSensitive ? "TRUE" : "FALSE"); + + //DumpBTreeNode(Node, 0, 0); + + // 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 median key index + MedianKeyIndex = (Node->KeyCount + 1) / 2; + MedianKeyIndex--; + + // Find the last key before the median + LastKeyBeforeMedian = Node->FirstKey; + for (i = 0; i < MedianKeyIndex - 1; i++) + LastKeyBeforeMedian = LastKeyBeforeMedian->NextKey; + + // Use size to locate the median key / index + ULONG HalfSize = 2016; // half the allocated size after subtracting the first index entry offset (TODO: MATH) + ULONG SizeSum = 0; + LastKeyBeforeMedian = Node->FirstKey; + MedianKeyIndex = 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; + + ULONG 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/mft.c b/drivers/filesystems/ntfs/mft.c index 547ab64a2b1..c4328a0491a 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -786,6 +786,11 @@ SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, 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); @@ -2131,6 +2136,8 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, PB_TREE NewTree; ULONG BtreeIndexLength; ULONG MaxIndexRootSize; + PB_TREE_KEY NewLeftKey; + PB_TREE_FILENAME_NODE NewRightHandNode; // Allocate memory for the parent directory ParentFileRecord = ExAllocatePoolWithTag(NonPagedPool, @@ -2152,8 +2159,10 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, 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, @@ -2176,7 +2185,7 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, 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 - - FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header) // Subtract the length of the index 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? @@ -2233,10 +2242,20 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, 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); + 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"); @@ -2247,9 +2266,57 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, return Status; } +#ifndef NDEBUG DumpBTree(NewTree); +#endif - // Convert B*Tree back to Index, starting with the index allocation + // 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 + LARGE_INTEGER MinIndexRootSize; + 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)) { @@ -2261,8 +2328,99 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, 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 + ULONG 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 + ULONG 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, MaxIndexRootSize, &NewIndexRoot, &BtreeIndexLength); + Status = CreateIndexRootFromBTree(DeviceExt, NewTree, NewMaxIndexRootSize, &NewIndexRoot, &BtreeIndexLength); if (!NT_SUCCESS(Status)) { DPRINT1("ERROR: Failed to create Index root from B-Tree!\n"); @@ -2280,9 +2438,7 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, // 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 if we extend past the file record, - // we must create an index allocation and index bitmap (TODO). Also TODO: support file records with - // $ATTRIBUTE_LIST's. + // 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) @@ -2344,8 +2500,22 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, } else { - DPRINT1("Dumping new parent file record:\n"); +#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 diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index aba8b291969..5c709c61e90 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -564,6 +564,13 @@ 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); @@ -576,6 +583,13 @@ AddRun(PNTFS_VCB Vcb, 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, @@ -723,6 +737,9 @@ CreateIndexRootFromBTree(PDEVICE_EXTENSION DeviceExt, PINDEX_ROOT_ATTRIBUTE *IndexRoot, ULONG *Length); +NTSTATUS +DemoteBTreeRoot(PB_TREE Tree); + VOID DestroyBTree(PB_TREE Tree); @@ -752,13 +769,26 @@ GetAllocationOffsetFromVCN(PDEVICE_EXTENSION DeviceExt, ULONG IndexBufferSize, ULONGLONG Vcn); +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 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, @@ -772,8 +802,7 @@ UpdateIndexNode(PDEVICE_EXTENSION DeviceExt, PB_TREE_FILENAME_NODE Node, ULONG IndexBufferSize, PNTFS_ATTR_CONTEXT IndexAllocationContext, - ULONG IndexAllocationOffset, - PNTFS_ATTR_CONTEXT BitmapContext); + ULONG IndexAllocationOffset); /* close.c */ From a40ba448d401e53788db2356108130e81b597a6f Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Fri, 1 Sep 2017 00:27:34 +0000 Subject: [PATCH 69/71] [NTFS] - Fix some errors that break building in C89 mode, and remove an extraneous "ninja livecd" that got inserted in a comment. Thanks to Doug Lyons for spotting these errors. SplitBTree() - comment-out redundant code for finding the median key and improve comments. svn path=/branches/GSoC_2016/NTFS/; revision=75727 --- drivers/filesystems/ntfs/btree.c | 30 ++++++++++++++++++++++-------- drivers/filesystems/ntfs/mft.c | 10 ++++++---- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/drivers/filesystems/ntfs/btree.c b/drivers/filesystems/ntfs/btree.c index bb745c5e9e2..9fb1b29c0f5 100644 --- a/drivers/filesystems/ntfs/btree.c +++ b/drivers/filesystems/ntfs/btree.c @@ -1139,7 +1139,7 @@ DemoteBTreeRoot(PB_TREE Tree) #ifndef NDEBUG DumpBTree(Tree); -#endif; +#endif return STATUS_SUCCESS; } @@ -1779,7 +1779,7 @@ NtfsInsertKey(PB_TREE Tree, #ifndef NDEBUG DumpBTree(Tree); -#endif NDEBUG +#endif } } else @@ -1880,6 +1880,9 @@ SplitBTreeNode(PB_TREE Tree, { ULONG MedianKeyIndex; PB_TREE_KEY LastKeyBeforeMedian, FirstKeyAfterMedian; + ULONG KeyCount; + ULONG HalfSize; + ULONG SizeSum; ULONG i; DPRINT1("SplitBTreeNode(%p, %p, %p, %p, %s) called\n", @@ -1889,7 +1892,9 @@ SplitBTreeNode(PB_TREE Tree, NewRightHandSibling, CaseSensitive ? "TRUE" : "FALSE"); - //DumpBTreeNode(Node, 0, 0); +#ifndef NDEBUG + DumpBTreeNode(Node, 0, 0); +#endif // Create the right hand sibling *NewRightHandSibling = ExAllocatePoolWithTag(NonPagedPool, sizeof(B_TREE_FILENAME_NODE), TAG_NTFS); @@ -1901,20 +1906,29 @@ SplitBTreeNode(PB_TREE Tree, 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--; - // Find the last key before the median LastKeyBeforeMedian = Node->FirstKey; for (i = 0; i < MedianKeyIndex - 1; i++) - LastKeyBeforeMedian = LastKeyBeforeMedian->NextKey; + 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 - ULONG HalfSize = 2016; // half the allocated size after subtracting the first index entry offset (TODO: MATH) - ULONG SizeSum = 0; 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; @@ -1989,7 +2003,7 @@ SplitBTreeNode(PB_TREE Tree, // Update Node's KeyCount (remember to add 1 for the new dummy key) Node->KeyCount = MedianKeyIndex + 2; - ULONG KeyCount = CountBTreeKeys(Node->FirstKey); + KeyCount = CountBTreeKeys(Node->FirstKey); ASSERT(Node->KeyCount == KeyCount); // everything to the right of MedianKey becomes the right hand sibling of Node diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index c4328a0491a..9d96bad239a 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -2138,6 +2138,9 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, 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, @@ -2287,7 +2290,6 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, // 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 - LARGE_INTEGER MinIndexRootSize; 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); @@ -2335,7 +2337,7 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, // 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 - ULONG NewMaxIndexRootSize = + 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 @@ -2361,7 +2363,7 @@ NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, // 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 - ULONG NodeSize = GetSizeOfIndexEntries(NewTree->RootNode); + NodeSize = GetSizeOfIndexEntries(NewTree->RootNode); if (NodeSize > NewMaxIndexRootSize) { DPRINT1("Demoting index root.\nNodeSize: 0x%lx\nNewMaxIndexRootSize: 0x%lx\n", NodeSize, NewMaxIndexRootSize); @@ -2624,7 +2626,7 @@ CompareFileName(PUNICODE_STRING FileName, * @param Vcb * Pointer to an NTFS_VCB for the volume whose Mft mirror is being updated. * -* @returninja livecd +* @return * STATUS_SUCCESS on success. * STATUS_INSUFFICIENT_RESOURCES if an allocation failed. From 1ac7128da037b1a770ab1468e604fe44c8857ec3 Mon Sep 17 00:00:00 2001 From: Trevor Thompson Date: Sat, 9 Sep 2017 23:10:52 +0000 Subject: [PATCH 70/71] [NTFS] - Respect NTFS' file ordering when enumerating a directory. Split off part of BrowseIndexEntries() into a separate function to simplify the code. AddNewMftEntry() - Zero the (larger) buffer for the bitmap prior to reading the bitmap. BrowseIndexEntries() - Check sub-nodes before checking an index entry. Read and use the index bitmap when checking sub-nodes. +BrowseSubNodeIndexEntries() - Called for sub-nodes of an index when browsing index entries. +NtfsDumpData() - Diagnostic function which allows for visualizing a series of bytes. svn path=/branches/GSoC_2016/NTFS/; revision=75817 --- drivers/filesystems/ntfs/mft.c | 342 ++++++++++++++++++++++++++------ drivers/filesystems/ntfs/ntfs.h | 6 + 2 files changed, 285 insertions(+), 63 deletions(-) diff --git a/drivers/filesystems/ntfs/mft.c b/drivers/filesystems/ntfs/mft.c index 9d96bad239a..4896d6d5500 100644 --- a/drivers/filesystems/ntfs/mft.c +++ b/drivers/filesystems/ntfs/mft.c @@ -1988,6 +1988,7 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, 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)); @@ -2765,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, @@ -2780,11 +2929,12 @@ BrowseIndexEntries(PDEVICE_EXTENSION Vcb, 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, %lu, %p, %p, %wZ, %lu, %lu, %s, %s, %p)\n", Vcb, @@ -2800,10 +2950,100 @@ BrowseIndexEntries(PDEVICE_EXTENSION Vcb, 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)) { + 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 && @@ -2811,71 +3051,29 @@ BrowseIndexEntries(PDEVICE_EXTENSION Vcb, { *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, NULL); - if (!NT_SUCCESS(Status)) - { - DPRINT1("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 = BrowseIndexEntries(NULL, - NULL, - NULL, - 0, - FirstEntry, - LastEntry, - FileName, - StartEntry, - CurrentEntry, - DirSearch, - CaseSensitive, - OutMFTIndex); - if (NT_SUCCESS(Status)) - { - break; - } - } - - ReleaseAttributeContext(IndexAllocationCtx); - return Status; + return STATUS_OBJECT_PATH_NOT_FOUND; } NTSTATUS @@ -2946,7 +3144,7 @@ NtfsFindMftRecord(PDEVICE_EXTENSION Vcb, Status = BrowseIndexEntries(Vcb, MftRecord, - IndexRecord, + (PINDEX_ROOT_ATTRIBUTE)IndexRecord, IndexRoot->SizeOfEntry, IndexEntry, IndexEntryEnd, @@ -3031,6 +3229,24 @@ NtfsLookupFile(PDEVICE_EXTENSION Vcb, 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 diff --git a/drivers/filesystems/ntfs/ntfs.h b/drivers/filesystems/ntfs/ntfs.h index 5c709c61e90..97f4acd1d63 100644 --- a/drivers/filesystems/ntfs/ntfs.h +++ b/drivers/filesystems/ntfs/ntfs.h @@ -769,6 +769,9 @@ GetAllocationOffsetFromVCN(PDEVICE_EXTENSION DeviceExt, ULONG IndexBufferSize, ULONGLONG Vcn); +ULONGLONG +GetIndexEntryVCN(PINDEX_ENTRY_ATTRIBUTE IndexEntry); + ULONG GetSizeOfIndexEntries(PB_TREE_FILENAME_NODE Node); @@ -1002,6 +1005,9 @@ AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, PULONGLONG DestinationIndex, BOOLEAN CanWait); +VOID +NtfsDumpData(ULONG_PTR Buffer, ULONG Length); + PNTFS_ATTR_CONTEXT PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord); From b5555650a83d3aec7c002f2215c0759c4efac832 Mon Sep 17 00:00:00 2001 From: Pierre Schweitzer Date: Sun, 10 Dec 2017 11:25:48 +0100 Subject: [PATCH 71/71] [NTFS] Fix the FileNamesInformation/FileNameInformation class confusion that was spotted on FastFAT. What should also be fixed is the partial return of data on first entry enumeration. --- drivers/filesystems/ntfs/dirctl.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/filesystems/ntfs/dirctl.c b/drivers/filesystems/ntfs/dirctl.c index 61316cb77d9..ab784fce3d5 100644 --- a/drivers/filesystems/ntfs/dirctl.c +++ b/drivers/filesystems/ntfs/dirctl.c @@ -61,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) @@ -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: