reactos/drivers/filesystems/ntfs/rw.c

728 lines
23 KiB
C
Raw Normal View History

/*
* ReactOS kernel
* Copyright (C) 2002, 2014 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/rw.c
* PURPOSE: NTFS filesystem driver
* PROGRAMMERS: Art Yerkes
* Pierre Schweitzer (pierre@reactos.org)
* Trevor Thompson
*/
/* INCLUDES *****************************************************************/
#include <ntddk.h>
#include "ntfs.h"
#define NDEBUG
#include <debug.h>
/* FUNCTIONS ****************************************************************/
/*
* FUNCTION: Reads data from a file
*/
static
NTSTATUS
NtfsReadFile(PDEVICE_EXTENSION DeviceExt,
PFILE_OBJECT FileObject,
PUCHAR Buffer,
ULONG Length,
ULONG ReadOffset,
ULONG IrpFlags,
PULONG LengthRead)
{
NTSTATUS Status = STATUS_SUCCESS;
PNTFS_FCB Fcb;
PFILE_RECORD_HEADER FileRecord;
PNTFS_ATTR_CONTEXT DataContext;
ULONG RealLength;
ULONG RealReadOffset;
ULONG RealLengthRead;
ULONG ToRead;
BOOLEAN AllocatedBuffer = FALSE;
PCHAR ReadBuffer = (PCHAR)Buffer;
ULONGLONG StreamSize;
DPRINT("NtfsReadFile(%p, %p, %p, %lu, %lu, %lx, %p)\n", DeviceExt, FileObject, Buffer, Length, ReadOffset, IrpFlags, LengthRead);
*LengthRead = 0;
if (Length == 0)
{
DPRINT1("Null read!\n");
return STATUS_SUCCESS;
}
Fcb = (PNTFS_FCB)FileObject->FsContext;
if (NtfsFCBIsCompressed(Fcb))
{
DPRINT1("Compressed file!\n");
UNIMPLEMENTED;
return STATUS_NOT_IMPLEMENTED;
}
2021-08-04 06:03:39 +00:00
if (NtfsFCBIsEncrypted(Fcb))
{
DPRINT1("Encrypted file!\n");
UNIMPLEMENTED;
return STATUS_NOT_IMPLEMENTED;
}
FileRecord = ExAllocateFromNPagedLookasideList(&DeviceExt->FileRecLookasideList);
if (FileRecord == NULL)
{
DPRINT1("Not enough memory!\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
Status = ReadFileRecord(DeviceExt, Fcb->MFTIndex, FileRecord);
if (!NT_SUCCESS(Status))
{
DPRINT1("Can't find record!\n");
ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, FileRecord);
return Status;
}
Status = FindAttribute(DeviceExt, FileRecord, AttributeData, Fcb->Stream, wcslen(Fcb->Stream), &DataContext, NULL);
if (!NT_SUCCESS(Status))
{
[NTFS] Totally rewrite the way MFT records attributes are handled. Up to now, we were having really similar loops, only looking at the resident part of the attribute list, not really caring about how the loop was going. This was leading to some issues: - In case the attribute we were looking for was stored in the non-resident part of the attribute list, we would miss it (excepted in the case of FindAttribute() which was properly browsing the whole attribute list). - In the specific case of FindAttribute(), one would have been able to setup a broken MFT record with the resident attribute list pointing on the non resident attribute list which itself would point to the resident attribute list. In such case, the driver would loop forever caught on the loop, allocating tones of memory. It was possible to trigger this by user space, from a non-privileged user, just by browsing the right directory entry. - In the case of the other loops (non FindAttribute()), another issue (other than missing attributes) was present, one would have been able to setup a broken MFT record with an attribute of null-length. This would have caused the driver to loop forever on the attribute list. This could be triggered from usermode too. And could be triggered by a non-privileged user. This commit introduces a new set of functions for attributes browsing: FindFirstAttribute(), FindNextAttribute(), FindCloseAttribute(). It allows safely browsing attributes and handles broken cases. It also performs reading of the attribute list when present and makes sure there's only one read. This method should be the only one to use to browse the attributes. The whole NTFS code base has been converted to use this newly set of functions. This really simplifies the implementation of FindAttribute(), and prevent unsafe code duplication. CORE-10037 #resolve #comment Fixed with r68829 svn path=/trunk/; revision=68829
2015-08-26 18:20:04 +00:00
NTSTATUS BrowseStatus;
FIND_ATTR_CONTXT Context;
PNTFS_ATTR_RECORD Attribute;
DPRINT1("No '%S' data stream associated with file!\n", Fcb->Stream);
[NTFS] Totally rewrite the way MFT records attributes are handled. Up to now, we were having really similar loops, only looking at the resident part of the attribute list, not really caring about how the loop was going. This was leading to some issues: - In case the attribute we were looking for was stored in the non-resident part of the attribute list, we would miss it (excepted in the case of FindAttribute() which was properly browsing the whole attribute list). - In the specific case of FindAttribute(), one would have been able to setup a broken MFT record with the resident attribute list pointing on the non resident attribute list which itself would point to the resident attribute list. In such case, the driver would loop forever caught on the loop, allocating tones of memory. It was possible to trigger this by user space, from a non-privileged user, just by browsing the right directory entry. - In the case of the other loops (non FindAttribute()), another issue (other than missing attributes) was present, one would have been able to setup a broken MFT record with an attribute of null-length. This would have caused the driver to loop forever on the attribute list. This could be triggered from usermode too. And could be triggered by a non-privileged user. This commit introduces a new set of functions for attributes browsing: FindFirstAttribute(), FindNextAttribute(), FindCloseAttribute(). It allows safely browsing attributes and handles broken cases. It also performs reading of the attribute list when present and makes sure there's only one read. This method should be the only one to use to browse the attributes. The whole NTFS code base has been converted to use this newly set of functions. This really simplifies the implementation of FindAttribute(), and prevent unsafe code duplication. CORE-10037 #resolve #comment Fixed with r68829 svn path=/trunk/; revision=68829
2015-08-26 18:20:04 +00:00
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);
}
[NTFS] Totally rewrite the way MFT records attributes are handled. Up to now, we were having really similar loops, only looking at the resident part of the attribute list, not really caring about how the loop was going. This was leading to some issues: - In case the attribute we were looking for was stored in the non-resident part of the attribute list, we would miss it (excepted in the case of FindAttribute() which was properly browsing the whole attribute list). - In the specific case of FindAttribute(), one would have been able to setup a broken MFT record with the resident attribute list pointing on the non resident attribute list which itself would point to the resident attribute list. In such case, the driver would loop forever caught on the loop, allocating tones of memory. It was possible to trigger this by user space, from a non-privileged user, just by browsing the right directory entry. - In the case of the other loops (non FindAttribute()), another issue (other than missing attributes) was present, one would have been able to setup a broken MFT record with an attribute of null-length. This would have caused the driver to loop forever on the attribute list. This could be triggered from usermode too. And could be triggered by a non-privileged user. This commit introduces a new set of functions for attributes browsing: FindFirstAttribute(), FindNextAttribute(), FindCloseAttribute(). It allows safely browsing attributes and handles broken cases. It also performs reading of the attribute list when present and makes sure there's only one read. This method should be the only one to use to browse the attributes. The whole NTFS code base has been converted to use this newly set of functions. This really simplifies the implementation of FindAttribute(), and prevent unsafe code duplication. CORE-10037 #resolve #comment Fixed with r68829 svn path=/trunk/; revision=68829
2015-08-26 18:20:04 +00:00
BrowseStatus = FindNextAttribute(&Context, &Attribute);
}
[NTFS] Totally rewrite the way MFT records attributes are handled. Up to now, we were having really similar loops, only looking at the resident part of the attribute list, not really caring about how the loop was going. This was leading to some issues: - In case the attribute we were looking for was stored in the non-resident part of the attribute list, we would miss it (excepted in the case of FindAttribute() which was properly browsing the whole attribute list). - In the specific case of FindAttribute(), one would have been able to setup a broken MFT record with the resident attribute list pointing on the non resident attribute list which itself would point to the resident attribute list. In such case, the driver would loop forever caught on the loop, allocating tones of memory. It was possible to trigger this by user space, from a non-privileged user, just by browsing the right directory entry. - In the case of the other loops (non FindAttribute()), another issue (other than missing attributes) was present, one would have been able to setup a broken MFT record with an attribute of null-length. This would have caused the driver to loop forever on the attribute list. This could be triggered from usermode too. And could be triggered by a non-privileged user. This commit introduces a new set of functions for attributes browsing: FindFirstAttribute(), FindNextAttribute(), FindCloseAttribute(). It allows safely browsing attributes and handles broken cases. It also performs reading of the attribute list when present and makes sure there's only one read. This method should be the only one to use to browse the attributes. The whole NTFS code base has been converted to use this newly set of functions. This really simplifies the implementation of FindAttribute(), and prevent unsafe code duplication. CORE-10037 #resolve #comment Fixed with r68829 svn path=/trunk/; revision=68829
2015-08-26 18:20:04 +00:00
FindCloseAttribute(&Context);
ReleaseAttributeContext(DataContext);
ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, FileRecord);
return Status;
}
StreamSize = AttributeDataLength(DataContext->pRecord);
if (ReadOffset >= StreamSize)
{
DPRINT1("Reading beyond stream end!\n");
ReleaseAttributeContext(DataContext);
ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, FileRecord);
return STATUS_END_OF_FILE;
}
ToRead = Length;
if (ReadOffset + Length > StreamSize)
ToRead = StreamSize - ReadOffset;
RealReadOffset = ReadOffset;
RealLength = ToRead;
if ((ReadOffset % DeviceExt->NtfsInfo.BytesPerSector) != 0 || (ToRead % DeviceExt->NtfsInfo.BytesPerSector) != 0)
{
RealReadOffset = ROUND_DOWN(ReadOffset, DeviceExt->NtfsInfo.BytesPerSector);
RealLength = ROUND_UP(ToRead, DeviceExt->NtfsInfo.BytesPerSector);
/* do we need to extend RealLength by one sector? */
if (RealLength + RealReadOffset < ReadOffset + Length)
{
if (RealReadOffset + RealLength + DeviceExt->NtfsInfo.BytesPerSector <= AttributeAllocatedLength(DataContext->pRecord))
RealLength += DeviceExt->NtfsInfo.BytesPerSector;
}
ReadBuffer = ExAllocatePoolWithTag(NonPagedPool, RealLength, TAG_NTFS);
if (ReadBuffer == NULL)
{
DPRINT1("Not enough memory!\n");
ReleaseAttributeContext(DataContext);
ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, FileRecord);
return STATUS_INSUFFICIENT_RESOURCES;
}
AllocatedBuffer = TRUE;
}
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)
{
DPRINT1("Read failure!\n");
ReleaseAttributeContext(DataContext);
ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, FileRecord);
if (AllocatedBuffer)
{
ExFreePoolWithTag(ReadBuffer, TAG_NTFS);
}
return Status;
}
ReleaseAttributeContext(DataContext);
ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, FileRecord);
*LengthRead = ToRead;
DPRINT("%lu got read\n", *LengthRead);
if (AllocatedBuffer)
{
RtlCopyMemory(Buffer, ReadBuffer + (ReadOffset - RealReadOffset), ToRead);
}
if (ToRead != Length)
{
RtlZeroMemory(Buffer + ToRead, Length - ToRead);
}
if (AllocatedBuffer)
{
ExFreePoolWithTag(ReadBuffer, TAG_NTFS);
}
return STATUS_SUCCESS;
}
NTSTATUS
NtfsRead(PNTFS_IRP_CONTEXT IrpContext)
{
PDEVICE_EXTENSION DeviceExt;
PIO_STACK_LOCATION Stack;
PFILE_OBJECT FileObject;
PVOID Buffer;
ULONG ReadLength;
LARGE_INTEGER ReadOffset;
ULONG ReturnedReadLength = 0;
NTSTATUS Status = STATUS_SUCCESS;
PIRP Irp;
PDEVICE_OBJECT DeviceObject;
DPRINT("NtfsRead(IrpContext %p)\n", IrpContext);
DeviceObject = IrpContext->DeviceObject;
Irp = IrpContext->Irp;
Stack = IrpContext->Stack;
FileObject = IrpContext->FileObject;
DeviceExt = DeviceObject->DeviceExtension;
ReadLength = Stack->Parameters.Read.Length;
ReadOffset = Stack->Parameters.Read.ByteOffset;
Buffer = NtfsGetUserBuffer(Irp, BooleanFlagOn(Irp->Flags, IRP_PAGING_IO));
Status = NtfsReadFile(DeviceExt,
FileObject,
Buffer,
ReadLength,
ReadOffset.u.LowPart,
Irp->Flags,
&ReturnedReadLength);
if (NT_SUCCESS(Status))
{
if (FileObject->Flags & FO_SYNCHRONOUS_IO)
{
FileObject->CurrentByteOffset.QuadPart =
ReadOffset.QuadPart + ReturnedReadLength;
}
Irp->IoStatus.Information = ReturnedReadLength;
}
else
{
Irp->IoStatus.Information = 0;
}
return Status;
}
/**
* @name NtfsWriteFile
* @implemented
*
* Writes a file to the disk. It presently borrows a lot of code from NtfsReadFile() and
* VFatWriteFileData(). It needs some more work before it will be complete; it won't handle
* page files, asnyc io, cached writes, etc.
*
* @param DeviceExt
* Points to the target disk's DEVICE_EXTENSION
*
* @param FileObject
* Pointer to a FILE_OBJECT describing the target file
*
* @param Buffer
* The data that's being written to the file
*
* @Param Length
* The size of the data buffer being written, in bytes
*
* @param WriteOffset
* Offset, in bytes, from the beginning of the file. Indicates where to start
* writing data.
*
* @param IrpFlags
* TODO: flags are presently ignored in code.
*
* @param CaseSensitive
* Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
* if an application opened the file with the FILE_FLAG_POSIX_SEMANTICS flag.
*
* @param LengthWritten
* Pointer to a ULONG. This ULONG will be set to the number of bytes successfully written.
*
* @return
* STATUS_SUCCESS if successful, STATUS_NOT_IMPLEMENTED if a required feature isn't implemented,
* STATUS_INSUFFICIENT_RESOURCES if an allocation failed, STATUS_ACCESS_DENIED if the write itself fails,
* STATUS_PARTIAL_COPY or STATUS_UNSUCCESSFUL if ReadFileRecord() fails, or
* STATUS_OBJECT_NAME_NOT_FOUND if the file's data stream could not be found.
*
* @remarks Called by NtfsWrite(). It may perform a read-modify-write operation if the requested write is
* not sector-aligned. LengthWritten only refers to how much of the requested data has been written;
* extra data that needs to be written to make the write sector-aligned will not affect it.
*
*/
NTSTATUS NtfsWriteFile(PDEVICE_EXTENSION DeviceExt,
PFILE_OBJECT FileObject,
const PUCHAR Buffer,
ULONG Length,
ULONG WriteOffset,
ULONG IrpFlags,
BOOLEAN CaseSensitive,
PULONG LengthWritten)
{
NTSTATUS Status = STATUS_NOT_IMPLEMENTED;
PNTFS_FCB Fcb;
PFILE_RECORD_HEADER FileRecord;
PNTFS_ATTR_CONTEXT DataContext;
ULONG AttributeOffset;
ULONGLONG StreamSize;
DPRINT("NtfsWriteFile(%p, %p, %p, %lu, %lu, %x, %s, %p)\n",
DeviceExt,
FileObject,
Buffer,
Length,
WriteOffset,
IrpFlags,
(CaseSensitive ? "TRUE" : "FALSE"),
LengthWritten);
*LengthWritten = 0;
ASSERT(DeviceExt);
if (Length == 0)
{
if (Buffer == NULL)
return STATUS_SUCCESS;
else
return STATUS_INVALID_PARAMETER;
}
// get the File control block
Fcb = (PNTFS_FCB)FileObject->FsContext;
ASSERT(Fcb);
DPRINT("Fcb->PathName: %wS\n", Fcb->PathName);
DPRINT("Fcb->ObjectName: %wS\n", Fcb->ObjectName);
// we don't yet handle compression
if (NtfsFCBIsCompressed(Fcb))
{
DPRINT("Compressed file!\n");
UNIMPLEMENTED;
return STATUS_NOT_IMPLEMENTED;
}
// allocate non-paged memory for the FILE_RECORD_HEADER
FileRecord = ExAllocateFromNPagedLookasideList(&DeviceExt->FileRecLookasideList);
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);
ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, FileRecord);
return Status;
}
DPRINT("Found record for %wS\n", Fcb->ObjectName);
// Find the attribute with the data stream for our file
DPRINT("Finding Data Attribute...\n");
Status = FindAttribute(DeviceExt, FileRecord, AttributeData, Fcb->Stream, wcslen(Fcb->Stream), &DataContext,
&AttributeOffset);
// Did we fail to find the attribute?
if (!NT_SUCCESS(Status))
{
NTSTATUS BrowseStatus;
FIND_ATTR_CONTXT Context;
PNTFS_ATTR_RECORD Attribute;
DPRINT1("No '%S' data stream associated with file!\n", Fcb->Stream);
// Couldn't find the requested data stream; print a list of streams available
BrowseStatus = FindFirstAttribute(&Context, DeviceExt, FileRecord, FALSE, &Attribute);
while (NT_SUCCESS(BrowseStatus))
{
if (Attribute->Type == AttributeData)
{
UNICODE_STRING Name;
Name.Length = Attribute->NameLength * sizeof(WCHAR);
Name.MaximumLength = Name.Length;
Name.Buffer = (PWCHAR)((ULONG_PTR)Attribute + Attribute->NameOffset);
DPRINT1("Data stream: '%wZ' available\n", &Name);
}
BrowseStatus = FindNextAttribute(&Context, &Attribute);
}
FindCloseAttribute(&Context);
ReleaseAttributeContext(DataContext);
ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, FileRecord);
return Status;
}
// Get the size of the stream on disk
StreamSize = AttributeDataLength(DataContext->pRecord);
DPRINT("WriteOffset: %lu\tStreamSize: %I64u\n", WriteOffset, StreamSize);
// Are we trying to write beyond the end of the stream?
if (WriteOffset + Length > StreamSize)
{
// is increasing the stream size allowed?
if (!(Fcb->Flags & FCB_IS_VOLUME) &&
!(IrpFlags & IRP_PAGING_IO))
{
LARGE_INTEGER DataSize;
ULONGLONG AllocationSize;
PFILENAME_ATTRIBUTE fileNameAttribute;
ULONGLONG ParentMFTId;
UNICODE_STRING filename;
DataSize.QuadPart = WriteOffset + Length;
// set the attribute data length
Status = SetAttributeDataLength(FileObject, Fcb, DataContext, AttributeOffset, FileRecord, &DataSize);
if (!NT_SUCCESS(Status))
{
ReleaseAttributeContext(DataContext);
ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, FileRecord);
*LengthWritten = 0;
return Status;
}
AllocationSize = AttributeAllocatedLength(DataContext->pRecord);
// now we need to update this file's size in every directory index entry that references it
// TODO: put this code in its own function and adapt it to work with every filename / hardlink
// stored in the file record.
fileNameAttribute = GetBestFileNameFromRecord(Fcb->Vcb, FileRecord);
ASSERT(fileNameAttribute);
ParentMFTId = fileNameAttribute->DirectoryFileReferenceNumber & NTFS_MFT_MASK;
filename.Buffer = fileNameAttribute->Name;
filename.Length = fileNameAttribute->NameLength * sizeof(WCHAR);
filename.MaximumLength = filename.Length;
Status = UpdateFileNameRecord(Fcb->Vcb,
ParentMFTId,
&filename,
FALSE,
DataSize.QuadPart,
AllocationSize,
CaseSensitive);
}
else
{
// TODO - just fail for now
ReleaseAttributeContext(DataContext);
ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, FileRecord);
*LengthWritten = 0;
return STATUS_ACCESS_DENIED;
}
}
DPRINT("Length: %lu\tWriteOffset: %lu\tStreamSize: %I64u\n", Length, WriteOffset, StreamSize);
// Write the data to the attribute
Status = WriteAttribute(DeviceExt, DataContext, WriteOffset, Buffer, Length, LengthWritten, FileRecord);
// Did the write fail?
if (!NT_SUCCESS(Status))
{
DPRINT1("Write failure!\n");
ReleaseAttributeContext(DataContext);
ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, FileRecord);
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);
ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, FileRecord);
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)
{
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;
DPRINT("NtfsWrite(IrpContext %p)\n", IrpContext);
ASSERT(IrpContext);
// get the I/O request packet
Irp = IrpContext->Irp;
// 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 File control block
Fcb = (PNTFS_FCB)IrpContext->FileObject->FsContext;
ASSERT(Fcb);
DPRINT("About to write %wS\n", Fcb->ObjectName);
DPRINT("NTFS Version: %d.%d\n", Fcb->Vcb->NtfsInfo.MajorVersion, Fcb->Vcb->NtfsInfo.MinorVersion);
// setup some more locals
FileObject = IrpContext->FileObject;
DeviceObject = IrpContext->DeviceObject;
DeviceExt = DeviceObject->DeviceExtension;
BytesPerSector = DeviceExt->StorageDevice->SectorSize;
Length = IrpContext->Stack->Parameters.Write.Length;
// get the file offset we'll be writing to
ByteOffset = IrpContext->Stack->Parameters.Write.ByteOffset;
if (ByteOffset.u.LowPart == FILE_WRITE_TO_END_OF_FILE &&
ByteOffset.u.HighPart == -1)
{
ByteOffset.QuadPart = Fcb->RFCB.FileSize.QuadPart;
}
DPRINT("ByteOffset: %I64u\tLength: %lu\tBytes per sector: %lu\n", ByteOffset.QuadPart,
Length, BytesPerSector);
if (ByteOffset.u.HighPart && !(Fcb->Flags & FCB_IS_VOLUME))
{
// TODO: Support large files
DPRINT1("FIXME: Writing to large files is not yet supported at this time.\n");
return STATUS_INVALID_PARAMETER;
}
// Is this a non-cached write? A non-buffered write?
if (IrpContext->Irp->Flags & (IRP_PAGING_IO | IRP_NOCACHE) || (Fcb->Flags & FCB_IS_VOLUME) ||
IrpContext->FileObject->Flags & FILE_NO_INTERMEDIATE_BUFFERING)
{
// non-cached and non-buffered writes must be sector aligned
if (ByteOffset.u.LowPart % BytesPerSector != 0 || Length % BytesPerSector != 0)
{
DPRINT1("Non-cached writes and non-buffered writes must be sector aligned!\n");
return STATUS_INVALID_PARAMETER;
}
}
if (Length == 0)
{
DPRINT1("Null write!\n");
IrpContext->Irp->IoStatus.Information = 0;
// FIXME: Doesn't accurately detect when a user passes NULL to WriteFile() for the buffer
if (Irp->UserBuffer == NULL && Irp->MdlAddress == NULL)
{
// FIXME: Update last write time
return STATUS_SUCCESS;
}
return STATUS_INVALID_PARAMETER;
}
// get the Resource
if (Fcb->Flags & FCB_IS_VOLUME)
{
Resource = &DeviceExt->DirResource;
}
else if (IrpContext->Irp->Flags & IRP_PAGING_IO)
{
Resource = &Fcb->PagingIoResource;
}
else
{
Resource = &Fcb->MainResource;
}
// acquire exclusive access to the Resource
if (!ExAcquireResourceExclusiveLite(Resource, BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT)))
{
return STATUS_CANT_WAIT;
}
/* From VfatWrite(). Todo: Handle file locks
if (!(IrpContext->Irp->Flags & IRP_PAGING_IO) &&
FsRtlAreThereCurrentFileLocks(&Fcb->FileLock))
{
if (!FsRtlCheckLockForWriteAccess(&Fcb->FileLock, IrpContext->Irp))
{
Status = STATUS_FILE_LOCK_CONFLICT;
goto ByeBye;
}
}*/
// Is this an async request to a file?
if (!(IrpContext->Flags & IRPCONTEXT_CANWAIT) && !(Fcb->Flags & FCB_IS_VOLUME))
{
DPRINT1("FIXME: Async writes not supported in NTFS!\n");
ExReleaseResourceLite(Resource);
return STATUS_NOT_IMPLEMENTED;
}
// get the buffer of data the user is trying to write
Buffer = NtfsGetUserBuffer(Irp, BooleanFlagOn(Irp->Flags, IRP_PAGING_IO));
ASSERT(Buffer);
// lock the buffer
Status = NtfsLockUserBuffer(Irp, Length, IoReadAccess);
// were we unable to lock the buffer?
if (!NT_SUCCESS(Status))
{
DPRINT1("Unable to lock user buffer!\n");
ExReleaseResourceLite(Resource);
return Status;
}
DPRINT("Existing File Size(Fcb->RFCB.FileSize.QuadPart): %I64u\n", Fcb->RFCB.FileSize.QuadPart);
DPRINT("About to write the data. Length: %lu\n", Length);
// TODO: handle HighPart of ByteOffset (large files)
// write the file
Status = NtfsWriteFile(DeviceExt,
FileObject,
Buffer,
Length,
ByteOffset.LowPart,
Irp->Flags,
BooleanFlagOn(IrpContext->Stack->Flags, SL_CASE_SENSITIVE),
&ReturnedWriteLength);
IrpContext->Irp->IoStatus.Status = Status;
// was the write successful?
if (NT_SUCCESS(Status))
{
// TODO: Update timestamps
if (FileObject->Flags & FO_SYNCHRONOUS_IO)
{
// advance the file pointer
FileObject->CurrentByteOffset.QuadPart = ByteOffset.QuadPart + ReturnedWriteLength;
}
IrpContext->PriorityBoost = IO_DISK_INCREMENT;
}
else
{
DPRINT1("Write not Succesful!\tReturned length: %lu\n", ReturnedWriteLength);
}
Irp->IoStatus.Information = ReturnedWriteLength;
// Note: We leave the user buffer that we locked alone, it's up to the I/O manager to unlock and free it
ExReleaseResourceLite(Resource);
return Status;
}
/* EOF */