/*++ Copyright (c) 1990 Microsoft Corporation Module Name: fssup.c Abstract: This module implements the File System support routines for the Cache subsystem. Author: Tom Miller [TomM] 4-May-1990 Revision History: --*/ #include "cc.h" // // The Bug check file id for this module // #define BugCheckFileId (CACHE_BUG_CHECK_FSSUP) // // Define our debug constant // #define me 0x00000001 // // For your debugging pleasure, if the flag doesn't move! (Currently not used) // #define IsSyscacheFile(FO) (((FO) != NULL) && \ (*(PUSHORT)(FO)->FsContext == 0X705) && \ FlagOn(*(PULONG)((PCHAR)(FO)->FsContext + 0x48), 0x80000000)) extern POBJECT_TYPE IoFileObjectType; extern ULONG MmLargeSystemCache; VOID CcUnmapAndPurge( IN PSHARED_CACHE_MAP SharedCacheMap ); VOID CcDeleteMbcb( IN PSHARED_CACHE_MAP SharedCacheMap ); VOID CcDeleteBcbs ( IN PSHARED_CACHE_MAP SharedCacheMap ); VOID CcPurgeAndClearCacheSection ( IN PSHARED_CACHE_MAP SharedCacheMap, IN PLARGE_INTEGER FileOffset ); #ifdef ALLOC_PRAGMA #pragma alloc_text(INIT,CcInitializeCacheManager) #pragma alloc_text(PAGE,CcZeroData) #endif BOOLEAN CcInitializeCacheManager ( ) /*++ Routine Description: This routine must be called during system initialization before the first call to any file system, to allow the Cache Manager to initialize its global data structures. This routine has no dependencies on other system components being initialized. Arguments: None Return Value: TRUE if initialization was successful --*/ { CLONG i; ULONG Index; PGENERAL_LOOKASIDE Lookaside; USHORT NumberOfItems; PKPRCB Prcb; PWORK_QUEUE_ITEM WorkItem; #ifdef CCDBG_LOCK KeInitializeSpinLock( &CcDebugTraceLock ); #endif #if DBG CcBcbCount = 0; InitializeListHead( &CcBcbList ); #endif // // Figure out the timeout clock tick for the lazy writer. // CcIdleDelayTick = LAZY_WRITER_IDLE_DELAY / KeQueryTimeIncrement(); // // Initialize shared cache map list structures // InitializeListHead( &CcCleanSharedCacheMapList ); InitializeListHead( &CcDirtySharedCacheMapList.SharedCacheMapLinks ); CcDirtySharedCacheMapList.Flags = IS_CURSOR; InsertTailList( &CcDirtySharedCacheMapList.SharedCacheMapLinks, &CcLazyWriterCursor.SharedCacheMapLinks ); CcLazyWriterCursor.Flags = IS_CURSOR; // // Initialize worker thread structures // InitializeListHead( &CcIdleWorkerThreadList ); InitializeListHead( &CcExpressWorkQueue ); InitializeListHead( &CcRegularWorkQueue ); InitializeListHead( &CcPostTickWorkQueue ); // // Set the number of worker threads based on the system size. // CcCapturedSystemSize = MmQuerySystemSize(); if (CcNumberWorkerThreads == 0) { switch (CcCapturedSystemSize) { case MmSmallSystem: CcNumberWorkerThreads = ExCriticalWorkerThreads - 1; CcDirtyPageThreshold = MmNumberOfPhysicalPages / 8; CcAggressiveZeroThreshold = 1; break; case MmMediumSystem: CcNumberWorkerThreads = ExCriticalWorkerThreads - 1; CcDirtyPageThreshold = MmNumberOfPhysicalPages / 4; CcAggressiveZeroThreshold = 2; break; case MmLargeSystem: CcNumberWorkerThreads = ExCriticalWorkerThreads - 2; CcDirtyPageThreshold = MmNumberOfPhysicalPages / 4 + MmNumberOfPhysicalPages / 8; CcAggressiveZeroThreshold = 4; break; default: CcNumberWorkerThreads = 1; CcDirtyPageThreshold = MmNumberOfPhysicalPages / 8; } if (MmSystemCacheWs.MaximumWorkingSetSize > ((4*1024*1024)/PAGE_SIZE)) { CcDirtyPageThreshold = (ULONG)(MmSystemCacheWs.MaximumWorkingSetSize - ((2*1024*1024)/PAGE_SIZE)); } CcDirtyPageTarget = CcDirtyPageThreshold / 2 + CcDirtyPageThreshold / 4; } CcAggressiveZeroCount = 0; // // Now allocate and initialize the above number of worker thread // items. // for (i = 0; i < CcNumberWorkerThreads; i++) { WorkItem = ExAllocatePoolWithTag( NonPagedPool, sizeof(WORK_QUEUE_ITEM), 'qWcC' ); if (WorkItem == NULL) { CcBugCheck( 0, 0, 0 ); } // // Initialize the work queue item and insert in our queue // of potential worker threads. // ExInitializeWorkItem( WorkItem, CcWorkerThread, WorkItem ); InsertTailList( &CcIdleWorkerThreadList, &WorkItem->List ); } // // Initialize the Lazy Writer thread structure, and start him up. // RtlZeroMemory( &LazyWriter, sizeof(LAZY_WRITER) ); InitializeListHead( &LazyWriter.WorkQueue ); // // Initialize the Scan Dpc and Timer. // KeInitializeDpc( &LazyWriter.ScanDpc, &CcScanDpc, NULL ); KeInitializeTimer( &LazyWriter.ScanTimer ); // // Now initialize the lookaside list for allocating Work Queue entries. // switch ( CcCapturedSystemSize ) { // // ~512 bytes // case MmSmallSystem : NumberOfItems = 32; break; // // ~1k bytes // case MmMediumSystem : NumberOfItems = 64; break; // // ~2k bytes // case MmLargeSystem : NumberOfItems = 128; if (MmIsThisAnNtAsSystem()) { NumberOfItems += 128; } break; } ExInitializeSystemLookasideList( &CcTwilightLookasideList, NonPagedPool, sizeof( WORK_QUEUE_ENTRY ), 'kWcC', NumberOfItems, &ExSystemLookasideListHead ); // // Initialize the per processor nonpaged lookaside lists and descriptors. // for (Index = 0; Index < (ULONG)KeNumberProcessors; Index += 1) { Prcb = KiProcessorBlock[Index]; // // Initialize the large IRP per processor lookaside pointers. // Prcb->PPLookasideList[LookasideTwilightList].L = &CcTwilightLookasideList; Lookaside = ExAllocatePoolWithTag( NonPagedPool, sizeof(GENERAL_LOOKASIDE), 'KWcC'); if (Lookaside != NULL) { ExInitializeSystemLookasideList( Lookaside, NonPagedPool, sizeof( WORK_QUEUE_ENTRY ), 'KWcC', NumberOfItems, &ExSystemLookasideListHead ); } else { Lookaside = &CcTwilightLookasideList; } Prcb->PPLookasideList[LookasideTwilightList].P = Lookaside; } // // Initialize the Deferred Write List. // KeInitializeSpinLock( &CcDeferredWriteSpinLock ); InitializeListHead( &CcDeferredWrites ); // // Initialize the Vacbs. // CcInitializeVacbs(); return TRUE; } VOID CcInitializeCacheMap ( IN PFILE_OBJECT FileObject, IN PCC_FILE_SIZES FileSizes, IN BOOLEAN PinAccess, IN PCACHE_MANAGER_CALLBACKS Callbacks, IN PVOID LazyWriteContext ) /*++ Routine Description: This routine is intended to be called by File Systems only. It initializes the cache maps for data caching. It should be called every time a file is opened or created, and NO_INTERMEDIATE_BUFFERING was specified as FALSE. Arguments: FileObject - A pointer to the newly-created file object. FileSizes - A pointer to AllocationSize, FileSize and ValidDataLength for the file. ValidDataLength should contain MAXLONGLONG if valid data length tracking and callbacks are not desired. PinAccess - FALSE if file will be used exclusively for Copy and Mdl access, or TRUE if file will be used for Pin access. (Files for Pin access are not limited in size as the caller must access multiple areas of the file at once.) Callbacks - Structure of callbacks used by the Lazy Writer LazyWriteContext - Parameter to be passed in to above routine. Return Value: None. If an error occurs, this routine will Raise the status. --*/ { KIRQL OldIrql; PSHARED_CACHE_MAP SharedCacheMap; PVOID CacheMapToFree = NULL; CC_FILE_SIZES LocalSizes; LOGICAL WeSetBeingCreated = FALSE; LOGICAL SharedListOwned = FALSE; LOGICAL MustUninitialize = FALSE; LOGICAL WeCreated = FALSE; PPRIVATE_CACHE_MAP PrivateCacheMap; NTSTATUS Status = STATUS_SUCCESS; DebugTrace(+1, me, "CcInitializeCacheMap:\n", 0 ); DebugTrace( 0, me, " FileObject = %08lx\n", FileObject ); DebugTrace( 0, me, " FileSizes = %08lx\n", FileSizes ); // // Make a local copy of the passed in file sizes before acquiring // the spin lock. // LocalSizes = *FileSizes; // // If no FileSize was given, set to one byte before maximizing below. // if (LocalSizes.AllocationSize.QuadPart == 0) { LocalSizes.AllocationSize.LowPart += 1; } // // If caller has Write access or will allow write, then round // size to next create modulo. (***Temp*** there may be too many // apps that end up allowing shared write, thanks to our Dos heritage, // to keep that part of the check in.) // if (FileObject->WriteAccess /*|| FileObject->SharedWrite */) { LocalSizes.AllocationSize.QuadPart = LocalSizes.AllocationSize.QuadPart + (LONGLONG)(DEFAULT_CREATE_MODULO - 1); LocalSizes.AllocationSize.LowPart &= ~(DEFAULT_CREATE_MODULO - 1); } else { LocalSizes.AllocationSize.QuadPart = LocalSizes.AllocationSize.QuadPart + (LONGLONG)(VACB_MAPPING_GRANULARITY - 1); LocalSizes.AllocationSize.LowPart &= ~(VACB_MAPPING_GRANULARITY - 1); } // // Do the allocate of the SharedCacheMap, based on an unsafe test, // while not holding a spinlock. If the allocation fails, it's ok // to fail the request even though the test was unsafe. // if (FileObject->SectionObjectPointer->SharedCacheMap == NULL) { restart: ASSERT (CacheMapToFree == NULL); SharedCacheMap = ExAllocatePoolWithTag( NonPagedPool, sizeof(SHARED_CACHE_MAP), 'cScC' ); if (SharedCacheMap == NULL) { DebugTrace( 0, 0, "Failed to allocate SharedCacheMap\n", 0 ); ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES ); } // // Stash a copy of it so we can free it in the error path below. // CacheMapToFree = SharedCacheMap; // // Zero the SharedCacheMap and fill in the nonzero portions later. // RtlZeroMemory( SharedCacheMap, sizeof(SHARED_CACHE_MAP) ); #if OPEN_COUNT_LOG SharedCacheMap->OpenCountLog.Size = sizeof(SharedCacheMap->OpenCountLog.Log)/sizeof(CC_OPEN_COUNT_LOG_ENTRY); #endif // // Now initialize the Shared Cache Map. // SharedCacheMap->NodeTypeCode = CACHE_NTC_SHARED_CACHE_MAP; SharedCacheMap->NodeByteSize = sizeof(SHARED_CACHE_MAP); SharedCacheMap->FileObject = FileObject; SharedCacheMap->FileSize = LocalSizes.FileSize; SharedCacheMap->ValidDataLength = LocalSizes.ValidDataLength; SharedCacheMap->ValidDataGoal = LocalSizes.ValidDataLength; // SharedCacheMap->Section set below // // Initialize the spin locks. // KeInitializeSpinLock( &SharedCacheMap->ActiveVacbSpinLock ); KeInitializeSpinLock( &SharedCacheMap->BcbSpinLock ); ExInitializePushLock( &SharedCacheMap->VacbPushLock ); if (PinAccess) { SetFlag(SharedCacheMap->Flags, PIN_ACCESS); } // // If this file has FO_SEQUENTIAL_ONLY set, then remember that // in the SharedCacheMap. // if (FlagOn(FileObject->Flags, FO_SEQUENTIAL_ONLY)) { SetFlag(SharedCacheMap->Flags, ONLY_SEQUENTIAL_ONLY_SEEN); } // // Do the round-robin allocation of the spinlock for the shared // cache map. Note the manipulation of the next // counter is safe, since we have the CcMasterSpinLock // exclusive. // InitializeListHead( &SharedCacheMap->BcbList ); SharedCacheMap->Callbacks = Callbacks; SharedCacheMap->LazyWriteContext = LazyWriteContext; // // Initialize listhead for all PrivateCacheMaps // InitializeListHead( &SharedCacheMap->PrivateList ); } // // Serialize Creation/Deletion of all Shared CacheMaps // SharedListOwned = TRUE; CcAcquireMasterLock( &OldIrql ); // // Check for second initialization of same file object // if (FileObject->PrivateCacheMap != NULL) { DebugTrace( 0, 0, "CacheMap already initialized\n", 0 ); CcReleaseMasterLock( OldIrql ); if (CacheMapToFree != NULL) { ExFreePool(CacheMapToFree); } DebugTrace(-1, me, "CcInitializeCacheMap -> VOID\n", 0 ); return; } // // Get current Shared Cache Map pointer indirectly off of the file object. // (The actual pointer is typically in a file system data structure, such // as an Fcb.) // SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap; // // If there is no SharedCacheMap, then we must create a section and // the SharedCacheMap structure. // if (SharedCacheMap == NULL) { // // Insert the new SharedCacheMap. // if (CacheMapToFree == NULL) { CcReleaseMasterLock( OldIrql ); SharedListOwned = FALSE; goto restart; } SharedCacheMap = CacheMapToFree; CacheMapToFree = NULL; // // Insert the new Shared Cache Map in the global list // InsertTailList( &CcCleanSharedCacheMapList, &SharedCacheMap->SharedCacheMapLinks ); WeCreated = TRUE; // // Finally, store the pointer to the Shared Cache Map back // via the indirect pointer in the File Object. // FileObject->SectionObjectPointer->SharedCacheMap = SharedCacheMap; // // We must reference this file object so that it cannot go away // until we do CcUninitializeCacheMap below. Note we cannot // find or rely on the FileObject that Memory Management has, // although normally it will be this same one anyway. // ObReferenceObject ( FileObject ); } else { // // If this file has FO_SEQUENTIAL_ONLY clear, then remember that // in the SharedCacheMap. // if (!FlagOn(FileObject->Flags, FO_SEQUENTIAL_ONLY)) { ClearFlag(SharedCacheMap->Flags, ONLY_SEQUENTIAL_ONLY_SEEN); } } // // If this file is opened for random access, remember this in // the SharedCacheMap. // if (FlagOn(FileObject->Flags, FO_RANDOM_ACCESS)) { SetFlag(SharedCacheMap->Flags, RANDOM_ACCESS_SEEN); } // // Make sure that no one is trying to lazy delete it in the case // that the Cache Map was already there. // ClearFlag(SharedCacheMap->Flags, TRUNCATE_REQUIRED); // // In case there has been a CcUnmapAndPurge call, we check here if we // if we need to recreate the section and map it. // if ((SharedCacheMap->Vacbs == NULL) && !FlagOn(SharedCacheMap->Flags, BEING_CREATED)) { // // Increment the OpenCount on the CacheMap. // CcIncrementOpenCount( SharedCacheMap, 'onnI' ); // // We still want anyone else to wait. // SetFlag(SharedCacheMap->Flags, BEING_CREATED); // // If there is a create event, then this must be the path where we // we were only unmapped. We will just clear it here again in case // someone needs to wait again this time too. // if (SharedCacheMap->CreateEvent != NULL) { KeInitializeEvent( SharedCacheMap->CreateEvent, NotificationEvent, FALSE ); } // // Release global resource // CcReleaseMasterLock( OldIrql ); SharedListOwned = FALSE; // // Signify we have incremented the open count. // MustUninitialize = TRUE; // // Signify we have marked BEING_CREATED in the CacheMap flags. // WeSetBeingCreated = TRUE; // // We have to test this, because the section may only be unmapped. // if (SharedCacheMap->Section == NULL) { // // Call MM to create a section for this file, for the calculated // section size. Note that we have the choice in this service to // pass in a FileHandle or a FileObject pointer, but not both. // Use the pointer as it results in much faster performance. // DebugTrace( 0, mm, "MmCreateSection:\n", 0 ); DebugTrace2(0, mm, " MaximumSize = %08lx, %08lx\n", LocalSizes.AllocationSize.LowPart, LocalSizes.AllocationSize.HighPart ); DebugTrace( 0, mm, " FileObject = %08lx\n", FileObject ); SharedCacheMap->Status = MmCreateSection( &SharedCacheMap->Section, SECTION_MAP_READ | SECTION_MAP_WRITE | SECTION_QUERY, NULL, &LocalSizes.AllocationSize, PAGE_READWRITE, SEC_COMMIT, NULL, FileObject ); DebugTrace( 0, mm, "
Section ); if (!NT_SUCCESS( SharedCacheMap->Status )){ DebugTrace( 0, 0, "Error from MmCreateSection = %08lx\n", SharedCacheMap->Status ); SharedCacheMap->Section = NULL; Status = FsRtlNormalizeNtstatus( SharedCacheMap->Status, STATUS_UNEXPECTED_MM_CREATE_ERR ); goto exitfinally; } ObDeleteCapturedInsertInfo(SharedCacheMap->Section); // // If this is a stream file object, then no user can map it, // and we should keep the modified page writer out of it. // if (!FlagOn(((PFSRTL_COMMON_FCB_HEADER)FileObject->FsContext)->Flags2, FSRTL_FLAG2_DO_MODIFIED_WRITE) && (FileObject->FsContext2 == NULL)) { MmDisableModifiedWriteOfSection( FileObject->SectionObjectPointer ); CcAcquireMasterLock( &OldIrql ); SetFlag(SharedCacheMap->Flags, MODIFIED_WRITE_DISABLED); CcReleaseMasterLock( OldIrql ); } // // Create the Vacb array. // Status = CcCreateVacbArray( SharedCacheMap, LocalSizes.AllocationSize ); if (!NT_SUCCESS(Status)) { goto exitfinally; } } // // If the section already exists, we still have to call MM to // extend, in case it is not large enough. // else { if ( LocalSizes.AllocationSize.QuadPart > SharedCacheMap->SectionSize.QuadPart ) { DebugTrace( 0, mm, "MmExtendSection:\n", 0 ); DebugTrace( 0, mm, " Section = %08lx\n", SharedCacheMap->Section ); DebugTrace2(0, mm, " Size = %08lx, %08lx\n", LocalSizes.AllocationSize.LowPart, LocalSizes.AllocationSize.HighPart ); Status = MmExtendSection( SharedCacheMap->Section, &LocalSizes.AllocationSize, TRUE ); if (!NT_SUCCESS(Status)) { DebugTrace( 0, 0, "Error from MmExtendSection, Status = %08lx\n", Status ); Status = FsRtlNormalizeNtstatus( Status, STATUS_UNEXPECTED_MM_EXTEND_ERR ); goto exitfinally; } } // // Extend the Vacb array. // Status = CcExtendVacbArray( SharedCacheMap, LocalSizes.AllocationSize ); if (!NT_SUCCESS(Status)) { goto exitfinally; } } // // Now show that we are all done and resume any waiters. // CcAcquireMasterLock( &OldIrql ); ClearFlag(SharedCacheMap->Flags, BEING_CREATED); if (SharedCacheMap->CreateEvent != NULL) { KeSetEvent( SharedCacheMap->CreateEvent, 0, FALSE ); } CcReleaseMasterLock( OldIrql ); WeSetBeingCreated = FALSE; } // // Else if the section is already there, we make sure it is large // enough by calling CcExtendCacheSection. // else { // // If the SharedCacheMap is currently being created we have // to optionally create and wait on an event for it. Note that // the only safe time to delete the event is in // CcUninitializeCacheMap, because we otherwise have no way of // knowing when everyone has reached the KeWaitForSingleObject. // if (FlagOn(SharedCacheMap->Flags, BEING_CREATED)) { if (SharedCacheMap->CreateEvent == NULL) { SharedCacheMap->CreateEvent = (PKEVENT)ExAllocatePoolWithTag( NonPagedPool, sizeof(KEVENT), 'vEcC' ); if (SharedCacheMap->CreateEvent == NULL) { DebugTrace( 0, 0, "Failed to allocate CreateEvent\n", 0 ); CcReleaseMasterLock( OldIrql ); SharedListOwned = FALSE; Status = STATUS_INSUFFICIENT_RESOURCES; goto exitfinally; } KeInitializeEvent( SharedCacheMap->CreateEvent, NotificationEvent, FALSE ); } // // Increment the OpenCount on the CacheMap. // CcIncrementOpenCount( SharedCacheMap, 'ecnI' ); // // Release global resource before waiting // CcReleaseMasterLock( OldIrql ); SharedListOwned = FALSE; MustUninitialize = TRUE; DebugTrace( 0, 0, "Waiting on CreateEvent\n", 0 ); KeWaitForSingleObject( SharedCacheMap->CreateEvent, Executive, KernelMode, FALSE, (PLARGE_INTEGER)NULL); // // If the real creator got an error, then we must bomb // out too. // if (!NT_SUCCESS(SharedCacheMap->Status)) { Status = FsRtlNormalizeNtstatus( SharedCacheMap->Status, STATUS_UNEXPECTED_MM_CREATE_ERR ); goto exitfinally; } } else { PCACHE_UNINITIALIZE_EVENT CUEvent, EventNext; // // Increment the OpenCount on the CacheMap. // CcIncrementOpenCount( SharedCacheMap, 'esnI' ); // // If there is a process waiting on an uninitialize on this // cache map to complete, let the thread that is waiting go, // since the uninitialize is now complete. // CUEvent = SharedCacheMap->UninitializeEvent; while (CUEvent != NULL) { EventNext = CUEvent->Next; KeSetEvent(&CUEvent->Event, 0, FALSE); CUEvent = EventNext; } SharedCacheMap->UninitializeEvent = NULL; // // Release global resource // CcReleaseMasterLock( OldIrql ); SharedListOwned = FALSE; MustUninitialize = TRUE; } } if (CacheMapToFree != NULL) { ExFreePool( CacheMapToFree ); CacheMapToFree = NULL; } // // Now allocate (if local one already in use) and initialize // the Private Cache Map. // PrivateCacheMap = &SharedCacheMap->PrivateCacheMap; // // See if we should allocate a PrivateCacheMap while not holding // a spinlock. // if (PrivateCacheMap->NodeTypeCode != 0) { restart2: CacheMapToFree = ExAllocatePoolWithTag( NonPagedPool, sizeof(PRIVATE_CACHE_MAP), 'cPcC' ); if (CacheMapToFree == NULL) { DebugTrace( 0, 0, "Failed to allocate PrivateCacheMap\n", 0 ); Status = STATUS_INSUFFICIENT_RESOURCES; goto exitfinally; } } // // Insert the new PrivateCacheMap in the list off the SharedCacheMap. // SharedListOwned = TRUE; CcAcquireMasterLock( &OldIrql ); // // Now make sure there is still no PrivateCacheMap, and if so just get out. // if (FileObject->PrivateCacheMap == NULL) { // // Is the local one already in use? // if (PrivateCacheMap->NodeTypeCode != 0) { // // Use the one allocated above, if there is one, else go to pool now. // if (CacheMapToFree == NULL) { CcReleaseMasterLock( OldIrql ); SharedListOwned = FALSE; goto restart2; } PrivateCacheMap = CacheMapToFree; CacheMapToFree = NULL; } RtlZeroMemory( PrivateCacheMap, sizeof(PRIVATE_CACHE_MAP) ); PrivateCacheMap->NodeTypeCode = CACHE_NTC_PRIVATE_CACHE_MAP; PrivateCacheMap->FileObject = FileObject; PrivateCacheMap->ReadAheadMask = PAGE_SIZE - 1; // // Initialize the spin lock. // KeInitializeSpinLock( &PrivateCacheMap->ReadAheadSpinLock ); InsertTailList( &SharedCacheMap->PrivateList, &PrivateCacheMap->PrivateLinks ); FileObject->PrivateCacheMap = PrivateCacheMap; } else { // // We raced with another initializer for the same fileobject and must // drop our (to this point speculative) opencount. // ASSERT( SharedCacheMap->OpenCount > 1 ); CcDecrementOpenCount( SharedCacheMap, 'rpnI' ); SharedCacheMap = NULL; } MustUninitialize = FALSE; exitfinally: // // See if we got an error and must uninitialize the SharedCacheMap // if (MustUninitialize) { if (!SharedListOwned) { CcAcquireMasterLock( &OldIrql ); } if (WeSetBeingCreated) { if (SharedCacheMap->CreateEvent != NULL) { KeSetEvent( SharedCacheMap->CreateEvent, 0, FALSE ); } ClearFlag(SharedCacheMap->Flags, BEING_CREATED); } // // Now release our open count. // CcDecrementOpenCount( SharedCacheMap, 'umnI' ); if ((SharedCacheMap->OpenCount == 0) && !FlagOn(SharedCacheMap->Flags, WRITE_QUEUED) && (SharedCacheMap->DirtyPages == 0)) { // // It is neccesary to eliminate the structure now. We should // be guaranteed that our dereference will not result in close // due to the caller's reference on the fileobject, unlike the // comment in the original code, below, would indicate. // // Not removing this structure can result in problems if the file // is also mapped and the mapped page writer extends VDL. An FS // will use CcSetFileSizes and cause us to issue a recursive flush // of the same range, resulting in a self-colliding page flush and // a deadlock. // // We also think that file extension/truncation in the interim // (if the section create failed) would result in an inconsistent // "resurrected" cache map if we managed to use the one we have // now. Note CcSetFileSizes aborts if the section is NULL. // CcDeleteSharedCacheMap( SharedCacheMap, OldIrql, FALSE ); #if 0 // // On PinAccess it is safe and necessary to eliminate // the structure immediately. // if (PinAccess) { CcDeleteSharedCacheMap( SharedCacheMap, OldIrql, FALSE ); // // If it is not PinAccess, we must lazy delete, because // we could get into a deadlock trying to acquire the // stream exclusive when we dereference the file object. // } else { // // Move it to the dirty list so the lazy write scan will // see it. // RemoveEntryList( &SharedCacheMap->SharedCacheMapLinks ); InsertTailList( &CcDirtySharedCacheMapList.SharedCacheMapLinks, &SharedCacheMap->SharedCacheMapLinks ); // // Make sure the Lazy Writer will wake up, because we // want him to delete this SharedCacheMap. // LazyWriter.OtherWork = TRUE; if (!LazyWriter.ScanActive) { CcScheduleLazyWriteScan( FALSE ); } CcReleaseMasterLock( OldIrql ); } #endif } else { CcReleaseMasterLock( OldIrql ); } SharedListOwned = FALSE; // // If we did not create this SharedCacheMap, then there is a // possibility that it is in the dirty list. Once we are sure // we have the spinlock, just make sure it is in the clean list // if there are no dirty bytes and the open count is nonzero. // (The latter test is almost guaranteed, of course, but we check // it to be safe.) // } else if (!WeCreated && (SharedCacheMap != NULL)) { if (!SharedListOwned) { CcAcquireMasterLock( &OldIrql ); SharedListOwned = TRUE; } if ((SharedCacheMap->DirtyPages == 0) && (SharedCacheMap->OpenCount != 0)) { RemoveEntryList( &SharedCacheMap->SharedCacheMapLinks ); InsertTailList( &CcCleanSharedCacheMapList, &SharedCacheMap->SharedCacheMapLinks ); } } // // Release global resource // if (SharedListOwned) { CcReleaseMasterLock( OldIrql ); } if (CacheMapToFree != NULL) { ExFreePool(CacheMapToFree); } if (!NT_SUCCESS(Status)) { DebugTrace(-1, me, "CcInitializeCacheMap -> RAISING EXCEPTION\n", 0 ); ExRaiseStatus(Status); } DebugTrace(-1, me, "CcInitializeCacheMap -> VOID\n", 0 ); return; } BOOLEAN CcUninitializeCacheMap ( IN PFILE_OBJECT FileObject, IN PLARGE_INTEGER TruncateSize OPTIONAL, IN PCACHE_UNINITIALIZE_EVENT UninitializeEvent OPTIONAL ) /*++ Routine Description: This routine uninitializes the previously initialized Shared and Private Cache Maps. This routine is only intended to be called by File Systems. It should be called when the File System receives a cleanup call on the File Object. A File System which supports data caching must always call this routine whenever it closes a file, whether the caller opened the file with NO_INTERMEDIATE_BUFFERING as FALSE or not. This is because the final cleanup of a file related to truncation or deletion of the file, can only occur on the last close, whether the last closer cached the file or not. When CcUnitializeCacheMap is called on a file object for which CcInitializeCacheMap was never called, the call has a benign effect iff no one has truncated or deleted the file; otherwise the necessary cleanup relating to the truncate or close is performed. In summary, CcUnitializeCacheMap does the following: If the caller had Write or Delete access, the cache is flushed. (This could change with lazy writing.) If a Cache Map was initialized on this File Object, it is unitialized (unmap any views, delete section, and delete Cache Map structures). On the last Cleanup, if the file has been deleted, the Section is forced closed. If the file has been truncated, then the truncated pages are purged from the cache. Arguments: FileObject - File Object which was previously supplied to CcInitializeCacheMap. TruncateSize - If specified, the file was truncated to the specified size, and the cache should be purged accordingly. UninitializeEvent - If specified, then the provided event will be set to the signalled state when the actual flush is completed. This is only of interest to file systems that require that they be notified when a cache flush operation has completed. Due to network protocol restrictions, it is critical that network file systems know exactly when a cache flush operation completes, by specifying this event, they can be notified when the cache section is finally purged if the section is "lazy-deleted". ReturnValue: FALSE if Section was not closed. TRUE if Section was closed. --*/ { KIRQL OldIrql; PSHARED_CACHE_MAP SharedCacheMap; ULONG ActivePage; ULONG PageIsDirty; PVACB ActiveVacb = NULL; BOOLEAN SectionClosed = FALSE; PPRIVATE_CACHE_MAP PrivateCacheMap; DebugTrace(+1, me, "CcUninitializeCacheMap:\n", 0 ); DebugTrace( 0, me, " FileObject = %08lx\n", FileObject ); DebugTrace( 0, me, " &TruncateSize = %08lx\n", TruncateSize ); // // Serialize Creation/Deletion of all Shared CacheMaps // CcAcquireMasterLock( &OldIrql ); // // Get pointer to SharedCacheMap via File Object. // SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap; PrivateCacheMap = FileObject->PrivateCacheMap; // // Decrement Open Count on SharedCacheMap, if we did a cached open. // Also unmap PrivateCacheMap if it is mapped and deallocate it. // if (PrivateCacheMap != NULL) { ASSERT( PrivateCacheMap->FileObject == FileObject ); CcDecrementOpenCount( SharedCacheMap, 'ninU' ); // // Remove PrivateCacheMap from list in SharedCacheMap. // RemoveEntryList( &PrivateCacheMap->PrivateLinks ); // // Free local or allocated PrivateCacheMap // if (PrivateCacheMap == &SharedCacheMap->PrivateCacheMap) { PrivateCacheMap->NodeTypeCode = 0; PrivateCacheMap = NULL; } FileObject->PrivateCacheMap = (PPRIVATE_CACHE_MAP)NULL; } // // Now if we have a SharedCacheMap whose Open Count went to 0, we // have some additional cleanup. // if (SharedCacheMap != NULL) { // // If a Truncate Size was specified, then remember that we want to // truncate the FileSize and purge the unneeded pages when OpenCount // goes to 0. // if (ARGUMENT_PRESENT(TruncateSize)) { if ( (TruncateSize->QuadPart == 0) && (SharedCacheMap->FileSize.QuadPart != 0) ) { SetFlag(SharedCacheMap->Flags, TRUNCATE_REQUIRED); } else if (IsListEmpty(&SharedCacheMap->PrivateList)) { // // If this is the last guy, I can drop the file size down // now. // SharedCacheMap->FileSize = *TruncateSize; } } // // If other file objects are still using this SharedCacheMap, // then we are done now. // if (SharedCacheMap->OpenCount != 0) { DebugTrace(-1, me, "SharedCacheMap OpenCount != 0\n", 0); // // If the caller specified an event to be set when // the cache uninitialize is completed, set the event // now, because the uninitialize is complete for this file. // (Note, we make him wait if he is the last guy.) // if (ARGUMENT_PRESENT(UninitializeEvent)) { if (!IsListEmpty(&SharedCacheMap->PrivateList)) { KeSetEvent(&UninitializeEvent->Event, 0, FALSE); } else { UninitializeEvent->Next = SharedCacheMap->UninitializeEvent; SharedCacheMap->UninitializeEvent = UninitializeEvent; } } CcReleaseMasterLock( OldIrql ); // // Free PrivateCacheMap now that we no longer have the spinlock. // if (PrivateCacheMap != NULL) { ExFreePool( PrivateCacheMap ); } DebugTrace(-1, me, "CcUnitializeCacheMap -> %02lx\n", FALSE ); return FALSE; } // // Remove the private write flag synchronously. Even though a // private writer is also opening the file exclusively, the // shared cache map is not going away synchronously and we // cannot let a non private writer re-reference the scm in // this state. Their data will never be written! // if (FlagOn(SharedCacheMap->Flags, PRIVATE_WRITE)) { ClearFlag(SharedCacheMap->Flags, PRIVATE_WRITE | DISABLE_WRITE_BEHIND); MmEnableModifiedWriteOfSection( FileObject->SectionObjectPointer ); } // // The private cache map list better be empty! // ASSERT(IsListEmpty(&SharedCacheMap->PrivateList)); // // Set the "uninitialize complete" in the shared cache map // so that CcDeleteSharedCacheMap will delete it. // if (ARGUMENT_PRESENT(UninitializeEvent)) { UninitializeEvent->Next = SharedCacheMap->UninitializeEvent; SharedCacheMap->UninitializeEvent = UninitializeEvent; } // // We are in the process of deleting this cache map. If the // Lazy Writer is active or the Bcb list is not empty or the Lazy // Writer will hit this SharedCacheMap because we are purging // the file to 0, then get out and let the Lazy Writer clean // up. // if ((!FlagOn(SharedCacheMap->Flags, PIN_ACCESS) && !ARGUMENT_PRESENT(UninitializeEvent)) || FlagOn(SharedCacheMap->Flags, WRITE_QUEUED) || (SharedCacheMap->DirtyPages != 0)) { // // Move it to the dirty list so the lazy write scan will // see it. // if (!FlagOn(SharedCacheMap->Flags, WRITE_QUEUED)) { RemoveEntryList( &SharedCacheMap->SharedCacheMapLinks ); InsertTailList( &CcDirtySharedCacheMapList.SharedCacheMapLinks, &SharedCacheMap->SharedCacheMapLinks ); } // // Make sure the Lazy Writer will wake up, because we // want him to delete this SharedCacheMap. // LazyWriter.OtherWork = TRUE; if (!LazyWriter.ScanActive) { CcScheduleLazyWriteScan( FALSE ); } // // Get the active Vacb if we are going to lazy delete, to // free it for someone who can use it. // GetActiveVacbAtDpcLevel( SharedCacheMap, ActiveVacb, ActivePage, PageIsDirty ); DebugTrace(-1, me, "SharedCacheMap has Bcbs and not purging to 0\n", 0); CcReleaseMasterLock( OldIrql ); ASSERT (SectionClosed == FALSE); } else { // // Now we can delete the SharedCacheMap. If there are any Bcbs, // then we must be truncating to 0, and they will also be deleted. // On return the Shared Cache Map List Spinlock will be released. // CcDeleteSharedCacheMap( SharedCacheMap, OldIrql, FALSE ); SectionClosed = TRUE; } } // // No Shared Cache Map. To make the file go away, we still need to // purge the section, if one exists. (And we still need to release // our global list first to avoid deadlocks.) // else { if (ARGUMENT_PRESENT(TruncateSize) && ( TruncateSize->QuadPart == 0 ) && (*(PCHAR *)FileObject->SectionObjectPointer != NULL)) { CcReleaseMasterLock( OldIrql ); DebugTrace( 0, mm, "MmPurgeSection:\n", 0 ); DebugTrace( 0, mm, " SectionObjectPointer = %08lx\n", FileObject->SectionObjectPointer ); DebugTrace2(0, mm, " Offset = %08lx\n", TruncateSize->LowPart, TruncateSize->HighPart ); // // 0 Length means to purge from the TruncateSize on. // CcPurgeCacheSection( FileObject->SectionObjectPointer, TruncateSize, 0, FALSE ); } else { CcReleaseMasterLock( OldIrql ); } // // If the caller specified an event to be set when // the cache uninitialize is completed, set the event // now, because the uninitialize is complete for this file. // if (ARGUMENT_PRESENT(UninitializeEvent)) { KeSetEvent(&UninitializeEvent->Event, 0, FALSE); } } // // Free the active vacb, if we found one. // if (ActiveVacb != NULL) { CcFreeActiveVacb( ActiveVacb->SharedCacheMap, ActiveVacb, ActivePage, PageIsDirty ); } // // Free PrivateCacheMap now that we no longer have the spinlock. // if (PrivateCacheMap != NULL) { ExFreePool( PrivateCacheMap ); } DebugTrace(-1, me, "CcUnitializeCacheMap -> %02lx\n", SectionClosed ); return SectionClosed; } // // Internal support routine. // VOID CcDeleteBcbs ( IN PSHARED_CACHE_MAP SharedCacheMap ) /*++ Routine Description: This routine may be called to delete all Bcbs for a stream. External synchronization must be acquired to guarantee no active pin on any bcb. Arguments: SharedCacheMap - Pointer to SharedCacheMap. Return Value: None. --*/ { KIRQL OldIrql; PLIST_ENTRY NextEntry; PBCB Bcb; // // If there are Bcbs, then empty the list. None of them can be pinned now! // Either the file is being truncated, in which case synchronization with // the lazy writer must have been externally acheived, or the file is being // closed down and nothing should be able to get a fresh reference on this // shared cache map. // NextEntry = SharedCacheMap->BcbList.Flink; while (NextEntry != &SharedCacheMap->BcbList) { Bcb = (PBCB)CONTAINING_RECORD( NextEntry, BCB, BcbLinks ); NextEntry = Bcb->BcbLinks.Flink; // // Skip over the pendaflex entries, only removing true Bcbs // so that level teardown doesn't need to special case unhooking // the pendaflex. This has the side benefit of dramatically // reducing write traffic to memory on teardown of large files. // if (Bcb->NodeTypeCode == CACHE_NTC_BCB) { ASSERT( Bcb->PinCount == 0 ); RemoveEntryList( &Bcb->BcbLinks ); // // For large metadata streams we unlock the Vacb level when // removing. We do not need spinlocks since no other thread // can be accessing this list when we are deleting the // SharedCacheMap. // CcUnlockVacbLevel( SharedCacheMap, Bcb->FileOffset.QuadPart ); // // There is a small window where the data could still be mapped // if (for example) the Lazy Writer collides with a CcCopyWrite // in the foreground, and then someone calls CcUninitializeCacheMap // while the Lazy Writer is active. This is because the Lazy // Writer biases the pin count. Deal with that here. // if (Bcb->BaseAddress != NULL) { CcFreeVirtualAddress( Bcb->Vacb ); } #if LIST_DBG // // Debug routines used to remove Bcbs from the global list // OldIrql = KeAcquireQueuedSpinLock( LockQueueBcbLock ); if (Bcb->CcBcbLinks.Flink != NULL) { RemoveEntryList( &Bcb->CcBcbLinks ); CcBcbCount -= 1; } KeReleaseQueuedSpinLock( LockQueueBcbLock, OldIrql ); #endif // // If the Bcb is dirty, we have to synchronize with the Lazy Writer // and reduce the total number of dirty. // CcAcquireMasterLock( &OldIrql ); if (Bcb->Dirty) { CcDeductDirtyPages( SharedCacheMap, Bcb->ByteLength >> PAGE_SHIFT ); } CcReleaseMasterLock( OldIrql ); CcDeallocateBcb( Bcb ); } } } // // Internal support routine. // VOID FASTCALL CcDeleteSharedCacheMap ( IN PSHARED_CACHE_MAP SharedCacheMap, IN KIRQL ListIrql, IN ULONG ReleaseFile ) /*++ Routine Description: The specified SharedCacheMap is removed from the global list of SharedCacheMap's and deleted with all of its related structures. Other objects which were referenced in CcInitializeCacheMap are dereferenced here. NOTE: The CcMasterSpinLock must already be acquired on entry. It is released on return. Arguments: SharedCacheMap - Pointer to Cache Map to delete ListIrql - priority to restore to when releasing shared cache map list ReleaseFile - Supplied as nonzero if file was acquired exclusive and should be released. ReturnValue: None. --*/ { LIST_ENTRY LocalList; PFILE_OBJECT FileObject; PVACB ActiveVacb; ULONG ActivePage; ULONG PageIsDirty; DebugTrace(+1, me, "CcDeleteSharedCacheMap:\n", 0 ); DebugTrace( 0, me, " SharedCacheMap = %08lx\n", SharedCacheMap ); // // Remove it from the global list and clear the pointer to it via // the File Object. // RemoveEntryList( &SharedCacheMap->SharedCacheMapLinks ); // // Zero pointer to SharedCacheMap. Once we have cleared the pointer, // we can/must release the global list to avoid deadlocks. // FileObject = SharedCacheMap->FileObject; FileObject->SectionObjectPointer->SharedCacheMap = (PSHARED_CACHE_MAP)NULL; SetFlag( SharedCacheMap->Flags, WRITE_QUEUED ); // // The OpenCount is 0, but we still need to flush out any dangling // cache read or writes. // if ((SharedCacheMap->VacbActiveCount != 0) || (SharedCacheMap->NeedToZero != NULL)) { // // We will put it in a local list and set a flag // to keep the Lazy Writer away from it, so that we can rip it out // below if someone manages to sneak in and set something dirty, etc. // If the file system does not synchronize cleanup calls with an // exclusive on the stream, then this case is possible. // InitializeListHead( &LocalList ); InsertTailList( &LocalList, &SharedCacheMap->SharedCacheMapLinks ); // // If there is an active Vacb, then nuke it now (before waiting!). // GetActiveVacbAtDpcLevel( SharedCacheMap, ActiveVacb, ActivePage, PageIsDirty ); CcReleaseMasterLock( ListIrql ); // // No point in saying the page is dirty (which can cause an allocation // failure), since we are deleting this SharedCacheMap anyway. // CcFreeActiveVacb( SharedCacheMap, ActiveVacb, ActivePage, FALSE ); while (SharedCacheMap->VacbActiveCount != 0) { CcWaitOnActiveCount( SharedCacheMap ); } // // Now in case we hit the rare path where someone moved the // SharedCacheMap again, do a remove again now. It may be // from our local list or it may be from the dirty list, // but who cares? The important thing is to remove it in // the case it was the dirty list, since we will delete it // below. // CcAcquireMasterLock( &ListIrql ); RemoveEntryList( &SharedCacheMap->SharedCacheMapLinks ); } CcReleaseMasterLock( ListIrql ); // // If there are Bcbs, then empty the list. // // I really wonder how often we have Bcbs at teardown. This is // a lot of work that could be avoided otherwise. // if (!IsListEmpty( &SharedCacheMap->BcbList )) { CcDeleteBcbs( SharedCacheMap ); } // // Call local routine to unmap, and purge if necessary. // CcUnmapAndPurge( SharedCacheMap ); // // Now release the file now that the purge is done. // if (ReleaseFile) { FsRtlReleaseFile( SharedCacheMap->FileObject ); } // // Dereference our pointer to the Section and FileObject // (We have to test the Section pointer since CcInitializeCacheMap // calls this routine for error recovery. Release our global // resource before dereferencing the FileObject to avoid deadlocks. // if (SharedCacheMap->Section != NULL) { ObDereferenceObject( SharedCacheMap->Section ); } ObDereferenceObject( FileObject ); // // If there is an Mbcb, deduct any dirty pages and deallocate. // if (SharedCacheMap->Mbcb != NULL) { CcDeleteMbcb( SharedCacheMap ); } // // If there was an uninitialize event specified for this shared cache // map, then set it to the signalled state, indicating that we are // removing the section and deleting the shared cache map. // if (SharedCacheMap->UninitializeEvent != NULL) { PCACHE_UNINITIALIZE_EVENT CUEvent, EventNext; CUEvent = SharedCacheMap->UninitializeEvent; while (CUEvent != NULL) { EventNext = CUEvent->Next; KeSetEvent(&CUEvent->Event, 0, FALSE); CUEvent = EventNext; } } // // Now delete the Vacb vector. // if ((SharedCacheMap->Vacbs != &SharedCacheMap->InitialVacbs[0]) && (SharedCacheMap->Vacbs != NULL)) { // // If there are Vacb levels, then the Vacb Array better be in an empty state. // ASSERT((SharedCacheMap->SectionSize.QuadPart <= VACB_SIZE_OF_FIRST_LEVEL) || !IsVacbLevelReferenced( SharedCacheMap, SharedCacheMap->Vacbs, 1 )); ExFreePool( SharedCacheMap->Vacbs ); } // // If an event had to be allocated for this SharedCacheMap, // deallocate it. // if ((SharedCacheMap->CreateEvent != NULL) && (SharedCacheMap->CreateEvent != &SharedCacheMap->Event)) { ExFreePool( SharedCacheMap->CreateEvent ); } if ((SharedCacheMap->WaitOnActiveCount != NULL) && (SharedCacheMap->WaitOnActiveCount != &SharedCacheMap->Event)) { ExFreePool( SharedCacheMap->WaitOnActiveCount ); } // // Deallocate the storeage for the SharedCacheMap. // ExFreePool( SharedCacheMap ); DebugTrace(-1, me, "CcDeleteSharedCacheMap -> VOID\n", 0 ); return; } VOID CcSetFileSizes ( IN PFILE_OBJECT FileObject, IN PCC_FILE_SIZES FileSizes ) /*++ Routine Description: This routine must be called whenever a file has been extended to reflect this extension in the cache maps and underlying section. Calling this routine has a benign effect if the current size of the section is already greater than or equal to the new AllocationSize. This routine must also be called whenever the FileSize for a file changes to reflect these changes in the Cache Manager. This routine seems rather large, but in the normal case it only acquires a spinlock, updates some fields, and exits. Less often it will either extend the section, or truncate/purge the file, but it would be unexpected to do both. On the other hand, the idea of this routine is that it does "everything" required when AllocationSize or FileSize change. Arguments: FileObject - A file object for which CcInitializeCacheMap has been previously called. FileSizes - A pointer to AllocationSize, FileSize and ValidDataLength for the file. AllocationSize is ignored if it is not larger than the current section size (i.e., it is ignored unless it has grown). ValidDataLength is not used. Return Value: None --*/ { LARGE_INTEGER NewSectionSize; LARGE_INTEGER NewFileSize; LARGE_INTEGER NewValidDataLength; IO_STATUS_BLOCK IoStatus; PSHARED_CACHE_MAP SharedCacheMap; NTSTATUS Status; KIRQL OldIrql; PVACB ActiveVacb; ULONG ActivePage; ULONG PageIsDirty; DebugTrace(+1, me, "CcSetFileSizes:\n", 0 ); DebugTrace( 0, me, " FileObject = %08lx\n", FileObject ); DebugTrace( 0, me, " FileSizes = %08lx\n", FileSizes ); // // Make a local copy of the new file size and section size. // NewSectionSize = FileSizes->AllocationSize; NewFileSize = FileSizes->FileSize; NewValidDataLength = FileSizes->ValidDataLength; // // Serialize Creation/Deletion of all Shared CacheMaps // CcAcquireMasterLock( &OldIrql ); // // Get pointer to SharedCacheMap via File Object. // SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap; // // If the file is not cached, just get out. // if ((SharedCacheMap == NULL) || (SharedCacheMap->Section == NULL)) { CcReleaseMasterLock( OldIrql ); // // Let's try to purge the file incase this is a truncate. In the // vast majority of cases when there is no shared cache map, there // is no data section either, so this call will eventually be // no-oped in Mm. // // First flush the first page we are keeping, if it has data, before // we throw it away. // if (NewFileSize.LowPart & (PAGE_SIZE - 1)) { MmFlushSection( FileObject->SectionObjectPointer, &NewFileSize, 1, &IoStatus, FALSE ); } CcPurgeCacheSection( FileObject->SectionObjectPointer, &NewFileSize, 0, FALSE ); DebugTrace(-1, me, "CcSetFileSizes -> VOID\n", 0 ); return; } // // Make call a Noop if file is not mapped, or section already big enough. // if ( NewSectionSize.QuadPart > SharedCacheMap->SectionSize.QuadPart ) { // // Increment open count to make sure the SharedCacheMap stays around, // then release the spinlock so that we can call Mm. // CcIncrementOpenCount( SharedCacheMap, '1fSS' ); CcReleaseMasterLock( OldIrql ); // // Round new section size to pages. // NewSectionSize.QuadPart = NewSectionSize.QuadPart + (LONGLONG)(DEFAULT_EXTEND_MODULO - 1); NewSectionSize.LowPart &= ~(DEFAULT_EXTEND_MODULO - 1); // // Call MM to extend the section. // DebugTrace( 0, mm, "MmExtendSection:\n", 0 ); DebugTrace( 0, mm, " Section = %08lx\n", SharedCacheMap->Section ); DebugTrace2(0, mm, " Size = %08lx, %08lx\n", NewSectionSize.LowPart, NewSectionSize.HighPart ); Status = MmExtendSection( SharedCacheMap->Section, &NewSectionSize, TRUE ); if (NT_SUCCESS(Status)) { // // Extend the Vacb array. // Status = CcExtendVacbArray( SharedCacheMap, NewSectionSize ); } else { DebugTrace( 0, 0, "Error from MmExtendSection, Status = %08lx\n", Status ); Status = FsRtlNormalizeNtstatus( Status, STATUS_UNEXPECTED_MM_EXTEND_ERR ); } // // Serialize again to decrement the open count. // CcAcquireMasterLock( &OldIrql ); CcDecrementOpenCount( SharedCacheMap, '1fSF' ); if ((SharedCacheMap->OpenCount == 0) && !FlagOn(SharedCacheMap->Flags, WRITE_QUEUED) && (SharedCacheMap->DirtyPages == 0)) { // // Move to the dirty list. // RemoveEntryList( &SharedCacheMap->SharedCacheMapLinks ); InsertTailList( &CcDirtySharedCacheMapList.SharedCacheMapLinks, &SharedCacheMap->SharedCacheMapLinks ); // // Make sure the Lazy Writer will wake up, because we // want him to delete this SharedCacheMap. // LazyWriter.OtherWork = TRUE; if (!LazyWriter.ScanActive) { CcScheduleLazyWriteScan( FALSE ); } } // // If section or VACB extension failed, raise an // exception to our caller. // if (!NT_SUCCESS(Status)) { CcReleaseMasterLock( OldIrql ); ExRaiseStatus( Status ); } // // It is now very unlikely that we have any more work to do, but since // the spinlock is already held, check again if we are cached. // // // Get pointer to SharedCacheMap via File Object. // SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap; // // If the file is not cached, just get out. // if (SharedCacheMap == NULL) { CcReleaseMasterLock( OldIrql ); DebugTrace(-1, me, "CcSetFileSizes -> VOID\n", 0 ); return; } } // // If we are shrinking either of these two sizes, then we must free the // active page, since it may be locked. // CcIncrementOpenCount( SharedCacheMap, '2fSS' ); if ( ( NewFileSize.QuadPart < SharedCacheMap->ValidDataGoal.QuadPart ) || ( NewFileSize.QuadPart < SharedCacheMap->FileSize.QuadPart )) { GetActiveVacbAtDpcLevel( SharedCacheMap, ActiveVacb, ActivePage, PageIsDirty ); if ((ActiveVacb != NULL) || (SharedCacheMap->NeedToZero != NULL)) { CcReleaseMasterLock( OldIrql ); CcFreeActiveVacb( SharedCacheMap, ActiveVacb, ActivePage, PageIsDirty ); // // Serialize again to reduce ValidDataLength. It cannot change // because the caller must have the file exclusive. // CcAcquireMasterLock( &OldIrql ); } } // // If the section did not grow, see if the file system supports // ValidDataLength, then update the valid data length in the file system. // if ( SharedCacheMap->ValidDataLength.QuadPart != MAXLONGLONG ) { if ( NewFileSize.QuadPart < SharedCacheMap->ValidDataLength.QuadPart ) { SharedCacheMap->ValidDataLength = NewFileSize; } // // Update our notion of ValidDataGoal (how far the file has been // written in the cache) with caller's ValidDataLength. (Our // ValidDataLength controls when we issue ValidDataLength callbacks.) // SharedCacheMap->ValidDataGoal = NewValidDataLength; } // // On truncate, be nice guys and actually purge away user data from // the cache. However, the PinAccess check is important to avoid deadlocks // in Ntfs. // // It is also important to check the Vacb Active count. The caller // must have the file exclusive, therefore, no one else can be actively // doing anything in the file. Normally the Active count will be zero // (like in a normal call from Set File Info), and we can go ahead and // truncate. However, if the active count is nonzero, chances are this // very thread has something pinned or mapped, and we will deadlock if // we try to purge and wait for the count to go zero. A rare case of // this which deadlocked DaveC on Christmas Day of 1992, is where Ntfs // was trying to convert an attribute from resident to nonresident - which // is a good example of a case where the purge was not needed. // if ( (NewFileSize.QuadPart < SharedCacheMap->FileSize.QuadPart ) && !FlagOn(SharedCacheMap->Flags, PIN_ACCESS) && (SharedCacheMap->VacbActiveCount == 0)) { // // Release the spinlock so that we can call Mm. // CcReleaseMasterLock( OldIrql ); // // If we are actually truncating to zero (a size which has particular // meaning to the Lazy Writer scan!) then we must reset the Mbcb/Bcbs, // if there are any, so that we do not keep dirty pages around forever. // if (NewFileSize.QuadPart == 0) { if (SharedCacheMap->Mbcb != NULL) { CcDeleteMbcb( SharedCacheMap ); } if (!IsListEmpty( &SharedCacheMap->BcbList )) { CcDeleteBcbs( SharedCacheMap ); } } CcPurgeAndClearCacheSection( SharedCacheMap, &NewFileSize ); // // Serialize again to decrement the open count. // CcAcquireMasterLock( &OldIrql ); } CcDecrementOpenCount( SharedCacheMap, '2fSF' ); SharedCacheMap->FileSize = NewFileSize; if ((SharedCacheMap->OpenCount == 0) && !FlagOn(SharedCacheMap->Flags, WRITE_QUEUED) && (SharedCacheMap->DirtyPages == 0)) { // // Move to the dirty list. // RemoveEntryList( &SharedCacheMap->SharedCacheMapLinks ); InsertTailList( &CcDirtySharedCacheMapList.SharedCacheMapLinks, &SharedCacheMap->SharedCacheMapLinks ); // // Make sure the Lazy Writer will wake up, because we // want him to delete this SharedCacheMap. // LazyWriter.OtherWork = TRUE; if (!LazyWriter.ScanActive) { CcScheduleLazyWriteScan( FALSE ); } } CcReleaseMasterLock( OldIrql ); DebugTrace(-1, me, "CcSetFileSizes -> VOID\n", 0 ); return; } VOID CcPurgeAndClearCacheSection ( IN PSHARED_CACHE_MAP SharedCacheMap, IN PLARGE_INTEGER FileOffset ) /*++ Routine Description: This routine calls CcPurgeCacheSection after zeroing the end any partial page at the start of the range. If the file is not cached it flushes this page before the purge. Arguments: SectionObjectPointer - A pointer to the Section Object Pointers structure in the nonpaged Fcb. FileOffset - Offset from which file should be purged - rounded down to page boundary. If NULL, purge the entire file. ReturnValue: FALSE - if the section was not successfully purged TRUE - if the section was successfully purged --*/ { ULONG TempLength, Length; LARGE_INTEGER LocalFileOffset; IO_STATUS_BLOCK IoStatus; PVOID TempVa; PVACB Vacb; // // Awareness is indicated by the lowbit of the fileoffset pointer. // Non-awareness of a private write stream results in a no-op. // if (FlagOn( SharedCacheMap->Flags, PRIVATE_WRITE )) { if (((ULONG_PTR)FileOffset & 1) == 0) { return; } FileOffset = (PLARGE_INTEGER)((ULONG_PTR)FileOffset ^ 1); } // // If a range was specified, then we have to see if we need to // save any user data before purging. // if ((FileOffset->LowPart & (PAGE_SIZE - 1)) != 0) { // // Switch to LocalFileOffset. We do it this way because we // still pass it on as an optional parameter. // LocalFileOffset = *FileOffset; FileOffset = &LocalFileOffset; // // If the file is cached, then we can actually zero the data to // be purged in memory, and not purge those pages. This is a huge // savings, because sometimes the flushes in the other case cause // us to kill lots of stack, time and I/O doing CcZeroData in especially // large user-mapped files. // if ((SharedCacheMap->Section != NULL) && (SharedCacheMap->Vacbs != NULL)) { // // First zero the first page we are keeping, if it has data, and // adjust FileOffset and Length to allow it to stay. // TempLength = PAGE_SIZE - (FileOffset->LowPart & (PAGE_SIZE - 1)); TempVa = CcGetVirtualAddress( SharedCacheMap, *FileOffset, &Vacb, &Length ); // // Do not map and zero the page if we are not reducing our notion // of Valid Data, because that does two bad things. First // CcSetDirtyInMask will arbitrarily smash up ValidDataGoal // (causing a potential invalid CcSetValidData call). Secondly, // if the Lazy Writer writes the last page ahead of another flush // through MM, then the file system will never see a write from // MM, and will not include the last page in ValidDataLength on // disk. // RtlZeroMemory( TempVa, TempLength ); if (FileOffset->QuadPart <= SharedCacheMap->ValidDataGoal.QuadPart) { // // Make sure the Lazy Writer writes it. // CcSetDirtyInMask( SharedCacheMap, FileOffset, TempLength ); // // Otherwise, we are mapped, so make sure at least that Mm // knows the page is dirty since we zeroed it. // } else { MmSetAddressRangeModified( TempVa, 1 ); } FileOffset->QuadPart += (LONGLONG)TempLength; // // If we get any kind of error, like failing to read the page from // the network, just charge on. Note that we only read it in order // to zero it and avoid the flush below, so if we cannot read it // there is really no stale data problem. // CcFreeVirtualAddress( Vacb ); } else { // // First flush the first page we are keeping, if it has data, before // we throw it away. // MmFlushSection( SharedCacheMap->FileObject->SectionObjectPointer, FileOffset, 1, &IoStatus, FALSE ); } } CcPurgeCacheSection( SharedCacheMap->FileObject->SectionObjectPointer, FileOffset, 0, FALSE ); } BOOLEAN CcPurgeCacheSection ( IN PSECTION_OBJECT_POINTERS SectionObjectPointer, IN PLARGE_INTEGER FileOffset, IN ULONG Length, IN BOOLEAN UninitializeCacheMaps ) /*++ Routine Description: This routine may be called to force a purge of the cache section, even if it is cached. Note, if a user has the file mapped, then the purge will *not* take effect, and this must be considered part of normal application interaction. The purpose of purge is to throw away potentially nonzero data, so that it will be read in again and presumably zeroed. This is not really a security issue, but rather an effort to not confuse the application when it sees nonzero data. We cannot help the fact that a user-mapped view forces us to hang on to stale data. This routine is intended to be called whenever previously written data is being truncated from the file, and the file is not being deleted. The file must be acquired exclusive in order to call this routine. Arguments: SectionObjectPointer - A pointer to the Section Object Pointers structure in the nonpaged Fcb. FileOffset - Offset from which file should be purged - rounded down to page boundary. If NULL, purge the entire file. Length - Defines the length of the byte range to purge, starting at FileOffset. This parameter is ignored if FileOffset is specified as NULL. If FileOffset is specified and Length is 0, then purge from FileOffset to the end of the file. UninitializeCacheMaps - If TRUE, we should uninitialize all the private cache maps before purging the data. ReturnValue: FALSE - if the section was not successfully purged TRUE - if the section was successfully purged --*/ { KIRQL OldIrql; PSHARED_CACHE_MAP SharedCacheMap; PPRIVATE_CACHE_MAP PrivateCacheMap; ULONG ActivePage; ULONG PageIsDirty; BOOLEAN PurgeWorked = TRUE; PVACB Vacb = NULL; DebugTrace(+1, me, "CcPurgeCacheSection:\n", 0 ); DebugTrace( 0, mm, " SectionObjectPointer = %08lx\n", SectionObjectPointer ); DebugTrace2(0, me, " FileOffset = %08lx, %08lx\n", ARGUMENT_PRESENT(FileOffset) ? FileOffset->LowPart : 0, ARGUMENT_PRESENT(FileOffset) ? FileOffset->HighPart : 0 ); DebugTrace( 0, me, " Length = %08lx\n", Length ); // // If you want us to uninitialize cache maps, the RtlZeroMemory paths // below depend on actually having to purge something after zeroing. // ASSERT(!UninitializeCacheMaps || (Length == 0) || (Length >= PAGE_SIZE * 2)); // // Serialize Creation/Deletion of all Shared CacheMaps // CcAcquireMasterLock( &OldIrql ); // // Get pointer to SharedCacheMap via File Object. // SharedCacheMap = SectionObjectPointer->SharedCacheMap; // // Increment open count to make sure the SharedCacheMap stays around, // then release the spinlock so that we can call Mm. // if (SharedCacheMap != NULL) { // // Awareness is indicated by the lowbit of the fileoffset pointer. // Non-awareness of a private write stream results in a no-op. // if (FlagOn( SharedCacheMap->Flags, PRIVATE_WRITE )) { if (((ULONG_PTR)FileOffset & 1) == 0) { CcReleaseMasterLock( OldIrql ); return TRUE; } FileOffset = (PLARGE_INTEGER)((ULONG_PTR)FileOffset ^ 1); } CcIncrementOpenCount( SharedCacheMap, 'scPS' ); // // If there is an active Vacb, then nuke it now (before waiting!). // GetActiveVacbAtDpcLevel( SharedCacheMap, Vacb, ActivePage, PageIsDirty ); } CcReleaseMasterLock( OldIrql ); if (Vacb != NULL) { CcFreeActiveVacb( SharedCacheMap, Vacb, ActivePage, PageIsDirty ); } // // Increment open count to make sure the SharedCacheMap stays around, // then release the spinlock so that we can call Mm. // if (SharedCacheMap != NULL) { // // Now loop to make sure that no one is currently caching the file. // if (UninitializeCacheMaps) { while (!IsListEmpty( &SharedCacheMap->PrivateList )) { PrivateCacheMap = CONTAINING_RECORD( SharedCacheMap->PrivateList.Flink, PRIVATE_CACHE_MAP, PrivateLinks ); CcUninitializeCacheMap( PrivateCacheMap->FileObject, NULL, NULL ); } } // // Now, let's unmap and purge here. // // We still need to wait for any dangling cache read or writes. // // In fact we have to loop and wait because the lazy writer can // sneak in and do an CcGetVirtualAddressIfMapped, and we are not // synchronized. // while ((SharedCacheMap->Vacbs != NULL) && !CcUnmapVacbArray( SharedCacheMap, FileOffset, Length, FALSE )) { CcWaitOnActiveCount( SharedCacheMap ); } } // // Purge failures are extremely rare if there are no user mapped sections. // However, it is possible that we will get one from our own mapping, if // the file is being lazy deleted from a previous open. For that case // we wait here until the purge succeeds, so that we are not left with // old user file data. Although Length is actually invariant in this loop, // we do need to keep checking that we are allowed to truncate in case a // user maps the file during a delay. // while (!(PurgeWorked = MmPurgeSection(SectionObjectPointer, FileOffset, Length, (BOOLEAN)((SharedCacheMap !=NULL) && ARGUMENT_PRESENT(FileOffset)))) && (Length == 0) && MmCanFileBeTruncated(SectionObjectPointer, FileOffset)) { (VOID)KeDelayExecutionThread( KernelMode, FALSE, &CcCollisionDelay ); } // // Reduce the open count on the SharedCacheMap if there was one. // if (SharedCacheMap != NULL) { // // Serialize again to decrement the open count. // CcAcquireMasterLock( &OldIrql ); CcDecrementOpenCount( SharedCacheMap, 'scPF' ); if ((SharedCacheMap->OpenCount == 0) && !FlagOn(SharedCacheMap->Flags, WRITE_QUEUED) && (SharedCacheMap->DirtyPages == 0)) { // // Move to the dirty list. // RemoveEntryList( &SharedCacheMap->SharedCacheMapLinks ); InsertTailList( &CcDirtySharedCacheMapList.SharedCacheMapLinks, &SharedCacheMap->SharedCacheMapLinks ); // // Make sure the Lazy Writer will wake up, because we // want him to delete this SharedCacheMap. // LazyWriter.OtherWork = TRUE; if (!LazyWriter.ScanActive) { CcScheduleLazyWriteScan( FALSE ); } } CcReleaseMasterLock( OldIrql ); } DebugTrace(-1, me, "CcPurgeCacheSection -> %02lx\n", PurgeWorked ); return PurgeWorked; } // // Internal support routine. // VOID CcUnmapAndPurge( IN PSHARED_CACHE_MAP SharedCacheMap ) /*++ Routine Description: This routine may be called to unmap and purge a section, causing Memory Management to throw the pages out and reset his notion of file size. Arguments: SharedCacheMap - Pointer to SharedCacheMap of section to purge. Return Value: None. --*/ { PFILE_OBJECT FileObject; FileObject = SharedCacheMap->FileObject; // // Unmap all Vacbs // if (SharedCacheMap->Vacbs != NULL) { (VOID)CcUnmapVacbArray( SharedCacheMap, NULL, 0, FALSE ); } // // Now that the file is unmapped, we can purge the truncated // pages from memory, if TRUNCATE_REQUIRED. Note that since the // entire section is being purged (FileSize == NULL), the purge // and subsequent delete of the SharedCacheMap should drop // all references on the section and file object clearing the // way for the Close Call and actual file delete to occur // immediately. // if (FlagOn(SharedCacheMap->Flags, TRUNCATE_REQUIRED)) { DebugTrace( 0, mm, "MmPurgeSection:\n", 0 ); DebugTrace( 0, mm, " SectionObjectPointer = %08lx\n", FileObject->SectionObjectPointer ); DebugTrace2(0, mm, " Offset = %08lx\n", SharedCacheMap->FileSize.LowPart, SharedCacheMap->FileSize.HighPart ); CcPurgeCacheSection( FileObject->SectionObjectPointer, NULL, 0, FALSE ); } } VOID CcDeleteMbcb( IN PSHARED_CACHE_MAP SharedCacheMap ) /*++ Routine Description: This routine may be called to reset the Mbcb for a stream to say there are no dirty pages, and free all auxillary allocation. Arguments: SharedCacheMap - Pointer to SharedCacheMap. Return Value: None. --*/ { PMBCB Mbcb; PBITMAP_RANGE BitmapRange; KLOCK_QUEUE_HANDLE LockHandle; ULONG DoDrain = FALSE; PLIST_ENTRY NextEntry; LIST_ENTRY BitmapRangesToFree; InitializeListHead( &BitmapRangesToFree ); KeAcquireInStackQueuedSpinLock( &SharedCacheMap->BcbSpinLock, &LockHandle ); Mbcb = SharedCacheMap->Mbcb; // // Is there an Mbcb? // if (Mbcb != NULL) { // // First deduct the dirty pages we are getting rid of. // CcAcquireMasterLockAtDpcLevel(); CcDeductDirtyPages( SharedCacheMap, Mbcb->DirtyPages ); CcReleaseMasterLockFromDpcLevel(); // // Now loop through all of the ranges. // while (!IsListEmpty(&Mbcb->BitmapRanges)) { // // Get next range and remove it from the list. // BitmapRange = (PBITMAP_RANGE)CONTAINING_RECORD( Mbcb->BitmapRanges.Flink, BITMAP_RANGE, Links ); RemoveEntryList( &BitmapRange->Links ); // // If there is a bitmap, and it is not the initial embedded one, then // delete it. // if ((BitmapRange->Bitmap != NULL) && (BitmapRange->Bitmap != (PULONG)&Mbcb->BitmapRange2)) { DoDrain = TRUE; // // Usually the bitmap is all zeros at this point, but it may not be. // if (BitmapRange->DirtyPages != 0) { RtlZeroMemory( BitmapRange->Bitmap, MBCB_BITMAP_BLOCK_SIZE ); } CcAcquireVacbLockAtDpcLevel(); CcDeallocateVacbLevel( (PVACB *)BitmapRange->Bitmap, FALSE ); CcReleaseVacbLockFromDpcLevel(); } // // If the range is not one of the initial embedded ranges, then delete it. // if ((BitmapRange < (PBITMAP_RANGE)Mbcb) && (BitmapRange > (PBITMAP_RANGE)((PCHAR)Mbcb + sizeof(MBCB)))) { InsertTailList( &BitmapRangesToFree, &BitmapRange->Links ); } } // // Zero the pointer and get out. // SharedCacheMap->Mbcb = NULL; KeReleaseInStackQueuedSpinLock( &LockHandle ); // // Free all the pool now that no locks are held. // while (!IsListEmpty(&BitmapRangesToFree)) { NextEntry = RemoveHeadList( &BitmapRangesToFree ); BitmapRange = CONTAINING_RECORD ( NextEntry, BITMAP_RANGE, Links ); ExFreePool( BitmapRange ); } // // Now delete the Mbcb. // CcDeallocateBcb( (PBCB)Mbcb ); } else { KeReleaseInStackQueuedSpinLock( &LockHandle ); } if (DoDrain) { CcDrainVacbLevelZone(); } } VOID CcSetDirtyPageThreshold ( IN PFILE_OBJECT FileObject, IN ULONG DirtyPageThreshold ) /*++ Routine Description: This routine may be called to set a dirty page threshold for this stream. The write throttling will kick in whenever the file system attempts to exceed the dirty page threshold for this file. Arguments: FileObject - Supplies file object for the stream DirtyPageThreshold - Supplies the dirty page threshold for this stream, or 0 for no threshold. Return Value: None Environment: The caller must guarantee exclusive access to the FsRtl header flags, for example, by calling this routine once during create of the structure containing the header. Then it would call the routine again when actually caching the stream. --*/ { PSHARED_CACHE_MAP SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap; if (SharedCacheMap != NULL) { SharedCacheMap->DirtyPageThreshold = DirtyPageThreshold; } // // Test the flag before setting, in case the caller is no longer properly // synchronized. // if (!FlagOn(((PFSRTL_COMMON_FCB_HEADER)(FileObject->FsContext))->Flags, FSRTL_FLAG_LIMIT_MODIFIED_PAGES)) { SetFlag(((PFSRTL_COMMON_FCB_HEADER)(FileObject->FsContext))->Flags, FSRTL_FLAG_LIMIT_MODIFIED_PAGES); } } VOID CcZeroEndOfLastPage ( IN PFILE_OBJECT FileObject ) /*++ Routine Description: This routine is only called by Mm before mapping a user view to a section. If there is an uninitialized page at the end of the file, we zero it by freeing that page. Parameters: FileObject - File object for section to be mapped Return Value: None --*/ { PSHARED_CACHE_MAP SharedCacheMap; ULONG ActivePage; ULONG PageIsDirty; KIRQL OldIrql; PVOID NeedToZero = NULL; PVACB ActiveVacb = NULL; IO_STATUS_BLOCK Iosb; BOOLEAN PurgeResult; // // See if we have an active Vacb, that we need to free. // FsRtlAcquireFileExclusive( FileObject ); CcAcquireMasterLock( &OldIrql ); SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap; if (SharedCacheMap != NULL) { // // See if there is an active vacb. // if ((SharedCacheMap->ActiveVacb != NULL) || ((NeedToZero = SharedCacheMap->NeedToZero) != NULL)) { CcIncrementOpenCount( SharedCacheMap, 'peZS' ); GetActiveVacbAtDpcLevel( SharedCacheMap, ActiveVacb, ActivePage, PageIsDirty ); } } CcReleaseMasterLock( OldIrql ); // // Remember in FsRtl header there is a user section. // If this is an advanced header then also acquire the mutex to access // this field. // if (FlagOn( ((PFSRTL_COMMON_FCB_HEADER)FileObject->FsContext)->Flags, FSRTL_FLAG_ADVANCED_HEADER )) { ExAcquireFastMutex( ((PFSRTL_ADVANCED_FCB_HEADER)FileObject->FsContext)->FastMutex ); SetFlag( ((PFSRTL_COMMON_FCB_HEADER)FileObject->FsContext)->Flags, FSRTL_FLAG_USER_MAPPED_FILE ); ExReleaseFastMutex( ((PFSRTL_ADVANCED_FCB_HEADER)FileObject->FsContext)->FastMutex ); } else { SetFlag( ((PFSRTL_COMMON_FCB_HEADER)FileObject->FsContext)->Flags, FSRTL_FLAG_USER_MAPPED_FILE ); } // // Free the active vacb now so we don't deadlock if we have to purge // if ((ActiveVacb != NULL) || (NeedToZero != NULL)) { CcFreeActiveVacb( SharedCacheMap, ActiveVacb, ActivePage, PageIsDirty ); } if (FlagOn( ((PFSRTL_COMMON_FCB_HEADER)FileObject->FsContext)->Flags2, FSRTL_FLAG2_PURGE_WHEN_MAPPED )) { if (FileObject->SectionObjectPointer->SharedCacheMap) { ASSERT( ((PSHARED_CACHE_MAP)(FileObject->SectionObjectPointer->SharedCacheMap))->VacbActiveCount == 0 ); } CcFlushCache( FileObject->SectionObjectPointer, NULL, 0, &Iosb ); PurgeResult = CcPurgeCacheSection( FileObject->SectionObjectPointer, NULL, 0, FALSE ); if (FileObject->SectionObjectPointer->SharedCacheMap) { ASSERT( ((PSHARED_CACHE_MAP)(FileObject->SectionObjectPointer->SharedCacheMap))->VacbActiveCount == 0 ); } } FsRtlReleaseFile( FileObject ); // // If the file is cached and we have a Vacb to free, we need to // use the lazy writer callback to synchronize so no one will be // extending valid data. // if ((ActiveVacb != NULL) || (NeedToZero != NULL)) { // // Serialize again to decrement the open count. // CcAcquireMasterLock( &OldIrql ); CcDecrementOpenCount( SharedCacheMap, 'peZF' ); if ((SharedCacheMap->OpenCount == 0) && !FlagOn(SharedCacheMap->Flags, WRITE_QUEUED) && (SharedCacheMap->DirtyPages == 0)) { // // Move to the dirty list. // RemoveEntryList( &SharedCacheMap->SharedCacheMapLinks ); InsertTailList( &CcDirtySharedCacheMapList.SharedCacheMapLinks, &SharedCacheMap->SharedCacheMapLinks ); // // Make sure the Lazy Writer will wake up, because we // want him to delete this SharedCacheMap. // LazyWriter.OtherWork = TRUE; if (!LazyWriter.ScanActive) { CcScheduleLazyWriteScan( FALSE ); } } CcReleaseMasterLock( OldIrql ); } } BOOLEAN CcZeroData ( IN PFILE_OBJECT FileObject, IN PLARGE_INTEGER StartOffset, IN PLARGE_INTEGER EndOffset, IN BOOLEAN Wait ) /*++ Routine Description: This routine attempts to zero the specified file data and deliver the correct I/O status. If the caller does not want to block (such as for disk I/O), then Wait should be supplied as FALSE. If Wait was supplied as FALSE and it is currently impossible to zero all of the requested data without blocking, then this routine will return FALSE. However, if the required space is immediately accessible in the cache and no blocking is required, this routine zeros the data and returns TRUE. If the caller supplies Wait as TRUE, then this routine is guaranteed to zero the data and return TRUE. If the correct space is immediately accessible in the cache, then no blocking will occur. Otherwise, the necessary work will be initiated to read and/or free cache data, and the caller will be blocked until the data can be received. File system Fsd's should typically supply Wait = TRUE if they are processing a synchronous I/O requests, or Wait = FALSE if they are processing an asynchronous request. File system threads should supply Wait = TRUE. IMPORTANT NOTE: File systems which call this routine must be prepared to handle a special form of a write call where the Mdl is already supplied. Namely, if Irp->MdlAddress is supplied, the file system must check the low order bit of Irp->MdlAddress->ByteOffset. If it is set, that means that the Irp was generated in this routine and the file system must do two things: Decrement Irp->MdlAddress->ByteOffset and Irp->UserBuffer Clear Irp->MdlAddress immediately prior to completing the request, as this routine expects to reuse the Mdl and ultimately deallocate the Mdl itself. Arguments: FileObject - pointer to the FileObject for which a range of bytes is to be zeroed. This FileObject may either be for a cached file or a noncached file. If the file is not cached, then WriteThrough must be TRUE and StartOffset and EndOffset must be on sector boundaries. StartOffset - Start offset in file to be zeroed. EndOffset - End offset in file to be zeroed. Wait - FALSE if caller may not block, TRUE otherwise (see description above) Return Value: FALSE - if Wait was supplied as FALSE and the data was not zeroed. TRUE - if the data has been zeroed. Raises: STATUS_INSUFFICIENT_RESOURCES - If a pool allocation failure occurs. This can only occur if Wait was specified as TRUE. (If Wait is specified as FALSE, and an allocation failure occurs, this routine simply returns FALSE.) --*/ { PSHARED_CACHE_MAP SharedCacheMap; PVOID CacheBuffer; LARGE_INTEGER FOffset; LARGE_INTEGER ToGo; ULONG ZeroBytes, ZeroTransfer; ULONG SectorMask; ULONG i; BOOLEAN WriteThrough; BOOLEAN AggressiveZero = FALSE; ULONG SavedState = 0; ULONG MaxZerosInCache = MAX_ZEROS_IN_CACHE; ULONG NumberOfColors = 1; PBCB Bcb = NULL; PCHAR Zeros = NULL; PMDL ZeroMdl = NULL; ULONG MaxBytesMappedInMdl = 0; BOOLEAN Result = TRUE; PPFN_NUMBER Page; ULONG SavedByteCount; LARGE_INTEGER SizeLeft; DebugTrace(+1, me, "CcZeroData\n", 0 ); WriteThrough = (BOOLEAN)(((FileObject->Flags & FO_WRITE_THROUGH) != 0) || (FileObject->PrivateCacheMap == NULL)); // // If the caller specified Wait, but the FileObject is WriteThrough, // then we need to just get out. // if (WriteThrough && !Wait) { DebugTrace(-1, me, "CcZeroData->FALSE (WriteThrough && !Wait)\n", 0 ); return FALSE; } SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap; SectorMask = IoGetRelatedDeviceObject(FileObject)->SectorSize - 1; FOffset = *StartOffset; // // Calculate how much to zero this time. // ToGo.QuadPart = EndOffset->QuadPart - FOffset.QuadPart; // // This magic number is what the fastpaths throttle on, and they will present // non-sector aligned zeroing requests. As long as we will always handle them // on the cached path, we are OK. // // If we will not make the cached path, the request must be aligned. // ASSERT( ToGo.QuadPart <= 0x2000 || ((ToGo.LowPart & SectorMask) == 0 && (FOffset.LowPart & SectorMask) == 0)); // // We will only do zeroing in the cache if the caller is using a // cached file object, and did not specify WriteThrough. We are // willing to zero some data in the cache if our total is not too // much, or there is sufficient available pages. // if (((ToGo.QuadPart <= 0x2000) || (MmAvailablePages >= ((MAX_ZEROS_IN_CACHE / PAGE_SIZE) * 4))) && !WriteThrough) { try { while (MaxZerosInCache != 0) { ULONG ReceivedLength; LARGE_INTEGER BeyondLastByte; if ( ToGo.QuadPart > (LONGLONG)MaxZerosInCache ) { // // If Wait == FALSE, then there is no point in getting started, // because we would have to start all over again zeroing with // Wait == TRUE, since we would fall out of this loop and // start synchronously writing pages to disk. // if (!Wait) { DebugTrace(-1, me, "CcZeroData -> FALSE\n", 0 ); try_return( Result = FALSE ); } } else { MaxZerosInCache = ToGo.LowPart; } // // Call local routine to Map or Access the file data, then zero the data, // then call another local routine to free the data. If we cannot map // the data because of a Wait condition, return FALSE. // // Note that this call may result in an exception, however, if it // does no Bcb is returned and this routine has absolutely no // cleanup to perform. Therefore, we do not have a try-finally // and we allow the possibility that we will simply be unwound // without notice. // if (!CcPinFileData( FileObject, &FOffset, MaxZerosInCache, FALSE, TRUE, Wait, &Bcb, &CacheBuffer, &BeyondLastByte )) { DebugTrace(-1, me, "CcZeroData -> FALSE\n", 0 ); try_return( Result = FALSE ); } // // Calculate how much data is described by Bcb starting at our desired // file offset. If it is more than we need, we will zero the whole thing // anyway. // ReceivedLength = (ULONG)(BeyondLastByte.QuadPart - FOffset.QuadPart ); // // Now attempt to allocate an Mdl to describe the mapped data. // ZeroMdl = IoAllocateMdl( CacheBuffer, ReceivedLength, FALSE, FALSE, NULL ); if (ZeroMdl == NULL) { ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES ); } // // It is necessary to probe and lock the pages, or else // the pages may not still be in memory when we do the // MmSetAddressRangeModified for the dirty Bcb. // MmDisablePageFaultClustering(&SavedState); MmProbeAndLockPages( ZeroMdl, KernelMode, IoReadAccess ); MmEnablePageFaultClustering(SavedState); SavedState = 0; // // Assume we did not get all the data we wanted, and set FOffset // to the end of the returned data, and advance buffer pointer. // FOffset = BeyondLastByte; // // Figure out how many bytes we are allowed to zero in the cache. // Note it is possible we have zeroed a little more than our maximum, // because we hit an existing Bcb that extended beyond the range. // if (MaxZerosInCache <= ReceivedLength) { MaxZerosInCache = 0; } else { MaxZerosInCache -= ReceivedLength; } // // Now set the Bcb dirty. We have to explicitly set the address // range modified here, because that work otherwise gets deferred // to the Lazy Writer. // MmSetAddressRangeModified( CacheBuffer, ReceivedLength ); CcSetDirtyPinnedData( Bcb, NULL ); // // Unmap the data now // CcUnpinFileData( Bcb, FALSE, UNPIN ); Bcb = NULL; // // Unlock and free the Mdl (we only loop back if we crossed // a 256KB boundary. // MmUnlockPages( ZeroMdl ); IoFreeMdl( ZeroMdl ); ZeroMdl = NULL; } try_exit: NOTHING; } finally { if (SavedState != 0) { MmEnablePageFaultClustering(SavedState); } // // Clean up only necessary in abnormal termination. // if (Bcb != NULL) { CcUnpinFileData( Bcb, FALSE, UNPIN ); } // // Since the last thing in the above loop which can // fail is the MmProbeAndLockPages, we only need to // free the Mdl here. // if (ZeroMdl != NULL) { IoFreeMdl( ZeroMdl ); } } // // If hit a wait condition above, return it now. // if (!Result) { return FALSE; } // // If we finished, get out nbow. // if ( FOffset.QuadPart >= EndOffset->QuadPart ) { return TRUE; } } // // We either get here because we decided above not to zero anything in // the cache directly, or else we zeroed up to our maximum and still // have some left to zero direct to the file on disk. In either case, // we will now zero from FOffset to *EndOffset, and then flush this // range in case the file is cached/mapped, and there are modified // changes in memory. // // // Round FOffset and EndOffset up to sector boundaries, since // we will be doing disk I/O, and calculate size left. // ASSERT( (FOffset.LowPart & SectorMask) == 0 ); FOffset.QuadPart += (LONGLONG)SectorMask; FOffset.LowPart &= ~SectorMask; SizeLeft.QuadPart = EndOffset->QuadPart + (LONGLONG)SectorMask; SizeLeft.LowPart &= ~SectorMask; SizeLeft.QuadPart -= FOffset.QuadPart; ASSERT( (FOffset.LowPart & SectorMask) == 0 ); ASSERT( (SizeLeft.LowPart & SectorMask) == 0 ); if (SizeLeft.QuadPart == 0) { return TRUE; } // // try-finally to guarantee cleanup. // try { // // Allocate a page to hold the zeros we will write, and // zero it. // ZeroBytes = NumberOfColors * PAGE_SIZE; if (SizeLeft.HighPart == 0 && SizeLeft.LowPart < ZeroBytes) { ZeroBytes = SizeLeft.LowPart; } Zeros = (PCHAR)ExAllocatePoolWithTag( NonPagedPoolCacheAligned, ZeroBytes, 'eZcC' ); if (Zeros != NULL) { // // Allocate and initialize an Mdl to describe the zeros // we need to transfer. Allocate to cover the maximum // size required, and we will use and reuse it in the // loop below, initialized correctly. // if (SizeLeft.HighPart == 0 && SizeLeft.LowPart < MAX_ZERO_TRANSFER) { ZeroTransfer = SizeLeft.LowPart; } else { // // See how aggressive we can afford to be. // if (InterlockedIncrement( &CcAggressiveZeroCount ) <= CcAggressiveZeroThreshold) { AggressiveZero = TRUE; ZeroTransfer = MAX_ZERO_TRANSFER; } else { InterlockedDecrement( &CcAggressiveZeroCount ); ZeroTransfer = MIN_ZERO_TRANSFER; } } // // Since the maximum zero may start at a very aggresive level, fall back // until we really have to give up. Since filter drivers, filesystems and // even storage drivers may need to map this Mdl, we have to pre-map it // into system space so that we know enough PTEs are available. We also // need to throttle our consumption of virtual addresses based on the size // of the system and the number of parallel instances of this work outstanding. // This may be a bit of overkill, but since running out of PTEs is a fatal // event for the rest of the system, try to help out while still being fast. // while (TRUE) { // // Spin down trying to get an MDL which can describe our operation. // while (TRUE) { ZeroMdl = IoAllocateMdl( Zeros, ZeroTransfer, FALSE, FALSE, NULL ); // // Throttle ourselves to what we've physically allocated. Note that // we could have started with an odd multiple of this number. If we // tried for exactly that size and failed, we're toast. // if (ZeroMdl || ZeroTransfer == ZeroBytes) { break; } Fall_Back: // // Fallback by half and round down to a sector multiple. // ZeroTransfer /= 2; ZeroTransfer &= ~SectorMask; if (ZeroTransfer < ZeroBytes) { ZeroTransfer = ZeroBytes; } ASSERT( (ZeroTransfer & SectorMask) == 0 && ZeroTransfer != 0); } if (ZeroMdl == NULL) { ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES ); } // // If we have throttled all the way down, stop and just build a // simple MDL describing our previous allocation. // if (ZeroTransfer == ZeroBytes) { MmBuildMdlForNonPagedPool( ZeroMdl ); break; } // // Now we will temporarily lock the allocated pages // only, and then replicate the page frame numbers through // the entire Mdl to keep writing the same pages of zeros. // // It would be nice if Mm exported a way for us to not have // to pull the Mdl apart and rebuild it ourselves, but this // is so bizzare a purpose as to be tolerable. // SavedByteCount = ZeroMdl->ByteCount; ZeroMdl->ByteCount = ZeroBytes; MmBuildMdlForNonPagedPool( ZeroMdl ); ZeroMdl->MdlFlags &= ~MDL_SOURCE_IS_NONPAGED_POOL; ZeroMdl->MdlFlags |= MDL_PAGES_LOCKED; ZeroMdl->MappedSystemVa = NULL; ZeroMdl->ByteCount = SavedByteCount; Page = MmGetMdlPfnArray( ZeroMdl ); for (i = NumberOfColors; i < (ADDRESS_AND_SIZE_TO_SPAN_PAGES( 0, SavedByteCount )); i++) { *(Page + i) = *(Page + i - NumberOfColors); } if (MmGetSystemAddressForMdlSafe( ZeroMdl, LowPagePriority ) == NULL) { // // Blow away this Mdl and trim for the retry. Since it didn't // get mapped, there is nothing fancy to do. // IoFreeMdl( ZeroMdl ); goto Fall_Back; } break; } // // We failed to allocate the space we wanted, so we will go to // half of a page and limp along. // } else { // // Of course, if we have a device which has large sectors, that defines // the lower limit of our attempt. // if (IoGetRelatedDeviceObject(FileObject)->SectorSize < PAGE_SIZE / 2) { ZeroBytes = PAGE_SIZE / 2; Zeros = (PCHAR)ExAllocatePoolWithTag( NonPagedPoolCacheAligned, ZeroBytes, 'eZcC' ); } // // If we cannot get even that much, then let's write a sector at a time. // if (Zeros == NULL) { ZeroBytes = IoGetRelatedDeviceObject(FileObject)->SectorSize; Zeros = (PCHAR)ExAllocatePoolWithTag( NonPagedPoolCacheAligned, ZeroBytes, 'eZcC' ); // // If we cannot get even the minimum, we have to give up. // if (Zeros == NULL) { ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES ); } } // // Allocate and initialize an Mdl to describe the zeros // we need to transfer. Allocate to cover the maximum // size required, and we will use and reuse it in the // loop below, initialized correctly. // ZeroTransfer = ZeroBytes; ZeroMdl = IoAllocateMdl( Zeros, ZeroBytes, FALSE, FALSE, NULL ); ASSERT( (ZeroTransfer & SectorMask) == 0 ); if (ZeroMdl == NULL) { ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES ); } // // Now we will lock and map the allocated pages. // MmBuildMdlForNonPagedPool( ZeroMdl ); ASSERT( ZeroMdl->MappedSystemVa == Zeros ); } // // Zero the buffer now. // RtlZeroMemory( Zeros, ZeroBytes ); // // We have a mapped and zeroed range back by an MDL to use. Note the // size we have for cleanup, since we will possibly wind this down // over the operation. // ASSERT( MmGetSystemAddressForMdl(ZeroMdl) ); MaxBytesMappedInMdl = ZeroMdl->ByteCount; // // Now loop to write buffers full of zeros through to the file // until we reach the starting Vbn for the transfer. // ASSERT( ZeroTransfer != 0 && (ZeroTransfer & SectorMask) == 0 && (SizeLeft.LowPart & SectorMask) == 0 ); while ( SizeLeft.QuadPart != 0 ) { IO_STATUS_BLOCK IoStatus; NTSTATUS Status; KEVENT Event; // // See if we really need to write that many zeros, and // trim the size back if not. // if ( (LONGLONG)ZeroTransfer > SizeLeft.QuadPart ) { ZeroTransfer = SizeLeft.LowPart; } // // (Re)initialize the kernel event to FALSE. // KeInitializeEvent( &Event, NotificationEvent, FALSE ); // // Initiate and wait for the synchronous transfer. // ZeroMdl->ByteCount = ZeroTransfer; Status = IoSynchronousPageWrite( FileObject, ZeroMdl, &FOffset, &Event, &IoStatus ); // // If pending is returned (which is a successful status), // we must wait for the request to complete. // if (Status == STATUS_PENDING) { KeWaitForSingleObject( &Event, Executive, KernelMode, FALSE, (PLARGE_INTEGER)NULL); } // // If we got an error back in Status, then the Iosb // was not written, so we will just copy the status // there, then test the final status after that. // if (!NT_SUCCESS(Status)) { ExRaiseStatus( Status ); } if (!NT_SUCCESS(IoStatus.Status)) { ExRaiseStatus( IoStatus.Status ); } // // If we succeeded, then update where we are at by how much // we wrote, and loop back to see if there is more. // FOffset.QuadPart = FOffset.QuadPart + (LONGLONG)ZeroTransfer; SizeLeft.QuadPart = SizeLeft.QuadPart - (LONGLONG)ZeroTransfer; } } finally{ // // Clean up anything from zeroing pages on a noncached // write. // if (ZeroMdl != NULL) { if ((MaxBytesMappedInMdl != 0) && !FlagOn(ZeroMdl->MdlFlags, MDL_SOURCE_IS_NONPAGED_POOL)) { ZeroMdl->ByteCount = MaxBytesMappedInMdl; MmUnmapLockedPages (ZeroMdl->MappedSystemVa, ZeroMdl); } IoFreeMdl( ZeroMdl ); } if (AggressiveZero) { InterlockedDecrement( &CcAggressiveZeroCount ); } if (Zeros != NULL) { ExFreePool( Zeros ); } DebugTrace(-1, me, "CcZeroData -> TRUE\n", 0 ); } return TRUE; } PFILE_OBJECT CcGetFileObjectFromSectionPtrs ( IN PSECTION_OBJECT_POINTERS SectionObjectPointer ) /*++ This routine may be used to retrieve a pointer to the FileObject that the Cache Manager is using for a given file from the Section Object Pointers in the nonpaged File System structure Fcb. The use of this function is intended for exceptional use unrelated to the processing of user requests, when the File System would otherwise not have a FileObject at its disposal. An example is for mount verification. Note that the File System is responsible for insuring that the File Object does not go away while in use. It is impossible for the Cache Manager to guarantee this. Arguments: SectionObjectPointer - A pointer to the Section Object Pointers structure in the nonpaged Fcb. Return Value: Pointer to the File Object, or NULL if the file is not cached or no longer cached --*/ { KIRQL OldIrql; PFILE_OBJECT FileObject = NULL; // // Serialize with Creation/Deletion of all Shared CacheMaps // CcAcquireMasterLock( &OldIrql ); if (SectionObjectPointer->SharedCacheMap != NULL) { FileObject = ((PSHARED_CACHE_MAP)SectionObjectPointer->SharedCacheMap)->FileObject; } CcReleaseMasterLock( OldIrql ); return FileObject; } PFILE_OBJECT CcGetFileObjectFromBcb ( IN PVOID Bcb ) /*++ This routine may be used to retrieve a pointer to the FileObject that the Cache Manager is using for a given file from a Bcb of that file. Note that the File System is responsible for insuring that the File Object does not go away while in use. It is impossible for the Cache Manager to guarantee this. Arguments: Bcb - A pointer to the pinned Bcb. Return Value: Pointer to the File Object, or NULL if the file is not cached or no longer cached --*/ { return ((PBCB)Bcb)->SharedCacheMap->FileObject; }