mirror of
https://github.com/reactos/reactos.git
synced 2024-12-28 01:55:19 +00:00
[NTFS]
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:
parent
950c86fb4c
commit
58a13831ef
6 changed files with 931 additions and 9 deletions
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
IrpContext->Irp->IoStatus.Information = 0;
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
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;
|
||||
|
||||
// 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 */
|
||||
|
|
Loading…
Reference in a new issue