Index: reactos/drivers/filesystems/ntfs/blockdev.c =================================================================== --- reactos/drivers/filesystems/ntfs/blockdev.c (revision 71153) +++ reactos/drivers/filesystems/ntfs/blockdev.c (working copy) @@ -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 *****************************************************************/ @@ -87,6 +88,9 @@ if (Irp == NULL) { DPRINT("IoBuildSynchronousFsdRequest failed\n"); + if (AllocatedBuffer) + ExFreePoolWithTag(ReadBuffer, TAG_NTFS); + return STATUS_INSUFFICIENT_RESOURCES; } @@ -119,7 +123,181 @@ 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, IN ULONG SectorCount, Index: reactos/drivers/filesystems/ntfs/create.c =================================================================== --- reactos/drivers/filesystems/ntfs/create.c (revision 71153) +++ reactos/drivers/filesystems/ntfs/create.c (working copy) @@ -483,12 +483,12 @@ 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; } @@ -495,14 +495,14 @@ } 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: reactos/drivers/filesystems/ntfs/mft.c =================================================================== --- reactos/drivers/filesystems/ntfs/mft.c (revision 71153) +++ reactos/drivers/filesystems/ntfs/mft.c (working copy) @@ -24,6 +24,7 @@ * Valentin Verkhovsky * Pierre Schweitzer (pierre@reactos.org) * Hervé Poussineau (hpoussin@reactos.org) + * Trevor Thompson */ /* INCLUDES *****************************************************************/ @@ -243,13 +244,17 @@ ReadLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset), Length); if (DataRunStartLCN == -1) + { RtlZeroMemory(Buffer, ReadLength); - Status = NtfsReadDisk(Vcb->StorageDevice, - DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset, - ReadLength, - Vcb->NtfsInfo.BytesPerSector, - (PVOID)Buffer, - FALSE); + Status = STATUS_SUCCESS; + } + else + Status = NtfsReadDisk(Vcb->StorageDevice, + DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset, + ReadLength, + Vcb->NtfsInfo.BytesPerSector, + (PVOID)Buffer, + FALSE); if (NT_SUCCESS(Status)) { Length -= ReadLength; @@ -331,7 +336,274 @@ } +/** +* @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, PFILE_RECORD_HEADER file) Index: reactos/drivers/filesystems/ntfs/misc.c =================================================================== --- reactos/drivers/filesystems/ntfs/misc.c (revision 71153) +++ reactos/drivers/filesystems/ntfs/misc.c (working copy) @@ -130,4 +130,62 @@ } } +/** +* @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: reactos/drivers/filesystems/ntfs/ntfs.h =================================================================== --- reactos/drivers/filesystems/ntfs/ntfs.h (revision 71153) +++ reactos/drivers/filesystems/ntfs/ntfs.h (working copy) @@ -551,6 +551,13 @@ 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, IN ULONG SectorCount, @@ -741,6 +748,14 @@ PCHAR Buffer, ULONG Length); +NTSTATUS +WriteAttribute(PDEVICE_EXTENSION Vcb, + PNTFS_ATTR_CONTEXT Context, + ULONGLONG Offset, + const PUCHAR Buffer, + ULONG Length, + PULONG RealLengthWritten); + ULONGLONG AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord); @@ -817,6 +832,11 @@ 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); Index: reactos/drivers/filesystems/ntfs/rw.c =================================================================== --- reactos/drivers/filesystems/ntfs/rw.c (revision 71153) +++ reactos/drivers/filesystems/ntfs/rw.c (working copy) @@ -22,6 +22,7 @@ * PURPOSE: NTFS filesystem driver * PROGRAMMERS: Art Yerkes * Pierre Schweitzer (pierre@reactos.org) + * Trevor Thompson */ /* INCLUDES *****************************************************************/ @@ -150,7 +151,7 @@ RealLength += DeviceExt->NtfsInfo.BytesPerSector; - ReadBuffer = ExAllocatePoolWithTag(NonPagedPool, RealLength + (DeviceExt->NtfsInfo.BytesPerSector * 2), TAG_NTFS); + ReadBuffer = ExAllocatePoolWithTag(NonPagedPool, RealLength, TAG_NTFS); if (ReadBuffer == NULL) { DPRINT1("Not enough memory!\n"); @@ -252,14 +253,405 @@ 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; + ULONG RealLengthWritten = 0; + 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, &RealLengthWritten); + + *LengthWritten = min(RealLengthWritten, Length); + + // 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 (RealLengthWritten > Length) + { + DPRINT1("\a\tNTFS WRITE DRIVER ERROR: actual length written (%lu) exceeds requested length (%lu)!\n", + RealLengthWritten, Length); + } + + 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 locked (I believe it's up to the cache manager to unlock it) + + ExReleaseResourceLite(Resource); + + return Status; } /* EOF */