Added minimal write support from CORE-10998 along with updates as suggested by CR-90.

svn path=/branches/GSoC_2016/NTFS/; revision=71224
This commit is contained in:
Trevor Thompson 2016-05-01 16:21:53 +00:00 committed by Thomas Faber
parent 950c86fb4c
commit 58a13831ef
6 changed files with 931 additions and 9 deletions

View file

@ -20,7 +20,8 @@
* PROJECT: ReactOS kernel * PROJECT: ReactOS kernel
* FILE: drivers/filesystem/ntfs/blockdev.c * FILE: drivers/filesystem/ntfs/blockdev.c
* PURPOSE: NTFS filesystem driver * PURPOSE: NTFS filesystem driver
* PROGRAMMER: Eric Kohl * PROGRAMMERS: Eric Kohl
* Trevor Thompson
*/ */
/* INCLUDES *****************************************************************/ /* INCLUDES *****************************************************************/
@ -129,6 +130,180 @@ NtfsReadDisk(IN PDEVICE_OBJECT DeviceObject,
return Status; 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 NTSTATUS
NtfsReadSectors(IN PDEVICE_OBJECT DeviceObject, NtfsReadSectors(IN PDEVICE_OBJECT DeviceObject,
IN ULONG DiskSector, IN ULONG DiskSector,

View file

@ -476,26 +476,26 @@ NtfsCreateFile(PDEVICE_OBJECT DeviceObject,
return Status; return Status;
} }
/* HUGLY HACK: remain RO so far... */ /* HUGLY HACK: Can't overwrite or supersede a file yet... */
if (RequestedDisposition == FILE_OVERWRITE || if (RequestedDisposition == FILE_OVERWRITE ||
RequestedDisposition == FILE_OVERWRITE_IF || RequestedDisposition == FILE_OVERWRITE_IF ||
RequestedDisposition == FILE_SUPERSEDE) 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); NtfsCloseFile(DeviceExt, FileObject);
return STATUS_ACCESS_DENIED; return STATUS_ACCESS_DENIED;
} }
} }
else else
{ {
/* HUGLY HACK: remain RO so far... */ /* HUGLY HACK: Can't create new files yet... */
if (RequestedDisposition == FILE_CREATE || if (RequestedDisposition == FILE_CREATE ||
RequestedDisposition == FILE_OPEN_IF || RequestedDisposition == FILE_OPEN_IF ||
RequestedDisposition == FILE_OVERWRITE_IF || RequestedDisposition == FILE_OVERWRITE_IF ||
RequestedDisposition == FILE_SUPERSEDE) RequestedDisposition == FILE_SUPERSEDE)
{ {
DPRINT1("Denying write request on NTFS volume\n"); DPRINT1("Denying file creation request on NTFS volume\n");
return STATUS_ACCESS_DENIED; return STATUS_CANNOT_MAKE;
} }
} }

View file

