mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4304 lines
112 KiB
4304 lines
112 KiB
/*++
|
|
|
|
Copyright (c) 1992 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
scavengr.c
|
|
|
|
Abstract:
|
|
|
|
This module implements the LAN Manager server FSP resource and
|
|
scavenger threads.
|
|
|
|
Author:
|
|
|
|
Chuck Lenzmeier (chuckl) 30-Dec-1989
|
|
David Treadwell (davidtr)
|
|
|
|
Environment:
|
|
|
|
Kernel mode
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "precomp.h"
|
|
#include <ntdddisk.h>
|
|
#include "scavengr.tmh"
|
|
#pragma hdrstop
|
|
|
|
#define BugCheckFileId SRV_FILE_SCAVENGR
|
|
|
|
//
|
|
// Local data
|
|
//
|
|
|
|
ULONG LastNonPagedPoolLimitHitCount = 0;
|
|
ULONG LastNonPagedPoolFailureCount = 0;
|
|
ULONG LastPagedPoolLimitHitCount = 0;
|
|
ULONG LastPagedPoolFailureCount = 0;
|
|
|
|
ULONG SrvScavengerCheckRfcbActive = 5;
|
|
LONG ScavengerUpdateQosCount = 0;
|
|
LONG ScavengerCheckRfcbActive = 0;
|
|
LONG FailedWorkItemAllocations = 0;
|
|
|
|
BOOLEAN EventSwitch = TRUE;
|
|
|
|
LARGE_INTEGER NextScavengeTime = {0};
|
|
LARGE_INTEGER NextAlertTime = {0};
|
|
|
|
//
|
|
// Fields used during shutdown to synchronize with EX worker threads. We
|
|
// need to make sure that no worker thread is running server code before
|
|
// we can declare shutdown to be complete -- otherwise the code may be
|
|
// unloaded while it's running!
|
|
//
|
|
|
|
BOOLEAN ScavengerInitialized = FALSE;
|
|
PKEVENT ScavengerTimerTerminationEvent = NULL;
|
|
PKEVENT ScavengerThreadTerminationEvent = NULL;
|
|
PKEVENT ResourceThreadTerminationEvent = NULL;
|
|
|
|
//
|
|
// Timer, DPC, and work item used to run the scavenger thread.
|
|
//
|
|
|
|
KTIMER ScavengerTimer = {0};
|
|
KDPC ScavengerDpc = {0};
|
|
|
|
PIO_WORKITEM ScavengerWorkItem = NULL;
|
|
|
|
BOOLEAN ScavengerRunning = FALSE;
|
|
|
|
KSPIN_LOCK ScavengerSpinLock = {0};
|
|
|
|
//
|
|
// Flags indicating which scavenger algorithms need to run.
|
|
//
|
|
|
|
BOOLEAN RunShortTermAlgorithm = FALSE;
|
|
BOOLEAN RunScavengerAlgorithm = FALSE;
|
|
BOOLEAN RunAlerterAlgorithm = FALSE;
|
|
BOOLEAN RunSuspectConnectionAlgorithm = FALSE;
|
|
|
|
//
|
|
// Base scavenger timeout. A timer DPC runs each interval. It
|
|
// schedules EX worker thread work when other longer intervals expire.
|
|
//
|
|
|
|
LARGE_INTEGER ScavengerBaseTimeout = { (ULONG)(-1*10*1000*1000*10), -1 };
|
|
|
|
#define SRV_MAX_DOS_ATTACK_EVENT_LOGS 10
|
|
|
|
//
|
|
// Defined somewhere else.
|
|
//
|
|
|
|
LARGE_INTEGER
|
|
SecondsToTime (
|
|
IN ULONG Seconds,
|
|
IN BOOLEAN MakeNegative
|
|
);
|
|
|
|
PIRP
|
|
BuildCoreOfSyncIoRequest (
|
|
IN HANDLE FileHandle,
|
|
IN PFILE_OBJECT FileObject OPTIONAL,
|
|
IN PKEVENT Event,
|
|
IN PIO_STATUS_BLOCK IoStatusBlock,
|
|
IN OUT PDEVICE_OBJECT *DeviceObject
|
|
);
|
|
|
|
NTSTATUS
|
|
StartIoAndWait (
|
|
IN PIRP Irp,
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PKEVENT Event,
|
|
IN PIO_STATUS_BLOCK IoStatusBlock
|
|
);
|
|
|
|
//
|
|
// Local declarations
|
|
//
|
|
|
|
VOID
|
|
ScavengerTimerRoutine (
|
|
IN PKDPC Dpc,
|
|
IN PVOID DeferredContext,
|
|
IN PVOID SystemArgument1,
|
|
IN PVOID SystemArgument2
|
|
);
|
|
|
|
VOID
|
|
SrvResourceThread (
|
|
IN PVOID Parameter
|
|
);
|
|
|
|
VOID
|
|
ScavengerThread (
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PVOID Parameter
|
|
);
|
|
|
|
VOID
|
|
ScavengerAlgorithm (
|
|
VOID
|
|
);
|
|
|
|
VOID
|
|
AlerterAlgorithm (
|
|
VOID
|
|
);
|
|
|
|
VOID
|
|
CloseIdleConnection (
|
|
IN PCONNECTION Connection,
|
|
IN PLARGE_INTEGER CurrentTime,
|
|
IN PLARGE_INTEGER DisconnectTime,
|
|
IN PLARGE_INTEGER PastExpirationTime,
|
|
IN PLARGE_INTEGER TwoMinuteWarningTime,
|
|
IN PLARGE_INTEGER FiveMinuteWarningTime
|
|
);
|
|
|
|
VOID
|
|
CreateConnections (
|
|
VOID
|
|
);
|
|
|
|
VOID
|
|
GeneratePeriodicEvents (
|
|
VOID
|
|
);
|
|
|
|
VOID
|
|
ProcessConnectionDisconnects (
|
|
VOID
|
|
);
|
|
|
|
VOID
|
|
ProcessOrphanedBlocks (
|
|
VOID
|
|
);
|
|
|
|
VOID
|
|
TimeoutSessions (
|
|
IN PLARGE_INTEGER CurrentTime
|
|
);
|
|
|
|
VOID
|
|
TimeoutWaitingOpens (
|
|
IN PLARGE_INTEGER CurrentTime
|
|
);
|
|
|
|
VOID
|
|
TimeoutStuckOplockBreaks (
|
|
IN PLARGE_INTEGER CurrentTime
|
|
);
|
|
|
|
VOID
|
|
UpdateConnectionQos (
|
|
IN PLARGE_INTEGER currentTime
|
|
);
|
|
|
|
VOID
|
|
UpdateSessionLastUseTime(
|
|
IN PLARGE_INTEGER CurrentTime
|
|
);
|
|
|
|
VOID
|
|
LazyFreeQueueDataStructures (
|
|
PWORK_QUEUE queue
|
|
);
|
|
|
|
VOID
|
|
SrvUserAlertRaise (
|
|
IN ULONG Message,
|
|
IN ULONG NumberOfStrings,
|
|
IN PUNICODE_STRING String1 OPTIONAL,
|
|
IN PUNICODE_STRING String2 OPTIONAL,
|
|
IN PUNICODE_STRING ComputerName
|
|
);
|
|
|
|
VOID
|
|
SrvAdminAlertRaise (
|
|
IN ULONG Message,
|
|
IN ULONG NumberOfStrings,
|
|
IN PUNICODE_STRING String1 OPTIONAL,
|
|
IN PUNICODE_STRING String2 OPTIONAL,
|
|
IN PUNICODE_STRING String3 OPTIONAL
|
|
);
|
|
|
|
NTSTATUS
|
|
TimeToTimeString (
|
|
IN PLARGE_INTEGER Time,
|
|
OUT PUNICODE_STRING TimeString
|
|
);
|
|
|
|
ULONG
|
|
CalculateErrorSlot (
|
|
PSRV_ERROR_RECORD ErrorRecord
|
|
);
|
|
|
|
VOID
|
|
CheckErrorCount (
|
|
PSRV_ERROR_RECORD ErrorRecord,
|
|
BOOLEAN UseRatio
|
|
);
|
|
|
|
VOID
|
|
CheckDiskSpace (
|
|
VOID
|
|
);
|
|
|
|
NTSTATUS
|
|
OpenAlerter (
|
|
OUT PHANDLE AlerterHandle
|
|
);
|
|
|
|
VOID
|
|
RecalcCoreSearchTimeout(
|
|
VOID
|
|
);
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text( PAGE, SrvInitializeScavenger )
|
|
#pragma alloc_text( PAGE, ScavengerAlgorithm )
|
|
#pragma alloc_text( PAGE, AlerterAlgorithm )
|
|
#pragma alloc_text( PAGE, CloseIdleConnection )
|
|
#pragma alloc_text( PAGE, CreateConnections )
|
|
#pragma alloc_text( PAGE, GeneratePeriodicEvents )
|
|
#pragma alloc_text( PAGE, TimeoutSessions )
|
|
#pragma alloc_text( PAGE, TimeoutWaitingOpens )
|
|
#pragma alloc_text( PAGE, TimeoutStuckOplockBreaks )
|
|
#pragma alloc_text( PAGE, UpdateConnectionQos )
|
|
#pragma alloc_text( PAGE, UpdateSessionLastUseTime )
|
|
#pragma alloc_text( PAGE, SrvUserAlertRaise )
|
|
#pragma alloc_text( PAGE, SrvAdminAlertRaise )
|
|
#pragma alloc_text( PAGE, TimeToTimeString )
|
|
#pragma alloc_text( PAGE, CheckErrorCount )
|
|
#pragma alloc_text( PAGE, CheckDiskSpace )
|
|
#pragma alloc_text( PAGE, OpenAlerter )
|
|
#pragma alloc_text( PAGE, ProcessOrphanedBlocks )
|
|
#pragma alloc_text( PAGE, RecalcCoreSearchTimeout )
|
|
#endif
|
|
#if 0
|
|
NOT PAGEABLE -- SrvTerminateScavenger
|
|
NOT PAGEABLE -- ScavengerTimerRoutine
|
|
NOT PAGEABLE -- SrvResourceThread
|
|
NOT PAGEABLE -- ScavengerThread
|
|
NOT PAGEABLE -- ProcessConnectionDisconnects
|
|
NOT PAGEABLE -- SrvServiceWorkItemShortage
|
|
NOT PAGEABLE -- LazyFreeQueueDataStructures
|
|
NOT PAGEABLE -- SrvUpdateStatisticsFromQueues
|
|
#endif
|
|
|
|
|
|
NTSTATUS
|
|
SrvInitializeScavenger (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function creates the scavenger thread for the LAN Manager
|
|
server FSP.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Status of thread creation
|
|
|
|
--*/
|
|
|
|
{
|
|
LARGE_INTEGER currentTime;
|
|
|
|
PAGED_CODE( );
|
|
|
|
//
|
|
// Initialize the scavenger spin lock.
|
|
//
|
|
|
|
INITIALIZE_SPIN_LOCK( &ScavengerSpinLock );
|
|
|
|
//
|
|
// When this count is zero, we will update the QOS information for
|
|
// each active connection.
|
|
//
|
|
|
|
ScavengerUpdateQosCount = SrvScavengerUpdateQosCount;
|
|
|
|
//
|
|
// When this count is zero, we will check the rfcb active status.
|
|
//
|
|
|
|
ScavengerCheckRfcbActive = SrvScavengerCheckRfcbActive;
|
|
|
|
//
|
|
// Get the current time and calculate the next time the scavenge and
|
|
// alert algorithms need to run.
|
|
//
|
|
|
|
KeQuerySystemTime( ¤tTime );
|
|
NextScavengeTime.QuadPart = currentTime.QuadPart + SrvScavengerTimeout.QuadPart;
|
|
NextAlertTime.QuadPart = currentTime.QuadPart + SrvAlertSchedule.QuadPart;
|
|
|
|
ScavengerWorkItem = IoAllocateWorkItem( SrvDeviceObject );
|
|
if( !ScavengerWorkItem )
|
|
{
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
//
|
|
// Initialize the scavenger DPC, which will queue the work item.
|
|
//
|
|
|
|
KeInitializeDpc( &ScavengerDpc, ScavengerTimerRoutine, NULL );
|
|
|
|
//
|
|
// Start the scavenger timer. When the timer expires, the DPC will
|
|
// run and will queue the work item.
|
|
//
|
|
|
|
KeInitializeTimer( &ScavengerTimer );
|
|
ScavengerInitialized = TRUE;
|
|
KeSetTimer( &ScavengerTimer, ScavengerBaseTimeout, &ScavengerDpc );
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
} // SrvInitializeScavenger
|
|
|
|
|
|
VOID
|
|
SrvTerminateScavenger (
|
|
VOID
|
|
)
|
|
{
|
|
KEVENT scavengerTimerTerminationEvent;
|
|
KEVENT scavengerThreadTerminationEvent;
|
|
KEVENT resourceThreadTerminationEvent;
|
|
BOOLEAN waitForResourceThread;
|
|
BOOLEAN waitForScavengerThread;
|
|
KIRQL oldIrql;
|
|
|
|
if ( ScavengerInitialized ) {
|
|
|
|
//
|
|
// Initialize shutdown events before marking the scavenger as
|
|
// shutting down.
|
|
//
|
|
|
|
KeInitializeEvent(
|
|
&scavengerTimerTerminationEvent,
|
|
NotificationEvent,
|
|
FALSE
|
|
);
|
|
ScavengerTimerTerminationEvent = &scavengerTimerTerminationEvent;
|
|
|
|
KeInitializeEvent(
|
|
&scavengerThreadTerminationEvent,
|
|
NotificationEvent,
|
|
FALSE
|
|
);
|
|
ScavengerThreadTerminationEvent = &scavengerThreadTerminationEvent;
|
|
|
|
KeInitializeEvent(
|
|
&resourceThreadTerminationEvent,
|
|
NotificationEvent,
|
|
FALSE
|
|
);
|
|
ResourceThreadTerminationEvent = &resourceThreadTerminationEvent;
|
|
|
|
//
|
|
// Lock the scavenger, then indicate that we're shutting down.
|
|
// Also, notice whether the resource and scavenger threads are
|
|
// running. Then release the lock. We must notice whether the
|
|
// threads are running while holding the lock so that we can
|
|
// know whether to expect the threads to set their termination
|
|
// events. (We don't have to do this with the scavenger timer
|
|
// because it's always running.)
|
|
//
|
|
|
|
ACQUIRE_SPIN_LOCK( &ScavengerSpinLock, &oldIrql );
|
|
|
|
waitForScavengerThread = ScavengerRunning;
|
|
waitForResourceThread = SrvResourceThreadRunning;
|
|
ScavengerInitialized = FALSE;
|
|
|
|
RELEASE_SPIN_LOCK( &ScavengerSpinLock, oldIrql );
|
|
|
|
//
|
|
// Cancel the scavenger timer. If this works, then we know that
|
|
// the timer DPC code is not running. Otherwise, it is running
|
|
// or queued to run, and we need to wait it to finish.
|
|
//
|
|
|
|
if ( !KeCancelTimer( &ScavengerTimer ) ) {
|
|
KeWaitForSingleObject(
|
|
&scavengerTimerTerminationEvent,
|
|
Executive,
|
|
KernelMode, // don't let stack be paged -- event on stack!
|
|
FALSE,
|
|
NULL
|
|
);
|
|
}
|
|
|
|
//
|
|
// If the scavenger thread was running when we marked the
|
|
// shutdown, wait for it to finish. (If it wasn't running
|
|
// before, we know that it can't be running now, because timer
|
|
// DPC wouldn't have started it once we marked the shutdown.)
|
|
//
|
|
|
|
if ( waitForScavengerThread ) {
|
|
KeWaitForSingleObject(
|
|
&scavengerThreadTerminationEvent,
|
|
Executive,
|
|
KernelMode, // don't let stack be paged -- event on stack!
|
|
FALSE,
|
|
NULL
|
|
);
|
|
}
|
|
else
|
|
{
|
|
IoFreeWorkItem( ScavengerWorkItem );
|
|
}
|
|
|
|
//
|
|
// If the resource thread was running when we marked the
|
|
// shutdown, wait for it to finish. (We know that it can't be
|
|
// started because no other part of the server is running.)
|
|
//
|
|
|
|
if ( waitForResourceThread ) {
|
|
KeWaitForSingleObject(
|
|
&resourceThreadTerminationEvent,
|
|
Executive,
|
|
KernelMode, // don't let stack be paged -- event on stack!
|
|
FALSE,
|
|
NULL
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// At this point, no part of the scavenger is running.
|
|
//
|
|
|
|
return;
|
|
|
|
} // SrvTerminateScavenger
|
|
|
|
|
|
VOID
|
|
SrvResourceThread (
|
|
IN PVOID Parameter
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Main routine for the resource thread. Is called via an executive
|
|
work item when resource work is needed.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOLEAN runAgain = TRUE;
|
|
PWORK_CONTEXT workContext;
|
|
KIRQL oldIrql;
|
|
|
|
do {
|
|
|
|
//
|
|
// The resource event was signaled. This can indicate a number
|
|
// of different things. Currently, this event is signaled for
|
|
// the following reasons:
|
|
//
|
|
// 1. The TDI disconnect event handler was called. The
|
|
// disconnected connection was marked. It is up to the
|
|
// scavenger shutdown the connection.
|
|
//
|
|
// 2. A connection has been accepted.
|
|
//
|
|
|
|
IF_DEBUG(SCAV1) {
|
|
KdPrint(( "SrvResourceThread: Resource event signaled!\n" ));
|
|
}
|
|
|
|
//
|
|
// Service endpoints that need connections.
|
|
//
|
|
|
|
if ( SrvResourceFreeConnection ) {
|
|
SrvResourceFreeConnection = FALSE;
|
|
CreateConnections( );
|
|
}
|
|
|
|
//
|
|
// Service pending disconnects.
|
|
//
|
|
|
|
if ( SrvResourceDisconnectPending ) {
|
|
SrvResourceDisconnectPending = FALSE;
|
|
ProcessConnectionDisconnects( );
|
|
}
|
|
|
|
//
|
|
// Service orphaned connections.
|
|
//
|
|
|
|
if ( SrvResourceOrphanedBlocks ) {
|
|
ProcessOrphanedBlocks( );
|
|
}
|
|
|
|
//
|
|
// At the end of the loop, check to see whether we need to run
|
|
// the loop again.
|
|
//
|
|
|
|
ACQUIRE_GLOBAL_SPIN_LOCK( Fsd, &oldIrql );
|
|
|
|
if ( !SrvResourceDisconnectPending &&
|
|
!SrvResourceOrphanedBlocks &&
|
|
!SrvResourceFreeConnection ) {
|
|
|
|
//
|
|
// No more work to do. If the server is shutting down,
|
|
// set the event that tells SrvTerminateScavenger that the
|
|
// resource thread is done running.
|
|
//
|
|
|
|
SrvResourceThreadRunning = FALSE;
|
|
runAgain = FALSE;
|
|
|
|
if ( !ScavengerInitialized ) {
|
|
KeSetEvent( ResourceThreadTerminationEvent, 0, FALSE );
|
|
}
|
|
|
|
}
|
|
|
|
RELEASE_GLOBAL_SPIN_LOCK( Fsd, oldIrql );
|
|
|
|
} while ( runAgain );
|
|
|
|
ObDereferenceObject( SrvDeviceObject );
|
|
|
|
return;
|
|
|
|
} // SrvResourceThread
|
|
|
|
|
|
VOID
|
|
ScavengerTimerRoutine (
|
|
IN PKDPC Dpc,
|
|
IN PVOID DeferredContext,
|
|
IN PVOID SystemArgument1,
|
|
IN PVOID SystemArgument2
|
|
)
|
|
{
|
|
BOOLEAN runShortTerm;
|
|
BOOLEAN runScavenger;
|
|
BOOLEAN runAlerter;
|
|
BOOLEAN start;
|
|
|
|
LARGE_INTEGER currentTime;
|
|
|
|
Dpc, DeferredContext; // prevent compiler warnings
|
|
|
|
//
|
|
// Query the system time (in ticks).
|
|
//
|
|
|
|
SET_SERVER_TIME( SrvWorkQueues );
|
|
|
|
//
|
|
// Capture the current time (in 100ns units).
|
|
//
|
|
|
|
currentTime.LowPart = PtrToUlong(SystemArgument1);
|
|
currentTime.HighPart = PtrToUlong(SystemArgument2);
|
|
|
|
//
|
|
// Determine which algorithms (if any) need to run.
|
|
//
|
|
|
|
start = FALSE;
|
|
|
|
if ( !IsListEmpty( &SrvOplockBreaksInProgressList ) ) {
|
|
runShortTerm = TRUE;
|
|
start = TRUE;
|
|
} else {
|
|
runShortTerm = FALSE;
|
|
}
|
|
|
|
if ( currentTime.QuadPart >= NextScavengeTime.QuadPart ) {
|
|
runScavenger = TRUE;
|
|
start = TRUE;
|
|
} else {
|
|
runScavenger = FALSE;
|
|
}
|
|
|
|
if ( currentTime.QuadPart >= NextAlertTime.QuadPart ) {
|
|
runAlerter = TRUE;
|
|
start = TRUE;
|
|
} else {
|
|
runAlerter = FALSE;
|
|
}
|
|
|
|
//
|
|
// If necessary, start the scavenger thread. Don't do this if
|
|
// the server is shutting down.
|
|
//
|
|
|
|
ACQUIRE_DPC_SPIN_LOCK( &ScavengerSpinLock );
|
|
|
|
if ( !ScavengerInitialized ) {
|
|
|
|
KeSetEvent( ScavengerTimerTerminationEvent, 0, FALSE );
|
|
|
|
} else {
|
|
|
|
if ( start ) {
|
|
|
|
if ( runShortTerm ) {
|
|
RunShortTermAlgorithm = TRUE;
|
|
}
|
|
|
|
if ( runScavenger ) {
|
|
RunScavengerAlgorithm = TRUE;
|
|
NextScavengeTime.QuadPart += SrvScavengerTimeout.QuadPart;
|
|
}
|
|
if ( runAlerter ) {
|
|
RunAlerterAlgorithm = TRUE;
|
|
NextAlertTime.QuadPart += SrvAlertSchedule.QuadPart;
|
|
}
|
|
|
|
if( !ScavengerRunning )
|
|
{
|
|
ScavengerRunning = TRUE;
|
|
IoQueueWorkItem( ScavengerWorkItem, ScavengerThread, CriticalWorkQueue, NULL );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Restart the timer.
|
|
//
|
|
|
|
KeSetTimer( &ScavengerTimer, ScavengerBaseTimeout, &ScavengerDpc );
|
|
|
|
}
|
|
|
|
RELEASE_DPC_SPIN_LOCK( &ScavengerSpinLock );
|
|
|
|
return;
|
|
|
|
} // ScavengerTimerRoutine
|
|
|
|
#if DBG_STUCK
|
|
|
|
//
|
|
// This keeps a record of the operation which has taken the longest time
|
|
// in the server
|
|
//
|
|
struct {
|
|
ULONG Seconds;
|
|
UCHAR Command;
|
|
UCHAR ClientName[ 16 ];
|
|
} SrvMostStuck;
|
|
|
|
VOID
|
|
SrvLookForStuckOperations()
|
|
{
|
|
USHORT index;
|
|
PLIST_ENTRY listEntry;
|
|
PLIST_ENTRY connectionListEntry;
|
|
PENDPOINT endpoint;
|
|
PCONNECTION connection;
|
|
KIRQL oldIrql;
|
|
BOOLEAN printed = FALSE;
|
|
ULONG stuckCount = 0;
|
|
|
|
//
|
|
// Look at all of the InProgress work items and chatter about any
|
|
// which look stuck
|
|
//
|
|
|
|
ACQUIRE_LOCK( &SrvEndpointLock );
|
|
|
|
listEntry = SrvEndpointList.ListHead.Flink;
|
|
|
|
while ( listEntry != &SrvEndpointList.ListHead ) {
|
|
|
|
endpoint = CONTAINING_RECORD(
|
|
listEntry,
|
|
ENDPOINT,
|
|
GlobalEndpointListEntry
|
|
);
|
|
|
|
//
|
|
// If this endpoint is closing, skip to the next one.
|
|
// Otherwise, reference the endpoint so that it can't go away.
|
|
//
|
|
|
|
if ( GET_BLOCK_STATE(endpoint) != BlockStateActive ) {
|
|
listEntry = listEntry->Flink;
|
|
continue;
|
|
}
|
|
|
|
SrvReferenceEndpoint( endpoint );
|
|
|
|
index = (USHORT)-1;
|
|
|
|
while ( TRUE ) {
|
|
|
|
PLIST_ENTRY wlistEntry, wlistHead;
|
|
KIRQL oldIrql;
|
|
LARGE_INTEGER now;
|
|
|
|
//
|
|
// Get the next active connection in the table. If no more
|
|
// are available, WalkConnectionTable returns NULL.
|
|
// Otherwise, it returns a referenced pointer to a
|
|
// connection.
|
|
//
|
|
|
|
connection = WalkConnectionTable( endpoint, &index );
|
|
if ( connection == NULL ) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Now walk the InProgressWorkItemList to see if any work items
|
|
// look stuck
|
|
//
|
|
wlistHead = &connection->InProgressWorkItemList;
|
|
wlistEntry = wlistHead;
|
|
|
|
KeQuerySystemTime( &now );
|
|
|
|
ACQUIRE_SPIN_LOCK( connection->EndpointSpinLock, &oldIrql )
|
|
|
|
while ( wlistEntry->Flink != wlistHead ) {
|
|
|
|
PWORK_CONTEXT workContext;
|
|
PSMB_HEADER header;
|
|
LARGE_INTEGER interval;
|
|
|
|
wlistEntry = wlistEntry->Flink;
|
|
|
|
workContext = CONTAINING_RECORD(
|
|
wlistEntry,
|
|
WORK_CONTEXT,
|
|
InProgressListEntry
|
|
);
|
|
|
|
interval.QuadPart = now.QuadPart - workContext->OpStartTime.QuadPart;
|
|
|
|
//
|
|
// Any operation over 45 seconds is VERY stuck....
|
|
//
|
|
|
|
if( workContext->IsNotStuck || interval.LowPart < 45 * 10000000 ) {
|
|
continue;
|
|
}
|
|
|
|
header = workContext->RequestHeader;
|
|
|
|
if ( (workContext->BlockHeader.ReferenceCount != 0) &&
|
|
(workContext->ProcessingCount != 0) &&
|
|
header != NULL ) {
|
|
|
|
//
|
|
// Convert to seconds
|
|
//
|
|
interval.LowPart /= 10000000;
|
|
|
|
if( !printed ) {
|
|
IF_STRESS() KdPrint(( "--- Potential stuck SRV.SYS Operations ---\n" ));
|
|
printed = TRUE;
|
|
}
|
|
|
|
if( interval.LowPart > SrvMostStuck.Seconds ) {
|
|
SrvMostStuck.Seconds = interval.LowPart;
|
|
RtlCopyMemory( SrvMostStuck.ClientName,
|
|
connection->OemClientMachineNameString.Buffer,
|
|
MIN( 16, connection->OemClientMachineNameString.Length )),
|
|
SrvMostStuck.ClientName[ MIN( 15, connection->OemClientMachineNameString.Length ) ] = 0;
|
|
SrvMostStuck.Command = header->Command;
|
|
}
|
|
|
|
if( stuckCount++ < 5 ) {
|
|
IF_STRESS() KdPrint(( "Client %s, %u secs, Context %p",
|
|
connection->OemClientMachineNameString.Buffer,
|
|
interval.LowPart, workContext ));
|
|
|
|
switch( header->Command ) {
|
|
case SMB_COM_NT_CREATE_ANDX:
|
|
IF_STRESS() KdPrint(( " NT_CREATE_ANDX\n" ));
|
|
break;
|
|
case SMB_COM_OPEN_PRINT_FILE:
|
|
IF_STRESS() KdPrint(( " OPEN_PRINT_FILE\n" ));
|
|
break;
|
|
case SMB_COM_CLOSE_PRINT_FILE:
|
|
IF_STRESS() KdPrint(( " CLOSE_PRINT_FILE\n" ));
|
|
break;
|
|
case SMB_COM_CLOSE:
|
|
IF_STRESS() KdPrint(( " CLOSE\n" ));
|
|
break;
|
|
case SMB_COM_SESSION_SETUP_ANDX:
|
|
IF_STRESS() KdPrint(( " SESSION_SETUP\n" ));
|
|
break;
|
|
case SMB_COM_OPEN_ANDX:
|
|
IF_STRESS() KdPrint(( " OPEN_ANDX\n" ));
|
|
break;
|
|
case SMB_COM_NT_TRANSACT:
|
|
case SMB_COM_NT_TRANSACT_SECONDARY:
|
|
IF_STRESS() KdPrint(( " NT_TRANSACT\n" ));
|
|
break;
|
|
case SMB_COM_TRANSACTION2:
|
|
case SMB_COM_TRANSACTION2_SECONDARY:
|
|
IF_STRESS() KdPrint(( " TRANSACTION2\n" ));
|
|
break;
|
|
case SMB_COM_TRANSACTION:
|
|
case SMB_COM_TRANSACTION_SECONDARY:
|
|
IF_STRESS() KdPrint(( " TRANSACTION\n" ));
|
|
break;
|
|
default:
|
|
IF_STRESS() KdPrint(( " Cmd %X\n", header->Command ));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
RELEASE_SPIN_LOCK( connection->EndpointSpinLock, oldIrql );
|
|
|
|
SrvDereferenceConnection( connection );
|
|
|
|
} // walk connection list
|
|
|
|
//
|
|
// Capture a pointer to the next endpoint in the list (that one
|
|
// can't go away because we hold the endpoint list), then
|
|
// dereference the current endpoint.
|
|
//
|
|
|
|
listEntry = listEntry->Flink;
|
|
SrvDereferenceEndpoint( endpoint );
|
|
|
|
} // walk endpoint list
|
|
|
|
if( printed && SrvMostStuck.Seconds ) {
|
|
IF_STRESS() KdPrint(( "Longest so far: %s, %u secs, cmd %u\n", SrvMostStuck.ClientName, SrvMostStuck.Seconds, SrvMostStuck.Command ));
|
|
}
|
|
|
|
if( stuckCount ) {
|
|
//DbgBreakPoint();
|
|
}
|
|
|
|
RELEASE_LOCK( &SrvEndpointLock );
|
|
}
|
|
#endif
|
|
|
|
|
|
VOID
|
|
ScavengerThread (
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PVOID Parameter
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Main routine for the FSP scavenger thread. Is called via an
|
|
executive work item when scavenger work is needed.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOLEAN runAgain = TRUE;
|
|
BOOLEAN oldPopupStatus;
|
|
BOOLEAN finalExecution = FALSE;
|
|
KIRQL oldIrql;
|
|
|
|
Parameter; // prevent compiler warnings
|
|
|
|
IF_DEBUG(SCAV1) KdPrint(( "ScavengerThread entered\n" ));
|
|
|
|
//
|
|
// Make sure that the thread does not generate pop-ups. We need to do
|
|
// this because the scavenger might be called by an Ex worker thread,
|
|
// which unlike the srv worker threads, don't have popups disabled.
|
|
//
|
|
|
|
oldPopupStatus = IoSetThreadHardErrorMode( FALSE );
|
|
|
|
//
|
|
// Main loop, executed until no scavenger events are set.
|
|
//
|
|
|
|
do {
|
|
|
|
#if DBG_STUCK
|
|
IF_STRESS() SrvLookForStuckOperations();
|
|
#endif
|
|
|
|
//
|
|
// If the short-term timer expired, run that algorithm now.
|
|
//
|
|
|
|
if ( RunShortTermAlgorithm ) {
|
|
|
|
LARGE_INTEGER currentTime;
|
|
|
|
RunShortTermAlgorithm = FALSE;
|
|
|
|
KeQuerySystemTime( ¤tTime );
|
|
|
|
//
|
|
// Time out oplock break requests.
|
|
//
|
|
|
|
TimeoutStuckOplockBreaks( ¤tTime );
|
|
}
|
|
|
|
//
|
|
// If the scavenger timer expired, run that algorithm now.
|
|
//
|
|
|
|
if ( RunScavengerAlgorithm ) {
|
|
//KePrintSpinLockCounts( 0 );
|
|
RunScavengerAlgorithm = FALSE;
|
|
ScavengerAlgorithm( );
|
|
}
|
|
|
|
//
|
|
// If the short-term timer expired, run that algorithm now.
|
|
// Note that we check the short-term timer twice in the loop
|
|
// in order to get more timely processing of the algorithm.
|
|
//
|
|
|
|
//if ( RunShortTermAlgorithm ) {
|
|
// RunShortTermAlgorithm = FALSE;
|
|
// ShortTermAlgorithm( );
|
|
//}
|
|
|
|
//
|
|
// If the alerter timer expired, run that algorithm now.
|
|
//
|
|
|
|
if ( RunAlerterAlgorithm ) {
|
|
RunAlerterAlgorithm = FALSE;
|
|
AlerterAlgorithm( );
|
|
}
|
|
|
|
//
|
|
// At the end of the loop, check to see whether we need to run
|
|
// the loop again.
|
|
//
|
|
|
|
ACQUIRE_SPIN_LOCK( &ScavengerSpinLock, &oldIrql );
|
|
|
|
if ( !RunShortTermAlgorithm &&
|
|
!RunScavengerAlgorithm &&
|
|
!RunAlerterAlgorithm ) {
|
|
|
|
//
|
|
// No more work to do. If the server is shutting down,
|
|
// set the event that tells SrvTerminateScavenger that the
|
|
// scavenger thread is done running.
|
|
//
|
|
|
|
ScavengerRunning = FALSE;
|
|
runAgain = FALSE;
|
|
|
|
if ( !ScavengerInitialized ) {
|
|
// The server was stopped while the scavenger was queued,
|
|
// so we need to delete the WorkItem ourselves.
|
|
finalExecution = TRUE;
|
|
KeSetEvent( ScavengerThreadTerminationEvent, 0, FALSE );
|
|
}
|
|
|
|
}
|
|
|
|
RELEASE_SPIN_LOCK( &ScavengerSpinLock, oldIrql );
|
|
|
|
} while ( runAgain );
|
|
|
|
//
|
|
// reset popup status.
|
|
//
|
|
|
|
IoSetThreadHardErrorMode( oldPopupStatus );
|
|
|
|
if( finalExecution )
|
|
{
|
|
IoFreeWorkItem( ScavengerWorkItem );
|
|
ScavengerWorkItem = NULL;
|
|
}
|
|
|
|
return;
|
|
|
|
} // ScavengerThread
|
|
|
|
VOID
|
|
DestroySuspectConnections(
|
|
VOID
|
|
)
|
|
{
|
|
USHORT index;
|
|
PLIST_ENTRY listEntry;
|
|
PLIST_ENTRY connectionListEntry;
|
|
PENDPOINT endpoint;
|
|
PCONNECTION connection;
|
|
KIRQL oldIrql;
|
|
BOOLEAN printed = FALSE;
|
|
ULONG stuckCount = 0;
|
|
|
|
IF_DEBUG( SCAV1 ) {
|
|
KdPrint(( "Looking for Suspect Connections.\n" ));
|
|
}
|
|
|
|
//
|
|
// Look at all of the InProgress work items and chatter about any
|
|
// which look stuck
|
|
//
|
|
|
|
ACQUIRE_LOCK( &SrvEndpointLock );
|
|
|
|
listEntry = SrvEndpointList.ListHead.Flink;
|
|
|
|
while ( listEntry != &SrvEndpointList.ListHead ) {
|
|
|
|
endpoint = CONTAINING_RECORD(
|
|
listEntry,
|
|
ENDPOINT,
|
|
GlobalEndpointListEntry
|
|
);
|
|
|
|
//
|
|
// If this endpoint is closing, skip to the next one.
|
|
// Otherwise, reference the endpoint so that it can't go away.
|
|
//
|
|
|
|
if ( GET_BLOCK_STATE(endpoint) != BlockStateActive ) {
|
|
listEntry = listEntry->Flink;
|
|
continue;
|
|
}
|
|
|
|
SrvReferenceEndpoint( endpoint );
|
|
|
|
index = (USHORT)-1;
|
|
|
|
while ( TRUE ) {
|
|
|
|
PLIST_ENTRY wlistEntry, wlistHead;
|
|
KIRQL oldIrql;
|
|
LARGE_INTEGER now;
|
|
|
|
//
|
|
// Get the next active connection in the table. If no more
|
|
// are available, WalkConnectionTable returns NULL.
|
|
// Otherwise, it returns a referenced pointer to a
|
|
// connection.
|
|
//
|
|
|
|
connection = WalkConnectionTable( endpoint, &index );
|
|
if ( connection == NULL ) {
|
|
break;
|
|
}
|
|
|
|
if( connection->IsConnectionSuspect )
|
|
{
|
|
// Prevent us from flooding the eventlog by only logging X DOS attacks every 24 hours.
|
|
LARGE_INTEGER CurrentTime;
|
|
KeQuerySystemTime( &CurrentTime );
|
|
if( CurrentTime.QuadPart > SrvLastDosAttackTime.QuadPart + SRV_ONE_DAY )
|
|
{
|
|
// reset the counter every 24 hours
|
|
SrvDOSAttacks = 0;
|
|
SrvLastDosAttackTime.QuadPart = CurrentTime.QuadPart;
|
|
SrvLogEventOnDOS = TRUE;
|
|
}
|
|
|
|
IF_DEBUG( ERRORS )
|
|
{
|
|
KdPrint(( "Disconnected suspected DoS attack (%z)\n", (PCSTRING)&connection->OemClientMachineNameString ));
|
|
}
|
|
|
|
RELEASE_LOCK( &SrvEndpointLock );
|
|
|
|
// Log an event if we need to
|
|
if( SrvLogEventOnDOS )
|
|
{
|
|
SrvLogError(
|
|
SrvDeviceObject,
|
|
EVENT_SRV_DOS_ATTACK_DETECTED,
|
|
STATUS_ACCESS_DENIED,
|
|
NULL,
|
|
0,
|
|
&connection->PagedConnection->ClientMachineNameString,
|
|
1
|
|
);
|
|
|
|
SrvDOSAttacks++;
|
|
if( SrvDOSAttacks > SRV_MAX_DOS_ATTACK_EVENT_LOGS )
|
|
{
|
|
SrvLogEventOnDOS = FALSE;
|
|
|
|
SrvLogError(
|
|
SrvDeviceObject,
|
|
EVENT_SRV_TOO_MANY_DOS,
|
|
STATUS_ACCESS_DENIED,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
0
|
|
);
|
|
}
|
|
}
|
|
|
|
connection->DisconnectReason = DisconnectSuspectedDOSConnection;
|
|
SrvCloseConnection( connection, FALSE );
|
|
|
|
ACQUIRE_LOCK( &SrvEndpointLock );
|
|
}
|
|
|
|
SrvDereferenceConnection( connection );
|
|
|
|
} // walk connection list
|
|
|
|
//
|
|
// Capture a pointer to the next endpoint in the list (that one
|
|
// can't go away because we hold the endpoint list), then
|
|
// dereference the current endpoint.
|
|
//
|
|
|
|
listEntry = listEntry->Flink;
|
|
SrvDereferenceEndpoint( endpoint );
|
|
|
|
} // walk endpoint list
|
|
|
|
RELEASE_LOCK( &SrvEndpointLock );
|
|
|
|
RunSuspectConnectionAlgorithm = FALSE;
|
|
}
|
|
|
|
|
|
VOID
|
|
ScavengerAlgorithm (
|
|
VOID
|
|
)
|
|
{
|
|
LARGE_INTEGER currentTime;
|
|
ULONG currentTick;
|
|
UNICODE_STRING insertionString[2];
|
|
WCHAR secondsBuffer[20];
|
|
WCHAR shortageBuffer[20];
|
|
BOOLEAN logError = FALSE;
|
|
PWORK_QUEUE queue;
|
|
|
|
PAGED_CODE( );
|
|
|
|
IF_DEBUG(SCAV1) KdPrint(( "ScavengerAlgorithm entered\n" ));
|
|
|
|
KeQuerySystemTime( ¤tTime );
|
|
GET_SERVER_TIME( SrvWorkQueues, ¤tTick );
|
|
|
|
//
|
|
// EventSwitch is used to schedule parts of the scavenger algorithm
|
|
// to run every other iteration.
|
|
//
|
|
|
|
EventSwitch = !EventSwitch;
|
|
|
|
//
|
|
// Time out opens that are waiting too long for the other
|
|
// opener to break the oplock.
|
|
//
|
|
|
|
TimeoutWaitingOpens( ¤tTime );
|
|
|
|
//
|
|
// Time out oplock break requests.
|
|
//
|
|
|
|
TimeoutStuckOplockBreaks( ¤tTime );
|
|
|
|
//
|
|
// Check for malicious attacks
|
|
//
|
|
if( RunSuspectConnectionAlgorithm )
|
|
{
|
|
DestroySuspectConnections( );
|
|
}
|
|
|
|
//
|
|
// See if we can free some work items at this time.
|
|
//
|
|
|
|
for( queue = SrvWorkQueues; queue < eSrvWorkQueues; queue++ ) {
|
|
LazyFreeQueueDataStructures( queue );
|
|
}
|
|
|
|
//
|
|
// See if we need to update QOS information.
|
|
//
|
|
|
|
if ( --ScavengerUpdateQosCount < 0 ) {
|
|
UpdateConnectionQos( ¤tTime );
|
|
ScavengerUpdateQosCount = SrvScavengerUpdateQosCount;
|
|
}
|
|
|
|
//
|
|
// See if we need to walk the rfcb list to update the session
|
|
// last use time.
|
|
//
|
|
|
|
if ( --ScavengerCheckRfcbActive < 0 ) {
|
|
UpdateSessionLastUseTime( ¤tTime );
|
|
ScavengerCheckRfcbActive = SrvScavengerCheckRfcbActive;
|
|
}
|
|
|
|
//
|
|
// See if we need to log an error for resource shortages
|
|
//
|
|
|
|
if ( FailedWorkItemAllocations > 0 ||
|
|
SrvOutOfFreeConnectionCount > 0 ||
|
|
SrvOutOfRawWorkItemCount > 0 ||
|
|
SrvFailedBlockingIoCount > 0 ) {
|
|
|
|
//
|
|
// Setup the strings for use in logging work item allocation failures.
|
|
//
|
|
|
|
insertionString[0].Buffer = shortageBuffer;
|
|
insertionString[0].MaximumLength = sizeof(shortageBuffer);
|
|
insertionString[1].Buffer = secondsBuffer;
|
|
insertionString[1].MaximumLength = sizeof(secondsBuffer);
|
|
|
|
(VOID) RtlIntegerToUnicodeString(
|
|
SrvScavengerTimeoutInSeconds * 2,
|
|
10,
|
|
&insertionString[1]
|
|
);
|
|
|
|
logError = TRUE;
|
|
}
|
|
|
|
if ( EventSwitch ) {
|
|
ULONG FailedCount;
|
|
|
|
//
|
|
// If we were unable to allocate any work items during
|
|
// the last two scavenger intervals, log an error.
|
|
//
|
|
|
|
FailedCount = InterlockedExchange( &FailedWorkItemAllocations, 0 );
|
|
|
|
if ( FailedCount != 0 ) {
|
|
|
|
(VOID) RtlIntegerToUnicodeString(
|
|
FailedCount,
|
|
10,
|
|
&insertionString[0]
|
|
);
|
|
|
|
SrvLogError(
|
|
SrvDeviceObject,
|
|
EVENT_SRV_NO_WORK_ITEM,
|
|
STATUS_INSUFFICIENT_RESOURCES,
|
|
NULL,
|
|
0,
|
|
insertionString,
|
|
2
|
|
);
|
|
}
|
|
|
|
//
|
|
// Generate periodic events and alerts (for events that
|
|
// could happen very quickly, so we don't flood the event
|
|
// log).
|
|
//
|
|
|
|
GeneratePeriodicEvents( );
|
|
|
|
} else {
|
|
|
|
if ( logError ) {
|
|
|
|
//
|
|
// If we failed to find free connections during
|
|
// the last two scavenger intervals, log an error.
|
|
//
|
|
|
|
if ( SrvOutOfFreeConnectionCount > 0 ) {
|
|
|
|
(VOID) RtlIntegerToUnicodeString(
|
|
SrvOutOfFreeConnectionCount,
|
|
10,
|
|
&insertionString[0]
|
|
);
|
|
|
|
SrvLogError(
|
|
SrvDeviceObject,
|
|
EVENT_SRV_NO_FREE_CONNECTIONS,
|
|
STATUS_INSUFFICIENT_RESOURCES,
|
|
NULL,
|
|
0,
|
|
insertionString,
|
|
2
|
|
);
|
|
|
|
SrvOutOfFreeConnectionCount = 0;
|
|
}
|
|
|
|
//
|
|
// If we failed to find free raw work items during
|
|
// the last two scavenger intervals, log an error.
|
|
//
|
|
|
|
if ( SrvOutOfRawWorkItemCount > 0 ) {
|
|
|
|
(VOID) RtlIntegerToUnicodeString(
|
|
SrvOutOfRawWorkItemCount,
|
|
10,
|
|
&insertionString[0]
|
|
);
|
|
|
|
SrvLogError(
|
|
SrvDeviceObject,
|
|
EVENT_SRV_NO_FREE_RAW_WORK_ITEM,
|
|
STATUS_INSUFFICIENT_RESOURCES,
|
|
NULL,
|
|
0,
|
|
insertionString,
|
|
2
|
|
);
|
|
|
|
SrvOutOfRawWorkItemCount = 0;
|
|
}
|
|
|
|
//
|
|
// If we failed a blocking io due to resource shortages during
|
|
// the last two scavenger intervals, log an error.
|
|
//
|
|
|
|
if ( SrvFailedBlockingIoCount > 0 ) {
|
|
|
|
(VOID) RtlIntegerToUnicodeString(
|
|
SrvFailedBlockingIoCount,
|
|
10,
|
|
&insertionString[0]
|
|
);
|
|
|
|
SrvLogError(
|
|
SrvDeviceObject,
|
|
EVENT_SRV_NO_BLOCKING_IO,
|
|
STATUS_INSUFFICIENT_RESOURCES,
|
|
NULL,
|
|
0,
|
|
insertionString,
|
|
2
|
|
);
|
|
|
|
SrvFailedBlockingIoCount = 0;
|
|
}
|
|
|
|
} // if ( logError )
|
|
|
|
//
|
|
// Recalculate the core search timeout time.
|
|
//
|
|
|
|
RecalcCoreSearchTimeout( );
|
|
|
|
//
|
|
// Time out users/connections that have been idle too long
|
|
// (autodisconnect).
|
|
//
|
|
|
|
TimeoutSessions( ¤tTime );
|
|
|
|
//
|
|
// Update the statistics from the the queues
|
|
//
|
|
|
|
SrvUpdateStatisticsFromQueues( NULL );
|
|
|
|
}
|
|
|
|
// Update the DoS variables as necessary for rundown. This reduces the percentage
|
|
// of work-items we free up whenever we detect a DoS by running out of work items
|
|
if( SrvDoSRundownIncreased && !SrvDoSRundownDetector )
|
|
{
|
|
// We've increased the percentage at some time, but no DoS has been detected since
|
|
// the last execution of the scavenger, so we can reduce our tear down amount
|
|
SRV_DOS_DECREASE_TEARDOWN();
|
|
}
|
|
SrvDoSRundownDetector = FALSE;
|
|
|
|
return;
|
|
|
|
} // ScavengerAlgorithm
|
|
|
|
|
|
VOID
|
|
AlerterAlgorithm (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
The other scavenger thread. This routine checks the server for
|
|
alert conditions, and if necessary raises alerts.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PAGED_CODE( );
|
|
|
|
IF_DEBUG(SCAV1) KdPrint(( "AlerterAlgorithm entered\n" ));
|
|
|
|
CheckErrorCount( &SrvErrorRecord, FALSE );
|
|
CheckErrorCount( &SrvNetworkErrorRecord, TRUE );
|
|
CheckDiskSpace();
|
|
|
|
return;
|
|
|
|
} // AlerterAlgorithm
|
|
|
|
|
|
VOID
|
|
CloseIdleConnection (
|
|
IN PCONNECTION Connection,
|
|
IN PLARGE_INTEGER CurrentTime,
|
|
IN PLARGE_INTEGER DisconnectTime,
|
|
IN PLARGE_INTEGER PastExpirationTime,
|
|
IN PLARGE_INTEGER TwoMinuteWarningTime,
|
|
IN PLARGE_INTEGER FiveMinuteWarningTime
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
The routine checks to see if some sessions need to be closed becaused
|
|
it has been idle too long or has exceeded its logon hours.
|
|
|
|
Endpoint lock assumed held.
|
|
|
|
Arguments:
|
|
|
|
Connection - The connection whose sessions we are currently looking at.
|
|
CurrentTime - The currest system time.
|
|
DisconnectTime - The time beyond which the session will be autodisconnected.
|
|
PastExpirationTime - Time when the past expiration message will be sent.
|
|
TwoMinuteWarningTime - Time when the 2 min warning will be sent.
|
|
FiveMinuteWarningTime - Time when the 5 min warning will be sent.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PTABLE_HEADER tableHeader;
|
|
NTSTATUS status;
|
|
BOOLEAN sessionClosed = FALSE;
|
|
PPAGED_CONNECTION pagedConnection = Connection->PagedConnection;
|
|
LONG i;
|
|
ULONG AllSessionsIdle = TRUE;
|
|
ULONG HasSessions = FALSE;
|
|
|
|
PAGED_CODE( );
|
|
|
|
//
|
|
// Is this is a connectionless connection (IPX), check first to see
|
|
// if it's been too long since we heard from the client. The client
|
|
// is supposed to send Echo SMBs every few minutes if nothing else
|
|
// is going on.
|
|
//
|
|
|
|
if ( Connection->Endpoint->IsConnectionless ) {
|
|
|
|
//
|
|
// Calculate the number of clock ticks that have happened since
|
|
// we last heard from the client. If that's more than we allow,
|
|
// kill the connection.
|
|
//
|
|
|
|
GET_SERVER_TIME( Connection->CurrentWorkQueue, (PULONG)&i );
|
|
i -= Connection->LastRequestTime;
|
|
if ( i > 0 && (ULONG)i > SrvIpxAutodisconnectTimeout ) {
|
|
IF_DEBUG( IPX2 ) {
|
|
KdPrint(("CloseIdleConnection: closing IPX conn %p, idle %u\n", Connection, i ));
|
|
}
|
|
Connection->DisconnectReason = DisconnectIdleConnection;
|
|
SrvCloseConnection( Connection, FALSE );
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Walk the active connection list, looking for connections that
|
|
// are idle.
|
|
//
|
|
|
|
tableHeader = &pagedConnection->SessionTable;
|
|
|
|
ACQUIRE_LOCK( &Connection->Lock );
|
|
|
|
for ( i = 0; i < tableHeader->TableSize; i++ ) {
|
|
|
|
PSESSION session = (PSESSION)tableHeader->Table[i].Owner;
|
|
|
|
if( session == NULL ) {
|
|
continue;
|
|
}
|
|
|
|
HasSessions = TRUE;
|
|
|
|
if ( GET_BLOCK_STATE( session ) == BlockStateActive ) {
|
|
|
|
SrvReferenceSession( session );
|
|
RELEASE_LOCK( &Connection->Lock );
|
|
|
|
//
|
|
// Test whether the session has been idle too long, and whether
|
|
// there are any files open on the session. If there are open
|
|
// files, we must not close the session, as this would seriously
|
|
// confuse the client. For purposes of autodisconnect, "open
|
|
// files" referes to open searches and blocking comm device
|
|
// requests as well as files actually opened.
|
|
//
|
|
|
|
if ( AllSessionsIdle == TRUE &&
|
|
(session->LastUseTime.QuadPart >= DisconnectTime->QuadPart ||
|
|
session->CurrentFileOpenCount != 0 ||
|
|
session->CurrentSearchOpenCount != 0 )
|
|
) {
|
|
|
|
AllSessionsIdle = FALSE;
|
|
}
|
|
|
|
// Check if the session has expired
|
|
if( session->LogOffTime.QuadPart < CurrentTime->QuadPart )
|
|
{
|
|
session->IsSessionExpired = TRUE;
|
|
KdPrint(( "Marking session as expired (scavenger)\n" ));
|
|
}
|
|
|
|
// Look for forced log-offs
|
|
if ( !SrvEnableForcedLogoff &&
|
|
!session->LogonSequenceInProgress &&
|
|
!session->LogoffAlertSent &&
|
|
PastExpirationTime->QuadPart <
|
|
session->LastExpirationMessage.QuadPart ) {
|
|
|
|
//
|
|
// Checks for forced logoff. If the client is beyond his logon
|
|
// hours, force him off. If the end of logon hours is
|
|
// approaching, send a warning message. Forced logoff occurs
|
|
// regardless of whether the client has open files or searches.
|
|
//
|
|
|
|
UNICODE_STRING timeString;
|
|
|
|
status = TimeToTimeString( &session->KickOffTime, &timeString );
|
|
|
|
if ( NT_SUCCESS(status) ) {
|
|
|
|
//
|
|
// Only the scavenger thread sets this, so no mutual
|
|
// exclusion is necessary.
|
|
//
|
|
|
|
session->LastExpirationMessage = *CurrentTime;
|
|
|
|
SrvUserAlertRaise(
|
|
MTXT_Past_Expiration_Message,
|
|
2,
|
|
&session->Connection->Endpoint->DomainName,
|
|
&timeString,
|
|
&pagedConnection->ClientMachineNameString
|
|
);
|
|
|
|
RtlFreeUnicodeString( &timeString );
|
|
}
|
|
|
|
// !!! need to raise an admin alert in this case?
|
|
|
|
} else if ( !session->LogoffAlertSent &&
|
|
!session->LogonSequenceInProgress &&
|
|
session->KickOffTime.QuadPart < CurrentTime->QuadPart ) {
|
|
|
|
session->LogoffAlertSent = TRUE;
|
|
|
|
SrvUserAlertRaise(
|
|
MTXT_Expiration_Message,
|
|
1,
|
|
&session->Connection->Endpoint->DomainName,
|
|
NULL,
|
|
&pagedConnection->ClientMachineNameString
|
|
);
|
|
|
|
//
|
|
// If real forced logoff is not enabled, all we do is send an
|
|
// alert, don't actually close the session/connection.
|
|
//
|
|
|
|
if ( SrvEnableForcedLogoff ) {
|
|
|
|
//
|
|
// Increment the count of sessions that have been
|
|
// forced to logoff.
|
|
//
|
|
|
|
SrvStatistics.SessionsForcedLogOff++;
|
|
|
|
SrvCloseSession( session );
|
|
sessionClosed = TRUE;
|
|
}
|
|
|
|
} else if ( SrvEnableForcedLogoff &&
|
|
!session->LogonSequenceInProgress &&
|
|
!session->TwoMinuteWarningSent &&
|
|
session->KickOffTime.QuadPart <
|
|
TwoMinuteWarningTime->QuadPart ) {
|
|
|
|
UNICODE_STRING timeString;
|
|
|
|
status = TimeToTimeString( &session->KickOffTime, &timeString );
|
|
|
|
if ( NT_SUCCESS(status) ) {
|
|
|
|
//
|
|
// We only send a two-minute warning if "real" forced logoff
|
|
// is enabled. If it is not enabled, the client doesn't
|
|
// actually get kicked off, so the extra messages are not
|
|
// necessary.
|
|
//
|
|
|
|
session->TwoMinuteWarningSent = TRUE;
|
|
|
|
//
|
|
// Send a different alert message based on whether the client
|
|
// has open files and/or searches.
|
|
//
|
|
|
|
if ( session->CurrentFileOpenCount != 0 ||
|
|
session->CurrentSearchOpenCount != 0 ) {
|
|
|
|
SrvUserAlertRaise(
|
|
MTXT_Immediate_Kickoff_Warning,
|
|
1,
|
|
&timeString,
|
|
NULL,
|
|
&pagedConnection->ClientMachineNameString
|
|
);
|
|
|
|
} else {
|
|
|
|
SrvUserAlertRaise(
|
|
MTXT_Kickoff_Warning,
|
|
1,
|
|
&session->Connection->Endpoint->DomainName,
|
|
NULL,
|
|
&pagedConnection->ClientMachineNameString
|
|
);
|
|
}
|
|
|
|
RtlFreeUnicodeString( &timeString );
|
|
}
|
|
|
|
} else if ( !session->FiveMinuteWarningSent &&
|
|
!session->LogonSequenceInProgress &&
|
|
session->KickOffTime.QuadPart <
|
|
FiveMinuteWarningTime->QuadPart ) {
|
|
|
|
UNICODE_STRING timeString;
|
|
|
|
status = TimeToTimeString( &session->KickOffTime, &timeString );
|
|
|
|
if ( NT_SUCCESS(status) ) {
|
|
|
|
session->FiveMinuteWarningSent = TRUE;
|
|
|
|
SrvUserAlertRaise(
|
|
MTXT_Expiration_Warning,
|
|
2,
|
|
&session->Connection->Endpoint->DomainName,
|
|
&timeString,
|
|
&pagedConnection->ClientMachineNameString
|
|
);
|
|
|
|
RtlFreeUnicodeString( &timeString );
|
|
}
|
|
}
|
|
|
|
SrvDereferenceSession( session );
|
|
ACQUIRE_LOCK( &Connection->Lock );
|
|
|
|
} // if GET_BLOCK_STATE(session) == BlockStateActive
|
|
|
|
} // for
|
|
|
|
//
|
|
// Nuke the connection if no sessions are active, and we have not heard
|
|
// from the client for one minute, drop the connection
|
|
//
|
|
if( HasSessions == FALSE ) {
|
|
|
|
RELEASE_LOCK( &Connection->Lock );
|
|
|
|
GET_SERVER_TIME( Connection->CurrentWorkQueue, (PULONG)&i );
|
|
i -= Connection->LastRequestTime;
|
|
|
|
if ( i > 0 && (ULONG)i > SrvConnectionNoSessionsTimeout ) {
|
|
#if SRVDBG29
|
|
UpdateConnectionHistory( "IDLE", Connection->Endpoint, Connection );
|
|
#endif
|
|
Connection->DisconnectReason = DisconnectIdleConnection;
|
|
SrvCloseConnection( Connection, FALSE );
|
|
}
|
|
|
|
} else if ( (sessionClosed && (pagedConnection->CurrentNumberOfSessions == 0)) ||
|
|
(HasSessions == TRUE && AllSessionsIdle == TRUE) ) {
|
|
|
|
//
|
|
// Update the statistics for the 'AllSessionsIdle' case
|
|
//
|
|
SrvStatistics.SessionsTimedOut += pagedConnection->CurrentNumberOfSessions;
|
|
|
|
RELEASE_LOCK( &Connection->Lock );
|
|
#if SRVDBG29
|
|
UpdateConnectionHistory( "IDLE", Connection->Endpoint, Connection );
|
|
#endif
|
|
Connection->DisconnectReason = DisconnectIdleConnection;
|
|
SrvCloseConnection( Connection, FALSE );
|
|
|
|
} else {
|
|
|
|
//
|
|
// If this connection has more than 20 core searches, we go in and
|
|
// try to remove dups. 20 is an arbitrary number.
|
|
//
|
|
|
|
|
|
if ( (pagedConnection->CurrentNumberOfCoreSearches > 20) &&
|
|
SrvRemoveDuplicateSearches ) {
|
|
|
|
RemoveDuplicateCoreSearches( pagedConnection );
|
|
}
|
|
|
|
RELEASE_LOCK( &Connection->Lock );
|
|
}
|
|
|
|
} // CloseIdleConnection
|
|
|
|
|
|
VOID
|
|
CreateConnections (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function attempts to service all endpoints that do not have
|
|
free connections available.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG count;
|
|
PLIST_ENTRY listEntry;
|
|
PENDPOINT endpoint;
|
|
|
|
PAGED_CODE( );
|
|
|
|
ACQUIRE_LOCK( &SrvEndpointLock );
|
|
|
|
//
|
|
// Walk the endpoint list, looking for endpoints that need
|
|
// connections. Note that we hold the endpoint lock for the
|
|
// duration of this routine. This keeps the endpoint list from
|
|
// changing.
|
|
//
|
|
// Note that we add connections based on level of need, so that
|
|
// if we are unable to create as many as we'd like, we at least
|
|
// take care of the most needy endpoints first.
|
|
//
|
|
|
|
for ( count = 0 ; count < SrvFreeConnectionMinimum; count++ ) {
|
|
|
|
listEntry = SrvEndpointList.ListHead.Flink;
|
|
|
|
while ( listEntry != &SrvEndpointList.ListHead ) {
|
|
|
|
endpoint = CONTAINING_RECORD(
|
|
listEntry,
|
|
ENDPOINT,
|
|
GlobalEndpointListEntry
|
|
);
|
|
|
|
//
|
|
// If the endpoint's free connection count is at or below
|
|
// our current level, try to create a connection now.
|
|
//
|
|
|
|
if ( (endpoint->FreeConnectionCount <= count) &&
|
|
(GET_BLOCK_STATE(endpoint) == BlockStateActive) ) {
|
|
|
|
//
|
|
// Try to create a connection. If this fails, leave.
|
|
//
|
|
|
|
if ( !NT_SUCCESS(SrvOpenConnection( endpoint )) ) {
|
|
RELEASE_LOCK( &SrvEndpointLock );
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
listEntry = listEntry->Flink;
|
|
|
|
} // walk endpoint list
|
|
|
|
} // 0 <= count < SrvFreeConnectionMinimum
|
|
|
|
RELEASE_LOCK( &SrvEndpointLock );
|
|
|
|
return;
|
|
|
|
} // CreateConnections
|
|
|
|
|
|
VOID
|
|
GeneratePeriodicEvents (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is called when the scavenger timeout occurs. It
|
|
generates events for things that have happened in the previous
|
|
period for which we did not want to immediately generate an event,
|
|
for fear of flooding the event log. An example of such an event is
|
|
being unable to allocate pool.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG capturedNonPagedFailureCount;
|
|
ULONG capturedPagedFailureCount;
|
|
ULONG capturedNonPagedLimitHitCount;
|
|
ULONG capturedPagedLimitHitCount;
|
|
|
|
ULONG nonPagedFailureCount;
|
|
ULONG pagedFailureCount;
|
|
ULONG nonPagedLimitHitCount;
|
|
ULONG pagedLimitHitCount;
|
|
|
|
PAGED_CODE( );
|
|
|
|
//
|
|
// Capture pool allocation failure statistics.
|
|
//
|
|
|
|
capturedNonPagedLimitHitCount = SrvNonPagedPoolLimitHitCount;
|
|
capturedNonPagedFailureCount = SrvStatistics.NonPagedPoolFailures;
|
|
capturedPagedLimitHitCount = SrvPagedPoolLimitHitCount;
|
|
capturedPagedFailureCount = SrvStatistics.PagedPoolFailures;
|
|
|
|
//
|
|
// Compute failure counts for the last period. The FailureCount
|
|
// fields in the statistics structure count both hitting the
|
|
// server's configuration limit and hitting the system's limit. The
|
|
// local versions of FailureCount include only system failures.
|
|
//
|
|
|
|
nonPagedLimitHitCount =
|
|
capturedNonPagedLimitHitCount - LastNonPagedPoolLimitHitCount;
|
|
nonPagedFailureCount =
|
|
capturedNonPagedFailureCount - LastNonPagedPoolFailureCount -
|
|
nonPagedLimitHitCount;
|
|
pagedLimitHitCount =
|
|
capturedPagedLimitHitCount - LastPagedPoolLimitHitCount;
|
|
pagedFailureCount =
|
|
capturedPagedFailureCount - LastPagedPoolFailureCount -
|
|
pagedLimitHitCount;
|
|
|
|
//
|
|
// Saved the current failure counts for next time.
|
|
//
|
|
|
|
LastNonPagedPoolLimitHitCount = capturedNonPagedLimitHitCount;
|
|
LastNonPagedPoolFailureCount = capturedNonPagedFailureCount;
|
|
LastPagedPoolLimitHitCount = capturedPagedLimitHitCount;
|
|
LastPagedPoolFailureCount = capturedPagedFailureCount;
|
|
|
|
//
|
|
// If we hit the nonpaged pool limit at least once in the last
|
|
// period, generate an event.
|
|
//
|
|
|
|
if ( nonPagedLimitHitCount != 0 ) {
|
|
SrvLogError(
|
|
SrvDeviceObject,
|
|
EVENT_SRV_NONPAGED_POOL_LIMIT,
|
|
STATUS_INSUFFICIENT_RESOURCES,
|
|
&nonPagedLimitHitCount,
|
|
sizeof( nonPagedLimitHitCount ),
|
|
NULL,
|
|
0
|
|
);
|
|
}
|
|
|
|
//
|
|
// If we had any nonpaged pool allocations failures in the last
|
|
// period, generate an event.
|
|
//
|
|
|
|
if ( nonPagedFailureCount != 0 ) {
|
|
SrvLogError(
|
|
SrvDeviceObject,
|
|
EVENT_SRV_NO_NONPAGED_POOL,
|
|
STATUS_INSUFFICIENT_RESOURCES,
|
|
&nonPagedFailureCount,
|
|
sizeof( nonPagedFailureCount ),
|
|
NULL,
|
|
0
|
|
);
|
|
}
|
|
|
|
//
|
|
// If we hit the paged pool limit at least once in the last period,
|
|
// generate an event.
|
|
//
|
|
|
|
if ( pagedLimitHitCount != 0 ) {
|
|
SrvLogError(
|
|
SrvDeviceObject,
|
|
EVENT_SRV_PAGED_POOL_LIMIT,
|
|
STATUS_INSUFFICIENT_RESOURCES,
|
|
&pagedLimitHitCount,
|
|
sizeof( pagedLimitHitCount ),
|
|
NULL,
|
|
0
|
|
);
|
|
}
|
|
|
|
//
|
|
// If we had any paged pool allocations failures in the last period,
|
|
// generate an event.
|
|
//
|
|
|
|
if ( pagedFailureCount != 0 ) {
|
|
SrvLogError(
|
|
SrvDeviceObject,
|
|
EVENT_SRV_NO_PAGED_POOL,
|
|
STATUS_INSUFFICIENT_RESOURCES,
|
|
&pagedFailureCount,
|
|
sizeof( pagedFailureCount ),
|
|
NULL,
|
|
0
|
|
);
|
|
}
|
|
|
|
return;
|
|
|
|
} // GeneratePeriodicEvents
|
|
|
|
|
|
VOID
|
|
ProcessConnectionDisconnects (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function processes connection disconnects.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLIST_ENTRY listEntry;
|
|
PCONNECTION connection;
|
|
KIRQL oldIrql;
|
|
|
|
//
|
|
// Run through the list of connection with pending disconnects.
|
|
// Do the work necessary to shut the disconnection connection
|
|
// down.
|
|
//
|
|
|
|
ACQUIRE_GLOBAL_SPIN_LOCK( Fsd, &oldIrql );
|
|
|
|
while ( !IsListEmpty( &SrvDisconnectQueue ) ) {
|
|
|
|
//
|
|
// This thread already owns the disconnect queue spin lock
|
|
// and there is at least one entry on the queue. Proceed.
|
|
//
|
|
|
|
listEntry = RemoveHeadList( &SrvDisconnectQueue );
|
|
|
|
connection = CONTAINING_RECORD(
|
|
listEntry,
|
|
CONNECTION,
|
|
ListEntry
|
|
);
|
|
|
|
ASSERT( connection->DisconnectPending );
|
|
connection->DisconnectPending = FALSE;
|
|
|
|
RELEASE_GLOBAL_SPIN_LOCK( Fsd, oldIrql );
|
|
|
|
IF_STRESS() {
|
|
if( connection->InProgressWorkContextCount > 0 )
|
|
{
|
|
KdPrint(("Abortive Disconnect for %z while work-in-progress (reason %d)\n", (PCSTRING)&connection->OemClientMachineNameString, connection->DisconnectReason ));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Do the disconnection processing. Dereference the connection
|
|
// an extra time to account for the reference made when it was
|
|
// put on the disconnect queue.
|
|
//
|
|
|
|
#if SRVDBG29
|
|
UpdateConnectionHistory( "PDSC", connection->Endpoint, connection );
|
|
#endif
|
|
SrvCloseConnection( connection, TRUE );
|
|
SrvDereferenceConnection( connection );
|
|
|
|
//
|
|
// We are about to go through the loop again, reacquire
|
|
// the disconnect queue spin lock first.
|
|
//
|
|
|
|
ACQUIRE_GLOBAL_SPIN_LOCK( Fsd, &oldIrql );
|
|
|
|
}
|
|
|
|
RELEASE_GLOBAL_SPIN_LOCK( Fsd, oldIrql );
|
|
return;
|
|
|
|
} // ProcessConnectionDisconnects
|
|
|
|
|
|
VOID SRVFASTCALL
|
|
SrvServiceWorkItemShortage (
|
|
IN PWORK_CONTEXT workContext
|
|
)
|
|
{
|
|
PLIST_ENTRY listEntry;
|
|
PCONNECTION connection;
|
|
KIRQL oldIrql;
|
|
BOOLEAN moreWork;
|
|
PWORK_QUEUE queue;
|
|
|
|
ASSERT( workContext );
|
|
|
|
queue = workContext->CurrentWorkQueue;
|
|
|
|
IF_DEBUG( WORKITEMS ) {
|
|
KdPrint(("SrvServiceWorkItemShortage: Processor %p\n",
|
|
(PVOID)(queue - SrvWorkQueues) ));
|
|
}
|
|
|
|
workContext->FspRestartRoutine = SrvRestartReceive;
|
|
|
|
ASSERT( queue >= SrvWorkQueues && queue < eSrvWorkQueues );
|
|
|
|
//
|
|
// If we got called, it's likely that we're running short of WorkItems.
|
|
// Allocate more if it makes sense.
|
|
//
|
|
|
|
do {
|
|
PWORK_CONTEXT NewWorkContext;
|
|
|
|
SrvAllocateNormalWorkItem( &NewWorkContext, queue );
|
|
if ( NewWorkContext != NULL ) {
|
|
|
|
IF_DEBUG( WORKITEMS ) {
|
|
KdPrint(( "SrvServiceWorkItemShortage: Created new work context "
|
|
"block\n" ));
|
|
}
|
|
|
|
SrvPrepareReceiveWorkItem( NewWorkContext, TRUE );
|
|
|
|
} else {
|
|
InterlockedIncrement( &FailedWorkItemAllocations );
|
|
break;
|
|
}
|
|
|
|
} while ( queue->FreeWorkItems < queue->MinFreeWorkItems );
|
|
|
|
if( GET_BLOCK_TYPE(workContext) == BlockTypeWorkContextSpecial ) {
|
|
//
|
|
// We've been called with a special workitem telling us to allocate
|
|
// more standby WorkContext structures. Since our passed-in workContext
|
|
// is not a "standard one", we can't use it for any further work
|
|
// on starved connections. Just release this workContext and return.
|
|
//
|
|
ACQUIRE_SPIN_LOCK( &queue->SpinLock, &oldIrql );
|
|
SET_BLOCK_TYPE( workContext, BlockTypeGarbage );
|
|
RELEASE_SPIN_LOCK( &queue->SpinLock, oldIrql );
|
|
return;
|
|
}
|
|
|
|
ACQUIRE_GLOBAL_SPIN_LOCK( Fsd, &oldIrql );
|
|
|
|
//
|
|
// Run through the list of queued connections and find one that
|
|
// we can service with this workContext. This will ignore processor
|
|
// affinity, but we're in exceptional times. The workContext will
|
|
// be freed back to the correct queue when done.
|
|
//
|
|
|
|
while( !IsListEmpty( &SrvNeedResourceQueue ) ) {
|
|
|
|
connection = CONTAINING_RECORD( SrvNeedResourceQueue.Flink, CONNECTION, ListEntry );
|
|
|
|
IF_DEBUG( WORKITEMS ) {
|
|
KdPrint(("SrvServiceWorkItemShortage: Processing connection %p.\n",
|
|
connection ));
|
|
}
|
|
|
|
ASSERT( connection->OnNeedResourceQueue );
|
|
ASSERT( connection->BlockHeader.ReferenceCount > 0 );
|
|
|
|
if( GET_BLOCK_STATE( connection ) != BlockStateActive ) {
|
|
|
|
IF_DEBUG( WORKITEMS ) {
|
|
KdPrint(("SrvServiceWorkItemShortage: Connection %p closing.\n", connection ));
|
|
}
|
|
|
|
//
|
|
// Take it off the queue
|
|
//
|
|
SrvRemoveEntryList(
|
|
&SrvNeedResourceQueue,
|
|
&connection->ListEntry
|
|
);
|
|
connection->OnNeedResourceQueue = FALSE;
|
|
|
|
RELEASE_GLOBAL_SPIN_LOCK( Fsd, oldIrql );
|
|
|
|
//
|
|
// Remove the queue reference
|
|
//
|
|
SrvDereferenceConnection( connection );
|
|
|
|
ACQUIRE_GLOBAL_SPIN_LOCK( Fsd, &oldIrql );
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Reference this connection so no one can delete this from under us.
|
|
//
|
|
ACQUIRE_DPC_SPIN_LOCK( connection->EndpointSpinLock );
|
|
SrvReferenceConnectionLocked( connection );
|
|
|
|
//
|
|
// Service the connection
|
|
//
|
|
do {
|
|
|
|
if( IsListEmpty( &connection->OplockWorkList ) && !connection->ReceivePending )
|
|
break;
|
|
|
|
IF_DEBUG( WORKITEMS ) {
|
|
KdPrint(("Work to do on connection %p\n", connection ));
|
|
}
|
|
|
|
INITIALIZE_WORK_CONTEXT( queue, workContext );
|
|
|
|
//
|
|
// Reference connection here.
|
|
//
|
|
workContext->Connection = connection;
|
|
SrvReferenceConnectionLocked( connection );
|
|
workContext->Endpoint = connection->Endpoint;
|
|
|
|
//
|
|
// Service this connection.
|
|
//
|
|
SrvFsdServiceNeedResourceQueue( &workContext, &oldIrql );
|
|
|
|
moreWork = (BOOLEAN) ( workContext != NULL &&
|
|
(!IsListEmpty(&connection->OplockWorkList) ||
|
|
connection->ReceivePending) &&
|
|
connection->OnNeedResourceQueue);
|
|
|
|
} while( moreWork );
|
|
|
|
//
|
|
// Is it now off the queue?
|
|
//
|
|
if ( !connection->OnNeedResourceQueue ) {
|
|
|
|
IF_DEBUG( WORKITEMS ) {
|
|
KdPrint(("SrvServiceWorkItemShortage: connection %p removed by another thread.\n", connection ));
|
|
}
|
|
|
|
RELEASE_DPC_SPIN_LOCK( connection->EndpointSpinLock );
|
|
RELEASE_GLOBAL_SPIN_LOCK( Fsd, oldIrql );
|
|
|
|
//
|
|
// Remove this routine's reference.
|
|
//
|
|
|
|
SrvDereferenceConnection( connection );
|
|
|
|
if( workContext == NULL ) {
|
|
IF_DEBUG( WORKITEMS ) {
|
|
KdPrint(("SrvServiceWorkItemShortage: DONE at %d\n", __LINE__ ));
|
|
}
|
|
return;
|
|
}
|
|
|
|
ACQUIRE_GLOBAL_SPIN_LOCK( Fsd, &oldIrql );
|
|
continue;
|
|
}
|
|
|
|
RELEASE_DPC_SPIN_LOCK( connection->EndpointSpinLock );
|
|
|
|
//
|
|
// The connection is still on the queue. Keep it on the queue if there is more
|
|
// work to be done for it.
|
|
//
|
|
if( !IsListEmpty(&connection->OplockWorkList) || connection->ReceivePending ) {
|
|
|
|
RELEASE_GLOBAL_SPIN_LOCK( Fsd, oldIrql );
|
|
|
|
if( workContext ) {
|
|
RETURN_FREE_WORKITEM( workContext );
|
|
}
|
|
|
|
//
|
|
// Remove this routine's reference.
|
|
//
|
|
SrvDereferenceConnection( connection );
|
|
|
|
IF_DEBUG( WORKITEMS ) {
|
|
KdPrint(("SrvServiceWorkItemShortage: More to do for %p. LATER\n", connection ));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// All the work has been done for this connection. Get it off the resource queue
|
|
//
|
|
IF_DEBUG( WORKITEMS ) {
|
|
KdPrint(("SrvServiceWorkItemShortage: Take %p off resource queue\n", connection ));
|
|
}
|
|
|
|
SrvRemoveEntryList(
|
|
&SrvNeedResourceQueue,
|
|
&connection->ListEntry
|
|
);
|
|
|
|
connection->OnNeedResourceQueue = FALSE;
|
|
|
|
RELEASE_GLOBAL_SPIN_LOCK( Fsd, oldIrql );
|
|
|
|
//
|
|
// Remove queue reference
|
|
//
|
|
SrvDereferenceConnection( connection );
|
|
|
|
//
|
|
// Remove this routine's reference.
|
|
//
|
|
|
|
SrvDereferenceConnection( connection );
|
|
|
|
IF_DEBUG( WORKITEMS ) {
|
|
KdPrint(("SrvServiceWorkItemShortage: Connection %p removed from queue.\n", connection ));
|
|
}
|
|
|
|
if( workContext == NULL ) {
|
|
IF_DEBUG( WORKITEMS ) {
|
|
KdPrint(("SrvServiceWorkItemShortage: DONE at %d\n", __LINE__ ));
|
|
}
|
|
return;
|
|
}
|
|
|
|
ACQUIRE_GLOBAL_SPIN_LOCK( Fsd, &oldIrql );
|
|
}
|
|
|
|
RELEASE_GLOBAL_SPIN_LOCK( Fsd, oldIrql );
|
|
|
|
//
|
|
// See if we need to free the workContext
|
|
//
|
|
|
|
if ( workContext != NULL ) {
|
|
|
|
IF_DEBUG( WORKITEMS ) {
|
|
KdPrint(("SrvServiceWorkItemShortage: Freeing WorkContext block %p\n",
|
|
workContext ));
|
|
}
|
|
workContext->BlockHeader.ReferenceCount = 0;
|
|
RETURN_FREE_WORKITEM( workContext );
|
|
}
|
|
|
|
IF_DEBUG(WORKITEMS) KdPrint(( "SrvServiceWorkItemShortage DONE at %d\n", __LINE__ ));
|
|
|
|
} // SrvServiceWorkItemShortage
|
|
|
|
VOID SRVFASTCALL
|
|
SrvServiceDoSTearDown (
|
|
IN PWORK_CONTEXT WorkContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called when we perceive a DoS attack against us. It results
|
|
in us tearing down connections randomly who have WorkItems trapped in the
|
|
transports to help prevent the DoS.
|
|
|
|
Arguments:
|
|
|
|
WorkContext - The special work item used to trigger this routine
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
USHORT index;
|
|
PLIST_ENTRY listEntry;
|
|
PLIST_ENTRY connectionListEntry;
|
|
PENDPOINT endpoint;
|
|
PCONNECTION connection;
|
|
KIRQL oldIrql;
|
|
BOOLEAN printed = FALSE;
|
|
LONG TearDownAmount = SRV_DOS_GET_TEARDOWN();
|
|
|
|
ASSERT( GET_BLOCK_TYPE(WorkContext) == BlockTypeWorkContextSpecial );
|
|
ASSERT( KeGetCurrentIrql() < DISPATCH_LEVEL );
|
|
|
|
SRV_DOS_INCREASE_TEARDOWN();
|
|
SrvDoSRundownDetector = TRUE;
|
|
|
|
// Tear down some connections. Look for ones with operations pending in the transport
|
|
|
|
ACQUIRE_LOCK( &SrvEndpointLock );
|
|
|
|
listEntry = SrvEndpointList.ListHead.Flink;
|
|
|
|
while ( (TearDownAmount > 0) && (listEntry != &SrvEndpointList.ListHead) ) {
|
|
|
|
endpoint = CONTAINING_RECORD(
|
|
listEntry,
|
|
ENDPOINT,
|
|
GlobalEndpointListEntry
|
|
);
|
|
|
|
//
|
|
// If this endpoint is closing, skip to the next one.
|
|
// Otherwise, reference the endpoint so that it can't go away.
|
|
//
|
|
|
|
if ( GET_BLOCK_STATE(endpoint) != BlockStateActive ) {
|
|
listEntry = listEntry->Flink;
|
|
continue;
|
|
}
|
|
|
|
SrvReferenceEndpoint( endpoint );
|
|
|
|
index = (USHORT)-1;
|
|
|
|
while ( TearDownAmount > 0 ) {
|
|
|
|
PLIST_ENTRY wlistEntry, wlistHead;
|
|
KIRQL oldIrql;
|
|
LARGE_INTEGER now;
|
|
|
|
//
|
|
// Get the next active connection in the table. If no more
|
|
// are available, WalkConnectionTable returns NULL.
|
|
// Otherwise, it returns a referenced pointer to a
|
|
// connection.
|
|
//
|
|
|
|
connection = WalkConnectionTable( endpoint, &index );
|
|
if ( connection == NULL ) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// To determine if we should tear this connection down, we require that there be work waiting on
|
|
// the transport, since that is the way anonymous users can attack us. If there is, we use a
|
|
// random method based on the timestamp of the last time this was ran. We cycle through the connections
|
|
// and use the index to determine if a teardown is issued (for a psuedo-random result)
|
|
//
|
|
if( (GET_BLOCK_STATE(connection) == BlockStateActive) && (connection->OperationsPendingOnTransport > 0) )
|
|
{
|
|
RELEASE_LOCK( &SrvEndpointLock );
|
|
|
|
KdPrint(( "Disconnected suspected DoS attacker by WorkItem shortage (%z)\n", (PCSTRING)&connection->OemClientMachineNameString ));
|
|
TearDownAmount -= connection->InProgressWorkContextCount;
|
|
SrvCloseConnection( connection, FALSE );
|
|
|
|
ACQUIRE_LOCK( &SrvEndpointLock );
|
|
}
|
|
|
|
SrvDereferenceConnection( connection );
|
|
|
|
} // walk connection list
|
|
|
|
index = (USHORT)-1;
|
|
|
|
while ( TearDownAmount > 0 ) {
|
|
|
|
PLIST_ENTRY wlistEntry, wlistHead;
|
|
KIRQL oldIrql;
|
|
LARGE_INTEGER now;
|
|
|
|
//
|
|
// Get the next active connection in the table. If no more
|
|
// are available, WalkConnectionTable returns NULL.
|
|
// Otherwise, it returns a referenced pointer to a
|
|
// connection.
|
|
//
|
|
|
|
connection = WalkConnectionTable( endpoint, &index );
|
|
if ( connection == NULL ) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// To determine if we should tear this connection down, we require that there be work waiting on
|
|
// the transport, since that is the way anonymous users can attack us. If there is, we use a
|
|
// random method based on the timestamp of the last time this was ran. We cycle through the connections
|
|
// and use the index to determine if a teardown is issued (for a psuedo-random result)
|
|
//
|
|
if( (GET_BLOCK_STATE(connection) == BlockStateActive) && (connection->InProgressWorkContextCount > 0) )
|
|
{
|
|
RELEASE_LOCK( &SrvEndpointLock );
|
|
|
|
KdPrint(( "Disconnected suspected DoS attack triggered by WorkItem shortage\n" ));
|
|
TearDownAmount -= connection->InProgressWorkContextCount;
|
|
SrvCloseConnection( connection, FALSE );
|
|
|
|
ACQUIRE_LOCK( &SrvEndpointLock );
|
|
}
|
|
|
|
SrvDereferenceConnection( connection );
|
|
|
|
} // walk connection list
|
|
|
|
//
|
|
// Capture a pointer to the next endpoint in the list (that one
|
|
// can't go away because we hold the endpoint list), then
|
|
// dereference the current endpoint.
|
|
//
|
|
|
|
listEntry = listEntry->Flink;
|
|
SrvDereferenceEndpoint( endpoint );
|
|
|
|
} // walk endpoint list
|
|
|
|
RELEASE_LOCK( &SrvEndpointLock );
|
|
|
|
|
|
// This is the special work item for tearing down connections to free WORK_ITEMS. We're done, so release it
|
|
SET_BLOCK_TYPE( WorkContext, BlockTypeGarbage );
|
|
SRV_DOS_COMPLETE_TEARDOWN();
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
TimeoutSessions (
|
|
IN PLARGE_INTEGER CurrentTime
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine walks the ordered list of sessions and closes those
|
|
that have been idle too long, sends warning messages to those
|
|
that are about to be forced closed due to logon hours expiring,
|
|
and closes those whose logon hours have expired.
|
|
|
|
Arguments:
|
|
|
|
CurrentTime - the current system time.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
USHORT index;
|
|
LARGE_INTEGER oldestTime;
|
|
LARGE_INTEGER pastExpirationTime;
|
|
LARGE_INTEGER twoMinuteWarningTime;
|
|
LARGE_INTEGER fiveMinuteWarningTime;
|
|
LARGE_INTEGER time;
|
|
LARGE_INTEGER searchCutoffTime;
|
|
PLIST_ENTRY listEntry;
|
|
PENDPOINT endpoint;
|
|
PCONNECTION connection;
|
|
|
|
PAGED_CODE( );
|
|
|
|
ACQUIRE_LOCK( &SrvConfigurationLock );
|
|
|
|
//
|
|
// If autodisconnect is turned off (the timeout == 0) set the oldest
|
|
// last use time to zero so that we and don't attempt to
|
|
// autodisconnect sessions.
|
|
//
|
|
|
|
if ( SrvAutodisconnectTimeout.QuadPart == 0 ) {
|
|
|
|
oldestTime.QuadPart = 0;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Determine the oldest last use time a session can have and not
|
|
// be closed.
|
|
//
|
|
|
|
oldestTime.QuadPart = CurrentTime->QuadPart -
|
|
SrvAutodisconnectTimeout.QuadPart;
|
|
}
|
|
|
|
searchCutoffTime.QuadPart = (*CurrentTime).QuadPart - SrvSearchMaxTimeout.QuadPart;
|
|
|
|
RELEASE_LOCK( &SrvConfigurationLock );
|
|
|
|
//
|
|
// Set up the warning times. If a client's kick-off time is sooner
|
|
// than one of these times, an appropriate warning message is sent
|
|
// to the client.
|
|
//
|
|
|
|
time.QuadPart = 10*1000*1000*60*2; // two minutes
|
|
twoMinuteWarningTime.QuadPart = CurrentTime->QuadPart + time.QuadPart;
|
|
|
|
time.QuadPart = (ULONG)10*1000*1000*60*5; // five minutes
|
|
fiveMinuteWarningTime.QuadPart = CurrentTime->QuadPart + time.QuadPart;
|
|
pastExpirationTime.QuadPart = CurrentTime->QuadPart - time.QuadPart;
|
|
|
|
//
|
|
// Walk each connection and determine if we should close it.
|
|
//
|
|
|
|
ACQUIRE_LOCK( &SrvEndpointLock );
|
|
|
|
listEntry = SrvEndpointList.ListHead.Flink;
|
|
|
|
while ( listEntry != &SrvEndpointList.ListHead ) {
|
|
|
|
endpoint = CONTAINING_RECORD(
|
|
listEntry,
|
|
ENDPOINT,
|
|
GlobalEndpointListEntry
|
|
);
|
|
|
|
//
|
|
// If this endpoint is closing, skip to the next one.
|
|
// Otherwise, reference the endpoint so that it can't go away.
|
|
//
|
|
|
|
if ( GET_BLOCK_STATE(endpoint) != BlockStateActive ) {
|
|
listEntry = listEntry->Flink;
|
|
continue;
|
|
}
|
|
|
|
SrvReferenceEndpoint( endpoint );
|
|
|
|
//
|
|
// Walk the endpoint's connection table.
|
|
//
|
|
|
|
index = (USHORT)-1;
|
|
|
|
while ( TRUE ) {
|
|
|
|
//
|
|
// Get the next active connection in the table. If no more
|
|
// are available, WalkConnectionTable returns NULL.
|
|
// Otherwise, it returns a referenced pointer to a
|
|
// connection.
|
|
//
|
|
|
|
connection = WalkConnectionTable( endpoint, &index );
|
|
if ( connection == NULL ) {
|
|
break;
|
|
}
|
|
|
|
RELEASE_LOCK( &SrvEndpointLock );
|
|
|
|
CloseIdleConnection(
|
|
connection,
|
|
CurrentTime,
|
|
&oldestTime,
|
|
&pastExpirationTime,
|
|
&twoMinuteWarningTime,
|
|
&fiveMinuteWarningTime
|
|
);
|
|
|
|
//
|
|
// Time out old core search blocks.
|
|
//
|
|
|
|
if ( GET_BLOCK_STATE(connection) == BlockStateActive ) {
|
|
(VOID)SrvTimeoutSearches(
|
|
&searchCutoffTime,
|
|
connection,
|
|
FALSE
|
|
);
|
|
}
|
|
|
|
ACQUIRE_LOCK( &SrvEndpointLock );
|
|
|
|
SrvDereferenceConnection( connection );
|
|
|
|
} // walk connection table
|
|
|
|
//
|
|
// Capture a pointer to the next endpoint in the list (that one
|
|
// can't go away because we hold the endpoint list), then
|
|
// dereference the current endpoint.
|
|
//
|
|
|
|
listEntry = listEntry->Flink;
|
|
SrvDereferenceEndpoint( endpoint );
|
|
|
|
} // walk endpoint list
|
|
|
|
RELEASE_LOCK( &SrvEndpointLock );
|
|
|
|
} // TimeoutSessions
|
|
|
|
|
|
VOID
|
|
TimeoutWaitingOpens (
|
|
IN PLARGE_INTEGER CurrentTime
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function times out opens that are waiting for another client
|
|
or local process to release its oplock. This opener's wait for
|
|
oplock break IRP is cancelled, causing the opener to return the
|
|
failure to the client.
|
|
|
|
Arguments:
|
|
|
|
CurrentTime - pointer to the current system time.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLIST_ENTRY listEntry;
|
|
PWAIT_FOR_OPLOCK_BREAK waitForOplockBreak;
|
|
|
|
PAGED_CODE( );
|
|
|
|
//
|
|
// Entries in wait for oplock break list are chronological, i.e. the
|
|
// oldest entries are closest to the head of the list.
|
|
//
|
|
|
|
ACQUIRE_LOCK( &SrvOplockBreakListLock );
|
|
|
|
while ( !IsListEmpty( &SrvWaitForOplockBreakList ) ) {
|
|
|
|
listEntry = SrvWaitForOplockBreakList.Flink;
|
|
waitForOplockBreak = CONTAINING_RECORD( listEntry,
|
|
WAIT_FOR_OPLOCK_BREAK,
|
|
ListEntry
|
|
);
|
|
|
|
if ( waitForOplockBreak->TimeoutTime.QuadPart > CurrentTime->QuadPart ) {
|
|
|
|
//
|
|
// No more wait for oplock breaks to timeout
|
|
//
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
IF_DEBUG( OPLOCK ) {
|
|
KdPrint(( "srv!TimeoutWaitingOpens: Failing stuck open, "
|
|
"cancelling wait IRP %p\n", waitForOplockBreak->Irp ));
|
|
KdPrint(( "Timeout time = %08lx.%08lx, current time = %08lx.%08lx\n",
|
|
waitForOplockBreak->TimeoutTime.HighPart,
|
|
waitForOplockBreak->TimeoutTime.LowPart,
|
|
CurrentTime->HighPart,
|
|
CurrentTime->LowPart ));
|
|
|
|
}
|
|
|
|
//
|
|
// Timeout this wait for oplock break
|
|
//
|
|
|
|
RemoveHeadList( &SrvWaitForOplockBreakList );
|
|
|
|
IoCancelIrp( waitForOplockBreak->Irp );
|
|
waitForOplockBreak->WaitState = WaitStateOplockWaitTimedOut;
|
|
|
|
SrvDereferenceWaitForOplockBreak( waitForOplockBreak );
|
|
}
|
|
|
|
RELEASE_LOCK( &SrvOplockBreakListLock );
|
|
|
|
} // TimeoutWaitingOpens
|
|
|
|
|
|
VOID
|
|
TimeoutStuckOplockBreaks (
|
|
IN PLARGE_INTEGER CurrentTime
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function times out blocked oplock breaks.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLIST_ENTRY listEntry;
|
|
PRFCB rfcb;
|
|
PPAGED_RFCB pagedRfcb;
|
|
|
|
PAGED_CODE( );
|
|
|
|
//
|
|
// Entries in wait for oplock break list are chronological, i.e. the
|
|
// oldest entries are closest to the head of the list.
|
|
//
|
|
|
|
ACQUIRE_LOCK( &SrvOplockBreakListLock );
|
|
|
|
while ( !IsListEmpty( &SrvOplockBreaksInProgressList ) ) {
|
|
|
|
listEntry = SrvOplockBreaksInProgressList.Flink;
|
|
rfcb = CONTAINING_RECORD( listEntry, RFCB, ListEntry );
|
|
|
|
pagedRfcb = rfcb->PagedRfcb;
|
|
if ( pagedRfcb->OplockBreakTimeoutTime.QuadPart > CurrentTime->QuadPart ) {
|
|
|
|
//
|
|
// No more wait for oplock break requests to timeout
|
|
//
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
IF_DEBUG( ERRORS ) {
|
|
KdPrint(( "srv!TimeoutStuckOplockBreaks: Failing stuck oplock, "
|
|
"break request. Closing %wZ\n",
|
|
&rfcb->Mfcb->FileName ));
|
|
}
|
|
|
|
IF_DEBUG( STUCK_OPLOCK ) {
|
|
KdPrint(( "srv!TimeoutStuckOplockBreaks: Failing stuck oplock, "
|
|
"break request. Closing %wZ\n",
|
|
&rfcb->Mfcb->FileName ));
|
|
|
|
KdPrint(( "Rfcb %p\n", rfcb ));
|
|
|
|
KdPrint(( "Timeout time = %08lx.%08lx, current time = %08lx.%08lx\n",
|
|
pagedRfcb->OplockBreakTimeoutTime.HighPart,
|
|
pagedRfcb->OplockBreakTimeoutTime.LowPart,
|
|
CurrentTime->HighPart,
|
|
CurrentTime->LowPart ));
|
|
|
|
DbgBreakPoint();
|
|
}
|
|
|
|
//
|
|
// We have been waiting too long for an oplock break response.
|
|
// Unilaterally acknowledge the oplock break, on the assumption
|
|
// that the client is dead.
|
|
//
|
|
|
|
rfcb->NewOplockLevel = NO_OPLOCK_BREAK_IN_PROGRESS;
|
|
rfcb->OnOplockBreaksInProgressList = FALSE;
|
|
|
|
//
|
|
// Remove the RFCB from the Oplock breaks in progress list, and
|
|
// release the RFCB reference.
|
|
//
|
|
|
|
SrvRemoveEntryList( &SrvOplockBreaksInProgressList, &rfcb->ListEntry );
|
|
#if DBG
|
|
rfcb->ListEntry.Flink = rfcb->ListEntry.Blink = NULL;
|
|
#endif
|
|
RELEASE_LOCK( &SrvOplockBreakListLock );
|
|
|
|
SrvAcknowledgeOplockBreak( rfcb, 0 );
|
|
|
|
ExInterlockedAddUlong(
|
|
&rfcb->Connection->OplockBreaksInProgress,
|
|
(ULONG)-1,
|
|
rfcb->Connection->EndpointSpinLock
|
|
);
|
|
|
|
SrvDereferenceRfcb( rfcb );
|
|
|
|
ACQUIRE_LOCK( &SrvOplockBreakListLock );
|
|
}
|
|
|
|
RELEASE_LOCK( &SrvOplockBreakListLock );
|
|
|
|
} // TimeoutStuckOplockBreaks
|
|
|
|
|
|
VOID
|
|
UpdateConnectionQos (
|
|
IN PLARGE_INTEGER CurrentTime
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function updates the qos information for each connection.
|
|
|
|
Arguments:
|
|
|
|
CurrentTime - the current system time.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
USHORT index;
|
|
PENDPOINT endpoint;
|
|
PLIST_ENTRY listEntry;
|
|
PCONNECTION connection;
|
|
|
|
PAGED_CODE( );
|
|
|
|
//
|
|
// Go through each connection of each endpoint and update the qos
|
|
// information.
|
|
//
|
|
|
|
ACQUIRE_LOCK( &SrvEndpointLock );
|
|
|
|
listEntry = SrvEndpointList.ListHead.Flink;
|
|
|
|
while ( listEntry != &SrvEndpointList.ListHead ) {
|
|
|
|
endpoint = CONTAINING_RECORD(
|
|
listEntry,
|
|
ENDPOINT,
|
|
GlobalEndpointListEntry
|
|
);
|
|
|
|
//
|
|
// If this endpoint is closing, or is a connectionless (IPX)
|
|
// endpoint, skip to the next one. Otherwise, reference the
|
|
// endpoint so that it can't go away.
|
|
//
|
|
|
|
if ( (GET_BLOCK_STATE(endpoint) != BlockStateActive) ||
|
|
endpoint->IsConnectionless ) {
|
|
listEntry = listEntry->Flink;
|
|
continue;
|
|
}
|
|
|
|
SrvReferenceEndpoint( endpoint );
|
|
|
|
//
|
|
// Walk the endpoint's connection table.
|
|
//
|
|
|
|
index = (USHORT)-1;
|
|
|
|
while ( TRUE ) {
|
|
|
|
//
|
|
// Get the next active connection in the table. If no more
|
|
// are available, WalkConnectionTable returns NULL.
|
|
// Otherwise, it returns a referenced pointer to a
|
|
// connection.
|
|
//
|
|
|
|
connection = WalkConnectionTable( endpoint, &index );
|
|
if ( connection == NULL ) {
|
|
break;
|
|
}
|
|
|
|
RELEASE_LOCK( &SrvEndpointLock );
|
|
|
|
SrvUpdateVcQualityOfService( connection, CurrentTime );
|
|
|
|
ACQUIRE_LOCK( &SrvEndpointLock );
|
|
|
|
SrvDereferenceConnection( connection );
|
|
|
|
}
|
|
|
|
//
|
|
// Capture a pointer to the next endpoint in the list (that one
|
|
// can't go away because we hold the endpoint list), then
|
|
// dereference the current endpoint.
|
|
//
|
|
|
|
listEntry = listEntry->Flink;
|
|
SrvDereferenceEndpoint( endpoint );
|
|
|
|
}
|
|
|
|
RELEASE_LOCK( &SrvEndpointLock );
|
|
|
|
return;
|
|
|
|
} // UpdateConnectionQos
|
|
|
|
VOID
|
|
LazyFreeQueueDataStructures (
|
|
PWORK_QUEUE queue
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function frees work context blocks and other per-queue data
|
|
structures that are held on linked lists when otherwise free. It
|
|
only frees a few at a time, to allow a slow ramp-down.
|
|
|
|
Arguments:
|
|
|
|
CurrentTime - the current system time.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PSINGLE_LIST_ENTRY listEntry;
|
|
KIRQL oldIrql;
|
|
ULONG i;
|
|
PWORK_CONTEXT workContext;
|
|
|
|
//
|
|
// Clean out the queue->FreeContext
|
|
//
|
|
workContext = NULL;
|
|
workContext = (PWORK_CONTEXT)InterlockedExchangePointer( &queue->FreeContext, workContext );
|
|
|
|
if( workContext != NULL ) {
|
|
ExInterlockedPushEntrySList( workContext->FreeList,
|
|
&workContext->SingleListEntry,
|
|
&queue->SpinLock
|
|
);
|
|
InterlockedIncrement( &queue->FreeWorkItems );
|
|
}
|
|
|
|
//
|
|
// Free 1 normal work item, if appropriate
|
|
//
|
|
if( queue->FreeWorkItems > queue->MinFreeWorkItems ) {
|
|
|
|
|
|
listEntry = ExInterlockedPopEntrySList( &queue->NormalWorkItemList,
|
|
&queue->SpinLock );
|
|
|
|
if( listEntry != NULL ) {
|
|
PWORK_CONTEXT workContext;
|
|
|
|
InterlockedDecrement( &queue->FreeWorkItems );
|
|
|
|
workContext = CONTAINING_RECORD( listEntry, WORK_CONTEXT, SingleListEntry );
|
|
|
|
SrvFreeNormalWorkItem( workContext );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Free 1 raw mode work item, if appropriate
|
|
//
|
|
|
|
if( (ULONG)queue->AllocatedRawModeWorkItems > SrvMaxRawModeWorkItemCount / SrvNumberOfProcessors ) {
|
|
|
|
PWORK_CONTEXT workContext;
|
|
|
|
listEntry = ExInterlockedPopEntrySList( &queue->RawModeWorkItemList, &queue->SpinLock );
|
|
|
|
if( listEntry != NULL ) {
|
|
InterlockedDecrement( &queue->FreeRawModeWorkItems );
|
|
ASSERT( queue->FreeRawModeWorkItems >= 0 );
|
|
workContext = CONTAINING_RECORD( listEntry, WORK_CONTEXT, SingleListEntry );
|
|
SrvFreeRawModeWorkItem( workContext );
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Free 1 rfcb off the list
|
|
//
|
|
{
|
|
PRFCB rfcb = NULL;
|
|
|
|
rfcb = (PRFCB)InterlockedExchangePointer( &queue->CachedFreeRfcb, rfcb );
|
|
|
|
if( rfcb != NULL ) {
|
|
ExInterlockedPushEntrySList( &queue->RfcbFreeList,
|
|
&rfcb->SingleListEntry,
|
|
&queue->SpinLock
|
|
);
|
|
InterlockedIncrement( &queue->FreeRfcbs );
|
|
}
|
|
|
|
listEntry = ExInterlockedPopEntrySList( &queue->RfcbFreeList,
|
|
&queue->SpinLock );
|
|
|
|
if( listEntry ) {
|
|
InterlockedDecrement( &queue->FreeRfcbs );
|
|
rfcb = CONTAINING_RECORD( listEntry, RFCB, SingleListEntry );
|
|
INCREMENT_DEBUG_STAT( SrvDbgStatistics.RfcbInfo.Frees );
|
|
FREE_HEAP( rfcb->PagedRfcb );
|
|
DEALLOCATE_NONPAGED_POOL( rfcb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Free 1 Mfcb off the list
|
|
//
|
|
{
|
|
|
|
PNONPAGED_MFCB nonpagedMfcb = NULL;
|
|
|
|
nonpagedMfcb = (PNONPAGED_MFCB)InterlockedExchangePointer(&queue->CachedFreeMfcb,
|
|
nonpagedMfcb);
|
|
|
|
if( nonpagedMfcb != NULL ) {
|
|
ExInterlockedPushEntrySList( &queue->MfcbFreeList,
|
|
&nonpagedMfcb->SingleListEntry,
|
|
&queue->SpinLock
|
|
);
|
|
InterlockedIncrement( &queue->FreeMfcbs );
|
|
}
|
|
|
|
listEntry = ExInterlockedPopEntrySList( &queue->MfcbFreeList,
|
|
&queue->SpinLock );
|
|
if( listEntry ) {
|
|
InterlockedDecrement( &queue->FreeMfcbs );
|
|
nonpagedMfcb = CONTAINING_RECORD( listEntry, NONPAGED_MFCB, SingleListEntry );
|
|
DEALLOCATE_NONPAGED_POOL( nonpagedMfcb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Free memory in the per-queue pool free lists
|
|
//
|
|
{
|
|
//
|
|
// Free the paged pool chunks
|
|
//
|
|
SrvClearLookAsideList( &queue->PagedPoolLookAsideList, SrvFreePagedPool );
|
|
|
|
//
|
|
// Free the non paged pool chunks
|
|
//
|
|
SrvClearLookAsideList( &queue->NonPagedPoolLookAsideList, SrvFreeNonPagedPool );
|
|
}
|
|
|
|
} // LazyFreeQueueDataStructures
|
|
|
|
VOID
|
|
SrvUserAlertRaise (
|
|
IN ULONG Message,
|
|
IN ULONG NumberOfStrings,
|
|
IN PUNICODE_STRING String1 OPTIONAL,
|
|
IN PUNICODE_STRING String2 OPTIONAL,
|
|
IN PUNICODE_STRING ComputerName
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
IO_STATUS_BLOCK ioStatusBlock;
|
|
PSTD_ALERT alert;
|
|
PUSER_OTHER_INFO user;
|
|
LARGE_INTEGER currentTime;
|
|
ULONG mailslotLength;
|
|
ULONG string1Length = 0;
|
|
ULONG string2Length = 0;
|
|
PCHAR variableInfo;
|
|
UNICODE_STRING computerName;
|
|
HANDLE alerterHandle;
|
|
|
|
PAGED_CODE( );
|
|
|
|
ASSERT( (NumberOfStrings == 2 && String1 != NULL && String2 != NULL) ||
|
|
(NumberOfStrings == 1 && String1 != NULL) ||
|
|
(NumberOfStrings == 0) );
|
|
|
|
//
|
|
// Open a handle to the alerter service's mailslot.
|
|
//
|
|
|
|
status = OpenAlerter( &alerterHandle );
|
|
if ( !NT_SUCCESS(status) ) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Get rid of the leading backslashes from the computer name.
|
|
//
|
|
|
|
computerName.Buffer = ComputerName->Buffer + 2;
|
|
computerName.Length = (USHORT)(ComputerName->Length - 2*sizeof(WCHAR));
|
|
computerName.MaximumLength =
|
|
(USHORT)(ComputerName->MaximumLength - 2*sizeof(WCHAR));
|
|
|
|
//
|
|
// Allocate a buffer to hold the mailslot we're going to send to the
|
|
// alerter.
|
|
//
|
|
|
|
if ( String1 != NULL ) {
|
|
string1Length = String1->Length + sizeof(WCHAR);
|
|
}
|
|
|
|
if ( String2 != NULL ) {
|
|
string2Length = String2->Length + sizeof(WCHAR);
|
|
}
|
|
|
|
mailslotLength = sizeof(STD_ALERT) + sizeof(USER_OTHER_INFO) +
|
|
string1Length + string2Length +
|
|
sizeof(WCHAR) +
|
|
ComputerName->Length + sizeof(WCHAR);
|
|
|
|
alert = ALLOCATE_HEAP_COLD( mailslotLength, BlockTypeDataBuffer );
|
|
if ( alert == NULL ) {
|
|
SRVDBG_RELEASE_HANDLE( alerterHandle, "ALR", 20, 0 );
|
|
SrvNtClose( alerterHandle, FALSE );
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Set up the standard alert structure.
|
|
//
|
|
|
|
KeQuerySystemTime( ¤tTime );
|
|
RtlTimeToSecondsSince1970( ¤tTime, &alert->alrt_timestamp );
|
|
|
|
STRCPY( alert->alrt_eventname, StrUserAlertEventName );
|
|
STRCPY( alert->alrt_servicename, SrvAlertServiceName );
|
|
|
|
//
|
|
// Set up the user info in the alert.
|
|
//
|
|
|
|
user = (PUSER_OTHER_INFO)ALERT_OTHER_INFO(alert);
|
|
|
|
user->alrtus_errcode = Message;
|
|
|
|
user->alrtus_numstrings = NumberOfStrings;
|
|
|
|
//
|
|
// Set up the variable portion of the message.
|
|
//
|
|
|
|
variableInfo = ALERT_VAR_DATA(user);
|
|
|
|
if ( String1 != NULL ) {
|
|
RtlCopyMemory(
|
|
variableInfo,
|
|
String1->Buffer,
|
|
String1->Length
|
|
);
|
|
*(PWCH)(variableInfo + String1->Length) = UNICODE_NULL;
|
|
variableInfo += String1->Length + sizeof(WCHAR);
|
|
}
|
|
|
|
if ( String2 != NULL ) {
|
|
RtlCopyMemory(
|
|
variableInfo,
|
|
String2->Buffer,
|
|
String2->Length
|
|
);
|
|
*(PWCH)(variableInfo + String2->Length) = UNICODE_NULL;
|
|
variableInfo += String2->Length + sizeof(WCHAR);
|
|
}
|
|
|
|
*(PWCH)variableInfo = UNICODE_NULL;
|
|
variableInfo += sizeof(WCHAR);
|
|
|
|
RtlCopyMemory(
|
|
variableInfo,
|
|
ComputerName->Buffer,
|
|
ComputerName->Length
|
|
);
|
|
*(PWCH)(variableInfo + ComputerName->Length) = UNICODE_NULL;
|
|
variableInfo += ComputerName->Length + sizeof(WCHAR);
|
|
|
|
status = NtWriteFile(
|
|
alerterHandle,
|
|
NULL, // Event
|
|
NULL, // ApcRoutine
|
|
NULL, // ApcContext
|
|
&ioStatusBlock,
|
|
alert,
|
|
mailslotLength,
|
|
NULL, // ByteOffset
|
|
NULL // Key
|
|
);
|
|
|
|
if ( !NT_SUCCESS(status) ) {
|
|
|
|
INTERNAL_ERROR(
|
|
ERROR_LEVEL_UNEXPECTED,
|
|
"SrvUserAlertRaise: NtWriteFile failed: %X\n",
|
|
status,
|
|
NULL
|
|
);
|
|
|
|
SrvLogServiceFailure( SRV_SVC_NT_WRITE_FILE, status );
|
|
|
|
}
|
|
|
|
FREE_HEAP( alert );
|
|
SRVDBG_RELEASE_HANDLE( alerterHandle, "ALR", 21, 0 );
|
|
SrvNtClose( alerterHandle, FALSE );
|
|
|
|
return;
|
|
|
|
} // SrvUserAlertRaise
|
|
|
|
|
|
VOID
|
|
SrvAdminAlertRaise (
|
|
IN ULONG Message,
|
|
IN ULONG NumberOfStrings,
|
|
IN PUNICODE_STRING String1 OPTIONAL,
|
|
IN PUNICODE_STRING String2 OPTIONAL,
|
|
IN PUNICODE_STRING String3 OPTIONAL
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
IO_STATUS_BLOCK ioStatusBlock;
|
|
PSTD_ALERT alert;
|
|
PADMIN_OTHER_INFO admin;
|
|
LARGE_INTEGER currentTime;
|
|
ULONG mailslotLength;
|
|
ULONG string1Length = 0;
|
|
ULONG string2Length = 0;
|
|
ULONG string3Length = 0;
|
|
PCHAR variableInfo;
|
|
HANDLE alerterHandle;
|
|
|
|
PAGED_CODE( );
|
|
|
|
ASSERT( (NumberOfStrings == 3 && String1 != NULL && String2 != NULL && String3 != NULL ) ||
|
|
(NumberOfStrings == 2 && String1 != NULL && String2 != NULL && String3 == NULL ) ||
|
|
(NumberOfStrings == 1 && String1 != NULL && String2 == NULL && String3 == NULL ) ||
|
|
(NumberOfStrings == 0 && String1 == NULL && String2 == NULL && String3 == NULL ) );
|
|
|
|
//
|
|
// Open a handle to the alerter service's mailslot.
|
|
//
|
|
|
|
status = OpenAlerter( &alerterHandle );
|
|
if ( !NT_SUCCESS(status) ) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Allocate a buffer to hold the mailslot we're going to send to the
|
|
// alerter.
|
|
//
|
|
|
|
if ( String1 != NULL ) {
|
|
string1Length = String1->Length + sizeof(WCHAR);
|
|
}
|
|
|
|
if ( String2 != NULL ) {
|
|
string2Length = String2->Length + sizeof(WCHAR);
|
|
}
|
|
|
|
if ( String3 != NULL ) {
|
|
string3Length = String3->Length + sizeof(WCHAR);
|
|
}
|
|
|
|
mailslotLength = sizeof(STD_ALERT) + sizeof(ADMIN_OTHER_INFO) +
|
|
string1Length + string2Length + string3Length;
|
|
|
|
alert = ALLOCATE_HEAP_COLD( mailslotLength, BlockTypeDataBuffer );
|
|
if ( alert == NULL ) {
|
|
SRVDBG_RELEASE_HANDLE( alerterHandle, "ALR", 22, 0 );
|
|
SrvNtClose( alerterHandle, FALSE );
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Set up the standard alert structure.
|
|
//
|
|
|
|
KeQuerySystemTime( ¤tTime );
|
|
RtlTimeToSecondsSince1970( ¤tTime, &alert->alrt_timestamp );
|
|
|
|
STRCPY( alert->alrt_eventname, StrAdminAlertEventName );
|
|
STRCPY( alert->alrt_servicename, SrvAlertServiceName );
|
|
|
|
//
|
|
// Set up the user info in the alert.
|
|
//
|
|
|
|
admin = (PADMIN_OTHER_INFO)ALERT_OTHER_INFO(alert);
|
|
|
|
admin->alrtad_errcode = Message;
|
|
admin->alrtad_numstrings = NumberOfStrings;
|
|
|
|
//
|
|
// Set up the variable portion of the message.
|
|
//
|
|
|
|
variableInfo = ALERT_VAR_DATA(admin);
|
|
|
|
if ( String1 != NULL ) {
|
|
RtlCopyMemory(
|
|
variableInfo,
|
|
String1->Buffer,
|
|
String1->Length
|
|
);
|
|
*(PWCH)(variableInfo + String1->Length) = UNICODE_NULL;
|
|
variableInfo += string1Length;
|
|
}
|
|
|
|
if ( String2 != NULL ) {
|
|
RtlCopyMemory(
|
|
variableInfo,
|
|
String2->Buffer,
|
|
String2->Length
|
|
);
|
|
*(PWCH)(variableInfo + String2->Length) = UNICODE_NULL;
|
|
variableInfo += string2Length;
|
|
}
|
|
|
|
if ( String3 != NULL ){
|
|
RtlCopyMemory(
|
|
variableInfo,
|
|
String3->Buffer,
|
|
String3->Length
|
|
);
|
|
*(PWCH)(variableInfo + String3->Length) = UNICODE_NULL;
|
|
}
|
|
|
|
status = NtWriteFile(
|
|
alerterHandle,
|
|
NULL, // Event
|
|
NULL, // ApcRoutine
|
|
NULL, // ApcContext
|
|
&ioStatusBlock,
|
|
alert,
|
|
mailslotLength,
|
|
NULL, // ByteOffset
|
|
NULL // Key
|
|
);
|
|
|
|
if ( !NT_SUCCESS(status) ) {
|
|
INTERNAL_ERROR(
|
|
ERROR_LEVEL_UNEXPECTED,
|
|
"SrvAdminAlertRaise: NtWriteFile failed: %X\n",
|
|
status,
|
|
NULL
|
|
);
|
|
|
|
SrvLogServiceFailure( SRV_SVC_NT_WRITE_FILE, status );
|
|
}
|
|
|
|
FREE_HEAP( alert );
|
|
SRVDBG_RELEASE_HANDLE( alerterHandle, "ALR", 23, 0 );
|
|
SrvNtClose( alerterHandle, FALSE );
|
|
|
|
return;
|
|
|
|
} // SrvAdminAlertRaise
|
|
|
|
|
|
NTSTATUS
|
|
TimeToTimeString (
|
|
IN PLARGE_INTEGER Time,
|
|
OUT PUNICODE_STRING TimeString
|
|
)
|
|
{
|
|
TIME_FIELDS timeFields;
|
|
UCHAR buffer[6];
|
|
ANSI_STRING ansiTimeString;
|
|
LARGE_INTEGER localTime;
|
|
|
|
PAGED_CODE( );
|
|
|
|
// !!! need a better, internationalizable way to do this.
|
|
|
|
//
|
|
// Convert Time To Local Time
|
|
//
|
|
|
|
ExSystemTimeToLocalTime(
|
|
Time,
|
|
&localTime
|
|
);
|
|
|
|
|
|
RtlTimeToTimeFields( &localTime, &timeFields );
|
|
|
|
buffer[0] = (UCHAR)( (timeFields.Hour / 10) + '0' );
|
|
buffer[1] = (UCHAR)( (timeFields.Hour % 10) + '0' );
|
|
buffer[2] = ':';
|
|
buffer[3] = (UCHAR)( (timeFields.Minute / 10) + '0' );
|
|
buffer[4] = (UCHAR)( (timeFields.Minute % 10) + '0' );
|
|
buffer[5] = '\0';
|
|
|
|
RtlInitString( &ansiTimeString, buffer );
|
|
|
|
return RtlAnsiStringToUnicodeString( TimeString, &ansiTimeString, TRUE );
|
|
|
|
} // TimeToTimeString
|
|
|
|
|
|
VOID
|
|
CheckErrorCount (
|
|
PSRV_ERROR_RECORD ErrorRecord,
|
|
BOOLEAN UseRatio
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine checks the record of server operations and adds up the
|
|
count of successes to failures.
|
|
|
|
Arguments:
|
|
|
|
ErrorRecord - Points to an SRV_ERROR_RECORD structure
|
|
|
|
UseRatio - If TRUE, look at count of errors,
|
|
If FALSE, look at ratio of error to total.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
ULONG totalOperations;
|
|
ULONG failedOperations;
|
|
|
|
UNICODE_STRING string1, string2;
|
|
WCHAR buffer1[20], buffer2[20];
|
|
NTSTATUS status;
|
|
|
|
PAGED_CODE( );
|
|
|
|
failedOperations = ErrorRecord->FailedOperations;
|
|
totalOperations = failedOperations + ErrorRecord->SuccessfulOperations;
|
|
|
|
//
|
|
// Zero out the counters
|
|
//
|
|
|
|
ErrorRecord->SuccessfulOperations = 0;
|
|
ErrorRecord->FailedOperations = 0;
|
|
|
|
if ( (UseRatio &&
|
|
( totalOperations != 0 &&
|
|
((failedOperations * 100 / totalOperations) >
|
|
ErrorRecord->ErrorThreshold)))
|
|
||
|
|
|
|
(!UseRatio &&
|
|
failedOperations > ErrorRecord->ErrorThreshold) ) {
|
|
|
|
//
|
|
// Raise an alert
|
|
//
|
|
|
|
string1.Buffer = buffer1;
|
|
string1.Length = string1.MaximumLength = sizeof(buffer1);
|
|
|
|
string2.Buffer = buffer2;
|
|
string2.Length = string2.MaximumLength = sizeof(buffer2);
|
|
|
|
status = RtlIntegerToUnicodeString( failedOperations, 10, &string1 );
|
|
ASSERT( NT_SUCCESS( status ) );
|
|
|
|
status = RtlIntegerToUnicodeString( SrvAlertMinutes, 10, &string2 );
|
|
ASSERT( NT_SUCCESS( status ) );
|
|
|
|
if ( ErrorRecord->AlertNumber == ALERT_NetIO) {
|
|
|
|
//
|
|
// We need a third string for the network name.
|
|
//
|
|
// This allocation is unfortunate. We need to maintain
|
|
// per xport error count so we can print out the actual
|
|
// xport name.
|
|
//
|
|
|
|
UNICODE_STRING string3;
|
|
RtlInitUnicodeString(
|
|
&string3,
|
|
StrNoNameTransport
|
|
);
|
|
|
|
|
|
//
|
|
// We need a third string for the network name
|
|
//
|
|
|
|
SrvAdminAlertRaise(
|
|
ErrorRecord->AlertNumber,
|
|
3,
|
|
&string1,
|
|
&string2,
|
|
&string3
|
|
);
|
|
|
|
} else {
|
|
|
|
SrvAdminAlertRaise(
|
|
ErrorRecord->AlertNumber,
|
|
2,
|
|
&string1,
|
|
&string2,
|
|
NULL
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
} // CheckErrorCount
|
|
|
|
|
|
VOID
|
|
CheckDiskSpace (
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine check disk space on local drives. If a drive
|
|
is low on space, an alert is raised.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG diskMask;
|
|
UNICODE_STRING insert1, insert2;
|
|
WCHAR buffer2[20];
|
|
UNICODE_STRING pathName;
|
|
WCHAR dosPathPrefix[] = L"\\DosDevices\\A:\\";
|
|
NTSTATUS status;
|
|
OBJECT_ATTRIBUTES objectAttributes;
|
|
IO_STATUS_BLOCK iosb;
|
|
FILE_FS_SIZE_INFORMATION sizeInformation;
|
|
FILE_FS_DEVICE_INFORMATION deviceInformation;
|
|
HANDLE handle;
|
|
ULONG percentFree;
|
|
PWCH currentDrive;
|
|
DWORD diskconfiguration;
|
|
|
|
PAGED_CODE( );
|
|
|
|
if( SrvFreeDiskSpaceThreshold == 0 ) {
|
|
return;
|
|
}
|
|
|
|
diskMask = 0x80000000; // Start at A:
|
|
|
|
pathName.Buffer = dosPathPrefix;
|
|
pathName.MaximumLength = 32;
|
|
pathName.Length = 28; // skip last backslash!
|
|
|
|
currentDrive = &dosPathPrefix[12];
|
|
insert1.Buffer = &dosPathPrefix[12];
|
|
insert1.Length = 4;
|
|
|
|
//
|
|
// SrvDiskConfiguration is a bitmask of drives that are
|
|
// administratively shared. It is updated by NetShareAdd and
|
|
// NetShareDel.
|
|
//
|
|
diskconfiguration = SrvDiskConfiguration;
|
|
|
|
for ( ; diskMask >= 0x40; diskMask >>= 1, dosPathPrefix[12]++ ) {
|
|
|
|
if ( !(diskconfiguration & diskMask) ) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Check disk space on this disk
|
|
//
|
|
|
|
SrvInitializeObjectAttributes_U(
|
|
&objectAttributes,
|
|
&pathName,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
status = NtOpenFile(
|
|
&handle,
|
|
FILE_READ_ATTRIBUTES,
|
|
&objectAttributes,
|
|
&iosb,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
FILE_NON_DIRECTORY_FILE
|
|
);
|
|
if ( !NT_SUCCESS( status) ) {
|
|
continue;
|
|
}
|
|
SRVDBG_CLAIM_HANDLE( handle, "DSK", 16, 0 );
|
|
|
|
status = NtQueryVolumeInformationFile(
|
|
handle,
|
|
&iosb,
|
|
&deviceInformation,
|
|
sizeof( FILE_FS_DEVICE_INFORMATION ),
|
|
FileFsDeviceInformation
|
|
);
|
|
if ( NT_SUCCESS(status) ) {
|
|
status = iosb.Status;
|
|
}
|
|
SRVDBG_RELEASE_HANDLE( handle, "DSK", 24, 0 );
|
|
if ( !NT_SUCCESS( status ) ||
|
|
(deviceInformation.Characteristics &
|
|
(FILE_FLOPPY_DISKETTE | FILE_READ_ONLY_DEVICE | FILE_WRITE_ONCE_MEDIA)) ||
|
|
!(deviceInformation.Characteristics &
|
|
FILE_DEVICE_IS_MOUNTED) ) {
|
|
SrvNtClose( handle, FALSE );
|
|
continue;
|
|
}
|
|
|
|
// Validate its write-able
|
|
if( deviceInformation.Characteristics & FILE_REMOVABLE_MEDIA )
|
|
{
|
|
PIRP Irp;
|
|
PIO_STACK_LOCATION IrpSp;
|
|
KEVENT CompletionEvent;
|
|
PDEVICE_OBJECT DeviceObject;
|
|
|
|
// Create the IRP
|
|
KeInitializeEvent( &CompletionEvent, SynchronizationEvent, FALSE );
|
|
Irp = BuildCoreOfSyncIoRequest(
|
|
handle,
|
|
NULL,
|
|
&CompletionEvent,
|
|
&iosb,
|
|
&DeviceObject );
|
|
if( !Irp )
|
|
{
|
|
// If we are out of memory, don't log an entry
|
|
goto skip_volume;
|
|
}
|
|
|
|
// Initialize the other IRP fields
|
|
IrpSp = IoGetNextIrpStackLocation( Irp );
|
|
IrpSp->MajorFunction = IRP_MJ_DEVICE_CONTROL;
|
|
IrpSp->MinorFunction = 0;
|
|
IrpSp->Parameters.DeviceIoControl.OutputBufferLength = 0;
|
|
IrpSp->Parameters.DeviceIoControl.InputBufferLength = 0;
|
|
IrpSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_DISK_IS_WRITABLE;
|
|
IrpSp->Parameters.DeviceIoControl.Type3InputBuffer = NULL;
|
|
|
|
// Issue the IO
|
|
status = StartIoAndWait( Irp, DeviceObject, &CompletionEvent, &iosb );
|
|
|
|
if( !NT_SUCCESS(status) )
|
|
{
|
|
skip_volume:
|
|
SrvNtClose( handle, FALSE );
|
|
continue;
|
|
}
|
|
}
|
|
|
|
SrvNtClose( handle, FALSE );
|
|
|
|
pathName.Length += 2; // include last backslash
|
|
status = NtOpenFile(
|
|
&handle,
|
|
FILE_READ_ATTRIBUTES,
|
|
&objectAttributes,
|
|
&iosb,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
FILE_DIRECTORY_FILE
|
|
);
|
|
pathName.Length -= 2; // skip last backslash
|
|
if ( !NT_SUCCESS( status) ) {
|
|
continue;
|
|
}
|
|
SRVDBG_CLAIM_HANDLE( handle, "DSK", 17, 0 );
|
|
|
|
status = NtQueryVolumeInformationFile(
|
|
handle,
|
|
&iosb,
|
|
&sizeInformation,
|
|
sizeof( FILE_FS_SIZE_INFORMATION ),
|
|
FileFsSizeInformation
|
|
);
|
|
if ( NT_SUCCESS(status) ) {
|
|
status = iosb.Status;
|
|
}
|
|
SRVDBG_RELEASE_HANDLE( handle, "DSK", 25, 0 );
|
|
SrvNtClose( handle, FALSE );
|
|
if ( !NT_SUCCESS( status) ) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Calculate % space available = AvailableSpace * 100 / TotalSpace
|
|
//
|
|
|
|
if( sizeInformation.TotalAllocationUnits.QuadPart > 0 )
|
|
{
|
|
LARGE_INTEGER mbFree;
|
|
LARGE_INTEGER mbTotal;
|
|
|
|
|
|
percentFree = (ULONG)(sizeInformation.AvailableAllocationUnits.QuadPart
|
|
* 100 / sizeInformation.TotalAllocationUnits.QuadPart);
|
|
|
|
mbFree.QuadPart = (ULONG)
|
|
(sizeInformation.AvailableAllocationUnits.QuadPart*
|
|
sizeInformation.SectorsPerAllocationUnit*
|
|
sizeInformation.BytesPerSector/
|
|
(1024*1024));
|
|
|
|
ASSERT( percentFree <= 100 );
|
|
|
|
//
|
|
// If space is low raise, and we have already raised an alert,
|
|
// then raise the alert.
|
|
//
|
|
// If space is not low, then clear the alert flag so the we will
|
|
// raise an alert if diskspace falls again.
|
|
//
|
|
|
|
if ( percentFree < SrvFreeDiskSpaceThreshold ) {
|
|
// If a ceiling is specified, make sure we have exceeded it
|
|
if( SrvFreeDiskSpaceCeiling &&
|
|
((mbFree.LowPart > SrvFreeDiskSpaceCeiling) ||
|
|
(mbFree.HighPart != 0))
|
|
)
|
|
{
|
|
goto abort_error;
|
|
}
|
|
|
|
if ( !SrvDiskAlertRaised[ *currentDrive - L'A' ] ) {
|
|
|
|
ULONGLONG FreeSpace;
|
|
|
|
SrvLogError(
|
|
SrvDeviceObject,
|
|
EVENT_SRV_DISK_FULL,
|
|
status,
|
|
NULL,
|
|
0,
|
|
&insert1,
|
|
1
|
|
);
|
|
|
|
//
|
|
// Raise alert
|
|
//
|
|
|
|
insert2.Buffer = buffer2;
|
|
insert2.Length = insert2.MaximumLength = sizeof(buffer2);
|
|
|
|
FreeSpace = (ULONGLONG)(sizeInformation.AvailableAllocationUnits.QuadPart
|
|
* sizeInformation.SectorsPerAllocationUnit
|
|
* sizeInformation.BytesPerSector);
|
|
|
|
|
|
status = RtlInt64ToUnicodeString(
|
|
FreeSpace,
|
|
10,
|
|
&insert2
|
|
);
|
|
|
|
ASSERT( NT_SUCCESS( status ) );
|
|
|
|
SrvAdminAlertRaise(
|
|
ALERT_Disk_Full,
|
|
2,
|
|
&insert1,
|
|
&insert2,
|
|
NULL
|
|
);
|
|
|
|
SrvDiskAlertRaised[ *currentDrive - L'A' ] = TRUE;
|
|
}
|
|
|
|
} else { // if ( percentFree < SrvFreeDiskSpaceThreshold )
|
|
|
|
abort_error:
|
|
SrvDiskAlertRaised[ *currentDrive - L'A' ] = FALSE;
|
|
|
|
}
|
|
}
|
|
} // for ( ; diskMask >= 0x40; ... )
|
|
|
|
return;
|
|
|
|
} // CheckDiskSpace
|
|
|
|
|
|
NTSTATUS
|
|
OpenAlerter (
|
|
OUT PHANDLE AlerterHandle
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine opens the alerter server's mailslot.
|
|
|
|
Arguments:
|
|
|
|
AlerterHandle - returns a handle to the mailslot.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Indicates whether the mailslot was opened.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS status;
|
|
IO_STATUS_BLOCK iosb;
|
|
UNICODE_STRING alerterName;
|
|
OBJECT_ATTRIBUTES objectAttributes;
|
|
|
|
PAGED_CODE( );
|
|
|
|
//
|
|
// Open a handle to the alerter service's mailslot.
|
|
//
|
|
// !!! use a #define for the name!
|
|
//
|
|
|
|
RtlInitUnicodeString( &alerterName, StrAlerterMailslot );
|
|
|
|
SrvInitializeObjectAttributes_U(
|
|
&objectAttributes,
|
|
&alerterName,
|
|
0,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
status = IoCreateFile(
|
|
AlerterHandle,
|
|
GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE,
|
|
&objectAttributes,
|
|
&iosb,
|
|
NULL,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
FILE_OPEN,
|
|
FILE_SYNCHRONOUS_IO_NONALERT, // Create Options
|
|
NULL, // EA Buffer
|
|
0, // EA Length
|
|
CreateFileTypeNone, // File type
|
|
NULL, // ExtraCreateParameters
|
|
0 // Options
|
|
);
|
|
|
|
if ( !NT_SUCCESS(status) ) {
|
|
KdPrint(( "OpenAlerter: failed to open alerter mailslot: %X, "
|
|
"an alert was lost.\n", status ));
|
|
} else {
|
|
SRVDBG_CLAIM_HANDLE( AlerterHandle, "ALR", 18, 0 );
|
|
}
|
|
|
|
return status;
|
|
|
|
} // OpenAlerter
|
|
|
|
VOID
|
|
RecalcCoreSearchTimeout(
|
|
VOID
|
|
)
|
|
{
|
|
ULONG factor;
|
|
ULONG newTimeout;
|
|
|
|
PAGED_CODE( );
|
|
|
|
//
|
|
// we reduce the timeout time by 2**factor
|
|
//
|
|
|
|
factor = SrvStatistics.CurrentNumberOfOpenSearches >> 9;
|
|
|
|
//
|
|
// Minimum is 30 secs.
|
|
//
|
|
|
|
ACQUIRE_LOCK( &SrvConfigurationLock );
|
|
newTimeout = MAX(30, SrvCoreSearchTimeout >> factor);
|
|
SrvSearchMaxTimeout = SecondsToTime( newTimeout, FALSE );
|
|
RELEASE_LOCK( &SrvConfigurationLock );
|
|
|
|
return;
|
|
|
|
} // RecalcCoreSearchTimeout
|
|
|
|
VOID
|
|
SrvCaptureScavengerTimeout (
|
|
IN PLARGE_INTEGER ScavengerTimeout,
|
|
IN PLARGE_INTEGER AlerterTimeout
|
|
)
|
|
{
|
|
KIRQL oldIrql;
|
|
|
|
ACQUIRE_SPIN_LOCK( &ScavengerSpinLock, &oldIrql );
|
|
|
|
SrvScavengerTimeout = *ScavengerTimeout;
|
|
SrvAlertSchedule = *AlerterTimeout;
|
|
|
|
RELEASE_SPIN_LOCK( &ScavengerSpinLock, oldIrql );
|
|
|
|
return;
|
|
|
|
} // SrvCaptureScavengerTimeout
|
|
|
|
|
|
#if SRVDBG_PERF
|
|
extern ULONG Trapped512s;
|
|
#endif
|
|
|
|
VOID
|
|
SrvUpdateStatisticsFromQueues (
|
|
OUT PSRV_STATISTICS CapturedSrvStatistics OPTIONAL
|
|
)
|
|
{
|
|
KIRQL oldIrql;
|
|
PWORK_QUEUE queue;
|
|
|
|
ACQUIRE_GLOBAL_SPIN_LOCK( Statistics, &oldIrql );
|
|
|
|
SrvStatistics.TotalBytesSent.QuadPart = 0;
|
|
SrvStatistics.TotalBytesReceived.QuadPart = 0;
|
|
SrvStatistics.TotalWorkContextBlocksQueued.Time.QuadPart = 0;
|
|
SrvStatistics.TotalWorkContextBlocksQueued.Count = 0;
|
|
|
|
//
|
|
// Get the nonblocking statistics
|
|
//
|
|
|
|
for( queue = SrvWorkQueues; queue < eSrvWorkQueues; queue++ ) {
|
|
|
|
SrvStatistics.TotalBytesSent.QuadPart += queue->stats.BytesSent;
|
|
SrvStatistics.TotalBytesReceived.QuadPart += queue->stats.BytesReceived;
|
|
|
|
SrvStatistics.TotalWorkContextBlocksQueued.Count +=
|
|
queue->stats.WorkItemsQueued.Count * STATISTICS_SMB_INTERVAL;
|
|
|
|
SrvStatistics.TotalWorkContextBlocksQueued.Time.QuadPart +=
|
|
queue->stats.WorkItemsQueued.Time.QuadPart;
|
|
}
|
|
|
|
#if SRVDBG_PERF
|
|
SrvStatistics.TotalWorkContextBlocksQueued.Count += Trapped512s;
|
|
Trapped512s = 0;
|
|
#endif
|
|
|
|
if ( ARGUMENT_PRESENT(CapturedSrvStatistics) ) {
|
|
*CapturedSrvStatistics = SrvStatistics;
|
|
}
|
|
|
|
RELEASE_GLOBAL_SPIN_LOCK( Statistics, oldIrql );
|
|
|
|
ACQUIRE_SPIN_LOCK( (PKSPIN_LOCK)IoStatisticsLock, &oldIrql );
|
|
|
|
for( queue = SrvWorkQueues; queue < eSrvWorkQueues; queue++ ) {
|
|
|
|
**(PULONG *)&IoReadOperationCount += (ULONG)(queue->stats.ReadOperations - queue->saved.ReadOperations );
|
|
queue->saved.ReadOperations = queue->stats.ReadOperations;
|
|
|
|
**(PLONGLONG *)&IoReadTransferCount += (queue->stats.BytesRead - queue->saved.BytesRead );
|
|
queue->saved.BytesRead = queue->stats.BytesRead;
|
|
|
|
**(PULONG *)&IoWriteOperationCount += (ULONG)(queue->stats.WriteOperations - queue->saved.WriteOperations );
|
|
queue->saved.WriteOperations = queue->stats.WriteOperations;
|
|
|
|
**(PLONGLONG *)&IoWriteTransferCount += (queue->stats.BytesWritten - queue->saved.BytesWritten );
|
|
queue->saved.BytesWritten = queue->stats.BytesWritten;
|
|
}
|
|
|
|
RELEASE_SPIN_LOCK( (PKSPIN_LOCK)IoStatisticsLock, oldIrql );
|
|
|
|
return;
|
|
|
|
} // SrvUpdateStatisticsFromQueues
|
|
|
|
VOID
|
|
ProcessOrphanedBlocks (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Orphaned connections are connections with ref counts of 1 but
|
|
with no workitem, etc associated with it. They need to be cleaned
|
|
up by a dereference.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PSINGLE_LIST_ENTRY listEntry;
|
|
PQUEUEABLE_BLOCK_HEADER block;
|
|
|
|
PAGED_CODE( );
|
|
|
|
//
|
|
// Run through the list of connection with pending disconnects.
|
|
// Do the work necessary to shut the disconnection connection
|
|
// down.
|
|
//
|
|
|
|
while ( TRUE ) {
|
|
|
|
listEntry = ExInterlockedPopEntrySList(
|
|
&SrvBlockOrphanage,
|
|
&GLOBAL_SPIN_LOCK(Fsd)
|
|
);
|
|
|
|
if( listEntry == NULL ) {
|
|
break;
|
|
}
|
|
|
|
InterlockedDecrement( &SrvResourceOrphanedBlocks );
|
|
|
|
block = CONTAINING_RECORD(
|
|
listEntry,
|
|
QUEUEABLE_BLOCK_HEADER,
|
|
SingleListEntry
|
|
);
|
|
|
|
if ( GET_BLOCK_TYPE(block) == BlockTypeConnection ) {
|
|
|
|
SrvDereferenceConnection( (PCONNECTION)block );
|
|
|
|
} else if ( GET_BLOCK_TYPE(block) == BlockTypeRfcb ) {
|
|
|
|
SrvDereferenceRfcb( (PRFCB)block );
|
|
|
|
} else {
|
|
ASSERT(0);
|
|
}
|
|
}
|
|
|
|
return;
|
|
|
|
} // ProcessOrphanedBlocks
|
|
|
|
VOID
|
|
UpdateSessionLastUseTime(
|
|
IN PLARGE_INTEGER CurrentTime
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine walks the rfcb list and if it is found to be marked active,
|
|
the session LastUseTime is updated with the current time.
|
|
|
|
Arguments:
|
|
|
|
CurrentTime - the current system time.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG listEntryOffset = SrvRfcbList.ListEntryOffset;
|
|
PLIST_ENTRY listEntry;
|
|
PRFCB rfcb;
|
|
|
|
PAGED_CODE( );
|
|
|
|
//
|
|
// Acquire the lock that protects the SrvRfcbList
|
|
//
|
|
|
|
ACQUIRE_LOCK( SrvRfcbList.Lock );
|
|
|
|
//
|
|
// Walk the list of blocks until we find one with a resume handle
|
|
// greater than or equal to the specified resume handle.
|
|
//
|
|
|
|
for (
|
|
listEntry = SrvRfcbList.ListHead.Flink;
|
|
listEntry != &SrvRfcbList.ListHead;
|
|
listEntry = listEntry->Flink ) {
|
|
|
|
//
|
|
// Get a pointer to the actual block.
|
|
//
|
|
|
|
rfcb = (PRFCB)((PCHAR)listEntry - listEntryOffset);
|
|
|
|
//
|
|
// Check the state of the block and if it is active,
|
|
// reference it. This must be done as an atomic operation
|
|
// order to prevent the block from being deleted.
|
|
//
|
|
|
|
if ( rfcb->IsActive ) {
|
|
|
|
rfcb->Lfcb->Session->LastUseTime = *CurrentTime;
|
|
rfcb->IsActive = FALSE;
|
|
}
|
|
|
|
} // walk list
|
|
|
|
RELEASE_LOCK( SrvRfcbList.Lock );
|
|
return;
|
|
|
|
} // UpdateSessionLastUseTime
|
|
|