/*++ Copyright (c) 1990 Microsoft Corporation Module Name: cache.c Abstract: This module implements the NtFlushBuffersFile API for NT and provides support routines for read and write caches. Author: Colin Watson (ColinW) 22-Jan-1991 Revision History: 22-Jan-1991 colinw Created --*/ #include "precomp.h" #pragma hdrstop VOID FindOldestFcb( IN PFCB FcbToCheck, IN PVOID Ctx ); VOID PurgeDormantCachedFile( IN PFCB FcbToCheck, IN PVOID Context ); VOID PurgeAnyDormantCachedFile( IN PFCB FcbToCheck, IN PVOID Context ); #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, RdrFsdFlushBuffersFile) #pragma alloc_text(PAGE, RdrFspFlushBuffersFile) #pragma alloc_text(PAGE, RdrFscFlushBuffersFile) #pragma alloc_text(PAGE, RdrAcquireFcbForLazyWrite) #pragma alloc_text(PAGE, RdrReleaseFcbFromLazyWrite) #pragma alloc_text(PAGE, RdrAcquireFcbForReadAhead) #pragma alloc_text(PAGE, RdrReleaseFcbFromReadAhead) #pragma alloc_text(PAGE, RdrPurgeCacheFile) #pragma alloc_text(PAGE, RdrUninitializeCacheMap) #pragma alloc_text(PAGE, RdrFlushCacheFile) #pragma alloc_text(PAGE, RdrPurgeDormantCachedFiles) #pragma alloc_text(PAGE, RdrSetDormantCachedFile) #pragma alloc_text(PAGE, RdrPurgeDormantFilesOnConnection) #pragma alloc_text(PAGE, PurgeDormantCachedFile) #pragma alloc_text(PAGE, PurgeAnyDormantCachedFile) #pragma alloc_text(PAGE, FindOldestFcb) #endif NTSTATUS RdrFsdFlushBuffersFile ( IN PFS_DEVICE_OBJECT DeviceObject, IN PIRP Irp ) /*++ Routine Description: This routine implements the FSD version of the NtFlushBuffersFile API. Arguments: IN PFS_DEVICE_OBJECT DeviceObject, - Supplies the device object for this request IN PIRP Irp - Supplies the IRP that describes the request Return Value: NTSTATUS - Status of operation --*/ { NTSTATUS Status; PAGED_CODE(); dprintf(DPRT_READWRITE, ("RdrFsdFlushBuffersFile\n")); FsRtlEnterFileSystem(); // // Decide if we can block for I/O // try { Status = RdrFscFlushBuffersFile( CanFsdWait( Irp ), DeviceObject, Irp ); } except (RdrExceptionFilter(GetExceptionInformation(), &Status)) { Status = RdrProcessException(Irp, Status); } dprintf(DPRT_READWRITE, ("RdrFsdFlushBuffersFile -> %X\n", Status)); FsRtlExitFileSystem(); return Status; } NTSTATUS RdrFspFlushBuffersFile ( IN PFS_DEVICE_OBJECT DeviceObject, IN PIRP Irp ) /*++ Routine Description: This routine implements the FSP version of the NtFlushBuffersFile API. API. Arguments: IN PFS_DEVICE_OBJECT DeviceObject, - Supplies the device object for this request IN PIRP Irp - Supplies the IRP that describes the request Return Value: NTSTATUS - Status of operation --*/ { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp ); PAGED_CODE(); dprintf(DPRT_READWRITE, ("RdrFspFlushBuffersFile\n")); // // Call the common routine. The Fsp is always allowed to block // return RdrFscFlushBuffersFile( TRUE, DeviceObject, Irp ); } NTSTATUS RdrFscFlushBuffersFile ( IN BOOLEAN Wait, IN PFS_DEVICE_OBJECT DeviceObject, IN PIRP Irp ) /*++ Routine Description: This routine implements the FSD version of the NtFlushBuffersFile API. API. Arguments: IN PFS_DEVICE_OBJECT DeviceObject, - Supplies the device object for this request IN PIRP Irp - Supplies the IRP that describes the request Return Value: NTSTATUS - Status of operation --*/ { NTSTATUS Status; PIO_STACK_LOCATION IrpSp; BOOLEAN FcbLocked = FALSE; PICB Icb; PSMB_BUFFER SMBBuffer; PSMB_HEADER Smb; PREQ_FLUSH FlushFile; PMDL SendMDL; ULONG SendLength; PAGED_CODE(); // // Get the current stack location // IrpSp = IoGetCurrentIrpStackLocation( Irp ); Icb = ICB_OF(IrpSp); dprintf(DPRT_READWRITE, ("RdrFscFlushBuffersFile. Wait: %lx, Irp:%08lx, FileObject: %08lx\n", Wait, Irp, IrpSp->FileObject)); try { if (!RdrAcquireFcbLock(Icb->Fcb, ExclusiveLock, Wait)) { try_return(Status = STATUS_PENDING); } FcbLocked = TRUE; Status = RdrIsOperationValid(Icb, IrpSp->MajorFunction, IrpSp->FileObject); if (!NT_SUCCESS(Status)) { try_return(Status); } try { dprintf(DPRT_READWRITE, ("RdrFscFlushBuffersFile. Type:%lx\n", Icb->Type)); switch ( Icb->Type ) { case NamedPipe: if (!Wait) { try_return(Status = STATUS_PENDING); } Status = RdrNpFlushBuffers( Wait, Irp, Icb); try_return(Status); break; case DiskFile: // // If this file is cached, flush the cache contents // if (!Wait) { try_return(Status = STATUS_PENDING); } dprintf(DPRT_READWRITE, ("Flush cache for file %lx\n", IrpSp->FileObject)); Status = RdrFlushWriteBufferForFile(Irp, Icb, (BOOLEAN)!RdrUseAsyncWriteBehind); if (!NT_SUCCESS(Status)) { try_return(Status); } //RdrLog(( "ccflush1", &Icb->Fcb->FileName, 1, 0xffffffff )); CcFlushCache(&Icb->NonPagedFcb->SectionObjectPointer, NULL, 0, &Irp->IoStatus); if (!NT_SUCCESS(Status)) { try_return(Status); } // // Serialize behind paging I/O to ensure flush is done. // ExAcquireResourceExclusive(Icb->Fcb->Header.PagingIoResource, TRUE); ExReleaseResource(Icb->Fcb->Header.PagingIoResource); // // Send a flush SMB to the server. // if ((SMBBuffer = RdrAllocateSMBBuffer()) == NULL) { try_return(Status = STATUS_INSUFFICIENT_RESOURCES); } // // Build the SMB // Smb = (PSMB_HEADER)SMBBuffer->Buffer; Smb->Command = SMB_COM_FLUSH; FlushFile = (PREQ_FLUSH)(Smb+1); FlushFile->WordCount = 1; SmbPutUshort(&FlushFile->Fid, Icb->FileId); SmbPutUshort( &FlushFile->ByteCount, 0); SendLength = FlushFile->Buffer - (PUCHAR )(Smb); SendMDL = SMBBuffer->Mdl; SendMDL->ByteCount = SendLength; Status = RdrNetTranceive(NT_NORMAL | NT_NORECONNECT, // Flags Irp, Icb->Fcb->Connection, SendMDL, NULL, // Only interested in the error code. Icb->Se); RdrFreeSMBBuffer(SMBBuffer); if (Status == STATUS_INVALID_HANDLE) { RdrInvalidateFileId(Icb->NonPagedFcb, Icb->FileId); } try_return(NOTHING); break; default: try_return(Status = STATUS_SUCCESS); break; } } except (EXCEPTION_EXECUTE_HANDLER) { try_return(Status = GetExceptionCode()); } try_exit:NOTHING; } finally { if (FcbLocked) { RdrReleaseFcbLock(Icb->Fcb); } } if ( Status == STATUS_PENDING ) { // // Need to block and caller requested thread not to block. // ASSERT (Wait == FALSE); RdrFsdPostToFsp(DeviceObject, Irp); } else { // // Complete the I/O request with the specified status. // RdrCompleteRequest(Irp, Status); } dprintf(DPRT_READWRITE, ("Returning status %X\n", Status)); return Status; } BOOLEAN RdrAcquireFcbForLazyWrite ( IN PVOID Context, IN BOOLEAN Wait ) /*++ Routine Description: This routine acquires an FCB for shared access for a lazy write operation. Arguments: IN PVOID Context - Supplies an FCB to lock. IN BOOLEAN Wait - True if we can block the callers thread for this request Return Value: None. --*/ { PFCB Fcb = Context; BOOLEAN RetValue; PAGED_CODE(); ASSERT(Fcb->Header.NodeTypeCode == STRUCTURE_SIGNATURE_FCB); RetValue = ExAcquireResourceShared(Fcb->Header.PagingIoResource, Wait); if (RetValue) { // // Remember the thread ID of the lazy writer. We use this to // avoid updating valid data length if the write behind extends // valid data length. // Fcb->LazyWritingThread = PsGetCurrentThread(); } return RetValue; } VOID RdrReleaseFcbFromLazyWrite ( IN PVOID Context ) /*++ Routine Description: This routine releases an FCB that was acquired for a close operation. Arguments: IN PVOID Context - Supplies an FCB to lock. Return Value: None. --*/ { PFCB Fcb = Context; PAGED_CODE(); ASSERT(Fcb->Header.NodeTypeCode == STRUCTURE_SIGNATURE_FCB); // // We're not doing a lazy write any more, we're done. // Fcb->LazyWritingThread = NULL; ExReleaseResource(Fcb->Header.PagingIoResource); } BOOLEAN RdrAcquireFcbForReadAhead ( IN PVOID Context, IN BOOLEAN Wait ) /*++ Routine Description: This routine acquires an FCB for shared access for a read ahead operation. Arguments: IN PVOID Context - Supplies an FCB to lock. IN BOOLEAN Wait - True if we can tie up the callers thread for this request Return Value: None. --*/ { PFCB Fcb = Context; BOOLEAN RetValue; PAGED_CODE(); ASSERT(Fcb->Header.NodeTypeCode == STRUCTURE_SIGNATURE_FCB); RetValue = RdrAcquireFcbLock(Fcb, SharedLock, Wait); return RetValue; } VOID RdrReleaseFcbFromReadAhead ( IN PVOID Context ) /*++ Routine Description: This routine releases an FCB that was acquired for a close operation. Arguments: IN PVOID Context - Supplies an FCB to lock. Return Value: None. --*/ { PFCB Fcb = Context; PAGED_CODE(); ASSERT(Fcb->Header.NodeTypeCode == STRUCTURE_SIGNATURE_FCB); RdrReleaseFcbLock(Fcb); } NTSTATUS RdrPurgeCacheFile ( IN PFCB Fcb ) /*++ Routine Description: This routine will purge the specified file from the cache. Arguments: IN PFCB Fcb - Supplies an FCB for the file to purge. Return Value: NTSTATUS - Status of purge operation. This operation may raise. Note: The file must be locked exclusively before calling this routine. --*/ { NTSTATUS Status = STATUS_SUCCESS; PFILE_OBJECT CacheFileObject; PLIST_ENTRY IcbEntry; PAGED_CODE(); dprintf(DPRT_CACHE, ("RdrPurgeCacheFile Fcb:%lx (%wZ)\n", Fcb, &Fcb->FileName)); ASSERT ((Fcb->NonPagedFcb->Type == DiskFile) || (Fcb->NonPagedFcb->Type == Directory) || (Fcb->NonPagedFcb->Type == FileOrDirectory)); ASSERT (Fcb->NonPagedFcb->FileType == FileTypeDisk); ASSERT (ExIsResourceAcquiredExclusive(Fcb->Header.Resource)); for (IcbEntry = Fcb->InstanceChain.Flink; IcbEntry != &Fcb->InstanceChain ; IcbEntry = IcbEntry->Flink) { PICB IcbToFlush = CONTAINING_RECORD(IcbEntry, ICB, InstanceNext); if (IcbToFlush->Type == DiskFile && IcbToFlush->Flags & ICB_OPENED) { Status = RdrFlushWriteBufferForFile(NULL, IcbToFlush, TRUE); } } // // Tell the cache manager to blow away all the references for all the // file objects associated with this FCB. // //RdrLog(( "ccpurge1", &Fcb->FileName, 2, 0xffffffff, 1 << 24 )); CcPurgeCacheSection(&Fcb->NonPagedFcb->SectionObjectPointer, NULL, 0, TRUE); // // Now try to get the cache file object from the cache manager, // and if there is none, we're done now. // CacheFileObject = CcGetFileObjectFromSectionPtrs(&Fcb->NonPagedFcb->SectionObjectPointer); dprintf(DPRT_CACHE, ("Removing file %lx (Fcb %lx) from the cache (hard)\n", CacheFileObject, Fcb)); if (CacheFileObject != NULL) { //RdrLog(( "rdunini1", &Fcb->FileName, 0 )); RdrUninitializeCacheMap(CacheFileObject, &RdrZero); } else { // // Make sure that this thread owns the FCB. // ASSERT (ExIsResourceAcquiredExclusive(Fcb->Header.Resource)); // // Release the lock on the FCB that our caller applied. // // We do this to make sure that other threads can come in while we // are waiting for the cache flush to complete. // RdrReleaseFcbLock(Fcb); ASSERT (!ExIsResourceAcquiredExclusive(Fcb->Header.Resource)); KeWaitForSingleObject(&Fcb->NonPagedFcb->PurgeCacheSynchronizer, Executive, KernelMode, FALSE, NULL); // // Re-acquire the FCB lock once we've waited for MM // finish forcing the section closed. // RdrAcquireFcbLock(Fcb, ExclusiveLock, TRUE); } // // Make sure that this thread owns the FCB. // ASSERT (ExIsResourceAcquiredExclusive(Fcb->Header.Resource)); // // Release the lock on the FCB that our caller applied. // RdrReleaseFcbLock(Fcb); ASSERT (!ExIsResourceAcquiredExclusive(Fcb->Header.Resource)); // // If this is an executable opened over the net, then // its possible that the executables image section // might still be kept open. // // Ask MM to flush the section closed. This will fail // if the executable in question is still running. // //RdrLog(( "mmflush1", &Fcb->FileName, 1, MmFlushForWrite )); MmFlushImageSection(&Fcb->NonPagedFcb->SectionObjectPointer, MmFlushForWrite); // // There is also a possiblity that there is a user section // open on this file, in which case we need to force the // section closed to make sure that they are cleaned up. // //RdrLog(( "mmforce1", &Fcb->FileName, 1, TRUE )); MmForceSectionClosed(&Fcb->NonPagedFcb->SectionObjectPointer, TRUE); // // Re-acquire the FCB lock once we've waited for MM // finish forcing the section closed. // RdrAcquireFcbLock(Fcb, ExclusiveLock, TRUE); ASSERT(Fcb->Header.NodeTypeCode == STRUCTURE_SIGNATURE_FCB);; ASSERT (ExIsResourceAcquiredExclusive(Fcb->Header.Resource)); dprintf(DPRT_CACHE, ("RdrPurgeCacheFile returning STATUS_SUCCESS, Fcb: %lx\n", Fcb)); return STATUS_SUCCESS; } BOOLEAN RdrUninitializeCacheMap( IN PFILE_OBJECT FileObject, IN PLARGE_INTEGER TruncateSize ) /*++ Routine Description: This routine is a redirector wrapper for CcUninitializeCacheMap. Arguments: IN PFILE_OBJECT FileObject - Supplies the file object for the file to purge. IN PLARGE_INTEGER TruncateSize - Specifies the new size for the file. Return Value: BOOLEAN - TRUE if file has been immediately purged, FALSE if we had to wait. Note: The file must be locked exclusively before calling this routine. --*/ { BOOLEAN CacheReturnValue; CACHE_UNINITIALIZE_EVENT PurgeCompleteEvent; PFCB Fcb = FileObject->FsContext; PAGED_CODE(); ASSERT (Fcb->Header.NodeTypeCode == STRUCTURE_SIGNATURE_FCB); // // Make sure that this thread owns the FCB. // ASSERT (ExIsResourceAcquiredExclusive(Fcb->Header.Resource)); // // In order to guarantee that only one thread is calling // RdrPurgeCacheFile, we reset this event to the // not-signalled state before calling CcUninitializeCacheMap, // and then set it when we exit. If any other threads come in // while we are waiting on the event, they will find that // CacheFileObject is NULL, and thus wait until the cache purge // completes. // KeClearEvent(&Fcb->NonPagedFcb->PurgeCacheSynchronizer); // // Now uninitialize the cache managers own file object. This is // done basically simply to allow us to wait until the cache purge // is complete. // KeInitializeEvent(&PurgeCompleteEvent.Event, SynchronizationEvent, FALSE); //RdrLog(( "ccunini1", &Fcb->FileName, 2, // (TruncateSize == NULL) ? 0xffffffff : TruncateSize->LowPart, // (ULONG)&PurgeCompleteEvent )); CacheReturnValue = CcUninitializeCacheMap(FileObject, TruncateSize, &PurgeCompleteEvent); // // Release the lock on the FCB that our caller applied. // RdrReleaseFcbLock(Fcb); // // Make sure that this thread doesn't own the FCB. // ASSERT (!ExIsResourceAcquiredExclusive(Fcb->Header.Resource)); // // Now wait for the cache manager to finish purging the file. // KeWaitForSingleObject(&PurgeCompleteEvent.Event, Executive, KernelMode, FALSE, NULL); // // Re-acquire the FCB lock once we've waited for the // cache manager to finish the uninitialize. // RdrAcquireFcbLock(Fcb, ExclusiveLock, TRUE); // // Now set the purge cache event to the signalled state to allow // other threads waiting on the cache purge to continue. // KeSetEvent(&Fcb->NonPagedFcb->PurgeCacheSynchronizer, 0, FALSE); return(CacheReturnValue); } NTSTATUS RdrFlushCacheFile ( IN PFCB Fcb ) /*++ Routine Description: This routine will purge the specified file from the cache. Arguments: IN PFCB Fcb - Supplies an FCB for the file to purge. Return Value: NTSTATUS - Status of purge operation. This operation may raise. Note: The file must be locked exclusively before calling this routine. --*/ { IO_STATUS_BLOCK IoStatus; PLIST_ENTRY IcbEntry; NTSTATUS Status; PAGED_CODE(); dprintf(DPRT_CACHE, ("RdrFlushCacheFile Fcb:%lx (%wZ)\n", Fcb, &Fcb->FileName)); ASSERT ((Fcb->NonPagedFcb->Type == DiskFile) || (Fcb->NonPagedFcb->Type == Directory) || (Fcb->NonPagedFcb->Type == FileOrDirectory)); ASSERT (Fcb->NonPagedFcb->FileType == FileTypeDisk); ASSERT (ExIsResourceAcquiredExclusive(Fcb->Header.Resource)); for (IcbEntry = Fcb->InstanceChain.Flink ; IcbEntry != &Fcb->InstanceChain ; IcbEntry = IcbEntry->Flink) { PICB Icb = CONTAINING_RECORD(IcbEntry, ICB, InstanceNext); ASSERT (Icb->Signature == STRUCTURE_SIGNATURE_ICB); if (Icb->Type == DiskFile && FlagOn(Icb->Flags, ICB_OPENED)) { // // Initiate a flush of the write behind data for this file. // Status = RdrFlushWriteBufferForFile(NULL, Icb, (BOOLEAN)!RdrUseAsyncWriteBehind); if (!NT_SUCCESS(Status)) { return Status; } // // Now wait for the flush operation on the file to complete. // RdrWaitForWriteBehindOperation(Icb); } } // // Flush dirty data for this file object from the cache. // //RdrLog(( "ccflush2", &Fcb->FileName, 1, 0xffffffff )); CcFlushCache(&Fcb->NonPagedFcb->SectionObjectPointer, NULL, 0, &IoStatus); if (NT_SUCCESS(IoStatus.Status)) { // // Serialize behind paging I/O to ensure flush is done. // ExAcquireResourceExclusive(Fcb->Header.PagingIoResource, TRUE); ExReleaseResource(Fcb->Header.PagingIoResource); } // // At this point, dirty data should be flushed for this file // return IoStatus.Status; } typedef struct _FINDOLDESTFCB { PFCB OldestFcb; ULONG NumberOfDormantCachedFiles; } FINDOLDESTFCB, *PFINDOLDESTFCB; VOID RdrSetDormantCachedFile( IN PFCB Fcb ) /*++ Routine Description: This routine will mark a specified FCB as being dormant. Arguments: IN PFCB Fcb - Fcb to mark as being dormant. Return Value: None. Note: This routine assumes that the FCB is currently locked. --*/ { PAGED_CODE(); // // If we are in 'TurboMode' we will cache all of the files we can, without // limit. This is for benchmarks // if( RdrTurboMode == FALSE ) { FINDOLDESTFCB Context; BOOLEAN FcbLocked = FALSE; Context.NumberOfDormantCachedFiles = 0; Context.OldestFcb = NULL; // // This thread cannot own the FCB resource on entry. If it does, we // might deadlock. // ASSERT (!ExIsResourceAcquiredExclusive(Fcb->Header.Resource)); RdrForeachFcbOnConnection (Fcb->Connection, NoLock, FindOldestFcb, &Context); // // Now that we've scanned the list to find the oldest cached file, // we want to pull this file out of the list if we've reached our // limit on dormant cached files. // if (Context.NumberOfDormantCachedFiles >= RdrData.DormantFileLimit) { // // Lock this FCB to protect the DormantTimeout field in the FCB. // RdrAcquireFcbLock(Context.OldestFcb, ExclusiveLock, TRUE); FcbLocked = TRUE; ASSERT (Context.OldestFcb != NULL); ASSERT (Context.OldestFcb != Fcb); // // If the oldest FCB is still dormant, purge it now. Note that // if this FCB is no longer dormant, we'll end up with an extra // dormant file, above the limit. So be it. // if (Context.OldestFcb->NumberOfOpens == 0) { //RdrLog(( "rdflush1", &Context.OldestFcb->FileName, 0 )); RdrFlushCacheFile(Context.OldestFcb); //RdrLog(( "rdpurge1", &Context.OldestFcb->FileName, 0 )); RdrPurgeCacheFile(Context.OldestFcb); } } // // We're done with the oldest FCB, we can release it now. // if (Context.OldestFcb != NULL) { BOOLEAN FcbDeleted; FcbDeleted = RdrDereferenceFcb(NULL, Context.OldestFcb->NonPagedFcb, FcbLocked, 0, NULL); // if (!FcbDeleted) { // ASSERT (Fcb->Header.Resource->Threads[0] != (ULONG) ExGetCurrentResourceThread()); // } } } // // Lock this FCB to protect the DormantTimeout field in the FCB. // RdrAcquireFcbLock(Fcb, ExclusiveLock, TRUE); // // Even for benchmarks, we want to eventually push out file changes // if( Fcb->UpdatedFile || RdrTurboMode == FALSE ) { ULONG CacheFileTimeout; // // Mark this file as being dormant // // // If the timeout is -1, we don't want to ever purge dormant // files, otherwise we will set the dormant timeout appropriately. // CacheFileTimeout = RdrData.CachedFileTimeout; if (CacheFileTimeout != -1) { Fcb->DormantTimeout = RdrCurrentTime + CacheFileTimeout; } } RdrReleaseFcbLock(Fcb); // // Now set the hint to indicate that there might be a dormant file on this connection // Fcb->Connection->NumberOfDormantFiles = 1; } VOID FindOldestFcb( IN PFCB FcbToCheck, IN PVOID Ctx ) /*++ Routine Description: This routine is called on each FCB open on a connection to determine which of them is the oldest FCB. Arguments: IN PFCB FcbToCheck - Fcb to check IN PVOID Ctx - Context block - contains the oldest FCB pointer. IN OUT PBOOLEAN UnlockFcb - Set/Cleared to indicate if we should unlock the FCB in question when we dereference it. Return Value: None. --*/ { PFINDOLDESTFCB Context = Ctx; PAGED_CODE(); // // If this FCB is dormant, then it is a candidate for flushing. // // // Please note that files that are currently open have a dormant // timeout of -1, thus we will always skip over the file we are // going to mark as dormant. // if ((FcbToCheck->NonPagedFcb->Type == DiskFile) && (FcbToCheck->DormantTimeout != 0xffffffff) && (FcbToCheck->NumberOfOpens == 0)) { Context->NumberOfDormantCachedFiles += 1; if ((Context->OldestFcb == NULL) || (FcbToCheck->DormantTimeout < Context->OldestFcb->DormantTimeout)) { BOOLEAN FcbDeleted; if (Context->OldestFcb != NULL) { FcbDeleted = RdrDereferenceFcb(NULL, Context->OldestFcb->NonPagedFcb, FALSE, 0, NULL); // if (!FcbDeleted) { // ASSERT (Fcb->Header.Resource->Threads[0] != (ULONG) ExGetCurrentResourceThread()); // } } // // Reference the new oldest FCB to make sure it doesn't // go away. // Context->OldestFcb = FcbToCheck; RdrReferenceFcb(Context->OldestFcb->NonPagedFcb); } } } VOID RdrPurgeDormantCachedFiles ( VOID ) /*++ Routine Description: This routine walks the FCB database and purges all cached files that have been dormant for longer than the global dormant timeout. Arguments: None Return Value: None. --*/ { PAGED_CODE(); // // If the discardable code reference count is 0, this means that there are // no open files that can possibly be dormant (the only files left are // either tree connections or are handles to the redirector directly), // so we can simply early out. // if (!RdrIsDiscardableCodeReferenced(RdrFileDiscardableSection)) { return; } RdrReferenceDiscardableCode(RdrFileDiscardableSection); RdrForeachFcb(NoLock, PurgeDormantCachedFile, NULL); RdrDereferenceDiscardableCode(RdrFileDiscardableSection); } VOID PurgeDormantCachedFile( IN PFCB FcbToCheck, IN PVOID Context ) /*++ Routine Description: This routine will purge a file from the cache if it is dormant. Arguments: None Return Value: None. --*/ { PAGED_CODE(); // ASSERT (ExIsResourceAcquiredExclusive(FcbToCheck->Header.Resource)); // // If this FCB is dormant, and is older than the dormant file timeout, // then it is to be flushed. // if ((FcbToCheck->NonPagedFcb->Type == DiskFile) && (FcbToCheck->NumberOfOpens == 0) && (RdrCurrentTime > FcbToCheck->DormantTimeout) ) { RdrAcquireFcbLock(FcbToCheck, ExclusiveLock, TRUE); if ((FcbToCheck->NumberOfOpens == 0) && (RdrCurrentTime > FcbToCheck->DormantTimeout) ) { // // Flush any write behind data outstanding on the file // //RdrLog(( "rdflush2", &FcbToCheck->FileName, 0 )); RdrFlushCacheFile(FcbToCheck); // // Now pull the file from the cache. // //RdrLog(( "rdpurge2", &FcbToCheck->FileName, 0 )); RdrPurgeCacheFile(FcbToCheck); } RdrReleaseFcbLock(FcbToCheck); } } VOID RdrPurgeDormantFilesOnConnection( IN PCONNECTLISTENTRY Connection ) /*++ Routine Description: This routine will close all dormant files on a connection. This fixes a series of problems such as copy a file and do a dir on the destination directory. If the connection is not purged then the dir will return 0 bytes as the file length. Arguments: IN PCONNECTLISTENTRY Connection - Connection to scan for dormant files. Return Value: None. Note: This routine assumes that the FCB is currently locked. --*/ { PAGED_CODE(); // // Early out if there aren't any dormant files on the connection. // if ( Connection->NumberOfDormantFiles == 0 ) { return; } Connection->NumberOfDormantFiles = 0; RdrForeachFcbOnConnection(Connection, NoLock, PurgeAnyDormantCachedFile, NULL); } VOID PurgeAnyDormantCachedFile( IN PFCB FcbToCheck, IN PVOID Context ) /*++ Routine Description: This routine will purge a file from the cache if it is dormant. Arguments: None Return Value: None. --*/ { PAGED_CODE(); // // If this FCB is dormant, then it is to be flushed. // if ((FcbToCheck->NonPagedFcb->Type == DiskFile) && (FcbToCheck->UpdatedFile == TRUE) && (FcbToCheck->NumberOfOpens == 0)) { RdrAcquireFcbLock(FcbToCheck, ExclusiveLock, TRUE); if (FcbToCheck->NumberOfOpens == 0) { // // Flush any write behind data outstanding on the file // //RdrLog(( "rdflush3", &FcbToCheck->FileName, 0 )); RdrFlushCacheFile(FcbToCheck); // // Now pull the file from the cache. // //RdrLog(( "rdpurge3", &FcbToCheck->FileName, 0 )); RdrPurgeCacheFile(FcbToCheck); } RdrReleaseFcbLock(FcbToCheck); } }