NT4/private/ntos/rdr/ritebhnd.c
2020-09-30 17:12:29 +02:00

1140 lines
35 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) 1992 Microsoft Corporation
Module Name:
ritebhnd.c
Abstract:
This module implements support for WRITE_BEHIND buffers
Author:
Larry Osterman (larryo) 19-Dec-1992
Revision History:
19-Dec-1992 larryo
Created
--*/
#define INCLUDE_SMB_READ_WRITE
#include "precomp.h"
#pragma hdrstop
typedef struct _WRITE_FLUSH_CONTEXT {
WORK_QUEUE_ITEM WorkItem;
PWRITE_BUFFER_HEAD BufferHead;
PWRITE_BUFFER Buffer;
NTSTATUS Status;
ULONG AmountActuallyWritten;
BOOLEAN WaitForCompletion;
BOOLEAN AllDataWritten;
} WRITE_FLUSH_CONTEXT, *PWRITE_FLUSH_CONTEXT;
NTSTATUS
FlushWriteBufferHead(
IN PIRP Irp OPTIONAL,
IN PWRITE_BUFFER_HEAD BufferHead,
IN BOOLEAN WaitForCompletion
);
NTSTATUS
FlushBufferCompletion(
IN NTSTATUS Status,
IN PVOID Ctx
);
VOID
CompleteBufferFlushOperation(
IN PVOID Ctx
);
NTSTATUS
DereferenceWriteBufferLocked(
IN PIRP Irp,
IN PWRITE_BUFFER WriteBuffer,
IN BOOLEAN WaitForCompletion,
IN ULONG EventNumber,
IN BOOLEAN IncrementWriteBuffersAvailable
);
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, FlushWriteBufferHead)
#pragma alloc_text(PAGE, CompleteBufferFlushOperation)
#pragma alloc_text(PAGE, RdrFlushWriteBufferForFile)
#pragma alloc_text(PAGE, RdrTruncateWriteBufferForFcb)
#pragma alloc_text(PAGE, RdrTruncateWriteBufferForIcb)
#pragma alloc_text(PAGE, RdrFindOrAllocateWriteBuffer)
#pragma alloc_text(PAGE, RdrDereferenceWriteBuffer)
#pragma alloc_text(PAGE, DereferenceWriteBufferLocked)
#pragma alloc_text(PAGE, RdrInitializeWriteBufferHead)
#pragma alloc_text(PAGE, RdrUninitializeWriteBufferHead)
#pragma alloc_text(PAGE3FILE, FlushBufferCompletion)
#endif
NTSTATUS
FlushWriteBufferHead(
IN PIRP Irp OPTIONAL,
IN PWRITE_BUFFER_HEAD BufferHead,
IN BOOLEAN WaitForCompletion
)
{
NTSTATUS Status = STATUS_SUCCESS;
PWRITE_FLUSH_CONTEXT Context = NULL;
PAGED_CODE();
dprintf(DPRT_RITEBHND, ("FlushWriteBufferHead: %lx\n", BufferHead));
//
// NOTE: This routine is called with the write buffer head locked.
// It unlocks it before returning.
//
if (BufferHead->FlushInProgress) {
dprintf(DPRT_RITEBHND, ("FlushWriteBufferHead: already flushing %lx\n", BufferHead));
UNLOCK_WRITE_BUFFER_HEAD( BufferHead );
return STATUS_SUCCESS;
}
BufferHead->FlushInProgress = TRUE;
while (!IsListEmpty(&BufferHead->FlushList)) {
PWRITE_BUFFER Buffer;
PLIST_ENTRY BufferEntry = RemoveHeadList(&BufferHead->FlushList);
Buffer = CONTAINING_RECORD(BufferEntry, WRITE_BUFFER, NextWbBuffer);
DEBUG Buffer->NextWbBuffer.Flink = NULL;
DEBUG Buffer->NextWbBuffer.Blink = NULL;
UNLOCK_WRITE_BUFFER_HEAD( BufferHead );
dprintf(DPRT_RITEBHND, ("FlushWriteBufferHead: %lx Offset: %lx%lx, Length: %lx\n", Buffer, Buffer->ByteOffset.HighPart, Buffer->ByteOffset.LowPart, Buffer->Length));
//
// Reference the file object so this file does not get cleaned up
// while flush is in progress.
//
#if 0 && RDRDBG_LOG
{
//LARGE_INTEGER tick;
//KeQueryTickCount(&tick);
//RdrLog(( "ritebhnd", &((PFCB)BufferHead->FileObject->FsContext)->FileName, 2, tick.LowPart, tick.HighPart ));
//RdrLog(( "ritebhnd", &((PFCB)BufferHead->FileObject->FsContext)->FileName, 2, Buffer->ByteOffset.LowPart, Buffer->Length ));
}
#endif
ObReferenceObject(BufferHead->FileObject);
RdrStartAndXBehindOperation(&BufferHead->AndXBehind);
Context = ALLOCATE_POOL(NonPagedPool, sizeof(WRITE_FLUSH_CONTEXT), POOL_WRITEFLUSHCTX);
if (Context == NULL) {
LOCK_WRITE_BUFFER_HEAD( BufferHead );
dprintf(DPRT_RITEBHND, ("FlushWriteBufferHead: alloc failed; requeueing %lx\n", Buffer));
InsertHeadList(&BufferHead->FlushList, &Buffer->NextWbBuffer);
BufferHead->FlushInProgress = FALSE;
UNLOCK_WRITE_BUFFER_HEAD( BufferHead );
ObDereferenceObject( BufferHead->FileObject );
RdrEndAndXBehindOperation(&BufferHead->AndXBehind);
return STATUS_INSUFFICIENT_RESOURCES;
}
Context->BufferHead = BufferHead;
Context->Buffer = Buffer;
Context->WaitForCompletion = (BOOLEAN)((ARGUMENT_PRESENT(Irp) ? TRUE : WaitForCompletion)),
//
// We need to ignore the IRP provided when we call RdrWriteRange. If
// this IRP is a user mode IRP, we will probe the buffer for UserMode
// inside RdrWriteRange, and this will cause us to access violate
// when we do this write.
//
Status = RdrWriteRange(NULL,
BufferHead->FileObject,
NULL,
Buffer->Buffer,
Buffer->Length,
Buffer->ByteOffset,
Context->WaitForCompletion,
FlushBufferCompletion,
Context,
&Context->AllDataWritten,
&Context->AmountActuallyWritten
);
LOCK_WRITE_BUFFER_HEAD( BufferHead );
if ((Status == STATUS_INSUFFICIENT_RESOURCES) ||
(Status == STATUS_FILE_LOCK_CONFLICT)) {
dprintf(DPRT_RITEBHND, ("FlushWriteBufferHead: Write range failed; requeueing %lx\n", Buffer));
//
// Stick this buffer back on the head of the queue.
//
InsertHeadList(&BufferHead->FlushList, &Buffer->NextWbBuffer);
BufferHead->FlushInProgress = FALSE;
UNLOCK_WRITE_BUFFER_HEAD( BufferHead );
ObDereferenceObject( BufferHead->FileObject );
RdrEndAndXBehindOperation(&BufferHead->AndXBehind);
FREE_POOL( Context );
return Status;
}
}
BufferHead->FlushInProgress = FALSE;
dprintf(DPRT_RITEBHND, ("FlushWriteBufferHead: done with %lx\n", BufferHead));
UNLOCK_WRITE_BUFFER_HEAD( BufferHead );
return Status;
}
NTSTATUS
FlushBufferCompletion(
IN NTSTATUS Status,
IN PVOID Ctx
)
{
PWRITE_FLUSH_CONTEXT Context = Ctx;
DISCARDABLE_CODE(RdrFileDiscardableSection);
dprintf(DPRT_RITEBHND, ("FlushBufferCompletion: Status:%lx, Context:%lx, Buffer:%lx\n", Status, Context, Context->Buffer));
Context->Status = Status;
ExInitializeWorkItem(&Context->WorkItem, CompleteBufferFlushOperation, Context);
//
// Queue directly to the EX queue, instead of the redir queue, in order to avoid
// deadlock where the redir's single thread for the queue is blocked waiting for
// this write operation to complete.
//
ExQueueWorkItem(&Context->WorkItem, DelayedWorkQueue);
return(STATUS_SUCCESS);
}
VOID
CompleteBufferFlushOperation(
IN PVOID Ctx
)
{
PWRITE_FLUSH_CONTEXT Context = Ctx;
PWRITE_BUFFER Buffer = Context->Buffer;
PWRITE_BUFFER_HEAD WriteHeader = Buffer->BufferHead;
PAGED_CODE();
dprintf(DPRT_RITEBHND, ("CompleteBufferFlushOperation: Status:%lx, Context:%lx, Buffer: %lx\n", Context->Status, Context, Context->Buffer));
if (!Context->WaitForCompletion &&
(!NT_SUCCESS(Context->Status) ||
!Context->AllDataWritten)) {
ULONG DataBuffer[2];
PICB Icb = Buffer->BufferHead->FileObject->FsContext2;
DataBuffer[0] = Buffer->Length;
DataBuffer[1] = Context->AmountActuallyWritten;
RdrWriteErrorLogEntry(Icb->Fcb->Connection->Server,
IO_ERR_LAYERED_FAILURE,
EVENT_RDR_WRITE_BEHIND_FLUSH_FAILED,
Context->Status,
&DataBuffer,
sizeof(DataBuffer)
);
}
//
// Decrement the count so it can allow another write behind to
// proceed.
//
LOCK_WRITE_BUFFER_HEAD( WriteHeader );
WriteHeader->WriteBuffersActive--;
UNLOCK_WRITE_BUFFER_HEAD( WriteHeader );
//
// Decrement the file object so it can be cleaned up.
//
#if 0 && RDRDBG_LOG
{
//LARGE_INTEGER tick;
//KeQueryTickCount(&tick);
//RdrLog(( "ritebCMP", &((PFCB)WriteHeader->FileObject->FsContext)->FileName, 2, tick.LowPart, tick.HighPart ));
}
#endif
//
// End the AndXBehind operation before dereferencing the file object.
// The dereference may cause the file object to be closed, and the
// processing of the close will need to acquire the FCB lock. But
// RdrFlushCacheFile might be holding the FCB lock while waiting for
// AndXBehind operations to complete.
//
RdrEndAndXBehindOperation(&WriteHeader->AndXBehind);
ObDereferenceObject( WriteHeader->FileObject );
FREE_POOL(Context);
FREE_POOL(Buffer);
}
NTSTATUS
RdrFlushWriteBufferForFile(
IN PIRP Irp OPTIONAL,
IN PICB Icb,
IN BOOLEAN WaitForCompletion
)
{
NTSTATUS Status = STATUS_SUCCESS;
NTSTATUS Status1;
PAGED_CODE();
dprintf(DPRT_RITEBHND, ("RdrFlushWriteBufferForFile: %lx.\n", Icb));
LOCK_WRITE_BUFFER_HEAD( &Icb->u.f.WriteBufferHead );
while (!IsListEmpty(&Icb->u.f.WriteBufferHead.BufferList)) {
PLIST_ENTRY List;
PWRITE_BUFFER Buffer;
//
// Remove the buffers in an LRU order.
//
List = RemoveTailList(&Icb->u.f.WriteBufferHead.BufferList);
DEBUG List->Flink = NULL;
DEBUG List->Blink = NULL;
Buffer = CONTAINING_RECORD(List, WRITE_BUFFER, NextWbBuffer);
dprintf(DPRT_RITEBHND, ("RdrFlushWriteBufferForFile: buffer %lx\n", Buffer));
//
// DereferenceWriteBufferLocked expects to own the write buffer
// head's lock on entry. It releases the lock before returning.
//
Status1 = DereferenceWriteBufferLocked(Irp,
Buffer,
WaitForCompletion,
0,
TRUE);
//
// If the buffer was flushed, and the flush failed, and this was
// the first such failure, save the failure status.
//
if (NT_SUCCESS(Status)) {
Status = Status1;
}
LOCK_WRITE_BUFFER_HEAD( &Icb->u.f.WriteBufferHead );
}
ASSERT (Icb->u.f.WriteBufferHead.WriteBuffersAvailable == WRITE_BUFFERS_PER_FILE);
UNLOCK_WRITE_BUFFER_HEAD( &Icb->u.f.WriteBufferHead );
dprintf(DPRT_RITEBHND, ("RdrFlushWriteBufferForFile %lx done, status %lx.\n", Icb, Status));
return(Status);
}
VOID
RdrTruncateWriteBufferForFcb (
IN PFCB Fcb
)
{
PLIST_ENTRY IcbEntry;
PICB Icb;
PAGED_CODE();
dprintf(DPRT_RITEBHND, ("RdrTruncateWriteBufferForFcb %lx\n", Fcb));
if ( Fcb->NonPagedFcb->Type == DiskFile ) {
for (IcbEntry = Fcb->InstanceChain.Flink ;
IcbEntry != &Fcb->InstanceChain ;
IcbEntry = IcbEntry->Flink) {
Icb = CONTAINING_RECORD(IcbEntry, ICB, InstanceNext);
if ( (Icb->Type == DiskFile) &&
(Icb->Flags & ICB_OPENED) ) {
RdrTruncateWriteBufferForIcb( Icb );
}
}
}
dprintf(DPRT_RITEBHND, ("RdrTruncateWriteBufferForFcb %lx done\n", Fcb));
return;
}
VOID
RdrTruncateWriteBufferForIcb (
IN PICB Icb
)
{
PWRITE_BUFFER_HEAD BufferHead;
PLIST_ENTRY BufferEntry;
PWRITE_BUFFER Buffer;
LARGE_INTEGER BufferEnd;
LARGE_INTEGER FileSize;
PAGED_CODE();
dprintf(DPRT_RITEBHND, ("RdrTruncateWriteBufferForIcb: %lx.\n", Icb));
ASSERT (Icb->Type == DiskFile);
FileSize = Icb->Fcb->Header.FileSize;
BufferHead = &Icb->u.f.WriteBufferHead;
LOCK_WRITE_BUFFER_HEAD( BufferHead );
//
// Walk the active buffer list, discarding buffers that lie entirely
// beyond the new EOF, and truncating those that cross the new EOF.
//
BufferEntry = BufferHead->BufferList.Flink;
while (BufferEntry != &BufferHead->BufferList) {
Buffer = CONTAINING_RECORD(BufferEntry, WRITE_BUFFER, NextWbBuffer);
BufferEntry = BufferEntry->Flink;
//
// If this buffer starts beyond the new end of file, remove it
// from the list and discard it.
//
if ( Buffer->ByteOffset.QuadPart > FileSize.QuadPart ) {
RemoveEntryList( &Buffer->NextWbBuffer );
BufferHead->WriteBuffersAvailable += 1;
BufferHead->WriteBuffersActive--;
dprintf(DPRT_RITEBHND, ("RdrTruncateWriteBufferForIcb: buffer %lx discarded\n", Buffer));
FREE_POOL(Buffer);
} else {
//
// If this buffer ends beyond the new end of file, truncate it.
//
BufferEnd.QuadPart = Buffer->ByteOffset.QuadPart + Buffer->Length;
if ( BufferEnd.QuadPart > FileSize.QuadPart ) {
dprintf(DPRT_RITEBHND, ("RdrTruncateWriteBufferForIcb: buffer %lx truncated\n", Buffer));
Buffer->Length = (ULONG)(FileSize.QuadPart - Buffer->ByteOffset.QuadPart);
}
}
}
//
// Do the same thing with the flush list.
//
BufferEntry = BufferHead->FlushList.Flink;
while (BufferEntry != &BufferHead->FlushList) {
Buffer = CONTAINING_RECORD(BufferEntry, WRITE_BUFFER, NextWbBuffer);
BufferEntry = BufferEntry->Flink;
//
// If this buffer starts beyond the new end of file, remove it
// from the list and discard it.
//
if ( Buffer->ByteOffset.QuadPart > FileSize.QuadPart ) {
RemoveEntryList( &Buffer->NextWbBuffer );
BufferHead->WriteBuffersActive--;
dprintf(DPRT_RITEBHND, ("RdrTruncateWriteBufferForIcb: buffer %lx discarded\n", Buffer));
FREE_POOL(Buffer);
} else {
//
// If this buffer ends beyond the new end of file, truncate it.
//
BufferEnd.QuadPart = Buffer->ByteOffset.QuadPart + Buffer->Length;
if ( BufferEnd.QuadPart > FileSize.QuadPart ) {
dprintf(DPRT_RITEBHND, ("RdrTruncateWriteBufferForIcb: buffer %lx truncated\n", Buffer));
Buffer->Length = (ULONG)(FileSize.QuadPart - Buffer->ByteOffset.QuadPart);
}
}
}
UNLOCK_WRITE_BUFFER_HEAD( BufferHead );
dprintf(DPRT_RITEBHND, ("RdrTruncateWriteBufferForIcb %lx done.\n", Icb));
return;
}
PWRITE_BUFFER
RdrFindOrAllocateWriteBuffer(
IN PWRITE_BUFFER_HEAD WriteHeader,
IN LARGE_INTEGER WriteOffset,
IN ULONG Length,
IN LARGE_INTEGER FileValidDataEnd
)
{
PLIST_ENTRY List;
LARGE_INTEGER TransferEnd;
LARGE_INTEGER TransferBufferEnd;
PWRITE_BUFFER Buffer;
PWRITE_BUFFER NewBuffer;
PAGED_CODE();
try {
if (!RdrUseWriteBehind) {
return (Buffer = NULL);
}
dprintf(DPRT_RITEBHND, ("RdrFindOrAllocateWriteBuffer: Header: %lx Offset: %lx%lx, Length: %lx\n", WriteHeader, WriteOffset.HighPart, WriteOffset.LowPart, Length));
//
// Calculate the end of the write. Note that this is actually the
// first byte NOT written.
//
TransferEnd.QuadPart = WriteOffset.QuadPart + Length;
TransferBufferEnd.QuadPart = WriteOffset.QuadPart + WriteHeader->MaxDataSize;
if ( TransferEnd.QuadPart > TransferBufferEnd.QuadPart) {
TransferBufferEnd = TransferEnd;
}
//
// Walk the write behind buffer list. At the end of this loop, if
// Buffer is not NULL, then it is the buffer to be returned to the
// caller. If it is NULL, then no matching buffer was found, and a
// new one must be created.
//
LOCK_WRITE_BUFFER_HEAD(WriteHeader);
for (Buffer = NULL, List = WriteHeader->BufferList.Flink ;
List != &WriteHeader->BufferList ;
Buffer = NULL, List = List->Flink) {
LARGE_INTEGER BufferEnd;
LARGE_INTEGER BufferValidDataEnd;
//
// If the new write overlaps the write buffer, but is not
// completely contained in the buffer, then we need to flush
// the buffer. We also need to flush the buffer if the write
// fits in the buffer but starts beyond the valid data in the
// buffer AND the end of the buffer's valid data is NOT the end
// of the file's valid data. This is necessary because we
// can't read from the file in order to fill in the gap. If
// the write is beyond the file's valid data, then we can fill
// the gap with zeroes.
//
// Calculate the ending offset of the buffer and the ending
// offset of the valid data in the buffer. Note that these
// are actually the first byte AFTER the ending offset, to
// simplify the calculations.
//
Buffer = CONTAINING_RECORD(List, WRITE_BUFFER, NextWbBuffer);
BufferEnd.QuadPart = Buffer->ByteOffset.QuadPart + WriteHeader->MaxDataSize;
BufferValidDataEnd.QuadPart = Buffer->ByteOffset.QuadPart + Buffer->Length;
//
// Does the write fall entirely within the buffer?
//
if ((WriteOffset.QuadPart >= Buffer->ByteOffset.QuadPart) &&
(TransferEnd.QuadPart <= BufferEnd.QuadPart)) {
//
// The write fits in the buffer. If the write starts at
// or before the end of the valid data in the buffer, then
// we can just use this buffer. Break out of the loop with
// Buffer != NULL to indicate that this buffer is to be
// returned.
//
if (WriteOffset.QuadPart <= BufferValidDataEnd.QuadPart) {
dprintf(DPRT_RITEBHND, ("RdrFindOrAllocateWriteBuffer: using existing buffer %lx Offset: %lx%lx, Length: %lx\n", Buffer, Buffer->ByteOffset.HighPart, Buffer->ByteOffset.LowPart, Buffer->Length));
break;
}
//
// The write starts after the end of the valid data in the
// buffer. If the end of the buffer's valid data is also
// the end of the file's valid data, then materialize
// zeroes in front of the write and break out of the loop.
//
ASSERT( BufferValidDataEnd.QuadPart <= FileValidDataEnd.QuadPart);
if (BufferValidDataEnd.QuadPart == FileValidDataEnd.QuadPart) {
// ASSERT(((LARGE_INTEGER)(WriteOffset.QuadPart - BufferValidDataEnd.QuadPart)).HighPart == 0);
RtlZeroMemory(Buffer->Buffer+Buffer->Length,
(ULONG)(WriteOffset.QuadPart - BufferValidDataEnd.QuadPart));
dprintf(DPRT_RITEBHND, ("RdrFindOrAllocateWriteBuffer: zeroing then using existing buffer %lx Offset: %lx%lx, Length: %lx\n", Buffer, Buffer->ByteOffset.HighPart, Buffer->ByteOffset.LowPart, Buffer->Length));
break;
}
//
// At this point, the write lies completely within the buffer,
// but starts beyond the valid data in the buffer, which is
// not at the end of the file's valid data. This means we are
// missing some data in the write buffer. Since we can't read
// from the file, we have to flush this write buffer and start
// a new one.
//
// As a simplification, we drop into the next conditional to
// do the flush. This means a longer code path for this case,
// but less code overall. We shouldn't hit this code path
// very often.
//
}
//
// The write doesn't fit within the buffer. Does it overlap?
// Note that overlap must be calculated using the size of the
// new buffer that would be allocated for this write, not just
// the length of this write. Otherwise we could get two buffers
// covering the same area.
//
if ((WriteOffset.QuadPart < BufferEnd.QuadPart) &&
(TransferBufferEnd.QuadPart > Buffer->ByteOffset.QuadPart)) {
//
// The write overlaps the buffer. We need to flush the buffer.
//
dprintf(DPRT_RITEBHND, ("RdrFindOrAllocateWriteBuffer: flushing overlapped buffer %lx Offset: %lx%lx, Length: %lx.\n", Buffer, Buffer->ByteOffset.HighPart, Buffer->ByteOffset.LowPart, Buffer->Length));
RemoveEntryList(List);
DEBUG List->Flink = NULL;
DEBUG List->Blink = NULL;
//
// Dereference the buffer. The contents will be flushed when
// all threads writing to the buffer have finished.
//
// It doesn't matter if the dereferenced write completes
// after this one, since the I/O system doesn't guarantee
// any ordering of the writes if two threads are writing to
// the same location in the file.
//
// Note that this call to DereferenceWriteBufferLocked is
// made with the write buffer head locked, and returns with
// it unlocked.
//
(VOID)DereferenceWriteBufferLocked(NULL,
Buffer,
(BOOLEAN)!RdrUseAsyncWriteBehind,
EVENT_RDR_WRITE_BEHIND_FLUSH_FAILED,
TRUE);
//
// Reacquire the write buffer head lock and restart the scan.
// We have to restart at the beginning of the list because
// the list may have changed while we were flushing the
// buffer.
//
LOCK_WRITE_BUFFER_HEAD(WriteHeader);
List = &WriteHeader->BufferList;
} else {
//
// The write does not overlap this buffer. Move on.
//
dprintf(DPRT_RITEBHND, ("RdrFindOrAllocateWriteBuffer: no overlap on buffer %lx Offset: %lx%lx, Length: %lx\n", Buffer, Buffer->ByteOffset.HighPart, Buffer->ByteOffset.LowPart, Buffer->Length));
}
}
//
// At this point, we've either found the buffer we want or exhausted
// the buffer list.
//
if ( Buffer != NULL ) {
//
// We have found the buffer for this write. Reference it, move
// it to the front of the list (MRU), and return a pointer to it.
//
Buffer->ReferenceCount += 1;
RemoveEntryList(List);
InsertHeadList(&WriteHeader->BufferList, List);
UNLOCK_WRITE_BUFFER_HEAD(WriteHeader);
dprintf(DPRT_RITEBHND, ("RdrFindOrAllocateWriteBuffer: Found existing %lx: Offset: %lx%lx, Length: %lx\n", Buffer, Buffer->ByteOffset.HighPart, Buffer->ByteOffset.LowPart, Buffer->Length));
try_return(Buffer);
}
//
// There is no matching buffer in the list. Is this write is too big
// to be cached?
//
if ( Length > WriteHeader->MaxDataSize ) {
try_return(Buffer = NULL);
}
//
// Don't allow more than ACTIVE_WRITE_BUFFERS_PER_FILE write behind buffers
// per file to be outstanding.
//
if ( WriteHeader->WriteBuffersActive == ACTIVE_WRITE_BUFFERS_PER_FILE ) {
try_return(Buffer = NULL);
}
//
// Allocate a new buffer.
//
NewBuffer = ALLOCATE_POOL(PagedPool,
sizeof(WRITE_BUFFER) - 1 + WriteHeader->MaxDataSize, POOL_WRITEBUFFERBUFFER);
if (NewBuffer == NULL) {
try_return(Buffer = NULL);
}
//
// Initialize the new write buffer.
//
RtlZeroMemory(NewBuffer, sizeof(WRITE_BUFFER) - 1);
NewBuffer->Signature = STRUCTURE_SIGNATURE_WRITE_BUFFER;
NewBuffer->Size = sizeof(WRITE_BUFFER);
NewBuffer->BufferHead = WriteHeader;
NewBuffer->ByteOffset = WriteOffset;
//RtlZeroMemory(NewBuffer->Buffer, WriteHeader->MaxDataSize);
//
// Insert the buffer on the list.
//
InsertHeadList(&WriteHeader->BufferList, &NewBuffer->NextWbBuffer);
//
// Initialize the reference count to 2 - one for being inserted to the
// list, the other because we are about to return this buffer.
//
NewBuffer->ReferenceCount = 2;
//
// If there are no write buffers available, flush the oldest one.
//
ASSERT (WriteHeader->WriteBuffersAvailable >= 0);
//
// Increment the number of active write buffers
//
WriteHeader->WriteBuffersActive++;
//
// The file's write buffer list is not full. Decrement the count
// of available buffers to account for the one we just added.
//
if (WriteHeader->WriteBuffersAvailable == 0) {
//
// The file's write buffer list is full. Remove the oldest one.
//
List = RemoveTailList(&WriteHeader->BufferList);
ASSERT (List != &WriteHeader->BufferList);
Buffer = CONTAINING_RECORD(List, WRITE_BUFFER, NextWbBuffer);
DEBUG List->Flink = NULL;
DEBUG List->Blink = NULL;
//
// Dereference the buffer. The contents will be flushed when
// all threads writing to the buffer have finished.
//
// It doesn't matter if the dereferenced write completes
// after this one, since the I/O system doesn't guarantee
// any ordering of the writes if two threads are writing to
// the same location in the file.
//
// Note that this call to DereferenceWriteBufferLocked has
// two special characteristics: 1) it is called with the write
// buffer head locked, and returns with it unlocked; and 2) it
// does not increment WriteBuffersAvailable, because we just
// added a new write buffer.
//
dprintf(DPRT_RITEBHND, ("RdrFindOrAllocateWriteBuffer: write buffer list full; flushing buffer %lx Offset: %lx%lx, Length: %lx\n", Buffer, Buffer->ByteOffset.HighPart, Buffer->ByteOffset.LowPart, Buffer->Length));
(VOID)DereferenceWriteBufferLocked(NULL,
Buffer,
(BOOLEAN)!RdrUseAsyncWriteBehind,
EVENT_RDR_WRITE_BEHIND_FLUSH_FAILED,
FALSE);
} else {
//
// The file's write buffer list is not full. Decrement the count
// of available buffers to account for the one we just added.
//
WriteHeader->WriteBuffersAvailable -= 1;
UNLOCK_WRITE_BUFFER_HEAD(WriteHeader);
}
dprintf(DPRT_RITEBHND, ("RdrFindOrAllocateWriteBuffer: New buffer %lx: Offset: %lx%lx, Length: %lx\n", NewBuffer, NewBuffer->ByteOffset.HighPart, NewBuffer->ByteOffset.LowPart, NewBuffer->Length));
try_return(Buffer = NewBuffer);
try_exit:NOTHING;
} finally {
if (Buffer == NULL) {
NTSTATUS Status;
PICB Icb = WriteHeader->FileObject->FsContext2;
ASSERT (Icb->Signature == STRUCTURE_SIGNATURE_ICB);
dprintf(DPRT_RITEBHND, ("RdrFindOrAllocateWriteBuffer: returning NULL\n"));
//
// There is a possible write ordering problem that can occur if
// we return before all the flushed writes have completed, so
// we want to wait for any active flushes to complete before returning
// NULL.
//
//
// FlushWriteBufferHead will unlock the write header.
//
Status = FlushWriteBufferHead(NULL, WriteHeader, TRUE);
if (!NT_SUCCESS(Status)) {
#if MAGIC_BULLET
if ( RdrEnableMagic ) {
RdrSendMagicBullet(NULL);
DbgPrint( "RDR: About to raise write behind hard error for ICB %x\n", Icb );
DbgBreakPoint();
}
#endif
IoRaiseInformationalHardError(Status,
NULL,
NULL);
RdrWriteErrorLogEntry(
NULL,
IO_ERR_LAYERED_FAILURE,
EVENT_RDR_WRITE_BEHIND_FLUSH_FAILED,
Status,
NULL,
0
);
}
//
// Now wait for any previously initiated write behind operations to
// complete.
//
RdrWaitForWriteBehindOperation(Icb);
}
}
return(Buffer);
}
VOID
RdrDereferenceWriteBuffer(
IN PWRITE_BUFFER WriteBuffer,
IN BOOLEAN WaitForCompletion
)
{
PAGED_CODE();
//
// Lock the write buffer head, then called DereferenceWriteBufferLocked.
// This function returns with the write buffer head unlocked.
//
LOCK_WRITE_BUFFER_HEAD(WriteBuffer->BufferHead);
(VOID)DereferenceWriteBufferLocked(NULL,
WriteBuffer,
WaitForCompletion,
EVENT_RDR_WRITE_BEHIND_FLUSH_FAILED,
TRUE);
return;
}
NTSTATUS
DereferenceWriteBufferLocked(
IN PIRP Irp OPTIONAL,
IN PWRITE_BUFFER WriteBuffer,
IN BOOLEAN WaitForCompletion,
IN ULONG EventNumber,
IN BOOLEAN IncrementWriteBuffersAvailable
)
{
NTSTATUS Status;
PAGED_CODE();
WriteBuffer->ReferenceCount -= 1;
if (WriteBuffer->ReferenceCount > 0) {
UNLOCK_WRITE_BUFFER_HEAD(WriteBuffer->BufferHead);
dprintf(DPRT_RITEBHND, ("DereferenceWriteBufferLocked: buffer %lx, new refcnt %d\n", WriteBuffer, WriteBuffer->ReferenceCount));
return STATUS_SUCCESS;
}
ASSERT (WriteBuffer->ReferenceCount == 0);
//
// If the ref count goes to 0, it had better not be linked into
// the list.
//
ASSERT (WriteBuffer->NextWbBuffer.Flink == NULL);
ASSERT (WriteBuffer->NextWbBuffer.Blink == NULL);
//
// There is one more write buffer available to be put on the list.
//
if (IncrementWriteBuffersAvailable) {
WriteBuffer->BufferHead->WriteBuffersAvailable += 1;
}
//
// Stick this buffer to the tail of the "pending write flush list"
//
InsertTailList(&WriteBuffer->BufferHead->FlushList, &WriteBuffer->NextWbBuffer);
//
// Start a flush operation. Note that FlushWriteBufferHead unlocks
// the write buffer head before returning.
//
dprintf(DPRT_RITEBHND, ("DereferenceWriteBufferLocked: flushing buffer %lx\n", WriteBuffer));
Status = FlushWriteBufferHead(Irp,
WriteBuffer->BufferHead,
WaitForCompletion);
if (!NT_SUCCESS(Status) && (EventNumber != 0)) {
#if MAGIC_BULLET
if ( RdrEnableMagic ) {
RdrSendMagicBullet(NULL);
DbgPrint( "RDR: About to raise write behind hard error for write buffer %x\n", WriteBuffer );
DbgBreakPoint();
}
#endif
IoRaiseInformationalHardError(Status,
NULL,
NULL);
RdrWriteErrorLogEntry(
NULL,
IO_ERR_LAYERED_FAILURE,
EventNumber,
Status,
NULL,
0
);
}
return Status;
}
VOID
RdrInitializeWriteBufferHead(
IN PWRITE_BUFFER_HEAD WriteHeader,
IN PFILE_OBJECT FileObject
)
{
PICB Icb = FileObject->FsContext2;
PAGED_CODE();
WriteHeader->Signature = STRUCTURE_SIGNATURE_WRITE_BUFFER_HEAD;
WriteHeader->Size = sizeof(WRITE_BUFFER_HEAD);
WriteHeader->FileObject = FileObject;
InitializeListHead(&WriteHeader->BufferList);
InitializeListHead(&WriteHeader->FlushList);
WriteHeader->FlushInProgress = FALSE;
INITIALIZE_WRITE_BUFFER_HEAD_LOCK(WriteHeader);
WriteHeader->MaxDataSize = Icb->Fcb->Connection->Server->BufferSize - (sizeof(SMB_HEADER) + sizeof(REQ_NT_WRITE_ANDX));
//
// We assume WRITE_BUFFERS_PER_FILE buffers for each file.
//
WriteHeader->WriteBuffersAvailable = WRITE_BUFFERS_PER_FILE;
//
// We initialize the count that restricts the number of
// active write behind buffers.
//
WriteHeader->WriteBuffersActive = 0;
RdrInitializeAndXBehind(&WriteHeader->AndXBehind);
}
VOID
RdrUninitializeWriteBufferHead(
IN PWRITE_BUFFER_HEAD WriteHeader
)
{
PAGED_CODE();
while (!IsListEmpty(&WriteHeader->BufferList)) {
PWRITE_BUFFER Buffer;
PLIST_ENTRY Entry = RemoveHeadList(&WriteHeader->BufferList);
Buffer = CONTAINING_RECORD(Entry, WRITE_BUFFER, NextWbBuffer);
dprintf(DPRT_RITEBHND, ("RdrUninitializeWriteBufferHead: buffer %lx still on buffer list\n", Buffer));
WriteHeader->WriteBuffersActive--;
FREE_POOL(Buffer);
//
// If this is not a temporary file, write an error log entry
// indicating that data has been lost.
//
if (!FlagOn(WriteHeader->FileObject->Flags, FO_TEMPORARY_FILE)) {
RdrWriteErrorLogEntry(NULL,
IO_ERR_LAYERED_FAILURE,
EVENT_RDR_WRITE_BEHIND_FLUSH_FAILED,
STATUS_SUCCESS,
NULL,
0
);
}
}
//
// Discard any buffers on the flush list as well.
//
while (!IsListEmpty(&WriteHeader->FlushList)) {
PWRITE_BUFFER Buffer;
PLIST_ENTRY Entry = RemoveHeadList(&WriteHeader->FlushList);
Buffer = CONTAINING_RECORD(Entry, WRITE_BUFFER, NextWbBuffer);
dprintf(DPRT_RITEBHND, ("RdrUninitializeWriteBufferHead: buffer %lx still on flush list\n", Buffer));
WriteHeader->WriteBuffersActive--;
FREE_POOL(Buffer);
//
// If this is not a temporary file, write an error log entry
// indicating that data has been lost.
//
if (!FlagOn(WriteHeader->FileObject->Flags, FO_TEMPORARY_FILE)) {
RdrWriteErrorLogEntry(NULL,
IO_ERR_LAYERED_FAILURE,
EVENT_RDR_WRITE_BEHIND_FLUSH_FAILED,
STATUS_SUCCESS,
NULL,
0
);
}
}
DELETE_WRITE_BUFFER_HEAD_LOCK(WriteHeader);
}