/*++ Copyright (c) 1991 Microsoft Corporation Module Name: Read.c Abstract: This module implements the File Read routine for Ntfs called by the dispatch driver. Author: Brian Andrew BrianAn 15-Aug-1991 Revision History: --*/ #include "NtfsProc.h" // // The local debug trace level // #define Dbg (DEBUG_TRACE_READ) // // Define stack overflow read threshhold. // #ifdef _X86_ #if DBG #define OVERFLOW_READ_THRESHHOLD (0xB80) #else #define OVERFLOW_READ_THRESHHOLD (0xA00) #endif #else #define OVERFLOW_READ_THRESHHOLD (0x1000) #endif // _X86_ // // Local procedure prototypes // // // The following procedure is used to handling read stack overflow operations. // VOID NtfsStackOverflowRead ( IN PVOID Context, IN PKEVENT Event ); // // VOID // SafeZeroMemory ( // IN PUCHAR At, // IN ULONG ByteCount // ); // // // This macro just puts a nice little try-except around RtlZeroMemory // #define SafeZeroMemory(AT,BYTE_COUNT) { \ try { \ RtlZeroMemory((AT), (BYTE_COUNT)); \ } except(EXCEPTION_EXECUTE_HANDLER) { \ NtfsRaiseStatus( IrpContext, STATUS_INVALID_USER_BUFFER, NULL, NULL );\ } \ } #define CollectReadStats(VCB,OPEN_TYPE,SCB,FCB,BYTE_COUNT) { \ PFILESYSTEM_STATISTICS FsStats = &(VCB)->Statistics[KeGetCurrentProcessorNumber()]; \ if (!FlagOn( (FCB)->FcbState, FCB_STATE_SYSTEM_FILE)) { \ if (NtfsIsTypeCodeUserData( (SCB)->AttributeTypeCode )) { \ FsStats->UserFileReads += 1; \ FsStats->UserFileReadBytes += (ULONG)(BYTE_COUNT); \ } else { \ FsStats->Ntfs.UserIndexReads += 1; \ FsStats->Ntfs.UserIndexReadBytes += (ULONG)(BYTE_COUNT); \ } \ } else { \ FsStats->MetaDataReads += 1; \ FsStats->MetaDataReadBytes += (ULONG)(BYTE_COUNT); \ \ if ((SCB) == (VCB)->MftScb) { \ FsStats->Ntfs.MftReads += 1; \ FsStats->Ntfs.MftReadBytes += (ULONG)(BYTE_COUNT); \ } else if ((SCB) == (VCB)->RootIndexScb) { \ FsStats->Ntfs.RootIndexReads += 1; \ FsStats->Ntfs.RootIndexReadBytes += (ULONG)(BYTE_COUNT); \ } else if ((SCB) == (VCB)->BitmapScb) { \ FsStats->Ntfs.BitmapReads += 1; \ FsStats->Ntfs.BitmapReadBytes += (ULONG)(BYTE_COUNT); \ } else if ((SCB) == (VCB)->MftBitmapScb) { \ FsStats->Ntfs.MftBitmapReads += 1; \ FsStats->Ntfs.MftBitmapReadBytes += (ULONG)(BYTE_COUNT); \ } \ } \ } NTSTATUS NtfsFsdRead ( IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject, IN PIRP Irp ) /*++ Routine Description: This is the driver entry to the common read routine for NtReadFile calls. For synchronous requests, the CommonRead is called with Wait == TRUE, which means the request will always be completed in the current thread, and never passed to the Fsp. If it is not a synchronous request, CommonRead is called with Wait == FALSE, which means the request will be passed to the Fsp only if there is a need to block. Arguments: VolumeDeviceObject - Supplies the volume device object where the file exists Irp - Supplies the Irp being processed Return Value: NTSTATUS - The FSD status for the IRP --*/ { TOP_LEVEL_CONTEXT TopLevelContext; PTOP_LEVEL_CONTEXT ThreadTopLevelContext; NTSTATUS Status = STATUS_SUCCESS; PIRP_CONTEXT IrpContext = NULL; ULONG RetryCount = 0; UNREFERENCED_PARAMETER( VolumeDeviceObject ); ASSERT_IRP( Irp ); DebugTrace( +1, Dbg, ("NtfsFsdRead\n") ); // // Call the common Read routine // FsRtlEnterFileSystem(); // // Always make the reads appear to be top level. As long as we don't have // log file full we won't post these requests. This will prevent paging // reads from trying to attach to uninitialized top level requests. // ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, TRUE, TRUE ); do { try { // // We are either initiating this request or retrying it. // if (IrpContext == NULL) { IrpContext = NtfsCreateIrpContext( Irp, CanFsdWait( Irp ) ); NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext ); if (ThreadTopLevelContext->ScbBeingHotFixed != NULL) { SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_HOTFIX_UNDERWAY ); } } else if (Status == STATUS_LOG_FILE_FULL) { NtfsCheckpointForLogFileFull( IrpContext ); } // // If this is an Mdl complete request, don't go through // common read. // ASSERT(!FlagOn( IrpContext->MinorFunction, IRP_MN_DPC )); if (FlagOn( IrpContext->MinorFunction, IRP_MN_COMPLETE )) { DebugTrace( 0, Dbg, ("Calling NtfsCompleteMdl\n") ); Status = NtfsCompleteMdl( IrpContext, Irp ); // // Check if we have enough stack space to process this request. If there // isn't enough then we will create a new thread to process this single // request // } else if (IoGetRemainingStackSize() < OVERFLOW_READ_THRESHHOLD) { PKEVENT Event; PFILE_OBJECT FileObject; TYPE_OF_OPEN TypeOfOpen; PVCB Vcb; PFCB Fcb; PSCB Scb; PCCB Ccb; PERESOURCE Resource; DebugTrace( 0, Dbg, ("Getting too close to stack limit pass request to Fsp\n") ); // // Decode the file object to get the Scb // FileObject = IoGetCurrentIrpStackLocation(Irp)->FileObject; TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE ); // // We cannot post any compressed reads, because that would interfere // with our reserved buffer strategy. We may currently own // NtfsReservedBufferResource, and it is important for our read to // be able to get a buffer. // ASSERT(Scb->CompressionUnit == 0); // // Allocate an event and get shared on the scb. We won't grab the // Scb for the paging file path or for non-cached io for our // system files. // Event = (PKEVENT)ExAllocateFromNPagedLookasideList( &NtfsKeventLookasideList ); KeInitializeEvent( Event, NotificationEvent, FALSE ); if ((FlagOn( Fcb->FcbState, FCB_STATE_PAGING_FILE ) && FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) || (NtfsLeqMftRef( &Fcb->FileReference, &VolumeFileReference ))) { // // There is nothing to release in this case. // Resource = NULL; } else { Resource = Scb->Header.Resource; ExAcquireResourceShared( Resource, TRUE ); } try { // // Make the Irp just like a regular post request and // then send the Irp to the special overflow thread. // After the post we will wait for the stack overflow // read routine to set the event that indicates we can // now release the scb resource and return. // NtfsPrePostIrp( IrpContext, Irp ); if (FlagOn( Fcb->FcbState, FCB_STATE_PAGING_FILE ) && FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) { FsRtlPostPagingFileStackOverflow( IrpContext, Event, NtfsStackOverflowRead ); } else { FsRtlPostStackOverflow( IrpContext, Event, NtfsStackOverflowRead ); } // // And wait for the worker thread to complete the item // (VOID) KeWaitForSingleObject( Event, Executive, KernelMode, FALSE, NULL ); Status = STATUS_PENDING; } finally { if (Resource != NULL) { ExReleaseResource( Resource ); } ExFreeToNPagedLookasideList( &NtfsKeventLookasideList, Event ); } // // Identify read requests which can't wait and post them to the // Fsp. // } else { // // Capture the auxiliary buffer and clear its address if it // is not supposed to be deleted by the I/O system on I/O completion. // if (Irp->Tail.Overlay.AuxiliaryBuffer != NULL) { IrpContext->Union.AuxiliaryBuffer = (PFSRTL_AUXILIARY_BUFFER)Irp->Tail.Overlay.AuxiliaryBuffer; if (!FlagOn(IrpContext->Union.AuxiliaryBuffer->Flags, FSRTL_AUXILIARY_FLAG_DEALLOCATE)) { Irp->Tail.Overlay.AuxiliaryBuffer = NULL; } } Status = NtfsCommonRead( IrpContext, Irp, TRUE ); } break; } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) { NTSTATUS ExceptionCode; // // We had some trouble trying to perform the requested // operation, so we'll abort the I/O request with // the error status that we get back from the // execption code // ExceptionCode = GetExceptionCode(); if (ExceptionCode == STATUS_FILE_DELETED) { IrpContext->ExceptionStatus = ExceptionCode = STATUS_END_OF_FILE; Irp->IoStatus.Information = 0; } Status = NtfsProcessException( IrpContext, Irp, ExceptionCode ); } // // Retry if this is a top level request, and the Irp was not completed due // to a retryable error. // RetryCount += 1; } while ((Status == STATUS_CANT_WAIT || Status == STATUS_LOG_FILE_FULL) && TopLevelContext.TopLevelRequest); if (ThreadTopLevelContext == &TopLevelContext) { NtfsRestoreTopLevelIrp( ThreadTopLevelContext ); } FsRtlExitFileSystem(); // // And return to our caller // DebugTrace( -1, Dbg, ("NtfsFsdRead -> %08lx\n", Status) ); return Status; } // // Internal support routine // VOID NtfsStackOverflowRead ( IN PVOID Context, IN PKEVENT Event ) /*++ Routine Description: This routine processes a read request that could not be processed by the fsp thread because of stack overflow potential. Arguments: Context - Supplies the IrpContext being processed Event - Supplies the event to be signaled when we are done processing this request. Return Value: None. --*/ { TOP_LEVEL_CONTEXT TopLevelContext; PTOP_LEVEL_CONTEXT ThreadTopLevelContext; PIRP_CONTEXT IrpContext = Context; // // Make it now look like we can wait for I/O to complete // SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT ); ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, TRUE, FALSE ); // // Do the read operation protected by a try-except clause // try { NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext ); // // Set the flag to indicate that we are in the overflow thread. // TopLevelContext.OverflowReadThread = TRUE; (VOID) NtfsCommonRead( IrpContext, IrpContext->OriginatingIrp, FALSE ); } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) { NTSTATUS ExceptionCode; // // We had some trouble trying to perform the requested // operation, so we'll abort the I/O request with // the error status that we get back from the // execption code // ExceptionCode = GetExceptionCode(); if (ExceptionCode == STATUS_FILE_DELETED) { IrpContext->ExceptionStatus = ExceptionCode = STATUS_END_OF_FILE; IrpContext->OriginatingIrp->IoStatus.Information = 0; } (VOID) NtfsProcessException( IrpContext, IrpContext->OriginatingIrp, ExceptionCode ); } NtfsRestoreTopLevelIrp( ThreadTopLevelContext ); // // Set the stack overflow item's event to tell the original // thread that we're done and then go get another work item. // KeSetEvent( Event, 0, FALSE ); } NTSTATUS NtfsCommonRead ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp, IN BOOLEAN AcquireScb ) /*++ Routine Description: This is the common routine for Read called by both the fsd and fsp threads. Arguments: Irp - Supplies the Irp to process AcquireScb - Indicates if this routine should acquire the scb Return Value: NTSTATUS - The return status for the operation --*/ { NTSTATUS Status; PIO_STACK_LOCATION IrpSp; PFILE_OBJECT FileObject; TYPE_OF_OPEN TypeOfOpen; PVCB Vcb; PFCB Fcb; PSCB Scb; PCCB Ccb; ATTRIBUTE_ENUMERATION_CONTEXT AttrContext; EOF_WAIT_BLOCK EofWaitBlock; PFSRTL_ADVANCED_FCB_HEADER Header; PTOP_LEVEL_CONTEXT TopLevelContext; VBO StartingVbo; LONGLONG ByteCount; LONGLONG ByteRange; ULONG RequestedByteCount; PBCB Bcb = NULL; BOOLEAN FoundAttribute = FALSE; BOOLEAN PostIrp = FALSE; BOOLEAN OplockPostIrp = FALSE; BOOLEAN ScbAcquired = FALSE; BOOLEAN ReleaseScb; BOOLEAN PagingIoAcquired = FALSE; BOOLEAN DoingIoAtEof = FALSE; BOOLEAN Wait; BOOLEAN PagingIo; BOOLEAN NonCachedIo; BOOLEAN SynchronousIo; NTFS_IO_CONTEXT LocalContext; // // A system buffer is only used if we have to access the // buffer directly from the Fsp to clear a portion or to // do a synchronous I/O, or a cached transfer. It is // possible that our caller may have already mapped a // system buffer, in which case we must remember this so // we do not unmap it on the way out. // PVOID SystemBuffer = NULL; ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_IRP( Irp ); // // Get the current Irp stack location // IrpSp = IoGetCurrentIrpStackLocation( Irp ); DebugTrace( +1, Dbg, ("NtfsCommonRead\n") ); DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) ); DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) ); DebugTrace( 0, Dbg, ("ByteCount = %08lx\n", IrpSp->Parameters.Read.Length) ); DebugTrace( 0, Dbg, ("ByteOffset = %016I64x\n", IrpSp->Parameters.Read.ByteOffset) ); // // Extract and decode the file object // FileObject = IrpSp->FileObject; TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE ); // // Let's kill invalid read requests. // if (TypeOfOpen != UserFileOpen && #ifdef _CAIRO_ DebugDoit( TypeOfOpen != UserPropertySetOpen && ) #endif // _CAIRO_ TypeOfOpen != UserVolumeOpen && TypeOfOpen != StreamFileOpen) { DebugTrace( 0, Dbg, ("Invalid file object for read\n") ); DebugTrace( -1, Dbg, ("NtfsCommonRead: Exit -> %08lx\n", STATUS_INVALID_DEVICE_REQUEST) ); NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_DEVICE_REQUEST ); return STATUS_INVALID_DEVICE_REQUEST; } // // Initialize the appropriate local variables. // Wait = BooleanFlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT ); PagingIo = BooleanFlagOn( Irp->Flags, IRP_PAGING_IO ); NonCachedIo = BooleanFlagOn( Irp->Flags,IRP_NOCACHE ); SynchronousIo = BooleanFlagOn( FileObject->Flags, FO_SYNCHRONOUS_IO ); // // Extract starting Vbo and offset. // StartingVbo = IrpSp->Parameters.Read.ByteOffset.QuadPart; ByteCount = IrpSp->Parameters.Read.Length; ByteRange = StartingVbo + ByteCount; RequestedByteCount = (ULONG)ByteCount; // // Check for a null request, and return immediately // if ((ULONG)ByteCount == 0) { DebugTrace( 0, Dbg, ("No bytes to read\n") ); DebugTrace( -1, Dbg, ("NtfsCommonRead: Exit -> %08lx\n", STATUS_SUCCESS) ); NtfsCompleteRequest( &IrpContext, &Irp, STATUS_SUCCESS ); return STATUS_SUCCESS; } // // Make sure there is an initialized NtfsIoContext block. // if (TypeOfOpen == UserVolumeOpen || NonCachedIo) { // // If there is a context pointer, we need to make sure it was // allocated and not a stale stack pointer. // if (IrpContext->Union.NtfsIoContext == NULL || !FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ALLOC_CONTEXT )) { // // If we can wait, use the context on the stack. Otherwise // we need to allocate one. // if (Wait) { IrpContext->Union.NtfsIoContext = &LocalContext; ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ALLOC_CONTEXT ); } else { IrpContext->Union.NtfsIoContext = (PNTFS_IO_CONTEXT)ExAllocateFromNPagedLookasideList( &NtfsIoContextLookasideList ); SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ALLOC_CONTEXT ); } } RtlZeroMemory( IrpContext->Union.NtfsIoContext, sizeof( NTFS_IO_CONTEXT )); // // Store whether we allocated this context structure in the structure // itself. // IrpContext->Union.NtfsIoContext->AllocatedContext = BooleanFlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ALLOC_CONTEXT ); if (Wait) { KeInitializeEvent( &IrpContext->Union.NtfsIoContext->Wait.SyncEvent, NotificationEvent, FALSE ); } else { IrpContext->Union.NtfsIoContext->PagingIo = PagingIo; IrpContext->Union.NtfsIoContext->Wait.Async.ResourceThreadId = ExGetCurrentResourceThread(); IrpContext->Union.NtfsIoContext->Wait.Async.RequestedByteCount = (ULONG)ByteCount; } } // // Handle volume Dasd here. // if (TypeOfOpen == UserVolumeOpen) { NTSTATUS Status; // // If the caller has not asked for extended DASD IO access then // limit with the volume size. // if (!FlagOn( Ccb->Flags, CCB_FLAG_ALLOW_XTENDED_DASD_IO )) { // // If the starting vbo is past the end of the volume, we are done. // if (Scb->Header.FileSize.QuadPart <= StartingVbo) { DebugTrace( 0, Dbg, ("No bytes to read\n") ); DebugTrace( -1, Dbg, ("NtfsCommonRead: Exit -> %08lx\n", STATUS_END_OF_FILE) ); NtfsCompleteRequest( &IrpContext, &Irp, STATUS_END_OF_FILE ); return STATUS_END_OF_FILE; // // If the write extends beyond the end of the volume, truncate the // bytes to write. // } else if (Scb->Header.FileSize.QuadPart < ByteRange) { ByteCount = Scb->Header.FileSize.QuadPart - StartingVbo; if (!Wait) { IrpContext->Union.NtfsIoContext->Wait.Async.RequestedByteCount = (ULONG)ByteCount; } } } Status = NtfsVolumeDasdIo( IrpContext, Irp, Vcb, StartingVbo, (ULONG)ByteCount ); // // If the volume was opened for Synchronous IO, update the current // file position. // if (SynchronousIo && !PagingIo && NT_SUCCESS(Status)) { IrpSp->FileObject->CurrentByteOffset.QuadPart = StartingVbo + Irp->IoStatus.Information; } DebugTrace( 0, Dbg, ("Complete with %08lx bytes read\n", Irp->IoStatus.Information) ); DebugTrace( -1, Dbg, ("NtfsCommonRead: Exit -> %08lx\n", Status) ); if (Wait) { NtfsCompleteRequest( &IrpContext, &Irp, Status ); } return Status; } // // Keep a pointer to the common fsrtl header. // Header = &Scb->Header; // // If this is a paging file, just send it to the device driver. // We assume Mm is a good citizen. // if (FlagOn( Fcb->FcbState, FCB_STATE_PAGING_FILE ) && FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) { if (FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED )) { NtfsRaiseStatus( IrpContext, STATUS_FILE_DELETED, NULL, NULL ); } // // Do the usual STATUS_PENDING things. // IoMarkIrpPending( Irp ); // // Perform the actual IO, it will be completed when the io finishes. // NtfsPagingFileIo( IrpContext, Irp, Scb, StartingVbo, (ULONG)ByteCount ); // // We, nor anybody else, need the IrpContext any more. // NtfsCompleteRequest( &IrpContext, NULL, 0 ); return STATUS_PENDING; } // // Accumulate interesting statistics. // if (PagingIo) { CollectReadStats( Vcb, TypeOfOpen, Scb, Fcb, ByteCount ); } // // Use a try-finally to free Scb and buffers on the way out. // At this point we can treat all requests identically since we // have a usable Scb for each of them. (Volume, User or Stream file) // try { // // This case corresponds to a non-directory file read. // LONGLONG FileSize; LONGLONG ValidDataLength; // // If this is a noncached transfer and is not a paging I/O, and // the file has a data section, then we will do a flush here // to avoid stale data problems. Note that we must flush before // acquiring the Fcb shared since the write may try to acquire // it exclusive. This is not necessary for compressed files, since // we will turn user noncached writes into cached writes. // if (!PagingIo && NonCachedIo && (FileObject->SectionObjectPointer->DataSectionObject != NULL)) { ExAcquireResourceShared( Scb->Header.PagingIoResource, TRUE ); if (Scb->CompressionUnit == 0) { // // It is possible that this read is part of a top level request or // is being called by MM to create an image section. We will update // the top-level context to reflect this. All of the exception // handling will correctly handle the log file full in this case. // TopLevelContext = NtfsGetTopLevelContext(); if (TopLevelContext->SavedTopLevelIrp != NULL) { TopLevelContext->TopLevelRequest = FALSE; } CcFlushCache( FileObject->SectionObjectPointer, (PLARGE_INTEGER)&StartingVbo, (ULONG)ByteCount, &Irp->IoStatus ); // // Make sure the data got out to disk. // ExReleaseResource( Scb->Header.PagingIoResource ); ExAcquireResourceExclusive( Scb->Header.PagingIoResource, TRUE ); ExReleaseResource( Scb->Header.PagingIoResource ); // // Check for errors in the flush. // NtfsNormalizeAndCleanupTransaction( IrpContext, &Irp->IoStatus.Status, TRUE, STATUS_UNEXPECTED_IO_ERROR ); } else { ExReleaseResource( Scb->Header.PagingIoResource ); } } // // We need shared access to the Scb before proceeding. // We won't acquire the Scb for a non-cached read of the first 4 // file records. // if (AcquireScb && (!NonCachedIo || NtfsGtrMftRef( &Fcb->FileReference, &VolumeFileReference))) { // // Figure out if we have been entered during the posting // of a top level request. // TopLevelContext = NtfsGetTopLevelContext(); // // Initially we always force reads to appear to be top level // requests. If we reach this point the read not to the paging // file so it is safe to determine if we are really a top level // request. If there is an Ntfs request above us we will clear // the TopLevelRequest field in the TopLevelContext. // if (TopLevelContext->ValidSavedTopLevel) { TopLevelContext->TopLevelRequest = FALSE; } // // If this is not a paging I/O (cached or user noncached I/O), // then acquire the paging I/O resource. (Note, you can only // do cached I/O to user streams, and they always have a paging // I/O resource. // if (!PagingIo) { // // If we cannot acquire the resource, then raise. // if (!ExAcquireSharedWaitForExclusive( Scb->Header.PagingIoResource, Wait )) { NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL ); } PagingIoAcquired = TRUE; // // Check if we have already gone through cleanup on this handle. // if (FlagOn( Ccb->Flags, CCB_FLAG_CLEANUP )) { NtfsRaiseStatus( IrpContext, STATUS_FILE_CLOSED, NULL, NULL ); } // // The reason that we always handle the user requests through the cache, // is that there is no better way to deal with alignment issues, for // the frequent case where the user noncached I/O is not an integral of // the Compression Unit. Also, the way we synchronize the case where // a compression unit is being moved to a different spot on disk during // a write, is to keep the pages locked in memory during the write, so // that there will be no need to read the disk at the same time. (If // we allowed real noncached I/O, then we would somehow have to synchronize // the noncached read with the write of the same data.) // // Bottom line is we can only really support cached reads to compresed // files. // if ((Scb->CompressionUnit != 0) && NonCachedIo) { NonCachedIo = FALSE; if (Scb->FileObject == NULL) { // // Make sure we are serialized with the FileSizes, and // will remove this condition if we abort. // FsRtlLockFsRtlHeader( Header ); IrpContext->FcbWithPagingExclusive = (PFCB)Scb; NtfsCreateInternalAttributeStream( IrpContext, Scb, FALSE ); FsRtlUnlockFsRtlHeader( Header ); IrpContext->FcbWithPagingExclusive = NULL; } FileObject = Scb->FileObject; } // // If this is async I/O directly to the disk we need to check that // we don't exhaust the number of times a single thread can // acquire the resource. // if (!Wait && NonCachedIo) { if (ExIsResourceAcquiredShared(Scb->Header.PagingIoResource) > MAX_SCB_ASYNC_ACQUIRE) { NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL ); } IrpContext->Union.NtfsIoContext->Wait.Async.Resource = Scb->Header.PagingIoResource; } // // Now check if the attribute has been deleted or if the // volume has been dismounted. // if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED | SCB_STATE_VOLUME_DISMOUNTED)) { if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )) { NtfsRaiseStatus( IrpContext, STATUS_FILE_DELETED, NULL, NULL ); } else { NtfsRaiseStatus( IrpContext, STATUS_VOLUME_DISMOUNTED, NULL, NULL ); } } // // If this is a paging I/O, and there is a paging I/O resource, then // we acquire the main resource here. Note that for most paging I/Os // (like faulting for cached I/O), we already own the paging I/O resource, // so we acquire nothing here! But, for other cases like user-mapped files, // we do check if paging I/O is acquired, and acquire the main resource if // not. The point is, we need some guarantee still that the file will not // be truncated. // } else if ((Scb->Header.PagingIoResource != NULL) && !ExIsResourceAcquiredShared(Scb->Header.PagingIoResource)) { // // If we cannot acquire the resource, then raise. // if (!ExAcquireResourceShared( Scb->Header.Resource, Wait )) { NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL ); } ScbAcquired = TRUE; // // Now check if the attribute has been deleted or if the // volume has been dismounted. // if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED | SCB_STATE_VOLUME_DISMOUNTED)) { if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )) { NtfsRaiseStatus( IrpContext, STATUS_FILE_DELETED, NULL, NULL ); } else { NtfsRaiseStatus( IrpContext, STATUS_VOLUME_DISMOUNTED, NULL, NULL ); } } } } // // If the Scb is uninitialized, we initialize it now. // if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) { DebugTrace( 0, Dbg, ("Initializing Scb -> %08lx\n", Scb) ); ReleaseScb = FALSE; if (AcquireScb && !ScbAcquired) { ExAcquireResourceShared( Scb->Header.Resource, TRUE ); ScbAcquired = TRUE; ReleaseScb = TRUE; } NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL ); if (ReleaseScb) { ExReleaseResource( Scb->Header.Resource ); ScbAcquired = FALSE; } } // // We check whether we can proceed // based on the state of the file oplocks. // if (TypeOfOpen == UserFileOpen) { Status = FsRtlCheckOplock( &Scb->ScbType.Data.Oplock, Irp, IrpContext, NtfsOplockComplete, NtfsPrePostIrp ); if (Status != STATUS_SUCCESS) { OplockPostIrp = TRUE; PostIrp = TRUE; try_return( NOTHING ); } // // This oplock call can affect whether fast IO is possible. // We may have broken an oplock to no oplock held. If the // current state of the file is FastIoIsNotPossible then // recheck the fast IO state. // if (Scb->Header.IsFastIoPossible == FastIoIsNotPossible) { NtfsAcquireFsrtlHeader( Scb ); Scb->Header.IsFastIoPossible = NtfsIsFastIoPossible( Scb ); NtfsReleaseFsrtlHeader( Scb ); } // // We have to check for read access according to the current // state of the file locks. // if (!PagingIo && Scb->ScbType.Data.FileLock != NULL && !FsRtlCheckLockForReadAccess( Scb->ScbType.Data.FileLock, Irp )) { try_return( Status = STATUS_FILE_LOCK_CONFLICT ); } } // // Now synchronize with the FsRtl Header // ExAcquireFastMutex( Header->FastMutex ); // // Now see if we are reading beyond ValidDataLength. We have to // do it now so that our reads are not nooped. We only need to block // on nonrecursive I/O (cached or page fault to user section, because // if it is paging I/O, we must be part of a reader or writer who is // synchronized. // if ((ByteRange > Header->ValidDataLength.QuadPart) && !PagingIo) { // // We must serialize with anyone else doing I/O at beyond // ValidDataLength, and then remember if we need to declare // when we are done. If our caller has already serialized // with EOF then there is nothing for us to do here. // if ((IrpContext->TopLevelIrpContext->FcbWithPagingExclusive == Fcb) || (IrpContext->TopLevelIrpContext->FcbWithPagingExclusive == (PFCB) Scb)) { DoingIoAtEof = TRUE; } else { DoingIoAtEof = !FlagOn( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE ) || NtfsWaitForIoAtEof( Header, (PLARGE_INTEGER)&StartingVbo, (ULONG)ByteCount, &EofWaitBlock ); // // Set the Flag if we are in fact beyond ValidDataLength. // if (DoingIoAtEof) { SetFlag( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE ); IrpContext->FcbWithPagingExclusive = (PFCB) Scb; } } } // // Get file sizes from the Scb. // // We must get ValidDataLength first since it is always // increased second (the case we are unprotected) and // we don't want to capture ValidDataLength > FileSize. // ValidDataLength = Header->ValidDataLength.QuadPart; FileSize = Header->FileSize.QuadPart; ExReleaseFastMutex( Header->FastMutex ); // // Optimize for the case where we are trying to fault in an entire // compression unit, even if past the end of the file. Go ahead // and round the local FileSize to a compression unit boundary. // This will allow all of these pages to come into memory when // CC touches the first page out of memory. Otherwise CC will // force them into memory one page at a time. // if (PagingIo && (Scb->CompressionUnit != 0) && !FlagOn(Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT)) { FileSize += (Scb->CompressionUnit - 1); ((PLARGE_INTEGER) &FileSize)->LowPart &= ~(Scb->CompressionUnit - 1); } // // If the read starts beyond End of File, return EOF. // if (StartingVbo >= FileSize) { DebugTrace( 0, Dbg, ("End of File\n") ); try_return ( Status = STATUS_END_OF_FILE ); } // // If the read extends beyond EOF, truncate the read // if (ByteRange > FileSize) { ByteCount = FileSize - StartingVbo; ByteRange = StartingVbo + ByteCount; RequestedByteCount = (ULONG)ByteCount; if (NonCachedIo && !Wait) { IrpContext->Union.NtfsIoContext->Wait.Async.RequestedByteCount = (ULONG)ByteCount; } } // // HANDLE THE NONCACHED RESIDENT ATTRIBUTE CASE // // We let the cached case take the normal path for the following // reasons: // // o To insure data coherency if a user maps the file // o To get a page in the cache to keep the Fcb around // o So the data can be accessed via the Fast I/O path // // The disadvantage is the overhead to fault the data in the // first time, but we may be able to do this with asynchronous // read ahead. // if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT | SCB_STATE_CONVERT_UNDERWAY ) && NonCachedIo) { ReleaseScb = FALSE; if (AcquireScb && !ScbAcquired) { ExAcquireResourceShared( Scb->Header.Resource, TRUE ); ScbAcquired = TRUE; ReleaseScb = TRUE; } if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT ) && NonCachedIo) { PUCHAR AttrValue; // // Get hold of the user's buffer. // SystemBuffer = NtfsMapUserBuffer( Irp ); // // This is a resident attribute, we need to look it up // and copy the desired range of bytes to the user's // buffer. // NtfsInitializeAttributeContext( &AttrContext ); FoundAttribute = TRUE; NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &AttrContext ); AttrValue = NtfsAttributeValue( NtfsFoundAttribute( &AttrContext )); RtlCopyMemory( SystemBuffer, Add2Ptr( AttrValue, ((ULONG)StartingVbo) ), (ULONG)ByteCount ); Irp->IoStatus.Information = (ULONG)ByteCount; try_return( Status = STATUS_SUCCESS ); } else { if (ReleaseScb) { ExReleaseResource( Scb->Header.Resource ); ScbAcquired = FALSE; } } } // // HANDLE THE NON-CACHED CASE // if (NonCachedIo) { ULONG BytesToRead; ULONG SectorSize; ULONG ZeroOffset; ULONG ZeroLength = 0; DebugTrace( 0, Dbg, ("Non cached read.\n") ); // // For a compressed stream, which is user-mapped, reserve space // as pages come in. // if (FlagOn(Header->Flags, FSRTL_FLAG_USER_MAPPED_FILE) && FlagOn(Scb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK) && !NtfsReserveClusters(IrpContext, Scb, StartingVbo, (ULONG)ByteCount)) { NtfsRaiseStatus( IrpContext, STATUS_DISK_FULL, NULL, NULL ); } // // Start by zeroing any part of the read after Valid Data // if (ByteRange > ValidDataLength) { ReleaseScb = FALSE; // // We need to look at ValidDataToDisk, because it could be higher. // // // We have to have the main resource to look at ValidDataToDisk. // if (AcquireScb && !ScbAcquired) { ExAcquireResourceShared( Scb->Header.Resource, TRUE ); ScbAcquired = TRUE; ReleaseScb = TRUE; } // // If ValidDataToDisk is actually greater than // ValidDataLength, then we must have lost a page // during the middle of a write, and we should not // zero that data on the way back in! // if (ValidDataLength < Scb->ValidDataToDisk) { ValidDataLength = Scb->ValidDataToDisk; } if (ByteRange > ValidDataLength) { SystemBuffer = NtfsMapUserBuffer( Irp ); if (StartingVbo < ValidDataLength) { // // Assume we will zero the entire amount. // ZeroLength = (ULONG)ByteCount; // // The new byte count and the offset to start filling with zeroes. // ByteCount = ValidDataLength - StartingVbo; ZeroOffset = (ULONG)ByteCount; // // Now reduce the amount to zero by the zero offset. // ZeroLength -= ZeroOffset; // // If this was non-cached I/O then convert it to synchronous. // This is because we don't want to zero the buffer now or // we will lose the data when the driver purges the cache. // if (!Wait) { Wait = TRUE; SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT ); RtlZeroMemory( IrpContext->Union.NtfsIoContext, sizeof( NTFS_IO_CONTEXT )); // // Store whether we allocated this context structure in the structure // itself. // IrpContext->Union.NtfsIoContext->AllocatedContext = BooleanFlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ALLOC_CONTEXT ); KeInitializeEvent( &IrpContext->Union.NtfsIoContext->Wait.SyncEvent, NotificationEvent, FALSE ); } } else { // // All we have to do now is sit here and zero the // user's buffer, no reading is required. // SafeZeroMemory( (PUCHAR)SystemBuffer, (ULONG)ByteCount ); Irp->IoStatus.Information = (ULONG)ByteCount; try_return ( Status = STATUS_SUCCESS ); } } // // Now free the Scb if we only acquired it here. // if (ReleaseScb) { ExReleaseResource( Scb->Header.Resource ); ScbAcquired = FALSE; } } // // Get the sector size // SectorSize = Vcb->BytesPerSector; // // Round up to a sector boundry // BytesToRead = ((ULONG)ByteCount + (SectorSize - 1)) & ~(SectorSize - 1); // // Call a special routine if we do not have sector alignment // and the file is not compressed. // if ((((ULONG)StartingVbo) & (SectorSize - 1) || BytesToRead > IrpSp->Parameters.Read.Length) && (Scb->CompressionUnit == 0)) { // // If we can't wait, we must post this. // if (!Wait) { try_return( PostIrp = TRUE ); } // // Do the physical read. // ASSERT(FileObject->SectionObjectPointer == &Scb->NonpagedScb->SegmentObject); NtfsNonCachedNonAlignedIo( IrpContext, Irp, Scb, StartingVbo, (ULONG)ByteCount ); BytesToRead = (ULONG)ByteCount; } else { // // Just to help reduce confusion. At this point: // // RequestedByteCount - is the number of bytes originally // taken from the Irp, but constrained // to filesize. // // ByteCount - is RequestedByteCount constrained to // ValidDataLength. // // BytesToRead - is ByteCount rounded up to sector // boundry. This is the number of bytes // that we must physically read. // // // Perform the actual IO // if (NtfsNonCachedIo( IrpContext, Irp, Scb, StartingVbo, BytesToRead, (FileObject->SectionObjectPointer != &Scb->NonpagedScb->SegmentObject) ) == STATUS_PENDING) { IrpContext->Union.NtfsIoContext = NULL; PagingIoAcquired = FALSE; Irp = NULL; try_return( Status = STATUS_PENDING ); } } // // If the call didn't succeed, raise the error status // if (!NT_SUCCESS( Status = Irp->IoStatus.Status )) { NtfsNormalizeAndRaiseStatus( IrpContext, Status, STATUS_UNEXPECTED_IO_ERROR ); } // // Else set the Irp information field to reflect the // entire desired read. // ASSERT( Irp->IoStatus.Information == BytesToRead ); Irp->IoStatus.Information = RequestedByteCount; // // If we rounded up to a sector boundry before, zero out // the other garbage we read from the disk. // if (BytesToRead > (ULONG)ByteCount) { if (SystemBuffer == NULL) { SystemBuffer = NtfsMapUserBuffer( Irp ); } SafeZeroMemory( (PUCHAR)SystemBuffer + (ULONG)ByteCount, BytesToRead - (ULONG)ByteCount ); } // // If we need to zero the tail of the buffer because of valid data // then do so now. // if (ZeroLength != 0) { if (SystemBuffer == NULL) { SystemBuffer = NtfsMapUserBuffer( Irp ); } SafeZeroMemory( Add2Ptr( SystemBuffer, ZeroOffset ), ZeroLength ); } // // The transfer is complete. // try_return( Status ); } // if No Intermediate Buffering // // HANDLE THE CACHED CASE // else { // // We need to go through the cache for this // file object. First handle the noncompressed calls. // #ifdef _CAIRO_ if (!FlagOn(IrpContext->MinorFunction, IRP_MN_COMPRESSED)) { #endif _CAIRO_ // // We delay setting up the file cache until now, in case the // caller never does any I/O to the file, and thus // FileObject->PrivateCacheMap == NULL. // if (FileObject->PrivateCacheMap == NULL) { DebugTrace( 0, Dbg, ("Initialize cache mapping.\n") ); // // Now initialize the cache map. // // Make sure we are serialized with the FileSizes, and // will remove this condition if we abort. // if (!DoingIoAtEof) { FsRtlLockFsRtlHeader( Header ); IrpContext->FcbWithPagingExclusive = (PFCB)Scb; } CcInitializeCacheMap( FileObject, (PCC_FILE_SIZES)&Header->AllocationSize, FALSE, &NtfsData.CacheManagerCallbacks, Scb ); if (!DoingIoAtEof) { FsRtlUnlockFsRtlHeader( Header ); IrpContext->FcbWithPagingExclusive = NULL; } CcSetReadAheadGranularity( FileObject, READ_AHEAD_GRANULARITY ); } // // DO A NORMAL CACHED READ, if the MDL bit is not set, // DebugTrace( 0, Dbg, ("Cached read.\n") ); if (!FlagOn(IrpContext->MinorFunction, IRP_MN_MDL)) { // // Get hold of the user's buffer. // SystemBuffer = NtfsMapUserBuffer( Irp ); // // Now try to do the copy. // if (!CcCopyRead( FileObject, (PLARGE_INTEGER)&StartingVbo, (ULONG)ByteCount, Wait, SystemBuffer, &Irp->IoStatus )) { DebugTrace( 0, Dbg, ("Cached Read could not wait\n") ); try_return( PostIrp = TRUE ); } Status = Irp->IoStatus.Status; ASSERT( NT_SUCCESS( Status )); try_return( Status ); } // // HANDLE A MDL READ // else { DebugTrace( 0, Dbg, ("MDL read.\n") ); ASSERT( Wait ); CcMdlRead( FileObject, (PLARGE_INTEGER)&StartingVbo, (ULONG)ByteCount, &Irp->MdlAddress, &Irp->IoStatus ); Status = Irp->IoStatus.Status; ASSERT( NT_SUCCESS( Status )); try_return( Status ); } // // Handle the compressed calls. // #ifdef _CAIRO_ } else { PCOMPRESSED_DATA_INFO CompressedDataInfo; PMDL *NewMdl; ASSERT((StartingVbo & (NTFS_CHUNK_SIZE - 1)) == 0); if (FlagOn(Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT)) { try_return(Status = STATUS_INVALID_READ_MODE); } if ((Header->FileObjectC == NULL) || (Header->FileObjectC->PrivateCacheMap == NULL)) { // // Make sure we are serialized with the FileSizes, and // will remove this condition if we abort. // if (!DoingIoAtEof) { FsRtlLockFsRtlHeader( Header ); IrpContext->FcbWithPagingExclusive = (PFCB)Scb; } NtfsCreateInternalCompressedStream( IrpContext, Scb, FALSE ); if (!DoingIoAtEof) { FsRtlUnlockFsRtlHeader( Header ); IrpContext->FcbWithPagingExclusive = NULL; } } // // Assume success. // Irp->IoStatus.Status = Status = STATUS_SUCCESS; Irp->IoStatus.Information = (ULONG)(ByteRange - StartingVbo); // // Based on the Mdl minor function, set up the appropriate // parameters for the call below. // if (!FlagOn(IrpContext->MinorFunction, IRP_MN_MDL)) { // // Get hold of the user's buffer. // SystemBuffer = NtfsMapUserBuffer( Irp ); NewMdl = NULL; } else { // // We will deliver the Mdl directly to the Irp. // SystemBuffer = NULL; NewMdl = &Irp->MdlAddress; } CompressedDataInfo = (PCOMPRESSED_DATA_INFO)IrpContext->Union.AuxiliaryBuffer->Buffer; CompressedDataInfo->CompressionFormatAndEngine = (USHORT)((Scb->AttributeFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK) + 1); CompressedDataInfo->CompressionUnitShift = (UCHAR)(Scb->CompressionUnitShift + Vcb->ClusterShift); CompressedDataInfo->ChunkShift = NTFS_CHUNK_SHIFT; CompressedDataInfo->ClusterShift = (UCHAR)Vcb->ClusterShift; CompressedDataInfo->Reserved = 0; CompressedDataInfo->NumberOfChunks = 0; // // Do the compressed read in common code with the Fast Io path. // We do it from a loop because we may need to create the other // data stream. // while (TRUE) { Status = NtfsCompressedCopyRead( FileObject, (PLARGE_INTEGER)&StartingVbo, (ULONG)ByteCount, SystemBuffer, NewMdl, CompressedDataInfo, IrpContext->Union.AuxiliaryBuffer->Length, IoGetRelatedDeviceObject(FileObject), Header, Scb->CompressionUnit, NTFS_CHUNK_SIZE ); // // On successful Mdl requests we hang on to the PagingIo resource. // if ((NewMdl != NULL) && NT_SUCCESS(Status)) { PagingIoAcquired = FALSE; } // // Check for the status that says we need to create the normal // data stream, else we are done. // if (Status != STATUS_NOT_MAPPED_DATA) { break; } // // Create the normal data stream and loop back to try again. // ASSERT(Scb->FileObject == NULL); // // Make sure we are serialized with the FileSizes, and // will remove this condition if we abort. // if (!DoingIoAtEof) { FsRtlLockFsRtlHeader( Header ); IrpContext->FcbWithPagingExclusive = (PFCB)Scb; } NtfsCreateInternalAttributeStream( IrpContext, Scb, FALSE ); if (!DoingIoAtEof) { FsRtlUnlockFsRtlHeader( Header ); IrpContext->FcbWithPagingExclusive = NULL; } } } #endif _CAIRO_ } try_exit: NOTHING; // // If the request was not posted, deal with it. // if (Irp) { if (!PostIrp) { LONGLONG ActualBytesRead; DebugTrace( 0, Dbg, ("Completing request with status = %08lx\n", Status)); DebugTrace( 0, Dbg, (" Information = %08lx\n", Irp->IoStatus.Information)); // // Record the total number of bytes actually read // ActualBytesRead = Irp->IoStatus.Information; // // If the file was opened for Synchronous IO, update the current // file position. Make sure to use the original file object // not an internal stream we may use within this routine. // if (!PagingIo) { if (SynchronousIo) { IrpSp->FileObject->CurrentByteOffset.QuadPart = StartingVbo + ActualBytesRead; } // // On success, do the following to let us update last access time. // if (NT_SUCCESS( Status )) { SetFlag( IrpSp->FileObject->Flags, FO_FILE_FAST_IO_READ ); } } // // Abort transaction on error by raising. // NtfsCleanupTransaction( IrpContext, Status, FALSE ); } else { DebugTrace( 0, Dbg, ("Passing request to Fsp\n") ); if (!OplockPostIrp) { Status = NtfsPostRequest( IrpContext, Irp ); } } } } finally { DebugUnwind( NtfsCommonRead ); // // Clean up any Bcb from read compressed. // if (Bcb != NULL) { CcUnpinData( Bcb ); } // // If the Scb has been acquired, release it. // if (PagingIoAcquired) { ExReleaseResource( Scb->Header.PagingIoResource ); } if (Irp) { if (ScbAcquired) { ExReleaseResource( Scb->Header.Resource ); } } // // Free the attribute enumeration context if // used. // if (FoundAttribute) { NtfsCleanupAttributeContext( &AttrContext ); } // // Complete the request if we didn't post it and no exception // // Note that NtfsCompleteRequest does the right thing if either // IrpContext or Irp are NULL // if (!PostIrp && !AbnormalTermination()) { NtfsCompleteRequest( &IrpContext, Irp ? &Irp : NULL, Status ); } DebugTrace( -1, Dbg, ("NtfsCommonRead -> %08lx\n", Status) ); } return Status; }