699 lines
16 KiB
C
699 lines
16 KiB
C
/*++
|
||
|
||
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 );
|
||
}
|