Windows2000/private/ntos/cache/fssup.c
2020-09-30 17:12:32 +02:00

2386 lines
99 KiB
C

/*++
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
--*/
#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 CcPurgeAndClearCacheSection(IN PSHARED_CACHE_MAP SharedCacheMap, IN PLARGE_INTEGER FileOffset);
#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT,CcInitializeCacheManager)
#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;
PNPAGED_LOOKASIDE_LIST Lookaside;
USHORT NumberOfItems;
PKPRCB Prcb;
PWORK_QUEUE_ITEM WorkItem;
#ifdef CCDBG_LOCK
KeInitializeSpinLock(&CcDebugTraceLock);
#endif
#if DBG
CcBcbCount = 0;
InitializeListHead(&CcBcbList);
KeInitializeSpinLock(&CcBcbSpinLock);
#endif
// Figure out the timeout clock tick for the lazy writer.
CcIdleDelayTick = LAZY_WRITER_IDLE_DELAY / KeQueryTimeIncrement();
// Initialize shared cache map list structures
KeInitializeSpinLock(&CcMasterSpinLock);
InitializeListHead(&CcCleanSharedCacheMapList);
InitializeListHead(&CcDirtySharedCacheMapList.SharedCacheMapLinks);
CcDirtySharedCacheMapList.Flags = IS_CURSOR;
InsertTailList(&CcDirtySharedCacheMapList.SharedCacheMapLinks, &CcLazyWriterCursor.SharedCacheMapLinks);
CcLazyWriterCursor.Flags = IS_CURSOR;
// Initialize worker thread structures
KeInitializeSpinLock(&CcWorkQueueSpinlock);
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;
#if 0
// Use more memory if we are a large server.
if ((MmLargeSystemCache != 0) && (CcDirtyPageThreshold < (MmNumberOfPhysicalPages - (0xE00000 / PAGE_SIZE)))) {
CcDirtyPageThreshold = MmNumberOfPhysicalPages - (0xE00000 / PAGE_SIZE);
}
#endif
break;
default:
CcNumberWorkerThreads = 1;
CcDirtyPageThreshold = MmNumberOfPhysicalPages / 8;
}
// CcDirtyPageThreshold = (2*1024*1024)/PAGE_SIZE;
if (MmSystemCacheWs.MaximumWorkingSetSize > ((4 * 1024 * 1024) / PAGE_SIZE)) {
CcDirtyPageThreshold = 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));
KeInitializeSpinLock(&CcWorkQueueSpinlock);
InitializeListHead(&LazyWriter.WorkQueue);
// Store process address
LazyWriter.OurProcess = PsGetCurrentProcess();
// 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;
}
ExInitializeNPagedLookasideList(&CcTwilightLookasideList, NULL, NULL, 0, sizeof(WORK_QUEUE_ENTRY), 'kWcC', NumberOfItems);
// 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 = (PNPAGED_LOOKASIDE_LIST)ExAllocatePoolWithTag(NonPagedPool, sizeof(NPAGED_LOOKASIDE_LIST), 'KWcC');
if (Lookaside != NULL) {
ExInitializeNPagedLookasideList(Lookaside, NULL, NULL, 0, sizeof(WORK_QUEUE_ENTRY), 'KWcC', NumberOfItems);
} else {
Lookaside = &CcTwilightLookasideList;
}
Prcb->PPLookasideList[LookasideTwilightList].P = Lookaside;
}
// Now initialize the Bcb zone
{
PVOID InitialSegment;
ULONG InitialSegmentSize;
ULONG RoundedBcbSize = (sizeof(BCB) + 7) & ~7;
switch (CcCapturedSystemSize) {
// ~1.5k bytes
case MmSmallSystem:
InitialSegmentSize = sizeof(ZONE_SEGMENT_HEADER) + RoundedBcbSize * 8;
break;
// 1 Page
case MmMediumSystem:
InitialSegmentSize = PAGE_SIZE;
break;
// 3 Pages
case MmLargeSystem:
InitialSegmentSize = 3 * PAGE_SIZE;
break;
}
// Allocate the initial allocation for the zone. If we cannot get it,
// something must really be wrong, so we will just bugcheck.
if ((InitialSegment = ExAllocatePoolWithTag(NonPagedPool, InitialSegmentSize, 'zBcC')) == NULL) {
CcBugCheck(0, 0, 0);
}
if (!NT_SUCCESS(ExInitializeZone(&LazyWriter.BcbZone, RoundedBcbSize, InitialSegment, InitialSegmentSize))) {
CcBugCheck(0, 0, 0);
}
}
// 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 open 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 = NULL;
PVOID CacheMapToFree = NULL;
CC_FILE_SIZES LocalSizes;
BOOLEAN WeSetBeingCreated = FALSE;
BOOLEAN SharedListOwned = FALSE;
BOOLEAN MustUninitialize = FALSE;
BOOLEAN WeCreated = FALSE;
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. Allocation failures look like we
// never decided to allocate one here!
if (FileObject->SectionObjectPointer->SharedCacheMap == NULL) {
CacheMapToFree = ExAllocatePoolWithTag(NonPagedPool, sizeof(SHARED_CACHE_MAP), 'cScC');
}
// Serialize Creation/Deletion of all Shared CacheMaps
CcAcquireMasterLock(&OldIrql);
SharedListOwned = TRUE;
// Insure release of our global resource
try {
// Check for second initialization of same file object
if (FileObject->PrivateCacheMap != NULL) {
DebugTrace(0, 0, "CacheMap already initialized\n", 0);
try_return(NOTHING);
}
// 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) {
// After successfully creating the section, allocate the SharedCacheMap.
WeCreated = TRUE;
if (CacheMapToFree == NULL) {
CacheMapToFree = (PSHARED_CACHE_MAP)ExAllocatePoolWithTag(NonPagedPool, sizeof(SHARED_CACHE_MAP), 'cScC');
}
SharedCacheMap = CacheMapToFree;
CacheMapToFree = NULL;
if (SharedCacheMap == NULL) {
DebugTrace(0, 0, "Failed to allocate SharedCacheMap\n", 0);
CcReleaseMasterLock(OldIrql);
SharedListOwned = FALSE;
ExRaiseStatus(STATUS_INSUFFICIENT_RESOURCES);
}
// Zero the SharedCacheMap and fill in the nonzero portions later.
RtlZeroMemory(SharedCacheMap, sizeof(SHARED_CACHE_MAP));
#if DANLO
SharedCacheMap->OpenCountLog.Size = sizeof(SharedCacheMap->OpenCountLog.Log) / sizeof(CC_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);
if (PinAccess) {
SetFlag(SharedCacheMap->Flags, PIN_ACCESS);
}
// Initialize our allocation hint for the local event.
SharedCacheMap->LocalEvent = &SharedCacheMap->Event;
// 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);
// Insert the new Shared Cache Map in the global list
InsertTailList(&CcCleanSharedCacheMapList, &SharedCacheMap->SharedCacheMapLinks);
// 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');
MustUninitialize = TRUE;
// We still want anyone else to wait.
SetFlag(SharedCacheMap->Flags, BEING_CREATED);
WeSetBeingCreated = TRUE;
// 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;
// We have to test this, because the section may only be unmapped.
if (SharedCacheMap->Section == NULL) {
LARGE_INTEGER LargeZero = {0,0};
// 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.
// Naturally we want to pass in the handle.
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 = %08lx\n", SharedCacheMap->Section);
if (!NT_SUCCESS(SharedCacheMap->Status)) {
DebugTrace(0, 0, "Error from MmCreateSection = %08lx\n", SharedCacheMap->Status);
SharedCacheMap->Section = NULL;
ExRaiseStatus(FsRtlNormalizeNtstatus(SharedCacheMap->Status, STATUS_UNEXPECTED_MM_CREATE_ERR));
}
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)) {
BOOLEAN Disabled;
Disabled = MmDisableModifiedWriteOfSection(FileObject->SectionObjectPointer);
CcAcquireMasterLock(&OldIrql);
SetFlag(SharedCacheMap->Flags, MODIFIED_WRITE_DISABLED);
CcReleaseMasterLock(OldIrql);
//**** ASSERT( Disabled );
}
// Create the Vacb array.
CcCreateVacbArray(SharedCacheMap, LocalSizes.AllocationSize);
}
// 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) {
NTSTATUS Status;
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);
ExRaiseStatus(FsRtlNormalizeNtstatus(Status, STATUS_UNEXPECTED_MM_EXTEND_ERR));
}
}
// Extend the Vacb array.
CcExtendVacbArray(SharedCacheMap, LocalSizes.AllocationSize);
}
// Now show that we are all done and resume any waiters.
CcAcquireMasterLock(&OldIrql);
ClearFlag(SharedCacheMap->Flags, BEING_CREATED);
WeSetBeingCreated = FALSE;
if (SharedCacheMap->CreateEvent != NULL) {
KeSetEvent(SharedCacheMap->CreateEvent, 0, FALSE);
}
CcReleaseMasterLock(OldIrql);
}
// 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) {
// If the local event is not being used then we can grab it.
// (Should be quite rare that it is in use.)
SharedCacheMap->CreateEvent = InterlockedExchangePointer(&SharedCacheMap->LocalEvent, NULL);
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;
ExRaiseStatus(STATUS_INSUFFICIENT_RESOURCES);
}
KeInitializeEvent(SharedCacheMap->CreateEvent, NotificationEvent, FALSE);
}
// Increment the OpenCount on the CacheMap.
CcIncrementOpenCount(SharedCacheMap, 'ecnI');
MustUninitialize = TRUE;
// Release global resource before waiting
CcReleaseMasterLock(OldIrql);
SharedListOwned = FALSE;
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)) {
ExRaiseStatus(FsRtlNormalizeNtstatus(SharedCacheMap->Status, STATUS_UNEXPECTED_MM_CREATE_ERR));
}
} else {
PCACHE_UNINITIALIZE_EVENT CUEvent, EventNext;
// Increment the OpenCount on the CacheMap.
CcIncrementOpenCount(SharedCacheMap, 'esnI');
MustUninitialize = TRUE;
// 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;
}
}
{
PPRIVATE_CACHE_MAP PrivateCacheMap;
// 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 (CacheMapToFree != NULL) {
ExFreePool(CacheMapToFree);
CacheMapToFree = NULL;
}
if (PrivateCacheMap->NodeTypeCode != 0) {
CacheMapToFree = ExAllocatePoolWithTag(NonPagedPool, sizeof(PRIVATE_CACHE_MAP), 'cPcC');
}
// Insert the new PrivateCacheMap in the list off the SharedCacheMap.
CcAcquireMasterLock(&OldIrql);
SharedListOwned = TRUE;
// 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) {
CacheMapToFree =
(PPRIVATE_CACHE_MAP)ExAllocatePoolWithTag(NonPagedPool, sizeof(PRIVATE_CACHE_MAP), 'cPcC');
}
PrivateCacheMap = CacheMapToFree;
CacheMapToFree = NULL;
}
if (PrivateCacheMap == NULL) {
DebugTrace(0, 0, "Failed to allocate PrivateCacheMap\n", 0);
CcReleaseMasterLock(OldIrql);
SharedListOwned = FALSE;
ExRaiseStatus(STATUS_INSUFFICIENT_RESOURCES);
}
RtlZeroMemory(PrivateCacheMap, sizeof(PRIVATE_CACHE_MAP));
PrivateCacheMap->NodeTypeCode = CACHE_NTC_PRIVATE_CACHE_MAP;
PrivateCacheMap->NodeByteSize = sizeof(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;
try_exit: NOTHING;
} finally{
// 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)) {
// 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();
}
CcReleaseMasterLock(OldIrql);
}
} 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);
}
}
DebugTrace(-1, me, "CcInitializeCacheMap -> VOID\n", 0);
}
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;
BOOLEAN SharedListAcquired = 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);
// Insure release of resources
try {
// Serialize Creation/Deletion of all Shared CacheMaps
CcAcquireMasterLock(&OldIrql);
SharedListAcquired = TRUE;
// 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 this is the last guy, I can drop the file size down now.
if (IsListEmpty(&SharedCacheMap->PrivateList)) {
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;
}
}
try_return(SectionClosed = FALSE);
}
// 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();
}
// 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);
try_return(SectionClosed = FALSE);
}
// 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);
SharedListAcquired = FALSE;
try_return(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);
SharedListAcquired = FALSE;
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);
}
// 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);
}
}
try_exit: NOTHING;
} finally{
// Release global resources
if (SharedListAcquired) {
CcReleaseMasterLock(OldIrql);
}
// 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 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;
PLIST_ENTRY NextEntry;
PFILE_OBJECT FileObject;
PVACB ActiveVacb;
ULONG ActivePage;
ULONG PageIsDirty;
KIRQL OldIrql;
PMBCB Mbcb;
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 wrip 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, asserting that none of them can be pinned now if we have gotten this far!
NextEntry = SharedCacheMap->BcbList.Flink;
while (NextEntry != &SharedCacheMap->BcbList) {
PBCB Bcb;
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.
// I really wonder how often we have Bcbs at teardown. This is a lot of work that could be avoided otherwise.
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);
}
// Debug routines used to remove Bcbs from the global list
#if LIST_DBG
{
KIRQL OldIrql;
ExAcquireSpinLock(&CcBcbSpinLock, &OldIrql);
if (Bcb->CcBcbLinks.Flink != NULL) {
RemoveEntryList(&Bcb->CcBcbLinks);
CcBcbCount -= 1;
}
ExReleaseSpinLock(&CcBcbSpinLock, OldIrql);
}
#endif
// If the Bcb is dirty, we have to synchronize with the Lazy Writer and reduce the total number of dirty.
CcAcquireMasterLock(&ListIrql);
if (Bcb->Dirty) {
SharedCacheMap->DirtyPages -= Bcb->ByteLength >> PAGE_SHIFT;
CcTotalDirtyPages -= Bcb->ByteLength >> PAGE_SHIFT;
}
CcReleaseMasterLock(ListIrql);
CcDeallocateBcb(Bcb);
}
}
// 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);
// Use try-finally to make sure we get the open count decremented.
try {
// 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)) {
DebugTrace(0, 0, "Error from MmExtendSection, Status = %08lx\n", Status);
ExRaiseStatus(FsRtlNormalizeNtstatus(Status, STATUS_UNEXPECTED_MM_EXTEND_ERR));
}
// Extend the Vacb array.
CcExtendVacbArray(SharedCacheMap, NewSectionSize);
} finally{
// 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();
}
}
CcReleaseMasterLock(OldIrql);
}
// It is now very unlikely that we have any more work to do, but just
// in case we reacquire the spinlock and check again if we are cached.
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) {
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');
try {
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.) *** For now play it safe by
// only growing here, which is the historical problem at hand, as with
// compressed and uncompressed stream caches.
if (NewValidDataLength.QuadPart > SharedCacheMap->ValidDataGoal.QuadPart) {
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 if
// there is one, so that we do not keep dirty pages around forever.
if ((NewFileSize.QuadPart == 0) && (SharedCacheMap->Mbcb != NULL)) {
CcDeleteMbcb(SharedCacheMap);
}
CcPurgeAndClearCacheSection(SharedCacheMap, &NewFileSize);
// Serialize again to decrement the open count.
CcAcquireMasterLock(&OldIrql);
}
} finally{
// We should only be raising without owning the spinlock.
if (AbnormalTermination()) {
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();
}
}
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;
// 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);
try {
// 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.
} except(EXCEPTION_EXECUTE_HANDLER)
{
NOTHING;
}
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) {
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);
}
// Use try-finally to insure cleanup of the Open Count and Vacb on the
// way out.
try {
// 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);
}
} finally{
// 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();
}
}
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;
KIRQL OldIrql;
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;
KIRQL OldIrql;
ULONG DoDrain = FALSE;
ExAcquireSpinLock(&SharedCacheMap->BcbSpinLock, &OldIrql);
Mbcb = SharedCacheMap->Mbcb;
// Is there an Mbcb?
if (Mbcb != NULL) {
// First deduct the dirty pages we are getting rid of.
CcAcquireMasterLockAtDpcLevel();
CcTotalDirtyPages -= Mbcb->DirtyPages;
SharedCacheMap->DirtyPages -= 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)))) {
ExFreePool(BitmapRange);
}
}
// Zero the pointer and get out.
SharedCacheMap->Mbcb = NULL;
ExReleaseSpinLock(&SharedCacheMap->BcbSpinLock, OldIrql);
// Now delete the Mbcb.
CcDeallocateBcb((PBCB)Mbcb);
} else {
ExReleaseSpinLock(&SharedCacheMap->BcbSpinLock, OldIrql);
}
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.
--*/
{
KIRQL OldIrql;
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 is 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();
}
}
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 avaliable. 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 < (COMPUTE_PAGES_SPANNED(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);
}
#ifdef MIPS
#ifdef MIPS_PREFILL
RtlFillMemory(Zeros, ZeroBytes, 0xDD);
KeSweepDcache(TRUE);
#endif
#endif
// 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;
}