summaryrefslogtreecommitdiffstats
path: root/private/ntos/cache/copysup.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--private/ntos/cache/copysup.c2117
1 files changed, 2117 insertions, 0 deletions
diff --git a/private/ntos/cache/copysup.c b/private/ntos/cache/copysup.c
new file mode 100644
index 000000000..e462014b8
--- /dev/null
+++ b/private/ntos/cache/copysup.c
@@ -0,0 +1,2117 @@
+/*++
+
+Copyright (c) 1990 Microsoft Corporation
+
+Module Name:
+
+ copysup.c
+
+Abstract:
+
+ This module implements the copy support routines for the Cache subsystem.
+
+Author:
+
+ Tom Miller [TomM] 4-May-1990
+
+Revision History:
+
+--*/
+
+#include "cc.h"
+
+//
+// Define our debug constant
+//
+
+#define me 0x00000004
+
+
+BOOLEAN
+CcCopyRead (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN ULONG Length,
+ IN BOOLEAN Wait,
+ OUT PVOID Buffer,
+ OUT PIO_STATUS_BLOCK IoStatus
+ )
+
+/*++
+
+Routine Description:
+
+ This routine attempts to copy the specified file data from the cache
+ into the output buffer, and deliver the correct I/O status. It is *not*
+ safe to call this routine from Dpc level.
+
+ 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 supply all of the requested data without
+ blocking, then this routine will return FALSE. However, if the
+ data is immediately accessible in the cache and no blocking is
+ required, this routine copies the data and returns TRUE.
+
+ If the caller supplies Wait as TRUE, then this routine is guaranteed
+ to copy the data and return TRUE. If the data is immediately
+ accessible in the cache, then no blocking will occur. Otherwise,
+ the the data transfer from the file into the cache will be initiated,
+ and the caller will be blocked until the data can be returned.
+
+ 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 or Server Fsp threads should supply Wait = TRUE.
+
+Arguments:
+
+ FileObject - Pointer to the file object for a file which was
+ opened with NO_INTERMEDIATE_BUFFERING clear, i.e., for
+ which CcInitializeCacheMap was called by the file system.
+
+ FileOffset - Byte offset in file for desired data.
+
+ Length - Length of desired data in bytes.
+
+ Wait - FALSE if caller may not block, TRUE otherwise (see description
+ above)
+
+ Buffer - Pointer to output buffer to which data should be copied.
+
+ IoStatus - Pointer to standard I/O status block to receive the status
+ for the transfer. (STATUS_SUCCESS guaranteed for cache
+ hits, otherwise the actual I/O status is returned.)
+
+ Note that even if FALSE is returned, the IoStatus.Information
+ field will return the count of any bytes successfully
+ transferred before a blocking condition occured. The caller
+ may either choose to ignore this information, or resume
+ the copy later accounting for bytes transferred.
+
+Return Value:
+
+ FALSE - if Wait was supplied as FALSE and the data was not delivered
+
+ TRUE - if the data is being delivered
+
+--*/
+
+{
+ PSHARED_CACHE_MAP SharedCacheMap;
+ PPRIVATE_CACHE_MAP PrivateCacheMap;
+ PVOID CacheBuffer;
+ LARGE_INTEGER FOffset;
+ PVACB Vacb;
+ PBCB Bcb;
+ PVACB ActiveVacb;
+ ULONG ActivePage;
+ ULONG PageIsDirty;
+ ULONG SavedState;
+ KIRQL OldIrql;
+ NTSTATUS Status;
+ ULONG OriginalLength = Length;
+ ULONG PageCount = COMPUTE_PAGES_SPANNED(((PVOID)FileOffset->LowPart), Length);
+ PETHREAD Thread = PsGetCurrentThread();
+ BOOLEAN GotAMiss = FALSE;
+
+ DebugTrace(+1, me, "CcCopyRead\n", 0 );
+
+ MmSavePageFaultReadAhead( Thread, &SavedState );
+
+ //
+ // Get pointer to shared and private cache maps
+ //
+
+ SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap;
+ PrivateCacheMap = FileObject->PrivateCacheMap;
+
+ //
+ // Check for read past file size, the caller must filter this case out.
+ //
+
+ ASSERT( ( FileOffset->QuadPart + (LONGLONG)Length) <= SharedCacheMap->FileSize.QuadPart );
+
+ //
+ // If read ahead is enabled, then do the read ahead here so it
+ // overlaps with the copy (otherwise we will do it below).
+ // Note that we are assuming that we will not get ahead of our
+ // current transfer - if read ahead is working it should either
+ // already be in memory or else underway.
+ //
+
+ if (PrivateCacheMap->ReadAheadEnabled && (PrivateCacheMap->ReadAheadLength[1] == 0)) {
+ CcScheduleReadAhead( FileObject, FileOffset, Length );
+ }
+
+ FOffset = *FileOffset;
+
+ //
+ // Increment performance counters
+ //
+
+ if (Wait) {
+ HOT_STATISTIC(CcCopyReadWait) += 1;
+
+ //
+ // This is not an exact solution, but when IoPageRead gets a miss,
+ // it cannot tell whether it was CcCopyRead or CcMdlRead, but since
+ // the miss should occur very soon, by loading the pointer here
+ // probably the right counter will get incremented, and in any case,
+ // we hope the errrors average out!
+ //
+
+ CcMissCounter = &CcCopyReadWaitMiss;
+
+ } else {
+ HOT_STATISTIC(CcCopyReadNoWait) += 1;
+ }
+
+ //
+ // See if we have an active Vacb, that we can just copy to.
+ //
+
+ GetActiveVacb( SharedCacheMap, OldIrql, ActiveVacb, ActivePage, PageIsDirty );
+
+ if (ActiveVacb != NULL) {
+
+ if ((ULONG)(FOffset.QuadPart >> VACB_OFFSET_SHIFT) == (ActivePage >> (VACB_OFFSET_SHIFT - PAGE_SHIFT))) {
+
+ ULONG LengthToCopy = VACB_MAPPING_GRANULARITY - (FOffset.LowPart & (VACB_MAPPING_GRANULARITY - 1));
+
+ if (SharedCacheMap->NeedToZero != NULL) {
+
+ PVOID NeedToZero;
+
+ ExAcquireFastLock( &SharedCacheMap->ActiveVacbSpinLock, &OldIrql );
+
+ //
+ // Note that the NeedToZero could be cleared, since we
+ // tested it without the spinlock.
+ //
+
+ NeedToZero = SharedCacheMap->NeedToZero;
+ if (NeedToZero != NULL) {
+
+ RtlZeroMemory( NeedToZero, PAGE_SIZE - ((((ULONG)NeedToZero - 1) & (PAGE_SIZE - 1)) + 1) );
+ SharedCacheMap->NeedToZero = NULL;
+ }
+
+ ExReleaseFastLock( &SharedCacheMap->ActiveVacbSpinLock, OldIrql );
+
+ if (NeedToZero != NULL) {
+ MmUnlockCachedPage( (PVOID)((PCHAR)NeedToZero - 1) );
+ }
+ }
+
+ //
+ // Reduce LengthToCopy if it is greater than our caller's length.
+ //
+
+ if (LengthToCopy > Length) {
+ LengthToCopy = Length;
+ }
+
+ //
+ // Copy the data to the user buffer.
+ //
+
+ try {
+
+ MmSetPageFaultReadAhead( Thread, PageCount - 1 );
+ RtlCopyBytes( Buffer,
+ (PVOID)((PCHAR)ActiveVacb->BaseAddress +
+ (FOffset.LowPart & (VACB_MAPPING_GRANULARITY - 1))),
+ LengthToCopy );
+
+ } except( CcCopyReadExceptionFilter( GetExceptionInformation(),
+ &Status ) ) {
+
+ MmResetPageFaultReadAhead( Thread, SavedState );
+
+ SetActiveVacb( SharedCacheMap, OldIrql, ActiveVacb, ActivePage, PageIsDirty );
+
+ //
+ // If we got an access violation, then the user buffer went
+ // away. Otherwise we must have gotten an I/O error trying
+ // to bring the data in.
+ //
+
+ if (Status == STATUS_ACCESS_VIOLATION) {
+ ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
+ }
+ else {
+ ExRaiseStatus( FsRtlNormalizeNtstatus( Status,
+ STATUS_UNEXPECTED_IO_ERROR ));
+ }
+ }
+
+ //
+ // Now adjust FOffset and Length by what we copied.
+ //
+
+ Buffer = (PVOID)((PCHAR)Buffer + LengthToCopy);
+ FOffset.QuadPart = FOffset.QuadPart + (LONGLONG)LengthToCopy;
+ Length -= LengthToCopy;
+
+ }
+
+ //
+ // If that was all the data, then remember the Vacb
+ //
+
+ if (Length == 0) {
+
+ SetActiveVacb( SharedCacheMap, OldIrql, ActiveVacb, ActivePage, PageIsDirty );
+
+ //
+ // Otherwise we must free it because we will map other vacbs below.
+ //
+
+ } else {
+
+ CcFreeActiveVacb( SharedCacheMap, ActiveVacb, ActivePage, PageIsDirty );
+ }
+ }
+
+ //
+ // Not all of the transfer will come back at once, so we have to loop
+ // until the entire transfer is complete.
+ //
+
+ while (Length != 0) {
+
+ ULONG ReceivedLength;
+ LARGE_INTEGER BeyondLastByte;
+
+ //
+ // Call local routine to Map or Access the file data, then move 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 (Wait) {
+
+ CacheBuffer = CcGetVirtualAddress( SharedCacheMap,
+ FOffset,
+ &Vacb,
+ &ReceivedLength );
+
+ BeyondLastByte.QuadPart = FOffset.QuadPart + (LONGLONG)ReceivedLength;
+
+ } else if (!CcPinFileData( FileObject,
+ &FOffset,
+ Length,
+ TRUE,
+ FALSE,
+ FALSE,
+ &Bcb,
+ &CacheBuffer,
+ &BeyondLastByte )) {
+
+ DebugTrace(-1, me, "CcCopyRead -> FALSE\n", 0 );
+
+ HOT_STATISTIC(CcCopyReadNoWaitMiss) += 1;
+
+ //
+ // Enable ReadAhead if we missed.
+ //
+
+ PrivateCacheMap->ReadAheadEnabled = TRUE;
+
+ return FALSE;
+
+ } else {
+
+ //
+ // Calculate how much data is described by Bcb starting at our desired
+ // file offset.
+ //
+
+ ReceivedLength = (ULONG)(BeyondLastByte.QuadPart - FOffset.QuadPart);
+ }
+
+ //
+ // If we got more than we need, make sure to only transfer
+ // the right amount.
+ //
+
+ if (ReceivedLength > Length) {
+ ReceivedLength = Length;
+ }
+
+ //
+ // It is possible for the user buffer to become no longer accessible
+ // since it was last checked by the I/O system. If we fail to access
+ // the buffer we must raise a status that the caller's exception
+ // filter considers as "expected". Also we unmap the Bcb here, since
+ // we otherwise would have no other reason to put a try-finally around
+ // this loop.
+ //
+
+ try {
+
+ ULONG PagesToGo = COMPUTE_PAGES_SPANNED( CacheBuffer,
+ ReceivedLength ) - 1;
+
+ //
+ // We know exactly how much we want to read here, and we do not
+ // want to read any more in case the caller is doing random access.
+ // Our read ahead logic takes care of detecting sequential reads,
+ // and tends to do large asynchronous read aheads. So far we have
+ // only mapped the data and we have not forced any in. What we
+ // do now is get into a loop where we copy a page at a time and
+ // just prior to each move, we tell MM how many additional pages
+ // we would like to have read in, in the event that we take a
+ // fault. With this strategy, for cache hits we never make a single
+ // expensive call to MM to guarantee that the data is in, yet if we
+ // do take a fault, we are guaranteed to only take one fault because
+ // we will read all of the data in for the rest of the transfer.
+ //
+ // We test first for the multiple page case, to keep the small
+ // reads faster.
+ //
+
+ if (PagesToGo != 0) {
+
+ ULONG MoveLength;
+ ULONG LengthToGo = ReceivedLength;
+
+ while (LengthToGo != 0) {
+
+ MoveLength = (PCHAR)(ROUND_TO_PAGES(((PCHAR)CacheBuffer + 1))) -
+ (PCHAR)CacheBuffer;
+
+ if (MoveLength > LengthToGo) {
+ MoveLength = LengthToGo;
+ }
+
+ //
+ // Here's hoping that it is cheaper to call Mm to see if
+ // the page is valid. If not let Mm know how many pages
+ // we are after before doing the move.
+ //
+
+ MmSetPageFaultReadAhead( Thread, PagesToGo );
+ GotAMiss = (BOOLEAN)!MmCheckCachedPageState( CacheBuffer, FALSE );
+
+ RtlCopyBytes( Buffer, CacheBuffer, MoveLength );
+
+ PagesToGo -= 1;
+
+ LengthToGo -= MoveLength;
+ Buffer = (PCHAR)Buffer + MoveLength;
+ CacheBuffer = (PCHAR)CacheBuffer + MoveLength;
+ }
+
+ //
+ // Handle the read here that stays on a single page.
+ //
+
+ } else {
+
+ //
+ // Here's hoping that it is cheaper to call Mm to see if
+ // the page is valid. If not let Mm know how many pages
+ // we are after before doing the move.
+ //
+
+ MmSetPageFaultReadAhead( Thread, 0 );
+ GotAMiss = (BOOLEAN)!MmCheckCachedPageState( CacheBuffer, FALSE );
+
+ RtlCopyBytes( Buffer, CacheBuffer, ReceivedLength );
+
+ Buffer = (PCHAR)Buffer + ReceivedLength;
+ }
+
+ }
+ except( CcCopyReadExceptionFilter( GetExceptionInformation(),
+ &Status ) ) {
+
+ CcMissCounter = &CcThrowAway;
+
+ //
+ // If we get an exception, then we have to renable page fault
+ // clustering and unmap on the way out.
+ //
+
+ MmResetPageFaultReadAhead( Thread, SavedState );
+
+
+ if (Wait) {
+ CcFreeVirtualAddress( Vacb );
+ } else {
+ CcUnpinFileData( Bcb, TRUE, UNPIN );
+ }
+
+ //
+ // If we got an access violation, then the user buffer went
+ // away. Otherwise we must have gotten an I/O error trying
+ // to bring the data in.
+ //
+
+ if (Status == STATUS_ACCESS_VIOLATION) {
+ ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
+ }
+ else {
+ ExRaiseStatus( FsRtlNormalizeNtstatus( Status,
+ STATUS_UNEXPECTED_IO_ERROR ));
+ }
+ }
+
+ //
+ // Update number of bytes transferred.
+ //
+
+ Length -= ReceivedLength;
+
+ //
+ // Unmap the data now, and calculate length left to transfer.
+ //
+
+ if (Wait) {
+
+ //
+ // If there is more to go, just free this vacb.
+ //
+
+ if (Length != 0) {
+
+ CcFreeVirtualAddress( Vacb );
+
+ //
+ // Otherwise save it for the next time through.
+ //
+
+ } else {
+
+ SetActiveVacb( SharedCacheMap, OldIrql, Vacb, (ULONG)(FOffset.QuadPart >> PAGE_SHIFT), 0 );
+ break;
+ }
+
+ } else {
+ CcUnpinFileData( Bcb, TRUE, UNPIN );
+ }
+
+ //
+ // Assume we did not get all the data we wanted, and set FOffset
+ // to the end of the returned data.
+ //
+
+ FOffset = BeyondLastByte;
+ }
+
+ MmResetPageFaultReadAhead( Thread, SavedState );
+
+ CcMissCounter = &CcThrowAway;
+
+ //
+ // Now enable read ahead if it looks like we got any misses, and do
+ // the first one.
+ //
+
+ if (GotAMiss && !PrivateCacheMap->ReadAheadEnabled) {
+
+ PrivateCacheMap->ReadAheadEnabled = TRUE;
+ CcScheduleReadAhead( FileObject, FileOffset, OriginalLength );
+ }
+
+ //
+ // Now that we have described our desired read ahead, let's
+ // shift the read history down.
+ //
+
+ PrivateCacheMap->FileOffset1 = PrivateCacheMap->FileOffset2;
+ PrivateCacheMap->BeyondLastByte1 = PrivateCacheMap->BeyondLastByte2;
+ PrivateCacheMap->FileOffset2 = *FileOffset;
+ PrivateCacheMap->BeyondLastByte2.QuadPart =
+ FileOffset->QuadPart + (LONGLONG)OriginalLength;
+
+ IoStatus->Status = STATUS_SUCCESS;
+ IoStatus->Information = OriginalLength;
+
+ DebugTrace(-1, me, "CcCopyRead -> TRUE\n", 0 );
+
+ return TRUE;
+}
+
+
+VOID
+CcFastCopyRead (
+ IN PFILE_OBJECT FileObject,
+ IN ULONG FileOffset,
+ IN ULONG Length,
+ IN ULONG PageCount,
+ OUT PVOID Buffer,
+ OUT PIO_STATUS_BLOCK IoStatus
+ )
+
+/*++
+
+Routine Description:
+
+ This routine attempts to copy the specified file data from the cache
+ into the output buffer, and deliver the correct I/O status.
+
+ This is a faster version of CcCopyRead which only supports 32-bit file
+ offsets and synchronicity (Wait = TRUE).
+
+Arguments:
+
+ FileObject - Pointer to the file object for a file which was
+ opened with NO_INTERMEDIATE_BUFFERING clear, i.e., for
+ which CcInitializeCacheMap was called by the file system.
+
+ FileOffset - Byte offset in file for desired data.
+
+ Length - Length of desired data in bytes.
+
+ PageCount - Number of pages spanned by the read.
+
+ Buffer - Pointer to output buffer to which data should be copied.
+
+ IoStatus - Pointer to standard I/O status block to receive the status
+ for the transfer. (STATUS_SUCCESS guaranteed for cache
+ hits, otherwise the actual I/O status is returned.)
+
+ Note that even if FALSE is returned, the IoStatus.Information
+ field will return the count of any bytes successfully
+ transferred before a blocking condition occured. The caller
+ may either choose to ignore this information, or resume
+ the copy later accounting for bytes transferred.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PSHARED_CACHE_MAP SharedCacheMap;
+ PPRIVATE_CACHE_MAP PrivateCacheMap;
+ PVOID CacheBuffer;
+ LARGE_INTEGER FOffset;
+ PVACB Vacb;
+ PVACB ActiveVacb;
+ ULONG ActivePage;
+ ULONG PageIsDirty;
+ ULONG SavedState;
+ KIRQL OldIrql;
+ NTSTATUS Status;
+ LARGE_INTEGER OriginalOffset;
+ ULONG OriginalLength = Length;
+ PETHREAD Thread = PsGetCurrentThread();
+ BOOLEAN GotAMiss = FALSE;
+
+ DebugTrace(+1, me, "CcFastCopyRead\n", 0 );
+
+ MmSavePageFaultReadAhead( Thread, &SavedState );
+
+ //
+ // Get pointer to shared and private cache maps
+ //
+
+ SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap;
+ PrivateCacheMap = FileObject->PrivateCacheMap;
+
+ //
+ // Check for read past file size, the caller must filter this case out.
+ //
+
+ ASSERT( (FileOffset + Length) <= SharedCacheMap->FileSize.LowPart );
+
+ //
+ // If read ahead is enabled, then do the read ahead here so it
+ // overlaps with the copy (otherwise we will do it below).
+ // Note that we are assuming that we will not get ahead of our
+ // current transfer - if read ahead is working it should either
+ // already be in memory or else underway.
+ //
+
+ OriginalOffset.LowPart = FileOffset;
+ OriginalOffset.HighPart = 0;
+
+ if (PrivateCacheMap->ReadAheadEnabled && (PrivateCacheMap->ReadAheadLength[1] == 0)) {
+ CcScheduleReadAhead( FileObject, &OriginalOffset, Length );
+ }
+
+ //
+ // This is not an exact solution, but when IoPageRead gets a miss,
+ // it cannot tell whether it was CcCopyRead or CcMdlRead, but since
+ // the miss should occur very soon, by loading the pointer here
+ // probably the right counter will get incremented, and in any case,
+ // we hope the errrors average out!
+ //
+
+ CcMissCounter = &CcCopyReadWaitMiss;
+
+ //
+ // Increment performance counters
+ //
+
+ HOT_STATISTIC(CcCopyReadWait) += 1;
+
+ //
+ // See if we have an active Vacb, that we can just copy to.
+ //
+
+ GetActiveVacb( SharedCacheMap, OldIrql, ActiveVacb, ActivePage, PageIsDirty );
+
+ if (ActiveVacb != NULL) {
+
+ if ((FileOffset >> VACB_OFFSET_SHIFT) == (ActivePage >> (VACB_OFFSET_SHIFT - PAGE_SHIFT))) {
+
+ ULONG LengthToCopy = VACB_MAPPING_GRANULARITY - (FileOffset & (VACB_MAPPING_GRANULARITY - 1));
+
+ if (SharedCacheMap->NeedToZero != NULL) {
+
+ PVOID NeedToZero;
+
+ ExAcquireFastLock( &SharedCacheMap->ActiveVacbSpinLock, &OldIrql );
+
+ //
+ // Note that the NeedToZero could be cleared, since we
+ // tested it without the spinlock.
+ //
+
+ NeedToZero = SharedCacheMap->NeedToZero;
+ if (NeedToZero != NULL) {
+
+ RtlZeroMemory( NeedToZero, PAGE_SIZE - ((((ULONG)NeedToZero - 1) & (PAGE_SIZE - 1)) + 1) );
+ SharedCacheMap->NeedToZero = NULL;
+ }
+
+ ExReleaseFastLock( &SharedCacheMap->ActiveVacbSpinLock, OldIrql );
+
+ if (NeedToZero != NULL) {
+ MmUnlockCachedPage( (PVOID)((PCHAR)NeedToZero - 1) );
+ }
+ }
+
+ //
+ // Reduce LengthToCopy if it is greater than our caller's length.
+ //
+
+ if (LengthToCopy > Length) {
+ LengthToCopy = Length;
+ }
+
+ //
+ // Copy the data to the user buffer.
+ //
+
+ try {
+
+ MmSetPageFaultReadAhead( Thread, PageCount - 1 );
+ RtlCopyBytes( Buffer,
+ (PVOID)((PCHAR)ActiveVacb->BaseAddress +
+ (FileOffset & (VACB_MAPPING_GRANULARITY - 1))),
+ LengthToCopy );
+
+ } except( CcCopyReadExceptionFilter( GetExceptionInformation(),
+ &Status ) ) {
+
+ MmResetPageFaultReadAhead( Thread, SavedState );
+
+
+ SetActiveVacb( SharedCacheMap, OldIrql, ActiveVacb, ActivePage, PageIsDirty );
+
+ //
+ // If we got an access violation, then the user buffer went
+ // away. Otherwise we must have gotten an I/O error trying
+ // to bring the data in.
+ //
+
+ if (Status == STATUS_ACCESS_VIOLATION) {
+ ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
+ }
+ else {
+ ExRaiseStatus( FsRtlNormalizeNtstatus( Status,
+ STATUS_UNEXPECTED_IO_ERROR ));
+ }
+ }
+
+ //
+ // Now adjust FileOffset and Length by what we copied.
+ //
+
+ Buffer = (PVOID)((PCHAR)Buffer + LengthToCopy);
+ FileOffset += LengthToCopy;
+ Length -= LengthToCopy;
+ }
+
+ //
+ // If that was all the data, then remember the Vacb
+ //
+
+ if (Length == 0) {
+
+ SetActiveVacb( SharedCacheMap, OldIrql, ActiveVacb, ActivePage, PageIsDirty );
+
+ //
+ // Otherwise we must free it because we will map other vacbs below.
+ //
+
+ } else {
+
+ CcFreeActiveVacb( SharedCacheMap, ActiveVacb, ActivePage, PageIsDirty );
+ }
+ }
+
+ //
+ // Not all of the transfer will come back at once, so we have to loop
+ // until the entire transfer is complete.
+ //
+
+ FOffset.HighPart = 0;
+ FOffset.LowPart = FileOffset;
+
+ while (Length != 0) {
+
+ ULONG ReceivedLength;
+ ULONG BeyondLastByte;
+
+ //
+ // Call local routine to Map or Access the file data, then move 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.
+ //
+
+ CacheBuffer = CcGetVirtualAddress( SharedCacheMap,
+ FOffset,
+ &Vacb,
+ &ReceivedLength );
+
+ BeyondLastByte = FOffset.LowPart + ReceivedLength;
+
+ //
+ // If we got more than we need, make sure to only transfer
+ // the right amount.
+ //
+
+ if (ReceivedLength > Length) {
+ ReceivedLength = Length;
+ }
+
+ //
+ // It is possible for the user buffer to become no longer accessible
+ // since it was last checked by the I/O system. If we fail to access
+ // the buffer we must raise a status that the caller's exception
+ // filter considers as "expected". Also we unmap the Bcb here, since
+ // we otherwise would have no other reason to put a try-finally around
+ // this loop.
+ //
+
+ try {
+
+ ULONG PagesToGo = COMPUTE_PAGES_SPANNED( CacheBuffer,
+ ReceivedLength ) - 1;
+
+ //
+ // We know exactly how much we want to read here, and we do not
+ // want to read any more in case the caller is doing random access.
+ // Our read ahead logic takes care of detecting sequential reads,
+ // and tends to do large asynchronous read aheads. So far we have
+ // only mapped the data and we have not forced any in. What we
+ // do now is get into a loop where we copy a page at a time and
+ // just prior to each move, we tell MM how many additional pages
+ // we would like to have read in, in the event that we take a
+ // fault. With this strategy, for cache hits we never make a single
+ // expensive call to MM to guarantee that the data is in, yet if we
+ // do take a fault, we are guaranteed to only take one fault because
+ // we will read all of the data in for the rest of the transfer.
+ //
+ // We test first for the multiple page case, to keep the small
+ // reads faster.
+ //
+
+ if (PagesToGo != 0) {
+
+ ULONG MoveLength;
+ ULONG LengthToGo = ReceivedLength;
+
+ while (LengthToGo != 0) {
+
+ MoveLength = (PCHAR)(ROUND_TO_PAGES(((PCHAR)CacheBuffer + 1))) -
+ (PCHAR)CacheBuffer;
+
+ if (MoveLength > LengthToGo) {
+ MoveLength = LengthToGo;
+ }
+
+ //
+ // Here's hoping that it is cheaper to call Mm to see if
+ // the page is valid. If not let Mm know how many pages
+ // we are after before doing the move.
+ //
+
+ MmSetPageFaultReadAhead( Thread, PagesToGo );
+ GotAMiss = (BOOLEAN)!MmCheckCachedPageState( CacheBuffer, FALSE );
+
+ RtlCopyBytes( Buffer, CacheBuffer, MoveLength );
+
+ PagesToGo -= 1;
+
+ LengthToGo -= MoveLength;
+ Buffer = (PCHAR)Buffer + MoveLength;
+ CacheBuffer = (PCHAR)CacheBuffer + MoveLength;
+ }
+
+ //
+ // Handle the read here that stays on a single page.
+ //
+
+ } else {
+
+ //
+ // Here's hoping that it is cheaper to call Mm to see if
+ // the page is valid. If not let Mm know how many pages
+ // we are after before doing the move.
+ //
+
+ MmSetPageFaultReadAhead( Thread, 0 );
+ GotAMiss = (BOOLEAN)!MmCheckCachedPageState( CacheBuffer, FALSE );
+
+ RtlCopyBytes( Buffer, CacheBuffer, ReceivedLength );
+
+ Buffer = (PCHAR)Buffer + ReceivedLength;
+ }
+ }
+ except( CcCopyReadExceptionFilter( GetExceptionInformation(),
+ &Status ) ) {
+
+ CcMissCounter = &CcThrowAway;
+
+ //
+ // If we get an exception, then we have to renable page fault
+ // clustering and unmap on the way out.
+ //
+
+ MmResetPageFaultReadAhead( Thread, SavedState );
+
+
+ CcFreeVirtualAddress( Vacb );
+
+ //
+ // If we got an access violation, then the user buffer went
+ // away. Otherwise we must have gotten an I/O error trying
+ // to bring the data in.
+ //
+
+ if (Status == STATUS_ACCESS_VIOLATION) {
+ ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
+ }
+ else {
+ ExRaiseStatus( FsRtlNormalizeNtstatus( Status,
+ STATUS_UNEXPECTED_IO_ERROR ));
+ }
+ }
+
+ //
+ // Update number of bytes transferred.
+ //
+
+ Length -= ReceivedLength;
+
+ //
+ // Unmap the data now, and calculate length left to transfer.
+ //
+
+ if (Length != 0) {
+
+ //
+ // If there is more to go, just free this vacb.
+ //
+
+ CcFreeVirtualAddress( Vacb );
+
+ } else {
+
+ //
+ // Otherwise save it for the next time through.
+ //
+
+ SetActiveVacb( SharedCacheMap, OldIrql, Vacb, (FOffset.LowPart >> PAGE_SHIFT), 0 );
+ break;
+ }
+
+ //
+ // Assume we did not get all the data we wanted, and set FOffset
+ // to the end of the returned data.
+ //
+
+ FOffset.LowPart = BeyondLastByte;
+ }
+
+ MmResetPageFaultReadAhead( Thread, SavedState );
+
+ CcMissCounter = &CcThrowAway;
+
+ //
+ // Now enable read ahead if it looks like we got any misses, and do
+ // the first one.
+ //
+
+ if (GotAMiss && !PrivateCacheMap->ReadAheadEnabled) {
+
+ PrivateCacheMap->ReadAheadEnabled = TRUE;
+ CcScheduleReadAhead( FileObject, &OriginalOffset, OriginalLength );
+ }
+
+ //
+ // Now that we have described our desired read ahead, let's
+ // shift the read history down.
+ //
+
+ PrivateCacheMap->FileOffset1.LowPart = PrivateCacheMap->FileOffset2.LowPart;
+ PrivateCacheMap->BeyondLastByte1.LowPart = PrivateCacheMap->BeyondLastByte2.LowPart;
+ PrivateCacheMap->FileOffset2.LowPart = OriginalOffset.LowPart;
+ PrivateCacheMap->BeyondLastByte2.LowPart = OriginalOffset.LowPart + OriginalLength;
+
+ IoStatus->Status = STATUS_SUCCESS;
+ IoStatus->Information = OriginalLength;
+
+ DebugTrace(-1, me, "CcFastCopyRead -> VOID\n", 0 );
+}
+
+
+BOOLEAN
+CcCopyWrite (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN ULONG Length,
+ IN BOOLEAN Wait,
+ IN PVOID Buffer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine attempts to copy the specified file data from the specified
+ buffer into the Cache, and deliver the correct I/O status. It is *not*
+ safe to call this routine from Dpc level.
+
+ 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 receive all of the requested data without
+ blocking, then this routine will return FALSE. However, if the
+ correct space is immediately accessible in the cache and no blocking is
+ required, this routine copies the data and returns TRUE.
+
+ If the caller supplies Wait as TRUE, then this routine is guaranteed
+ to copy 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 or Server Fsp threads should supply Wait = TRUE.
+
+Arguments:
+
+ FileObject - Pointer to the file object for a file which was
+ opened with NO_INTERMEDIATE_BUFFERING clear, i.e., for
+ which CcInitializeCacheMap was called by the file system.
+
+ FileOffset - Byte offset in file to receive the data.
+
+ Length - Length of data in bytes.
+
+ Wait - FALSE if caller may not block, TRUE otherwise (see description
+ above)
+
+ Buffer - Pointer to input buffer from which data should be copied.
+
+Return Value:
+
+ FALSE - if Wait was supplied as FALSE and the data was not copied.
+
+ TRUE - if the data has been copied.
+
+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;
+ PVACB ActiveVacb;
+ ULONG ActivePage;
+ PVOID ActiveAddress;
+ ULONG PageIsDirty;
+ KIRQL OldIrql;
+ NTSTATUS Status;
+ PVOID CacheBuffer;
+ LARGE_INTEGER FOffset;
+ PBCB Bcb;
+ ULONG ZeroFlags;
+ LARGE_INTEGER Temp;
+
+ DebugTrace(+1, me, "CcCopyWrite\n", 0 );
+
+ //
+ // If the caller specified Wait == FALSE, but the FileObject is WriteThrough,
+ // then we need to just get out.
+ //
+
+ if ((FileObject->Flags & FO_WRITE_THROUGH) && !Wait) {
+
+ DebugTrace(-1, me, "CcCopyWrite->FALSE (WriteThrough && !Wait)\n", 0 );
+
+ return FALSE;
+ }
+
+ //
+ // Get pointer to shared cache map
+ //
+
+ SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap;
+ FOffset = *FileOffset;
+
+ //
+ // See if we have an active Vacb, that we can just copy to.
+ //
+
+ GetActiveVacb( SharedCacheMap, OldIrql, ActiveVacb, ActivePage, PageIsDirty );
+
+ if (ActiveVacb != NULL) {
+
+ //
+ // See if the request starts in the ActivePage. WriteThrough requests must
+ // go the longer route through CcMapAndCopy, where WriteThrough flushes are
+ // implemented.
+ //
+
+ if (((ULONG)(FOffset.QuadPart >> PAGE_SHIFT) == ActivePage) && (Length != 0) &&
+ !FlagOn( FileObject->Flags, FO_WRITE_THROUGH )) {
+
+ ULONG LengthToCopy = PAGE_SIZE - (FOffset.LowPart & (PAGE_SIZE - 1));
+
+ //
+ // Reduce LengthToCopy if it is greater than our caller's length.
+ //
+
+ if (LengthToCopy > Length) {
+ LengthToCopy = Length;
+ }
+
+ //
+ // Copy the data to the user buffer.
+ //
+
+ try {
+
+ //
+ // If we are copying to a page that is locked down, then
+ // we have to do it under our spinlock, and update the
+ // NeedToZero field.
+ //
+
+ OldIrql = 0xFF;
+
+ CacheBuffer = (PVOID)((PCHAR)ActiveVacb->BaseAddress +
+ (FOffset.LowPart & (VACB_MAPPING_GRANULARITY - 1)));
+
+ if (SharedCacheMap->NeedToZero != NULL) {
+
+ //
+ // The FastLock may not write our "flag".
+ //
+
+ OldIrql = 0;
+
+ ExAcquireFastLock( &SharedCacheMap->ActiveVacbSpinLock, &OldIrql );
+
+ //
+ // Note that the NeedToZero could be cleared, since we
+ // tested it without the spinlock.
+ //
+
+ ActiveAddress = SharedCacheMap->NeedToZero;
+ if ((ActiveAddress != NULL) &&
+ (((PCHAR)CacheBuffer + LengthToCopy) > (PCHAR)ActiveAddress)) {
+
+ //
+ // If we are skipping some bytes in the page, then we need
+ // to zero them.
+ //
+
+ if ((PCHAR)CacheBuffer > (PCHAR)ActiveAddress) {
+
+ RtlZeroMemory( ActiveAddress, (PCHAR)CacheBuffer - (PCHAR)ActiveAddress );
+ }
+ SharedCacheMap->NeedToZero = (PVOID)((PCHAR)CacheBuffer + LengthToCopy);
+ }
+
+ ExReleaseFastLock( &SharedCacheMap->ActiveVacbSpinLock, OldIrql );
+ }
+
+ RtlCopyBytes( CacheBuffer, Buffer, LengthToCopy );
+
+ } except( CcCopyReadExceptionFilter( GetExceptionInformation(),
+ &Status ) ) {
+
+ //
+ // If we failed to overwrite the uninitialized data,
+ // zero it now (we cannot safely restore NeedToZero).
+ //
+
+ if (OldIrql != 0xFF) {
+ RtlZeroBytes( CacheBuffer, LengthToCopy );
+ }
+
+ SetActiveVacb( SharedCacheMap, OldIrql, ActiveVacb, ActivePage, ACTIVE_PAGE_IS_DIRTY );
+
+ //
+ // If we got an access violation, then the user buffer went
+ // away. Otherwise we must have gotten an I/O error trying
+ // to bring the data in.
+ //
+
+ if (Status == STATUS_ACCESS_VIOLATION) {
+ ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
+ }
+ else {
+ ExRaiseStatus( FsRtlNormalizeNtstatus( Status,
+ STATUS_UNEXPECTED_IO_ERROR ));
+ }
+ }
+
+ //
+ // Now adjust FOffset and Length by what we copied.
+ //
+
+ Buffer = (PVOID)((PCHAR)Buffer + LengthToCopy);
+ FOffset.QuadPart = FOffset.QuadPart + (LONGLONG)LengthToCopy;
+ Length -= LengthToCopy;
+
+ //
+ // If that was all the data, then get outski...
+ //
+
+ if (Length == 0) {
+
+ SetActiveVacb( SharedCacheMap, OldIrql, ActiveVacb, ActivePage, ACTIVE_PAGE_IS_DIRTY );
+ return TRUE;
+ }
+
+ //
+ // Remember that the page is dirty now.
+ //
+
+ PageIsDirty |= ACTIVE_PAGE_IS_DIRTY;
+ }
+
+ CcFreeActiveVacb( SharedCacheMap, ActiveVacb, ActivePage, PageIsDirty );
+
+ //
+ // Else someone else could have the active page, and may want to zero
+ // the range we plan to write!
+ //
+
+ } else if (SharedCacheMap->NeedToZero != NULL) {
+
+ CcFreeActiveVacb( SharedCacheMap, NULL, 0, FALSE );
+ }
+
+ //
+ // At this point we can calculate the ZeroFlags.
+ //
+
+ //
+ // We can always zero middle pages, if any.
+ //
+
+ ZeroFlags = ZERO_MIDDLE_PAGES;
+
+ if (((FOffset.LowPart & (PAGE_SIZE - 1)) == 0) &&
+ (Length >= PAGE_SIZE)) {
+ ZeroFlags |= ZERO_FIRST_PAGE;
+ }
+
+ if (((FOffset.LowPart + Length) & (PAGE_SIZE - 1)) == 0) {
+ ZeroFlags |= ZERO_LAST_PAGE;
+ }
+
+ Temp = FOffset;
+ Temp.LowPart &= ~(PAGE_SIZE -1);
+ Temp.QuadPart = ((PFSRTL_COMMON_FCB_HEADER)FileObject->FsContext)->ValidDataLength.QuadPart -
+ Temp.QuadPart;
+
+ if (Temp.QuadPart <= 0) {
+ ZeroFlags |= ZERO_FIRST_PAGE | ZERO_MIDDLE_PAGES | ZERO_LAST_PAGE;
+ } else if ((Temp.HighPart == 0) && (Temp.LowPart <= PAGE_SIZE)) {
+ ZeroFlags |= ZERO_MIDDLE_PAGES | ZERO_LAST_PAGE;
+ }
+
+ //
+ // Call a routine to map and copy the data in Mm and get out.
+ //
+
+ if (Wait) {
+
+ CcMapAndCopy( SharedCacheMap,
+ Buffer,
+ &FOffset,
+ Length,
+ ZeroFlags,
+ BooleanFlagOn( FileObject->Flags, FO_WRITE_THROUGH ));
+
+ return TRUE;
+ }
+
+ //
+ // The rest of this routine is the Wait == FALSE case.
+ //
+ // Not all of the transfer will come back at once, so we have to loop
+ // until the entire transfer is complete.
+ //
+
+ while (Length != 0) {
+
+ ULONG ReceivedLength;
+ LARGE_INTEGER BeyondLastByte;
+
+ if (!CcPinFileData( FileObject,
+ &FOffset,
+ Length,
+ FALSE,
+ TRUE,
+ FALSE,
+ &Bcb,
+ &CacheBuffer,
+ &BeyondLastByte )) {
+
+ DebugTrace(-1, me, "CcCopyWrite -> FALSE\n", 0 );
+
+ return FALSE;
+
+ } else {
+
+ //
+ // Calculate how much data is described by Bcb starting at our desired
+ // file offset.
+ //
+
+ ReceivedLength = (ULONG)(BeyondLastByte.QuadPart - FOffset.QuadPart);
+
+ //
+ // If we got more than we need, make sure to only transfer
+ // the right amount.
+ //
+
+ if (ReceivedLength > Length) {
+ ReceivedLength = Length;
+ }
+ }
+
+ //
+ // It is possible for the user buffer to become no longer accessible
+ // since it was last checked by the I/O system. If we fail to access
+ // the buffer we must raise a status that the caller's exception
+ // filter considers as "expected". Also we unmap the Bcb here, since
+ // we otherwise would have no other reason to put a try-finally around
+ // this loop.
+ //
+
+ try {
+
+ RtlCopyBytes( CacheBuffer, Buffer, ReceivedLength );
+
+ CcSetDirtyPinnedData( Bcb, NULL );
+ CcUnpinFileData( Bcb, FALSE, UNPIN );
+ }
+ except( CcCopyReadExceptionFilter( GetExceptionInformation(),
+ &Status ) ) {
+
+ CcUnpinFileData( Bcb, TRUE, UNPIN );
+
+ //
+ // If we got an access violation, then the user buffer went
+ // away. Otherwise we must have gotten an I/O error trying
+ // to bring the data in.
+ //
+
+ if (Status == STATUS_ACCESS_VIOLATION) {
+ ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
+ }
+ else {
+
+ ExRaiseStatus(FsRtlNormalizeNtstatus( Status, STATUS_UNEXPECTED_IO_ERROR ));
+ }
+ }
+
+ //
+ // Assume we did not get all the data we wanted, and set FOffset
+ // to the end of the returned data and adjust the Buffer and Length.
+ //
+
+ FOffset = BeyondLastByte;
+ Buffer = (PCHAR)Buffer + ReceivedLength;
+ Length -= ReceivedLength;
+ }
+
+ DebugTrace(-1, me, "CcCopyWrite -> TRUE\n", 0 );
+
+ return TRUE;
+}
+
+
+VOID
+CcFastCopyWrite (
+ IN PFILE_OBJECT FileObject,
+ IN ULONG FileOffset,
+ IN ULONG Length,
+ IN PVOID Buffer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine attempts to copy the specified file data from the specified
+ buffer into the Cache, and deliver the correct I/O status.
+
+ This is a faster version of CcCopyWrite which only supports 32-bit file
+ offsets and synchronicity (Wait = TRUE) and no Write Through.
+
+Arguments:
+
+ FileObject - Pointer to the file object for a file which was
+ opened with NO_INTERMEDIATE_BUFFERING clear, i.e., for
+ which CcInitializeCacheMap was called by the file system.
+
+ FileOffset - Byte offset in file to receive the data.
+
+ Length - Length of data in bytes.
+
+ Buffer - Pointer to input buffer from which data should be copied.
+
+Return Value:
+
+ None
+
+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;
+ PVACB ActiveVacb;
+ ULONG ActivePage;
+ PVOID ActiveAddress;
+ ULONG PageIsDirty;
+ KIRQL OldIrql;
+ NTSTATUS Status;
+ ULONG ZeroFlags;
+ ULONG ValidDataLength;
+ LARGE_INTEGER FOffset;
+
+ DebugTrace(+1, me, "CcFastCopyWrite\n", 0 );
+
+ //
+ // Get pointer to shared cache map and a copy of valid data length
+ //
+
+ SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap;
+
+ //
+ // See if we have an active Vacb, that we can just copy to.
+ //
+
+ GetActiveVacb( SharedCacheMap, OldIrql, ActiveVacb, ActivePage, PageIsDirty );
+
+ if (ActiveVacb != NULL) {
+
+ //
+ // See if the request starts in the ActivePage. WriteThrough requests must
+ // go the longer route through CcMapAndCopy, where WriteThrough flushes are
+ // implemented.
+ //
+
+ if (((FileOffset >> PAGE_SHIFT) == ActivePage) && (Length != 0) &&
+ !FlagOn( FileObject->Flags, FO_WRITE_THROUGH )) {
+
+ ULONG LengthToCopy = PAGE_SIZE - (FileOffset & (PAGE_SIZE - 1));
+
+ //
+ // Reduce LengthToCopy if it is greater than our caller's length.
+ //
+
+ if (LengthToCopy > Length) {
+ LengthToCopy = Length;
+ }
+
+ //
+ // Copy the data to the user buffer.
+ //
+
+ try {
+
+ //
+ // If we are copying to a page that is locked down, then
+ // we have to do it under our spinlock, and update the
+ // NeedToZero field.
+ //
+
+ OldIrql = 0xFF;
+
+ CacheBuffer = (PVOID)((PCHAR)ActiveVacb->BaseAddress +
+ (FileOffset & (VACB_MAPPING_GRANULARITY - 1)));
+
+ if (SharedCacheMap->NeedToZero != NULL) {
+
+ //
+ // The FastLock may not write our "flag".
+ //
+
+ OldIrql = 0;
+
+ ExAcquireFastLock( &SharedCacheMap->ActiveVacbSpinLock, &OldIrql );
+
+ //
+ // Note that the NeedToZero could be cleared, since we
+ // tested it without the spinlock.
+ //
+
+ ActiveAddress = SharedCacheMap->NeedToZero;
+ if ((ActiveAddress != NULL) &&
+ (((PCHAR)CacheBuffer + LengthToCopy) > (PCHAR)ActiveAddress)) {
+
+ //
+ // If we are skipping some bytes in the page, then we need
+ // to zero them.
+ //
+
+ if ((PCHAR)CacheBuffer > (PCHAR)ActiveAddress) {
+
+ RtlZeroMemory( ActiveAddress, (PCHAR)CacheBuffer - (PCHAR)ActiveAddress );
+ }
+ SharedCacheMap->NeedToZero = (PVOID)((PCHAR)CacheBuffer + LengthToCopy);
+ }
+
+ ExReleaseFastLock( &SharedCacheMap->ActiveVacbSpinLock, OldIrql );
+ }
+
+ RtlCopyBytes( CacheBuffer, Buffer, LengthToCopy );
+
+ } except( CcCopyReadExceptionFilter( GetExceptionInformation(),
+ &Status ) ) {
+
+ //
+ // If we failed to overwrite the uninitialized data,
+ // zero it now (we cannot safely restore NeedToZero).
+ //
+
+ if (OldIrql != 0xFF) {
+ RtlZeroBytes( CacheBuffer, LengthToCopy );
+ }
+
+ SetActiveVacb( SharedCacheMap, OldIrql, ActiveVacb, ActivePage, ACTIVE_PAGE_IS_DIRTY );
+
+ //
+ // If we got an access violation, then the user buffer went
+ // away. Otherwise we must have gotten an I/O error trying
+ // to bring the data in.
+ //
+
+ if (Status == STATUS_ACCESS_VIOLATION) {
+ ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
+ }
+ else {
+ ExRaiseStatus( FsRtlNormalizeNtstatus( Status,
+ STATUS_UNEXPECTED_IO_ERROR ));
+ }
+ }
+
+ //
+ // Now adjust FileOffset and Length by what we copied.
+ //
+
+ Buffer = (PVOID)((PCHAR)Buffer + LengthToCopy);
+ FileOffset += LengthToCopy;
+ Length -= LengthToCopy;
+
+ //
+ // If that was all the data, then get outski...
+ //
+
+ if (Length == 0) {
+
+ SetActiveVacb( SharedCacheMap, OldIrql, ActiveVacb, ActivePage, ACTIVE_PAGE_IS_DIRTY );
+ return;
+ }
+
+ //
+ // Remember that the page is dirty now.
+ //
+
+ PageIsDirty |= ACTIVE_PAGE_IS_DIRTY;
+ }
+
+ CcFreeActiveVacb( SharedCacheMap, ActiveVacb, ActivePage, PageIsDirty );
+
+ //
+ // Else someone else could have the active page, and may want to zero
+ // the range we plan to write!
+ //
+
+ } else if (SharedCacheMap->NeedToZero != NULL) {
+
+ CcFreeActiveVacb( SharedCacheMap, NULL, 0, FALSE );
+ }
+
+ //
+ // Set up for call to CcMapAndCopy
+ //
+
+ FOffset.LowPart = FileOffset;
+ FOffset.HighPart = 0;
+
+ ValidDataLength = ((PFSRTL_COMMON_FCB_HEADER)FileObject->FsContext)->ValidDataLength.LowPart;
+
+ ASSERT((ValidDataLength == MAXULONG) ||
+ (((PFSRTL_COMMON_FCB_HEADER)FileObject->FsContext)->ValidDataLength.HighPart == 0));
+
+ //
+ // At this point we can calculate the ReadOnly flag for
+ // the purposes of whether to use the Bcb resource, and
+ // we can calculate the ZeroFlags.
+ //
+
+ //
+ // We can always zero middle pages, if any.
+ //
+
+ ZeroFlags = ZERO_MIDDLE_PAGES;
+
+ if (((FileOffset & (PAGE_SIZE - 1)) == 0) &&
+ (Length >= PAGE_SIZE)) {
+ ZeroFlags |= ZERO_FIRST_PAGE;
+ }
+
+ if (((FileOffset + Length) & (PAGE_SIZE - 1)) == 0) {
+ ZeroFlags |= ZERO_LAST_PAGE;
+ }
+
+ if ((FileOffset & ~(PAGE_SIZE - 1)) >= ValidDataLength) {
+ ZeroFlags |= ZERO_FIRST_PAGE | ZERO_MIDDLE_PAGES | ZERO_LAST_PAGE;
+ } else if (((FileOffset & ~(PAGE_SIZE - 1)) + PAGE_SIZE) >= ValidDataLength) {
+ ZeroFlags |= ZERO_MIDDLE_PAGES | ZERO_LAST_PAGE;
+ }
+
+ //
+ // Call a routine to map and copy the data in Mm and get out.
+ //
+
+ CcMapAndCopy( SharedCacheMap,
+ Buffer,
+ &FOffset,
+ Length,
+ ZeroFlags,
+ BooleanFlagOn( FileObject->Flags, FO_WRITE_THROUGH ));
+
+ DebugTrace(-1, me, "CcFastCopyWrite -> VOID\n", 0 );
+}
+
+
+LONG
+CcCopyReadExceptionFilter(
+ IN PEXCEPTION_POINTERS ExceptionPointer,
+ IN PNTSTATUS ExceptionCode
+ )
+
+/*++
+
+Routine Description:
+
+ This routine serves as a exception filter and has the special job of
+ extracting the "real" I/O error when Mm raises STATUS_IN_PAGE_ERROR
+ beneath us.
+
+Arguments:
+
+ ExceptionPointer - A pointer to the exception record that contains
+ the real Io Status.
+
+ ExceptionCode - A pointer to an NTSTATUS that is to receive the real
+ status.
+
+Return Value:
+
+ EXCEPTION_EXECUTE_HANDLER
+
+--*/
+
+{
+ *ExceptionCode = ExceptionPointer->ExceptionRecord->ExceptionCode;
+
+ if ( (*ExceptionCode == STATUS_IN_PAGE_ERROR) &&
+ (ExceptionPointer->ExceptionRecord->NumberParameters >= 3) ) {
+
+ *ExceptionCode = ExceptionPointer->ExceptionRecord->ExceptionInformation[2];
+ }
+
+ ASSERT( !NT_SUCCESS(*ExceptionCode) );
+
+ return EXCEPTION_EXECUTE_HANDLER;
+}
+
+
+BOOLEAN
+CcCanIWrite (
+ IN PFILE_OBJECT FileObject,
+ IN ULONG BytesToWrite,
+ IN BOOLEAN Wait,
+ IN UCHAR Retrying
+ )
+
+/*++
+
+Routine Description:
+
+ This routine tests whether it is ok to do a write to the cache
+ or not, according to the Thresholds of dirty bytes and available
+ pages. The first time this routine is called for a request (Retrying
+ FALSE), we automatically make the new request queue if there are other
+ requests in the queue.
+
+ Note that the ListEmpty test is important to prevent small requests from sneaking
+ in and starving large requests.
+
+Arguments:
+
+ FileObject - for the file to be written
+
+ BytesToWrite - Number of bytes caller wishes to write to the Cache.
+
+ Wait - TRUE if the caller owns no resources, and can block inside this routine
+ until it is ok to write.
+
+ Retrying - Specified as FALSE when the request is first received, and
+ otherwise specified as TRUE if this write has already entered
+ the queue. Special non-zero value of MAXUCHAR indicates that
+ we were called within the cache manager with a MasterSpinLock held,
+ so do not attempt to acquire it here. MAXUCHAR - 1 means we
+ were called within the Cache Manager with some other spinlock
+ held. For either of these two special values, we do not touch
+ the FsRtl header.
+
+Return Value:
+
+ TRUE if it is ok to write.
+ FALSE if the caller should defer the write via a call to CcDeferWrite.
+
+--*/
+
+{
+ PSHARED_CACHE_MAP SharedCacheMap;
+ KEVENT Event;
+ KIRQL OldIrql;
+ ULONG PagesToWrite;
+ BOOLEAN ExceededPerFileThreshold;
+ DEFERRED_WRITE DeferredWrite;
+ PSECTION_OBJECT_POINTERS SectionObjectPointers;
+
+ //
+ // Do a special test here for file objects that keep track of dirty
+ // pages on a per-file basis. This is used mainly for slow links.
+ //
+
+ ExceededPerFileThreshold = FALSE;
+
+ PagesToWrite = ((BytesToWrite < 0x40000 ?
+ BytesToWrite : 0x40000) + (PAGE_SIZE - 1)) / PAGE_SIZE;
+
+ //
+ // Don't dereference the FsContext field if we were called while holding
+ // a spinlock.
+ //
+
+ if ((Retrying >= MAXUCHAR - 1) ||
+
+ FlagOn(((PFSRTL_COMMON_FCB_HEADER)(FileObject->FsContext))->Flags,
+ FSRTL_FLAG_LIMIT_MODIFIED_PAGES)) {
+
+ if (Retrying != MAXUCHAR) {
+ ExAcquireFastLock( &CcMasterSpinLock, &OldIrql );
+ }
+
+ if (((SectionObjectPointers = FileObject->SectionObjectPointer) != NULL) &&
+ ((SharedCacheMap = SectionObjectPointers->SharedCacheMap) != NULL) &&
+ (SharedCacheMap->DirtyPageThreshold != 0) &&
+ (SharedCacheMap->DirtyPages != 0) &&
+ ((PagesToWrite + SharedCacheMap->DirtyPages) >
+ SharedCacheMap->DirtyPageThreshold)) {
+
+ ExceededPerFileThreshold = TRUE;
+ }
+
+ if (Retrying != MAXUCHAR) {
+ ExReleaseFastLock( &CcMasterSpinLock, OldIrql );
+ }
+ }
+
+ //
+ // See if it is ok to do the write right now
+ //
+
+ if ((Retrying || IsListEmpty(&CcDeferredWrites))
+
+ &&
+
+ (CcTotalDirtyPages + PagesToWrite < CcDirtyPageThreshold)
+
+ &&
+
+ MmEnoughMemoryForWrite()
+
+ &&
+
+ !ExceededPerFileThreshold) {
+
+ return TRUE;
+
+ //
+ // Otherwise, if our caller is synchronous, we will just wait here.
+ //
+
+ }
+
+ if (IsListEmpty(&CcDeferredWrites) ) {
+
+ //
+ // Get a write scan to occur NOW
+ //
+
+ KeSetTimer( &LazyWriter.ScanTimer, CcNoDelay, &LazyWriter.ScanDpc );
+ }
+
+ if (Wait) {
+
+ KeInitializeEvent( &Event, NotificationEvent, FALSE );
+
+ //
+ // Fill in the block. Note that we can access the Fsrtl Common Header
+ // even if it's paged because Wait will be FALSE if called from
+ // within the cache.
+ //
+
+ DeferredWrite.NodeTypeCode = CACHE_NTC_DEFERRED_WRITE;
+ DeferredWrite.NodeByteSize = sizeof(DEFERRED_WRITE);
+ DeferredWrite.FileObject = FileObject;
+ DeferredWrite.BytesToWrite = BytesToWrite;
+ DeferredWrite.Event = &Event;
+ DeferredWrite.LimitModifiedPages = BooleanFlagOn(((PFSRTL_COMMON_FCB_HEADER)(FileObject->FsContext))->Flags,
+ FSRTL_FLAG_LIMIT_MODIFIED_PAGES);
+
+ //
+ // Now insert at the appropriate end of the list
+ //
+
+ if (Retrying) {
+ (VOID)ExInterlockedInsertHeadList( &CcDeferredWrites,
+ &DeferredWrite.DeferredWriteLinks,
+ &CcDeferredWriteSpinLock );
+ } else {
+ (VOID)ExInterlockedInsertTailList( &CcDeferredWrites,
+ &DeferredWrite.DeferredWriteLinks,
+ &CcDeferredWriteSpinLock );
+ }
+
+ while (TRUE) {
+
+ //
+ // Now since we really didn't synchronize anything but the insertion,
+ // we call the post routine to make sure that in some wierd case we
+ // do not leave anyone hanging with no dirty bytes for the Lazy Writer.
+ //
+
+ CcPostDeferredWrites();
+
+ //
+ // Finally wait until the event is signalled and we can write
+ // and return to tell the guy he can write.
+ //
+
+ if (KeWaitForSingleObject( &Event,
+ Executive,
+ KernelMode,
+ FALSE,
+ &CcIdleDelay ) == STATUS_SUCCESS) {
+
+
+ return TRUE;
+ }
+ }
+
+ } else {
+ return FALSE;
+ }
+}
+
+
+VOID
+CcDeferWrite (
+ IN PFILE_OBJECT FileObject,
+ IN PCC_POST_DEFERRED_WRITE PostRoutine,
+ IN PVOID Context1,
+ IN PVOID Context2,
+ IN ULONG BytesToWrite,
+ IN BOOLEAN Retrying
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to have the Cache Manager defer posting
+ of a write until the Lazy Writer makes some progress writing, or
+ there are more available pages. A file system would normally call
+ this routine after receiving FALSE from CcCanIWrite, and preparing
+ the request to be posted.
+
+Arguments:
+
+ FileObject - for the file to be written
+
+ PostRoutine - Address of the PostRoutine that the Cache Manager can
+ call to post the request when conditions are right. Note
+ that it is possible that this routine will be called
+ immediately from this routine.
+
+ Context1 - First context parameter for the post routine.
+
+ Context2 - Secont parameter for the post routine.
+
+ BytesToWrite - Number of bytes that the request is trying to write
+ to the cache.
+
+ Retrying - Supplied as FALSE if the request is being posted for the
+ first time, TRUE otherwise.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PDEFERRED_WRITE DeferredWrite;
+
+ //
+ // Attempt to allocate a deferred write block, and if we do not get
+ // one, just post it immediately rather than gobbling up must succeed
+ // pool.
+ //
+
+ DeferredWrite = ExAllocatePool( NonPagedPool, sizeof(DEFERRED_WRITE) );
+
+ if (DeferredWrite == NULL) {
+ (*PostRoutine)( Context1, Context2 );
+ return;
+ }
+
+ //
+ // Fill in the block.
+ //
+
+ DeferredWrite->NodeTypeCode = CACHE_NTC_DEFERRED_WRITE;
+ DeferredWrite->NodeByteSize = sizeof(DEFERRED_WRITE);
+ DeferredWrite->FileObject = FileObject;
+ DeferredWrite->BytesToWrite = BytesToWrite;
+ DeferredWrite->Event = NULL;
+ DeferredWrite->PostRoutine = PostRoutine;
+ DeferredWrite->Context1 = Context1;
+ DeferredWrite->Context2 = Context2;
+ DeferredWrite->LimitModifiedPages = BooleanFlagOn(((PFSRTL_COMMON_FCB_HEADER)(FileObject->FsContext))->Flags,
+ FSRTL_FLAG_LIMIT_MODIFIED_PAGES);
+
+ //
+ // Now insert at the appropriate end of the list
+ //
+
+ if (Retrying) {
+ (VOID)ExInterlockedInsertHeadList( &CcDeferredWrites,
+ &DeferredWrite->DeferredWriteLinks,
+ &CcDeferredWriteSpinLock );
+ } else {
+ (VOID)ExInterlockedInsertTailList( &CcDeferredWrites,
+ &DeferredWrite->DeferredWriteLinks,
+ &CcDeferredWriteSpinLock );
+ }
+
+ //
+ // Now since we really didn't synchronize anything but the insertion,
+ // we call the post routine to make sure that in some wierd case we
+ // do not leave anyone hanging with no dirty bytes for the Lazy Writer.
+ //
+
+ CcPostDeferredWrites();
+}
+
+
+VOID
+CcPostDeferredWrites (
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to see if any deferred writes should be posted
+ now, and to post them. It should be called any time the status of the
+ queue may have changed, such as when a new entry has been added, or the
+ Lazy Writer has finished writing out buffers and set them clean.
+
+Arguments:
+
+ None
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PDEFERRED_WRITE DeferredWrite;
+ ULONG TotalBytesLetLoose = 0;
+ KIRQL OldIrql;
+
+ do {
+
+ //
+ // Initially clear the deferred write structure pointer
+ // and syncrhronize.
+ //
+
+ DeferredWrite = NULL;
+
+ ExAcquireSpinLock( &CcDeferredWriteSpinLock, &OldIrql );
+
+ //
+ // If the list is empty we are done.
+ //
+
+ if (!IsListEmpty(&CcDeferredWrites)) {
+
+ PLIST_ENTRY Entry;
+
+ Entry = CcDeferredWrites.Flink;
+
+ while (Entry != &CcDeferredWrites) {
+
+ DeferredWrite = CONTAINING_RECORD( Entry,
+ DEFERRED_WRITE,
+ DeferredWriteLinks );
+
+ //
+ // Check for a paranoid case here that TotalBytesLetLoose
+ // wraps. We stop processing the list at this time.
+ //
+
+ TotalBytesLetLoose += DeferredWrite->BytesToWrite;
+
+ if (TotalBytesLetLoose < DeferredWrite->BytesToWrite) {
+
+ DeferredWrite = NULL;
+ break;
+ }
+
+ //
+ // If it is now ok to post this write, remove him from
+ // the list.
+ //
+
+ if (CcCanIWrite( DeferredWrite->FileObject,
+ TotalBytesLetLoose,
+ FALSE,
+ MAXUCHAR - 1 )) {
+
+ RemoveEntryList( &DeferredWrite->DeferredWriteLinks );
+ break;
+
+ //
+ // Otherwise, it is time to stop processing the list, so
+ // we clear the pointer again unless we throttled this item
+ // because of a private dirty page limit.
+ //
+
+ } else {
+
+ //
+ // If this was a private throttle, skip over it and
+ // remove its byte count from the running total.
+ //
+
+ if (DeferredWrite->LimitModifiedPages) {
+
+ Entry = Entry->Flink;
+ TotalBytesLetLoose -= DeferredWrite->BytesToWrite;
+ DeferredWrite = NULL;
+ continue;
+
+ } else {
+
+ DeferredWrite = NULL;
+
+ break;
+ }
+ }
+ }
+ }
+
+ ExReleaseSpinLock( &CcDeferredWriteSpinLock, OldIrql );
+
+ //
+ // If we got something, set the event or call the post routine
+ // and deallocate the structure.
+ //
+
+ if (DeferredWrite != NULL) {
+
+ if (DeferredWrite->Event != NULL) {
+
+ KeSetEvent( DeferredWrite->Event, 0, FALSE );
+
+ } else {
+
+ (*DeferredWrite->PostRoutine)( DeferredWrite->Context1,
+ DeferredWrite->Context2 );
+ ExFreePool( DeferredWrite );
+ }
+ }
+
+ //
+ // Loop until we find no more work to do.
+ //
+
+ } while (DeferredWrite != NULL);
+}