2257 lines
69 KiB
C
2257 lines
69 KiB
C
/*++
|
||
|
||
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
|
||
|
||
#ifdef ALLOC_PRAGMA
|
||
#pragma alloc_text(PAGE,CcCopyRead)
|
||
#pragma alloc_text(PAGE,CcFastCopyRead)
|
||
#endif
|
||
|
||
|
||
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;
|
||
ULONG PagesToGo;
|
||
ULONG MoveLength;
|
||
ULONG LengthToGo;
|
||
NTSTATUS Status;
|
||
ULONG OriginalLength = Length;
|
||
PETHREAD Thread = PsGetCurrentThread();
|
||
ULONG GotAMiss = 0;
|
||
|
||
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->Flags.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) {
|
||
CcFreeActiveVacb( SharedCacheMap, NULL, 0, FALSE );
|
||
}
|
||
|
||
//
|
||
// Get the starting point in the view.
|
||
//
|
||
|
||
CacheBuffer = (PVOID)((PCHAR)ActiveVacb->BaseAddress +
|
||
(FOffset.LowPart & (VACB_MAPPING_GRANULARITY - 1)));
|
||
|
||
//
|
||
// Reduce LengthToCopy if it is greater than our caller's length.
|
||
//
|
||
|
||
if (LengthToCopy > Length) {
|
||
LengthToCopy = Length;
|
||
}
|
||
|
||
//
|
||
// Like the logic for the normal case below, we want to spin around
|
||
// making sure Mm only reads the pages we will need.
|
||
//
|
||
|
||
PagesToGo = ADDRESS_AND_SIZE_TO_SPAN_PAGES( CacheBuffer,
|
||
LengthToCopy ) - 1;
|
||
|
||
//
|
||
// Copy the data to the user buffer.
|
||
//
|
||
|
||
try {
|
||
|
||
if (PagesToGo != 0) {
|
||
|
||
LengthToGo = LengthToCopy;
|
||
|
||
while (LengthToGo != 0) {
|
||
|
||
MoveLength = (ULONG)((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 |= !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 |= !MmCheckCachedPageState( CacheBuffer, FALSE );
|
||
|
||
RtlCopyBytes( Buffer, CacheBuffer, LengthToCopy );
|
||
|
||
Buffer = (PCHAR)Buffer + 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.
|
||
//
|
||
|
||
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.
|
||
//
|
||
|
||
CC_SET_PRIVATE_CACHE_MAP (PrivateCacheMap, PRIVATE_CACHE_MAP_READ_AHEAD_ENABLED);
|
||
|
||
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 {
|
||
|
||
PagesToGo = ADDRESS_AND_SIZE_TO_SPAN_PAGES( 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) {
|
||
|
||
LengthToGo = ReceivedLength;
|
||
|
||
while (LengthToGo != 0) {
|
||
|
||
MoveLength = (ULONG)((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 |= !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 |= !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 &&
|
||
!FlagOn( FileObject->Flags, FO_RANDOM_ACCESS ) &&
|
||
!PrivateCacheMap->Flags.ReadAheadEnabled) {
|
||
|
||
CC_SET_PRIVATE_CACHE_MAP (PrivateCacheMap, PRIVATE_CACHE_MAP_READ_AHEAD_ENABLED);
|
||
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;
|
||
ULONG PagesToGo;
|
||
ULONG MoveLength;
|
||
ULONG LengthToGo;
|
||
NTSTATUS Status;
|
||
LARGE_INTEGER OriginalOffset;
|
||
ULONG OriginalLength = Length;
|
||
PETHREAD Thread = PsGetCurrentThread();
|
||
ULONG GotAMiss = 0;
|
||
|
||
UNREFERENCED_PARAMETER (PageCount);
|
||
|
||
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->Flags.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) {
|
||
CcFreeActiveVacb( SharedCacheMap, NULL, 0, FALSE );
|
||
}
|
||
|
||
//
|
||
// Get the starting point in the view.
|
||
//
|
||
|
||
CacheBuffer = (PVOID)((PCHAR)ActiveVacb->BaseAddress +
|
||
(FileOffset & (VACB_MAPPING_GRANULARITY - 1)));
|
||
|
||
//
|
||
// Reduce LengthToCopy if it is greater than our caller's length.
|
||
//
|
||
|
||
if (LengthToCopy > Length) {
|
||
LengthToCopy = Length;
|
||
}
|
||
|
||
//
|
||
// Like the logic for the normal case below, we want to spin around
|
||
// making sure Mm only reads the pages we will need.
|
||
//
|
||
|
||
PagesToGo = ADDRESS_AND_SIZE_TO_SPAN_PAGES( CacheBuffer,
|
||
LengthToCopy ) - 1;
|
||
|
||
//
|
||
// Copy the data to the user buffer.
|
||
//
|
||
|
||
try {
|
||
|
||
if (PagesToGo != 0) {
|
||
|
||
LengthToGo = LengthToCopy;
|
||
|
||
while (LengthToGo != 0) {
|
||
|
||
MoveLength = (ULONG)((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 |= !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 |= !MmCheckCachedPageState( CacheBuffer, FALSE );
|
||
|
||
RtlCopyBytes( Buffer, CacheBuffer, LengthToCopy );
|
||
|
||
Buffer = (PCHAR)Buffer + 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.
|
||
//
|
||
|
||
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 {
|
||
|
||
PagesToGo = ADDRESS_AND_SIZE_TO_SPAN_PAGES( 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) {
|
||
|
||
LengthToGo = ReceivedLength;
|
||
|
||
while (LengthToGo != 0) {
|
||
|
||
MoveLength = (ULONG)((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 |= !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 |= !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 &&
|
||
!FlagOn( FileObject->Flags, FO_RANDOM_ACCESS ) &&
|
||
!PrivateCacheMap->Flags.ReadAheadEnabled) {
|
||
|
||
CC_SET_PRIVATE_CACHE_MAP (PrivateCacheMap, PRIVATE_CACHE_MAP_READ_AHEAD_ENABLED);
|
||
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;
|
||
PFSRTL_ADVANCED_FCB_HEADER FcbHeader;
|
||
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) &&
|
||
(ActiveVacb == SharedCacheMap->NeedToZeroVacb) &&
|
||
(((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);
|
||
|
||
//
|
||
// If there is an advanced header, then we can acquire the FastMutex to
|
||
// make capturing ValidDataLength atomic. Currently our other file systems
|
||
// are either RO or do not really support 64-bits.
|
||
//
|
||
|
||
FcbHeader = (PFSRTL_ADVANCED_FCB_HEADER)FileObject->FsContext;
|
||
if (FlagOn(FcbHeader->Flags, FSRTL_FLAG_ADVANCED_HEADER)) {
|
||
ExAcquireFastMutex( FcbHeader->FastMutex );
|
||
Temp.QuadPart = ((PFSRTL_COMMON_FCB_HEADER)FileObject->FsContext)->ValidDataLength.QuadPart -
|
||
Temp.QuadPart;
|
||
ExReleaseFastMutex( FcbHeader->FastMutex );
|
||
} else {
|
||
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,
|
||
FileObject );
|
||
|
||
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) &&
|
||
(ActiveVacb == SharedCacheMap->NeedToZeroVacb) &&
|
||
(((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,
|
||
FileObject );
|
||
|
||
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 = (NTSTATUS) 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. MAXUCHAR - 2 means we want to enforce throttling, even if
|
||
the file object is flagged as being of remote origin. For either
|
||
of the first 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;
|
||
|
||
//
|
||
// If this file is writethrough or of remote origin, exempt it from throttling
|
||
// and let it write. We do this under the assumption that it has been throttled
|
||
// at the remote location and we do not want to block it here. If we were called
|
||
// with Retrying set to MAXUCHAR - 2, enforce the throttle regardless of the
|
||
// file object origin (see above).
|
||
//
|
||
|
||
if (BooleanFlagOn( FileObject->Flags, FO_WRITE_THROUGH) ||
|
||
(IoIsFileOriginRemote(FileObject) && (Retrying != MAXUCHAR - 2))) {
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
//
|
||
// 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 < WRITE_CHARGE_THRESHOLD ?
|
||
BytesToWrite : WRITE_CHARGE_THRESHOLD) + (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) {
|
||
CcAcquireMasterLock( &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) {
|
||
CcReleaseMasterLock( 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 (Wait) {
|
||
|
||
if (IsListEmpty(&CcDeferredWrites) ) {
|
||
|
||
//
|
||
// Get a write scan to occur NOW
|
||
//
|
||
|
||
CcAcquireMasterLock( &OldIrql );
|
||
CcScheduleLazyWriteScan( TRUE );
|
||
CcReleaseMasterLock( OldIrql );
|
||
}
|
||
|
||
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) {
|
||
ExInterlockedInsertHeadList( &CcDeferredWrites,
|
||
&DeferredWrite.DeferredWriteLinks,
|
||
&CcDeferredWriteSpinLock );
|
||
} else {
|
||
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;
|
||
KIRQL OldIrql;
|
||
|
||
//
|
||
// 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 = ExAllocatePoolWithTag( NonPagedPool, sizeof(DEFERRED_WRITE), 'wDcC' );
|
||
|
||
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) {
|
||
ExInterlockedInsertHeadList( &CcDeferredWrites,
|
||
&DeferredWrite->DeferredWriteLinks,
|
||
&CcDeferredWriteSpinLock );
|
||
} else {
|
||
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();
|
||
|
||
//
|
||
// Schedule the lazy writer in case the reason we're blocking
|
||
// is that we're waiting for Mm (or some other external flag)
|
||
// to lower and let this write happen. He will be the one to
|
||
// keep coming back and checking if this can proceed, even if
|
||
// there are no cache manager pages to write.
|
||
//
|
||
|
||
CcAcquireMasterLock( &OldIrql);
|
||
|
||
if (!LazyWriter.ScanActive) {
|
||
CcScheduleLazyWriteScan( FALSE );
|
||
}
|
||
|
||
CcReleaseMasterLock( OldIrql);
|
||
}
|
||
|
||
|
||
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);
|
||
}
|