https://git.reactos.org/?p=reactos.git;a=commitdiff;h=58a13831ef75324ed2b31…
commit 58a13831ef75324ed2b3101c1c98ecffeabe942b
Author: Trevor Thompson <tmt256(a)email.vccs.edu>
AuthorDate: Sun May 1 16:21:53 2016 +0000
[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 6c90131b19..1ae67e52d7 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 4a4a6a6a8d..5662806880 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 d02a32dc23..43b38ef2ab 100644
--- a/drivers/filesystems/ntfs/mft.c
+++ b/drivers/filesystems/ntfs/mft.c
@@ -24,6 +24,7 @@
* Valentin Verkhovsky
* Pierre Schweitzer (pierre(a)reactos.org)
* Hervé Poussineau (hpoussin(a)reactos.org)
+ * Trevor Thompson
*/
/* INCLUDES *****************************************************************/
@@ -31,6 +32,7 @@
#include "ntfs.h"
#define NDEBUG
+#undef NDEBUG
#include <debug.h>
/* 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 ba461b04a8..76b77134c9 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 9e987c923f..f80bb973fe 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 0437b61d99..076a3e8578 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(a)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;
+
+ 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);
- IrpContext->Irp->IoStatus.Information = 0;
- return STATUS_NOT_SUPPORTED;
+ 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 */