reactos/ntoskrnl/cache/pinsup.c

998 lines
29 KiB
C

/*
* COPYRIGHT: See COPYING in the top level directory
* PROJECT: ReactOS Kernel
* FILE: ntoskrnl/cache/pinsup.c
* PURPOSE: Logging and configuration routines
* PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
* Art Yerkes
*/
/* INCLUDES *******************************************************************/
#include <ntoskrnl.h>
#include "newcc.h"
#include "section/newmm.h"
#define NDEBUG
#include <debug.h>
/* The following is a test mode that only works with modified filesystems.
* it maps the cache sections read only until they're pinned writable, and then
* turns them readonly again when they're unpinned.
* This helped me determine that a certain bug was not a memory overwrite. */
//#define PIN_WRITE_ONLY
/*
Pinsup implements the core of NewCC.
A couple of things about this code:
I wrote this code over the course of about 2 years, often referring to Rajeev
Nagar's Filesystem Internals, book, the msdn pages on the Cc interface, and
a few NT filesystems that are open sourced. I went to fairly great lengths to
achieve a couple of goals.
1) To make a strictly layered facility that relies entirely on Mm to provide
maps. There were many ways in which data segments in the legacy Mm were unable
to provide what I needed; page maps were only 4 gig, and all offsets were in
ULONG, so no mapping at an offset greater than 4 gig was possible. Worse than
that, due to a convoluted set of dependencies, it would have been impossible to
support any two mappings farther apart than 4 gig, even if the above was
corrected. Along with that, the cache system's ownership of some pages was
integral to the operation of legacy Mm. All of the above problems, along with
an ambiguity about when the size of a file for mapping purposes is acquired,
and its inability to allow a file to be resized when any mappings were active
led me to rewrite data sections (and all other kinds of sections in the
original version), and use that layer to implement the Cc API without regard
to any internal, undocumented parts.
2) To write the simplest possible code that implements the Cc interface as
documented. Again this is without regard to any information that might be
gained through reverse engineering the real Cc. All conclusions about workings
of Cc here are mine, any failures are mine, any differences to the documented
interface were introduced by me due to misreading, misunderstanding or mis
remembering while implementing the code. I also implemented some obvious, but
not actually specified behaviors of Cc, for example that each cache stripe is
represented by a distinct BCB that the user can make decisions about as an
opaque pointer.
3) To make real filesystems work properly.
So about how it works:
CcCacheSections is the collection of cache sections that are currently mapped.
The cache ranges which are allocated and contain pages is larger, due to the
addition of sections containing rmaps and page references, but this array
determines the actual mapped pages on behalf of all mapped files for Cc's use.
All BCB pointers yielded to a driver are a pointer to one of these cache stripe
structures. The data structure is specified as opaque and so it contains
information convenient to NEWCC's implementation here. Free entries are
summarized in CcpBitmapBuffer, for which bits are set when the entry may be
safely evicted and redirected for use by another client. Note that the
reference count for an evictable cache section will generally be 1, since
we'll keep a reference to wait for any subsequent mapping of the same stripe.
We use CcCacheClockHand as a hint to start checking free bits at a point that
walks around the cache stripe list, so that we might evict a different stripe
every time even if all are awaiting reuse. This is a way to avoid thrashing.
CcpBitmapBuffer is the RTL_BITMAP that allows us to quickly decide what buffer
to allocate from the mapped buffer set.
CcDeleteEvent is an event used to wait for a cache stripe reference count to
go to 1, thus making the stripe eligible for eviction. It's used by CcpMapData
to wait for a free map when we can't fail.
All in all, use of Mm by Cc makes this code into a simple manager that wields
sections on behalf of filesystems. As such, its code is fairly high level and
no architecture specific changes should be necessary.
*/
/* GLOBALS ********************************************************************/
#define TAG_MAP_SEC TAG('C', 'c', 'S', 'x')
#define TAG_MAP_READ TAG('M', 'c', 'p', 'y')
#define TAG_MAP_BCB TAG('B', 'c', 'b', ' ')
NOCC_BCB CcCacheSections[CACHE_NUM_SECTIONS];
CHAR CcpBitmapBuffer[sizeof(RTL_BITMAP) + ROUND_UP((CACHE_NUM_SECTIONS), 32) / 8];
PRTL_BITMAP CcCacheBitmap = (PRTL_BITMAP)&CcpBitmapBuffer;
FAST_MUTEX CcMutex;
KEVENT CcDeleteEvent;
KEVENT CcFinalizeEvent;
ULONG CcCacheClockHand;
LONG CcOutstandingDeletes;
/* FUNCTIONS ******************************************************************/
PETHREAD LastThread;
VOID
_CcpLock(const char *file,
int line)
{
//DPRINT("<<<---<<< CC In Mutex(%s:%d %x)!\n", file, line, PsGetCurrentThread());
ExAcquireFastMutex(&CcMutex);
}
VOID
_CcpUnlock(const char *file,
int line)
{
ExReleaseFastMutex(&CcMutex);
//DPRINT(">>>--->>> CC Exit Mutex!\n", file, line);
}
PDEVICE_OBJECT
NTAPI
MmGetDeviceObjectForFile(IN PFILE_OBJECT FileObject);
/*
Allocate an almost ordinary section object for use by the cache system.
The special internal SEC_CACHE flag is used to indicate that the section
should not count when determining whether the file can be resized.
*/
NTSTATUS
CcpAllocateSection(PFILE_OBJECT FileObject,
ULONG Length,
ULONG Protect,
PROS_SECTION_OBJECT *Result)
{
NTSTATUS Status;
LARGE_INTEGER MaxSize;
MaxSize.QuadPart = Length;
DPRINT("Making Section for File %x\n", FileObject);
DPRINT("File name %wZ\n", &FileObject->FileName);
Status = MmCreateSection((PVOID*)Result,
STANDARD_RIGHTS_REQUIRED,
NULL,
&MaxSize,
Protect,
SEC_RESERVE | SEC_CACHE,
NULL,
FileObject);
return Status;
}
typedef struct _WORK_QUEUE_WITH_CONTEXT
{
WORK_QUEUE_ITEM WorkItem;
PVOID ToUnmap;
LARGE_INTEGER FileOffset;
LARGE_INTEGER MapSize;
PROS_SECTION_OBJECT ToDeref;
PACQUIRE_FOR_LAZY_WRITE AcquireForLazyWrite;
PRELEASE_FROM_LAZY_WRITE ReleaseFromLazyWrite;
PVOID LazyContext;
BOOLEAN Dirty;
} WORK_QUEUE_WITH_CONTEXT, *PWORK_QUEUE_WITH_CONTEXT;
/*
Unmap a cache stripe. Note that cache stripes aren't unmapped when their
last reference disappears. We enter this code only if cache for the file
is uninitialized in the last file object, or a cache stripe is evicted.
*/
VOID
CcpUnmapCache(PVOID Context)
{
PWORK_QUEUE_WITH_CONTEXT WorkItem = (PWORK_QUEUE_WITH_CONTEXT)Context;
DPRINT("Unmapping (finally) %x\n", WorkItem->ToUnmap);
MmUnmapCacheViewInSystemSpace(WorkItem->ToUnmap);
ObDereferenceObject(WorkItem->ToDeref);
ExFreePool(WorkItem);
DPRINT("Done\n");
}
/*
Somewhat deceptively named function which removes the last reference to a
cache stripe and completely removes it using CcUnmapCache. This may be
done either inline (if the Immediate BOOLEAN is set), or using a work item
at a later time. Whether this is called to unmap immeidately is mainly
determined by whether the caller is calling from a place in filesystem code
where a deadlock may occur if immediate flushing is required.
It's always safe to reuse the Bcb at CcCacheSections[Start] after calling
this.
*/
/* Must have acquired the mutex */
VOID
CcpDereferenceCache(ULONG Start,
BOOLEAN Immediate)
{
PVOID ToUnmap;
PNOCC_BCB Bcb;
BOOLEAN Dirty;
LARGE_INTEGER MappedSize;
LARGE_INTEGER BaseOffset;
PWORK_QUEUE_WITH_CONTEXT WorkItem;
DPRINT("CcpDereferenceCache(#%x)\n", Start);
Bcb = &CcCacheSections[Start];
Dirty = Bcb->Dirty;
ToUnmap = Bcb->BaseAddress;
BaseOffset = Bcb->FileOffset;
MappedSize = Bcb->Map->FileSizes.ValidDataLength;
DPRINT("Dereference #%x (count %d)\n", Start, Bcb->RefCount);
ASSERT(Bcb->SectionObject);
ASSERT(Bcb->RefCount == 1);
DPRINT("Firing work item for %x\n", Bcb->BaseAddress);
if (Dirty) {
CcpUnlock();
Bcb->RefCount++;
MiFlushMappedSection(ToUnmap, &BaseOffset, &MappedSize, Dirty);
Bcb->RefCount--;
CcpLock();
}
if (Immediate)
{
PROS_SECTION_OBJECT ToDeref = Bcb->SectionObject;
Bcb->Map = NULL;
Bcb->SectionObject = NULL;
Bcb->BaseAddress = NULL;
Bcb->FileOffset.QuadPart = 0;
Bcb->Length = 0;
Bcb->RefCount = 0;
Bcb->Dirty = FALSE;
RemoveEntryList(&Bcb->ThisFileList);
CcpUnlock();
MmUnmapCacheViewInSystemSpace(ToUnmap);
ObDereferenceObject(ToDeref);
CcpLock();
}
else
{
WorkItem = ExAllocatePool(NonPagedPool, sizeof(*WorkItem));
if (!WorkItem) KeBugCheck(0);
WorkItem->ToUnmap = Bcb->BaseAddress;
WorkItem->FileOffset = Bcb->FileOffset;
WorkItem->Dirty = Bcb->Dirty;
WorkItem->MapSize = MappedSize;
WorkItem->ToDeref = Bcb->SectionObject;
WorkItem->AcquireForLazyWrite = Bcb->Map->Callbacks.AcquireForLazyWrite;
WorkItem->ReleaseFromLazyWrite = Bcb->Map->Callbacks.ReleaseFromLazyWrite;
WorkItem->LazyContext = Bcb->Map->LazyContext;
ExInitializeWorkItem(&WorkItem->WorkItem,
(PWORKER_THREAD_ROUTINE)CcpUnmapCache,
WorkItem);
Bcb->Map = NULL;
Bcb->SectionObject = NULL;
Bcb->BaseAddress = NULL;
Bcb->FileOffset.QuadPart = 0;
Bcb->Length = 0;
Bcb->RefCount = 0;
Bcb->Dirty = FALSE;
RemoveEntryList(&Bcb->ThisFileList);
CcpUnlock();
ExQueueWorkItem(&WorkItem->WorkItem, DelayedWorkQueue);
CcpLock();
}
DPRINT("Done\n");
}
/*
CcpAllocateCacheSections is called by CcpMapData to obtain a cache stripe,
possibly evicting an old stripe by calling CcpDereferenceCache in order to
obtain an empty Bcb.
This function was named plural due to a question I had at the beginning of
this endeavor about whether a map may span a 256k stripe boundary. It can't
so this function can only return the index of one Bcb. Returns INVALID_CACHE
on failure.
*/
/* Needs mutex */
ULONG
CcpAllocateCacheSections(PFILE_OBJECT FileObject,
PROS_SECTION_OBJECT SectionObject)
{
ULONG i = INVALID_CACHE;
PNOCC_CACHE_MAP Map;
PNOCC_BCB Bcb;
DPRINT("AllocateCacheSections: FileObject %x\n", FileObject);
if (!FileObject->SectionObjectPointer)
return INVALID_CACHE;
Map = (PNOCC_CACHE_MAP)FileObject->SectionObjectPointer->SharedCacheMap;
if (!Map)
return INVALID_CACHE;
DPRINT("Allocating Cache Section\n");
i = RtlFindClearBitsAndSet(CcCacheBitmap, 1, CcCacheClockHand);
CcCacheClockHand = (i + 1) % CACHE_NUM_SECTIONS;
if (i != INVALID_CACHE)
{
DPRINT("Setting up Bcb #%x\n", i);
Bcb = &CcCacheSections[i];
ASSERT(Bcb->RefCount < 2);
if (Bcb->RefCount > 0)
{
CcpDereferenceCache(i, FALSE);
}
ASSERT(!Bcb->RefCount);
Bcb->RefCount = 1;
DPRINT("Bcb #%x RefCount %d\n", Bcb - CcCacheSections, Bcb->RefCount);
if (!RtlTestBit(CcCacheBitmap, i))
{
DPRINT1("Somebody stoeled BCB #%x\n", i);
}
ASSERT(RtlTestBit(CcCacheBitmap, i));
DPRINT("Allocated #%x\n", i);
ASSERT(CcCacheSections[i].RefCount);
}
else
{
DPRINT1("Failed to allocate cache segment\n");
}
return i;
}
/* Must have acquired the mutex */
VOID
CcpReferenceCache(ULONG Start)
{
PNOCC_BCB Bcb;
Bcb = &CcCacheSections[Start];
ASSERT(Bcb->SectionObject);
Bcb->RefCount++;
RtlSetBit(CcCacheBitmap, Start);
}
VOID
CcpMarkForExclusive(ULONG Start)
{
PNOCC_BCB Bcb;
Bcb = &CcCacheSections[Start];
Bcb->ExclusiveWaiter++;
}
/*
Cache stripes have an idea of exclusive access, which would be hard to support
properly in the previous code. In our case, it's fairly easy, since we have
an event that indicates that the previous exclusive waiter has returned in each
Bcb.
*/
/* Must not have the mutex */
VOID
CcpReferenceCacheExclusive(ULONG Start)
{
PNOCC_BCB Bcb = &CcCacheSections[Start];
KeWaitForSingleObject(&Bcb->ExclusiveWait,
Executive,
KernelMode,
FALSE,
NULL);
CcpLock();
ASSERT(Bcb->ExclusiveWaiter);
ASSERT(Bcb->SectionObject);
Bcb->Exclusive = TRUE;
Bcb->ExclusiveWaiter--;
RtlSetBit(CcCacheBitmap, Start);
CcpUnlock();
}
/*
Find a map that encompasses the target range. This function does not check
whether the desired range is partly outside the stripe. This could be
implemented with a generic table, but we generally aren't carring around a lot
of segments at once for a particular file.
When this returns a map for a given file address, then that address is by
definition already mapped and can be operated on.
Returns a valid index or INVALID_CACHE.
*/
/* Must have the mutex */
ULONG
CcpFindMatchingMap(PLIST_ENTRY Head,
PLARGE_INTEGER FileOffset,
ULONG Length)
{
PLIST_ENTRY Entry;
//DPRINT("Find Matching Map: (%x) %x:%x\n", FileOffset->LowPart, Length);
for (Entry = Head->Flink; Entry != Head; Entry = Entry->Flink)
{
//DPRINT("Link @%x\n", Entry);
PNOCC_BCB Bcb = CONTAINING_RECORD(Entry, NOCC_BCB, ThisFileList);
//DPRINT("Selected BCB %x #%x\n", Bcb, Bcb - CcCacheSections);
//DPRINT("This File: %x:%x\n", Bcb->FileOffset.LowPart, Bcb->Length);
if (FileOffset->QuadPart >= Bcb->FileOffset.QuadPart &&
FileOffset->QuadPart < Bcb->FileOffset.QuadPart + CACHE_STRIPE)
{
//DPRINT("Found match at #%x\n", Bcb - CcCacheSections);
return Bcb - CcCacheSections;
}
}
//DPRINT("This region isn't mapped\n");
return INVALID_CACHE;
}
/*
Internal function that's used by all pinning functions.
It causes a mapped region to exist and prefaults the pages in it if possible,
possibly evicting another stripe in order to get our stripe.
*/
BOOLEAN
NTAPI
CcpMapData(IN PFILE_OBJECT FileObject,
IN PLARGE_INTEGER FileOffset,
IN ULONG Length,
IN ULONG Flags,
OUT PVOID *BcbResult,
OUT PVOID *Buffer)
{
BOOLEAN Success = FALSE, FaultIn = FALSE;
/* Note: windows 2000 drivers treat this as a bool */
//BOOLEAN Wait = (Flags & MAP_WAIT) || (Flags == TRUE);
LARGE_INTEGER Target, EndInterval;
ULONG BcbHead, SectionSize, ViewSize;
PNOCC_BCB Bcb = NULL;
PROS_SECTION_OBJECT SectionObject = NULL;
NTSTATUS Status;
PNOCC_CACHE_MAP Map = (PNOCC_CACHE_MAP)FileObject->SectionObjectPointer->SharedCacheMap;
ViewSize = CACHE_STRIPE;
if (!Map)
{
DPRINT1("File object was not mapped\n");
return FALSE;
}
DPRINT("CcMapData(F->%x, %I64x:%d)\n",
FileObject,
FileOffset->QuadPart,
Length);
ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
Target.HighPart = FileOffset->HighPart;
Target.LowPart = CACHE_ROUND_DOWN(FileOffset->LowPart);
CcpLock();
/* Find out if any range is a superset of what we want */
/* Find an accomodating section */
BcbHead = CcpFindMatchingMap(&Map->AssociatedBcb, FileOffset, Length);
if (BcbHead != INVALID_CACHE)
{
Bcb = &CcCacheSections[BcbHead];
Success = TRUE;
*BcbResult = Bcb;
*Buffer = ((PCHAR)Bcb->BaseAddress) + (int)(FileOffset->QuadPart - Bcb->FileOffset.QuadPart);
DPRINT("Bcb #%x Buffer maps (%I64x) At %x Length %x (Getting %p:%x) %wZ\n",
Bcb - CcCacheSections,
Bcb->FileOffset.QuadPart,
Bcb->BaseAddress,
Bcb->Length,
*Buffer,
Length,
&FileObject->FileName);
DPRINT("w1n\n");
goto cleanup;
}
DPRINT("File size %I64x\n",
Map->FileSizes.ValidDataLength.QuadPart);
/* Not all files have length, in fact filesystems often use stream file
objects for various internal purposes and are loose about the file
length, since the filesystem promises itself to write the right number
of bytes to the internal stream. In these cases, we just allow the file
to have the full stripe worth of space. */
if (Map->FileSizes.ValidDataLength.QuadPart)
{
SectionSize = min(CACHE_STRIPE,
Map->FileSizes.ValidDataLength.QuadPart - Target.QuadPart);
}
else
{
SectionSize = CACHE_STRIPE;
}
DPRINT("Allocating a cache stripe at %x:%d\n",
Target.LowPart, SectionSize);
//ASSERT(SectionSize <= CACHE_STRIPE);
CcpUnlock();
/* CcpAllocateSection doesn't need the lock, so we'll give other action
a chance in here. */
Status = CcpAllocateSection(FileObject,
SectionSize,
#ifdef PIN_WRITE_ONLY
PAGE_READONLY,
#else
PAGE_READWRITE,
#endif
&SectionObject);
CcpLock();
if (!NT_SUCCESS(Status))
{
*BcbResult = NULL;
*Buffer = NULL;
DPRINT1("End %08x\n", Status);
goto cleanup;
}
retry:
/* Returns a reference */
DPRINT("Allocating cache sections: %wZ\n", &FileObject->FileName);
BcbHead = CcpAllocateCacheSections(FileObject, SectionObject);
/* XXX todo: we should handle the immediate fail case here, but don't */
if (BcbHead == INVALID_CACHE)
{
ULONG i;
DbgPrint("Cache Map:");
for (i = 0; i < CACHE_NUM_SECTIONS; i++)
{
if (!(i % 64)) DbgPrint("\n");
DbgPrint("%c",
CcCacheSections[i].RefCount + (RtlTestBit(CcCacheBitmap, i) ? '@' : '`'));
}
DbgPrint("\n");
KeWaitForSingleObject(&CcDeleteEvent,
Executive,
KernelMode,
FALSE,
NULL);
goto retry;
}
DPRINT("BcbHead #%x (final)\n", BcbHead);
if (BcbHead == INVALID_CACHE)
{
*BcbResult = NULL;
*Buffer = NULL;
DPRINT1("End\n");
goto cleanup;
}
DPRINT("Selected BCB #%x\n", BcbHead);
ViewSize = CACHE_STRIPE;
Bcb = &CcCacheSections[BcbHead];
/* MmMapCacheViewInSystemSpaceAtOffset is one of three methods of Mm
that are specific to NewCC. In this case, it's implementation
exactly mirrors MmMapViewInSystemSpace, but allows an offset to
be specified. */
Status = MmMapCacheViewInSystemSpaceAtOffset(SectionObject->Segment,
&Bcb->BaseAddress,
&Target,
&ViewSize);
/* Summary: Failure. Dereference our section and tell the user we failed */
if (!NT_SUCCESS(Status))
{
*BcbResult = NULL;
*Buffer = NULL;
ObDereferenceObject(SectionObject);
RemoveEntryList(&Bcb->ThisFileList);
RtlZeroMemory(Bcb, sizeof(*Bcb));
RtlClearBit(CcCacheBitmap, BcbHead);
DPRINT1("Failed to map\n");
goto cleanup;
}
/* Summary: Success. Put together a valid Bcb and link it with the others
* in the NOCC_CACHE_MAP.
*/
Success = TRUE;
Bcb->Length = MIN(Map->FileSizes.ValidDataLength.QuadPart - Target.QuadPart,
CACHE_STRIPE);
Bcb->SectionObject = SectionObject;
Bcb->Map = Map;
Bcb->FileOffset = Target;
InsertTailList(&Map->AssociatedBcb, &Bcb->ThisFileList);
*BcbResult = &CcCacheSections[BcbHead];
*Buffer = ((PCHAR)Bcb->BaseAddress) + (int)(FileOffset->QuadPart - Bcb->FileOffset.QuadPart);
FaultIn = TRUE;
DPRINT("Bcb #%x Buffer maps (%I64x) At %x Length %x (Getting %p:%lx) %wZ\n",
Bcb - CcCacheSections,
Bcb->FileOffset.QuadPart,
Bcb->BaseAddress,
Bcb->Length,
*Buffer,
Length,
&FileObject->FileName);
EndInterval.QuadPart = Bcb->FileOffset.QuadPart + Bcb->Length - 1;
ASSERT((EndInterval.QuadPart & ~(CACHE_STRIPE - 1)) ==
(Bcb->FileOffset.QuadPart & ~(CACHE_STRIPE - 1)));
cleanup:
CcpUnlock();
if (Success)
{
if (FaultIn)
{
/* Fault in the pages. This forces reads to happen now. */
ULONG i;
PCHAR FaultIn = Bcb->BaseAddress;
DPRINT("Faulting in pages at this point: file %wZ %I64x:%x\n",
&FileObject->FileName,
Bcb->FileOffset.QuadPart,
Bcb->Length);
for (i = 0; i < Bcb->Length; i += PAGE_SIZE)
{
FaultIn[i] ^= 0;
}
}
ASSERT(Bcb >= CcCacheSections &&
Bcb < (CcCacheSections + CACHE_NUM_SECTIONS));
}
else
{
ASSERT(FALSE);
}
return Success;
}
BOOLEAN
NTAPI
CcMapData(IN PFILE_OBJECT FileObject,
IN PLARGE_INTEGER FileOffset,
IN ULONG Length,
IN ULONG Flags,
OUT PVOID *BcbResult,
OUT PVOID *Buffer)
{
BOOLEAN Result;
Result = CcpMapData(FileObject,
FileOffset,
Length,
Flags,
BcbResult,
Buffer);
if (Result)
{
PNOCC_BCB Bcb = (PNOCC_BCB)*BcbResult;
ASSERT(Bcb >= CcCacheSections &&
Bcb < CcCacheSections + CACHE_NUM_SECTIONS);
ASSERT(Bcb->BaseAddress);
CcpLock();
CcpReferenceCache(Bcb - CcCacheSections);
CcpUnlock();
}
return Result;
}
/* Used by functions that repin data, CcpPinMappedData does not alter the map,
but finds the appropriate stripe and update the accounting. */
BOOLEAN
NTAPI
CcpPinMappedData(IN PNOCC_CACHE_MAP Map,
IN PLARGE_INTEGER FileOffset,
IN ULONG Length,
IN ULONG Flags,
IN OUT PVOID *Bcb)
{
BOOLEAN Exclusive = Flags & PIN_EXCLUSIVE;
ULONG BcbHead;
PNOCC_BCB TheBcb;
CcpLock();
ASSERT(Map->AssociatedBcb.Flink == &Map->AssociatedBcb || (CONTAINING_RECORD(Map->AssociatedBcb.Flink, NOCC_BCB, ThisFileList) >= CcCacheSections && CONTAINING_RECORD(Map->AssociatedBcb.Flink, NOCC_BCB, ThisFileList) < CcCacheSections + CACHE_NUM_SECTIONS));
BcbHead = CcpFindMatchingMap(&Map->AssociatedBcb, FileOffset, Length);
if (BcbHead == INVALID_CACHE)
{
CcpUnlock();
return FALSE;
}
TheBcb = &CcCacheSections[BcbHead];
if (Exclusive)
{
DPRINT("Requesting #%x Exclusive\n", BcbHead);
CcpMarkForExclusive(BcbHead);
}
else
{
DPRINT("Reference #%x\n", BcbHead);
CcpReferenceCache(BcbHead);
}
if (Exclusive)
CcpReferenceCacheExclusive(BcbHead);
CcpUnlock();
*Bcb = TheBcb;
return TRUE;
}
BOOLEAN
NTAPI
CcPinMappedData(IN PFILE_OBJECT FileObject,
IN PLARGE_INTEGER FileOffset,
IN ULONG Length,
IN ULONG Flags,
IN OUT PVOID *Bcb)
{
PVOID Buffer;
PNOCC_CACHE_MAP Map = (PNOCC_CACHE_MAP)FileObject->SectionObjectPointer->SharedCacheMap;
if (!Map)
{
DPRINT1("Not cached\n");
return FALSE;
}
if (CcpMapData(FileObject, FileOffset, Length, Flags, Bcb, &Buffer))
{
return CcpPinMappedData(Map, FileOffset, Length, Flags, Bcb);
}
else
{
DPRINT1("could not map\n");
return FALSE;
}
}
BOOLEAN
NTAPI
CcPinRead(IN PFILE_OBJECT FileObject,
IN PLARGE_INTEGER FileOffset,
IN ULONG Length,
IN ULONG Flags,
OUT PVOID *Bcb,
OUT PVOID *Buffer)
{
PNOCC_BCB RealBcb;
BOOLEAN Result;
Result = CcPinMappedData(FileObject, FileOffset, Length, Flags, Bcb);
if (Result)
{
CcpLock();
RealBcb = *Bcb;
*Buffer = ((PCHAR)RealBcb->BaseAddress) + (int)(FileOffset->QuadPart - RealBcb->FileOffset.QuadPart);
CcpUnlock();
}
return Result;
}
BOOLEAN
NTAPI
CcPreparePinWrite(IN PFILE_OBJECT FileObject,
IN PLARGE_INTEGER FileOffset,
IN ULONG Length,
IN BOOLEAN Zero,
IN ULONG Flags,
OUT PVOID *Bcb,
OUT PVOID *Buffer)
{
BOOLEAN Result;
PNOCC_BCB RealBcb;
#ifdef PIN_WRITE_ONLY
PVOID BaseAddress;
SIZE_T NumberOfBytes;
ULONG OldProtect;
#endif
DPRINT("CcPreparePinWrite(%x:%x)\n", Buffer, Length);
Result = CcPinRead(FileObject, FileOffset, Length, Flags, Bcb, Buffer);
if (Result)
{
CcpLock();
RealBcb = *Bcb;
#ifdef PIN_WRITE_ONLY
BaseAddress = RealBcb->BaseAddress;
NumberOfBytes = RealBcb->Length;
MiProtectVirtualMemory(NULL,
&BaseAddress,
&NumberOfBytes,
PAGE_READWRITE,
&OldProtect);
#endif
CcpUnlock();
RealBcb->Dirty = TRUE;
if (Zero)
{
DPRINT("Zero fill #%x %I64x:%x Buffer %x %wZ\n",
RealBcb - CcCacheSections,
FileOffset->QuadPart,
Length,
*Buffer,
&FileObject->FileName);
DPRINT1("RtlZeroMemory(%p, %lx)\n", *Buffer, Length);
RtlZeroMemory(*Buffer, Length);
}
}
return Result;
}
/*
CcpUnpinData is the internal function that generally handles unpinning data.
It may be a little confusing, because of the way reference counts are handled.
A reference count of 2 or greater means that the stripe is still fully pinned
and can't be removed. If the owner had taken an exclusive reference, then
give one up. Note that it's an error to take more than one exclusive reference
or to take a non-exclusive reference after an exclusive reference, so detecting
or handling that case is not considered.
ReleaseBit is unset if we want to detect when a cache stripe would become
evictable without actually giving up our reference. We might want to do that
if we were going to flush before formally releasing the cache stripe, although
that facility is not used meaningfully at this time.
A reference count of exactly 1 means that the stripe could potentially be
reused, but could also be evicted for another mapping. In general, most
stripes should be in that state most of the time.
A reference count of zero means that the Bcb is completely unused. That's the
start state and the state of a Bcb formerly owned by a file that is
uninitialized.
*/
BOOLEAN
NTAPI
CcpUnpinData(IN PNOCC_BCB RealBcb, BOOLEAN ReleaseBit)
{
if (RealBcb->RefCount <= 2)
{
RealBcb->Exclusive = FALSE;
if (RealBcb->ExclusiveWaiter)
{
DPRINT("Triggering exclusive waiter\n");
KeSetEvent(&RealBcb->ExclusiveWait, IO_NO_INCREMENT, FALSE);
return TRUE;
}
}
if (RealBcb->RefCount == 2 && !ReleaseBit)
return FALSE;
if (RealBcb->RefCount > 1)
{
DPRINT("Removing one reference #%x\n", RealBcb - CcCacheSections);
RealBcb->RefCount--;
KeSetEvent(&CcDeleteEvent, IO_DISK_INCREMENT, FALSE);
}
if (RealBcb->RefCount == 1)
{
DPRINT("Clearing allocation bit #%x\n", RealBcb - CcCacheSections);
RtlClearBit(CcCacheBitmap, RealBcb - CcCacheSections);
#ifdef PIN_WRITE_ONLY
PVOID BaseAddress = RealBcb->BaseAddress;
SIZE_T NumberOfBytes = RealBcb->Length;
ULONG OldProtect;
MiProtectVirtualMemory(NULL,
&BaseAddress,
&NumberOfBytes,
PAGE_READONLY,
&OldProtect);
#endif
}
return TRUE;
}
VOID
NTAPI
CcUnpinData(IN PVOID Bcb)
{
PNOCC_BCB RealBcb = (PNOCC_BCB)Bcb;
ULONG Selected = RealBcb - CcCacheSections;
BOOLEAN Released;
ASSERT(RealBcb >= CcCacheSections &&
RealBcb - CcCacheSections < CACHE_NUM_SECTIONS);
DPRINT("CcUnpinData Bcb #%x (RefCount %d)\n", Selected, RealBcb->RefCount);
CcpLock();
Released = CcpUnpinData(RealBcb, FALSE);
CcpUnlock();
if (!Released) {
CcpLock();
CcpUnpinData(RealBcb, TRUE);
CcpUnlock();
}
}
VOID
NTAPI
CcSetBcbOwnerPointer(IN PVOID Bcb,
IN PVOID OwnerPointer)
{
PNOCC_BCB RealBcb = (PNOCC_BCB)Bcb;
CcpLock();
CcpReferenceCache(RealBcb - CcCacheSections);
RealBcb->OwnerPointer = OwnerPointer;
CcpUnlock();
}
VOID
NTAPI
CcUnpinDataForThread(IN PVOID Bcb,
IN ERESOURCE_THREAD ResourceThreadId)
{
CcUnpinData(Bcb);
}
/* EOF */