ProcessHacker/phlib/provider.c
2025-05-13 19:45:22 +03:00

471 lines
15 KiB
C

/*
* Process Hacker -
* provider system
*
* Copyright (C) 2009-2016 wj32
*
* This file is part of Process Hacker.
*
* Process Hacker is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Process Hacker is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Provider objects allow a function to be executed periodically. This is managed by a
* synchronization timer object which is signaled periodically. The use of a timer object as opposed
* to a simple sleep call means that the length of time a provider function takes to execute has no
* effect on the interval between runs.
*
* In contrast to callback objects, the context passed to provider functions must be
* reference-counted objects. This means that it is not guaranteed that the function will not be in
* execution after the unregister operation is complete. However, the since the context object is
* reference-counted, there are no safety issues.
*
* Providers can be boosted, which causes them to be run immediately ignoring the interval. This is
* separate to the periodic runs, and does not cause the next periodic run to be missed. Providers,
* even when boosted, always run on the same provider thread. The other option would be to have the
* boosting thread run the provider function directly, which would involve unnecessary blocking and
* synchronization.
*/
#include <ph.h>
#include <provider.h>
#ifdef DEBUG
PPH_LIST PhDbgProviderList;
PH_QUEUED_LOCK PhDbgProviderListLock = PH_QUEUED_LOCK_INIT;
#endif
/**
* Initializes a provider thread.
*
* \param ProviderThread A pointer to a provider thread object.
* \param Interval The interval between each run, in milliseconds.
*/
VOID PhInitializeProviderThread(
_Out_ PPH_PROVIDER_THREAD ProviderThread,
_In_ ULONG Interval
)
{
ProviderThread->ThreadHandle = NULL;
ProviderThread->TimerHandle = NULL;
ProviderThread->Interval = Interval;
ProviderThread->State = ProviderThreadStopped;
PhInitializeQueuedLock(&ProviderThread->Lock);
InitializeListHead(&ProviderThread->ListHead);
ProviderThread->BoostCount = 0;
#ifdef DEBUG
PhAcquireQueuedLockExclusive(&PhDbgProviderListLock);
if (!PhDbgProviderList)
PhDbgProviderList = PhCreateList(4);
PhAddItemList(PhDbgProviderList, ProviderThread);
PhReleaseQueuedLockExclusive(&PhDbgProviderListLock);
#endif
}
/**
* Frees resources used by a provider thread.
*
* \param ProviderThread A pointer to a provider thread object.
*/
VOID PhDeleteProviderThread(
_Inout_ PPH_PROVIDER_THREAD ProviderThread
)
{
#ifdef DEBUG
ULONG index;
#endif
// Nothing
#ifdef DEBUG
PhAcquireQueuedLockExclusive(&PhDbgProviderListLock);
if ((index = PhFindItemList(PhDbgProviderList, ProviderThread)) != -1)
PhRemoveItemList(PhDbgProviderList, index);
PhReleaseQueuedLockExclusive(&PhDbgProviderListLock);
#endif
}
NTSTATUS NTAPI PhpProviderThreadStart(
_In_ PVOID Parameter
)
{
PH_AUTO_POOL autoPool;
PPH_PROVIDER_THREAD providerThread = (PPH_PROVIDER_THREAD)Parameter;
NTSTATUS status = STATUS_SUCCESS;
PLIST_ENTRY listEntry;
PPH_PROVIDER_REGISTRATION registration;
PPH_PROVIDER_FUNCTION providerFunction;
PVOID object;
LIST_ENTRY tempListHead;
PhInitializeAutoPool(&autoPool);
while (providerThread->State != ProviderThreadStopping)
{
// Keep removing and executing providers from the list until there are no more. Each removed
// provider will be placed on the temporary list. After this is done, all providers on the
// temporary list will be re-added to the list again.
//
// The key to this working safely with the other functions (boost, register, unregister) is
// that at all times when the mutex is not acquired every single provider must be in a list
// (main list or the temp list).
InitializeListHead(&tempListHead);
PhAcquireQueuedLockExclusive(&providerThread->Lock);
// Main loop.
// We check the status variable for STATUS_ALERTED, which means that someone is requesting
// that a provider be boosted. Note that if they alert this thread while we are not waiting
// on the timer, when we do perform the wait it will return immediately with STATUS_ALERTED.
while (TRUE)
{
if (status == STATUS_ALERTED)
{
// Check if we have any more providers to boost. Note that this always works because
// boosted providers are always in front of normal providers. Therefore we will
// never mistakenly boost normal providers.
if (providerThread->BoostCount == 0)
break;
}
listEntry = RemoveHeadList(&providerThread->ListHead);
if (listEntry == &providerThread->ListHead)
break;
registration = CONTAINING_RECORD(listEntry, PH_PROVIDER_REGISTRATION, ListEntry);
// Add the provider to the temp list.
InsertTailList(&tempListHead, listEntry);
if (status != STATUS_ALERTED)
{
if (!registration->Enabled || registration->Unregistering)
continue;
}
else
{
// If we're boosting providers, we don't care if they are enabled or not. However,
// we have to make sure any providers which are being unregistered get a chance to
// fix the boost count.
if (registration->Unregistering)
{
PhReleaseQueuedLockExclusive(&providerThread->Lock);
PhAcquireQueuedLockExclusive(&providerThread->Lock);
continue;
}
}
if (status == STATUS_ALERTED)
{
assert(registration->Boosting);
registration->Boosting = FALSE;
providerThread->BoostCount--;
}
providerFunction = registration->Function;
object = registration->Object;
if (object)
PhReferenceObject(object);
registration->RunId++;
PhReleaseQueuedLockExclusive(&providerThread->Lock);
providerFunction(object);
PhDrainAutoPool(&autoPool);
PhAcquireQueuedLockExclusive(&providerThread->Lock);
if (object)
PhDereferenceObject(object);
}
// Re-add the items in the temp list to the main list.
while ((listEntry = RemoveHeadList(&tempListHead)) != &tempListHead)
{
registration = CONTAINING_RECORD(listEntry, PH_PROVIDER_REGISTRATION, ListEntry);
// We must insert boosted providers at the front of the list in order to maintain the
// condition that boosted providers are always in front of normal providers. This occurs
// when the timer is signaled just before a boosting provider alerts our thread.
if (!registration->Boosting)
InsertTailList(&providerThread->ListHead, listEntry);
else
InsertHeadList(&providerThread->ListHead, listEntry);
}
PhReleaseQueuedLockExclusive(&providerThread->Lock);
// Perform an alertable wait so we can be woken up by someone telling us to boost providers,
// or to terminate.
status = NtWaitForSingleObject(
providerThread->TimerHandle,
TRUE,
NULL
);
}
PhDeleteAutoPool(&autoPool);
return STATUS_SUCCESS;
}
/**
* Starts a provider thread.
*
* \param ProviderThread A pointer to a provider thread object.
*/
VOID PhStartProviderThread(
_Inout_ PPH_PROVIDER_THREAD ProviderThread
)
{
if (ProviderThread->State != ProviderThreadStopped)
return;
// Create and set the timer.
NtCreateTimer(&ProviderThread->TimerHandle, TIMER_ALL_ACCESS, NULL, SynchronizationTimer);
PhSetIntervalProviderThread(ProviderThread, ProviderThread->Interval);
// Create and start the thread.
ProviderThread->ThreadHandle = PhCreateThread(
0,
PhpProviderThreadStart,
ProviderThread
);
ProviderThread->State = ProviderThreadRunning;
}
/**
* Stops a provider thread.
*
* \param ProviderThread A pointer to a provider thread object.
*/
VOID PhStopProviderThread(
_Inout_ PPH_PROVIDER_THREAD ProviderThread
)
{
if (ProviderThread->State != ProviderThreadRunning)
return;
// Signal to the thread that we are shutting down, and wait for it to exit.
ProviderThread->State = ProviderThreadStopping;
NtAlertThread(ProviderThread->ThreadHandle); // wake it up
NtWaitForSingleObject(ProviderThread->ThreadHandle, FALSE, NULL);
// Free resources.
NtClose(ProviderThread->ThreadHandle);
NtClose(ProviderThread->TimerHandle);
ProviderThread->ThreadHandle = NULL;
ProviderThread->TimerHandle = NULL;
ProviderThread->State = ProviderThreadStopped;
}
/**
* Sets the run interval for a provider thread.
*
* \param ProviderThread A pointer to a provider thread object.
* \param Interval The interval between each run, in milliseconds.
*/
VOID PhSetIntervalProviderThread(
_Inout_ PPH_PROVIDER_THREAD ProviderThread,
_In_ ULONG Interval
)
{
ProviderThread->Interval = Interval;
if (ProviderThread->TimerHandle)
{
LARGE_INTEGER interval;
interval.QuadPart = -(LONGLONG)Interval * PH_TIMEOUT_MS;
NtSetTimer(ProviderThread->TimerHandle, &interval, NULL, NULL, FALSE, Interval, NULL);
}
}
/**
* Registers a provider with a provider thread.
*
* \param ProviderThread A pointer to a provider thread object.
* \param Function The provider function.
* \param Object A pointer to an object to pass to the provider function. The object must be managed
* by the reference-counting system.
* \param Registration A variable which receives registration information for the provider.
*
* \remarks The provider is initially disabled. Call PhSetEnabledProvider() to enable it.
*/
VOID PhRegisterProvider(
_Inout_ PPH_PROVIDER_THREAD ProviderThread,
_In_ PPH_PROVIDER_FUNCTION Function,
_In_opt_ PVOID Object,
_Out_ PPH_PROVIDER_REGISTRATION Registration
)
{
Registration->ProviderThread = ProviderThread;
Registration->Function = Function;
Registration->Object = Object;
Registration->RunId = 0;
Registration->Enabled = FALSE;
Registration->Unregistering = FALSE;
Registration->Boosting = FALSE;
if (Object)
PhReferenceObject(Object);
PhAcquireQueuedLockExclusive(&ProviderThread->Lock);
InsertTailList(&ProviderThread->ListHead, &Registration->ListEntry);
PhReleaseQueuedLockExclusive(&ProviderThread->Lock);
}
/**
* Unregisters a provider.
*
* \param Registration A pointer to the registration object for a provider.
*
* \remarks The provider function may still be in execution once this function returns.
*/
VOID PhUnregisterProvider(
_Inout_ PPH_PROVIDER_REGISTRATION Registration
)
{
PPH_PROVIDER_THREAD providerThread;
providerThread = Registration->ProviderThread;
Registration->Unregistering = TRUE;
// There are two possibilities for removal:
// 1. The provider is removed while the thread is not in the main loop. This is the normal case.
// 2. The provider is removed while the thread is in the main loop. In that case the provider
// will be removed from the temp list and so it won't be re-added to the main list.
PhAcquireQueuedLockExclusive(&providerThread->Lock);
RemoveEntryList(&Registration->ListEntry);
// Fix the boost count.
if (Registration->Boosting)
providerThread->BoostCount--;
// The user-supplied object must be dereferenced
// while the mutex is held.
if (Registration->Object)
PhDereferenceObject(Registration->Object);
PhReleaseQueuedLockExclusive(&providerThread->Lock);
}
/**
* Causes a provider to be queued for immediate execution.
*
* \param Registration A pointer to the registration object for a provider.
* \param FutureRunId A variable which receives the run ID of the future run.
*
* \return TRUE if the operation was successful; FALSE if the provider is being unregistered, the
* provider is already being boosted, or the provider thread is not running.
*
* \remarks Boosted providers will be run immediately, ignoring the run interval. Boosting will not
* however affect the normal runs.
*/
BOOLEAN PhBoostProvider(
_Inout_ PPH_PROVIDER_REGISTRATION Registration,
_Out_opt_ PULONG FutureRunId
)
{
PPH_PROVIDER_THREAD providerThread;
ULONG futureRunId;
if (Registration->Unregistering)
return FALSE;
providerThread = Registration->ProviderThread;
// Simply move to the provider to the front of the list. This works even if the provider is
// currently in the temp list.
PhAcquireQueuedLockExclusive(&providerThread->Lock);
// Abort if the provider is already being boosted or the provider thread is stopping/stopped.
if (Registration->Boosting || providerThread->State != ProviderThreadRunning)
{
PhReleaseQueuedLockExclusive(&providerThread->Lock);
return FALSE;
}
RemoveEntryList(&Registration->ListEntry);
InsertHeadList(&providerThread->ListHead, &Registration->ListEntry);
Registration->Boosting = TRUE;
providerThread->BoostCount++;
futureRunId = Registration->RunId + 1;
PhReleaseQueuedLockExclusive(&providerThread->Lock);
// Wake up the thread.
NtAlertThread(providerThread->ThreadHandle);
if (FutureRunId)
*FutureRunId = futureRunId;
return TRUE;
}
/**
* Gets the current run ID of a provider.
*
* \param Registration A pointer to the registration object for a provider.
*/
ULONG PhGetRunIdProvider(
_In_ PPH_PROVIDER_REGISTRATION Registration
)
{
return Registration->RunId;
}
/**
* Gets whether a provider is enabled.
*
* \param Registration A pointer to the registration object for a provider.
*/
BOOLEAN PhGetEnabledProvider(
_In_ PPH_PROVIDER_REGISTRATION Registration
)
{
return Registration->Enabled;
}
/**
* Sets whether a provider is enabled.
*
* \param Registration A pointer to the registration object for a provider.
* \param Enabled TRUE if the provider is enabled, otherwise FALSE.
*/
VOID PhSetEnabledProvider(
_Inout_ PPH_PROVIDER_REGISTRATION Registration,
_In_ BOOLEAN Enabled
)
{
Registration->Enabled = Enabled;
}