/*++ Copyright (c) 1989 Microsoft Corporation Module Name: FastIo2.c Abstract: This module REimplements the fsrtl copy read/write routines. Author: Joe Linn [JoeLinn] 9-Nov-1994 Revision History: --*/ #include "precomp.h" #pragma hdrstop #ifndef FlagOn // // This macro returns TRUE if a flag in a set of flags is on and FALSE // otherwise // #define FlagOn(Flags,SingleFlag) ((Flags) & (SingleFlag)) #endif BOOLEAN FsRtlCopyRead2 ( IN PFILE_OBJECT FileObject, IN PLARGE_INTEGER FileOffset, IN ULONG Length, IN BOOLEAN Wait, IN ULONG LockKey, OUT PVOID Buffer, OUT PIO_STATUS_BLOCK IoStatus, IN PDEVICE_OBJECT DeviceObject, IN ULONG_PTR TopLevelIrpValue ); BOOLEAN FsRtlCopyWrite2 ( IN PFILE_OBJECT FileObject, IN PLARGE_INTEGER FileOffset, IN ULONG Length, IN BOOLEAN Wait, IN ULONG LockKey, IN PVOID Buffer, OUT PIO_STATUS_BLOCK IoStatus, IN PDEVICE_OBJECT DeviceObject, IN ULONG_PTR TopLevelIrpValue ); #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, FsRtlCopyRead2) #pragma alloc_text(PAGE, FsRtlCopyWrite2) #endif BOOLEAN FsRtlCopyRead2 ( IN PFILE_OBJECT FileObject, IN PLARGE_INTEGER FileOffset, IN ULONG Length, IN BOOLEAN Wait, IN ULONG LockKey, OUT PVOID Buffer, OUT PIO_STATUS_BLOCK IoStatus, IN PDEVICE_OBJECT DeviceObject, IN ULONG_PTR TopLevelIrpValue ) /*++ Routine Description: This routine does a fast cached read bypassing the usual file system entry routine (i.e., without the Irp). It is used to do a copy read of a cached file object. For a complete description of the arguments see CcCopyRead. Arguments: FileObject - Pointer to the file object being read. FileOffset - Byte offset in file for desired data. Length - Length of desired data in bytes. Wait - FALSE if caller may not block, TRUE otherwise Buffer - Pointer to output buffer to which data should be copied. IoStatus - Pointer to standard I/O status block to receive the status for the transfer. Return Value: FALSE - if Wait was supplied as FALSE and the data was not delivered, or if there is an I/O error. TRUE - if the data is being delivered --*/ { PFSRTL_COMMON_FCB_HEADER Header; BOOLEAN Status = TRUE; ULONG PageCount = ADDRESS_AND_SIZE_TO_SPAN_PAGES((ULongToPtr(FileOffset->LowPart)), Length); LARGE_INTEGER BeyondLastByte; PDEVICE_OBJECT targetVdo; PAGED_CODE(); // // Special case a read of zero length // if (Length != 0) { // // Get a real pointer to the common fcb header // BeyondLastByte.QuadPart = FileOffset->QuadPart + (LONGLONG)Length; Header = (PFSRTL_COMMON_FCB_HEADER)FileObject->FsContext; // // Enter the file system // FsRtlEnterFileSystem(); // // Increment performance counters and get the resource // if (Wait) { // // Acquired shared on the common fcb header // (VOID)ExAcquireResourceSharedLite( Header->Resource, TRUE ); } else { // // Acquired shared on the common fcb header, and return if we // don't get it // if (!ExAcquireResourceSharedLite( Header->Resource, FALSE )) { FsRtlExitFileSystem(); //the ntfs guys dont do this AND it causes a compile error for me so //comment it out //CcFastReadResourceMiss += 1; return FALSE; } } // // Now that the File is acquired shared, we can safely test if it // is really cached and if we can do fast i/o and if not, then // release the fcb and return. // if ((FileObject->PrivateCacheMap == NULL) || (Header->IsFastIoPossible == FastIoIsNotPossible)) { ExReleaseResourceLite( Header->Resource ); FsRtlExitFileSystem(); return FALSE; } // // Check if fast I/O is questionable and if so then go ask the // file system the answer // if (Header->IsFastIoPossible == FastIoIsQuestionable) { PFAST_IO_DISPATCH FastIoDispatch; ASSERT(!KeIsExecutingDpc()); targetVdo = IoGetRelatedDeviceObject( FileObject ); FastIoDispatch = targetVdo->DriverObject->FastIoDispatch; // // All file systems that set "Is Questionable" had better support // fast I/O // ASSERT(FastIoDispatch != NULL); ASSERT(FastIoDispatch->FastIoCheckIfPossible != NULL); // // Call the file system to check for fast I/O. If the answer is // anything other than GoForIt then we cannot take the fast I/O // path. // if (!FastIoDispatch->FastIoCheckIfPossible( FileObject, FileOffset, Length, Wait, LockKey, TRUE, // read operation IoStatus, targetVdo )) { // // Fast I/O is not possible so release the Fcb and return. // ExReleaseResourceLite( Header->Resource ); FsRtlExitFileSystem(); return FALSE; } } // // Check for read past file size. // if ( BeyondLastByte.QuadPart > Header->FileSize.QuadPart ) { if ( FileOffset->QuadPart >= Header->FileSize.QuadPart ) { IoStatus->Status = STATUS_END_OF_FILE; IoStatus->Information = 0; ExReleaseResourceLite( Header->Resource ); FsRtlExitFileSystem(); return TRUE; } Length = (ULONG)( Header->FileSize.QuadPart - FileOffset->QuadPart ); } // // We can do fast i/o so call the cc routine to do the work and then // release the fcb when we've done. If for whatever reason the // copy read fails, then return FALSE to our caller. // // Also mark this as the top level "Irp" so that lower file system // levels will not attempt a pop-up // //PsGetCurrentThread()->TopLevelIrp = FSRTL_FAST_IO_TOP_LEVEL_IRP; PsGetCurrentThread()->TopLevelIrp = TopLevelIrpValue; try { if (Wait && ((BeyondLastByte.HighPart | Header->FileSize.HighPart) == 0)) { CcFastCopyRead( FileObject, FileOffset->LowPart, Length, PageCount, Buffer, IoStatus ); FileObject->Flags |= FO_FILE_FAST_IO_READ; ASSERT( (IoStatus->Status == STATUS_END_OF_FILE) || ((FileOffset->LowPart + IoStatus->Information) <= Header->FileSize.LowPart)); } else { Status = CcCopyRead( FileObject, FileOffset, Length, Wait, Buffer, IoStatus ); FileObject->Flags |= FO_FILE_FAST_IO_READ; ASSERT( !Status || (IoStatus->Status == STATUS_END_OF_FILE) || (((ULONGLONG)FileOffset->QuadPart + IoStatus->Information) <= (ULONGLONG)Header->FileSize.QuadPart)); } if (Status) { FileObject->CurrentByteOffset.QuadPart = FileOffset->QuadPart + IoStatus->Information; } } except( FsRtlIsNtstatusExpected(GetExceptionCode()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) { Status = FALSE; } PsGetCurrentThread()->TopLevelIrp = 0; ExReleaseResourceLite( Header->Resource ); FsRtlExitFileSystem(); return Status; } else { // // A zero length transfer was requested. // IoStatus->Status = STATUS_SUCCESS; IoStatus->Information = 0; return TRUE; } } BOOLEAN FsRtlCopyWrite2 ( IN PFILE_OBJECT FileObject, IN PLARGE_INTEGER FileOffset, IN ULONG Length, IN BOOLEAN Wait, IN ULONG LockKey, IN PVOID Buffer, OUT PIO_STATUS_BLOCK IoStatus, IN PDEVICE_OBJECT DeviceObject, IN ULONG_PTR TopLevelIrpValue ) /*++ Routine Description: This routine does a fast cached write bypassing the usual file system entry routine (i.e., without the Irp). It is used to do a copy write of a cached file object. For a complete description of the arguments see CcCopyWrite. Arguments: FileObject - Pointer to the file object being write. FileOffset - Byte offset in file for desired data. Length - Length of desired data in bytes. Wait - FALSE if caller may not block, TRUE otherwise Buffer - Pointer to output buffer to which data should be copied. IoStatus - Pointer to standard I/O status block to receive the status for the transfer. Return Value: FALSE - if Wait was supplied as FALSE and the data was not delivered, or if there is an I/O error. TRUE - if the data is being delivered --*/ { PFSRTL_COMMON_FCB_HEADER Header; BOOLEAN AcquiredShared = FALSE; BOOLEAN Status = TRUE; BOOLEAN FileSizeChanged = FALSE; BOOLEAN WriteToEndOfFile = (BOOLEAN)((FileOffset->LowPart == FILE_WRITE_TO_END_OF_FILE) && (FileOffset->HighPart == -1)); PAGED_CODE(); // // Get a real pointer to the common fcb header // Header = (PFSRTL_COMMON_FCB_HEADER)FileObject->FsContext; // // Do we need to verify the volume? If so, we must go to the file // system. Also return FALSE if FileObject is write through, the // File System must do that. // if (CcCanIWrite( FileObject, Length, Wait, FALSE ) && !FlagOn(FileObject->Flags, FO_WRITE_THROUGH) && CcCopyWriteWontFlush(FileObject, FileOffset, Length)) { // // Assume our transfer will work // IoStatus->Status = STATUS_SUCCESS; IoStatus->Information = Length; // // Special case the zero byte length // if (Length != 0) { // // Enter the file system // FsRtlEnterFileSystem(); // // Split into separate paths for increased performance. First // we have the faster path which only supports Wait == TRUE and // 32 bits. We will make an unsafe test on whether the fast path // is ok, then just return FALSE later if we were wrong. This // should virtually never happen. // // IMPORTANT NOTE: It is very important that any changes mad to // this path also be applied to the 64-bit path // which is the else of this test! // if (Wait && (Header->AllocationSize.HighPart == 0)) { ULONG Offset, NewFileSize; ULONG OldFileSize; ULONG OldValidDataLength; BOOLEAN Wrapped; // // Make our best guess on whether we need the file exclusive // or shared. Note that we do not check FileOffset->HighPart // until below. // NewFileSize = FileOffset->LowPart + Length; if (WriteToEndOfFile || (NewFileSize > Header->ValidDataLength.LowPart)) { // // Acquired shared on the common fcb header // ExAcquireResourceExclusiveLite( Header->Resource, TRUE ); } else { // // Acquired shared on the common fcb header // ExAcquireResourceSharedLite( Header->Resource, TRUE ); AcquiredShared = TRUE; } // // We have the fcb shared now check if we can do fast i/o // and if the file space is allocated, and if not then // release the fcb and return. // if (WriteToEndOfFile) { Offset = Header->FileSize.LowPart; NewFileSize = Header->FileSize.LowPart + Length; Wrapped = NewFileSize < Header->FileSize.LowPart; } else { Offset = FileOffset->LowPart; NewFileSize = FileOffset->LowPart + Length; Wrapped = (NewFileSize < FileOffset->LowPart) || (FileOffset->HighPart != 0); } // // Now that the File is acquired shared, we can safely test // if it is really cached and if we can do fast i/o and we // do not have to extend. If not then release the fcb and // return. // // Get out if we have too much to zero. This case is not important // for performance, and a file system supporting sparseness may have // a way to do this more efficiently. // if ((FileObject->PrivateCacheMap == NULL) || (Header->IsFastIoPossible == FastIoIsNotPossible) || (NewFileSize > Header->AllocationSize.LowPart) || (Offset >= (Header->ValidDataLength.LowPart + 0x2000)) || (Header->AllocationSize.HighPart != 0) || Wrapped) { ExReleaseResourceLite( Header->Resource ); FsRtlExitFileSystem(); return FALSE; } // // If we will be extending ValidDataLength, we will have to // get the Fcb exclusive, and make sure that FastIo is still // possible. We should only execute this block of code very // rarely, when the unsafe test for ValidDataLength failed // above. // if (AcquiredShared && (NewFileSize > Header->ValidDataLength.LowPart)) { ExReleaseResourceLite( Header->Resource ); ExAcquireResourceExclusiveLite( Header->Resource, TRUE ); // // If writing to end of file, we must recalculate new size. // if (WriteToEndOfFile) { Offset = Header->FileSize.LowPart; NewFileSize = Header->FileSize.LowPart + Length; Wrapped = NewFileSize < Header->FileSize.LowPart; } if ((FileObject->PrivateCacheMap == NULL) || (Header->IsFastIoPossible == FastIoIsNotPossible) || (NewFileSize > Header->AllocationSize.LowPart) || (Header->AllocationSize.HighPart != 0) || Wrapped) { ExReleaseResourceLite( Header->Resource ); FsRtlExitFileSystem(); return FALSE; } } // // Check if fast I/O is questionable and if so then go ask // the file system the answer // if (Header->IsFastIoPossible == FastIoIsQuestionable) { PDEVICE_OBJECT targetVdo = IoGetRelatedDeviceObject( FileObject ); PFAST_IO_DISPATCH FastIoDispatch = targetVdo->DriverObject->FastIoDispatch; IO_STATUS_BLOCK IoStatus; // // All file system then set "Is Questionable" had better // support fast I/O // ASSERT(FastIoDispatch != NULL); ASSERT(FastIoDispatch->FastIoCheckIfPossible != NULL); // // Call the file system to check for fast I/O. If the // answer is anything other than GoForIt then we cannot // take the fast I/O path. // ASSERT(FILE_WRITE_TO_END_OF_FILE == 0xffffffff); if (!FastIoDispatch->FastIoCheckIfPossible( FileObject, FileOffset->QuadPart != (LONGLONG)-1 ? FileOffset : &Header->FileSize, Length, TRUE, LockKey, FALSE, // write operation &IoStatus, targetVdo )) { // // Fast I/O is not possible so release the Fcb and // return. // ExReleaseResourceLite( Header->Resource ); FsRtlExitFileSystem(); return FALSE; } } // // Now see if we will change FileSize. We have to do it now // so that our reads are not nooped. // if (NewFileSize > Header->FileSize.LowPart) { FileSizeChanged = TRUE; OldFileSize = Header->FileSize.LowPart; OldValidDataLength = Header->ValidDataLength.LowPart; Header->FileSize.LowPart = NewFileSize; } // // We can do fast i/o so call the cc routine to do the work // and then release the fcb when we've done. If for whatever // reason the copy write fails, then return FALSE to our // caller. // // Also mark this as the top level "Irp" so that lower file // system levels will not attempt a pop-up // PsGetCurrentThread()->TopLevelIrp = TopLevelIrpValue; try { // // See if we have to do some zeroing // if (Offset > Header->ValidDataLength.LowPart) { LARGE_INTEGER ZeroEnd; ZeroEnd.LowPart = Offset; ZeroEnd.HighPart = 0; CcZeroData( FileObject, &Header->ValidDataLength, &ZeroEnd, TRUE ); } CcFastCopyWrite( FileObject, Offset, Length, Buffer ); } except( FsRtlIsNtstatusExpected(GetExceptionCode()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) { Status = FALSE; } PsGetCurrentThread()->TopLevelIrp = 0; // // If we succeeded, see if we have to update FileSize or // ValidDataLength. // if (Status) { // // In the case of ValidDataLength, we really have to // check again since we did not do this when we acquired // the resource exclusive. // if (NewFileSize > Header->ValidDataLength.LowPart) { Header->ValidDataLength.LowPart = NewFileSize; } // // Set this handle as having modified the file // FileObject->Flags |= FO_FILE_MODIFIED; if (FileSizeChanged) { CcGetFileSizePointer(FileObject)->LowPart = NewFileSize; FileObject->Flags |= FO_FILE_SIZE_CHANGED; } // // Also update the file position pointer // FileObject->CurrentByteOffset.LowPart = Offset + Length; FileObject->CurrentByteOffset.HighPart = 0; // // If we did not succeed, then we must restore the original // FileSize while holding the PagingIoResource exclusive if // it exists. // } else if (FileSizeChanged) { if ( Header->PagingIoResource != NULL ) { (VOID)ExAcquireResourceExclusiveLite( Header->PagingIoResource, TRUE ); Header->FileSize.LowPart = OldFileSize; Header->ValidDataLength.LowPart = OldValidDataLength; ExReleaseResourceLite( Header->PagingIoResource ); } else { Header->FileSize.LowPart = OldFileSize; Header->ValidDataLength.LowPart = OldValidDataLength; } } // // Here is the 64-bit or no-wait path. // } else { LARGE_INTEGER Offset, NewFileSize; LARGE_INTEGER OldFileSize; LARGE_INTEGER OldValidDataLength; ASSERT(!KeIsExecutingDpc()); // // Make our best guess on whether we need the file exclusive // or shared. // NewFileSize.QuadPart = FileOffset->QuadPart + (LONGLONG)Length; if (WriteToEndOfFile || (NewFileSize.QuadPart > Header->ValidDataLength.QuadPart)) { // // Acquired shared on the common fcb header, and return // if we don't get it. // if (!ExAcquireResourceExclusiveLite( Header->Resource, Wait )) { FsRtlExitFileSystem(); return FALSE; } } else { // // Acquired shared on the common fcb header, and return // if we don't get it. // if (!ExAcquireResourceSharedLite( Header->Resource, Wait )) { FsRtlExitFileSystem(); return FALSE; } AcquiredShared = TRUE; } // // We have the fcb shared now check if we can do fast i/o // and if the file space is allocated, and if not then // release the fcb and return. // if (WriteToEndOfFile) { Offset = Header->FileSize; NewFileSize.QuadPart = Header->FileSize.QuadPart + (LONGLONG)Length; } else { Offset = *FileOffset; NewFileSize.QuadPart = FileOffset->QuadPart + (LONGLONG)Length; } // // Now that the File is acquired shared, we can safely test // if it is really cached and if we can do fast i/o and we // do not have to extend. If not then release the fcb and // return. // // Get out if we are about to zero too much as well, as commented above. // if ((FileObject->PrivateCacheMap == NULL) || (Header->IsFastIoPossible == FastIoIsNotPossible) || (Offset.QuadPart >= (Header->ValidDataLength.QuadPart + 0x2000)) || ( NewFileSize.QuadPart > Header->AllocationSize.QuadPart ) ) { ExReleaseResourceLite( Header->Resource ); FsRtlExitFileSystem(); return FALSE; } // // If we will be extending ValidDataLength, we will have to // get the Fcb exclusive, and make sure that FastIo is still // possible. We should only execute this block of code very // rarely, when the unsafe test for ValidDataLength failed // above. // if (AcquiredShared && ( NewFileSize.QuadPart > Header->ValidDataLength.QuadPart )) { ExReleaseResourceLite( Header->Resource ); if (!ExAcquireResourceExclusiveLite( Header->Resource, Wait )) { FsRtlExitFileSystem(); return FALSE; } // // If writing to end of file, we must recalculate new size. // if (WriteToEndOfFile) { Offset = Header->FileSize; NewFileSize.QuadPart = Header->FileSize.QuadPart + (LONGLONG)Length; } if ((FileObject->PrivateCacheMap == NULL) || (Header->IsFastIoPossible == FastIoIsNotPossible) || ( NewFileSize.QuadPart > Header->AllocationSize.QuadPart ) ) { ExReleaseResourceLite( Header->Resource ); FsRtlExitFileSystem(); return FALSE; } } // // Check if fast I/O is questionable and if so then go ask // the file system the answer // if (Header->IsFastIoPossible == FastIoIsQuestionable) { PDEVICE_OBJECT targetVdo = IoGetRelatedDeviceObject( FileObject ); PFAST_IO_DISPATCH FastIoDispatch = targetVdo->DriverObject->FastIoDispatch; IO_STATUS_BLOCK IoStatus; // // All file system then set "Is Questionable" had better // support fast I/O // ASSERT(FastIoDispatch != NULL); ASSERT(FastIoDispatch->FastIoCheckIfPossible != NULL); // // Call the file system to check for fast I/O. If the // answer is anything other than GoForIt then we cannot // take the fast I/O path. // ASSERT(FILE_WRITE_TO_END_OF_FILE == 0xffffffff); if (!FastIoDispatch->FastIoCheckIfPossible( FileObject, FileOffset->QuadPart != (LONGLONG)-1 ? FileOffset : &Header->FileSize, Length, Wait, LockKey, FALSE, // write operation &IoStatus, targetVdo )) { // // Fast I/O is not possible so release the Fcb and // return. // ExReleaseResourceLite( Header->Resource ); FsRtlExitFileSystem(); return FALSE; } } // // Now see if we will change FileSize. We have to do it now // so that our reads are not nooped. // if ( NewFileSize.QuadPart > Header->FileSize.QuadPart ) { FileSizeChanged = TRUE; OldFileSize = Header->FileSize; OldValidDataLength = Header->ValidDataLength; // // Deal with an extremely rare pathalogical case here the // file size wraps. // if ( (Header->FileSize.HighPart != NewFileSize.HighPart) && (Header->PagingIoResource != NULL) ) { (VOID)ExAcquireResourceExclusiveLite( Header->PagingIoResource, TRUE ); Header->FileSize = NewFileSize; ExReleaseResourceLite( Header->PagingIoResource ); } else { Header->FileSize = NewFileSize; } } // // We can do fast i/o so call the cc routine to do the work // and then release the fcb when we've done. If for whatever // reason the copy write fails, then return FALSE to our // caller. // // Also mark this as the top level "Irp" so that lower file // system levels will not attempt a pop-up // PsGetCurrentThread()->TopLevelIrp = TopLevelIrpValue; try { // // See if we have to do some zeroing // if ( Offset.QuadPart > Header->ValidDataLength.QuadPart ) { Status = CcZeroData( FileObject, &Header->ValidDataLength, &Offset, Wait ); } if (Status) { Status = CcCopyWrite( FileObject, &Offset, Length, Wait, Buffer ); } } except( FsRtlIsNtstatusExpected(GetExceptionCode()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) { Status = FALSE; } PsGetCurrentThread()->TopLevelIrp = 0; // // If we succeeded, see if we have to update FileSize or // ValidDataLength. // if (Status) { // // In the case of ValidDataLength, we really have to // check again since we did not do this when we acquired // the resource exclusive. // if ( NewFileSize.QuadPart > Header->ValidDataLength.QuadPart ) { // // Deal with an extremely rare pathalogical case here // the ValidDataLength wraps. // if ( (Header->ValidDataLength.HighPart != NewFileSize.HighPart) && (Header->PagingIoResource != NULL) ) { (VOID)ExAcquireResourceExclusiveLite( Header->PagingIoResource, TRUE ); Header->ValidDataLength = NewFileSize; ExReleaseResourceLite( Header->PagingIoResource ); } else { Header->ValidDataLength = NewFileSize; } } // // Set this handle as having modified the file // FileObject->Flags |= FO_FILE_MODIFIED; if (FileSizeChanged) { *CcGetFileSizePointer(FileObject) = NewFileSize; FileObject->Flags |= FO_FILE_SIZE_CHANGED; } // // Also update the current file position pointer // FileObject->CurrentByteOffset.QuadPart = Offset.QuadPart + Length; // // If we did not succeed, then we must restore the original // FileSize while holding the PagingIoResource exclusive if // it exists. // } else if (FileSizeChanged) { if ( Header->PagingIoResource != NULL ) { (VOID)ExAcquireResourceExclusiveLite( Header->PagingIoResource, TRUE ); Header->FileSize = OldFileSize; Header->ValidDataLength = OldValidDataLength; ExReleaseResourceLite( Header->PagingIoResource ); } else { Header->FileSize = OldFileSize; Header->ValidDataLength = OldValidDataLength; } } } ExReleaseResourceLite( Header->Resource ); FsRtlExitFileSystem(); return Status; } else { // // A zero length transfer was requested. // return TRUE; } } else { // // The volume must be verified or the file is write through. // return FALSE; } }