/*++ 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 #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