/*++
Copyright (c) 1994 Microsoft Corporation
Module Name:
Cache.c
Abstract:
This module implements internal caching support routines. It does
not interact with the cache manager.
Author:
Manny Weiser [MannyW] 05-Jan-1994
Revision History:
--*/
#include "Procs.h"
//
// The local debug trace level
//
BOOLEAN
SpaceForWriteBehind(
PNONPAGED_FCB NpFcb,
ULONG FileOffset,
ULONG BytesToWrite
);
BOOLEAN
OkToReadAhead(
PFCB Fcb,
IN ULONG FileOffset,
IN UCHAR IoType
);
#define Dbg (DEBUG_TRACE_CACHE)
//
// Local procedure prototypes
//
#ifdef ALLOC_PRAGMA
#pragma alloc_text( PAGE, CacheRead )
#pragma alloc_text( PAGE, SpaceForWriteBehind )
#pragma alloc_text( PAGE, CacheWrite )
#pragma alloc_text( PAGE, OkToReadAhead )
#pragma alloc_text( PAGE, CalculateReadAheadSize )
#pragma alloc_text( PAGE, FlushCache )
#pragma alloc_text( PAGE, AcquireFcbAndFlushCache )
#endif
ULONG
CacheRead(
IN PNONPAGED_FCB NpFcb,
IN ULONG FileOffset,
IN ULONG BytesToRead,
IN PVOID UserBuffer
, IN BOOLEAN WholeBufferOnly
)
/*++
Routine Description:
This routine attempts to satisfy a user read from cache. It returns
the number of bytes actually copied from cache.
Arguments:
NpFcb - A pointer the the nonpaged FCB of the file being read.
FileOffset - The file offset to read.
BytesToRead - The number of bytes to read.
UserBuffer - A pointer to the users target buffer.
WholeBufferOnly - Do a cache read only if we can satisfy the entire
read request.
Return Value:
The number of bytes copied to the user buffer.
--*/
{
ULONG BytesToCopy;
PAGED_CODE();
if (DisableReadCache) return 0 ;
DebugTrace(0, Dbg, "CacheRead...\n", 0 );
DebugTrace( 0, Dbg, "FileOffset = %d\n", FileOffset );
DebugTrace( 0, Dbg, "ByteCount = %d\n", BytesToRead );
NwAcquireSharedFcb( NpFcb, TRUE );
//
// If this is a read ahead and it contains some data that the user
// could be interested in, copy the interesting data.
//
if ( NpFcb->CacheType == ReadAhead &&
NpFcb->CacheDataSize != 0 &&
FileOffset >= NpFcb->CacheFileOffset &&
FileOffset <= NpFcb->CacheFileOffset + NpFcb->CacheDataSize ) {
if ( NpFcb->CacheBuffer ) {
//
// Make sure we have a CacheBuffer.
//
BytesToCopy =
MIN ( BytesToRead,
NpFcb->CacheFileOffset +
NpFcb->CacheDataSize - FileOffset );
if ( WholeBufferOnly && BytesToCopy != BytesToRead ) {
NwReleaseFcb( NpFcb );
return( 0 );
}
RtlCopyMemory(
UserBuffer,
NpFcb->CacheBuffer + ( FileOffset - NpFcb->CacheFileOffset ),
BytesToCopy );
DebugTrace(0, Dbg, "CacheRead -> %d\n", BytesToCopy );
} else {
ASSERT(FALSE); // we should never get here
DebugTrace(0, Dbg, "CacheRead -> %08lx\n", 0 );
BytesToCopy = 0;
}
} else {
DebugTrace(0, Dbg, "CacheRead -> %08lx\n", 0 );
BytesToCopy = 0;
}
NwReleaseFcb( NpFcb );
return( BytesToCopy );
}
BOOLEAN
SpaceForWriteBehind(
PNONPAGED_FCB NpFcb,
ULONG FileOffset,
ULONG BytesToWrite
)
/*++
Routine Description:
This routine determines if it is ok to write behind this data to
this FCB.
Arguments:
NpFcb - A pointer the the NONPAGED_FCB of the file being written.
FileOffset - The file offset to write.
BytesToWrite - The number of bytes to write.
Return Value:
The number of bytes copied to the user buffer.
--*/
{
PAGED_CODE();
if ( NpFcb->CacheDataSize == 0 ) {
NpFcb->CacheFileOffset = FileOffset;
}
if ( NpFcb->CacheDataSize == 0 && BytesToWrite >= NpFcb->CacheSize ) {
return( FALSE );
}
if ( FileOffset - NpFcb->CacheFileOffset + BytesToWrite >
NpFcb->CacheSize ) {
return( FALSE );
}
return( TRUE );
}
BOOLEAN
CacheWrite(
IN PIRP_CONTEXT IrpContext OPTIONAL,
IN PNONPAGED_FCB NpFcb,
IN ULONG FileOffset,
IN ULONG BytesToWrite,
IN PVOID UserBuffer
)
/*++
Routine Description:
This routine attempts to satisfy a user write to cache. The write
succeeds if it is sequential and fits in the cache buffer.
Arguments:
IrpContext - A pointer to request parameters.
NpFcb - A pointer the the NONPAGED_FCB of the file being read.
FileOffset - The file offset to write.
BytesToWrite - The number of bytes to write.
UserBuffer - A pointer to the users source buffer.
Return Value:
The number of bytes copied to the user buffer.
--*/
{
ULONG CacheSize;
NTSTATUS status;
PAGED_CODE();
if (DisableWriteCache) return FALSE ;
DebugTrace( +1, Dbg, "CacheWrite...\n", 0 );
DebugTrace( 0, Dbg, "FileOffset = %d\n", FileOffset );
DebugTrace( 0, Dbg, "ByteCount = %d\n", BytesToWrite );
if ( NpFcb->Fcb->ShareAccess.SharedWrite ||
NpFcb->Fcb->ShareAccess.SharedRead ) {
DebugTrace( 0, Dbg, "File is not open in exclusive mode\n", 0 );
DebugTrace( -1, Dbg, "CacheWrite -> FALSE\n", 0 );
return( FALSE );
}
//
// Note, If we decide to send data to the server we must be at the front
// of the queue before we grab the Fcb exclusive.
//
TryAgain:
NwAcquireExclusiveFcb( NpFcb, TRUE );
//
// Allocate a cache buffer if we don't already have one.
//
if ( NpFcb->CacheBuffer == NULL ) {
if ( IrpContext == NULL ) {
DebugTrace( 0, Dbg, "No cache buffer\n", 0 );
DebugTrace( -1, Dbg, "CacheWrite -> FALSE\n", 0 );
NwReleaseFcb( NpFcb );
return( FALSE );
}
NpFcb->CacheType = WriteBehind;
if (( IrpContext->pNpScb->SendBurstModeEnabled ) ||
( IrpContext->pNpScb->ReceiveBurstModeEnabled )) {
CacheSize = IrpContext->pNpScb->MaxReceiveSize;
} else {
CacheSize = IrpContext->pNpScb->BufferSize;
}
try {
NpFcb->CacheBuffer = ALLOCATE_POOL_EX( NonPagedPool, CacheSize );
NpFcb->CacheSize = CacheSize;
NpFcb->CacheMdl = ALLOCATE_MDL( NpFcb->CacheBuffer, CacheSize, FALSE, FALSE, NULL );
if ( NpFcb->CacheMdl == NULL ) {
ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
}
MmBuildMdlForNonPagedPool( NpFcb->CacheMdl );
} except ( EXCEPTION_EXECUTE_HANDLER ) {
if ( NpFcb->CacheBuffer != NULL) {
FREE_POOL( NpFcb->CacheBuffer );
NpFcb->CacheBuffer = NULL;
NpFcb->CacheSize = 0;
}
DebugTrace( 0, Dbg, "Allocate failed\n", 0 );
DebugTrace( -1, Dbg, "CacheWrite -> FALSE\n", 0 );
NpFcb->CacheDataSize = 0;
NwReleaseFcb( NpFcb );
return( FALSE );
}
NpFcb->CacheFileOffset = 0;
NpFcb->CacheDataSize = 0;
} else if ( NpFcb->CacheType != WriteBehind ) {
DebugTrace( -1, Dbg, "CacheWrite not writebehind -> FALSE\n", 0 );
NwReleaseFcb( NpFcb );
return( FALSE );
}
//
// If the data is non sequential and non overlapping, flush the
// existing cache.
//
if ( NpFcb->CacheDataSize != 0 &&
( FileOffset < NpFcb->CacheFileOffset ||
FileOffset > NpFcb->CacheFileOffset + NpFcb->CacheDataSize ) ) {
//
// Release and then AcquireFcbAndFlush() will get us to the front
// of the queue before re-acquiring. This avoids potential deadlocks.
//
NwReleaseFcb( NpFcb );
if ( IrpContext != NULL ) {
DebugTrace( 0, Dbg, "Data is not sequential, flushing data\n", 0 );
status = AcquireFcbAndFlushCache( IrpContext, NpFcb );
if ( !NT_SUCCESS( status ) ) {
ExRaiseStatus( status );
}
}
DebugTrace( -1, Dbg, "CacheWrite -> FALSE\n", 0 );
return( FALSE );
}
//
// The data is sequential, see if it fits.
//
if ( SpaceForWriteBehind( NpFcb, FileOffset, BytesToWrite ) ) {
try {
RtlCopyMemory(
NpFcb->CacheBuffer + ( FileOffset - NpFcb->CacheFileOffset ),
UserBuffer,
BytesToWrite );
} except ( EXCEPTION_EXECUTE_HANDLER ) {
DebugTrace( 0, Dbg, "Bad user mode buffer in CacheWrite.\n", 0 );
DebugTrace(-1, Dbg, "CacheWrite -> FALSE\n", 0 );
NwReleaseFcb( NpFcb );
return ( FALSE );
}
if ( NpFcb->CacheDataSize <
(FileOffset - NpFcb->CacheFileOffset + BytesToWrite) ) {
NpFcb->CacheDataSize =
FileOffset - NpFcb->CacheFileOffset + BytesToWrite;
}
DebugTrace(-1, Dbg, "CacheWrite -> TRUE\n", 0 );
NwReleaseFcb( NpFcb );
return( TRUE );
} else if ( IrpContext != NULL ) {
//
// The data didn't fit in the cache. If the cache is empty
// then its time to return because it never will fit and we
// have no stale data. This can happen if this request or
// another being processed in parallel flush the cache and
// TryAgain.
//
if ( NpFcb->CacheDataSize == 0 ) {
DebugTrace(-1, Dbg, "CacheWrite -> FALSE\n", 0 );
NwReleaseFcb( NpFcb );
return( FALSE );
}
//
// The data didn't fit in the cache, flush the cache
//
DebugTrace( 0, Dbg, "Cache is full, flushing data\n", 0 );
//
// We must be at the front of the Queue before writing.
//
NwReleaseFcb( NpFcb );
status = AcquireFcbAndFlushCache( IrpContext, NpFcb );
if ( !NT_SUCCESS( status ) ) {
ExRaiseStatus( status );
}
//
// Now see if it fits in the cache. We need to repeat all
// the tests again because two requests can flush the cache at the
// same time and the other one of them could have nearly filled it again.
//
goto TryAgain;
} else {
DebugTrace(-1, Dbg, "CacheWrite full -> FALSE\n", 0 );
NwReleaseFcb( NpFcb );
return( FALSE );
}
}
BOOLEAN
OkToReadAhead(
PFCB Fcb,
IN ULONG FileOffset,
IN UCHAR IoType
)
/*++
Routine Description:
This routine determines whether the attempted i/o is sequential (so that
we can use the cache).
Arguments:
Fcb - A pointer the the Fcb of the file being read.
FileOffset - The file offset to read.
Return Value:
TRUE - The operation is sequential.
FALSE - The operation is not sequential.
--*/
{
PAGED_CODE();
if ( Fcb->NonPagedFcb->CacheType == IoType &&
!Fcb->ShareAccess.SharedWrite &&
FileOffset == Fcb->LastReadOffset + Fcb->LastReadSize ) {
DebugTrace(0, Dbg, "Io is sequential\n", 0 );
return( TRUE );
} else {
DebugTrace(0, Dbg, "Io is not sequential\n", 0 );
return( FALSE );
}
}
ULONG
CalculateReadAheadSize(
IN PIRP_CONTEXT IrpContext,
IN PNONPAGED_FCB NpFcb,
IN ULONG CacheReadSize,
IN ULONG FileOffset,
IN ULONG ByteCount
)
/*++
Routine Description:
This routine determines the amount of data that can be read ahead,
and sets up for the read.
Note: Fcb must be acquired exclusive before calling.
Arguments:
NpFcb - A pointer the the nonpaged FCB of the file being read.
FileOffset - The file offset to read.
Return Value:
The amount of data to read.
--*/
{
ULONG ReadSize;
ULONG CacheSize;
PAGED_CODE();
DebugTrace(+1, Dbg, "CalculateReadAheadSize\n", 0 );
if (( IrpContext->pNpScb->SendBurstModeEnabled ) ||
( IrpContext->pNpScb->ReceiveBurstModeEnabled )) {
CacheSize = IrpContext->pNpScb->MaxReceiveSize;
} else {
CacheSize = IrpContext->pNpScb->BufferSize;
}
if ( OkToReadAhead( NpFcb->Fcb, FileOffset - CacheReadSize, ReadAhead ) &&
ByteCount < CacheSize ) {
ReadSize = CacheSize;
} else {
//
// Do not read ahead.
//
DebugTrace( 0, Dbg, "No read ahead\n", 0 );
DebugTrace(-1, Dbg, "CalculateReadAheadSize -> %d\n", ByteCount );
return ( ByteCount );
}
//
// Allocate pool for the segment of the read
//
if ( NpFcb->CacheBuffer == NULL ) {
try {
NpFcb->CacheBuffer = ALLOCATE_POOL_EX( NonPagedPool, ReadSize );
NpFcb->CacheSize = ReadSize;
NpFcb->CacheMdl = ALLOCATE_MDL( NpFcb->CacheBuffer, ReadSize, FALSE, FALSE, NULL );
if ( NpFcb->CacheMdl == NULL ) {
ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
}
MmBuildMdlForNonPagedPool( NpFcb->CacheMdl );
} except ( EXCEPTION_EXECUTE_HANDLER ) {
if ( NpFcb->CacheBuffer != NULL) {
FREE_POOL( NpFcb->CacheBuffer );
NpFcb->CacheBuffer = NULL;
}
NpFcb->CacheSize = 0;
NpFcb->CacheDataSize = 0;
DebugTrace( 0, Dbg, "Failed to allocated buffer\n", 0 );
DebugTrace(-1, Dbg, "CalculateReadAheadSize -> %d\n", ByteCount );
return( ByteCount );
}
} else {
ReadSize = MIN ( NpFcb->CacheSize, ReadSize );
}
DebugTrace(-1, Dbg, "CalculateReadAheadSize -> %d\n", ReadSize );
return( ReadSize );
}
NTSTATUS
FlushCache(
PIRP_CONTEXT IrpContext,
PNONPAGED_FCB NpFcb
)
/*++
Routine Description:
This routine flushes the cache buffer for the NpFcb. The caller must
have acquired the FCB exclusive prior to making this call!
Arguments:
IrpContext - A pointer to request parameters.
NpFcb - A pointer the the nonpaged FCB of the file to flush.
Return Value:
The amount of data to read.
--*/
{
NTSTATUS status = STATUS_SUCCESS;
PAGED_CODE();
if ( NpFcb->CacheDataSize != 0 && NpFcb->CacheType == WriteBehind ) {
LARGE_INTEGER ByteOffset;
ByteOffset.QuadPart = NpFcb->CacheFileOffset;
status = DoWrite(
IrpContext,
ByteOffset,
NpFcb->CacheDataSize,
NpFcb->CacheBuffer,
NpFcb->CacheMdl );
//
// DoWrite leaves us at the head of the queue. The caller
// is responsible for dequeueing the irp context appropriately.
//
if ( NT_SUCCESS( status ) ) {
NpFcb->CacheDataSize = 0;
}
}
return( status );
}
NTSTATUS
AcquireFcbAndFlushCache(
PIRP_CONTEXT IrpContext,
PNONPAGED_FCB NpFcb
)
/*++
Routine Description:
This routine acquires the FCB exclusive and flushes the cache
buffer for the acquired NpFcb.
Arguments:
IrpContext - A pointer to request parameters.
NpFcb - A pointer the the nonpaged FCB of the file to flush.
Return Value:
The amount of data to read.
--*/
{
NTSTATUS status = STATUS_SUCCESS;
PAGED_CODE();
NwAppendToQueueAndWait( IrpContext );
NwAcquireExclusiveFcb( NpFcb, TRUE );
status = FlushCache( IrpContext, NpFcb );
//
// Release the FCB and remove ourselves from the queue.
// Frequently the caller will want to grab a resource so
// we need to be off the queue then.
//
NwReleaseFcb( NpFcb );
NwDequeueIrpContext( IrpContext, FALSE );
return( status );
}