Process Hacker
srvprv.c
Go to the documentation of this file.
1 /*
2  * Process Hacker -
3  * service provider
4  *
5  * Copyright (C) 2009-2015 wj32
6  *
7  * This file is part of Process Hacker.
8  *
9  * Process Hacker is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * Process Hacker is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 #include <phapp.h>
24 #include <winevt.h>
25 #include <extmgri.h>
26 
27 typedef DWORD (WINAPI *_NotifyServiceStatusChangeW)(
28  _In_ SC_HANDLE hService,
29  _In_ DWORD dwNotifyMask,
30  _In_ PSERVICE_NOTIFYW pNotifyBuffer
31  );
32 
33 typedef struct _PHP_SERVICE_NAME_ENTRY
34 {
35  PH_HASH_ENTRY HashEntry;
36  PH_STRINGREF Name;
37  ENUM_SERVICE_STATUS_PROCESS *ServiceEntry;
39 
41 {
47 
48 typedef struct _PHP_SERVICE_NOTIFY_CONTEXT
49 {
50  LIST_ENTRY ListEntry;
51  SC_HANDLE ServiceHandle;
52  PPH_STRING ServiceName; // Valid only when adding
53  BOOLEAN IsServiceManager;
55  SERVICE_NOTIFY Buffer;
57 
59  _In_ PVOID Object,
60  _In_ ULONG Flags
61  );
62 
64  _In_ PVOID Entry1,
65  _In_ PVOID Entry2
66  );
67 
69  _In_ PVOID Entry
70  );
71 
73  _In_ PPH_PROCESS_ITEM ProcessItem,
74  _In_ PPH_SERVICE_ITEM ServiceItem
75  );
76 
78  _In_ PPH_PROCESS_ITEM ProcessItem,
79  _In_ PPH_SERVICE_ITEM ServiceItem
80  );
81 
83  VOID
84  );
85 
87 
90 
91 PHAPPAPI PH_CALLBACK_DECLARE(PhServiceAddedEvent);
92 PHAPPAPI PH_CALLBACK_DECLARE(PhServiceModifiedEvent);
93 PHAPPAPI PH_CALLBACK_DECLARE(PhServiceRemovedEvent);
94 PHAPPAPI PH_CALLBACK_DECLARE(PhServicesUpdatedEvent);
95 
97 static BOOLEAN PhpNonPollInitialized = FALSE;
98 static BOOLEAN PhpNonPollActive = FALSE;
99 static HANDLE PhpNonPollThreadHandle;
100 static ULONG PhpNonPollGate;
101 static _NotifyServiceStatusChangeW NotifyServiceStatusChangeW_I;
102 static HANDLE PhpNonPollEventHandle;
103 static PH_QUEUED_LOCK PhpNonPollServiceListLock = PH_QUEUED_LOCK_INIT;
104 static LIST_ENTRY PhpNonPollServiceListHead;
105 static LIST_ENTRY PhpNonPollServicePendingListHead;
106 
108  VOID
109  )
110 {
111  PhServiceItemType = PhCreateObjectType(L"ServiceItem", 0, PhpServiceItemDeleteProcedure);
112  PhServiceHashtable = PhCreateHashtable(
113  sizeof(PPH_SERVICE_ITEM),
116  40
117  );
118 
119  return TRUE;
120 }
121 
123  _In_opt_ LPENUM_SERVICE_STATUS_PROCESS Information
124  )
125 {
126  PPH_SERVICE_ITEM serviceItem;
127 
128  serviceItem = PhCreateObject(
130  PhServiceItemType
131  );
132  memset(serviceItem, 0, sizeof(PH_SERVICE_ITEM));
133 
134  if (Information)
135  {
136  serviceItem->Name = PhCreateString(Information->lpServiceName);
137  serviceItem->Key = serviceItem->Name->sr;
138  serviceItem->DisplayName = PhCreateString(Information->lpDisplayName);
139  serviceItem->Type = Information->ServiceStatusProcess.dwServiceType;
140  serviceItem->State = Information->ServiceStatusProcess.dwCurrentState;
141  serviceItem->ControlsAccepted = Information->ServiceStatusProcess.dwControlsAccepted;
142  serviceItem->Flags = Information->ServiceStatusProcess.dwServiceFlags;
143  serviceItem->ProcessId = (HANDLE)Information->ServiceStatusProcess.dwProcessId;
144 
145  if (serviceItem->ProcessId)
146  PhPrintUInt32(serviceItem->ProcessIdString, (ULONG)serviceItem->ProcessId);
147  }
148 
150 
151  return serviceItem;
152 }
153 
155  _In_ PVOID Object,
156  _In_ ULONG Flags
157  )
158 {
159  PPH_SERVICE_ITEM serviceItem = (PPH_SERVICE_ITEM)Object;
160 
162 
163  if (serviceItem->Name) PhDereferenceObject(serviceItem->Name);
164  if (serviceItem->DisplayName) PhDereferenceObject(serviceItem->DisplayName);
165 }
166 
168  _In_ PVOID Entry1,
169  _In_ PVOID Entry2
170  )
171 {
172  PPH_SERVICE_ITEM serviceItem1 = *(PPH_SERVICE_ITEM *)Entry1;
173  PPH_SERVICE_ITEM serviceItem2 = *(PPH_SERVICE_ITEM *)Entry2;
174 
175  return PhEqualStringRef(&serviceItem1->Key, &serviceItem2->Key, TRUE);
176 }
177 
179  _In_ PVOID Entry
180  )
181 {
182  PPH_SERVICE_ITEM serviceItem = *(PPH_SERVICE_ITEM *)Entry;
183 
184  return PhHashStringRef(&serviceItem->Key, TRUE);
185 }
186 
188  _In_ PPH_STRINGREF Name
189  )
190 {
191  PH_SERVICE_ITEM lookupServiceItem;
192  PPH_SERVICE_ITEM lookupServiceItemPtr = &lookupServiceItem;
193  PPH_SERVICE_ITEM *serviceItem;
194 
195  lookupServiceItem.Key = *Name;
196 
197  serviceItem = (PPH_SERVICE_ITEM *)PhFindEntryHashtable(
198  PhServiceHashtable,
199  &lookupServiceItemPtr
200  );
201 
202  if (serviceItem)
203  return *serviceItem;
204  else
205  return NULL;
206 }
207 
209  _In_ PWSTR Name
210  )
211 {
212  PPH_SERVICE_ITEM serviceItem;
213  PH_STRINGREF key;
214 
215  // Construct a temporary service item for the lookup.
216  PhInitializeStringRef(&key, Name);
217 
218  PhAcquireQueuedLockShared(&PhServiceHashtableLock);
219 
220  serviceItem = PhpLookupServiceItem(&key);
221 
222  if (serviceItem)
223  PhReferenceObject(serviceItem);
224 
225  PhReleaseQueuedLockShared(&PhServiceHashtableLock);
226 
227  return serviceItem;
228 }
229 
231  _In_ PPH_SERVICE_ITEM ServiceItem
232  )
233 {
234  ServiceItem->NeedsConfigUpdate = TRUE;
235 }
236 
238  _In_ PPH_SERVICE_ITEM ServiceItem
239  )
240 {
241  PhRemoveEntryHashtable(PhServiceHashtable, &ServiceItem);
242  PhDereferenceObject(ServiceItem);
243 }
244 
246  _In_ PPH_SERVICE_MODIFIED_DATA Data
247  )
248 {
249  if (
250  (
251  Data->OldService.State == SERVICE_STOPPED ||
252  Data->OldService.State == SERVICE_START_PENDING
253  ) &&
254  Data->Service->State == SERVICE_RUNNING
255  )
256  {
257  return ServiceStarted;
258  }
259 
260  if (
261  (
262  Data->OldService.State == SERVICE_PAUSED ||
263  Data->OldService.State == SERVICE_CONTINUE_PENDING
264  ) &&
265  Data->Service->State == SERVICE_RUNNING
266  )
267  {
268  return ServiceContinued;
269  }
270 
271  if (
272  (
273  Data->OldService.State == SERVICE_RUNNING ||
274  Data->OldService.State == SERVICE_PAUSE_PENDING
275  ) &&
276  Data->Service->State == SERVICE_PAUSED
277  )
278  {
279  return ServicePaused;
280  }
281 
282  if (
283  (
284  Data->OldService.State == SERVICE_RUNNING ||
285  Data->OldService.State == SERVICE_STOP_PENDING
286  ) &&
287  Data->Service->State == SERVICE_STOPPED
288  )
289  {
290  return ServiceStopped;
291  }
292 
293  return -1;
294 }
295 
297  _In_ PPH_PROCESS_ITEM ProcessItem
298  )
299 {
300  PH_HASHTABLE_ENUM_CONTEXT enumContext;
301  PPH_SERVICE_ITEM *serviceItem;
302 
303  // We don't need to lock as long as the service provider
304  // never runs concurrently with the process provider. This
305  // is currently true.
306 
307  PhBeginEnumHashtable(PhServiceHashtable, &enumContext);
308 
309  while (serviceItem = PhNextEnumHashtable(&enumContext))
310  {
311  if (
312  (*serviceItem)->PendingProcess &&
313  (*serviceItem)->ProcessId == ProcessItem->ProcessId
314  )
315  {
316  PhpAddProcessItemService(ProcessItem, *serviceItem);
317  }
318  }
319 }
320 
322  _In_ PPH_PROCESS_ITEM ProcessItem,
323  _In_ PPH_SERVICE_ITEM ServiceItem
324  )
325 {
326  PhAcquireQueuedLockExclusive(&ProcessItem->ServiceListLock);
327 
328  if (!ProcessItem->ServiceList)
329  ProcessItem->ServiceList = PhCreatePointerList(2);
330 
331  if (!PhFindItemPointerList(ProcessItem->ServiceList, ServiceItem))
332  {
333  PhReferenceObject(ServiceItem);
334  PhAddItemPointerList(ProcessItem->ServiceList, ServiceItem);
335  }
336 
337  PhReleaseQueuedLockExclusive(&ProcessItem->ServiceListLock);
338 
339  ServiceItem->PendingProcess = FALSE;
340  ProcessItem->JustProcessed = 1;
341 }
342 
344  _In_ PPH_PROCESS_ITEM ProcessItem,
345  _In_ PPH_SERVICE_ITEM ServiceItem
346  )
347 {
348  HANDLE pointerHandle;
349 
350  if (!ProcessItem->ServiceList)
351  return;
352 
353  PhAcquireQueuedLockExclusive(&ProcessItem->ServiceListLock);
354 
355  if (pointerHandle = PhFindItemPointerList(ProcessItem->ServiceList, ServiceItem))
356  {
357  PhRemoveItemPointerList(ProcessItem->ServiceList, pointerHandle);
358  PhDereferenceObject(ServiceItem);
359  }
360 
361  PhReleaseQueuedLockExclusive(&ProcessItem->ServiceListLock);
362 
363  ProcessItem->JustProcessed = 1;
364 }
365 
367  _In_ SC_HANDLE ScManagerHandle,
368  _In_ PPH_SERVICE_ITEM ServiceItem
369  )
370 {
371  SC_HANDLE serviceHandle;
372 
373  serviceHandle = OpenService(ScManagerHandle, ServiceItem->Name->Buffer, SERVICE_QUERY_CONFIG);
374 
375  if (serviceHandle)
376  {
377  LPQUERY_SERVICE_CONFIG config;
378  SERVICE_DELAYED_AUTO_START_INFO delayedAutoStartInfo;
379  ULONG returnLength;
380  PSERVICE_TRIGGER_INFO triggerInfo;
381 
382  config = PhGetServiceConfig(serviceHandle);
383 
384  if (config)
385  {
386  ServiceItem->StartType = config->dwStartType;
387  ServiceItem->ErrorControl = config->dwErrorControl;
388 
389  PhFree(config);
390  }
391 
392  if (QueryServiceConfig2(
393  serviceHandle,
394  SERVICE_CONFIG_DELAYED_AUTO_START_INFO,
395  (BYTE *)&delayedAutoStartInfo,
396  sizeof(SERVICE_DELAYED_AUTO_START_INFO),
397  &returnLength
398  ))
399  {
400  ServiceItem->DelayedStart = delayedAutoStartInfo.fDelayedAutostart;
401  }
402  else
403  {
404  ServiceItem->DelayedStart = FALSE;
405  }
406 
407  if (triggerInfo = PhQueryServiceVariableSize(serviceHandle, SERVICE_CONFIG_TRIGGER_INFO))
408  {
409  ServiceItem->HasTriggers = triggerInfo->cTriggers != 0;
410  PhFree(triggerInfo);
411  }
412  else
413  {
414  ServiceItem->HasTriggers = FALSE;
415  }
416 
417  CloseServiceHandle(serviceHandle);
418  }
419 }
420 
421 static BOOLEAN PhpCompareServiceNameEntry(
422  _In_ PPHP_SERVICE_NAME_ENTRY Value1,
423  _In_ PPHP_SERVICE_NAME_ENTRY Value2
424  )
425 {
426  return PhEqualStringRef(&Value1->Name, &Value2->Name, TRUE);
427 }
428 
429 static ULONG PhpHashServiceNameEntry(
430  _In_ PPHP_SERVICE_NAME_ENTRY Value
431  )
432 {
433  return PhHashStringRef(&Value->Name, TRUE);
434 }
435 
437  _In_ PVOID Object
438  )
439 {
440  static SC_HANDLE scManagerHandle = NULL;
441  static ULONG runCount = 0;
442 
443  static PPH_HASH_ENTRY nameHashSet[256];
444  static PPHP_SERVICE_NAME_ENTRY nameEntries = NULL;
445  static ULONG nameEntriesCount;
446  static ULONG nameEntriesAllocated = 0;
447 
448  LPENUM_SERVICE_STATUS_PROCESS services;
449  ULONG numberOfServices;
450  ULONG i;
451  PPH_HASH_ENTRY hashEntry;
452 
453  // We always execute the first run, and we only initialize non-polling after the first run.
454  if (PhEnableServiceNonPoll && runCount != 0)
455  {
456  if (!PhpNonPollInitialized)
457  {
459  {
461  }
462 
463  PhpNonPollInitialized = TRUE;
464  }
465 
466  if (PhpNonPollActive)
467  {
468  if (InterlockedExchange(&PhpNonPollGate, 0) == 0)
469  {
470  // Non-poll gate is closed; skip all processing.
471  goto UpdateEnd;
472  }
473  }
474  }
475 
476  if (!scManagerHandle)
477  {
478  scManagerHandle = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);
479 
480  if (!scManagerHandle)
481  return;
482  }
483 
484  services = PhEnumServices(scManagerHandle, 0, 0, &numberOfServices);
485 
486  if (!services)
487  return;
488 
489  // Build a hash set containing the service names.
490 
491  // This has caused a massive decrease in background CPU usage, and
492  // is certainly much better than the quadratic-time string comparisons
493  // we were doing before (in the "Look for dead services" section).
494 
495  nameEntriesCount = 0;
496 
497  if (nameEntriesAllocated < numberOfServices)
498  {
499  nameEntriesAllocated = numberOfServices + 32;
500 
501  if (nameEntries) PhFree(nameEntries);
502  nameEntries = PhAllocate(sizeof(PHP_SERVICE_NAME_ENTRY) * nameEntriesAllocated);
503  }
504 
505  PhInitializeHashSet(nameHashSet, PH_HASH_SET_SIZE(nameHashSet));
506 
507  for (i = 0; i < numberOfServices; i++)
508  {
510 
511  entry = &nameEntries[nameEntriesCount++];
512  PhInitializeStringRefLongHint(&entry->Name, services[i].lpServiceName);
513  entry->ServiceEntry = &services[i];
515  nameHashSet,
516  PH_HASH_SET_SIZE(nameHashSet),
517  &entry->HashEntry,
518  PhpHashServiceNameEntry(entry)
519  );
520  }
521 
522  // Look for dead services.
523  {
524  PPH_LIST servicesToRemove = NULL;
525  PH_HASHTABLE_ENUM_CONTEXT enumContext;
526  PPH_SERVICE_ITEM *serviceItem;
527 
528  PhBeginEnumHashtable(PhServiceHashtable, &enumContext);
529 
530  while (serviceItem = PhNextEnumHashtable(&enumContext))
531  {
532  BOOLEAN found = FALSE;
533  PHP_SERVICE_NAME_ENTRY lookupNameEntry;
534 
535  // Check if the service still exists.
536 
537  lookupNameEntry.Name = (*serviceItem)->Name->sr;
538  hashEntry = PhFindEntryHashSet(
539  nameHashSet,
540  PH_HASH_SET_SIZE(nameHashSet),
541  PhpHashServiceNameEntry(&lookupNameEntry)
542  );
543 
544  for (; hashEntry; hashEntry = hashEntry->Next)
545  {
546  PPHP_SERVICE_NAME_ENTRY nameEntry;
547 
548  nameEntry = CONTAINING_RECORD(hashEntry, PHP_SERVICE_NAME_ENTRY, HashEntry);
549 
550  if (PhpCompareServiceNameEntry(&lookupNameEntry, nameEntry))
551  {
552  found = TRUE;
553  break;
554  }
555  }
556 
557  if (!found)
558  {
559  // Remove the service from its process.
560  if ((*serviceItem)->ProcessId)
561  {
562  PPH_PROCESS_ITEM processItem;
563 
564  processItem = PhReferenceProcessItem((HANDLE)(*serviceItem)->ProcessId);
565 
566  if (processItem)
567  {
568  PhpRemoveProcessItemService(processItem, *serviceItem);
569  PhDereferenceObject(processItem);
570  }
571  }
572 
573  // Raise the service removed event.
574  PhInvokeCallback(&PhServiceRemovedEvent, *serviceItem);
575 
576  if (!servicesToRemove)
577  servicesToRemove = PhCreateList(2);
578 
579  PhAddItemList(servicesToRemove, *serviceItem);
580  }
581  }
582 
583  if (servicesToRemove)
584  {
585  PhAcquireQueuedLockExclusive(&PhServiceHashtableLock);
586 
587  for (i = 0; i < servicesToRemove->Count; i++)
588  {
589  PhpRemoveServiceItem((PPH_SERVICE_ITEM)servicesToRemove->Items[i]);
590  }
591 
592  PhReleaseQueuedLockExclusive(&PhServiceHashtableLock);
593  PhDereferenceObject(servicesToRemove);
594  }
595  }
596 
597  // Look for new services and update existing ones.
598  for (i = 0; i < PH_HASH_SET_SIZE(nameHashSet); i++)
599  {
600  for (hashEntry = nameHashSet[i]; hashEntry; hashEntry = hashEntry->Next)
601  {
602  PPH_SERVICE_ITEM serviceItem;
603  PPHP_SERVICE_NAME_ENTRY nameEntry;
604  ENUM_SERVICE_STATUS_PROCESS *serviceEntry;
605 
606  nameEntry = CONTAINING_RECORD(hashEntry, PHP_SERVICE_NAME_ENTRY, HashEntry);
607  serviceEntry = nameEntry->ServiceEntry;
608  serviceItem = PhpLookupServiceItem(&nameEntry->Name);
609 
610  if (!serviceItem)
611  {
612  // Create the service item and fill in basic information.
613 
614  serviceItem = PhCreateServiceItem(serviceEntry);
615 
616  PhpUpdateServiceItemConfig(scManagerHandle, serviceItem);
617 
618  // Add the service to its process, if appropriate.
619  if (
620  (
621  serviceItem->State == SERVICE_RUNNING ||
622  serviceItem->State == SERVICE_PAUSED
623  ) &&
624  serviceItem->ProcessId
625  )
626  {
627  PPH_PROCESS_ITEM processItem;
628 
629  if (processItem = PhReferenceProcessItem(serviceItem->ProcessId))
630  {
631  PhpAddProcessItemService(processItem, serviceItem);
632  PhDereferenceObject(processItem);
633  }
634  else
635  {
636  // The process doesn't exist yet (to us). Set the pending
637  // flag and when the process is added this will be
638  // fixed.
639  serviceItem->PendingProcess = TRUE;
640  }
641  }
642 
643  // Add the service item to the hashtable.
644  PhAcquireQueuedLockExclusive(&PhServiceHashtableLock);
645  PhAddEntryHashtable(PhServiceHashtable, &serviceItem);
646  PhReleaseQueuedLockExclusive(&PhServiceHashtableLock);
647 
648  // Raise the service added event.
649  PhInvokeCallback(&PhServiceAddedEvent, serviceItem);
650  }
651  else
652  {
653  if (
654  serviceItem->Type != serviceEntry->ServiceStatusProcess.dwServiceType ||
655  serviceItem->State != serviceEntry->ServiceStatusProcess.dwCurrentState ||
656  serviceItem->ControlsAccepted != serviceEntry->ServiceStatusProcess.dwControlsAccepted ||
657  serviceItem->ProcessId != (HANDLE)serviceEntry->ServiceStatusProcess.dwProcessId ||
658  serviceItem->NeedsConfigUpdate
659  )
660  {
661  PH_SERVICE_MODIFIED_DATA serviceModifiedData;
662  PH_SERVICE_CHANGE serviceChange;
663 
664  // The service has been "modified".
665 
666  serviceModifiedData.Service = serviceItem;
667  memset(&serviceModifiedData.OldService, 0, sizeof(PH_SERVICE_ITEM));
668  serviceModifiedData.OldService.Type = serviceItem->Type;
669  serviceModifiedData.OldService.State = serviceItem->State;
670  serviceModifiedData.OldService.ControlsAccepted = serviceItem->ControlsAccepted;
671  serviceModifiedData.OldService.ProcessId = serviceItem->ProcessId;
672 
673  // Update the service item.
674  serviceItem->Type = serviceEntry->ServiceStatusProcess.dwServiceType;
675  serviceItem->State = serviceEntry->ServiceStatusProcess.dwCurrentState;
676  serviceItem->ControlsAccepted = serviceEntry->ServiceStatusProcess.dwControlsAccepted;
677  serviceItem->ProcessId = (HANDLE)serviceEntry->ServiceStatusProcess.dwProcessId;
678 
679  if (serviceItem->ProcessId)
680  PhPrintUInt32(serviceItem->ProcessIdString, (ULONG)serviceItem->ProcessId);
681  else
682  serviceItem->ProcessIdString[0] = 0;
683 
684  // Add/remove the service from its process.
685 
686  serviceChange = PhGetServiceChange(&serviceModifiedData);
687 
688  if (
689  (serviceChange == ServiceStarted && serviceItem->ProcessId) ||
690  (serviceChange == ServiceStopped && serviceModifiedData.OldService.ProcessId)
691  )
692  {
693  PPH_PROCESS_ITEM processItem;
694 
695  if (serviceChange == ServiceStarted)
696  processItem = PhReferenceProcessItem(serviceItem->ProcessId);
697  else
698  processItem = PhReferenceProcessItem(serviceModifiedData.OldService.ProcessId);
699 
700  if (processItem)
701  {
702  if (serviceChange == ServiceStarted)
703  PhpAddProcessItemService(processItem, serviceItem);
704  else
705  PhpRemoveProcessItemService(processItem, serviceItem);
706 
707  PhDereferenceObject(processItem);
708  }
709  else
710  {
711  if (serviceChange == ServiceStarted)
712  serviceItem->PendingProcess = TRUE;
713  else
714  serviceItem->PendingProcess = FALSE;
715  }
716  }
717  else if (
718  serviceItem->State == SERVICE_RUNNING &&
719  serviceItem->ProcessId != serviceModifiedData.OldService.ProcessId &&
720  serviceItem->ProcessId
721  )
722  {
723  PPH_PROCESS_ITEM processItem;
724 
725  // The service stopped and started, and the only change we have detected
726  // is in the process ID.
727 
728  if (processItem = PhReferenceProcessItem(serviceModifiedData.OldService.ProcessId))
729  {
730  PhpRemoveProcessItemService(processItem, serviceItem);
731  PhDereferenceObject(processItem);
732  }
733 
734  if (processItem = PhReferenceProcessItem(serviceItem->ProcessId))
735  {
736  PhpAddProcessItemService(processItem, serviceItem);
737  PhDereferenceObject(processItem);
738  }
739  else
740  {
741  serviceItem->PendingProcess = TRUE;
742  }
743  }
744 
745  // Do a config update if necessary.
746  if (serviceItem->NeedsConfigUpdate)
747  {
748  PhpUpdateServiceItemConfig(scManagerHandle, serviceItem);
749  serviceItem->NeedsConfigUpdate = FALSE;
750  }
751 
752  // Raise the service modified event.
753  PhInvokeCallback(&PhServiceModifiedEvent, &serviceModifiedData);
754  }
755  }
756  }
757  }
758 
759  PhFree(services);
760 
761 UpdateEnd:
763  runCount++;
764 }
765 
767  _In_ PVOID pParameter
768  )
769 {
770  PSERVICE_NOTIFYW notifyBuffer = pParameter;
771  PPHP_SERVICE_NOTIFY_CONTEXT notifyContext = notifyBuffer->pContext;
772 
773  if (notifyBuffer->dwNotificationStatus == ERROR_SUCCESS)
774  {
775  if ((notifyBuffer->dwNotificationTriggered & (SERVICE_NOTIFY_CREATED | SERVICE_NOTIFY_DELETED)) &&
776  notifyBuffer->pszServiceNames)
777  {
778  PWSTR name;
779  SIZE_T nameLength;
780 
781  name = notifyBuffer->pszServiceNames;
782 
783  while (TRUE)
784  {
785  nameLength = PhCountStringZ(name);
786 
787  if (nameLength == 0)
788  break;
789 
790  if (name[0] == '/')
791  {
792  PPHP_SERVICE_NOTIFY_CONTEXT newNotifyContext;
793 
794  // Service creation
795  newNotifyContext = PhAllocate(sizeof(PHP_SERVICE_NOTIFY_CONTEXT));
796  memset(newNotifyContext, 0, sizeof(PHP_SERVICE_NOTIFY_CONTEXT));
797  newNotifyContext->State = SnAdding;
798  newNotifyContext->ServiceName = PhCreateString(name + 1);
799  InsertTailList(&PhpNonPollServicePendingListHead, &newNotifyContext->ListEntry);
800  }
801 
802  name += nameLength + 1;
803  }
804 
805  LocalFree(notifyBuffer->pszServiceNames);
806  }
807 
808  notifyContext->State = SnNotify;
809  RemoveEntryList(&notifyContext->ListEntry);
810  InsertTailList(&PhpNonPollServicePendingListHead, &notifyContext->ListEntry);
811  }
812  else if (notifyBuffer->dwNotificationStatus == ERROR_SERVICE_MARKED_FOR_DELETE)
813  {
814  if (!notifyContext->IsServiceManager)
815  {
816  notifyContext->State = SnRemoving;
817  RemoveEntryList(&notifyContext->ListEntry);
818  InsertTailList(&PhpNonPollServicePendingListHead, &notifyContext->ListEntry);
819  }
820  }
821  else
822  {
823  notifyContext->State = SnNotify;
824  RemoveEntryList(&notifyContext->ListEntry);
825  InsertTailList(&PhpNonPollServicePendingListHead, &notifyContext->ListEntry);
826  }
827 
828  PhpNonPollGate = 1;
829  NtSetEvent(PhpNonPollEventHandle, NULL);
830 }
831 
833  _In_ PPHP_SERVICE_NOTIFY_CONTEXT NotifyContext
834  )
835 {
836  if (NotifyContext->Buffer.pszServiceNames)
837  LocalFree(NotifyContext->Buffer.pszServiceNames);
838 
839  CloseServiceHandle(NotifyContext->ServiceHandle);
840  PhClearReference(&NotifyContext->ServiceName);
841  PhFree(NotifyContext);
842 }
843 
845  _In_ PVOID Parameter
846  )
847 {
848  ULONG result;
849  SC_HANDLE scManagerHandle;
850  LPENUM_SERVICE_STATUS_PROCESS services;
851  ULONG numberOfServices;
852  ULONG i;
853  PLIST_ENTRY listEntry;
854  PPHP_SERVICE_NOTIFY_CONTEXT notifyContext;
855 
856  if (!NT_SUCCESS(NtCreateEvent(&PhpNonPollEventHandle, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE)))
857  {
858  PhpNonPollActive = FALSE;
859  PhpNonPollGate = 1;
860  return STATUS_UNSUCCESSFUL;
861  }
862 
863  while (TRUE)
864  {
865  scManagerHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
866 
867  if (!scManagerHandle)
868  goto ErrorExit;
869 
870  InitializeListHead(&PhpNonPollServiceListHead);
871  InitializeListHead(&PhpNonPollServicePendingListHead);
872 
873  if (!(services = PhEnumServices(scManagerHandle, 0, 0, &numberOfServices)))
874  goto ErrorExit;
875 
876  for (i = 0; i < numberOfServices; i++)
877  {
878  SC_HANDLE serviceHandle;
879 
880  if (serviceHandle = OpenService(scManagerHandle, services[i].lpServiceName, SERVICE_QUERY_STATUS))
881  {
882  notifyContext = PhAllocate(sizeof(PHP_SERVICE_NOTIFY_CONTEXT));
883  memset(notifyContext, 0, sizeof(PHP_SERVICE_NOTIFY_CONTEXT));
884  notifyContext->ServiceHandle = serviceHandle;
885  notifyContext->State = SnNotify;
886  InsertTailList(&PhpNonPollServicePendingListHead, &notifyContext->ListEntry);
887  }
888  }
889 
890  PhFree(services);
891 
892  notifyContext = PhAllocate(sizeof(PHP_SERVICE_NOTIFY_CONTEXT));
893  memset(notifyContext, 0, sizeof(PHP_SERVICE_NOTIFY_CONTEXT));
894  notifyContext->ServiceHandle = scManagerHandle;
895  notifyContext->IsServiceManager = TRUE;
896  notifyContext->State = SnNotify;
897  InsertTailList(&PhpNonPollServicePendingListHead, &notifyContext->ListEntry);
898 
899  while (TRUE)
900  {
901  BOOLEAN lagging = FALSE;
902 
903  listEntry = PhpNonPollServicePendingListHead.Flink;
904 
905  while (listEntry != &PhpNonPollServicePendingListHead)
906  {
907  notifyContext = CONTAINING_RECORD(listEntry, PHP_SERVICE_NOTIFY_CONTEXT, ListEntry);
908  listEntry = listEntry->Flink;
909 
910  switch (notifyContext->State)
911  {
912  case SnNone:
913  break;
914  case SnAdding:
915  notifyContext->ServiceHandle =
916  OpenService(scManagerHandle, notifyContext->ServiceName->Buffer, SERVICE_QUERY_STATUS);
917 
918  if (!notifyContext->ServiceHandle)
919  {
920  RemoveEntryList(&notifyContext->ListEntry);
921  PhpDestroyServiceNotifyContext(notifyContext);
922  continue;
923  }
924 
925  PhClearReference(&notifyContext->ServiceName);
926  notifyContext->State = SnNotify;
927  goto NotifyCase;
928  case SnRemoving:
929  RemoveEntryList(&notifyContext->ListEntry);
930  PhpDestroyServiceNotifyContext(notifyContext);
931  break;
932  case SnNotify:
933 NotifyCase:
934  memset(&notifyContext->Buffer, 0, sizeof(SERVICE_NOTIFY));
935  notifyContext->Buffer.dwVersion = SERVICE_NOTIFY_STATUS_CHANGE;
936  notifyContext->Buffer.pfnNotifyCallback = PhpServiceNonPollScNotifyCallback;
937  notifyContext->Buffer.pContext = notifyContext;
938  result = NotifyServiceStatusChangeW_I(
939  notifyContext->ServiceHandle,
940  notifyContext->IsServiceManager
941  ? (SERVICE_NOTIFY_CREATED | SERVICE_NOTIFY_DELETED)
942  : (SERVICE_NOTIFY_STOPPED | SERVICE_NOTIFY_START_PENDING | SERVICE_NOTIFY_STOP_PENDING |
943  SERVICE_NOTIFY_RUNNING | SERVICE_NOTIFY_CONTINUE_PENDING | SERVICE_NOTIFY_PAUSE_PENDING |
944  SERVICE_NOTIFY_PAUSED | SERVICE_NOTIFY_DELETE_PENDING),
945  &notifyContext->Buffer
946  );
947 
948  switch (result)
949  {
950  case ERROR_SUCCESS:
951  notifyContext->State = SnNone;
952  RemoveEntryList(&notifyContext->ListEntry);
953  InsertTailList(&PhpNonPollServiceListHead, &notifyContext->ListEntry);
954  break;
955  case ERROR_SERVICE_NOTIFY_CLIENT_LAGGING:
956  // We are lagging behind. Re-open the handle to the SCM as soon as possible.
957  lagging = TRUE;
958  break;
959  case ERROR_SERVICE_MARKED_FOR_DELETE:
960  default:
961  RemoveEntryList(&notifyContext->ListEntry);
962  PhpDestroyServiceNotifyContext(notifyContext);
963  break;
964  }
965 
966  break;
967  }
968  }
969 
970  while (NtWaitForSingleObject(PhpNonPollEventHandle, TRUE, NULL) != STATUS_WAIT_0)
971  NOTHING;
972 
973  if (lagging)
974  break;
975  }
976 
977  // Execute all pending callbacks.
978  NtTestAlert();
979 
980  listEntry = PhpNonPollServiceListHead.Flink;
981 
982  while (listEntry != &PhpNonPollServiceListHead)
983  {
984  notifyContext = CONTAINING_RECORD(listEntry, PHP_SERVICE_NOTIFY_CONTEXT, ListEntry);
985  listEntry = listEntry->Flink;
986  PhpDestroyServiceNotifyContext(notifyContext);
987  }
988 
989  listEntry = PhpNonPollServicePendingListHead.Flink;
990 
991  while (listEntry != &PhpNonPollServicePendingListHead)
992  {
993  notifyContext = CONTAINING_RECORD(listEntry, PHP_SERVICE_NOTIFY_CONTEXT, ListEntry);
994  listEntry = listEntry->Flink;
995  PhpDestroyServiceNotifyContext(notifyContext);
996  }
997 
998  CloseServiceHandle(scManagerHandle);
999  }
1000 
1001  NtClose(PhpNonPollEventHandle);
1002 
1003  return STATUS_SUCCESS;
1004 
1005 ErrorExit:
1006  PhpNonPollActive = FALSE;
1007  PhpNonPollGate = 1;
1008  NtClose(PhpNonPollEventHandle);
1009  return STATUS_UNSUCCESSFUL;
1010 }
1011 
1013  VOID
1014  )
1015 {
1016  // Dynamically import the required functions.
1017 
1018  NotifyServiceStatusChangeW_I = PhGetModuleProcAddress(L"advapi32.dll", "NotifyServiceStatusChangeW");
1019 
1020  if (!NotifyServiceStatusChangeW_I)
1021  return;
1022 
1023  PhpNonPollActive = TRUE;
1024  PhpNonPollGate = 1; // initially the gate should be open since we only just initialized everything
1025 
1026  PhpNonPollThreadHandle = PhCreateThread(0, PhpServiceNonPollThreadStart, NULL);
1027 
1028  if (!PhpNonPollThreadHandle)
1029  {
1030  PhpNonPollActive = FALSE;
1031  return;
1032  }
1033 }