471 lines
15 KiB
C
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;
|
|
}
|