[NTFS]
authorTrevor Thompson <tmt256@email.vccs.edu>
Sun, 1 May 2016 16:21:53 +0000 (16:21 +0000)
committerThomas Faber <thomas.faber@reactos.org>
Sun, 10 Dec 2017 10:13:19 +0000 (11:13 +0100)
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
drivers/filesystems/ntfs/create.c
drivers/filesystems/ntfs/mft.c
drivers/filesystems/ntfs/misc.c
drivers/filesystems/ntfs/ntfs.h
drivers/filesystems/ntfs/rw.c

index 6c90131..1ae67e5 100644 (file)
@@ -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,
index 4a4a6a6..5662806 100644 (file)
@@ -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;
         }
     }
 
index d02a32d..43b38ef 100644 (file)
@@ -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 <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,
index ba461b0..76b7713 100644 (file)
@@ -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 */
index 9e987c9..f80bb97 100644 (file)
@@ -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);
index 0437b61..076a3e8 100644 (file)
@@ -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;
+
+    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 */