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 */