Process Hacker
provider.c
Go to the documentation of this file.
1 /*
2  * Process Hacker -
3  * provider system
4  *
5  * Copyright (C) 2009-2012 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 /*
24  * Provider objects allow a function to be executed periodically.
25  * This is managed by a synchronization timer object which is
26  * signaled periodically. The use of a timer object as opposed to
27  * a simple sleep call means that the length of time a provider
28  * function takes to execute has no effect on the interval between
29  * runs.
30  *
31  * In contrast to callback objects, the context passed to provider
32  * functions must be reference-counted objects. This means that
33  * it is not guaranteed that the function will not be in execution
34  * after the unregister operation is complete. However, the
35  * since the context object is reference-counted, there are no
36  * safety issues.
37  *
38  * Providers can be boosted, which causes them to be run immediately
39  * ignoring the interval. This is separate to the periodic runs,
40  * and does not cause the next periodic run to be missed. Providers, even
41  * when boosted, always run on the same provider thread. The other option
42  * would be to have the boosting thread run the provider function
43  * directly, which would involve unnecessary blocking and synchronization.
44  */
45 
46 #include <ph.h>
47 
48 #ifdef DEBUG
49 PPH_LIST PhDbgProviderList;
50 PH_QUEUED_LOCK PhDbgProviderListLock = PH_QUEUED_LOCK_INIT;
51 #endif
52 
60  _Out_ PPH_PROVIDER_THREAD ProviderThread,
61  _In_ ULONG Interval
62  )
63 {
64  ProviderThread->ThreadHandle = NULL;
65  ProviderThread->TimerHandle = NULL;
66  ProviderThread->Interval = Interval;
67  ProviderThread->State = ProviderThreadStopped;
68 
69  PhInitializeQueuedLock(&ProviderThread->Lock);
70  InitializeListHead(&ProviderThread->ListHead);
71  ProviderThread->BoostCount = 0;
72 
73 #ifdef DEBUG
74  PhAcquireQueuedLockExclusive(&PhDbgProviderListLock);
75  if (!PhDbgProviderList)
76  PhDbgProviderList = PhCreateList(4);
77  PhAddItemList(PhDbgProviderList, ProviderThread);
78  PhReleaseQueuedLockExclusive(&PhDbgProviderListLock);
79 #endif
80 }
81 
88  _Inout_ PPH_PROVIDER_THREAD ProviderThread
89  )
90 {
91 #ifdef DEBUG
92  ULONG index;
93 #endif
94  // Nothing
95 
96 #ifdef DEBUG
97  PhAcquireQueuedLockExclusive(&PhDbgProviderListLock);
98  if ((index = PhFindItemList(PhDbgProviderList, ProviderThread)) != -1)
99  PhRemoveItemList(PhDbgProviderList, index);
100  PhReleaseQueuedLockExclusive(&PhDbgProviderListLock);
101 #endif
102 }
103 
104 NTSTATUS NTAPI PhpProviderThreadStart(
105  _In_ PVOID Parameter
106  )
107 {
108  PPH_PROVIDER_THREAD providerThread = (PPH_PROVIDER_THREAD)Parameter;
109  NTSTATUS status = STATUS_SUCCESS;
110  PLIST_ENTRY listEntry;
111  PPH_PROVIDER_REGISTRATION registration;
112  PPH_PROVIDER_FUNCTION providerFunction;
113  PVOID object;
114  LIST_ENTRY tempListHead;
115 
116  while (providerThread->State != ProviderThreadStopping)
117  {
118  // Keep removing and executing providers from the list
119  // until there are no more. Each removed provider will
120  // be placed on the temporary list.
121  // After this is done, all providers on the temporary
122  // list will be re-added to the list again.
123  //
124  // The key to this working safely with the other functions
125  // (boost, register, unregister) is that at all times
126  // when the mutex is not acquired every single provider
127  // must be in a list (main list or the temp list).
128 
129  InitializeListHead(&tempListHead);
130 
131  PhAcquireQueuedLockExclusive(&providerThread->Lock);
132 
133  // Main loop.
134 
135  // We check the status variable for STATUS_ALERTED, which
136  // means that someone is requesting that a provider be
137  // boosted. Note that if they alert this thread while we
138  // are not waiting on the timer, when we do perform the
139  // wait it will return immediately with STATUS_ALERTED.
140 
141  while (TRUE)
142  {
143  if (status == STATUS_ALERTED)
144  {
145  // Check if we have any more providers to boost.
146  // Note that this always works because boosted
147  // providers are always in front of normal providers.
148  // Therefore we will never mistakenly boost normal
149  // providers.
150 
151  if (providerThread->BoostCount == 0)
152  break;
153  }
154 
155  listEntry = RemoveHeadList(&providerThread->ListHead);
156 
157  if (listEntry == &providerThread->ListHead)
158  break;
159 
160  registration = CONTAINING_RECORD(listEntry, PH_PROVIDER_REGISTRATION, ListEntry);
161 
162  // Add the provider to the temp list.
163  InsertTailList(&tempListHead, listEntry);
164 
165  if (status != STATUS_ALERTED)
166  {
167  if (!registration->Enabled || registration->Unregistering)
168  continue;
169  }
170  else
171  {
172  // If we're boosting providers, we don't care if they
173  // are enabled or not. However, we have to make sure
174  // any providers which are being unregistered get a
175  // chance to fix the boost count.
176 
177  if (registration->Unregistering)
178  {
179  PhReleaseQueuedLockExclusive(&providerThread->Lock);
180  PhAcquireQueuedLockExclusive(&providerThread->Lock);
181 
182  continue;
183  }
184  }
185 
186  if (status == STATUS_ALERTED)
187  {
188  assert(registration->Boosting);
189  registration->Boosting = FALSE;
190  providerThread->BoostCount--;
191  }
192 
193  providerFunction = registration->Function;
194  object = registration->Object;
195 
196  if (object)
197  PhReferenceObject(object);
198 
199  registration->RunId++;
200 
201  PhReleaseQueuedLockExclusive(&providerThread->Lock);
202  providerFunction(object);
203  PhAcquireQueuedLockExclusive(&providerThread->Lock);
204 
205  if (object)
206  PhDereferenceObject(object);
207  }
208 
209  // Re-add the items in the temp list to the main list.
210 
211  while ((listEntry = RemoveHeadList(&tempListHead)) != &tempListHead)
212  {
213  registration = CONTAINING_RECORD(listEntry, PH_PROVIDER_REGISTRATION, ListEntry);
214 
215  // We must insert boosted providers at the front of the list in order to maintain
216  // the condition that boosted providers are always in front of normal providers.
217  // This occurs when the timer is signaled just before a boosting provider alerts
218  // our thread.
219  if (!registration->Boosting)
220  InsertTailList(&providerThread->ListHead, listEntry);
221  else
222  InsertHeadList(&providerThread->ListHead, listEntry);
223  }
224 
225  PhReleaseQueuedLockExclusive(&providerThread->Lock);
226 
227  // Perform an alertable wait so we can be woken up by
228  // someone telling us to boost providers, or to terminate.
229  status = NtWaitForSingleObject(
230  providerThread->TimerHandle,
231  TRUE,
232  NULL
233  );
234  }
235 
236  return STATUS_SUCCESS;
237 }
238 
245  _Inout_ PPH_PROVIDER_THREAD ProviderThread
246  )
247 {
248  if (ProviderThread->State != ProviderThreadStopped)
249  return;
250 
251  // Create and set the timer.
252  NtCreateTimer(&ProviderThread->TimerHandle, TIMER_ALL_ACCESS, NULL, SynchronizationTimer);
253  PhSetIntervalProviderThread(ProviderThread, ProviderThread->Interval);
254 
255  // Create and start the thread.
256  ProviderThread->ThreadHandle = PhCreateThread(
257  0,
259  ProviderThread
260  );
261 
262  ProviderThread->State = ProviderThreadRunning;
263 }
264 
271  _Inout_ PPH_PROVIDER_THREAD ProviderThread
272  )
273 {
274  if (ProviderThread->State != ProviderThreadRunning)
275  return;
276 
277  // Signal to the thread that we are shutting down, and
278  // wait for it to exit.
279  ProviderThread->State = ProviderThreadStopping;
280  NtAlertThread(ProviderThread->ThreadHandle); // wake it up
281  NtWaitForSingleObject(ProviderThread->ThreadHandle, FALSE, NULL);
282 
283  // Free resources.
284  NtClose(ProviderThread->ThreadHandle);
285  NtClose(ProviderThread->TimerHandle);
286  ProviderThread->ThreadHandle = NULL;
287  ProviderThread->TimerHandle = NULL;
288 
289  ProviderThread->State = ProviderThreadStopped;
290 }
291 
299  _Inout_ PPH_PROVIDER_THREAD ProviderThread,
300  _In_ ULONG Interval
301  )
302 {
303  ProviderThread->Interval = Interval;
304 
305  if (ProviderThread->TimerHandle)
306  {
307  LARGE_INTEGER interval;
308 
309  interval.QuadPart = -(LONGLONG)Interval * PH_TIMEOUT_MS;
310  NtSetTimer(ProviderThread->TimerHandle, &interval, NULL, NULL, FALSE, Interval, NULL);
311  }
312 }
313 
329  _Inout_ PPH_PROVIDER_THREAD ProviderThread,
330  _In_ PPH_PROVIDER_FUNCTION Function,
331  _In_opt_ PVOID Object,
332  _Out_ PPH_PROVIDER_REGISTRATION Registration
333  )
334 {
335  Registration->ProviderThread = ProviderThread;
336  Registration->Function = Function;
337  Registration->Object = Object;
338  Registration->RunId = 0;
339  Registration->Enabled = FALSE;
340  Registration->Unregistering = FALSE;
341  Registration->Boosting = FALSE;
342 
343  if (Object)
344  PhReferenceObject(Object);
345 
346  PhAcquireQueuedLockExclusive(&ProviderThread->Lock);
347  InsertTailList(&ProviderThread->ListHead, &Registration->ListEntry);
348  PhReleaseQueuedLockExclusive(&ProviderThread->Lock);
349 }
350 
361  _Inout_ PPH_PROVIDER_REGISTRATION Registration
362  )
363 {
364  PPH_PROVIDER_THREAD providerThread;
365 
366  providerThread = Registration->ProviderThread;
367 
368  Registration->Unregistering = TRUE;
369 
370  // There are two possibilities for removal:
371  // 1. The provider is removed while the thread is
372  // not in the main loop. This is the normal case.
373  // 2. The provider is removed while the thread is
374  // in the main loop. In that case the provider
375  // will be removed from the temp list and so
376  // it won't be re-added to the main list.
377 
378  PhAcquireQueuedLockExclusive(&providerThread->Lock);
379 
380  RemoveEntryList(&Registration->ListEntry);
381 
382  // Fix the boost count.
383  if (Registration->Boosting)
384  providerThread->BoostCount--;
385 
386  // The user-supplied object must be dereferenced
387  // while the mutex is held.
388  if (Registration->Object)
389  PhDereferenceObject(Registration->Object);
390 
391  PhReleaseQueuedLockExclusive(&providerThread->Lock);
392 }
393 
411  _Inout_ PPH_PROVIDER_REGISTRATION Registration,
412  _Out_opt_ PULONG FutureRunId
413  )
414 {
415  PPH_PROVIDER_THREAD providerThread;
416  ULONG futureRunId;
417 
418  if (Registration->Unregistering)
419  return FALSE;
420 
421  providerThread = Registration->ProviderThread;
422 
423  // Simply move to the provider to the front of the list.
424  // This works even if the provider is currently in the temp list.
425 
426  PhAcquireQueuedLockExclusive(&providerThread->Lock);
427 
428  // Abort if the provider is already being boosted or the
429  // provider thread is stopping/stopped.
430  if (Registration->Boosting || providerThread->State != ProviderThreadRunning)
431  {
432  PhReleaseQueuedLockExclusive(&providerThread->Lock);
433  return FALSE;
434  }
435 
436  RemoveEntryList(&Registration->ListEntry);
437  InsertHeadList(&providerThread->ListHead, &Registration->ListEntry);
438 
439  Registration->Boosting = TRUE;
440  providerThread->BoostCount++;
441 
442  futureRunId = Registration->RunId + 1;
443 
444  PhReleaseQueuedLockExclusive(&providerThread->Lock);
445 
446  // Wake up the thread.
447  NtAlertThread(providerThread->ThreadHandle);
448 
449  if (FutureRunId)
450  *FutureRunId = futureRunId;
451 
452  return TRUE;
453 }
454 
462  _In_ PPH_PROVIDER_REGISTRATION Registration
463  )
464 {
465  return Registration->RunId;
466 }
467 
475  _In_ PPH_PROVIDER_REGISTRATION Registration
476  )
477 {
478  return Registration->Enabled;
479 }
480 
490  _Inout_ PPH_PROVIDER_REGISTRATION Registration,
491  _In_ BOOLEAN Enabled
492  )
493 {
494  Registration->Enabled = Enabled;
495 }