mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3343 lines
101 KiB
3343 lines
101 KiB
/*++
|
|
|
|
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
|
|
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;
|
|
USHORT NumberOfItems;
|
|
PWORK_QUEUE_ITEM WorkItem;
|
|
|
|
#ifdef CCDBG_LOCK
|
|
KeInitializeSpinLock( &CcDebugTraceLock );
|
|
#endif
|
|
|
|
#if DBG
|
|
CcBcbCount = 0;
|
|
InitializeListHead( &CcBcbList );
|
|
KeInitializeSpinLock( &CcBcbSpinLock );
|
|
#endif
|
|
|
|
//
|
|
// 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 );
|
|
|
|
//
|
|
// 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;
|
|
break;
|
|
|
|
case MmMediumSystem:
|
|
CcNumberWorkerThreads = ExCriticalWorkerThreads - 1;
|
|
CcDirtyPageThreshold = MmNumberOfPhysicalPages / 4;
|
|
break;
|
|
|
|
case MmLargeSystem:
|
|
CcNumberWorkerThreads = ExCriticalWorkerThreads - 2;
|
|
CcDirtyPageThreshold = MmNumberOfPhysicalPages / 4 +
|
|
MmNumberOfPhysicalPages / 8;
|
|
|
|
#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;
|
|
}
|
|
|
|
//
|
|
// Now allocate and initialize the above number of worker thread
|
|
// items.
|
|
//
|
|
|
|
for (i = 0; i < CcNumberWorkerThreads; i++) {
|
|
|
|
WorkItem = ExAllocatePool( NonPagedPool, sizeof(WORK_QUEUE_ITEM) );
|
|
|
|
//
|
|
// 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 );
|
|
|
|
//
|
|
// Now initialize the Bcb zone
|
|
//
|
|
|
|
{
|
|
PVOID InitialSegment;
|
|
ULONG InitialSegmentSize;
|
|
ULONG RoundedBcbSize = (sizeof(BCB) + 7) & ~7;
|
|
ULONG NumberOfItems;
|
|
|
|
|
|
switch ( CcCapturedSystemSize ) {
|
|
|
|
//
|
|
// ~1.5k bytes
|
|
//
|
|
|
|
case MmSmallSystem :
|
|
NumberOfItems = 8;
|
|
break;
|
|
|
|
//
|
|
// ~4k bytes
|
|
//
|
|
|
|
case MmMediumSystem :
|
|
NumberOfItems = 20;
|
|
break;
|
|
|
|
//
|
|
// ~12k bytes
|
|
//
|
|
|
|
case MmLargeSystem :
|
|
NumberOfItems = 64;
|
|
break;
|
|
}
|
|
|
|
InitialSegmentSize = sizeof(ZONE_SEGMENT_HEADER) + RoundedBcbSize * NumberOfItems;
|
|
|
|
//
|
|
// Allocate the initial allocation for the zone. If we cannot get it,
|
|
// something must really be wrong, so we will just bugcheck.
|
|
//
|
|
|
|
if ((InitialSegment = ExAllocatePool( NonPagedPool,
|
|
InitialSegmentSize)) == 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 = ExAllocatePool( NonPagedPool, sizeof(SHARED_CACHE_MAP) );
|
|
}
|
|
|
|
//
|
|
// Serialize Creation/Deletion of all Shared CacheMaps
|
|
//
|
|
|
|
ExAcquireSpinLock( &CcMasterSpinLock, &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)ExAllocatePool( NonPagedPool,
|
|
sizeof(SHARED_CACHE_MAP) );
|
|
}
|
|
|
|
SharedCacheMap = CacheMapToFree;
|
|
CacheMapToFree = NULL;
|
|
|
|
if (SharedCacheMap == NULL) {
|
|
|
|
DebugTrace( 0, 0, "Failed to allocate SharedCacheMap\n", 0 );
|
|
|
|
ExReleaseSpinLock( &CcMasterSpinLock, OldIrql );
|
|
SharedListOwned = FALSE;
|
|
|
|
ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
|
|
}
|
|
|
|
//
|
|
// Zero the SharedCacheMap and fill in the nonzero portions later.
|
|
//
|
|
|
|
RtlZeroMemory( SharedCacheMap, sizeof(SHARED_CACHE_MAP) );
|
|
|
|
//
|
|
// Now initialize the Shared Cache Map.
|
|
//
|
|
|
|
SharedCacheMap->NodeTypeCode = CACHE_NTC_SHARED_CACHE_MAP;
|
|
SharedCacheMap->NodeByteSize = sizeof(SHARED_CACHE_MAP);
|
|
SharedCacheMap->FileSize = LocalSizes.FileSize;
|
|
SharedCacheMap->ValidDataLength =
|
|
SharedCacheMap->ValidDataGoal = LocalSizes.ValidDataLength;
|
|
SharedCacheMap->FileObject = FileObject;
|
|
// SharedCacheMap->Section set below
|
|
|
|
//
|
|
// Initialize the ActiveVacbSpinLock.
|
|
//
|
|
|
|
KeInitializeSpinLock( &SharedCacheMap->ActiveVacbSpinLock );
|
|
|
|
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 the pointer to the uninitialize event chain.
|
|
//
|
|
|
|
SharedCacheMap->UninitializeEvent = NULL;
|
|
|
|
//
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
|
|
SharedCacheMap->OpenCount += 1;
|
|
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
|
|
//
|
|
|
|
ExReleaseSpinLock( &CcMasterSpinLock, 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 );
|
|
ExAcquireFastLock( &CcMasterSpinLock, &OldIrql );
|
|
SetFlag(SharedCacheMap->Flags, MODIFIED_WRITE_DISABLED);
|
|
ExReleaseFastLock( &CcMasterSpinLock, 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.
|
|
//
|
|
|
|
ExAcquireSpinLock( &CcMasterSpinLock, &OldIrql );
|
|
ClearFlag(SharedCacheMap->Flags, BEING_CREATED);
|
|
WeSetBeingCreated = FALSE;
|
|
if (SharedCacheMap->CreateEvent != NULL) {
|
|
KeSetEvent( SharedCacheMap->CreateEvent, 0, FALSE );
|
|
}
|
|
ExReleaseSpinLock( &CcMasterSpinLock, 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) {
|
|
|
|
//
|
|
// We create for the loacl event with the WaitOnActiveCount
|
|
// event, and we synchronize the claiming of that event with
|
|
// CcVacbSpinLock.
|
|
//
|
|
|
|
ExAcquireSpinLockAtDpcLevel( &CcVacbSpinLock );
|
|
|
|
//
|
|
// If the local even is not being used as a create event,
|
|
// then we can use it. (Should be quite rare that it is in use.)
|
|
//
|
|
|
|
if (SharedCacheMap->WaitOnActiveCount == NULL) {
|
|
|
|
SharedCacheMap->CreateEvent = &SharedCacheMap->Event;
|
|
|
|
} else {
|
|
|
|
SharedCacheMap->CreateEvent = (PKEVENT)ExAllocatePool( NonPagedPool, sizeof(KEVENT) );
|
|
}
|
|
|
|
ExReleaseSpinLockFromDpcLevel( &CcVacbSpinLock );
|
|
|
|
if (SharedCacheMap->CreateEvent == NULL) {
|
|
DebugTrace( 0, 0, "Failed to allocate CreateEvent\n", 0 );
|
|
|
|
ExReleaseSpinLock( &CcMasterSpinLock, OldIrql );
|
|
SharedListOwned = FALSE;
|
|
|
|
ExRaiseStatus(STATUS_INSUFFICIENT_RESOURCES);
|
|
}
|
|
|
|
KeInitializeEvent( SharedCacheMap->CreateEvent,
|
|
NotificationEvent,
|
|
FALSE );
|
|
}
|
|
|
|
//
|
|
// Increment the OpenCount on the CacheMap.
|
|
//
|
|
|
|
SharedCacheMap->OpenCount += 1;
|
|
MustUninitialize = TRUE;
|
|
|
|
//
|
|
// Release global resource before waiting
|
|
//
|
|
|
|
ExReleaseSpinLock( &CcMasterSpinLock, 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;
|
|
|
|
//
|
|
// Increment the OpenCount on the CacheMap.
|
|
//
|
|
|
|
SharedCacheMap->OpenCount += 1;
|
|
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) {
|
|
PCACHE_UNINITIALIZE_EVENT EventNext = CUEvent->Next;
|
|
KeSetEvent(&CUEvent->Event, 0, FALSE);
|
|
CUEvent = EventNext;
|
|
}
|
|
|
|
SharedCacheMap->UninitializeEvent = NULL;
|
|
|
|
//
|
|
// Release global resource
|
|
//
|
|
|
|
ExReleaseSpinLock( &CcMasterSpinLock, 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 = ExAllocatePool( NonPagedPool, sizeof(PRIVATE_CACHE_MAP) );
|
|
}
|
|
|
|
//
|
|
// Insert the new PrivateCacheMap in the list off the SharedCacheMap.
|
|
//
|
|
|
|
ExAcquireSpinLock( &CcMasterSpinLock, &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)ExAllocatePool( NonPagedPool,
|
|
sizeof(PRIVATE_CACHE_MAP) );
|
|
}
|
|
PrivateCacheMap = CacheMapToFree;
|
|
CacheMapToFree = NULL;
|
|
}
|
|
|
|
if (PrivateCacheMap == NULL) {
|
|
|
|
DebugTrace( 0, 0, "Failed to allocate PrivateCacheMap\n", 0 );
|
|
|
|
ExReleaseSpinLock( &CcMasterSpinLock, 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;
|
|
}
|
|
}
|
|
|
|
MustUninitialize = FALSE;
|
|
try_exit: NOTHING;
|
|
}
|
|
finally {
|
|
|
|
//
|
|
// See if we got an error and must uninitialize the SharedCacheMap
|
|
//
|
|
|
|
if (MustUninitialize) {
|
|
|
|
if (!SharedListOwned) {
|
|
ExAcquireSpinLock( &CcMasterSpinLock, &OldIrql );
|
|
}
|
|
if (WeSetBeingCreated) {
|
|
if (SharedCacheMap->CreateEvent != NULL) {
|
|
KeSetEvent( SharedCacheMap->CreateEvent, 0, FALSE );
|
|
}
|
|
ClearFlag(SharedCacheMap->Flags, BEING_CREATED);
|
|
}
|
|
|
|
//
|
|
// Now release our open count.
|
|
//
|
|
|
|
SharedCacheMap->OpenCount -= 1;
|
|
|
|
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();
|
|
}
|
|
|
|
ExReleaseSpinLock( &CcMasterSpinLock, OldIrql );
|
|
}
|
|
|
|
} else {
|
|
|
|
ExReleaseSpinLock( &CcMasterSpinLock, 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) {
|
|
|
|
ExAcquireSpinLock( &CcMasterSpinLock, &OldIrql );
|
|
SharedListOwned = TRUE;
|
|
}
|
|
|
|
if ((SharedCacheMap->DirtyPages == 0) &&
|
|
(SharedCacheMap->OpenCount != 0)) {
|
|
|
|
RemoveEntryList( &SharedCacheMap->SharedCacheMapLinks );
|
|
InsertTailList( &CcCleanSharedCacheMapList,
|
|
&SharedCacheMap->SharedCacheMapLinks );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Release global resource
|
|
//
|
|
|
|
if (SharedListOwned) {
|
|
ExReleaseSpinLock( &CcMasterSpinLock, OldIrql );
|
|
}
|
|
|
|
if (CacheMapToFree != NULL) {
|
|
ExFreePool(CacheMapToFree);
|
|
}
|
|
|
|
}
|
|
|
|
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;
|
|
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
|
|
//
|
|
|
|
ExAcquireSpinLock( &CcMasterSpinLock, &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) {
|
|
|
|
SharedCacheMap->OpenCount -= 1;
|
|
|
|
//
|
|
// 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);
|
|
}
|
|
|
|
//
|
|
// 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 );
|
|
}
|
|
|
|
//
|
|
// 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)) {
|
|
|
|
ExReleaseSpinLock( &CcMasterSpinLock, 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) {
|
|
ExReleaseSpinLock( &CcMasterSpinLock, 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;
|
|
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 );
|
|
|
|
ExReleaseSpinLock( &CcMasterSpinLock, ListIrql );
|
|
|
|
CcFreeActiveVacb( SharedCacheMap, ActiveVacb, ActivePage, PageIsDirty );
|
|
|
|
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.
|
|
//
|
|
|
|
ExAcquireSpinLock( &CcMasterSpinLock, &ListIrql );
|
|
RemoveEntryList( &SharedCacheMap->SharedCacheMapLinks );
|
|
}
|
|
|
|
//
|
|
// If there are Bcbs, then empty the list, asserting that none of them
|
|
// can be pinned now if we have gotten this far!
|
|
//
|
|
|
|
while (!IsListEmpty( &SharedCacheMap->BcbList )) {
|
|
|
|
PBCB Bcb;
|
|
|
|
Bcb = (PBCB)CONTAINING_RECORD( SharedCacheMap->BcbList.Flink,
|
|
BCB,
|
|
BcbLinks );
|
|
|
|
RemoveEntryList( &Bcb->BcbLinks );
|
|
|
|
//
|
|
// Skip over the pendaflex entries
|
|
//
|
|
|
|
if (Bcb->NodeTypeCode == CACHE_NTC_BCB) {
|
|
|
|
ASSERT( Bcb->PinCount == 0 );
|
|
|
|
//
|
|
// If the Bcb is dirty, we have to synchronize with the Lazy Writer
|
|
// and reduce the total number of dirty.
|
|
//
|
|
|
|
if (Bcb->Dirty) {
|
|
|
|
CcTotalDirtyPages -= Bcb->ByteLength >> PAGE_SHIFT;
|
|
}
|
|
|
|
//
|
|
// 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
|
|
|
|
CcDeallocateBcb( Bcb );
|
|
}
|
|
}
|
|
ExReleaseSpinLock( &CcMasterSpinLock, ListIrql );
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
|
|
ExAcquireSpinLock( &CcMasterSpinLock, &OldIrql );
|
|
Mbcb = SharedCacheMap->Mbcb;
|
|
if (Mbcb != NULL) {
|
|
|
|
if (Mbcb->DirtyPages != 0) {
|
|
|
|
CcTotalDirtyPages -= Mbcb->DirtyPages;
|
|
}
|
|
|
|
CcDeallocateBcb( (PBCB)Mbcb );
|
|
}
|
|
|
|
ExReleaseSpinLock( &CcMasterSpinLock, OldIrql );
|
|
|
|
//
|
|
// 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 = SharedCacheMap->UninitializeEvent;
|
|
|
|
while (CUEvent != NULL) {
|
|
PCACHE_UNINITIALIZE_EVENT EventNext = CUEvent->Next;
|
|
|
|
KeSetEvent(&CUEvent->Event, 0, FALSE);
|
|
|
|
CUEvent = EventNext;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now delete the Vacb vector.
|
|
//
|
|
|
|
if ((SharedCacheMap->Vacbs != &SharedCacheMap->InitialVacbs[0])
|
|
|
|
&&
|
|
|
|
(SharedCacheMap->Vacbs != NULL)) {
|
|
|
|
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;
|
|
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.
|
|
//
|
|
|
|
NewFileSize = FileSizes->FileSize;
|
|
NewSectionSize = FileSizes->AllocationSize;
|
|
|
|
//
|
|
// Serialize Creation/Deletion of all Shared CacheMaps
|
|
//
|
|
|
|
ExAcquireSpinLock( &CcMasterSpinLock, &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)) {
|
|
|
|
ExReleaseSpinLock( &CcMasterSpinLock, 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.
|
|
//
|
|
|
|
SharedCacheMap->OpenCount += 1;
|
|
ExReleaseSpinLock( &CcMasterSpinLock, 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.
|
|
//
|
|
|
|
ExAcquireSpinLock( &CcMasterSpinLock, &OldIrql );
|
|
|
|
SharedCacheMap->OpenCount -= 1;
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
ExReleaseSpinLock( &CcMasterSpinLock, 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.
|
|
//
|
|
|
|
ExAcquireSpinLock( &CcMasterSpinLock, &OldIrql );
|
|
|
|
//
|
|
// Get pointer to SharedCacheMap via File Object.
|
|
//
|
|
|
|
SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap;
|
|
|
|
//
|
|
// If the file is not cached, just get out.
|
|
//
|
|
|
|
if (SharedCacheMap == NULL) {
|
|
|
|
ExReleaseSpinLock( &CcMasterSpinLock, 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.
|
|
//
|
|
|
|
SharedCacheMap->OpenCount += 1;
|
|
|
|
try {
|
|
|
|
if ( ( NewFileSize.QuadPart < SharedCacheMap->ValidDataGoal.QuadPart ) ||
|
|
( NewFileSize.QuadPart < SharedCacheMap->FileSize.QuadPart )) {
|
|
|
|
GetActiveVacbAtDpcLevel( SharedCacheMap, ActiveVacb, ActivePage, PageIsDirty );
|
|
|
|
if ((ActiveVacb != NULL) || (SharedCacheMap->NeedToZero != NULL)) {
|
|
|
|
ExReleaseSpinLock( &CcMasterSpinLock, OldIrql );
|
|
|
|
CcFreeActiveVacb( SharedCacheMap, ActiveVacb, ActivePage, PageIsDirty );
|
|
|
|
//
|
|
// Serialize again to reduce ValidDataLength. It cannot change
|
|
// because the caller must have the file exclusive.
|
|
//
|
|
|
|
ExAcquireSpinLock( &CcMasterSpinLock, &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;
|
|
}
|
|
|
|
//
|
|
// When truncating Valid Data Goal, remember that it must always
|
|
// stay rounded to the top of the page, to protect writes of user-mapped
|
|
// files. ** no longer rounding **
|
|
//
|
|
|
|
if ( NewFileSize.QuadPart < SharedCacheMap->ValidDataGoal.QuadPart ) {
|
|
|
|
SharedCacheMap->ValidDataGoal = NewFileSize;
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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)) {
|
|
|
|
//
|
|
// 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)) {
|
|
|
|
PMBCB Mbcb = SharedCacheMap->Mbcb;
|
|
|
|
CcTotalDirtyPages -= Mbcb->DirtyPages;
|
|
SharedCacheMap->DirtyPages -= Mbcb->DirtyPages;
|
|
Mbcb->DirtyPages = 0;
|
|
Mbcb->FirstDirtyPage = MAXULONG;
|
|
Mbcb->LastDirtyPage = 0;
|
|
Mbcb->ResumeWritePage = 0;
|
|
Mbcb->PagesToWrite = 0;
|
|
RtlZeroMemory( Mbcb->Bitmap.Buffer, Mbcb->Bitmap.SizeOfBitMap / 8 );
|
|
}
|
|
|
|
//
|
|
// Increment open count to make sure the SharedCacheMap stays around,
|
|
// then release the spinlock so that we can call Mm.
|
|
//
|
|
|
|
ExReleaseSpinLock( &CcMasterSpinLock, OldIrql );
|
|
|
|
CcPurgeAndClearCacheSection( SharedCacheMap, &NewFileSize );
|
|
|
|
//
|
|
// Serialize again to decrement the open count.
|
|
//
|
|
|
|
ExAcquireSpinLock( &CcMasterSpinLock, &OldIrql );
|
|
}
|
|
|
|
} finally {
|
|
|
|
//
|
|
// We should only be raising without owning the spinlock.
|
|
//
|
|
|
|
if (AbnormalTermination()) {
|
|
|
|
ExAcquireSpinLock( &CcMasterSpinLock, &OldIrql );
|
|
}
|
|
|
|
SharedCacheMap->OpenCount -= 1;
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
ExReleaseSpinLock( &CcMasterSpinLock, 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
|
|
//
|
|
|
|
ExAcquireFastLock( &CcMasterSpinLock, &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) {
|
|
|
|
SharedCacheMap->OpenCount += 1;
|
|
|
|
//
|
|
// If there is an active Vacb, then nuke it now (before waiting!).
|
|
//
|
|
|
|
GetActiveVacbAtDpcLevel( SharedCacheMap, Vacb, ActivePage, PageIsDirty );
|
|
}
|
|
|
|
ExReleaseFastLock( &CcMasterSpinLock, 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 )) {
|
|
|
|
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.
|
|
//
|
|
|
|
ExAcquireSpinLock( &CcMasterSpinLock, &OldIrql );
|
|
|
|
SharedCacheMap->OpenCount -= 1;
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
ExReleaseSpinLock( &CcMasterSpinLock, 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 );
|
|
}
|
|
|
|
//
|
|
// Now that the file is unmapped, we can purge the truncated
|
|
// pages from memory, if TRUNCATE_REQUIRED. Note that if all
|
|
// of the section is being purged (FileSize == 0), 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 );
|
|
|
|
//
|
|
// 0 Length means to purge from the TruncateSize on.
|
|
//
|
|
|
|
CcPurgeCacheSection( FileObject->SectionObjectPointer,
|
|
&SharedCacheMap->FileSize,
|
|
0,
|
|
FALSE );
|
|
}
|
|
}
|
|
|
|
|
|
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
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
PSHARED_CACHE_MAP SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap;
|
|
|
|
if (SharedCacheMap != NULL) {
|
|
|
|
SharedCacheMap->DirtyPageThreshold = DirtyPageThreshold;
|
|
|
|
ExAcquireFastLock( &CcMasterSpinLock, &OldIrql );
|
|
SetFlag(((PFSRTL_COMMON_FCB_HEADER)(FileObject->FsContext))->Flags,
|
|
FSRTL_FLAG_LIMIT_MODIFIED_PAGES);
|
|
ExReleaseFastLock( &CcMasterSpinLock, OldIrql );
|
|
}
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
//
|
|
// See if we have an active Vacb, that we need to free.
|
|
//
|
|
|
|
FsRtlAcquireFileExclusive( FileObject );
|
|
ExAcquireFastLock( &CcMasterSpinLock, &OldIrql );
|
|
SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap;
|
|
|
|
if (SharedCacheMap != NULL) {
|
|
|
|
//
|
|
// See if there is an active vacb.
|
|
//
|
|
|
|
if ((SharedCacheMap->ActiveVacb != NULL) || ((NeedToZero = SharedCacheMap->NeedToZero) != NULL)) {
|
|
|
|
SharedCacheMap->OpenCount += 1;
|
|
GetActiveVacbAtDpcLevel( SharedCacheMap, ActiveVacb, ActivePage, PageIsDirty );
|
|
}
|
|
}
|
|
|
|
ExReleaseFastLock( &CcMasterSpinLock, 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 );
|
|
}
|
|
|
|
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)) {
|
|
|
|
CcFreeActiveVacb( SharedCacheMap, ActiveVacb, ActivePage, PageIsDirty );
|
|
|
|
//
|
|
// Serialize again to decrement the open count.
|
|
//
|
|
|
|
ExAcquireSpinLock( &CcMasterSpinLock, &OldIrql );
|
|
|
|
SharedCacheMap->OpenCount -= 1;
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
ExReleaseSpinLock( &CcMasterSpinLock, 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 i;
|
|
BOOLEAN WriteThrough;
|
|
ULONG SavedState = 0;
|
|
ULONG MaxZerosInCache = MAX_ZEROS_IN_CACHE;
|
|
|
|
PBCB Bcb = NULL;
|
|
PCHAR Zeros = NULL;
|
|
PMDL ZeroMdl = NULL;
|
|
ULONG MaxBytesMappedInMdl = 0;
|
|
BOOLEAN Result = TRUE;
|
|
|
|
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;
|
|
|
|
FOffset = *StartOffset;
|
|
|
|
//
|
|
// Calculate how much to zero this time.
|
|
//
|
|
|
|
ToGo.QuadPart = EndOffset->QuadPart - FOffset.QuadPart;
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
|
|
//
|
|
// try-finally to guarantee cleanup.
|
|
//
|
|
|
|
try {
|
|
PULONG Page;
|
|
ULONG SavedByteCount;
|
|
LARGE_INTEGER SizeLeft;
|
|
|
|
//
|
|
// Round FOffset and EndOffset up to sector boundaries, since
|
|
// we will be doing disk I/O, and calculate size left.
|
|
//
|
|
|
|
i = IoGetRelatedDeviceObject(FileObject)->SectorSize - 1;
|
|
FOffset.QuadPart += (LONGLONG)i;
|
|
FOffset.LowPart &= ~i;
|
|
SizeLeft.QuadPart = EndOffset->QuadPart + (LONGLONG)i;
|
|
SizeLeft.LowPart &= ~i;
|
|
SizeLeft.QuadPart -= FOffset.QuadPart;
|
|
|
|
if (SizeLeft.QuadPart == 0) {
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Allocate a page to hold the zeros we will write, and
|
|
// zero it.
|
|
//
|
|
|
|
ZeroBytes = MmNumberOfColors * PAGE_SIZE;
|
|
|
|
if (SizeLeft.QuadPart < (LONGLONG)ZeroBytes) {
|
|
ZeroBytes = SizeLeft.LowPart;
|
|
}
|
|
|
|
Zeros = (PCHAR)ExAllocatePool( NonPagedPoolCacheAligned, ZeroBytes );
|
|
|
|
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.
|
|
//
|
|
|
|
ZeroTransfer = MAX_ZERO_TRANSFER;
|
|
|
|
if (ZeroBytes < MmNumberOfColors * PAGE_SIZE) {
|
|
ZeroTransfer = ZeroBytes;
|
|
}
|
|
|
|
ZeroMdl = IoAllocateMdl( Zeros, ZeroTransfer, FALSE, FALSE, NULL );
|
|
|
|
if (ZeroMdl == NULL) {
|
|
ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
|
|
}
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
|
|
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 = (PULONG)(ZeroMdl + 1);
|
|
for (i = MmNumberOfColors;
|
|
i < (COMPUTE_PAGES_SPANNED( 0, SavedByteCount ));
|
|
i++) {
|
|
|
|
*(Page + i) = *(Page + i - MmNumberOfColors);
|
|
}
|
|
|
|
//
|
|
// We failed to allocate the space we wanted, so we will go to
|
|
// half of page of must succeed pool.
|
|
//
|
|
|
|
} else {
|
|
|
|
ZeroBytes = PAGE_SIZE / 2;
|
|
Zeros = (PCHAR)ExAllocatePool( NonPagedPoolCacheAligned, ZeroBytes );
|
|
|
|
//
|
|
// 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)ExAllocatePool( NonPagedPoolCacheAligned, ZeroBytes );
|
|
}
|
|
|
|
//
|
|
// 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 );
|
|
|
|
if ((Zeros == NULL) || (ZeroMdl == NULL)) {
|
|
ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
|
|
}
|
|
|
|
//
|
|
// Now we will lock the allocated pages
|
|
//
|
|
|
|
MmBuildMdlForNonPagedPool( ZeroMdl );
|
|
}
|
|
|
|
#ifdef MIPS
|
|
#ifdef MIPS_PREFILL
|
|
RtlFillMemory( Zeros, ZeroBytes, 0xDD );
|
|
KeSweepDcache( TRUE );
|
|
#endif
|
|
#endif
|
|
|
|
//
|
|
// Zero the buffer now.
|
|
//
|
|
|
|
RtlZeroMemory( Zeros, ZeroBytes );
|
|
|
|
//
|
|
// Map the full Mdl even if we will only use a part of it. This
|
|
// allow the unmapping operation to be deterministic.
|
|
//
|
|
|
|
(VOID)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.
|
|
//
|
|
|
|
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 (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
|
|
//
|
|
|
|
ExAcquireFastLock( &CcMasterSpinLock, &OldIrql );
|
|
|
|
if (SectionObjectPointer->SharedCacheMap != NULL) {
|
|
|
|
FileObject = ((PSHARED_CACHE_MAP)SectionObjectPointer->SharedCacheMap)->FileObject;
|
|
}
|
|
|
|
ExReleaseFastLock( &CcMasterSpinLock, 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;
|
|
}
|