@ -24,6 +24,7 @@
* Valentin Verkhovsky * Valentin Verkhovsky
* Pierre Schweitzer (pierre@reactos.org) * Pierre Schweitzer (pierre@reactos.org)
* Hervé Poussineau (hpoussin@reactos.org) * Hervé Poussineau (hpoussin@reactos.org)
* Trevor Thompson
*/ */
/* INCLUDES *****************************************************************/ /* INCLUDES *****************************************************************/
@ -31,6 +32,7 @@
#include "ntfs.h" #include "ntfs.h"
#define NDEBUG #define NDEBUG
#undef NDEBUG
#include <debug.h> #include <debug.h>
/* FUNCTIONS ****************************************************************/ /* 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 NTSTATUS
ReadFileRecord(PDEVICE_EXTENSION Vcb, ReadFileRecord(PDEVICE_EXTENSION Vcb,
ULONGLONG index, ULONGLONG index,

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

View file

@ -550,6 +550,13 @@ NtfsReadDisk(IN PDEVICE_OBJECT DeviceObject,
IN OUT PUCHAR Buffer, IN OUT PUCHAR Buffer,
IN BOOLEAN Override); IN BOOLEAN Override);
NTSTATUS
NtfsWriteDisk(IN PDEVICE_OBJECT DeviceObject,
IN LONGLONG StartingOffset,
IN ULONG Length,
IN ULONG SectorSize,
IN PUCHAR Buffer);
NTSTATUS NTSTATUS
NtfsReadSectors(IN PDEVICE_OBJECT DeviceObject, NtfsReadSectors(IN PDEVICE_OBJECT DeviceObject,
IN ULONG DiskSector, IN ULONG DiskSector,
@ -741,6 +748,14 @@ ReadAttribute(PDEVICE_EXTENSION Vcb,
PCHAR Buffer, PCHAR Buffer,
ULONG Length); ULONG Length);
NTSTATUS
WriteAttribute(PDEVICE_EXTENSION Vcb,
PNTFS_ATTR_CONTEXT Context,
ULONGLONG Offset,
const PUCHAR Buffer,
ULONG Length,
PULONG LengthWritten);
ULONGLONG ULONGLONG
AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord); AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord);
@ -817,6 +832,21 @@ PVOID
NtfsGetUserBuffer(PIRP Irp, NtfsGetUserBuffer(PIRP Irp,
BOOLEAN Paging); 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 VOID
NtfsFileFlagsToAttributes(ULONG NtfsAttributes, NtfsFileFlagsToAttributes(ULONG NtfsAttributes,
PULONG FileAttributes); PULONG FileAttributes);

View file

@ -22,6 +22,7 @@
* PURPOSE: NTFS filesystem driver * PURPOSE: NTFS filesystem driver
* PROGRAMMERS: Art Yerkes * PROGRAMMERS: Art Yerkes
* Pierre Schweitzer (pierre@reactos.org) * Pierre Schweitzer (pierre@reactos.org)
* Trevor Thompson
*/ */
/* INCLUDES *****************************************************************/ /* INCLUDES *****************************************************************/
@ -255,14 +256,403 @@ NtfsRead(PNTFS_IRP_CONTEXT IrpContext)
return Status; 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 NTSTATUS
NtfsWrite(PNTFS_IRP_CONTEXT IrpContext) 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); DPRINT("NtfsWrite(IrpContext %p)\n", IrpContext);
ASSERT(IrpContext);
// This request is not allowed on the main device object
if (IrpContext->DeviceObject == NtfsGlobalData->DeviceObject)
{
DPRINT1("\t\t\t\tNtfsWrite is called with the main device object.\n");
Irp->IoStatus.Information = 0;
return STATUS_INVALID_DEVICE_REQUEST;
}
// get the I/O request packet
Irp = IrpContext->Irp;
// get the File control block
Fcb = (PNTFS_FCB)IrpContext->FileObject->FsContext;
ASSERT(Fcb);
DPRINT("About to write %wS\n", Fcb->ObjectName);
DPRINT("NTFS Version: %d.%d\n", Fcb->Vcb->NtfsInfo.MajorVersion, Fcb->Vcb->NtfsInfo.MinorVersion);
// setup some more locals
FileObject = IrpContext->FileObject;
DeviceObject = IrpContext->DeviceObject;
DeviceExt = DeviceObject->DeviceExtension;
BytesPerSector = DeviceExt->StorageDevice->SectorSize;
Length = IrpContext->Stack->Parameters.Write.Length;
// get the file offset we'll be writing to
ByteOffset = IrpContext->Stack->Parameters.Write.ByteOffset;
if (ByteOffset.u.LowPart == FILE_WRITE_TO_END_OF_FILE &&
ByteOffset.u.HighPart == -1)
{
ByteOffset.QuadPart = Fcb->RFCB.FileSize.QuadPart;
}
DPRINT("ByteOffset: %I64u\tLength: %lu\tBytes per sector: %lu\n", ByteOffset.QuadPart,
Length, BytesPerSector);
if (ByteOffset.u.HighPart && !(Fcb->Flags & FCB_IS_VOLUME))
{
// TODO: Support large files
DPRINT1("FIXME: Writing to large files is not yet supported at this time.\n");
return STATUS_INVALID_PARAMETER;
}
// Is this a non-cached write? A non-buffered write?
if (IrpContext->Irp->Flags & (IRP_PAGING_IO | IRP_NOCACHE) || (Fcb->Flags & FCB_IS_VOLUME) ||
IrpContext->FileObject->Flags & FILE_NO_INTERMEDIATE_BUFFERING)
{
// non-cached and non-buffered writes must be sector aligned
if (ByteOffset.u.LowPart % BytesPerSector != 0 || Length % BytesPerSector != 0)
{
DPRINT1("Non-cached writes and non-buffered writes must be sector aligned!\n");
return STATUS_INVALID_PARAMETER;
}
}
if (Length == 0)
{
DPRINT1("Null write!\n");
IrpContext->Irp->IoStatus.Information = 0; IrpContext->Irp->IoStatus.Information = 0;
return STATUS_NOT_SUPPORTED;
// 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 */ /* EOF */