/*++ Copyright (c) 2000-2001 Microsoft Corporation Module Name: readwrit.c Abstract: This module implements routines related to handling IRP_MJ_READ and IRP_MJ_WRITE. --*/ #include "fatx.h" // // Local support. // VOID FatxStartNextAsynchronousIoEntry( IN PFAT_VOLUME_EXTENSION VolumeExtension, IN PIRP Irp, IN PFAT_ASYNC_IO_DESCRIPTOR AsyncIoDescriptor ); NTSTATUS FatxVolumeIoCompletion( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ) /*++ Routine Description: This routine is called to process the completion of a volume I/O transfer. Arguments: DeviceObject - Specifies the device object that the I/O request is for. Irp - Specifies the packet that describes the I/O request. Context - Specifies the context that was supplied to IoSetCompletionRoutine. Return Value: Status of operation. --*/ { PFAT_VOLUME_EXTENSION VolumeExtension; PIO_STACK_LOCATION IrpSp; PFILE_OBJECT FileObject; VolumeExtension = (PFAT_VOLUME_EXTENSION)DeviceObject->DeviceExtension; IrpSp = IoGetCurrentIrpStackLocation(Irp); // // Propagate the pending flag up the IRP stack. // if (Irp->PendingReturned) { IoMarkIrpPending(Irp); } // // Check if this is a read or write completion. This completion routine is // shared by other IRP dispatch routines. // if ((IrpSp->MajorFunction == IRP_MJ_READ) || (IrpSp->MajorFunction == IRP_MJ_WRITE)) { if (NT_SUCCESS(Irp->IoStatus.Status)) { ASSERT(Irp->IoStatus.Information > 0); ASSERT(FatxIsSectorAligned(VolumeExtension, Irp->IoStatus.Information)); // // If the file is open for synchronous I/O, then we need to update // the current file position. // FileObject = IrpSp->FileObject; if (FatxIsFlagSet(FileObject->Flags, FO_SYNCHRONOUS_IO)) { FileObject->CurrentByteOffset.QuadPart = IrpSp->Parameters.Read.ByteOffset.QuadPart + Irp->IoStatus.Information; } } } FatxDpcReleaseVolumeMutex(VolumeExtension); return STATUS_SUCCESS; } NTSTATUS FatxSignalIoEventCompletion( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ) /*++ Routine Description: This routine is called to signal an event when a thread is blocked on an I/O operation inside an IRP dispatch routine. Arguments: DeviceObject - Specifies the device object that the I/O request is for. Irp - Specifies the packet that describes the I/O request. Context - Specifies the context that was supplied to IoSetCompletionRoutine. Return Value: Status of operation. --*/ { KeSetEvent((PKEVENT)Context, IO_DISK_INCREMENT, FALSE); return STATUS_MORE_PROCESSING_REQUIRED; } NTSTATUS FatxSynchronousIoTail( IN PFAT_VOLUME_EXTENSION VolumeExtension, IN PIRP Irp, IN UCHAR MajorFunction, IN PFILE_OBJECT FileObject, IN ULONG FileByteOffset, IN ULONG IoLength ) /*++ Routine Description: This routine is called at the tail of the non-cached and cached synchronous I/O routines to update the state in the file control block and file objects. This routine is only called on success from these other routines. Arguments: VolumeExtension - Specifies the extension that the I/O request is for. Irp - Specifies the packet that describes the I/O request. MajorFunction - Specifies the function to be performed; either IRP_MJ_READ or IRP_MJ_WRITE. FileObject - Specifies the file object that the I/O request is for. FileByteOffset - Specifies the file byte offset where the transfer started from. IoLength - Specifies the number of bytes that were transferred. Return Value: Status of operation. --*/ { NTSTATUS status; PFAT_FCB FileFcb; ULONG EndingByteOffset; ULONG OriginalFileSize; FileFcb = (PFAT_FCB)FileObject->FsContext; EndingByteOffset = FileByteOffset + IoLength; // // If we've written to the file, then update the last write time. The last // write time is lazily flushed to the directory entry. We need to acquire // the volume mutex for exclusive access to synchronize with // FatxFsdSetInformation and to write out any directory changes below. // if (MajorFunction == IRP_MJ_WRITE) { FatxAcquireVolumeMutexExclusive(VolumeExtension); if (FatxIsFlagClear(FileFcb->Flags, FAT_FCB_DISABLE_LAST_WRITE_TIME)) { KeQuerySystemTime(&FileFcb->LastWriteTime); FileFcb->Flags |= FAT_FCB_UPDATE_DIRECTORY_ENTRY; } // // If the ending byte offset is beyond the current size of the file, // then we've extended the file. Change the file size to the new size // and commit the directory change. // if (EndingByteOffset > FileFcb->FileSize) { OriginalFileSize = FileFcb->FileSize; FileFcb->FileSize = EndingByteOffset; status = FatxUpdateDirectoryEntry(VolumeExtension, Irp, FileFcb); if (!NT_SUCCESS(status)) { FileFcb->FileSize = OriginalFileSize; FatxReleaseVolumeMutex(VolumeExtension); return status; } } FatxReleaseVolumeMutex(VolumeExtension); } // // If the file is open for synchronous I/O, then we need to update the // current file position. // if (FatxIsFlagSet(FileObject->Flags, FO_SYNCHRONOUS_IO)) { FileObject->CurrentByteOffset.LowPart = EndingByteOffset; FileObject->CurrentByteOffset.HighPart = 0; } // // Fill in the number of bytes of transferred. This number may be less than // the actual number of modified bytes in the buffer if we're at the end of // file. // Irp->IoStatus.Information = IoLength; return STATUS_SUCCESS; } NTSTATUS FatxNonCachedSynchronousIo( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN UCHAR MajorFunction, IN PFILE_OBJECT FileObject, IN ULONG FileByteOffset, IN ULONG BufferByteOffset, IN ULONG IoLength, IN BOOLEAN PartialTransfer ) /*++ Routine Description: This routine is called to synchronously read or write a sector aligned buffer from or to the target device. Arguments: DeviceObject - Specifies the device object that the I/O request is for. Irp - Specifies the packet that describes the I/O request. MajorFunction - Specifies the function to be performed; either IRP_MJ_READ or IRP_MJ_WRITE. FileObject - Specifies the file object that the I/O request is for. FileByteOffset - Specifies the file byte offset to start the transfer from. BufferByteOffset - Specifies the buffer byte offset ot start the transfer from. IoLength - Specifies the number of bytes to transfer. PartialTransfer - TRUE if called from FatxPartiallyCachedSynchronousIo, else FALSE. Return Value: Status of operation. --*/ { NTSTATUS status; PFAT_VOLUME_EXTENSION VolumeExtension; PFAT_FCB Fcb; PIO_STACK_LOCATION NextIrpSp; ULONG OriginalFileByteOffset; ULONG IoLengthRemaining; ULONGLONG PhysicalByteOffset; ULONG PhysicalRunLength; ULONGLONG StartingPhysicalByteOffset; ULONG PhysicalIoLength; ULONGLONG AdjacentPhysicalByteOffset; KEVENT IoEvent; VolumeExtension = (PFAT_VOLUME_EXTENSION)DeviceObject->DeviceExtension; Fcb = (PFAT_FCB)FileObject->FsContext; NextIrpSp = IoGetNextIrpStackLocation(Irp); OriginalFileByteOffset = FileByteOffset; IoLengthRemaining = FatxRoundToSectors(VolumeExtension, IoLength); ASSERT(IoLengthRemaining > 0); ASSERT(FatxIsSectorAligned(VolumeExtension, FileByteOffset)); // // Get the physical byte offset corresponding to the starting byte offset. // status = FatxFileByteOffsetToPhysicalByteOffset(VolumeExtension, Irp, Fcb, FileByteOffset, TRUE, &PhysicalByteOffset, &PhysicalRunLength); if (!NT_SUCCESS(status)) { return status; } // // Do the transfer. // do { StartingPhysicalByteOffset = PhysicalByteOffset; // // Compute how many physical bytes we can process in this pass. The // cluster cache doesn't follow adjacent clusters that cross cache pages // so we handle that logic here by increasing the physical I/O length // until we find a non-adjacent cluster run. This non-adjacent cluster // run is the starting cluster for the next iteration of the outer loop. // PhysicalIoLength = 0; do { // // Limit the number of bytes in the physical run to the number of // bytes left to process. // if (PhysicalRunLength > IoLengthRemaining) { PhysicalRunLength = IoLengthRemaining; IoLengthRemaining = 0; } else { IoLengthRemaining -= PhysicalRunLength; } // // Update the number of physical bytes we can process and the offset // into the file. // PhysicalIoLength += PhysicalRunLength; FileByteOffset += PhysicalRunLength; if (IoLengthRemaining == 0) { break; } // // Compute the physical byte offset for the adjacent run. // AdjacentPhysicalByteOffset = PhysicalByteOffset + PhysicalRunLength; // // Obtain the next physical run. // status = FatxFileByteOffsetToPhysicalByteOffset(VolumeExtension, Irp, Fcb, FileByteOffset, TRUE, &PhysicalByteOffset, &PhysicalRunLength); if (!NT_SUCCESS(status)) { return status; } // // If the next physical byte offset isn't adjacent to the last // physical run, then we're done. // } while (PhysicalByteOffset == AdjacentPhysicalByteOffset); // // Invalidate any file system cache buffers for this byte range if this // is a write operation. // if (MajorFunction == IRP_MJ_WRITE) { FscInvalidateByteRange(&VolumeExtension->CacheExtension, StartingPhysicalByteOffset, PhysicalIoLength); } // // Initialize (or reinitialize) the synchronization event we'll use to // block for the port driver to handle the I/O. // KeInitializeEvent(&IoEvent, SynchronizationEvent, FALSE); // // Fill in the starting physical byte offset and the number of bytes to // transfer. // NextIrpSp->Parameters.Read.ByteOffset.QuadPart = StartingPhysicalByteOffset; NextIrpSp->Parameters.Read.Length = PhysicalIoLength; // // Fill in the offset into the buffer to start the transfer and update // that offset by the number of bytes we'll physically transfer. // NextIrpSp->Parameters.Read.BufferOffset = BufferByteOffset; BufferByteOffset += PhysicalIoLength; // // Fill in the header for the stack location. This clears out the // MinorFunction, Control, and Flags field as well. // *((PULONG)&NextIrpSp->MajorFunction) = MajorFunction; // // Set the completion routine that will signal our synchronization // event. // IoSetCompletionRoutine(Irp, FatxSignalIoEventCompletion, &IoEvent, TRUE, TRUE, TRUE); // // Call down to the target device and block for the I/O to complete. // status = IoCallDriver(VolumeExtension->TargetDeviceObject, Irp); if (status == STATUS_PENDING) { KeWaitForSingleObject(&IoEvent, Executive, KernelMode, FALSE, NULL); status = Irp->IoStatus.Status; } if (!NT_SUCCESS(status)) { return status; } // // Assert that the device handled as many bytes as we programmed it to. // ASSERT(Irp->IoStatus.Information == PhysicalIoLength); } while (IoLengthRemaining > 0); if (!PartialTransfer) { status = FatxSynchronousIoTail(VolumeExtension, Irp, MajorFunction, FileObject, OriginalFileByteOffset, IoLength); } else { status = STATUS_SUCCESS; } return status; } NTSTATUS FatxNonCachedAsynchronousIoCompletion( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ) /*++ Routine Description: This routine is called to process the completion of a non-cached asynchronous I/O transfer. Arguments: DeviceObject - Specifies the device object that the I/O request is for. Irp - Specifies the packet that describes the I/O request. Context - Specifies the context that was supplied to IoSetCompletionRoutine. Return Value: Status of operation. --*/ { PFAT_VOLUME_EXTENSION VolumeExtension; PFAT_ASYNC_IO_DESCRIPTOR AsyncIoDescriptor; PFAT_FCB FileFcb; VolumeExtension = (PFAT_VOLUME_EXTENSION)DeviceObject->DeviceExtension; AsyncIoDescriptor = (PFAT_ASYNC_IO_DESCRIPTOR)Context; if (NT_SUCCESS(Irp->IoStatus.Status)) { // // If there are still more bytes to transfer, then start the entry and // bail out. // if (AsyncIoDescriptor->IoLengthRemaining > 0) { FatxStartNextAsynchronousIoEntry(VolumeExtension, Irp, AsyncIoDescriptor); return STATUS_MORE_PROCESSING_REQUIRED; } // // If this was a file write, then we need to update the last write time. // if (AsyncIoDescriptor->MajorFunction == IRP_MJ_WRITE) { FileFcb = AsyncIoDescriptor->FileFcb; if (FatxIsFlagClear(FileFcb->Flags, FAT_FCB_DISABLE_LAST_WRITE_TIME)) { KeQuerySystemTime(&FileFcb->LastWriteTime); FileFcb->Flags |= FAT_FCB_UPDATE_DIRECTORY_ENTRY; } } // // Fill in the number of bytes of transferred. This number may be less // than the actual number of modified bytes in the buffer if we're at // the end of file. // Irp->IoStatus.Information = AsyncIoDescriptor->IoLength; } FatxDecrementDismountBlockCount(VolumeExtension); FatxDpcReleaseFileMutex(AsyncIoDescriptor->FileFcb); ExFreePool(AsyncIoDescriptor); return STATUS_SUCCESS; } VOID FatxStartNextAsynchronousIoEntry( IN PFAT_VOLUME_EXTENSION VolumeExtension, IN PIRP Irp, IN PFAT_ASYNC_IO_DESCRIPTOR AsyncIoDescriptor ) /*++ Routine Description: This routine is called to start the next read or write from the supplied asynchronous I/O descriptor. Arguments: VolumeExtension - Specifies the extension that the I/O request is for. Irp - Specifies the packet that describes the I/O request. AsyncIoDescriptor - Specifies the asynchronous I/O descriptor that indicates the next starting physical sector and transfer length. Return Value: None. --*/ { PIO_STACK_LOCATION NextIrpSp; PFAT_ASYNC_IO_ENTRY AsyncIoEntry; ULONG PhysicalIoLength; NextIrpSp = IoGetNextIrpStackLocation(Irp); AsyncIoEntry = &AsyncIoDescriptor->Entries[AsyncIoDescriptor->NextAsyncIoEntry++]; PhysicalIoLength = AsyncIoEntry->PhysicalIoLength; // // Adjust the number of bytes remaining in the transfer. // AsyncIoDescriptor->IoLengthRemaining -= PhysicalIoLength; // // Fill in the starting physical byte offset and the number of bytes to // transfer. // NextIrpSp->Parameters.Read.ByteOffset.QuadPart = (ULONGLONG)AsyncIoEntry->PhysicalSector << VolumeExtension->SectorShift; NextIrpSp->Parameters.Read.Length = PhysicalIoLength; // // Fill in the offset into the buffer to start the transfer and update // that offset by the number of bytes we'll physically transfer. // NextIrpSp->Parameters.Read.BufferOffset = AsyncIoDescriptor->BufferOffset; AsyncIoDescriptor->BufferOffset += PhysicalIoLength; // // Fill in the header for the stack location. This clears out the // MinorFunction, Control, and Flags field as well. // *((PULONG)&NextIrpSp->MajorFunction) = AsyncIoDescriptor->MajorFunction; // // Set the completion routine that will start the next entry or finish the // transfer. // IoSetCompletionRoutine(Irp, FatxNonCachedAsynchronousIoCompletion, AsyncIoDescriptor, TRUE, TRUE, TRUE); // // Call down to the target device. // IoCallDriver(VolumeExtension->TargetDeviceObject, Irp); } NTSTATUS FatxNonCachedAsynchronousIo( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN UCHAR MajorFunction, IN PFILE_OBJECT FileObject, IN ULONG FileByteOffset, IN ULONG IoLength ) /*++ Routine Description: This routine is called to asynchronously read or write a sector aligned buffer from or to the target device. Arguments: DeviceObject - Specifies the device object that the I/O request is for. Irp - Specifies the packet that describes the I/O request. MajorFunction - Specifies the function to be performed; either IRP_MJ_READ or IRP_MJ_WRITE. FileObject - Specifies the file object that the I/O request is for. FileByteOffset - Specifies the file byte offset to start the transfer from. IoLength - Specifies the number of bytes to transfer. Return Value: Status of operation. --*/ { NTSTATUS status; PFAT_VOLUME_EXTENSION VolumeExtension; PFAT_FCB Fcb; ULONG IoLengthRemaining; SIZE_T AsyncIoDescriptorSize; PFAT_ASYNC_IO_DESCRIPTOR AsyncIoDescriptor; ULONGLONG PhysicalByteOffset; ULONG PhysicalRunLength; PFAT_ASYNC_IO_ENTRY AsyncIoEntry; ULONGLONG StartingPhysicalByteOffset; ULONG PhysicalIoLength; ULONGLONG AdjacentPhysicalByteOffset; VolumeExtension = (PFAT_VOLUME_EXTENSION)DeviceObject->DeviceExtension; Fcb = (PFAT_FCB)FileObject->FsContext; IoLengthRemaining = FatxRoundToSectors(VolumeExtension, IoLength); ASSERT(IoLengthRemaining > 0); ASSERT(FatxIsSectorAligned(VolumeExtension, FileByteOffset)); // // Always assume the worst case for the state of the file. If the file is // highly fragmented, then we'll need one entry per cluster. If the file // isn't fragmented, then we'll end up wasting memory, but the application // will always be charged a consistent number of bytes per transfer. // // #####: bug fix // If the area to write wraps a cluster, allocate space for one more // entry. XDKs starting with 4627 patch this bug in older kernels. // They patch by always adding 8 extra (sizeof(FAT_ASYNC_IO_ENTRY)). // Note the 2 below - that used to be a 1. // AsyncIoDescriptorSize = sizeof(FAT_ASYNC_IO_DESCRIPTOR) + ((IoLengthRemaining >> VolumeExtension->ClusterShift) + 2) * sizeof(FAT_ASYNC_IO_ENTRY); AsyncIoDescriptor = ExAllocatePoolWithTag(AsyncIoDescriptorSize, 'dAtF'); if (AsyncIoDescriptor == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } // // Initialize the asynchronous I/O descriptor. // AsyncIoDescriptor->MajorFunction = MajorFunction; AsyncIoDescriptor->IoLength = IoLength; AsyncIoDescriptor->IoLengthRemaining = IoLengthRemaining; AsyncIoDescriptor->BufferOffset = 0; AsyncIoDescriptor->NextAsyncIoEntry = 0; AsyncIoDescriptor->FileFcb = Fcb; // // Get the physical byte offset corresponding to the starting byte offset. // status = FatxFileByteOffsetToPhysicalByteOffset(VolumeExtension, Irp, Fcb, FileByteOffset, TRUE, &PhysicalByteOffset, &PhysicalRunLength); if (!NT_SUCCESS(status)) { return status; } // // Fill in the entries of the asynchronous I/O descriptor. // AsyncIoEntry = AsyncIoDescriptor->Entries; do { StartingPhysicalByteOffset = PhysicalByteOffset; // // Compute how many physical bytes we can process in this pass. The // cluster cache doesn't follow adjacent clusters that cross cache pages // so we handle that logic here by increasing the physical I/O length // until we find a non-adjacent cluster run. This non-adjacent cluster // run is the starting cluster for the next iteration of the outer loop. // PhysicalIoLength = 0; do { // // Limit the number of bytes in the physical run to the number of // bytes left to process. // if (PhysicalRunLength > IoLengthRemaining) { PhysicalRunLength = IoLengthRemaining; IoLengthRemaining = 0; } else { IoLengthRemaining -= PhysicalRunLength; } // // Update the number of physical bytes we can process and the offset // into the file. // PhysicalIoLength += PhysicalRunLength; FileByteOffset += PhysicalRunLength; if (IoLengthRemaining == 0) { break; } // // Compute the physical byte offset for the adjacent run. // AdjacentPhysicalByteOffset = PhysicalByteOffset + PhysicalRunLength; // // Obtain the next physical run. // status = FatxFileByteOffsetToPhysicalByteOffset(VolumeExtension, Irp, Fcb, FileByteOffset, TRUE, &PhysicalByteOffset, &PhysicalRunLength); if (!NT_SUCCESS(status)) { ExFreePool(AsyncIoDescriptor); return status; } // // If the next physical byte offset isn't adjacent to the last // physical run, then we're done. // } while (PhysicalByteOffset == AdjacentPhysicalByteOffset); ASSERT(FatxIsSectorAligned(VolumeExtension, PhysicalIoLength)); // // Invalidate any file system cache buffers for this byte range if this // is a write operation. // if (MajorFunction == IRP_MJ_WRITE) { FscInvalidateByteRange(&VolumeExtension->CacheExtension, StartingPhysicalByteOffset, PhysicalIoLength); } // // Fill in the starting physical byte offset and the number of bytes to // transfer. // AsyncIoEntry->PhysicalSector = (ULONG)(StartingPhysicalByteOffset >> VolumeExtension->SectorShift); AsyncIoEntry->PhysicalIoLength = PhysicalIoLength; AsyncIoEntry++; } while (IoLengthRemaining > 0); // // Start transferring the first entry in the asynchronous I/O descriptor. // IoMarkIrpPending(Irp); FatxStartNextAsynchronousIoEntry(VolumeExtension, Irp, AsyncIoDescriptor); return STATUS_PENDING; } NTSTATUS FatxFullyCachedSynchronousIo( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN UCHAR MajorFunction, IN PFILE_OBJECT FileObject, IN ULONG FileByteOffset, IN ULONG BufferByteOffset, IN ULONG IoLength, IN BOOLEAN PartialTransfer ) /*++ Routine Description: This routine is called to synchronously read or write a buffer through the file system cache. Arguments: DeviceObject - Specifies the device object that the I/O request is for. Irp - Specifies the packet that describes the I/O request. MajorFunction - Specifies the function to be performed; either IRP_MJ_READ or IRP_MJ_WRITE. FileObject - Specifies the file object that the I/O request is for. FileByteOffset - Specifies the file byte offset to start the transfer from. BufferByteOffset - Specifies the buffer byte offset ot start the transfer from. IoLength - Specifies the number of bytes to transfer. PartialTransfer - TRUE if called from FatxPartiallyCachedSynchronousIo, else FALSE. Return Value: Status of operation. --*/ { NTSTATUS status; PFAT_VOLUME_EXTENSION VolumeExtension; PFAT_FCB Fcb; ULONG OriginalFileByteOffset; ULONG IoLengthRemaining; PVOID UserBuffer; ULONGLONG PhysicalByteOffset; ULONG PhysicalRunLength; ULONG PhysicalRunBytesRemaining; PVOID CacheBuffer; ULONG BytesToCopy; BOOLEAN MapEmptyBuffer; VolumeExtension = (PFAT_VOLUME_EXTENSION)DeviceObject->DeviceExtension; Fcb = (PFAT_FCB)FileObject->FsContext; OriginalFileByteOffset = FileByteOffset; IoLengthRemaining = IoLength; UserBuffer = (PUCHAR)Irp->UserBuffer + BufferByteOffset; ASSERT(IoLengthRemaining > 0); // // Get the physical byte offset corresponding to the starting byte offset. // status = FatxFileByteOffsetToPhysicalByteOffset(VolumeExtension, Irp, Fcb, FileByteOffset, TRUE, &PhysicalByteOffset, &PhysicalRunLength); if (!NT_SUCCESS(status)) { return status; } // // Do the transfer. // for (;;) { PhysicalRunBytesRemaining = PhysicalRunLength; do { // // Compute the number of bytes remaining on this cache buffer. // BytesToCopy = PAGE_SIZE - BYTE_OFFSET(PhysicalByteOffset); // // Adjust the number of bytes remaining in this physical run and the // next cache byte offset depending on whether we're near the end of // the run or not. // if (BytesToCopy < PhysicalRunBytesRemaining) { PhysicalRunBytesRemaining -= BytesToCopy; } else { BytesToCopy = PhysicalRunBytesRemaining; PhysicalRunBytesRemaining = 0; } // // Limit the number of bytes copied to the number of bytes we // actually need. // if (BytesToCopy > IoLengthRemaining) { BytesToCopy = IoLengthRemaining; } if (MajorFunction == IRP_MJ_WRITE) { // // If we're going to be writing out an entire page or if we're // going to be writing to the first byte of the last page of the // file, then we can map in an empty cache page. // if (BytesToCopy == PAGE_SIZE) { MapEmptyBuffer = TRUE; } else if ((BYTE_OFFSET(FileByteOffset) == 0) && (FileByteOffset + IoLengthRemaining >= Fcb->FileSize)) { MapEmptyBuffer = TRUE; } else { MapEmptyBuffer = FALSE; } // // Map in the next page of the physical run. // if (MapEmptyBuffer) { status = FscMapEmptyBuffer(&VolumeExtension->CacheExtension, PhysicalByteOffset, &CacheBuffer); } else { status = FscMapBuffer(&VolumeExtension->CacheExtension, Irp, PhysicalByteOffset, TRUE, &CacheBuffer); } if (!NT_SUCCESS(status)) { return status; } // // Write to the cache buffer and unmap the cache buffer. // RtlCopyMemory(CacheBuffer, UserBuffer, BytesToCopy); status = FscWriteBuffer(&VolumeExtension->CacheExtension, Irp, PhysicalByteOffset, BytesToCopy, CacheBuffer); if (!NT_SUCCESS(status)) { return status; } } else { // // Map in the next page of the physical run. // status = FscMapBuffer(&VolumeExtension->CacheExtension, Irp, PhysicalByteOffset, FALSE, &CacheBuffer); if (!NT_SUCCESS(status)) { return status; } // // Read from the cache buffer and unmap the cache buffer. // RtlCopyMemory(UserBuffer, CacheBuffer, BytesToCopy); FscUnmapBuffer(CacheBuffer); } // // Adjust the number of bytes remaining and check if we're through // with the transfer. // PhysicalByteOffset += BytesToCopy; IoLengthRemaining -= BytesToCopy; if (IoLengthRemaining == 0) { if (!PartialTransfer) { status = FatxSynchronousIoTail(VolumeExtension, Irp, MajorFunction, FileObject, OriginalFileByteOffset, IoLength); } else { status = STATUS_SUCCESS; } return status; } // // There's still more bytes to transfer. Update the other loop // variables and continue transfering this physical run. // FileByteOffset += BytesToCopy; UserBuffer = (PUCHAR)UserBuffer + BytesToCopy; } while (PhysicalRunBytesRemaining > 0); // // Obtain the next physical run. // status = FatxFileByteOffsetToPhysicalByteOffset(VolumeExtension, Irp, Fcb, FileByteOffset, TRUE, &PhysicalByteOffset, &PhysicalRunLength); if (!NT_SUCCESS(status)) { return status; } } } NTSTATUS FatxPartiallyCachedSynchronousIo( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN UCHAR MajorFunction, IN PFILE_OBJECT FileObject, IN ULONG FileByteOffset, IN ULONG IoLength, IN BOOLEAN NonCachedEndOfFileTransfer ) /*++ Routine Description: This routine is called to synchronously read or write a buffer partially through the file system cache and partially through direct device I/O. This routine is intended for large I/O transfers where we want to maximize use of direct device I/O in order to avoid PAGE_SIZE I/O transfers. Arguments: DeviceObject - Specifies the device object that the I/O request is for. Irp - Specifies the packet that describes the I/O request. MajorFunction - Specifies the function to be performed; either IRP_MJ_READ or IRP_MJ_WRITE. FileObject - Specifies the file object that the I/O request is for. FileByteOffset - Specifies the file byte offset to start the transfer from. IoLength - Specifies the number of bytes to transfer. NonCachedEndOfFileTransfer - Specifies TRUE if the read is to the end of file and the output buffer is large enough to hold the transfer length rounded up to a sector boundary. Return Value: Status of operation. --*/ { NTSTATUS status; PFAT_VOLUME_EXTENSION VolumeExtension; ULONG OriginalFileByteOffset; ULONG OriginalIoLength; ULONG PartialIoLength; ULONG BufferByteOffset; VolumeExtension = (PFAT_VOLUME_EXTENSION)DeviceObject->DeviceExtension; OriginalFileByteOffset = FileByteOffset; OriginalIoLength = IoLength; // // The below code assumes that the relative byte offsets into the file and // the file area have the same page alignment as their corresponding // physical byte offsets. // ASSERT(BYTE_OFFSET(VolumeExtension->FileAreaByteOffset) == 0); // // Transfer the head of the request if it's not page aligned. // if (BYTE_OFFSET(FileByteOffset) != 0) { PartialIoLength = PAGE_SIZE - BYTE_OFFSET(FileByteOffset); ASSERT(PartialIoLength < IoLength); status = FatxFullyCachedSynchronousIo(DeviceObject, Irp, MajorFunction, FileObject, FileByteOffset, 0, PartialIoLength, TRUE); if (!NT_SUCCESS(status)) { return status; } IoLength -= PartialIoLength; FileByteOffset += PartialIoLength; BufferByteOffset = PartialIoLength; } else { BufferByteOffset = 0; } // // If NonCachedEndOfFileTransfer is TRUE, then we're reading to the end of // the file and the transfer buffer is large enough to hold the transfer // length rounded up to a sector boundary. // if (NonCachedEndOfFileTransfer) { ASSERT(MajorFunction == IRP_MJ_READ); IoLength = FatxRoundToSectors(VolumeExtension, IoLength); PartialIoLength = IoLength; } else { ASSERT(IoLength >= PAGE_SIZE); PartialIoLength = (ULONG)PAGE_ALIGN(IoLength); } status = FatxNonCachedSynchronousIo(DeviceObject, Irp, MajorFunction, FileObject, FileByteOffset, BufferByteOffset, PartialIoLength, TRUE); // // Transfer the remaining non whole page of the request if necessary. // if (NT_SUCCESS(status)) { IoLength -= PartialIoLength; if (IoLength > 0) { FileByteOffset += PartialIoLength; BufferByteOffset += PartialIoLength; status = FatxFullyCachedSynchronousIo(DeviceObject, Irp, MajorFunction, FileObject, FileByteOffset, BufferByteOffset, IoLength, TRUE); } } if (NT_SUCCESS(status)) { status = FatxSynchronousIoTail(VolumeExtension, Irp, MajorFunction, FileObject, OriginalFileByteOffset, OriginalIoLength); } return status; } NTSTATUS FatxFsdReadWrite( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) /*++ Routine Description: This routine is called by the I/O manager to handle IRP_MJ_READ and IRP_MJ_WRITE requests. Arguments: DeviceObject - Specifies the device object that the I/O request is for. Irp - Specifies the packet that describes the I/O request. Return Value: Status of operation. --*/ { NTSTATUS status; PFAT_VOLUME_EXTENSION VolumeExtension; PIO_STACK_LOCATION IrpSp; PIO_STACK_LOCATION NextIrpSp; UCHAR MajorFunction; PFILE_OBJECT FileObject; PFAT_FCB Fcb; ULONG IoLength; BOOLEAN NonCachedEndOfFileTransfer; ULONGLONG PartitionBytesRemaining; ULONG FileBytesRemaining; BOOLEAN SynchronousIo; ULONG EndingByteOffset; ULONG ClusterNumber; VolumeExtension = (PFAT_VOLUME_EXTENSION)DeviceObject->DeviceExtension; IrpSp = IoGetCurrentIrpStackLocation(Irp); NextIrpSp = IoGetNextIrpStackLocation(Irp); MajorFunction = IrpSp->MajorFunction; FileObject = IrpSp->FileObject; Fcb = (PFAT_FCB)FileObject->FsContext; IoLength = IrpSp->Parameters.Read.Length; NonCachedEndOfFileTransfer = FALSE; // // Ensure that the file object is not for a directory. // if (FatxIsFlagSet(Fcb->Flags, FAT_FCB_DIRECTORY)) { status = STATUS_INVALID_DEVICE_REQUEST; goto CompleteAndExit; } // // If this is a zero length request, then we can complete the IRP now. // if (IoLength == 0) { Irp->IoStatus.Information = 0; status = STATUS_SUCCESS; goto CompleteAndExit; } // // Check if we're reading from or writing to the volume file control block. // if (FatxIsFlagSet(Fcb->Flags, FAT_FCB_VOLUME)) { if (MajorFunction == IRP_MJ_READ) { FatxAcquireVolumeMutexShared(VolumeExtension); } else { FatxAcquireVolumeMutexExclusive(VolumeExtension); } // // Check if the volume has been dismounted. // if (FatxIsFlagSet(VolumeExtension->Flags, FAT_VOLUME_DISMOUNTED)) { status = STATUS_VOLUME_DISMOUNTED; goto UnlockVolumeAndExit; } // // Check if the starting offset is beyond the end of the partition. // if ((ULONGLONG)IrpSp->Parameters.Read.ByteOffset.QuadPart >= (ULONGLONG)VolumeExtension->PartitionLength.QuadPart) { status = STATUS_END_OF_FILE; goto UnlockVolumeAndExit; } // // If the number of bytes to transfer is greater than the number of // bytes remaining in the partition, then truncate the number of bytes // we'll actually transfer. // PartitionBytesRemaining = (ULONGLONG)VolumeExtension->PartitionLength.QuadPart - (ULONGLONG)IrpSp->Parameters.Read.ByteOffset.QuadPart; if ((ULONGLONG)IoLength >= PartitionBytesRemaining) { IoLength = (ULONG)PartitionBytesRemaining; } ASSERT(IoLength > 0); // // We'll leave it to the target device's driver to validate that the // user's buffer, the starting byte offset, and the transfer length are // all properly aligned. // IoCopyCurrentIrpStackLocationToNext(Irp); // // Lock the user's buffer into memory if necessary. // IoLockUserBuffer(Irp, IrpSp->Parameters.Read.Length); // // Invalidate any file system cache buffers for this byte range if this // is a write operation. // if (MajorFunction == IRP_MJ_WRITE) { FscInvalidateByteRange(&VolumeExtension->CacheExtension, IrpSp->Parameters.Read.ByteOffset.QuadPart, IoLength); } // // Limit the number of bytes physically read to the end of the volume. // NextIrpSp->Parameters.Read.Length = IoLength; // // Set a completion routine to unlock the volume mutex and update any // state in the file control block. // IoSetCompletionRoutine(Irp, FatxVolumeIoCompletion, NULL, TRUE, TRUE, TRUE); // // Call down to the target device. // status = IoCallDriver(VolumeExtension->TargetDeviceObject, Irp); // // Leave the critical region that we acquired when we took the volume // mutex. // KeLeaveCriticalRegion(); return status; UnlockVolumeAndExit: FatxReleaseVolumeMutex(VolumeExtension); goto CompleteAndExit; } // // Otherwise, we're reading from or writing to a standard file. // if (MajorFunction == IRP_MJ_READ) { FatxAcquireFileMutexShared(Fcb); } else { FatxAcquireFileMutexExclusive(Fcb); } // // Increment the dismount unblock count for the volume. We won't be // holding the volume's mutex throughout the processing of this request, // so FatxDismountVolume needs to have some synchronization mechanism to // know when all pending read/write IRPs have completed. // // If a request enters the file system after a dismount has been // unblocked or completed, then the dismount flag will have been set for // the volume and the code below will fail the request. // // We don't need to do this for volume file control block because in // that code path, the volume's mutex is held for the entire operation. // FatxIncrementDismountBlockCount(VolumeExtension); // // Check if the volume has been dismounted. // if (FatxIsFlagSet(VolumeExtension->Flags, FAT_VOLUME_DISMOUNTED)) { status = STATUS_VOLUME_DISMOUNTED; goto UnlockFileAndExit; } // // Check if the file object has already been cleaned up. We don't allow a // a file object to be modified after its handle has been closed. // if (FatxIsFlagSet(FileObject->Flags, FO_CLEANUP_COMPLETE)) { status = STATUS_FILE_CLOSED; goto UnlockFileAndExit; } if (MajorFunction == IRP_MJ_READ) { // // Check if the starting offset is beyond the end of file. // if ((IrpSp->Parameters.Read.ByteOffset.HighPart != 0) || (IrpSp->Parameters.Read.ByteOffset.LowPart >= Fcb->FileSize)) { status = STATUS_END_OF_FILE; goto UnlockFileAndExit; } // // If the number of bytes to read is greater than the number of bytes // remaining in the file, then truncate the number of bytes we'll // actually read. // FileBytesRemaining = Fcb->FileSize - IrpSp->Parameters.Read.ByteOffset.LowPart; if (IoLength >= FileBytesRemaining) { // // If the user's buffer is large enough to hold the logical read // length rounded up to a sector boundary, then set a flag so that // the below code will potentially read this part of the file as // non cached. // if (IoLength >= (FatxRoundToSectors(VolumeExtension, Fcb->FileSize) - IrpSp->Parameters.Read.ByteOffset.LowPart)) { NonCachedEndOfFileTransfer = TRUE; } IoLength = FileBytesRemaining; } ASSERT(IoLength > 0); // // Check if we should do synchronous or asynchronous I/O depending on // how the file object was originally opened. // SynchronousIo = (BOOLEAN)FatxIsFlagSet(FileObject->Flags, FO_SYNCHRONOUS_IO); } else { // // If we're supposed to write to the end of the file, then change the // starting byte offset to the current end of file. // if ((IrpSp->Parameters.Read.ByteOffset.LowPart == FILE_WRITE_TO_END_OF_FILE) && (IrpSp->Parameters.Read.ByteOffset.HighPart == -1)) { IrpSp->Parameters.Read.ByteOffset.LowPart = Fcb->FileSize; IrpSp->Parameters.Read.ByteOffset.HighPart = 0; } // // Verify that the starting or ending offset are only 32-bits. // EndingByteOffset = IrpSp->Parameters.Read.ByteOffset.LowPart + IoLength; if ((IrpSp->Parameters.Read.ByteOffset.HighPart != 0) || (EndingByteOffset <= IrpSp->Parameters.Read.ByteOffset.LowPart)) { status = STATUS_DISK_FULL; goto UnlockFileAndExit; } // // Determine the number of bytes currently allocated to the file. // if (Fcb->AllocationSize == MAXULONG) { // // Attempt to find the cluster corresponding to the maximum byte // offset which will have the side effect of filling in the number // of bytes allocated. // FatxAcquireVolumeMutexShared(VolumeExtension); status = FatxFileByteOffsetToCluster(VolumeExtension, Irp, Fcb, MAXULONG, &ClusterNumber, NULL); FatxReleaseVolumeMutex(VolumeExtension); if (!NT_SUCCESS(status) && (status != STATUS_END_OF_FILE)) { goto UnlockFileAndExit; } } // // If the file size is greater than the number of bytes allocated to the // file, then the file is corrupt and we won't allow any writes to it. // if (Fcb->FileSize > Fcb->AllocationSize) { status = STATUS_FILE_CORRUPT_ERROR; goto UnlockFileAndExit; } // // If the ending byte offset is greater than the number of bytes // allocated to the file, then we need to grow the allocation size for // the file. The volume mutex must be acquired exclusively so that the // volume can be modified. // if (EndingByteOffset > Fcb->AllocationSize) { FatxAcquireVolumeMutexExclusive(VolumeExtension); status = FatxExtendFileAllocation(VolumeExtension, Irp, Fcb, FatxRoundToClusters(VolumeExtension, EndingByteOffset)); FatxReleaseVolumeMutex(VolumeExtension); if (!NT_SUCCESS(status)) { goto UnlockFileAndExit; } } // // Check if we should do synchronous or asynchronous I/O depending on // how the file object was originally opened. // // If this is an asynchronous I/O operation, verify that the ending byte // offset is before the end of file. If it isn't, then we can't change // the directory entry's file size asynchronously, so switch this back // to a synchronous I/O operation. // SynchronousIo = (BOOLEAN)FatxIsFlagSet(FileObject->Flags, FO_SYNCHRONOUS_IO); if (!SynchronousIo && (EndingByteOffset > Fcb->FileSize)) { SynchronousIo = TRUE; } } // // The upper 32-bits of the byte offset should be zero at this point. Code // below this point will only look at ByteOffset.LowPart. // ASSERT(IrpSp->Parameters.Read.ByteOffset.HighPart == 0); // // Check if we're supposed to bypass the file system cache. // if (FatxIsFlagSet(Irp->Flags, IRP_NOCACHE)) { // // Verify that the starting byte offset and transfer length are sector // aligned. We'll leave it to the target device's driver to validate // that the user's buffer is properly aligned. // if (!FatxIsSectorAligned(VolumeExtension, IrpSp->Parameters.Read.ByteOffset.LowPart) || !FatxIsSectorAligned(VolumeExtension, IrpSp->Parameters.Read.Length)) { status = STATUS_INVALID_PARAMETER; goto UnlockFileAndExit; } // // Lock the user's buffer into memory if necessary. // IoLockUserBuffer(Irp, IrpSp->Parameters.Read.Length); // // Do the transfer depending on whether we can block waiting for the // I/O to complete or not. // if (SynchronousIo) { status = FatxNonCachedSynchronousIo(DeviceObject, Irp, MajorFunction, FileObject, IrpSp->Parameters.Read.ByteOffset.LowPart, 0, IoLength, FALSE); } else { status = FatxNonCachedAsynchronousIo(DeviceObject, Irp, MajorFunction, FileObject, IrpSp->Parameters.Read.ByteOffset.LowPart, IoLength); if (status == STATUS_PENDING) { // // Leave the critical region that we acquired when we took the // file mutex. // KeLeaveCriticalRegion(); return status; } } } else { // // Scatter/gather operations are always non-cached. // ASSERT(FatxIsFlagClear(Irp->Flags, IRP_SCATTER_GATHER_OPERATION)); // // Otherwise, handle reading the data using the file cache. // // Check if this transfer should use the fully or partially cached path. // if (FscTestForFullyCachedIo(Irp, IrpSp->Parameters.Read.ByteOffset.LowPart, IoLength, NonCachedEndOfFileTransfer)) { status = FatxFullyCachedSynchronousIo(DeviceObject, Irp, MajorFunction, FileObject, IrpSp->Parameters.Read.ByteOffset.LowPart, 0, IoLength, FALSE); } else { // // Lock the user's buffer into memory if necessary. // IoLockUserBuffer(Irp, IrpSp->Parameters.Read.Length); status = FatxPartiallyCachedSynchronousIo(DeviceObject, Irp, MajorFunction, FileObject, IrpSp->Parameters.Read.ByteOffset.LowPart, IoLength, NonCachedEndOfFileTransfer); } } ASSERT(status != STATUS_PENDING); UnlockFileAndExit: FatxDecrementDismountBlockCount(VolumeExtension); FatxReleaseFileMutex(Fcb); CompleteAndExit: Irp->IoStatus.Status = status; IoCompleteRequest(Irp, IO_NO_INCREMENT); return status; }