Index: reactos/drivers/filesystems/ntfs/blockdev.c =================================================================== --- reactos/drivers/filesystems/ntfs/blockdev.c (revision 71033) +++ 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 *****************************************************************/ @@ -43,7 +44,7 @@ PIO_STACK_LOCATION Stack; IO_STATUS_BLOCK IoStatus; LARGE_INTEGER Offset; - KEVENT Event; + PKEVENT pEvent; PIRP Irp; NTSTATUS Status; ULONGLONG RealReadOffset; @@ -53,7 +54,19 @@ DPRINT("NtfsReadDisk(%p, %I64x, %u, %u, %p, %d)\n", DeviceObject, StartingOffset, Length, SectorSize, Buffer, Override); - KeInitializeEvent(&Event, + if (Length == 0) + { + DPRINT1("NULL READ!\n"); + return STATUS_SUCCESS; + } + + pEvent = ExAllocatePoolWithTag(NonPagedPool, sizeof(KEVENT), TAG_NTFS); + if (pEvent == NULL) + { + DPRINT1("Not enough memory!\n"); + return STATUS_INSUFFICIENT_RESOURCES; + } + KeInitializeEvent(pEvent, NotificationEvent, FALSE); @@ -69,6 +82,7 @@ if (ReadBuffer == NULL) { DPRINT1("Not enough memory!\n"); + ExFreePoolWithTag(pEvent, TAG_NTFS); return STATUS_INSUFFICIENT_RESOURCES; } AllocatedBuffer = TRUE; @@ -82,11 +96,14 @@ ReadBuffer, RealLength, &Offset, - &Event, + pEvent, &IoStatus); if (Irp == NULL) { DPRINT("IoBuildSynchronousFsdRequest failed\n"); + if( AllocatedBuffer ) + ExFreePoolWithTag(pEvent, TAG_NTFS); + ExFreePoolWithTag(pEvent, TAG_NTFS); return STATUS_INSUFFICIENT_RESOURCES; } @@ -103,7 +120,7 @@ if (Status == STATUS_PENDING) { DPRINT("Operation pending\n"); - KeWaitForSingleObject(&Event, Suspended, KernelMode, FALSE, NULL); + KeWaitForSingleObject(pEvent, Suspended, KernelMode, FALSE, NULL); DPRINT("Getting IO Status... for %p\n", Irp); Status = IoStatus.Status; } @@ -114,12 +131,142 @@ ExFreePoolWithTag(ReadBuffer, TAG_NTFS); } + ExFreePoolWithTag(pEvent, TAG_NTFS); + DPRINT("NtfsReadDisk() done (Status %x)\n", 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 +* +* @param Overide +* If true, adds SL_OVERRIDE_VERIFY_VOLUME to the Stack->Flags +* +* @return +* STATUS_SUCCESS in case of success, STATUS_INSUFFICIENT_RESOURCES if applicable, or +* whatever status IoCallDriver() set. +* +* @remarks Called by NtfsWriteFile(). +* +*/ NTSTATUS +NtfsWriteDisk(IN PDEVICE_OBJECT DeviceObject, + IN LONGLONG StartingOffset, + IN ULONG Length, + IN ULONG SectorSize, + IN PUCHAR Buffer, + IN BOOLEAN Override) +{ + PIO_STACK_LOCATION Stack; + IO_STATUS_BLOCK IoStatus; + LARGE_INTEGER Offset; + PKEVENT pEvent; + PIRP Irp; + NTSTATUS Status; + ULONGLONG RealWriteOffset; + ULONG RealLength; + + DPRINT("NtfsWriteDisk(%p, %I64x, %u, %u, %p, %d)\n", DeviceObject, StartingOffset, Length, SectorSize, Buffer, Override); + DPRINT("Buffer: %s\n", Buffer); + + if (Length == 0) + { + DPRINT1("NULL WRITE!\n"); + return STATUS_SUCCESS; + } + + RealWriteOffset = (ULONGLONG)StartingOffset; + RealLength = Length; + + // ensure the write will be sector-aligned + if ((RealWriteOffset % SectorSize) != 0 || (RealLength % SectorSize) != 0) + { + RealWriteOffset = ROUND_DOWN(StartingOffset, SectorSize); + RealLength = ROUND_UP(Length, SectorSize); + } + + // set the destination offset + Offset.QuadPart = RealWriteOffset; + + // allocate non-paged memory for a notification event (See MSDN Re: IoBuildSynchronousFsdRequest()) + pEvent = ExAllocatePoolWithTag(NonPagedPool, sizeof(KEVENT), TAG_NTFS); + if (!pEvent) + { + DPRINT1("Not enough memory!\n"); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // setup the notification event for the write + KeInitializeEvent(pEvent, + NotificationEvent, + FALSE); + + DPRINT("Building synchronous FSD Request...\n"); + + // Build an IRP requesting the lower-level [disk] driver to perform the write + Irp = IoBuildSynchronousFsdRequest(IRP_MJ_WRITE, + DeviceObject, + Buffer, + RealLength, + &Offset, + pEvent, + &IoStatus); + if (Irp == NULL) + { + DPRINT1("IoBuildSynchronousFsdRequest failed\n"); + ExFreePoolWithTag(pEvent, TAG_NTFS); + return STATUS_INSUFFICIENT_RESOURCES; + } + + if (Override) + { + DPRINT("Override == true\n"); + Stack = IoGetNextIrpStackLocation(Irp); + Stack->Flags |= SL_OVERRIDE_VERIFY_VOLUME; + } + + // 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(pEvent, Suspended, KernelMode, FALSE, NULL); + DPRINT("Getting IO Status... for %p\n", Irp); + Status = IoStatus.Status; + } + + DPRINT("NtfsWriteDisk() done (Status %x)\n", Status); + + // free the event notification memory + ExFreePoolWithTag(pEvent, TAG_NTFS); + 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 71033) +++ reactos/drivers/filesystems/ntfs/create.c (working copy) @@ -359,13 +359,6 @@ FileObject = Stack->FileObject; - if (RequestedDisposition == FILE_CREATE || - RequestedDisposition == FILE_OVERWRITE_IF || - RequestedDisposition == FILE_SUPERSEDE) - { - return STATUS_ACCESS_DENIED; - } - if ((RequestedOptions & FILE_OPEN_BY_FILE_ID) == FILE_OPEN_BY_FILE_ID) { ULONGLONG MFTId; @@ -385,6 +378,18 @@ if (!NT_SUCCESS(Status)) { + if (Status == STATUS_OBJECT_PATH_NOT_FOUND ) + { + // we couldn't find the file; is the user trying to create it? + if (RequestedDisposition == FILE_CREATE || RequestedDisposition == FILE_SUPERSEDE + || RequestedDisposition == FILE_OPEN_IF || RequestedDisposition == FILE_OVERWRITE_IF + || RequestedDisposition == FILE_SUPERSEDE) + { + DPRINT1("TODO: File creation is not yet supported on NTFS!\n"); + return STATUS_ACCESS_DENIED; + } + } + return Status; } @@ -391,7 +396,7 @@ DPRINT1("Open by ID: %I64x -> %wZ\n", (*(PULONGLONG)FileObject->FileName.Buffer) & NTFS_MFT_MASK, &FullPath); } - /* This a open operation for the volume itself */ + /* This is an open operation for the volume itself */ if (FileObject->FileName.Length == 0 && (FileObject->RelatedFileObject == NULL || FileObject->RelatedFileObject->FsContext2 != NULL)) { @@ -482,26 +487,16 @@ NtfsCloseFile(DeviceExt, FileObject); return Status; } - - /* HUGLY HACK: remain RO so far... */ - if (RequestedDisposition == FILE_OVERWRITE || - RequestedDisposition == FILE_OVERWRITE_IF || - RequestedDisposition == FILE_SUPERSEDE) - { - DPRINT1("Denying write 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"); + DPRINT1("Denying file creation request on NTFS volume\n"); return STATUS_ACCESS_DENIED; } } Index: reactos/drivers/filesystems/ntfs/mft.c =================================================================== --- reactos/drivers/filesystems/ntfs/mft.c (revision 71033) +++ 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 *****************************************************************/ @@ -330,7 +331,228 @@ return AlreadyRead; } +/** +* @name WriteAttribute +* @implemented +* +* writes an NTFS attribute to the disk. It presently borrows a lot of code from ReadAttribute, +* and (they both) could use a lot 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 +* +* @return +* The number of bytes written +* +* @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. +* +*/ +ULONG +WriteAttribute(PDEVICE_EXTENSION Vcb, + PNTFS_ATTR_CONTEXT Context, + ULONGLONG Offset, + PCHAR Buffer, + ULONG Length) +{ + ULONGLONG LastLCN; + PUCHAR DataRun; + LONGLONG DataRunOffset; + ULONGLONG DataRunLength; + LONGLONG DataRunStartLCN; + ULONGLONG CurrentOffset; + ULONG WriteLength; + ULONG AlreadyWritten; + NTSTATUS Status; + DPRINT("WriteAttribute called. Buffer: %s\n", Buffer); + + // 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 0;// STATUS_ACCESS_DENIED; // until we implement it + } + + // This is a non-resident attribute. + + // I. Find the corresponding start data run. + + AlreadyWritten = 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. + DataRunStartLCN = LastLCN + DataRunOffset; + LastLCN = DataRunStartLCN; + } + else + { + // Sparse data run. For a write, we can't support this yet. + DataRunStartLCN = -1; + DPRINT1("FIXME: Writing to sparse files is not supported yet!\n"); + return 0; + } + + if (Offset >= CurrentOffset && + Offset < CurrentOffset + (DataRunLength * Vcb->NtfsInfo.BytesPerCluster)) + { + break; + } + + if (*DataRun == 0) + { + return AlreadyWritten; + } + + CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster; + } + } + + // II. Go through the run list and write the data + + WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset), Length); + if (DataRunStartLCN == -1) + { + DPRINT1("FIXME: Writing to sparse files is not yet supported! (DataRunStartLCN == -1)\n"); + return STATUS_ACCESS_DENIED; + } + + // Write the data to the disk + Status = NtfsWriteDisk(Vcb->StorageDevice, + DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset, + WriteLength, + Vcb->NtfsInfo.BytesPerSector, + (PVOID)Buffer, + FALSE); + + // Did the write succeed? + if (NT_SUCCESS(Status)) + { + Length -= WriteLength; + Buffer += WriteLength; + AlreadyWritten += WriteLength; + + if (WriteLength == DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset)) + { + CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster; + DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength); + if (DataRunLength != (ULONGLONG)-1) + { + DataRunStartLCN = LastLCN + DataRunOffset; + LastLCN = DataRunStartLCN; + } + else + DataRunStartLCN = -1; + + if (*DataRun == 0) + return AlreadyWritten; + } + + // Do we have more clusters to write? + while (Length > 0) + { + WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster, Length); + if (DataRunStartLCN == -1) + { + DPRINT1("FIXME: Don't know how to write to sparse files yet! (DataRunStartLCN == -1)\n"); + return STATUS_ACCESS_DENIED; + } + else + { + // write the data to the disk + Status = NtfsWriteDisk(Vcb->StorageDevice, + DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster, + WriteLength, + Vcb->NtfsInfo.BytesPerSector, + (PVOID)Buffer, + FALSE); + if (!NT_SUCCESS(Status)) + break; + } + + Length -= WriteLength; + Buffer += WriteLength; + AlreadyWritten += 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) + break; + 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 clusters to write] + + } // end if (NT_SUCCESS(Status)) [first cluster write succeeded] + + Context->CacheRun = DataRun; + Context->CacheRunOffset = Offset + AlreadyWritten; + Context->CacheRunStartLCN = DataRunStartLCN; + Context->CacheRunLength = DataRunLength; + Context->CacheRunLastLCN = LastLCN; + Context->CacheRunCurrentOffset = CurrentOffset; + + return AlreadyWritten; +} + + NTSTATUS ReadFileRecord(PDEVICE_EXTENSION Vcb, ULONGLONG index, Index: reactos/drivers/filesystems/ntfs/misc.c =================================================================== --- reactos/drivers/filesystems/ntfs/misc.c (revision 71033) +++ reactos/drivers/filesystems/ntfs/misc.c (working copy) @@ -76,7 +76,7 @@ RtlZeroMemory(IrpContext, sizeof(NTFS_IRP_CONTEXT)); - IrpContext->Identifier.Type = NTFS_TYPE_IRP_CONTEST; + IrpContext->Identifier.Type = NTFS_TYPE_IRP_CONTEXT; IrpContext->Identifier.Size = sizeof(NTFS_IRP_CONTEXT); IrpContext->Irp = Irp; IrpContext->DeviceObject = DeviceObject; @@ -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 71033) +++ reactos/drivers/filesystems/ntfs/ntfs.h (working copy) @@ -11,8 +11,8 @@ #endif #define CACHEPAGESIZE(pDeviceExt) \ - ((pDeviceExt)->NtfsInfo.UCHARsPerCluster > PAGE_SIZE ? \ - (pDeviceExt)->NtfsInfo.UCHARsPerCluster : PAGE_SIZE) + ((pDeviceExt)->NtfsInfo.UCHARsPerCluster > PAGE_SIZE ? \ + (pDeviceExt)->NtfsInfo.UCHARsPerCluster : PAGE_SIZE) #define TAG_NTFS 'SFTN' @@ -87,7 +87,7 @@ #define NTFS_TYPE_CCB '20SF' #define NTFS_TYPE_FCB '30SF' #define NTFS_TYPE_VCB '50SF' -#define NTFS_TYPE_IRP_CONTEST '60SF' +#define NTFS_TYPE_IRP_CONTEXT '60SF' #define NTFS_TYPE_GLOBAL_DATA '70SF' typedef struct @@ -551,6 +551,14 @@ IN BOOLEAN Override); NTSTATUS +NtfsWriteDisk(IN PDEVICE_OBJECT DeviceObject, + IN LONGLONG StartingOffset, + IN ULONG Length, + IN ULONG SectorSize, + IN PUCHAR Buffer, + IN BOOLEAN Override); + +NTSTATUS NtfsReadSectors(IN PDEVICE_OBJECT DeviceObject, IN ULONG DiskSector, IN ULONG SectorCount, @@ -741,6 +749,13 @@ PCHAR Buffer, ULONG Length); +ULONG +WriteAttribute(PDEVICE_EXTENSION Vcb, + PNTFS_ATTR_CONTEXT Context, + ULONGLONG Offset, + PCHAR Buffer, + ULONG Length); + 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); @@ -823,8 +843,8 @@ VOID CdfsSwapString(PWCHAR Out, - PUCHAR In, - ULONG Count); + PUCHAR In, + ULONG Count); #endif VOID Index: reactos/drivers/filesystems/ntfs/rw.c =================================================================== --- reactos/drivers/filesystems/ntfs/rw.c (revision 71033) +++ 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 *****************************************************************/ @@ -146,7 +147,11 @@ RealReadOffset = ROUND_DOWN(ReadOffset, DeviceExt->NtfsInfo.BytesPerSector); RealLength = ROUND_UP(ToRead, DeviceExt->NtfsInfo.BytesPerSector); - ReadBuffer = ExAllocatePoolWithTag(NonPagedPool, RealLength + DeviceExt->NtfsInfo.BytesPerSector, TAG_NTFS); + // do we need to extend RealLength by one sector? + if (RealLength + RealReadOffset < ReadOffset + Length) + RealLength += DeviceExt->NtfsInfo.BytesPerSector; + + ReadBuffer = ExAllocatePoolWithTag(NonPagedPool, RealLength + (DeviceExt->NtfsInfo.BytesPerSector * 2), TAG_NTFS); if (ReadBuffer == NULL) { DPRINT1("Not enough memory!\n"); @@ -157,7 +162,7 @@ AllocatedBuffer = TRUE; } - DPRINT1("Effective read: %lu at %lu for stream '%S'\n", RealLength, RealReadOffset, Fcb->Stream); + DPRINT1("Effective read length: %lu\nat: %lu\nfor stream: '%S'\n", RealLength, RealReadOffset, Fcb->Stream); RealLengthRead = ReadAttribute(DeviceExt, DataContext, RealReadOffset, (PCHAR)ReadBuffer, RealLength); if (RealLengthRead == 0) { @@ -248,14 +253,555 @@ return Status; } +/** +* @name NtfsWriteFile +* @implemented +* +* Writes a file to the disk. It presently borrows a lot of code from NtfsReadFile() and +* VFatWriteFileData(). It needs to borrow some more code before it will be complete; it +* won't handle page files, asnyc io, or cached writes. +* +* @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, + 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 RealLength = Length; + ULONG RealWriteOffset = WriteOffset; + ULONG RealLengthWritten = 0; + ULONG ToWrite; + ULONGLONG FileSize; + BOOLEAN allocateBuffer = FALSE; + BOOLEAN AllocatedBuffer = FALSE; + PCHAR SourceBuffer = (PCHAR)Buffer; + PCHAR TempBuffer = (PCHAR)Buffer; + ULONG TempBufferSize = 0; + ULONGLONG StreamSize; + ULONG DataOffset = 0; + ULONG BytesPerSector; + DPRINT("NtfsWriteFile(%p, %p, %p, %u, %u, %x, %p)\n", DeviceExt, FileObject, Buffer, Length, WriteOffset, IrpFlags, LengthWritten); + + *LengthWritten = 0; + + ASSERT(DeviceExt); + BytesPerSector = DeviceExt->NtfsInfo.BytesPerSector; + + if (Length == 0) + { + DPRINT1("Null Write!\n"); + return STATUS_SUCCESS; + } + + DPRINT("Buffer: %s\n", Buffer); + + // 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!\nCan'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\n,StreamSize: %I64u\n", WriteOffset, StreamSize); + + // Are we trying to write beyond the end of the stream? + // TODO: allocate additional clusters as needed and expand stream + if (WriteOffset >= StreamSize) + { + DPRINT1("TODO: Stream embiggening (appending files) is not yet supported!\n"); + ReleaseAttributeContext(DataContext); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + *LengthWritten = Length; // we'll pretend the write succeeded for now + return STATUS_SUCCESS; // temporarily; we don't change file sizes now, or report any error + } + + ToWrite = Length; + + // Would the requested write have us going past the stream end? + if (WriteOffset + Length > StreamSize) + { + DPRINT1("Stream embiggening (appending files) is not yet supported!\n"); + // TODO: allocate additional clusters as needed and expand stream + // just write as much as we can, for now + ToWrite = StreamSize - WriteOffset; + } + + DPRINT("ToWrite: %lu\nWriteOffset: %lu\n\nStreamSize: %I64u\n", ToWrite, WriteOffset, StreamSize); + + RealWriteOffset = WriteOffset; + RealLength = ToWrite; + + // Are we trying to write beyond the end of the file? (This check is probably redundant) + // TODO: update the file size (allocating/freeing clusters as needed), handle HighPart of FileSize + FileSize = Fcb->RFCB.FileSize.LowPart; + if (ToWrite > FileSize) + { + DPRINT1( + "TODO: NTFS Driver does not yet support enlarging files!\n\tTruncating data written to %wS!\n\n", + Fcb->PathName); + ToWrite = FileSize; + } + + // Writes to the disk need to be sector-aligned. Do we need to adjust this write? + if ((WriteOffset % BytesPerSector) != 0 || (ToWrite % BytesPerSector) != 0) + { + RealWriteOffset = ROUND_DOWN(WriteOffset, BytesPerSector); + RealLength = ROUND_UP(ToWrite, BytesPerSector); + + // Consider 512b sectors & a write to offset 503 with Length 24, ROUND_UP will produce + // a RealLength of 512, which is wrong, you would want a length of 1024. + + // do we need to extend RealLength by one sector? + if (RealLength + RealWriteOffset < WriteOffset + Length) + RealLength += BytesPerSector; + + if (RealLength + RealWriteOffset < WriteOffset + Length) + { + DPRINT1("\n\n\n\n\t\t\tFIXME: reserving less memory than needed!\n"); + DPRINT1("WriteOffset: %lu\n\nLength: %lu\nRealWriteOffset: %lu\n\nRealLength: %lu\n", + WriteOffset, Length, RealWriteOffset, RealLength); + } + + // calculate the offset from TempBuffer that the data should be written to + DataOffset = WriteOffset - RealWriteOffset; + + DPRINT("ToWrite: %lu\nWriteOffset: %lu\nAfter adjustment:\n", ToWrite, WriteOffset ); + DPRINT("data Offset: %lu\n", DataOffset); + DPRINT("RealWriteOffset: %lu\nRealLength: %lu\n", RealWriteOffset, RealLength); + + allocateBuffer = TRUE; + } + + // Do we need to copy the existing attribute to a buffer before overwriting it? + if (allocateBuffer) + { + ULONG Read; + DPRINT("Allocating buffer to read data on disk to...\n"); + + // we need a buffer to copy the data from the sectors we're about to overwrite, so we don't overwrite + // data in areas we aren't trying to update. + TempBufferSize = RealLength + (2 * BytesPerSector); // a write can span two sector boundaries + TempBuffer = ExAllocatePoolWithTag(NonPagedPool, TempBufferSize, TAG_NTFS); + if (TempBuffer == NULL) + { + DPRINT1("Not enough memory!\n"); + ReleaseAttributeContext(DataContext); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + return STATUS_INSUFFICIENT_RESOURCES; + } + AllocatedBuffer = TRUE; + + // copy the existing attribute + DPRINT("Copying attribute before overwriting it...\n"); + + DPRINT("RealWriteOffset: %lu\nRealLength: %lu\n", RealWriteOffset, RealLength); + + Read = ReadAttribute(DeviceExt, DataContext, RealWriteOffset, (PCHAR)TempBuffer, RealLength); + + // Did we read less than we requested? + if (Read != RealLength) + { + DPRINT("Read != RealLength\n"); + + // Was there perfectly good data to read (or did we reach the file end) + if (RealWriteOffset + Read < Fcb->Entry.DataSize) + { + DPRINT1("Error reading attribute! was expecting to read: %lu\nbut only read: %lu\n", RealLength, Read); + DPRINT1("Can't continue...\n"); + ReleaseAttributeContext(DataContext); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + return STATUS_UNSUCCESSFUL; + } + } + + // update the buffer with the new data being written + RtlCopyMemory(TempBuffer + DataOffset, Buffer, Length); + + // update the source buffer to point to this new buffer + SourceBuffer = TempBuffer; + } + + DPRINT("Effective write: %lu at %lu for stream '%S'\n", RealLength, RealWriteOffset, Fcb->Stream); + DPRINT("AllocatedBuffer: %d\n", AllocatedBuffer); + + // write the data to the attribute + RealLengthWritten = WriteAttribute(DeviceExt, DataContext, RealWriteOffset, (PCHAR)SourceBuffer, RealLength); + *LengthWritten = min(RealLengthWritten, Length); + + + // did the write fail? + if (RealLengthWritten == 0) + { + DPRINT1("Write failure!\n"); + ReleaseAttributeContext(DataContext); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + if (AllocatedBuffer) + { + // zero the buffer, so private user data can't be snooped + RtlZeroMemory(TempBuffer, TempBufferSize); + ExFreePoolWithTag(TempBuffer, TAG_NTFS); + } + return STATUS_ACCESS_DENIED; + } + + // do some cleanup + if (AllocatedBuffer) + { + // zero the buffer, so private user data can't be snooped + RtlZeroMemory(TempBuffer, TempBufferSize); + + // free the buffer memory + ExFreePoolWithTag(TempBuffer, TAG_NTFS); + } + + DPRINT("About to free FileRecord\n"); + ExFreePoolWithTag(FileRecord, TAG_NTFS); + + ReleaseAttributeContext(DataContext); + + 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 if a required feature isn't implemented, +* STATUS_INSUFFICIENT_RESOURCES if an allocation failed, or +* STATUS_ACCESS_DENIED if the write itself fails. +* 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 and transactions - 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; + PIO_STACK_LOCATION Stack = NULL; + PIRP Irp = NULL; + ULONG BytesPerSector; - IrpContext->Irp->IoStatus.Information = 0; - return STATUS_NOT_SUPPORTED; + DPRINT("NtfsWrite(IrpContext %p)\n", IrpContext); + ASSERT(IrpContext); + + // get the I/O request packet + Irp = IrpContext->Irp; + ASSERT(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); + + FileObject = IrpContext->FileObject; + ASSERT(FileObject); + + Stack = IrpContext->Stack; + ASSERT(Stack); + + DeviceObject = IrpContext->DeviceObject; + ASSERT(DeviceObject); + + DeviceExt = DeviceObject->DeviceExtension; + ASSERT(DeviceExt); + + // get the file's 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; + } + + // get the write length + Length = IrpContext->Stack->Parameters.Write.Length; + + BytesPerSector = DeviceExt->StorageDevice->SectorSize; + + DPRINT("ByteOffset: %I64u\nLength: %lu\nBytes per sector: %lu\n", ByteOffset.QuadPart, + Length, BytesPerSector); + + if (ByteOffset.u.HighPart && !(Fcb->Flags & FCB_IS_VOLUME)) + { + // TODO: Support large files + DPRINT1("Writing to large files is not yet supported at this time.\n"); + return STATUS_INVALID_PARAMETER; + } + + if (IrpContext->Irp->Flags & (IRP_PAGING_IO | IRP_NOCACHE) || (Fcb->Flags & FCB_IS_VOLUME)) + { + if (ByteOffset.u.LowPart % BytesPerSector != 0 || Length % BytesPerSector != 0) + { + // non cached write must be sector aligned + DPRINT1("Non-cached writes must be sector aligned!\n"); + return STATUS_INVALID_PARAMETER; + } + } + + if (Length == 0) + { + // FIXME: Update last write time + IrpContext->Irp->IoStatus.Information = 0; + return STATUS_SUCCESS; + } + + // 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_UNSUCCESSFUL; + } + + /* 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"); + + if (ByteOffset.u.LowPart + Length > Fcb->RFCB.AllocationSize.u.LowPart) + { + 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"); + return Status; + } + + DPRINT("Length: %lu\n", Length); + DPRINT("Existing File Size(Fcb->RFCB.FileSize.QuadPart): %I64u\n", Fcb->RFCB.FileSize.QuadPart); + DPRINT("Data to write: %p\n\n%s\n\n", Buffer, Buffer); + + /* REVIEWME: This next bit of code is located about here in VfatWrite. I'm not sure + it's relevant to NTFS; I haven't given it a close inspection. + I have dutifully transcribed it and adapted it regardless - Trevor */ + + // This request is not allowed on the main device object + if (IrpContext->DeviceObject == NtfsGlobalData->DeviceObject) + { + DPRINT1("\n\n\n\t\t\tNtfsWrite is called with the main device object.\n\n\n\n"); + + // NtfsUnlockUserBuffer(Irp->MdlAddress); + + ExReleaseResourceLite(Resource); + + Irp->IoStatus.Information = 0; + return STATUS_INVALID_DEVICE_REQUEST; + } + /* end VfatWrite code */ + + DPRINT("About to write the data...\n"); + + DeviceExt = DeviceObject->DeviceExtension; + + // 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)) + { + DPRINT("Success! Returned length: %lu\n", ReturnedWriteLength); + + // TODO: Update timestamps + + if (FileObject->Flags & FO_SYNCHRONOUS_IO) + { + // advance the file pointer + FileObject->CurrentByteOffset.QuadPart = ByteOffset.QuadPart + ReturnedWriteLength; + } + + Irp->IoStatus.Information = ReturnedWriteLength; + IrpContext->PriorityBoost = IO_DISK_INCREMENT; + } + else + { + DPRINT1("Write not Succesful!\n"); + Irp->IoStatus.Information = 0; + } + + // ReviewMe: I need to do more research on memory descriptor lists. It seems like in ReactOS + // the IO manager unlocks the MdlAddress for us. TODO: Research; does Windows behave the same way? + // NtfsUnlockUserBuffer(Irp->MdlAddress); + + ExReleaseResourceLite(Resource); + + return Status; } /* EOF */