windows-nt/Source/XPSP1/NT/base/fs/srv/fsdsmb.c
2020-09-26 16:20:57 +08:00

2446 lines
74 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
Copyright (c) 1990 Microsoft Corporation
Module Name:
fsdsmb.c
Abstract:
This module implements SMB processing routines and their support
routines for the File System Driver of the LAN Manager server.
*** This module must be nonpageable.
Author:
Chuck Lenzmeier (chuckl) 19-Mar-1990
Revision History:
--*/
//
// This module is laid out as follows:
// Includes
// Local #defines
// Local type definitions
// Forward declarations of local functions
// SMB processing routines
// Restart routines and other support routines
//
#include "precomp.h"
#include "fsdsmb.tmh"
#pragma hdrstop
VOID SRVFASTCALL
SrvFspRestartLargeReadAndXComplete(
IN OUT PWORK_CONTEXT WorkContext
);
VOID SRVFASTCALL
RestartReadAndXCompressedSendComplete (
IN OUT PWORK_CONTEXT WorkContext
);
#if SRVCATCH
VOID
SrvUpdateCatchBuffer (
IN PWORK_CONTEXT WorkContext,
IN OUT PBYTE Buffer,
IN DWORD BufferLength
);
#endif
#ifdef ALLOC_PRAGMA
//#pragma alloc_text( PAGE8FIL, SrvFsdRestartRead )
#pragma alloc_text( PAGE8FIL, SrvFsdRestartReadAndX )
#pragma alloc_text( PAGE8FIL, SrvFsdRestartReadAndXCompressed )
#pragma alloc_text( PAGE8FIL, SrvFsdRestartWrite )
#pragma alloc_text( PAGE8FIL, SrvFsdRestartWriteAndX )
#pragma alloc_text( PAGE, RestartReadAndXCompressedSendComplete )
#pragma alloc_text( PAGE, SrvFspRestartLargeReadAndXComplete )
#if DBG
#pragma alloc_text( PAGE, SrvValidateCompressedData )
#endif
#if SRVCATCH
#pragma alloc_text( PAGE, SrvUpdateCatchBuffer )
#endif
#endif
VOID SRVFASTCALL
SrvFsdRestartRead (
IN OUT PWORK_CONTEXT WorkContext
)
/*++
Routine Description:
Processes file read completion for a Read SMB.
Arguments:
WorkContext - Supplies a pointer to the work context block
describing server-specific context for the request.
Return Value:
None.
--*/
{
PREQ_READ request;
PRESP_READ response;
NTSTATUS status = STATUS_SUCCESS;
PRFCB rfcb;
SHARE_TYPE shareType;
KIRQL oldIrql;
USHORT readLength;
BOOLEAN bNeedTrace = (WorkContext->bAlreadyTrace == FALSE);
UNLOCKABLE_CODE( 8FIL );
if (bNeedTrace) {
if (WorkContext->PreviousSMB == EVENT_TYPE_SMB_LAST_EVENT)
WorkContext->PreviousSMB = EVENT_TYPE_SMB_READ;
SrvWmiStartContext(WorkContext);
}
else
WorkContext->bAlreadyTrace = FALSE;
IF_DEBUG(FSD2) SrvPrint0( " - SrvFsdRestartRead\n" );
//
// Get the request and response parameter pointers.
//
request = (PREQ_READ)WorkContext->RequestParameters;
response = (PRESP_READ)WorkContext->ResponseParameters;
//
// Get the file pointer.
//
rfcb = WorkContext->Rfcb;
shareType = rfcb->ShareType;
IF_DEBUG(FSD2) {
SrvPrint2( " connection 0x%p, RFCB 0x%p\n",
WorkContext->Connection, rfcb );
}
//
// If the read failed, set an error status in the response header.
// (If we tried to read entirely beyond the end of file, we return a
// normal response indicating that nothing was read.)
//
status = WorkContext->Irp->IoStatus.Status;
readLength = (USHORT)WorkContext->Irp->IoStatus.Information;
if ( status == STATUS_BUFFER_OVERFLOW && shareType == ShareTypePipe ) {
//
// If this is an named pipe and the error is
// STATUS_BUFFER_OVERFLOW, set the error in the smb header, but
// return all the data to the client.
//
SrvSetBufferOverflowError( WorkContext );
} else if ( !NT_SUCCESS(status) ) {
if ( status != STATUS_END_OF_FILE ) {
IF_DEBUG(ERRORS) SrvPrint1( "Read failed: %X\n", status );
if ( KeGetCurrentIrql() >= DISPATCH_LEVEL ) {
WorkContext->FspRestartRoutine = SrvFsdRestartRead;
QUEUE_WORK_TO_FSP( WorkContext );
} else {
SrvSetSmbError( WorkContext, status );
SrvFsdSendResponse( WorkContext );
}
IF_DEBUG(FSD2) SrvPrint0( "SrvFsdRestartRead complete\n" );
goto Cleanup;
} else {
readLength = 0;
}
}
#ifdef SLMDBG
{
PRFCB_TRACE entry;
PCHAR readAddress;
ACQUIRE_GLOBAL_SPIN_LOCK( Fsd, &oldIrql );
rfcb->OperationCount++;
entry = &rfcb->Trace[rfcb->NextTrace];
if ( ++rfcb->NextTrace == SLMDBG_TRACE_COUNT ) {
rfcb->NextTrace = 0;
rfcb->TraceWrapped = TRUE;
}
RELEASE_GLOBAL_SPIN_LOCK( Fsd, oldIrql );
entry->Command = WorkContext->NextCommand;
KeQuerySystemTime( &entry->Time );
entry->Data.ReadWrite.Offset = SmbGetUlong( &request->Offset );
entry->Data.ReadWrite.Length = readLength;
readAddress = (PCHAR)response->Buffer;
}
#endif
//
// The read completed successfully. If this is a disk file, update
// the file position.
//
if (shareType == ShareTypeDisk) {
#if SRVCATCH
if( KeGetCurrentIrql() == 0 &&
rfcb->SrvCatch &&
SmbGetUlong( &request->Offset ) == 0 ) {
SrvUpdateCatchBuffer( WorkContext, (PCHAR)response->Buffer, readLength );
}
#endif
rfcb->CurrentPosition = SmbGetUlong( &request->Offset ) + readLength;
}
//
// Save the count of bytes read, to be used to update the server
// statistics database.
//
UPDATE_READ_STATS( WorkContext, readLength );
//
// Build the response message.
//
response->WordCount = 5;
SmbPutUshort( &response->Count, readLength );
RtlZeroMemory( (PVOID)&response->Reserved[0], sizeof(response->Reserved) );
SmbPutUshort(
&response->ByteCount,
(USHORT)(readLength + FIELD_OFFSET(RESP_READ,Buffer[0]) -
FIELD_OFFSET(RESP_READ,BufferFormat))
);
response->BufferFormat = SMB_FORMAT_DATA;
SmbPutUshort( &response->DataLength, readLength );
WorkContext->ResponseParameters = NEXT_LOCATION(
response,
RESP_READ,
readLength
);
//
// Processing of the SMB is complete. Send the response.
//
SrvFsdSendResponse( WorkContext );
Cleanup:
IF_DEBUG(FSD2) SrvPrint0( "SrvFsdRestartRead complete\n" );
if (bNeedTrace) {
SrvWmiEndContext(WorkContext);
}
return;
} // SrvFsdRestartRead
VOID SRVFASTCALL
RestartReadAndXCompressedSendComplete (
IN OUT PWORK_CONTEXT WorkContext
)
/*++
Routine Description:
Processes the TDI transport send complete for a compressed ReadAndX SMB
Arguments:
WorkContext - Supplies a pointer to the work context block
describing server-specific context for the request.
Return Value:
None.
--*/
{
PRFCB rfcb = WorkContext->Rfcb;
PLFCB lfcb = WorkContext->Rfcb->Lfcb;
NTSTATUS status;
PMDL mdl = WorkContext->ResponseBuffer->Mdl->Next;
PAGED_CODE();
//
// If we have a cache mdl, give it back to the cache manager
//
if( mdl != NULL ) {
WorkContext->ResponseBuffer->Mdl->Next = NULL;
}
if( lfcb->MdlReadCompleteCompressed == NULL ||
!lfcb->MdlReadCompleteCompressed( lfcb->FileObject,
mdl,
lfcb->DeviceObject) ) {
status = SrvIssueMdlCompleteRequest( WorkContext, NULL, mdl, IRP_MJ_READ,
&WorkContext->Parameters.ReadAndXCompressed.ReadOffset,
WorkContext->Parameters.ReadAndXCompressed.ReadLength
);
if( !NT_SUCCESS( status ) ) {
SrvLogServiceFailure( SRV_SVC_MDL_COMPLETE, status );
}
}
//
// Free the work item
//
SrvDereferenceWorkItem( WorkContext );
}
#if DBG
VOID
SrvValidateCompressedData(
PWORK_CONTEXT WorkContext,
PMDL CompressedDataMdl,
PCOMPRESSED_DATA_INFO Cdi
)
/*++
Make sure the compressed buffer that we just got from the filesystem is correct
We do this by decompressing it!
--*/
{
ULONG i, compressedDataLength;
PMDL mdl;
NTSTATUS status;
PBYTE compressedBuffer;
PBYTE uncompressedBuffer;
ULONG count;
PBYTE pin;
PAGED_CODE();
//
// We need to copy the compressed data into a flat buffer
//
for ( i = compressedDataLength = 0; i < Cdi->NumberOfChunks; i++ ) {
compressedDataLength += Cdi->CompressedChunkSizes[i];
}
if( compressedDataLength && CompressedDataMdl == NULL ) {
DbgPrint( "SrvValidateCompressedData(wc %X, cdi %X, mdl %X ) -- no MDL!",
WorkContext, Cdi, WorkContext->Irp->MdlAddress );
// DbgBreakPoint();
return;
}
if( compressedDataLength > SrvMaxCompressedDataLength ) {
DbgPrint( "SrvValidateCompressedData(wc %X, cdi %X) compresseddatalength %u\n",
WorkContext, Cdi, compressedDataLength );
DbgBreakPoint();
return;
}
pin = compressedBuffer =
ALLOCATE_NONPAGED_POOL( compressedDataLength, BlockTypeDataBuffer );
if( compressedBuffer == NULL ) {
return;
}
for( mdl = CompressedDataMdl; mdl != NULL; mdl = mdl->Next ) {
i = MmGetMdlByteCount( mdl );
RtlCopyMemory( pin, MmGetSystemAddressForMdl( mdl ), i );
pin += i;
}
//
// The largest output chunk is SrvMaxCompressedDataLength
//
uncompressedBuffer = ALLOCATE_HEAP_COLD( SrvMaxCompressedDataLength, BlockTypeDataBuffer );
if( uncompressedBuffer == NULL ) {
DEALLOCATE_NONPAGED_POOL( compressedBuffer );
return;
}
status = RtlDecompressChunks( uncompressedBuffer, SrvMaxCompressedDataLength,
compressedBuffer, compressedDataLength,
NULL, 0, Cdi );
if( !NT_SUCCESS( status ) ) {
DbgPrint( "\nSrvValidateCompressedData - status %X\n", status );
DbgPrint( " WC %X, CDI %X, Irp %X, Mdl %X, InBuffer %X\n",
WorkContext, Cdi, WorkContext->Irp,
CompressedDataMdl, uncompressedBuffer
);
DbgBreakPoint();
}
DEALLOCATE_NONPAGED_POOL( compressedBuffer );
FREE_HEAP( uncompressedBuffer );
}
#endif
VOID SRVFASTCALL
SrvFsdRestartReadAndXCompressed (
IN OUT PWORK_CONTEXT WorkContext
)
/*++
Routine Description:
Processes file read completion for a compressed ReadAndX SMB
This routine may be called in the FSD or the FSP. There can be
no chained command.
Arguments:
WorkContext - Supplies a pointer to the work context block
describing server-specific context for the request.
Return Value:
None.
--*/
{
PRFCB rfcb = WorkContext->Rfcb;
PIRP irp = WorkContext->Irp;
NTSTATUS status = irp->IoStatus.Status;
ULONG readLength = (ULONG)irp->IoStatus.Information; // size of uncompressed data
ULONG dataLength; // size of compressed data
ULONG cdiLength; // size of compressedDataInfo
USHORT flags2;
PCOMPRESSED_DATA_INFO compressedDataInfo;
PRESP_READ_ANDX response;
ULONG i;
BOOLEAN bNeedTrace = (WorkContext->bAlreadyTrace == FALSE);
UNLOCKABLE_CODE( 8FIL );
if (bNeedTrace) {
if (WorkContext->PreviousSMB == EVENT_TYPE_SMB_LAST_EVENT)
WorkContext->PreviousSMB = EVENT_TYPE_SMB_READ_AND_X;
SrvWmiStartContext(WorkContext);
}
else
WorkContext->bAlreadyTrace = FALSE;
IF_DEBUG( COMPRESSION ) {
KdPrint(( "SRV: SrvFsdRestartReadAndXCompressed status %X, uncompressed len %u, mdl %p\n",
status, readLength, irp->MdlAddress ));
}
if( NT_SUCCESS( status ) && readLength != 0 ) {
//
// Scan the Compression Info structure tallying the sizes of
// each chunk.
//
//
compressedDataInfo = WorkContext->Parameters.ReadAndXCompressed.Aux.Buffer;
#if DBG
SrvValidateCompressedData( WorkContext, WorkContext->Irp->MdlAddress,compressedDataInfo );
#endif
cdiLength = FIELD_OFFSET( COMPRESSED_DATA_INFO, CompressedChunkSizes ) +
(sizeof(ULONG) * compressedDataInfo->NumberOfChunks );
for ( i = dataLength = 0;
i < compressedDataInfo->NumberOfChunks;
i++ ) {
dataLength += compressedDataInfo->CompressedChunkSizes[i];
}
} else if( status == STATUS_END_OF_FILE || readLength == 0 ) {
readLength = dataLength = cdiLength = 0;
} else {
//
// Make sure we are at passive level
//
if( KeGetCurrentIrql() >= DISPATCH_LEVEL ) {
WorkContext->FspRestartRoutine = SrvFsdRestartReadAndXCompressed;
SrvQueueWorkToFsp( WorkContext );
goto Cleanup;
}
irp->Tail.Overlay.AuxiliaryBuffer = NULL;
IF_DEBUG( ERRORS ) {
KdPrint(("SRV: SrvFsdRestartReadAndXCompressed status %X\n", status ));
}
if( status != STATUS_INVALID_READ_MODE &&
status != STATUS_UNSUPPORTED_COMPRESSION &&
status != STATUS_USER_MAPPED_FILE &&
status != STATUS_NOT_SUPPORTED &&
status != STATUS_BAD_COMPRESSION_BUFFER ) {
//
// Set the error and get out!
//
SrvSetSmbError( WorkContext, status );
SrvFsdSendResponse( WorkContext );
goto Cleanup;
}
IF_DEBUG( COMPRESSION ) {
KdPrint(( " %X: rerouting to SrvSmbReadAndX\n", status ));
}
//
// No more compressed reads for this file!
//
rfcb->Mfcb->NonpagedMfcb->OpenFileAttributes &= ~FILE_ATTRIBUTE_COMPRESSED;
//
// We now need to turn around and do a regular non-compressed read. We
// can just reroute this back to the original Read&X processor because we
// know that we have not changed the original request received from the client.
// Also, we have turned off both the FILE_ATTRIBUTE_COMPRESSED flag in the
// Mfcb, as well as the SMB_FLAGS2_COMPRESSED bit in the header. Therefore
// we will not loop!
//
// We also know that SrvSmbReadAndX will return SmbStatusInProgress, because
// all of the error checks have previously passed!
//
// This is not a terribly efficient way to handle this error, but it should be
// extremely rare.
//
SrvStatistics.CompressedReadsFailed++;
(VOID)SrvSmbReadAndX( WorkContext );
goto Cleanup;
}
irp->Tail.Overlay.AuxiliaryBuffer = NULL;
//
// Update the file position
//
WorkContext->Rfcb->CurrentPosition =
WorkContext->Parameters.ReadAndXCompressed.ReadOffset.LowPart + readLength;
//
// Build and send the response
//
response = (PRESP_READ_ANDX)WorkContext->ResponseParameters;
SmbPutUshort( &response->Remaining, (USHORT)-1 );
response->WordCount = 12;
response->AndXCommand = SMB_COM_NO_ANDX_COMMAND;
response->AndXReserved = 0;
SmbPutUshort( &response->AndXOffset, 0 );
SmbPutUshort( &response->DataCompactionMode, 0 );
SmbPutUshort( &response->CdiLength, (USHORT)cdiLength );
SmbPutUshort( &response->DataLength, (USHORT)readLength );
SmbPutUshort( &response->DataLengthHigh, (USHORT)(readLength >> 16) );
SmbPutUshort( &response->DataOffset, (USHORT)READX_BUFFER_OFFSET );
RtlZeroMemory( (PVOID)&response->Reserved3[0], sizeof(response->Reserved3) );
SmbPutUshort( &response->ByteCount, (USHORT)(cdiLength + dataLength) );
WorkContext->ResponseHeader->Flags |= SMB_FLAGS_SERVER_TO_REDIR;
//
// If we got some data, indicate to the client that we are returning
// a compressed response
//
if( cdiLength ) {
//
// Copy the COMPRESSED_DATA_INFO structure to the Data[] portion of the
// response.
//
RtlCopyMemory( response->Buffer, compressedDataInfo, cdiLength );
flags2 = SmbGetAlignedUshort( &WorkContext->ResponseHeader->Flags2 );
flags2 |= SMB_FLAGS2_COMPRESSED;
SmbPutAlignedUshort( &WorkContext->ResponseHeader->Flags2, flags2 );
WorkContext->ResponseBuffer->Mdl->Next = irp->MdlAddress;
}
WorkContext->ResponseBuffer->Mdl->ByteCount = READX_BUFFER_OFFSET + cdiLength;
WorkContext->ResponseBuffer->DataLength =
READX_BUFFER_OFFSET + cdiLength + dataLength;
WorkContext->FspRestartRoutine = RestartReadAndXCompressedSendComplete;
irp->Cancel = FALSE;
SrvStartSend2( WorkContext, SrvQueueWorkToFspAtSendCompletion );
Cleanup:
if (bNeedTrace) {
SrvWmiEndContext(WorkContext);
}
return;
}
VOID SRVFASTCALL
SrvFsdRestartReadAndX (
IN OUT PWORK_CONTEXT WorkContext
)
/*++
Routine Description:
Processes file read completion for a ReadAndX SMB.
This routine may be called in the FSD or the FSP. If the chained
command is Close, it will be called in the FSP.
*** This routine cannot look at the original ReadAndX request!
This is because the read data may have overlaid the request.
All necessary information from the request must be stored
in WorkContext->Parameters.ReadAndX.
Arguments:
WorkContext - Supplies a pointer to the work context block
describing server-specific context for the request.
Return Value:
None.
--*/
{
PRESP_READ_ANDX response;
NTSTATUS status = STATUS_SUCCESS;
PRFCB rfcb;
SHARE_TYPE shareType;
KIRQL oldIrql;
PCHAR readAddress;
CLONG bufferOffset;
ULONG readLength;
BOOLEAN bNeedTrace = (WorkContext->bAlreadyTrace == FALSE);
UNLOCKABLE_CODE( 8FIL );
if (bNeedTrace) {
if (WorkContext->PreviousSMB == EVENT_TYPE_SMB_LAST_EVENT)
WorkContext->PreviousSMB = EVENT_TYPE_SMB_READ_AND_X;
SrvWmiStartContext(WorkContext);
}
else
WorkContext->bAlreadyTrace = FALSE;
IF_DEBUG(FSD2) SrvPrint0( " - SrvFsdRestartReadAndX\n" );
//
// Get the response parameter pointer.
//
response = (PRESP_READ_ANDX)WorkContext->ResponseParameters;
//
// Get the file pointer.
//
rfcb = WorkContext->Rfcb;
shareType = rfcb->ShareType;
IF_DEBUG(FSD2) {
SrvPrint2( " connection 0x%p, RFCB 0x%p\n",
WorkContext->Connection, rfcb );
}
//
// If the read failed, set an error status in the response header.
// (If we tried to read entirely beyond the end of file, we return a
// normal response indicating that nothing was read.)
//
status = WorkContext->Irp->IoStatus.Status;
readLength = (ULONG)WorkContext->Irp->IoStatus.Information;
if ( status == STATUS_BUFFER_OVERFLOW && shareType == ShareTypePipe ) {
//
// If this is an named pipe and the error is
// STATUS_BUFFER_OVERFLOW, set the error in the smb header, but
// return all the data to the client.
//
SrvSetBufferOverflowError( WorkContext );
} else if ( !NT_SUCCESS(status) ) {
if ( status != STATUS_END_OF_FILE ) {
IF_DEBUG(ERRORS) SrvPrint1( "Read failed: %X\n", status );
if ( KeGetCurrentIrql() >= DISPATCH_LEVEL ) {
WorkContext->FspRestartRoutine = SrvFsdRestartReadAndX;
QUEUE_WORK_TO_FSP( WorkContext );
} else {
SrvSetSmbError( WorkContext, status );
SrvFsdSendResponse( WorkContext );
}
IF_DEBUG(FSD2) SrvPrint0("SrvFsdRestartReadAndX complete\n");
goto Cleanup;
} else {
readLength = 0;
}
}
//
// The read completed successfully. Generate information about the
// destination of the read data. Find out how much was actually
// read. If none was read, we don't have to worry about the offset.
//
if ( readLength != 0 ) {
readAddress = WorkContext->Parameters.ReadAndX.ReadAddress;
bufferOffset = (ULONG)(readAddress - (PCHAR)WorkContext->ResponseHeader);
//
// Save the count of bytes read, to be used to update the server
// statistics database.
//
UPDATE_READ_STATS( WorkContext, readLength );
} else {
readAddress = (PCHAR)response->Buffer;
bufferOffset = 0;
}
#ifdef SLMDBG
{
PRFCB_TRACE entry;
ACQUIRE_GLOBAL_SPIN_LOCK( Fsd, &oldIrql );
rfcb->OperationCount++;
entry = &rfcb->Trace[rfcb->NextTrace];
if ( ++rfcb->NextTrace == SLMDBG_TRACE_COUNT ) {
rfcb->NextTrace = 0;
rfcb->TraceWrapped = TRUE;
}
RELEASE_GLOBAL_SPIN_LOCK( Fsd, oldIrql );
entry->Command = WorkContext->NextCommand;
KeQuerySystemTime( &entry->Time );
entry->Data.ReadWrite.Offset =
WorkContext->Parameters.ReadAndX.ReadOffset.LowPart;
ASSERT (WorkContext->Parameters.ReadAndX.ReadOffset.HighPart == 0);
entry->Data.ReadWrite.Length = readLength;
}
#endif
if (shareType == ShareTypePipe) {
//
// If this is NPFS then, Irp->Overlay.AllocationSize actually
// contains the number bytes left to read on this side of the named
// pipe. Return this information to the client.
//
if (WorkContext->Irp->Overlay.AllocationSize.LowPart != 0) {
SmbPutUshort(
&response->Remaining,
(USHORT)(WorkContext->Irp->Overlay.AllocationSize.LowPart - readLength)
);
} else {
SmbPutUshort(
&response->Remaining,
0
);
}
} else {
if ( shareType == ShareTypeDisk ) {
#if SRVCATCH
if( KeGetCurrentIrql() == 0 &&
rfcb->SrvCatch &&
WorkContext->Parameters.ReadAndX.ReadOffset.QuadPart == 0 ) {
SrvUpdateCatchBuffer( WorkContext, readAddress, readLength );
}
#endif
//
// If this is a disk file, then update the file position.
//
rfcb->CurrentPosition =
WorkContext->Parameters.ReadAndX.ReadOffset.LowPart +
readLength;
}
SmbPutUshort( &response->Remaining, (USHORT)-1 );
}
//
// Build the response message. (Note that if no data was read, we
// return a byte count of 0 -- we don't add padding.)
//
// *** Note that even though there may have been a chained command,
// we make this the last response in the chain. This is what
// the OS/2 server does. (Sort of -- it doesn't bother to
// update the AndX fields of the response.) Since the only legal
// chained commands are Close and CloseAndTreeDisc, this seems
// like a reasonable thing to do. It does make life easier --
// we don't have to find the end of the read data and write
// another response there. Besides, the read data might have
// completely filled the SMB buffer.
//
response->WordCount = 12;
response->AndXCommand = SMB_COM_NO_ANDX_COMMAND;
response->AndXReserved = 0;
SmbPutUshort( &response->AndXOffset, 0 );
SmbPutUshort( &response->DataCompactionMode, 0 );
SmbPutUshort( &response->Reserved, 0 );
SmbPutUshort( &response->DataLength, (USHORT)readLength );
SmbPutUshort( &response->DataOffset, (USHORT)bufferOffset );
SmbPutUshort( &response->DataLengthHigh, (USHORT)(readLength >> 16) );
RtlZeroMemory( (PVOID)&response->Reserved3[0], sizeof(response->Reserved3) );
SmbPutUshort(
&response->ByteCount,
(USHORT)(readLength + (readAddress - response->Buffer))
);
WorkContext->ResponseParameters = NEXT_LOCATION(
response,
RESP_READ_ANDX,
readLength +
(readAddress - response->Buffer)
);
//
// Processing of the SMB is complete, except that the file may still
// need to be closed. If not, just send the response. If this is a
// ReadAndX and Close, we need to close the file first.
//
// *** Note that other chained commands are illegal, but are ignored
// -- no error is returned.
//
if ( WorkContext->NextCommand != SMB_COM_CLOSE ) {
//
// Not a chained Close. Just send the response.
//
SrvFsdSendResponse( WorkContext );
} else {
ASSERT( KeGetCurrentIrql() < DISPATCH_LEVEL );
//
// Remember the file last write time, to correctly set this on
// close.
//
WorkContext->Parameters.LastWriteTime =
WorkContext->Parameters.ReadAndX.LastWriteTimeInSeconds;
//
// This is a ReadAndX and Close. Call SrvRestartChainedClose to
// do the close and send the response.
//
SrvRestartChainedClose( WorkContext );
}
IF_DEBUG(FSD2) SrvPrint0( "SrvFsdRestartReadAndX complete\n" );
Cleanup:
if (bNeedTrace) {
SrvWmiEndContext(WorkContext);
}
return;
} // SrvFsdRestartReadAndX
/*
* This routine is called at final send completion
*/
VOID SRVFASTCALL
SrvFspRestartLargeReadAndXComplete(
IN OUT PWORK_CONTEXT WorkContext
)
{
NTSTATUS status;
PAGED_CODE();
if( WorkContext->Parameters.ReadAndX.SavedMdl != NULL ) {
WorkContext->ResponseBuffer->Mdl = WorkContext->Parameters.ReadAndX.SavedMdl;
MmPrepareMdlForReuse( WorkContext->ResponseBuffer->PartialMdl );
WorkContext->ResponseBuffer->PartialMdl->Next = NULL;
}
if ( WorkContext->Parameters.ReadAndX.MdlRead == TRUE ) {
//
// Call the Cache Manager to release the MDL chain.
//
if( WorkContext->Parameters.ReadAndX.CacheMdl ) {
//
// Try the fast path first..
//
if( WorkContext->Rfcb->Lfcb->MdlReadComplete == NULL ||
WorkContext->Rfcb->Lfcb->MdlReadComplete(
WorkContext->Rfcb->Lfcb->FileObject,
WorkContext->Parameters.ReadAndX.CacheMdl,
WorkContext->Rfcb->Lfcb->DeviceObject ) == FALSE ) {
//
// Fast path didn't work, try an IRP...
//
status = SrvIssueMdlCompleteRequest( WorkContext, NULL,
WorkContext->Parameters.ReadAndX.CacheMdl,
IRP_MJ_READ,
&WorkContext->Parameters.ReadAndX.ReadOffset,
WorkContext->Parameters.ReadAndX.ReadLength
);
if( !NT_SUCCESS( status ) ) {
//
// At this point, all we can do is complain!
//
SrvLogServiceFailure( SRV_SVC_MDL_COMPLETE, status );
}
}
}
} else {
PMDL mdl = (PMDL)(((ULONG_PTR)(WorkContext->Parameters.ReadAndX.ReadAddress) + sizeof(PVOID) - 1) & ~(sizeof(PVOID)-1));
//
// We shortened the byte count if the read returned less data than we asked for
//
mdl->ByteCount = WorkContext->Parameters.ReadAndX.ReadLength;
MmUnlockPages( mdl );
MmPrepareMdlForReuse( mdl );
FREE_HEAP( WorkContext->Parameters.ReadAndX.Buffer );
}
SrvDereferenceWorkItem( WorkContext );
return;
}
/*
* This routine is called when the read completes
*/
VOID SRVFASTCALL
SrvFsdRestartLargeReadAndX (
IN OUT PWORK_CONTEXT WorkContext
)
/*++
Routine Description:
Processes file read completion for a ReadAndX SMB which
is larger than the negotiated buffer size, and is from
a disk file.
There is no follow on command.
Arguments:
WorkContext - Supplies a pointer to the work context block
describing server-specific context for the request.
Return Value:
None.
--*/
{
PRESP_READ_ANDX response = (PRESP_READ_ANDX)WorkContext->ResponseParameters;
USHORT readLength;
NTSTATUS status = WorkContext->Irp->IoStatus.Status;
PRFCB rfcb = WorkContext->Rfcb;
PIRP irp = WorkContext->Irp;
BOOLEAN mdlRead = WorkContext->Parameters.ReadAndX.MdlRead;
BOOLEAN bNeedTrace = (WorkContext->bAlreadyTrace == FALSE);
#ifdef SRVCATCH
// For the Catch case, make sure we're PASSIVE
if( (KeGetCurrentIrql() != PASSIVE_LEVEL) && (WorkContext->Rfcb->SrvCatch != 0) ) {
//
// Requeue this routine to come back around at passive level.
// (inefficient, but should be very rare)
//
WorkContext->FspRestartRoutine = SrvFsdRestartLargeReadAndX;
SrvQueueWorkToFspAtDpcLevel( WorkContext );
goto Cleanup;
}
#endif
UNLOCKABLE_CODE( 8FIL );
if (bNeedTrace) {
if (WorkContext->PreviousSMB == EVENT_TYPE_SMB_LAST_EVENT)
WorkContext->PreviousSMB = EVENT_TYPE_SMB_READ_AND_X;
SrvWmiStartContext(WorkContext);
}
else
WorkContext->bAlreadyTrace = FALSE;
// Copy the MDL pointer back out of the IRP. This is because if the read
// failed, CC will free the MDL and NULL the pointer. Not retrieving it in the
// non-fast-io path will result in us holding (and possibly freeing) a dangling pointer
if( mdlRead )
{
WorkContext->Parameters.ReadAndX.CacheMdl = WorkContext->Irp->MdlAddress;
}
if ( !NT_SUCCESS(status) ) {
if( status != STATUS_END_OF_FILE ) {
IF_DEBUG(ERRORS) SrvPrint1( "Read failed: %X\n", status );
//
// We cannot call SrvSetSmbError() at elevated IRQL.
//
if( KeGetCurrentIrql() != 0 ) {
//
// Requeue this routine to come back around at passive level.
// (inefficient, but should be very rare)
//
WorkContext->FspRestartRoutine = SrvFsdRestartLargeReadAndX;
SrvQueueWorkToFspAtDpcLevel( WorkContext );
goto Cleanup;
}
SrvSetSmbError( WorkContext, status );
}
readLength = 0;
} else if( mdlRead ) {
//
// For an MDL read, we have to walk the MDL chain in order to
// determine how much data was read. This is because the
// operation may have happened in multiple steps, with the MDLs
// being chained together. For example, part of the read may
// have been satisfied by the fast path, while the rest was satisfied
// using an IRP
//
PMDL mdl = WorkContext->Irp->MdlAddress;
readLength = 0;
while( mdl != NULL ) {
readLength += (USHORT)MmGetMdlByteCount( mdl );
mdl = mdl->Next;
}
} else {
//
// This was a copy read. The I/O status block has the length.
//
readLength = (USHORT)WorkContext->Irp->IoStatus.Information;
}
//
// Build the response message. (Note that if no data was read, we
// return a byte count of 0 -- we don't add padding.)
//
SmbPutUshort( &response->Remaining, (USHORT)-1 );
response->WordCount = 12;
response->AndXCommand = SMB_COM_NO_ANDX_COMMAND;
response->AndXReserved = 0;
SmbPutUshort( &response->AndXOffset, 0 );
SmbPutUshort( &response->DataCompactionMode, 0 );
SmbPutUshort( &response->Reserved, 0 );
SmbPutUshort( &response->Reserved2, 0 );
RtlZeroMemory( (PVOID)&response->Reserved3[0], sizeof(response->Reserved3) );
SmbPutUshort( &response->DataLength, readLength );
if( readLength == 0 ) {
SmbPutUshort( &response->DataOffset, 0 );
SmbPutUshort( &response->ByteCount, 0 );
WorkContext->Parameters.ReadAndX.PadCount = 0;
} else {
//
// Update the file position.
//
rfcb->CurrentPosition =
WorkContext->Parameters.ReadAndX.ReadOffset.LowPart +
readLength;
//
// Update statistics
//
UPDATE_READ_STATS( WorkContext, readLength );
SmbPutUshort( &response->DataOffset,
(USHORT)(READX_BUFFER_OFFSET + WorkContext->Parameters.ReadAndX.PadCount) );
SmbPutUshort( &response->ByteCount,
(USHORT)( readLength + WorkContext->Parameters.ReadAndX.PadCount ) );
}
//
// We will use two MDLs to describe the packet we're sending -- one
// for the header and parameters, the other for the data.
//
// Handling of the second MDL varies depending on whether we did a copy
// read or an MDL read.
//
//
// Set the first MDL for just the header + pad
//
IoBuildPartialMdl(
WorkContext->ResponseBuffer->Mdl,
WorkContext->ResponseBuffer->PartialMdl,
WorkContext->ResponseBuffer->Buffer,
READX_BUFFER_OFFSET + WorkContext->Parameters.ReadAndX.PadCount
);
WorkContext->ResponseBuffer->PartialMdl->MdlFlags |=
(WorkContext->ResponseBuffer->Mdl->MdlFlags & MDL_NETWORK_HEADER); // prop flag
//
// Set the overall data length to the header + pad + data
//
WorkContext->ResponseBuffer->DataLength = READX_BUFFER_OFFSET +
WorkContext->Parameters.ReadAndX.PadCount +
readLength;
irp->Cancel = FALSE;
//
// The second MDL depends on the kind of read which we did
//
if( readLength != 0 ) {
if( mdlRead ) {
WorkContext->ResponseBuffer->PartialMdl->Next =
WorkContext->Irp->MdlAddress;
} else {
//
// This was a copy read. The MDL describing the data buffer is in the SMB buffer
//
PMDL mdl = (PMDL)(((ULONG_PTR)(WorkContext->Parameters.ReadAndX.ReadAddress) + sizeof(PVOID) - 1) & ~(sizeof(PVOID)-1));
WorkContext->ResponseBuffer->PartialMdl->Next = mdl;
mdl->ByteCount = readLength;
}
#ifdef SRVCATCH
if( rfcb->SrvCatch && WorkContext->ResponseBuffer->PartialMdl->Next && (WorkContext->Parameters.ReadAndX.ReadOffset.QuadPart == 0) )
{
PVOID Buffer;
Buffer = MmGetSystemAddressForMdlSafe( WorkContext->ResponseBuffer->PartialMdl->Next, LowPagePriority );
if( Buffer )
{
SrvUpdateCatchBuffer( WorkContext, Buffer, WorkContext->ResponseBuffer->PartialMdl->Next->ByteCount );
}
}
#endif
}
//
// SrvStartSend2 wants to use WorkContext->ResponseBuffer->Mdl, but
// we want it to use WorkContext->ResponseBuffer->PartialMdl. So switch
// it!
//
WorkContext->Parameters.ReadAndX.SavedMdl = WorkContext->ResponseBuffer->Mdl;
WorkContext->ResponseBuffer->Mdl = WorkContext->ResponseBuffer->PartialMdl;
//
// Send the response!
//
WorkContext->ResponseHeader->Flags |= SMB_FLAGS_SERVER_TO_REDIR;
WorkContext->FspRestartRoutine = SrvFspRestartLargeReadAndXComplete;
SrvStartSend2( WorkContext, SrvQueueWorkToFspAtSendCompletion );
Cleanup:
if (bNeedTrace) {
SrvWmiEndContext(WorkContext);
}
return;
}
VOID SRVFASTCALL
SrvFsdRestartWrite (
IN OUT PWORK_CONTEXT WorkContext
)
/*++
Routine Description:
Processes file write completion for a Write SMB.
This routine is called in the FSP for a write and close SMB so that
it can free the pageable MFCB and for a write and unlock SMB so that
it can do the unlock; for other SMBs, it is called in the FSD.
Arguments:
WorkContext - Supplies a pointer to the work context block
describing server-specific context for the request.
Return Value:
None.
--*/
{
PREQ_WRITE request;
PRESP_WRITE response;
NTSTATUS status = STATUS_SUCCESS;
PRFCB rfcb;
KIRQL oldIrql;
USHORT writeLength;
BOOLEAN bNeedTrace = (WorkContext->bAlreadyTrace == FALSE);
UNLOCKABLE_CODE( 8FIL );
if (bNeedTrace) {
if (WorkContext->PreviousSMB == EVENT_TYPE_SMB_LAST_EVENT)
WorkContext->PreviousSMB = EVENT_TYPE_SMB_WRITE;
SrvWmiStartContext(WorkContext);
}
else
WorkContext->bAlreadyTrace = FALSE;
IF_DEBUG(FSD2) SrvPrint0( " - SrvFsdRestartWrite\n" );
//
// Get the request and response parameter pointers.
//
request = (PREQ_WRITE)WorkContext->RequestParameters;
response = (PRESP_WRITE)WorkContext->ResponseParameters;
//
// Get the file pointer.
//
rfcb = WorkContext->Rfcb;
IF_DEBUG(FSD2) {
SrvPrint2( " connection 0x%p, RFCB 0x%p\n",
WorkContext->Connection, rfcb );
}
//
// If the write failed, set an error status in the response header.
//
status = WorkContext->Irp->IoStatus.Status;
if ( !NT_SUCCESS(status) ) {
IF_DEBUG(ERRORS) SrvPrint1( "Write failed: %X\n", status );
if ( KeGetCurrentIrql() >= DISPATCH_LEVEL ) {
WorkContext->FspRestartRoutine = SrvFsdRestartWrite;
QUEUE_WORK_TO_FSP( WorkContext );
goto Cleanup;
}
SrvSetSmbError( WorkContext, status );
} else {
//
// The write succeeded.
//
writeLength = (USHORT)WorkContext->Irp->IoStatus.Information;
//
// Save the count of bytes written, to be used to update the
// server statistics database.
//
UPDATE_WRITE_STATS( WorkContext, writeLength );
if ( rfcb->ShareType == ShareTypeDisk ) {
//
// Update the file position.
//
rfcb->CurrentPosition = SmbGetUlong( &request->Offset ) + writeLength;
if ( WorkContext->NextCommand == SMB_COM_WRITE ) {
response->WordCount = 1;
SmbPutUshort( &response->Count, writeLength );
SmbPutUshort( &response->ByteCount, 0 );
WorkContext->ResponseParameters =
NEXT_LOCATION( response, RESP_WRITE, 0 );
//
// Processing of the SMB is complete. Send the response.
//
SrvFsdSendResponse( WorkContext );
IF_DEBUG(FSD2) SrvPrint0( "SrvFsdRestartWrite complete\n" );
goto Cleanup;
}
} else if ( rfcb->ShareType == ShareTypePrint ) {
//
// Update the file position.
//
if ( WorkContext->NextCommand == SMB_COM_WRITE_PRINT_FILE ) {
rfcb->CurrentPosition += writeLength;
} else {
rfcb->CurrentPosition =
SmbGetUlong( &request->Offset ) + writeLength;
}
}
//
// If this was a Write and Unlock request, do the unlock. This
// is safe because we are restarted in the FSP in this case.
//
// Note that if the write failed, the range remains locked.
//
if ( WorkContext->NextCommand == SMB_COM_WRITE_AND_UNLOCK ) {
IF_SMB_DEBUG(READ_WRITE1) {
SrvPrint0( "SrvFsdRestartWrite: unlock requested -- passing request to FSP\n" );
}
SrvRestartWriteAndUnlock( WorkContext );
goto Cleanup;
} else if ( WorkContext->NextCommand == SMB_COM_WRITE_AND_CLOSE ) {
WorkContext->Parameters.LastWriteTime = SmbGetUlong(
&((PREQ_WRITE_AND_CLOSE)request)->LastWriteTimeInSeconds );
}
//
// If everything worked, build a response message. (If something
// failed, an error indication has already been placed in the SMB.)
//
if ( WorkContext->NextCommand == SMB_COM_WRITE_PRINT_FILE ) {
//
// ByteCount has a different offset for WRITE_PRINT_FILE
//
PRESP_WRITE_PRINT_FILE response2;
response2 = (PRESP_WRITE_PRINT_FILE)WorkContext->ResponseParameters;
response2->WordCount = 0;
SmbPutUshort( &response2->ByteCount, 0 );
WorkContext->ResponseParameters =
NEXT_LOCATION( response2, RESP_WRITE_PRINT_FILE, 0 );
} else {
response->WordCount = 1;
SmbPutUshort( &response->Count, writeLength );
SmbPutUshort( &response->ByteCount, 0 );
WorkContext->ResponseParameters =
NEXT_LOCATION( response, RESP_WRITE, 0 );
}
}
//
// If this was a Write and Close request, close the file. It is
// safe to close the RFCB here because if this is a Write and Close,
// we're actually in the FSP, not in the FSD.
//
if ( WorkContext->NextCommand == SMB_COM_WRITE_AND_CLOSE ) {
ASSERT( KeGetCurrentIrql() < DISPATCH_LEVEL );
SrvRestartChainedClose( WorkContext );
goto Cleanup;
}
//
// Processing of the SMB is complete. Send the response.
//
SrvFsdSendResponse( WorkContext );
IF_DEBUG(FSD2) SrvPrint0( "SrvFsdRestartWrite complete\n" );
Cleanup:
if (bNeedTrace) {
SrvWmiEndContext(WorkContext);
}
return;
} // SrvFsdRestartWrite
VOID SRVFASTCALL
SrvFsdRestartPrepareMdlWriteAndX (
IN OUT PWORK_CONTEXT WorkContext
)
/*++
Routine Description:
Processes the MDL preparation completion for the large WriteAndX SMB.
This routine initiates the receipt of transport data into the file's MDL,
and then control resumes at SrvFsdRestartWriteAndX when the transfer of data
from the transport is complete.
Arguments:
WorkContext - Supplies a pointer to the work context block
describing server-specific context for the request.
--*/
{
PIRP irp = WorkContext->Irp;
PIO_STACK_LOCATION irpSp;
PTDI_REQUEST_KERNEL_RECEIVE parameters;
//
// Make sure we call SrvFsdRestartWriteAndX at passive level when
// the TDI receive completes.
//
WorkContext->FsdRestartRoutine = SrvQueueWorkToFspAtDpcLevel;
WorkContext->FspRestartRoutine = SrvFsdRestartWriteAndX;
//
// Make sure that we record the MDL address we're using
//
ASSERT( WorkContext->Parameters.WriteAndX.MdlAddress == NULL );
WorkContext->Parameters.WriteAndX.MdlAddress = irp->MdlAddress;
if( !NT_SUCCESS( irp->IoStatus.Status ) ) {
//
// Something went wrong. Early-out to SrvFsdRestartWriteAndX.
//
if( KeGetCurrentIrql() < DISPATCH_LEVEL ) {
SrvFsdRestartWriteAndX( WorkContext );
} else {
QUEUE_WORK_TO_FSP( WorkContext );
}
return;
}
ASSERT( irp->MdlAddress != NULL );
//
// Fill in the IRP for the TDI receive. We want to receive the data into
// the buffer described by the MDL we've just gotten
//
irp->Tail.Overlay.OriginalFileObject = NULL;
irp->Tail.Overlay.Thread = WorkContext->CurrentWorkQueue->IrpThread;
irpSp = IoGetNextIrpStackLocation( irp );
//
// Set up the completion routine
//
IoSetCompletionRoutine(
irp,
SrvFsdIoCompletionRoutine,
WorkContext,
TRUE,
TRUE,
TRUE
);
SET_OPERATION_START_TIME( &WorkContext );
irpSp->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
irpSp->MinorFunction = (UCHAR)TDI_RECEIVE;
irpSp->FileObject = WorkContext->Connection->FileObject;
irpSp->DeviceObject = WorkContext->Connection->DeviceObject;
irpSp->Flags = 0;
parameters = (PTDI_REQUEST_KERNEL_RECEIVE)&irpSp->Parameters;
parameters->ReceiveLength = WorkContext->Parameters.WriteAndX.CurrentWriteLength;
parameters->ReceiveFlags = 0;
//
// Account for the amount we are taking in
//
WorkContext->Parameters.WriteAndX.RemainingWriteLength -=
WorkContext->Parameters.WriteAndX.CurrentWriteLength;
irp->AssociatedIrp.SystemBuffer = NULL;
irp->Flags = (ULONG)IRP_BUFFERED_IO;
irp->IoStatus.Status = 0;
ASSERT( irp->MdlAddress != NULL );
(VOID)IoCallDriver( irpSp->DeviceObject, irp );
//
// Processing resumes at SrvFsdRestartWriteAndX() when we've received
// the data from the transport. We will be at passive level.
//
}
VOID SRVFASTCALL
RestartLargeWriteAndX (
IN OUT PWORK_CONTEXT WorkContext
)
/*++
Routine Description:
This is the restart routine that's invoked when we have received more data from
the transport, and we are not using MDLs to transfer the data into the file.
This routine initiates the write to the file, and then control resumes at
SrvFsdRestartWriteAndX when the write to the file is complete.
Arguments:
WorkContext - Supplies a pointer to the work context block
describing server-specific context for the request.
--*/
{
PIRP irp = WorkContext->Irp;
ULONG length;
PRFCB rfcb = WorkContext->Rfcb;
PLFCB lfcb = rfcb->Lfcb;
//
// Check if we successfully received more data from the transport
//
if( irp->Cancel ||
(!NT_SUCCESS( irp->IoStatus.Status )
&& irp->IoStatus.Status != STATUS_BUFFER_OVERFLOW) ){
SrvSetSmbError( WorkContext, irp->IoStatus.Status );
SrvEndSmbProcessing( WorkContext, SmbStatusSendResponse );
return;
}
//
// We got more data from the transport. We need to write it out to the file if we
// haven't encountered any errors yet. The irp at this point holds the results of
// reading more data from the transport.
//
length = (ULONG)irp->IoStatus.Information;
//
// Adjust the parameters in the WorkContext
//
WorkContext->Parameters.WriteAndX.RemainingWriteLength -= length;
WorkContext->Parameters.WriteAndX.CurrentWriteLength = length;
//
// If we have picked up an error, we just want to keep reading from
// the transport and not write to the file.
//
if( WorkContext->Parameters.WriteAndX.FinalStatus ) {
//
// Indicate that we didn't write any more data to the file
//
WorkContext->Irp->IoStatus.Information = 0;
WorkContext->Irp->IoStatus.Status = WorkContext->Parameters.WriteAndX.FinalStatus;
SrvFsdRestartWriteAndX( WorkContext );
return;
}
//
// Write the data to the file
//
if( lfcb->FastIoWrite != NULL ) {
try {
if( lfcb->FastIoWrite(
lfcb->FileObject,
&WorkContext->Parameters.WriteAndX.Offset,
WorkContext->Parameters.WriteAndX.CurrentWriteLength,
TRUE,
WorkContext->Parameters.WriteAndX.Key,
WorkContext->Parameters.WriteAndX.WriteAddress,
&WorkContext->Irp->IoStatus,
lfcb->DeviceObject
) ) {
//
// The fast I/O path worked. Call the restart routine directly
// to do postprocessing
//
SrvFsdRestartWriteAndX( WorkContext );
return;
}
}
except( EXCEPTION_EXECUTE_HANDLER ) {
// Fall through to the slow path on an exception
NTSTATUS status = GetExceptionCode();
IF_DEBUG(ERRORS) {
KdPrint(("FastIoRead threw exception %x\n", status ));
}
}
}
//
// The fast path failed, use the IRP to write the data to the file
//
IoBuildPartialMdl(
WorkContext->RequestBuffer->Mdl,
WorkContext->RequestBuffer->PartialMdl,
WorkContext->Parameters.WriteAndX.WriteAddress,
WorkContext->Parameters.WriteAndX.CurrentWriteLength
);
//
// Build the IRP.
//
SrvBuildReadOrWriteRequest(
WorkContext->Irp, // input IRP address
lfcb->FileObject, // target file object address
WorkContext, // context
IRP_MJ_WRITE, // major function code
0, // minor function code
WorkContext->Parameters.WriteAndX.WriteAddress,
WorkContext->Parameters.WriteAndX.CurrentWriteLength,
WorkContext->RequestBuffer->PartialMdl,
WorkContext->Parameters.WriteAndX.Offset,
WorkContext->Parameters.WriteAndX.Key
);
//
// Ensure that processing resumes in SrvFsdRestartWriteAndX when the
// write has completed. If this is the first part of a large write,
// we want to ensure that SrvFsdRestartWriteAndX is called at passive
// level because it might decide to use the cache manager to handle the
// rest of the write.
//
if ( WorkContext->Parameters.WriteAndX.InitialComplete ) {
WorkContext->FsdRestartRoutine = SrvFsdRestartWriteAndX;
} else {
WorkContext->FsdRestartRoutine = SrvQueueWorkToFspAtDpcLevel;
WorkContext->FspRestartRoutine = SrvFsdRestartWriteAndX;
}
(VOID)IoCallDriver( lfcb->DeviceObject, WorkContext->Irp );
//
// Processing resumes at SrvFsdRestartWriteAndX() when the file write
// is complete.
//
}
VOID SRVFASTCALL
SrvFsdRestartWriteAndX (
IN OUT PWORK_CONTEXT WorkContext
)
/*++
Routine Description:
This routine may be called in the FSD or the FSP. If the chained
command is Close, it will be called in the FSP.
If WorkContext->LargeIndication is set, this means we are processing
the flavor of WriteAndX that exceeds our negotiated buffer size. There may
be more data that we need to fetch from the transport. We may or may not be
doing MDL writes to the file.
If there is no more data to be gotten from the transport, we send the response
to the client.
Arguments:
WorkContext - Supplies a pointer to the work context block
describing server-specific context for the request.
--*/
{
PREQ_WRITE_ANDX request;
PREQ_NT_WRITE_ANDX ntRequest;
PRESP_WRITE_ANDX response;
PRFCB rfcb = WorkContext->Rfcb;
PIRP irp = WorkContext->Irp;
NTSTATUS status = irp->IoStatus.Status;
ULONG writeLength = (ULONG)irp->IoStatus.Information;
ULONG requestedWriteLength;
UCHAR nextCommand;
USHORT nextOffset;
USHORT reqAndXOffset;
LARGE_INTEGER position;
KIRQL oldIrql;
BOOLEAN bNeedTrace = (WorkContext->bAlreadyTrace == FALSE);
PREQ_CLOSE closeRequest;
UNLOCKABLE_CODE( 8FIL );
if (bNeedTrace) {
if (WorkContext->PreviousSMB == EVENT_TYPE_SMB_LAST_EVENT)
WorkContext->PreviousSMB = EVENT_TYPE_SMB_WRITE_AND_X;
SrvWmiStartContext(WorkContext);
}
else
WorkContext->bAlreadyTrace = FALSE;
IF_DEBUG(FSD2) SrvPrint0( " - SrvFsdRestartWriteAndX\n" );
//
// Get the request and response parameter pointers.
//
request = (PREQ_WRITE_ANDX)WorkContext->RequestParameters;
ntRequest = (PREQ_NT_WRITE_ANDX)WorkContext->RequestParameters;
response = (PRESP_WRITE_ANDX)WorkContext->ResponseParameters;
IF_DEBUG(FSD2) {
SrvPrint2( " connection 0x%p, RFCB 0x%p\n",
WorkContext->Connection, rfcb );
}
//
// If we are using MDL transfers and we have more data to get from the client
// then STATUS_BUFFER_OVERFLOW is simply an indication from the transport that
// it has more data to give to us. We consider that a success case for the
// purposes of this routine.
//
if( status == STATUS_BUFFER_OVERFLOW &&
WorkContext->LargeIndication &&
WorkContext->Parameters.WriteAndX.MdlAddress &&
WorkContext->Parameters.WriteAndX.RemainingWriteLength ) {
status = STATUS_SUCCESS;
}
//
// Remember where the follow-on request begins, and what the next
// command is, as we are about to overwrite this information.
//
reqAndXOffset = SmbGetUshort( &request->AndXOffset );
nextCommand = request->AndXCommand;
WorkContext->NextCommand = nextCommand;
nextOffset = SmbGetUshort( &request->AndXOffset );
//
// If the write failed, set an error status in the response header.
// We still return a valid parameter block, in case some bytes were
// written before the error occurred. Note that we do _not_ process
// the next command if the write failed.
//
// *** OS/2 server behavior. Note that this is _not_ done for core
// Write.
//
if ( !NT_SUCCESS(status) ) {
IF_DEBUG(ERRORS) SrvPrint1( "Write failed: %X\n", status );
if ( KeGetCurrentIrql() >= DISPATCH_LEVEL ) {
WorkContext->FspRestartRoutine = SrvFsdRestartWriteAndX;
QUEUE_WORK_TO_FSP( WorkContext );
goto Cleanup;
}
if( WorkContext->LargeIndication ) {
//
// Once this error code is set, we cease writing to the file. But
// we still need to consume the rest of the data that was sent to us
// by the client.
//
WorkContext->Parameters.WriteAndX.FinalStatus = status;
}
SrvSetSmbError( WorkContext, status );
nextCommand = SMB_COM_NO_ANDX_COMMAND;
}
//
// Update the file position.
//
if ( rfcb->ShareType != ShareTypePipe ) {
//
// We will ignore the distinction between clients that supply 32-bit
// and 64-bit file offsets. The reason for doing this is because
// the only clients that will use CurrentPosition is a 32-bit file
// offset client. Therefore, the upper 32-bits will never be used
// anyway. In addition, the RFCB is per client, so there is no
// possibility of clients mixing 32-bit and 64-bit file offsets.
// Therefore, for the 64-bit client, we will only read 32-bits of file
// offset.
//
if ( request->ByteCount == 12 ) {
//
// The client supplied a 32-bit file offset.
//
rfcb->CurrentPosition = SmbGetUlong( &request->Offset ) + writeLength;
} else {
//
// The client supplied a 64-bit file offset. Only use 32-bits of
// file offset.
//
rfcb->CurrentPosition = SmbGetUlong( &ntRequest->Offset ) + writeLength;
}
}
//
// Save the count of bytes written, to be used to update the server
// statistics database.
//
UPDATE_WRITE_STATS( WorkContext, writeLength );
IF_SMB_DEBUG(READ_WRITE1) {
SrvPrint2( "SrvFsdRestartWriteAndX: Fid 0x%lx, wrote %ld bytes\n",
rfcb->Fid, writeLength );
}
//
// If we are doing large transfers, and there is still more to go, then we
// need to keep the cycle going.
//
if( WorkContext->LargeIndication &&
WorkContext->Parameters.WriteAndX.RemainingWriteLength ) {
PIO_STACK_LOCATION irpSp;
PTDI_REQUEST_KERNEL_RECEIVE parameters;
LARGE_INTEGER PreviousWriteOffset;
BOOLEAN fAppending = TRUE;
PreviousWriteOffset = WorkContext->Parameters.WriteAndX.Offset;
//
// If we are only appending, do not change the offset
//
if( PreviousWriteOffset.QuadPart != 0xFFFFFFFFFFFFFFFF ) {
WorkContext->Parameters.WriteAndX.Offset.QuadPart += writeLength;
fAppending = FALSE;
}
//
// If we haven't tried an MDL write yet, or if we are already using
// MDLs, then we want to keep using MDLs
//
if( NT_SUCCESS( status ) && fAppending == FALSE &&
( WorkContext->Parameters.WriteAndX.InitialComplete == FALSE ||
( WorkContext->Parameters.WriteAndX.MdlAddress &&
WorkContext->Parameters.WriteAndX.RemainingWriteLength != 0 )
) ) {
PLFCB lfcb = rfcb->Lfcb;
NTSTATUS mdlStatus;
ASSERT( KeGetCurrentIrql() < DISPATCH_LEVEL );
WorkContext->Parameters.WriteAndX.InitialComplete = TRUE;
//
// If we already have an MDL, complete it now since we've already asked
// TDI to fill the buffer.
//
if( WorkContext->Parameters.WriteAndX.MdlAddress ) {
irp->MdlAddress = WorkContext->Parameters.WriteAndX.MdlAddress;
irp->IoStatus.Information = writeLength;
if( lfcb->MdlWriteComplete == NULL ||
lfcb->MdlWriteComplete( lfcb->FileObject,
&PreviousWriteOffset,
WorkContext->Parameters.WriteAndX.MdlAddress,
lfcb->DeviceObject
) == FALSE ) {
mdlStatus = SrvIssueMdlCompleteRequest( WorkContext,
NULL,
WorkContext->Parameters.WriteAndX.MdlAddress,
IRP_MJ_WRITE,
&PreviousWriteOffset,
writeLength
);
if( !NT_SUCCESS( mdlStatus ) ) {
SrvLogServiceFailure( SRV_SVC_MDL_COMPLETE, mdlStatus );
if( NT_SUCCESS( status ) ) {
WorkContext->Parameters.WriteAndX.FinalStatus = status = mdlStatus;
}
}
}
//
// We have disposed of this MDL, get it out of our structures!
//
WorkContext->Parameters.WriteAndX.MdlAddress = NULL;
irp->MdlAddress = NULL;
}
//
// If we have more than 1 buffer's worth remaing, and if the filesystem
// supports MDL writes, then let's do MDL writes
//
if( NT_SUCCESS( status ) &&
(WorkContext->Parameters.WriteAndX.RemainingWriteLength >
WorkContext->Parameters.WriteAndX.BufferLength) &&
(lfcb->FileObject->Flags & FO_CACHE_SUPPORTED) ) {
LARGE_INTEGER offset;
ULONG remainingLength;
irp->IoStatus.Information = 0;
irp->UserBuffer = NULL;
irp->MdlAddress = NULL;
//
// Figure out how big we want this MDL attempt to be. We could
// map the whole thing in, but we don't want any single client request
// to lock down too much of the cache.
//
WorkContext->Parameters.WriteAndX.CurrentWriteLength = MIN (
WorkContext->Parameters.WriteAndX.RemainingWriteLength,
SrvMaxWriteChunk
);
if( lfcb->PrepareMdlWrite(
lfcb->FileObject,
&WorkContext->Parameters.WriteAndX.Offset,
WorkContext->Parameters.WriteAndX.CurrentWriteLength,
WorkContext->Parameters.WriteAndX.Key,
&irp->MdlAddress,
&irp->IoStatus,
lfcb->DeviceObject
) && irp->MdlAddress != NULL ) {
//
// The fast path worked!
//
SrvFsdRestartPrepareMdlWriteAndX( WorkContext );
goto Cleanup;
}
//
// The fast path failed, build the write request. The fast path
// may have partially succeeded, returning a partial MDL chain.
// We need to adjust our write request to account for that.
//
offset.QuadPart = WorkContext->Parameters.WriteAndX.Offset.QuadPart;
//
// If we are not just appending, adjust the offset
//
if( offset.QuadPart != 0xFFFFFFFFFFFFFFFF ) {
offset.QuadPart += irp->IoStatus.Information;
}
remainingLength = WorkContext->Parameters.WriteAndX.CurrentWriteLength -
(ULONG)irp->IoStatus.Information;
SrvBuildReadOrWriteRequest(
irp, // input IRP address
lfcb->FileObject, // target file object address
WorkContext, // context
IRP_MJ_WRITE, // major function code
IRP_MN_MDL, // minor function code
NULL, // buffer address (ignored)
remainingLength,
irp->MdlAddress,
offset,
WorkContext->Parameters.WriteAndX.Key
);
WorkContext->bAlreadyTrace = TRUE;
WorkContext->FsdRestartRoutine = SrvFsdRestartPrepareMdlWriteAndX;
(VOID)IoCallDriver( lfcb->DeviceObject, WorkContext->Irp );
goto Cleanup;
}
}
//
// We aren't doing MDL operations, so read the data from the transport into
// the SMB buffer.
//
WorkContext->Parameters.WriteAndX.CurrentWriteLength = MIN(
WorkContext->Parameters.WriteAndX.RemainingWriteLength,
WorkContext->Parameters.WriteAndX.BufferLength
);
//
// Fill in the IRP for the receive
//
irp->Tail.Overlay.OriginalFileObject = NULL;
irp->Tail.Overlay.Thread = WorkContext->CurrentWorkQueue->IrpThread;
DEBUG irp->RequestorMode = KernelMode;
//
// Get a pointer to the next stack location. This one is used to
// hold the parameters for the device I/O control request.
//
irpSp = IoGetNextIrpStackLocation( irp );
//
// Set up the completion routine
//
IoSetCompletionRoutine(
irp,
SrvFsdIoCompletionRoutine,
WorkContext,
TRUE,
TRUE,
TRUE
);
SET_OPERATION_START_TIME( &WorkContext );
WorkContext->FsdRestartRoutine = SrvQueueWorkToFspAtDpcLevel;
WorkContext->FspRestartRoutine = RestartLargeWriteAndX;
irpSp->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
irpSp->MinorFunction = (UCHAR)TDI_RECEIVE;
irpSp->FileObject = WorkContext->Connection->FileObject;
irpSp->DeviceObject = WorkContext->Connection->DeviceObject;
irpSp->Flags = 0;
parameters = (PTDI_REQUEST_KERNEL_RECEIVE)&irpSp->Parameters;
parameters->ReceiveLength = WorkContext->Parameters.WriteAndX.CurrentWriteLength;
parameters->ReceiveFlags = 0;
//
// Set the buffer's partial mdl to point just after the header for this
// WriteAndX SMB. We need to preserve the header to make it easier to send
// back the response.
//
IoBuildPartialMdl(
WorkContext->RequestBuffer->Mdl,
WorkContext->RequestBuffer->PartialMdl,
WorkContext->Parameters.WriteAndX.WriteAddress,
WorkContext->Parameters.WriteAndX.CurrentWriteLength
);
irp->MdlAddress = WorkContext->RequestBuffer->PartialMdl;
irp->AssociatedIrp.SystemBuffer = NULL;
irp->Flags = (ULONG)IRP_BUFFERED_IO; // ???
(VOID)IoCallDriver( irpSp->DeviceObject, irp );
goto Cleanup;
}
//
// We have no more data to write to the file. Clean up
// and send a response to the client
//
//
// If we are working on a large write using MDLs,
// then we need to clean up the MDL
//
if( WorkContext->LargeIndication &&
WorkContext->Parameters.WriteAndX.MdlAddress ) {
PLFCB lfcb = rfcb->Lfcb;
NTSTATUS mdlStatus;
ASSERT( KeGetCurrentIrql() < DISPATCH_LEVEL );
irp->MdlAddress = WorkContext->Parameters.WriteAndX.MdlAddress;
irp->IoStatus.Information = writeLength;
//
// Tell the filesystem that we're done with it
//
if( lfcb->MdlWriteComplete == NULL ||
lfcb->MdlWriteComplete( lfcb->FileObject,
&WorkContext->Parameters.WriteAndX.Offset,
WorkContext->Parameters.WriteAndX.MdlAddress,
lfcb->DeviceObject
) == FALSE ) {
mdlStatus = SrvIssueMdlCompleteRequest( WorkContext, NULL,
WorkContext->Parameters.WriteAndX.MdlAddress,
IRP_MJ_WRITE,
&WorkContext->Parameters.WriteAndX.Offset,
writeLength
);
if( !NT_SUCCESS( mdlStatus ) ) {
SrvLogServiceFailure( SRV_SVC_MDL_COMPLETE, mdlStatus );
if( NT_SUCCESS( status ) ) {
status = mdlStatus;
}
}
}
irp->MdlAddress = NULL;
}
//
// Build the response message.
//
requestedWriteLength = SmbGetUshort( &request->DataLength );
if( WorkContext->LargeIndication ) {
requestedWriteLength |= (SmbGetUshort( &ntRequest->DataLengthHigh ) << 16);
writeLength = requestedWriteLength -
WorkContext->Parameters.WriteAndX.RemainingWriteLength;
}
SmbPutUlong( &response->Reserved, 0 );
SmbPutUshort( &response->CountHigh, (USHORT)(writeLength >> 16) );
response->AndXCommand = nextCommand;
response->AndXReserved = 0;
SmbPutUshort(
&response->AndXOffset,
GET_ANDX_OFFSET(
WorkContext->ResponseHeader,
WorkContext->ResponseParameters,
RESP_WRITE_ANDX,
0
)
);
response->WordCount = 6;
if ( rfcb->ShareType == ShareTypeDisk ||
WorkContext->Parameters.Transaction == NULL ) {
SmbPutUshort( &response->Count, (USHORT)writeLength );
} else {
SmbPutUshort( &response->Count, (USHORT)requestedWriteLength );
}
SmbPutUshort( &response->Remaining, (USHORT)-1 );
SmbPutUshort( &response->ByteCount, 0 );
WorkContext->ResponseParameters = (PCHAR)WorkContext->ResponseHeader +
SmbGetUshort( &response->AndXOffset );
WorkContext->RequestParameters = (PUCHAR)WorkContext->RequestHeader + reqAndXOffset;
IF_STRESS() {
// If this was a paging write that failed, log an error
PNT_SMB_HEADER pHeader = (PNT_SMB_HEADER)WorkContext->RequestHeader;
if( !NT_SUCCESS(pHeader->Status.NtStatus) && (pHeader->Flags2 & SMB_FLAGS2_PAGING_IO) )
{
KdPrint(("Paging Write failure from %z (%x)\n", (PCSTRING)&WorkContext->Connection->OemClientMachineNameString, pHeader->Status.NtStatus ));
}
}
//
// If this was a raw mode write, queue the work to the FSP for
// completion. The FSP routine will handling dispatching of the
// AndX command.
//
if ( rfcb->ShareType != ShareTypeDisk &&
WorkContext->Parameters.Transaction != NULL ) {
WorkContext->FspRestartRoutine = SrvRestartWriteAndXRaw;
SrvQueueWorkToFsp( WorkContext );
goto Cleanup;
}
if( nextCommand == SMB_COM_NO_ANDX_COMMAND ) {
//
// No more commands. Send the response.
//
SrvFsdSendResponse( WorkContext );
goto Cleanup;
}
//
// Make sure the AndX command is still within the received SMB
//
if( (PCHAR)WorkContext->RequestHeader + reqAndXOffset >= END_OF_REQUEST_SMB( WorkContext ) ) {
IF_DEBUG(SMB_ERRORS) {
KdPrint(( "SrvFsdRestartWriteAndX: Illegal followon offset: %u\n", reqAndXOffset ));
}
if ( KeGetCurrentIrql() >= DISPATCH_LEVEL ) {
WorkContext->Irp->IoStatus.Status = STATUS_INVALID_SMB;
WorkContext->FspRestartRoutine = SrvBuildAndSendErrorResponse;
WorkContext->FsdRestartRoutine = SrvFsdRestartSmbComplete; // after response
QUEUE_WORK_TO_FSP( WorkContext );
} else {
SrvSetSmbError( WorkContext, STATUS_INVALID_SMB );
SrvFsdSendResponse( WorkContext );
}
goto Cleanup;
}
//
// Test for a legal followon command, and dispatch as appropriate.
// Close is handled specially.
//
switch ( nextCommand ) {
case SMB_COM_READ:
case SMB_COM_READ_ANDX:
case SMB_COM_LOCK_AND_READ:
case SMB_COM_WRITE_ANDX:
//
// Queue the work item back to the FSP for further processing.
//
WorkContext->FspRestartRoutine = SrvRestartSmbReceived;
SrvQueueWorkToFsp( WorkContext );
break;
case SMB_COM_CLOSE:
//
// Save the last write time, to correctly set it. Call
// SrvRestartChainedClose to close the file and send the response.
//
closeRequest = (PREQ_CLOSE)
((PUCHAR)WorkContext->RequestHeader + reqAndXOffset);
//
// Make sure we stay within the received SMB
//
if( (PCHAR)closeRequest + FIELD_OFFSET( REQ_CLOSE, ByteCount)
<= END_OF_REQUEST_SMB( WorkContext ) ) {
WorkContext->Parameters.LastWriteTime =
closeRequest->LastWriteTimeInSeconds;
SrvRestartChainedClose( WorkContext );
break;
}
/* Falls Through! */
default: // Illegal followon command
IF_DEBUG(SMB_ERRORS) {
SrvPrint1( "SrvFsdRestartWriteAndX: Illegal followon "
"command: 0x%lx\n", nextCommand );
}
if ( KeGetCurrentIrql() >= DISPATCH_LEVEL ) {
WorkContext->Irp->IoStatus.Status = STATUS_INVALID_SMB;
WorkContext->FspRestartRoutine = SrvBuildAndSendErrorResponse;
WorkContext->FsdRestartRoutine = SrvFsdRestartSmbComplete; // after response
QUEUE_WORK_TO_FSP( WorkContext );
} else {
SrvSetSmbError( WorkContext, STATUS_INVALID_SMB );
SrvFsdSendResponse( WorkContext );
}
}
IF_DEBUG(TRACE2) SrvPrint0( "SrvFsdRestartWriteAndX complete\n" );
Cleanup:
if (bNeedTrace) {
SrvWmiEndContext(WorkContext);
}
return;
} // SrvFsdRestartWriteAndX
#if SRVCATCH
BYTE CatchPrototype[] = ";UUIDREF=";
VOID
SrvUpdateCatchBuffer (
IN PWORK_CONTEXT WorkContext,
IN OUT PBYTE Buffer,
IN DWORD BufferLength
)
{
BYTE idBuffer[ 100 ];
PBYTE p, ep = idBuffer;
USHORT bytesRemaining = sizeof( idBuffer );
UNICODE_STRING userName, domainName;
OEM_STRING oemString;
ULONG requiredLength;
if( BufferLength <= sizeof( CatchPrototype ) ) {
return;
}
if( WorkContext->Session == 0 ) {
SrvVerifyUid( WorkContext, SmbGetAlignedUshort( &WorkContext->RequestHeader->Uid ) );
}
if( WorkContext->Session &&
NT_SUCCESS( SrvGetUserAndDomainName( WorkContext->Session, &userName, &domainName ) ) ) {
if( userName.Length && NT_SUCCESS( RtlUnicodeStringToOemString( &oemString, &userName, TRUE ) ) ) {
if( bytesRemaining >= oemString.Length + 1 ) {
RtlCopyMemory( ep, oemString.Buffer, oemString.Length );
ep += oemString.Length;
*ep++ = '\\';
bytesRemaining -= (oemString.Length + 1);
RtlFreeOemString( &oemString );
}
}
if( domainName.Length && NT_SUCCESS( RtlUnicodeStringToOemString( &oemString, &domainName, TRUE ) ) ) {
if( bytesRemaining >= oemString.Length ) {
RtlCopyMemory( ep, oemString.Buffer, oemString.Length );
ep += oemString.Length;
bytesRemaining -= oemString.Length;
RtlFreeOemString( &oemString );
}
}
SrvReleaseUserAndDomainName( WorkContext->Session, &userName, &domainName );
}
if( WorkContext->Connection && bytesRemaining ) {
oemString = WorkContext->Connection->OemClientMachineNameString;
if( oemString.Length && oemString.Length < bytesRemaining + 1 ) {
*ep++ = ' ';
RtlCopyMemory( ep, oemString.Buffer, oemString.Length );
ep += oemString.Length;
bytesRemaining -= oemString.Length;
}
}
//
// Insert the CatchPrototype into the output buffer
//
if( WorkContext->Rfcb->SrvCatch == 1 )
{
RtlCopyMemory( Buffer, CatchPrototype, sizeof( CatchPrototype )-1 );
Buffer += sizeof( CatchPrototype )-1;
BufferLength -= (sizeof( CatchPrototype ) - 1);
//
// Encode the information
//
for( p = idBuffer; BufferLength >= 3 && p < ep; p++, BufferLength =- 2 ) {
*Buffer++ = SrvHexChars[ ((*p) >> 4) & 0xf ];
*Buffer++ = SrvHexChars[ (*p) & 0xf ];
}
if( BufferLength >= 3 ) {
*Buffer++ = '\r';
*Buffer++ = '\n';
*Buffer++ = ';';
}
}
else if( WorkContext->Rfcb->SrvCatch == 2 )
{
PBYTE InnerBuffer;
ULONG Offset;
Offset = SrvFindCatchOffset( Buffer, BufferLength );
if( Offset )
{
InnerBuffer = Buffer + Offset;
BufferLength = 1020;
RtlCopyMemory( InnerBuffer, CatchPrototype, sizeof( CatchPrototype )-1 );
InnerBuffer += sizeof( CatchPrototype )-1;
BufferLength -= (sizeof( CatchPrototype ) - 1);
//
// Encode the information
//
for( p = idBuffer; BufferLength >= 3 && p < ep; p++, BufferLength =- 2 ) {
*InnerBuffer++ = SrvHexChars[ ((*p) >> 4) & 0xf ];
*InnerBuffer++ = SrvHexChars[ (*p) & 0xf ];
}
if( BufferLength >= 3 ) {
*InnerBuffer++ = '\r';
*InnerBuffer++ = '\n';
*InnerBuffer++ = ';';
}
SrvCorrectCatchBuffer( Buffer, Offset );
}
}
}
#endif