mirror of
https://github.com/mirror/processhacker
synced 2026-06-08 16:03:24 +00:00
f4ad8a85da
git-svn-id: svn://svn.code.sf.net/p/processhacker/code@6179 21ef857c-d57f-4fe0-8362-d861dc6d29cd
1034 lines
33 KiB
C
1034 lines
33 KiB
C
/*
|
|
* Process Hacker -
|
|
* service provider
|
|
*
|
|
* Copyright (C) 2009-2015 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/>.
|
|
*/
|
|
|
|
#include <phapp.h>
|
|
#include <winevt.h>
|
|
#include <extmgri.h>
|
|
|
|
typedef DWORD (WINAPI *_NotifyServiceStatusChangeW)(
|
|
_In_ SC_HANDLE hService,
|
|
_In_ DWORD dwNotifyMask,
|
|
_In_ PSERVICE_NOTIFYW pNotifyBuffer
|
|
);
|
|
|
|
typedef struct _PHP_SERVICE_NAME_ENTRY
|
|
{
|
|
PH_HASH_ENTRY HashEntry;
|
|
PH_STRINGREF Name;
|
|
ENUM_SERVICE_STATUS_PROCESS *ServiceEntry;
|
|
} PHP_SERVICE_NAME_ENTRY, *PPHP_SERVICE_NAME_ENTRY;
|
|
|
|
typedef enum _PHP_SERVICE_NOTIFY_STATE
|
|
{
|
|
SnNone,
|
|
SnAdding,
|
|
SnRemoving,
|
|
SnNotify
|
|
} PHP_SERVICE_NOTIFY_STATE;
|
|
|
|
typedef struct _PHP_SERVICE_NOTIFY_CONTEXT
|
|
{
|
|
LIST_ENTRY ListEntry;
|
|
SC_HANDLE ServiceHandle;
|
|
PPH_STRING ServiceName; // Valid only when adding
|
|
BOOLEAN IsServiceManager;
|
|
PHP_SERVICE_NOTIFY_STATE State;
|
|
SERVICE_NOTIFY Buffer;
|
|
} PHP_SERVICE_NOTIFY_CONTEXT, *PPHP_SERVICE_NOTIFY_CONTEXT;
|
|
|
|
VOID NTAPI PhpServiceItemDeleteProcedure(
|
|
_In_ PVOID Object,
|
|
_In_ ULONG Flags
|
|
);
|
|
|
|
BOOLEAN NTAPI PhpServiceHashtableCompareFunction(
|
|
_In_ PVOID Entry1,
|
|
_In_ PVOID Entry2
|
|
);
|
|
|
|
ULONG NTAPI PhpServiceHashtableHashFunction(
|
|
_In_ PVOID Entry
|
|
);
|
|
|
|
VOID PhpAddProcessItemService(
|
|
_In_ PPH_PROCESS_ITEM ProcessItem,
|
|
_In_ PPH_SERVICE_ITEM ServiceItem
|
|
);
|
|
|
|
VOID PhpRemoveProcessItemService(
|
|
_In_ PPH_PROCESS_ITEM ProcessItem,
|
|
_In_ PPH_SERVICE_ITEM ServiceItem
|
|
);
|
|
|
|
VOID PhpInitializeServiceNonPoll(
|
|
VOID
|
|
);
|
|
|
|
PPH_OBJECT_TYPE PhServiceItemType;
|
|
|
|
PPH_HASHTABLE PhServiceHashtable;
|
|
PH_QUEUED_LOCK PhServiceHashtableLock = PH_QUEUED_LOCK_INIT;
|
|
|
|
PHAPPAPI PH_CALLBACK_DECLARE(PhServiceAddedEvent);
|
|
PHAPPAPI PH_CALLBACK_DECLARE(PhServiceModifiedEvent);
|
|
PHAPPAPI PH_CALLBACK_DECLARE(PhServiceRemovedEvent);
|
|
PHAPPAPI PH_CALLBACK_DECLARE(PhServicesUpdatedEvent);
|
|
|
|
BOOLEAN PhEnableServiceNonPoll = FALSE;
|
|
static BOOLEAN PhpNonPollInitialized = FALSE;
|
|
static BOOLEAN PhpNonPollActive = FALSE;
|
|
static HANDLE PhpNonPollThreadHandle;
|
|
static ULONG PhpNonPollGate;
|
|
static _NotifyServiceStatusChangeW NotifyServiceStatusChangeW_I;
|
|
static HANDLE PhpNonPollEventHandle;
|
|
static PH_QUEUED_LOCK PhpNonPollServiceListLock = PH_QUEUED_LOCK_INIT;
|
|
static LIST_ENTRY PhpNonPollServiceListHead;
|
|
static LIST_ENTRY PhpNonPollServicePendingListHead;
|
|
|
|
BOOLEAN PhServiceProviderInitialization(
|
|
VOID
|
|
)
|
|
{
|
|
PhServiceItemType = PhCreateObjectType(L"ServiceItem", 0, PhpServiceItemDeleteProcedure);
|
|
PhServiceHashtable = PhCreateHashtable(
|
|
sizeof(PPH_SERVICE_ITEM),
|
|
PhpServiceHashtableCompareFunction,
|
|
PhpServiceHashtableHashFunction,
|
|
40
|
|
);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
PPH_SERVICE_ITEM PhCreateServiceItem(
|
|
_In_opt_ LPENUM_SERVICE_STATUS_PROCESS Information
|
|
)
|
|
{
|
|
PPH_SERVICE_ITEM serviceItem;
|
|
|
|
serviceItem = PhCreateObject(
|
|
PhEmGetObjectSize(EmServiceItemType, sizeof(PH_SERVICE_ITEM)),
|
|
PhServiceItemType
|
|
);
|
|
memset(serviceItem, 0, sizeof(PH_SERVICE_ITEM));
|
|
|
|
if (Information)
|
|
{
|
|
serviceItem->Name = PhCreateString(Information->lpServiceName);
|
|
serviceItem->Key = serviceItem->Name->sr;
|
|
serviceItem->DisplayName = PhCreateString(Information->lpDisplayName);
|
|
serviceItem->Type = Information->ServiceStatusProcess.dwServiceType;
|
|
serviceItem->State = Information->ServiceStatusProcess.dwCurrentState;
|
|
serviceItem->ControlsAccepted = Information->ServiceStatusProcess.dwControlsAccepted;
|
|
serviceItem->Flags = Information->ServiceStatusProcess.dwServiceFlags;
|
|
serviceItem->ProcessId = UlongToHandle(Information->ServiceStatusProcess.dwProcessId);
|
|
|
|
if (serviceItem->ProcessId)
|
|
PhPrintUInt32(serviceItem->ProcessIdString, HandleToUlong(serviceItem->ProcessId));
|
|
}
|
|
|
|
PhEmCallObjectOperation(EmServiceItemType, serviceItem, EmObjectCreate);
|
|
|
|
return serviceItem;
|
|
}
|
|
|
|
VOID PhpServiceItemDeleteProcedure(
|
|
_In_ PVOID Object,
|
|
_In_ ULONG Flags
|
|
)
|
|
{
|
|
PPH_SERVICE_ITEM serviceItem = (PPH_SERVICE_ITEM)Object;
|
|
|
|
PhEmCallObjectOperation(EmServiceItemType, serviceItem, EmObjectDelete);
|
|
|
|
if (serviceItem->Name) PhDereferenceObject(serviceItem->Name);
|
|
if (serviceItem->DisplayName) PhDereferenceObject(serviceItem->DisplayName);
|
|
}
|
|
|
|
BOOLEAN PhpServiceHashtableCompareFunction(
|
|
_In_ PVOID Entry1,
|
|
_In_ PVOID Entry2
|
|
)
|
|
{
|
|
PPH_SERVICE_ITEM serviceItem1 = *(PPH_SERVICE_ITEM *)Entry1;
|
|
PPH_SERVICE_ITEM serviceItem2 = *(PPH_SERVICE_ITEM *)Entry2;
|
|
|
|
return PhEqualStringRef(&serviceItem1->Key, &serviceItem2->Key, TRUE);
|
|
}
|
|
|
|
ULONG PhpServiceHashtableHashFunction(
|
|
_In_ PVOID Entry
|
|
)
|
|
{
|
|
PPH_SERVICE_ITEM serviceItem = *(PPH_SERVICE_ITEM *)Entry;
|
|
|
|
return PhHashStringRef(&serviceItem->Key, TRUE);
|
|
}
|
|
|
|
PPH_SERVICE_ITEM PhpLookupServiceItem(
|
|
_In_ PPH_STRINGREF Name
|
|
)
|
|
{
|
|
PH_SERVICE_ITEM lookupServiceItem;
|
|
PPH_SERVICE_ITEM lookupServiceItemPtr = &lookupServiceItem;
|
|
PPH_SERVICE_ITEM *serviceItem;
|
|
|
|
lookupServiceItem.Key = *Name;
|
|
|
|
serviceItem = (PPH_SERVICE_ITEM *)PhFindEntryHashtable(
|
|
PhServiceHashtable,
|
|
&lookupServiceItemPtr
|
|
);
|
|
|
|
if (serviceItem)
|
|
return *serviceItem;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
PPH_SERVICE_ITEM PhReferenceServiceItem(
|
|
_In_ PWSTR Name
|
|
)
|
|
{
|
|
PPH_SERVICE_ITEM serviceItem;
|
|
PH_STRINGREF key;
|
|
|
|
// Construct a temporary service item for the lookup.
|
|
PhInitializeStringRef(&key, Name);
|
|
|
|
PhAcquireQueuedLockShared(&PhServiceHashtableLock);
|
|
|
|
serviceItem = PhpLookupServiceItem(&key);
|
|
|
|
if (serviceItem)
|
|
PhReferenceObject(serviceItem);
|
|
|
|
PhReleaseQueuedLockShared(&PhServiceHashtableLock);
|
|
|
|
return serviceItem;
|
|
}
|
|
|
|
VOID PhMarkNeedsConfigUpdateServiceItem(
|
|
_In_ PPH_SERVICE_ITEM ServiceItem
|
|
)
|
|
{
|
|
ServiceItem->NeedsConfigUpdate = TRUE;
|
|
}
|
|
|
|
VOID PhpRemoveServiceItem(
|
|
_In_ PPH_SERVICE_ITEM ServiceItem
|
|
)
|
|
{
|
|
PhRemoveEntryHashtable(PhServiceHashtable, &ServiceItem);
|
|
PhDereferenceObject(ServiceItem);
|
|
}
|
|
|
|
PH_SERVICE_CHANGE PhGetServiceChange(
|
|
_In_ PPH_SERVICE_MODIFIED_DATA Data
|
|
)
|
|
{
|
|
if (
|
|
(
|
|
Data->OldService.State == SERVICE_STOPPED ||
|
|
Data->OldService.State == SERVICE_START_PENDING
|
|
) &&
|
|
Data->Service->State == SERVICE_RUNNING
|
|
)
|
|
{
|
|
return ServiceStarted;
|
|
}
|
|
|
|
if (
|
|
(
|
|
Data->OldService.State == SERVICE_PAUSED ||
|
|
Data->OldService.State == SERVICE_CONTINUE_PENDING
|
|
) &&
|
|
Data->Service->State == SERVICE_RUNNING
|
|
)
|
|
{
|
|
return ServiceContinued;
|
|
}
|
|
|
|
if (
|
|
(
|
|
Data->OldService.State == SERVICE_RUNNING ||
|
|
Data->OldService.State == SERVICE_PAUSE_PENDING
|
|
) &&
|
|
Data->Service->State == SERVICE_PAUSED
|
|
)
|
|
{
|
|
return ServicePaused;
|
|
}
|
|
|
|
if (
|
|
(
|
|
Data->OldService.State == SERVICE_RUNNING ||
|
|
Data->OldService.State == SERVICE_STOP_PENDING
|
|
) &&
|
|
Data->Service->State == SERVICE_STOPPED
|
|
)
|
|
{
|
|
return ServiceStopped;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
VOID PhUpdateProcessItemServices(
|
|
_In_ PPH_PROCESS_ITEM ProcessItem
|
|
)
|
|
{
|
|
PH_HASHTABLE_ENUM_CONTEXT enumContext;
|
|
PPH_SERVICE_ITEM *serviceItem;
|
|
|
|
// We don't need to lock as long as the service provider
|
|
// never runs concurrently with the process provider. This
|
|
// is currently true.
|
|
|
|
PhBeginEnumHashtable(PhServiceHashtable, &enumContext);
|
|
|
|
while (serviceItem = PhNextEnumHashtable(&enumContext))
|
|
{
|
|
if (
|
|
(*serviceItem)->PendingProcess &&
|
|
(*serviceItem)->ProcessId == ProcessItem->ProcessId
|
|
)
|
|
{
|
|
PhpAddProcessItemService(ProcessItem, *serviceItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
VOID PhpAddProcessItemService(
|
|
_In_ PPH_PROCESS_ITEM ProcessItem,
|
|
_In_ PPH_SERVICE_ITEM ServiceItem
|
|
)
|
|
{
|
|
PhAcquireQueuedLockExclusive(&ProcessItem->ServiceListLock);
|
|
|
|
if (!ProcessItem->ServiceList)
|
|
ProcessItem->ServiceList = PhCreatePointerList(2);
|
|
|
|
if (!PhFindItemPointerList(ProcessItem->ServiceList, ServiceItem))
|
|
{
|
|
PhReferenceObject(ServiceItem);
|
|
PhAddItemPointerList(ProcessItem->ServiceList, ServiceItem);
|
|
}
|
|
|
|
PhReleaseQueuedLockExclusive(&ProcessItem->ServiceListLock);
|
|
|
|
ServiceItem->PendingProcess = FALSE;
|
|
ProcessItem->JustProcessed = 1;
|
|
}
|
|
|
|
VOID PhpRemoveProcessItemService(
|
|
_In_ PPH_PROCESS_ITEM ProcessItem,
|
|
_In_ PPH_SERVICE_ITEM ServiceItem
|
|
)
|
|
{
|
|
HANDLE pointerHandle;
|
|
|
|
if (!ProcessItem->ServiceList)
|
|
return;
|
|
|
|
PhAcquireQueuedLockExclusive(&ProcessItem->ServiceListLock);
|
|
|
|
if (pointerHandle = PhFindItemPointerList(ProcessItem->ServiceList, ServiceItem))
|
|
{
|
|
PhRemoveItemPointerList(ProcessItem->ServiceList, pointerHandle);
|
|
PhDereferenceObject(ServiceItem);
|
|
}
|
|
|
|
PhReleaseQueuedLockExclusive(&ProcessItem->ServiceListLock);
|
|
|
|
ProcessItem->JustProcessed = 1;
|
|
}
|
|
|
|
VOID PhpUpdateServiceItemConfig(
|
|
_In_ SC_HANDLE ScManagerHandle,
|
|
_In_ PPH_SERVICE_ITEM ServiceItem
|
|
)
|
|
{
|
|
SC_HANDLE serviceHandle;
|
|
|
|
serviceHandle = OpenService(ScManagerHandle, ServiceItem->Name->Buffer, SERVICE_QUERY_CONFIG);
|
|
|
|
if (serviceHandle)
|
|
{
|
|
LPQUERY_SERVICE_CONFIG config;
|
|
SERVICE_DELAYED_AUTO_START_INFO delayedAutoStartInfo;
|
|
ULONG returnLength;
|
|
PSERVICE_TRIGGER_INFO triggerInfo;
|
|
|
|
config = PhGetServiceConfig(serviceHandle);
|
|
|
|
if (config)
|
|
{
|
|
ServiceItem->StartType = config->dwStartType;
|
|
ServiceItem->ErrorControl = config->dwErrorControl;
|
|
|
|
PhFree(config);
|
|
}
|
|
|
|
if (QueryServiceConfig2(
|
|
serviceHandle,
|
|
SERVICE_CONFIG_DELAYED_AUTO_START_INFO,
|
|
(BYTE *)&delayedAutoStartInfo,
|
|
sizeof(SERVICE_DELAYED_AUTO_START_INFO),
|
|
&returnLength
|
|
))
|
|
{
|
|
ServiceItem->DelayedStart = delayedAutoStartInfo.fDelayedAutostart;
|
|
}
|
|
else
|
|
{
|
|
ServiceItem->DelayedStart = FALSE;
|
|
}
|
|
|
|
if (triggerInfo = PhQueryServiceVariableSize(serviceHandle, SERVICE_CONFIG_TRIGGER_INFO))
|
|
{
|
|
ServiceItem->HasTriggers = triggerInfo->cTriggers != 0;
|
|
PhFree(triggerInfo);
|
|
}
|
|
else
|
|
{
|
|
ServiceItem->HasTriggers = FALSE;
|
|
}
|
|
|
|
CloseServiceHandle(serviceHandle);
|
|
}
|
|
}
|
|
|
|
static BOOLEAN PhpCompareServiceNameEntry(
|
|
_In_ PPHP_SERVICE_NAME_ENTRY Value1,
|
|
_In_ PPHP_SERVICE_NAME_ENTRY Value2
|
|
)
|
|
{
|
|
return PhEqualStringRef(&Value1->Name, &Value2->Name, TRUE);
|
|
}
|
|
|
|
static ULONG PhpHashServiceNameEntry(
|
|
_In_ PPHP_SERVICE_NAME_ENTRY Value
|
|
)
|
|
{
|
|
return PhHashStringRef(&Value->Name, TRUE);
|
|
}
|
|
|
|
VOID PhServiceProviderUpdate(
|
|
_In_ PVOID Object
|
|
)
|
|
{
|
|
static SC_HANDLE scManagerHandle = NULL;
|
|
static ULONG runCount = 0;
|
|
|
|
static PPH_HASH_ENTRY nameHashSet[256];
|
|
static PPHP_SERVICE_NAME_ENTRY nameEntries = NULL;
|
|
static ULONG nameEntriesCount;
|
|
static ULONG nameEntriesAllocated = 0;
|
|
|
|
LPENUM_SERVICE_STATUS_PROCESS services;
|
|
ULONG numberOfServices;
|
|
ULONG i;
|
|
PPH_HASH_ENTRY hashEntry;
|
|
|
|
// We always execute the first run, and we only initialize non-polling after the first run.
|
|
if (PhEnableServiceNonPoll && runCount != 0)
|
|
{
|
|
if (!PhpNonPollInitialized)
|
|
{
|
|
if (WindowsVersion >= WINDOWS_VISTA)
|
|
{
|
|
PhpInitializeServiceNonPoll();
|
|
}
|
|
|
|
PhpNonPollInitialized = TRUE;
|
|
}
|
|
|
|
if (PhpNonPollActive)
|
|
{
|
|
if (InterlockedExchange(&PhpNonPollGate, 0) == 0)
|
|
{
|
|
// Non-poll gate is closed; skip all processing.
|
|
goto UpdateEnd;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!scManagerHandle)
|
|
{
|
|
scManagerHandle = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);
|
|
|
|
if (!scManagerHandle)
|
|
return;
|
|
}
|
|
|
|
services = PhEnumServices(scManagerHandle, 0, 0, &numberOfServices);
|
|
|
|
if (!services)
|
|
return;
|
|
|
|
// Build a hash set containing the service names.
|
|
|
|
// This has caused a massive decrease in background CPU usage, and
|
|
// is certainly much better than the quadratic-time string comparisons
|
|
// we were doing before (in the "Look for dead services" section).
|
|
|
|
nameEntriesCount = 0;
|
|
|
|
if (nameEntriesAllocated < numberOfServices)
|
|
{
|
|
nameEntriesAllocated = numberOfServices + 32;
|
|
|
|
if (nameEntries) PhFree(nameEntries);
|
|
nameEntries = PhAllocate(sizeof(PHP_SERVICE_NAME_ENTRY) * nameEntriesAllocated);
|
|
}
|
|
|
|
PhInitializeHashSet(nameHashSet, PH_HASH_SET_SIZE(nameHashSet));
|
|
|
|
for (i = 0; i < numberOfServices; i++)
|
|
{
|
|
PPHP_SERVICE_NAME_ENTRY entry;
|
|
|
|
entry = &nameEntries[nameEntriesCount++];
|
|
PhInitializeStringRefLongHint(&entry->Name, services[i].lpServiceName);
|
|
entry->ServiceEntry = &services[i];
|
|
PhAddEntryHashSet(
|
|
nameHashSet,
|
|
PH_HASH_SET_SIZE(nameHashSet),
|
|
&entry->HashEntry,
|
|
PhpHashServiceNameEntry(entry)
|
|
);
|
|
}
|
|
|
|
// Look for dead services.
|
|
{
|
|
PPH_LIST servicesToRemove = NULL;
|
|
PH_HASHTABLE_ENUM_CONTEXT enumContext;
|
|
PPH_SERVICE_ITEM *serviceItem;
|
|
|
|
PhBeginEnumHashtable(PhServiceHashtable, &enumContext);
|
|
|
|
while (serviceItem = PhNextEnumHashtable(&enumContext))
|
|
{
|
|
BOOLEAN found = FALSE;
|
|
PHP_SERVICE_NAME_ENTRY lookupNameEntry;
|
|
|
|
// Check if the service still exists.
|
|
|
|
lookupNameEntry.Name = (*serviceItem)->Name->sr;
|
|
hashEntry = PhFindEntryHashSet(
|
|
nameHashSet,
|
|
PH_HASH_SET_SIZE(nameHashSet),
|
|
PhpHashServiceNameEntry(&lookupNameEntry)
|
|
);
|
|
|
|
for (; hashEntry; hashEntry = hashEntry->Next)
|
|
{
|
|
PPHP_SERVICE_NAME_ENTRY nameEntry;
|
|
|
|
nameEntry = CONTAINING_RECORD(hashEntry, PHP_SERVICE_NAME_ENTRY, HashEntry);
|
|
|
|
if (PhpCompareServiceNameEntry(&lookupNameEntry, nameEntry))
|
|
{
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
// Remove the service from its process.
|
|
if ((*serviceItem)->ProcessId)
|
|
{
|
|
PPH_PROCESS_ITEM processItem;
|
|
|
|
processItem = PhReferenceProcessItem((HANDLE)(*serviceItem)->ProcessId);
|
|
|
|
if (processItem)
|
|
{
|
|
PhpRemoveProcessItemService(processItem, *serviceItem);
|
|
PhDereferenceObject(processItem);
|
|
}
|
|
}
|
|
|
|
// Raise the service removed event.
|
|
PhInvokeCallback(&PhServiceRemovedEvent, *serviceItem);
|
|
|
|
if (!servicesToRemove)
|
|
servicesToRemove = PhCreateList(2);
|
|
|
|
PhAddItemList(servicesToRemove, *serviceItem);
|
|
}
|
|
}
|
|
|
|
if (servicesToRemove)
|
|
{
|
|
PhAcquireQueuedLockExclusive(&PhServiceHashtableLock);
|
|
|
|
for (i = 0; i < servicesToRemove->Count; i++)
|
|
{
|
|
PhpRemoveServiceItem((PPH_SERVICE_ITEM)servicesToRemove->Items[i]);
|
|
}
|
|
|
|
PhReleaseQueuedLockExclusive(&PhServiceHashtableLock);
|
|
PhDereferenceObject(servicesToRemove);
|
|
}
|
|
}
|
|
|
|
// Look for new services and update existing ones.
|
|
for (i = 0; i < PH_HASH_SET_SIZE(nameHashSet); i++)
|
|
{
|
|
for (hashEntry = nameHashSet[i]; hashEntry; hashEntry = hashEntry->Next)
|
|
{
|
|
PPH_SERVICE_ITEM serviceItem;
|
|
PPHP_SERVICE_NAME_ENTRY nameEntry;
|
|
ENUM_SERVICE_STATUS_PROCESS *serviceEntry;
|
|
|
|
nameEntry = CONTAINING_RECORD(hashEntry, PHP_SERVICE_NAME_ENTRY, HashEntry);
|
|
serviceEntry = nameEntry->ServiceEntry;
|
|
serviceItem = PhpLookupServiceItem(&nameEntry->Name);
|
|
|
|
if (!serviceItem)
|
|
{
|
|
// Create the service item and fill in basic information.
|
|
|
|
serviceItem = PhCreateServiceItem(serviceEntry);
|
|
|
|
PhpUpdateServiceItemConfig(scManagerHandle, serviceItem);
|
|
|
|
// Add the service to its process, if appropriate.
|
|
if (
|
|
(
|
|
serviceItem->State == SERVICE_RUNNING ||
|
|
serviceItem->State == SERVICE_PAUSED
|
|
) &&
|
|
serviceItem->ProcessId
|
|
)
|
|
{
|
|
PPH_PROCESS_ITEM processItem;
|
|
|
|
if (processItem = PhReferenceProcessItem(serviceItem->ProcessId))
|
|
{
|
|
PhpAddProcessItemService(processItem, serviceItem);
|
|
PhDereferenceObject(processItem);
|
|
}
|
|
else
|
|
{
|
|
// The process doesn't exist yet (to us). Set the pending
|
|
// flag and when the process is added this will be
|
|
// fixed.
|
|
serviceItem->PendingProcess = TRUE;
|
|
}
|
|
}
|
|
|
|
// Add the service item to the hashtable.
|
|
PhAcquireQueuedLockExclusive(&PhServiceHashtableLock);
|
|
PhAddEntryHashtable(PhServiceHashtable, &serviceItem);
|
|
PhReleaseQueuedLockExclusive(&PhServiceHashtableLock);
|
|
|
|
// Raise the service added event.
|
|
PhInvokeCallback(&PhServiceAddedEvent, serviceItem);
|
|
}
|
|
else
|
|
{
|
|
if (
|
|
serviceItem->Type != serviceEntry->ServiceStatusProcess.dwServiceType ||
|
|
serviceItem->State != serviceEntry->ServiceStatusProcess.dwCurrentState ||
|
|
serviceItem->ControlsAccepted != serviceEntry->ServiceStatusProcess.dwControlsAccepted ||
|
|
serviceItem->ProcessId != UlongToHandle(serviceEntry->ServiceStatusProcess.dwProcessId) ||
|
|
serviceItem->NeedsConfigUpdate
|
|
)
|
|
{
|
|
PH_SERVICE_MODIFIED_DATA serviceModifiedData;
|
|
PH_SERVICE_CHANGE serviceChange;
|
|
|
|
// The service has been "modified".
|
|
|
|
serviceModifiedData.Service = serviceItem;
|
|
memset(&serviceModifiedData.OldService, 0, sizeof(PH_SERVICE_ITEM));
|
|
serviceModifiedData.OldService.Type = serviceItem->Type;
|
|
serviceModifiedData.OldService.State = serviceItem->State;
|
|
serviceModifiedData.OldService.ControlsAccepted = serviceItem->ControlsAccepted;
|
|
serviceModifiedData.OldService.ProcessId = serviceItem->ProcessId;
|
|
|
|
// Update the service item.
|
|
serviceItem->Type = serviceEntry->ServiceStatusProcess.dwServiceType;
|
|
serviceItem->State = serviceEntry->ServiceStatusProcess.dwCurrentState;
|
|
serviceItem->ControlsAccepted = serviceEntry->ServiceStatusProcess.dwControlsAccepted;
|
|
serviceItem->ProcessId = UlongToHandle(serviceEntry->ServiceStatusProcess.dwProcessId);
|
|
|
|
if (serviceItem->ProcessId)
|
|
PhPrintUInt32(serviceItem->ProcessIdString, HandleToUlong(serviceItem->ProcessId));
|
|
else
|
|
serviceItem->ProcessIdString[0] = 0;
|
|
|
|
// Add/remove the service from its process.
|
|
|
|
serviceChange = PhGetServiceChange(&serviceModifiedData);
|
|
|
|
if (
|
|
(serviceChange == ServiceStarted && serviceItem->ProcessId) ||
|
|
(serviceChange == ServiceStopped && serviceModifiedData.OldService.ProcessId)
|
|
)
|
|
{
|
|
PPH_PROCESS_ITEM processItem;
|
|
|
|
if (serviceChange == ServiceStarted)
|
|
processItem = PhReferenceProcessItem(serviceItem->ProcessId);
|
|
else
|
|
processItem = PhReferenceProcessItem(serviceModifiedData.OldService.ProcessId);
|
|
|
|
if (processItem)
|
|
{
|
|
if (serviceChange == ServiceStarted)
|
|
PhpAddProcessItemService(processItem, serviceItem);
|
|
else
|
|
PhpRemoveProcessItemService(processItem, serviceItem);
|
|
|
|
PhDereferenceObject(processItem);
|
|
}
|
|
else
|
|
{
|
|
if (serviceChange == ServiceStarted)
|
|
serviceItem->PendingProcess = TRUE;
|
|
else
|
|
serviceItem->PendingProcess = FALSE;
|
|
}
|
|
}
|
|
else if (
|
|
serviceItem->State == SERVICE_RUNNING &&
|
|
serviceItem->ProcessId != serviceModifiedData.OldService.ProcessId &&
|
|
serviceItem->ProcessId
|
|
)
|
|
{
|
|
PPH_PROCESS_ITEM processItem;
|
|
|
|
// The service stopped and started, and the only change we have detected
|
|
// is in the process ID.
|
|
|
|
if (processItem = PhReferenceProcessItem(serviceModifiedData.OldService.ProcessId))
|
|
{
|
|
PhpRemoveProcessItemService(processItem, serviceItem);
|
|
PhDereferenceObject(processItem);
|
|
}
|
|
|
|
if (processItem = PhReferenceProcessItem(serviceItem->ProcessId))
|
|
{
|
|
PhpAddProcessItemService(processItem, serviceItem);
|
|
PhDereferenceObject(processItem);
|
|
}
|
|
else
|
|
{
|
|
serviceItem->PendingProcess = TRUE;
|
|
}
|
|
}
|
|
|
|
// Do a config update if necessary.
|
|
if (serviceItem->NeedsConfigUpdate)
|
|
{
|
|
PhpUpdateServiceItemConfig(scManagerHandle, serviceItem);
|
|
serviceItem->NeedsConfigUpdate = FALSE;
|
|
}
|
|
|
|
// Raise the service modified event.
|
|
PhInvokeCallback(&PhServiceModifiedEvent, &serviceModifiedData);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PhFree(services);
|
|
|
|
UpdateEnd:
|
|
PhInvokeCallback(&PhServicesUpdatedEvent, NULL);
|
|
runCount++;
|
|
}
|
|
|
|
VOID CALLBACK PhpServiceNonPollScNotifyCallback(
|
|
_In_ PVOID pParameter
|
|
)
|
|
{
|
|
PSERVICE_NOTIFYW notifyBuffer = pParameter;
|
|
PPHP_SERVICE_NOTIFY_CONTEXT notifyContext = notifyBuffer->pContext;
|
|
|
|
if (notifyBuffer->dwNotificationStatus == ERROR_SUCCESS)
|
|
{
|
|
if ((notifyBuffer->dwNotificationTriggered & (SERVICE_NOTIFY_CREATED | SERVICE_NOTIFY_DELETED)) &&
|
|
notifyBuffer->pszServiceNames)
|
|
{
|
|
PWSTR name;
|
|
SIZE_T nameLength;
|
|
|
|
name = notifyBuffer->pszServiceNames;
|
|
|
|
while (TRUE)
|
|
{
|
|
nameLength = PhCountStringZ(name);
|
|
|
|
if (nameLength == 0)
|
|
break;
|
|
|
|
if (name[0] == '/')
|
|
{
|
|
PPHP_SERVICE_NOTIFY_CONTEXT newNotifyContext;
|
|
|
|
// Service creation
|
|
newNotifyContext = PhAllocate(sizeof(PHP_SERVICE_NOTIFY_CONTEXT));
|
|
memset(newNotifyContext, 0, sizeof(PHP_SERVICE_NOTIFY_CONTEXT));
|
|
newNotifyContext->State = SnAdding;
|
|
newNotifyContext->ServiceName = PhCreateString(name + 1);
|
|
InsertTailList(&PhpNonPollServicePendingListHead, &newNotifyContext->ListEntry);
|
|
}
|
|
|
|
name += nameLength + 1;
|
|
}
|
|
|
|
LocalFree(notifyBuffer->pszServiceNames);
|
|
}
|
|
|
|
notifyContext->State = SnNotify;
|
|
RemoveEntryList(¬ifyContext->ListEntry);
|
|
InsertTailList(&PhpNonPollServicePendingListHead, ¬ifyContext->ListEntry);
|
|
}
|
|
else if (notifyBuffer->dwNotificationStatus == ERROR_SERVICE_MARKED_FOR_DELETE)
|
|
{
|
|
if (!notifyContext->IsServiceManager)
|
|
{
|
|
notifyContext->State = SnRemoving;
|
|
RemoveEntryList(¬ifyContext->ListEntry);
|
|
InsertTailList(&PhpNonPollServicePendingListHead, ¬ifyContext->ListEntry);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
notifyContext->State = SnNotify;
|
|
RemoveEntryList(¬ifyContext->ListEntry);
|
|
InsertTailList(&PhpNonPollServicePendingListHead, ¬ifyContext->ListEntry);
|
|
}
|
|
|
|
PhpNonPollGate = 1;
|
|
NtSetEvent(PhpNonPollEventHandle, NULL);
|
|
}
|
|
|
|
VOID PhpDestroyServiceNotifyContext(
|
|
_In_ PPHP_SERVICE_NOTIFY_CONTEXT NotifyContext
|
|
)
|
|
{
|
|
if (NotifyContext->Buffer.pszServiceNames)
|
|
LocalFree(NotifyContext->Buffer.pszServiceNames);
|
|
|
|
CloseServiceHandle(NotifyContext->ServiceHandle);
|
|
PhClearReference(&NotifyContext->ServiceName);
|
|
PhFree(NotifyContext);
|
|
}
|
|
|
|
NTSTATUS PhpServiceNonPollThreadStart(
|
|
_In_ PVOID Parameter
|
|
)
|
|
{
|
|
ULONG result;
|
|
SC_HANDLE scManagerHandle;
|
|
LPENUM_SERVICE_STATUS_PROCESS services;
|
|
ULONG numberOfServices;
|
|
ULONG i;
|
|
PLIST_ENTRY listEntry;
|
|
PPHP_SERVICE_NOTIFY_CONTEXT notifyContext;
|
|
|
|
if (!NT_SUCCESS(NtCreateEvent(&PhpNonPollEventHandle, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE)))
|
|
{
|
|
PhpNonPollActive = FALSE;
|
|
PhpNonPollGate = 1;
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
while (TRUE)
|
|
{
|
|
scManagerHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
|
|
|
|
if (!scManagerHandle)
|
|
goto ErrorExit;
|
|
|
|
InitializeListHead(&PhpNonPollServiceListHead);
|
|
InitializeListHead(&PhpNonPollServicePendingListHead);
|
|
|
|
if (!(services = PhEnumServices(scManagerHandle, 0, 0, &numberOfServices)))
|
|
goto ErrorExit;
|
|
|
|
for (i = 0; i < numberOfServices; i++)
|
|
{
|
|
SC_HANDLE serviceHandle;
|
|
|
|
if (serviceHandle = OpenService(scManagerHandle, services[i].lpServiceName, SERVICE_QUERY_STATUS))
|
|
{
|
|
notifyContext = PhAllocate(sizeof(PHP_SERVICE_NOTIFY_CONTEXT));
|
|
memset(notifyContext, 0, sizeof(PHP_SERVICE_NOTIFY_CONTEXT));
|
|
notifyContext->ServiceHandle = serviceHandle;
|
|
notifyContext->State = SnNotify;
|
|
InsertTailList(&PhpNonPollServicePendingListHead, ¬ifyContext->ListEntry);
|
|
}
|
|
}
|
|
|
|
PhFree(services);
|
|
|
|
notifyContext = PhAllocate(sizeof(PHP_SERVICE_NOTIFY_CONTEXT));
|
|
memset(notifyContext, 0, sizeof(PHP_SERVICE_NOTIFY_CONTEXT));
|
|
notifyContext->ServiceHandle = scManagerHandle;
|
|
notifyContext->IsServiceManager = TRUE;
|
|
notifyContext->State = SnNotify;
|
|
InsertTailList(&PhpNonPollServicePendingListHead, ¬ifyContext->ListEntry);
|
|
|
|
while (TRUE)
|
|
{
|
|
BOOLEAN lagging = FALSE;
|
|
|
|
listEntry = PhpNonPollServicePendingListHead.Flink;
|
|
|
|
while (listEntry != &PhpNonPollServicePendingListHead)
|
|
{
|
|
notifyContext = CONTAINING_RECORD(listEntry, PHP_SERVICE_NOTIFY_CONTEXT, ListEntry);
|
|
listEntry = listEntry->Flink;
|
|
|
|
switch (notifyContext->State)
|
|
{
|
|
case SnNone:
|
|
break;
|
|
case SnAdding:
|
|
notifyContext->ServiceHandle =
|
|
OpenService(scManagerHandle, notifyContext->ServiceName->Buffer, SERVICE_QUERY_STATUS);
|
|
|
|
if (!notifyContext->ServiceHandle)
|
|
{
|
|
RemoveEntryList(¬ifyContext->ListEntry);
|
|
PhpDestroyServiceNotifyContext(notifyContext);
|
|
continue;
|
|
}
|
|
|
|
PhClearReference(¬ifyContext->ServiceName);
|
|
notifyContext->State = SnNotify;
|
|
goto NotifyCase;
|
|
case SnRemoving:
|
|
RemoveEntryList(¬ifyContext->ListEntry);
|
|
PhpDestroyServiceNotifyContext(notifyContext);
|
|
break;
|
|
case SnNotify:
|
|
NotifyCase:
|
|
memset(¬ifyContext->Buffer, 0, sizeof(SERVICE_NOTIFY));
|
|
notifyContext->Buffer.dwVersion = SERVICE_NOTIFY_STATUS_CHANGE;
|
|
notifyContext->Buffer.pfnNotifyCallback = PhpServiceNonPollScNotifyCallback;
|
|
notifyContext->Buffer.pContext = notifyContext;
|
|
result = NotifyServiceStatusChangeW_I(
|
|
notifyContext->ServiceHandle,
|
|
notifyContext->IsServiceManager
|
|
? (SERVICE_NOTIFY_CREATED | SERVICE_NOTIFY_DELETED)
|
|
: (SERVICE_NOTIFY_STOPPED | SERVICE_NOTIFY_START_PENDING | SERVICE_NOTIFY_STOP_PENDING |
|
|
SERVICE_NOTIFY_RUNNING | SERVICE_NOTIFY_CONTINUE_PENDING | SERVICE_NOTIFY_PAUSE_PENDING |
|
|
SERVICE_NOTIFY_PAUSED | SERVICE_NOTIFY_DELETE_PENDING),
|
|
¬ifyContext->Buffer
|
|
);
|
|
|
|
switch (result)
|
|
{
|
|
case ERROR_SUCCESS:
|
|
notifyContext->State = SnNone;
|
|
RemoveEntryList(¬ifyContext->ListEntry);
|
|
InsertTailList(&PhpNonPollServiceListHead, ¬ifyContext->ListEntry);
|
|
break;
|
|
case ERROR_SERVICE_NOTIFY_CLIENT_LAGGING:
|
|
// We are lagging behind. Re-open the handle to the SCM as soon as possible.
|
|
lagging = TRUE;
|
|
break;
|
|
case ERROR_SERVICE_MARKED_FOR_DELETE:
|
|
default:
|
|
RemoveEntryList(¬ifyContext->ListEntry);
|
|
PhpDestroyServiceNotifyContext(notifyContext);
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
while (NtWaitForSingleObject(PhpNonPollEventHandle, TRUE, NULL) != STATUS_WAIT_0)
|
|
NOTHING;
|
|
|
|
if (lagging)
|
|
break;
|
|
}
|
|
|
|
// Execute all pending callbacks.
|
|
NtTestAlert();
|
|
|
|
listEntry = PhpNonPollServiceListHead.Flink;
|
|
|
|
while (listEntry != &PhpNonPollServiceListHead)
|
|
{
|
|
notifyContext = CONTAINING_RECORD(listEntry, PHP_SERVICE_NOTIFY_CONTEXT, ListEntry);
|
|
listEntry = listEntry->Flink;
|
|
PhpDestroyServiceNotifyContext(notifyContext);
|
|
}
|
|
|
|
listEntry = PhpNonPollServicePendingListHead.Flink;
|
|
|
|
while (listEntry != &PhpNonPollServicePendingListHead)
|
|
{
|
|
notifyContext = CONTAINING_RECORD(listEntry, PHP_SERVICE_NOTIFY_CONTEXT, ListEntry);
|
|
listEntry = listEntry->Flink;
|
|
PhpDestroyServiceNotifyContext(notifyContext);
|
|
}
|
|
|
|
CloseServiceHandle(scManagerHandle);
|
|
}
|
|
|
|
NtClose(PhpNonPollEventHandle);
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
ErrorExit:
|
|
PhpNonPollActive = FALSE;
|
|
PhpNonPollGate = 1;
|
|
NtClose(PhpNonPollEventHandle);
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
VOID PhpInitializeServiceNonPoll(
|
|
VOID
|
|
)
|
|
{
|
|
// Dynamically import the required functions.
|
|
|
|
NotifyServiceStatusChangeW_I = PhGetModuleProcAddress(L"advapi32.dll", "NotifyServiceStatusChangeW");
|
|
|
|
if (!NotifyServiceStatusChangeW_I)
|
|
return;
|
|
|
|
PhpNonPollActive = TRUE;
|
|
PhpNonPollGate = 1; // initially the gate should be open since we only just initialized everything
|
|
|
|
PhpNonPollThreadHandle = PhCreateThread(0, PhpServiceNonPollThreadStart, NULL);
|
|
|
|
if (!PhpNonPollThreadHandle)
|
|
{
|
|
PhpNonPollActive = FALSE;
|
|
return;
|
|
}
|
|
}
|