Windows NT 4.0 source code leak
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.
 
 
 
 
 
 

3855 lines
98 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"
#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};
WORK_QUEUE_ITEM ScavengerWorkItem = {0};
BOOLEAN ScavengerRunning = FALSE;
KSPIN_LOCK ScavengerSpinLock = {0};
//
// Flags indicating which scavenger algorithms need to run.
//
BOOLEAN RunShortTermAlgorithm = FALSE;
BOOLEAN RunScavengerAlgorithm = FALSE;
BOOLEAN RunAlerterAlgorithm = 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 };
//
// Defined somewhere else.
//
LARGE_INTEGER
SecondsToTime (
IN ULONG Seconds,
IN BOOLEAN MakeNegative
);
//
// Local declarations
//
VOID
ScavengerTimerRoutine (
IN PKDPC Dpc,
IN PVOID DeferredContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
);
VOID
SrvResourceThread (
IN PVOID Parameter
);
VOID
ScavengerThread (
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( &currentTime );
NextScavengeTime.QuadPart = currentTime.QuadPart + SrvScavengerTimeout.QuadPart;
NextAlertTime.QuadPart = currentTime.QuadPart + SrvAlertSchedule.QuadPart;
//
// Initialize the scavenger thread work item.
//
ExInitializeWorkItem( &ScavengerWorkItem, ScavengerThread, NULL );
//
// 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
);
}
//
// 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 );
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 = (ULONG)SystemArgument1;
currentTime.HighPart = (LONG)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;
}
SrvFsdQueueExWorkItem(
&ScavengerWorkItem,
&ScavengerRunning,
CriticalWorkQueue
);
}
//
// 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()
{
CSHORT 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 = (CSHORT)-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 30 seconds is VERY stuck....
//
if( workContext->IsNotStuck || interval.LowPart < 30 * 10000000 ) {
continue;
}
header = workContext->RequestHeader;
if ( (workContext->BlockHeader.ReferenceCount != 0) &&
(workContext->ProcessingCount != 0) &&
header != NULL ) {
//
// Convert to seconds
//
interval.LowPart /= 10000000;
if( !printed ) {
DbgPrint( "--- 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 ) {
DbgPrint( "Client %s, %u secs, Context %X",
connection->OemClientMachineNameString.Buffer,
interval.LowPart, workContext );
switch( header->Command ) {
case SMB_COM_NT_CREATE_ANDX:
DbgPrint( " NT_CREATE_ANDX\n" );
break;
case SMB_COM_OPEN_PRINT_FILE:
DbgPrint( " OPEN_PRINT_FILE\n" );
break;
case SMB_COM_CLOSE_PRINT_FILE:
DbgPrint( " CLOSE_PRINT_FILE\n" );
break;
case SMB_COM_CLOSE:
DbgPrint( " CLOSE\n" );
break;
case SMB_COM_SESSION_SETUP_ANDX:
DbgPrint( " SESSION_SETUP\n" );
break;
case SMB_COM_OPEN_ANDX:
DbgPrint( " OPEN_ANDX\n" );
break;
case SMB_COM_NT_TRANSACT:
case SMB_COM_NT_TRANSACT_SECONDARY:
DbgPrint( " NT_TRANSACT\n" );
break;
case SMB_COM_TRANSACTION2:
case SMB_COM_TRANSACTION2_SECONDARY:
DbgPrint( " TRANSACTION2\n" );
break;
case SMB_COM_TRANSACTION:
case SMB_COM_TRANSACTION_SECONDARY:
DbgPrint( " TRANSACTION\n" );
break;
default:
DbgPrint( " 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 ) {
DbgPrint( "Longest so far: %s, %u secs, cmd %u\n", SrvMostStuck.ClientName, SrvMostStuck.Seconds, SrvMostStuck.Command );
}
RELEASE_LOCK( &SrvEndpointLock );
}
#endif
VOID
ScavengerThread (
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;
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
SrvLookForStuckOperations();
#endif
//
// If the short-term timer expired, run that algorithm now.
//
if ( RunShortTermAlgorithm ) {
LARGE_INTEGER currentTime;
RunShortTermAlgorithm = FALSE;
KeQuerySystemTime( &currentTime );
//
// Time out oplock break requests.
//
TimeoutStuckOplockBreaks( &currentTime );
}
//
// 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 ) {
KeSetEvent( ScavengerThreadTerminationEvent, 0, FALSE );
}
}
RELEASE_SPIN_LOCK( &ScavengerSpinLock, oldIrql );
} while ( runAgain );
//
// reset popup status.
//
IoSetThreadHardErrorMode( oldPopupStatus );
return;
} // ScavengerThread
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( &currentTime );
GET_SERVER_TIME( SrvWorkQueues, &currentTick );
//
// 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( &currentTime );
//
// Time out oplock break requests.
//
TimeoutStuckOplockBreaks( &currentTime );
//
// See if we can free some work items at this time.
//
for( queue = SrvWorkQueues; queue < eSrvWorkQueues; queue++ ) {
LazyFreeQueueDataStructures( queue );
}
//
// See if we can decommission some threads at this time.
//
for( queue = SrvWorkQueues; queue < eSrvWorkQueues; queue++ ) {
if( (queue->AvailableThreads > 1) &&
(queue->AvgQueueDepthSum >> LOG2_QUEUE_SAMPLES) == 0 &&
KeReadStateQueue( &queue->Queue ) == 0 &&
GET_BLOCK_TYPE( &queue->KillOneThreadWorkItem ) == BlockTypeGarbage ) {
//
// This queue has available worker threads, the
// average queue depth is zero, and the current queue depth
// is zero. Kill off one thread.
//
SET_BLOCK_TYPE( &queue->KillOneThreadWorkItem, BlockTypeWorkContextSpecial );
queue->KillOneThreadWorkItem.FspRestartRoutine = SrvTerminateWorkerThread;
SrvInsertWorkQueueHead( queue, &queue->KillOneThreadWorkItem );
}
}
//
// See if we can decommission one thread from the blocking work queue
//
if( SrvBlockingWorkQueue.AvailableThreads > 1 &&
KeReadStateQueue( &SrvBlockingWorkQueue.Queue ) == 0 &&
GET_BLOCK_TYPE( &SrvBlockingWorkQueue.KillOneThreadWorkItem ) == BlockTypeGarbage ) {
//
// We have available worker threads, the queue length is zero.
// Kill off one thread.
//
SET_BLOCK_TYPE( &SrvBlockingWorkQueue.KillOneThreadWorkItem,
BlockTypeWorkContextSpecial );
SrvBlockingWorkQueue.KillOneThreadWorkItem.FspRestartRoutine = SrvTerminateWorkerThread;
SrvInsertWorkQueueHead( &SrvBlockingWorkQueue,
&SrvBlockingWorkQueue.KillOneThreadWorkItem );
}
//
// See if we need to update QOS information.
//
if ( --ScavengerUpdateQosCount < 0 ) {
UpdateConnectionQos( &currentTime );
ScavengerUpdateQosCount = SrvScavengerUpdateQosCount;
}
//
// See if we need to walk the rfcb list to update the session
// last use time.
//
if ( --ScavengerCheckRfcbActive < 0 ) {
UpdateSessionLastUseTime( &currentTime );
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( &currentTime );
//
// Update the statistics from the the queues
//
SrvUpdateStatisticsFromQueues( NULL );
}
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 %X, idle %u\n", Connection, i ));
}
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;
}
if ( !SrvEnableForcedLogoff &&
!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->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->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->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.
//
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
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 );
//
// 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 %d\n",
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 %x.\n",
connection ));
}
ASSERT( connection->OnNeedResourceQueue );
ASSERT( connection->BlockHeader.ReferenceCount > 0 );
if( GET_BLOCK_STATE( connection ) != BlockStateActive ) {
IF_DEBUG( WORKITEMS ) {
KdPrint(("SrvServiceWorkItemShortage: Connection %x "
"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 %X\n", connection ));
}
workContext->BlockHeader.ReferenceCount = 1;
//
// 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 %x "
"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 %X."
" 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 %X 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 %x "
"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 %x\n",
workContext ));
}
workContext->BlockHeader.ReferenceCount = 0;
RETURN_FREE_WORKITEM( workContext );
}
IF_DEBUG(WORKITEMS) KdPrint(( "SrvServiceWorkItemShortage DONE at %d\n", __LINE__ ));
} // SrvServiceWorkItemShortage
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
--*/
{
CSHORT 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 = (CSHORT)-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 %lx\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( OPLOCK ) {
KdPrint(( "srv!TimeoutStuckOplockBreaks: Failing stuck oplock, "
"break request. Closing %wZ\n",
&rfcb->Mfcb->FileName ));
KdPrint(( "Timeout time = %08lx.%08lx, current time = %08lx.%08lx\n",
pagedRfcb->OplockBreakTimeoutTime.HighPart,
pagedRfcb->OplockBreakTimeoutTime.LowPart,
CurrentTime->HighPart,
CurrentTime->LowPart ));
}
//
// 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.
--*/
{
CSHORT 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 = (CSHORT)-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)InterlockedExchange( (PLONG)&queue->FreeContext, (LONG)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)InterlockedExchange( (PLONG)&queue->CachedFreeRfcb, (LONG)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)InterlockedExchange((PLONG)&queue->CachedFreeMfcb,
(LONG)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( mailslotLength, BlockTypeDataBuffer );
if ( alert == NULL ) {
SRVDBG_RELEASE_HANDLE( alerterHandle, "ALR", 20, 0 );
SrvNtClose( alerterHandle, FALSE );
return;
}
//
// Set up the standard alert structure.
//
KeQuerySystemTime( &currentTime );
RtlTimeToSecondsSince1970( &currentTime, &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( mailslotLength, BlockTypeDataBuffer );
if ( alert == NULL ) {
SRVDBG_RELEASE_HANDLE( alerterHandle, "ALR", 22, 0 );
SrvNtClose( alerterHandle, FALSE );
return;
}
//
// Set up the standard alert structure.
//
KeQuerySystemTime( &currentTime );
RtlTimeToSecondsSince1970( &currentTime, &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( );
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 );
SrvNtClose( handle, FALSE );
if ( !NT_SUCCESS( status ) ||
(deviceInformation.Characteristics &
(FILE_FLOPPY_DISKETTE | FILE_READ_ONLY_DEVICE | FILE_WRITE_ONCE_MEDIA)) ||
!(deviceInformation.Characteristics &
FILE_DEVICE_IS_MOUNTED) ) {
continue;
}
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
//
percentFree = (ULONG)(sizeInformation.AvailableAllocationUnits.QuadPart
* 100 / sizeInformation.TotalAllocationUnits.QuadPart);
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 ( !SrvDiskAlertRaised[ *currentDrive - L'A' ] ) {
SrvLogError(
SrvDeviceObject,
EVENT_SRV_DISK_FULL,
status,
NULL,
0,
&insert1,
1
);
//
// Raise alert
//
insert2.Buffer = buffer2;
insert2.Length = insert2.MaximumLength = sizeof(buffer2);
status = RtlIntegerToUnicodeString(
(ULONG)(sizeInformation.AvailableAllocationUnits.QuadPart
* sizeInformation.SectorsPerAllocationUnit
* sizeInformation.BytesPerSector),
10,
&insert2
);
ASSERT( NT_SUCCESS( status ) );
SrvAdminAlertRaise(
ALERT_Disk_Full,
2,
&insert1,
&insert2,
NULL
);
SrvDiskAlertRaised[ *currentDrive - L'A' ] = TRUE;
}
} else { // if ( percentFree < SrvFreeDiskSpaceThreshold )
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