aboutsummaryrefslogtreecommitdiff
path: root/src/dutil/monutil.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/dutil/monutil.cpp')
-rw-r--r--src/dutil/monutil.cpp2004
1 files changed, 2004 insertions, 0 deletions
diff --git a/src/dutil/monutil.cpp b/src/dutil/monutil.cpp
new file mode 100644
index 00000000..6f280538
--- /dev/null
+++ b/src/dutil/monutil.cpp
@@ -0,0 +1,2004 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5const int MON_THREAD_GROWTH = 5;
6const int MON_ARRAY_GROWTH = 40;
7const int MON_MAX_MONITORS_PER_THREAD = 63;
8const int MON_THREAD_INIT_RETRIES = 1000;
9const int MON_THREAD_INIT_RETRY_PERIOD_IN_MS = 10;
10const int MON_THREAD_NETWORK_FAIL_RETRY_IN_MS = 1000*60; // if we know we failed to connect, retry every minute
11const int MON_THREAD_NETWORK_SUCCESSFUL_RETRY_IN_MS = 1000*60*20; // if we're just checking for remote servers dieing, check much less frequently
12const int MON_THREAD_WAIT_REMOVE_DEVICE = 5000;
13const LPCWSTR MONUTIL_WINDOW_CLASS = L"MonUtilClass";
14
15enum MON_MESSAGE
16{
17 MON_MESSAGE_ADD = WM_APP + 1,
18 MON_MESSAGE_REMOVE,
19 MON_MESSAGE_REMOVED, // Sent by waiter thread back to coordinator thread to indicate a remove occurred
20 MON_MESSAGE_NETWORK_WAIT_FAILED, // Sent by waiter thread back to coordinator thread to indicate a network wait failed. Coordinator thread will periodically trigger retries (via MON_MESSAGE_NETWORK_STATUS_UPDATE messages).
21 MON_MESSAGE_NETWORK_WAIT_SUCCEEDED, // Sent by waiter thread back to coordinator thread to indicate a previously failing network wait is now succeeding. Coordinator thread will stop triggering retries if no other failing waits exist.
22 MON_MESSAGE_NETWORK_STATUS_UPDATE, // Some change to network connectivity occurred (a network connection was connected or disconnected for example)
23 MON_MESSAGE_NETWORK_RETRY_SUCCESSFUL_NETWORK_WAITS, // Coordinator thread is telling waiters to retry any successful network waits.
24 // Annoyingly, this is necessary to catch the rare case that the remote server goes offline unexpectedly, such as by
25 // network cable unplugged or power loss - in this case there is no local network status change, and the wait will just never fire.
26 // So we very occasionally retry all successful network waits. When this occurs, we notify for changes, even though there may not have been any.
27 // This is because we have no way to detect if the old wait had failed (and changes were lost) due to the remote server going offline during that time or not.
28 // If we do this often, it can cause a lot of wasted work (which could be expensive for battery life), so the default is to do it very rarely (every 20 minutes).
29 MON_MESSAGE_NETWORK_RETRY_FAILED_NETWORK_WAITS, // Coordinator thread is telling waiters to retry any failed network waits
30 MON_MESSAGE_DRIVE_STATUS_UPDATE, // Some change to local drive has occurred (new drive created or plugged in, or removed)
31 MON_MESSAGE_DRIVE_QUERY_REMOVE, // User wants to unplug a drive, which MonUtil will always allow
32 MON_MESSAGE_STOP
33};
34
35enum MON_TYPE
36{
37 MON_NONE = 0,
38 MON_DIRECTORY = 1,
39 MON_REGKEY = 2
40};
41
42struct MON_REQUEST
43{
44 MON_TYPE type;
45 DWORD dwMaxSilencePeriodInMs;
46
47 // Handle to the main window for RegisterDeviceNotification() (same handle as owned by coordinator thread)
48 HWND hwnd;
49 // and handle to the notification (specific to this request)
50 HDEVNOTIFY hNotify;
51
52 BOOL fRecursive;
53 void *pvContext;
54
55 HRESULT hrStatus;
56
57 LPWSTR sczOriginalPathRequest;
58 BOOL fNetwork; // This reflects either a UNC or mounted drive original request
59 DWORD dwPathHierarchyIndex;
60 LPWSTR *rgsczPathHierarchy;
61 DWORD cPathHierarchy;
62
63 // If the notify fires, fPendingFire gets set to TRUE, and we wait to see if other writes are occurring, and only after the configured silence period do we notify of changes
64 // after notification, we set fPendingFire back to FALSE
65 BOOL fPendingFire;
66 BOOL fSkipDeltaAdd;
67 DWORD dwSilencePeriodInMs;
68
69 union
70 {
71 struct
72 {
73 } directory;
74 struct
75 {
76 HKEY hkRoot;
77 HKEY hkSubKey;
78 REG_KEY_BITNESS kbKeyBitness; // Only used to pass on 32-bit, 64-bit, or default parameter
79 } regkey;
80 };
81};
82
83struct MON_ADD_MESSAGE
84{
85 MON_REQUEST request;
86 HANDLE handle;
87};
88
89struct MON_REMOVE_MESSAGE
90{
91 MON_TYPE type;
92 BOOL fRecursive;
93
94 union
95 {
96 struct
97 {
98 LPWSTR sczDirectory;
99 } directory;
100 struct
101 {
102 HKEY hkRoot;
103 LPWSTR sczSubKey;
104 REG_KEY_BITNESS kbKeyBitness;
105 } regkey;
106 };
107};
108
109struct MON_WAITER_CONTEXT
110{
111 DWORD dwCoordinatorThreadId;
112
113 HANDLE hWaiterThread;
114 DWORD dwWaiterThreadId;
115 BOOL fWaiterThreadMessageQueueInitialized;
116
117 // Callbacks
118 PFN_MONGENERAL vpfMonGeneral;
119 PFN_MONDIRECTORY vpfMonDirectory;
120 PFN_MONREGKEY vpfMonRegKey;
121
122 // Context for callbacks
123 LPVOID pvContext;
124
125 // HANDLEs are in their own array for easy use with WaitForMultipleObjects()
126 // After initialization, the very first handle is just to wake the listener thread to have it re-wait on a new list
127 // Because this array is read by both coordinator thread and waiter thread, to avoid locking between both threads, it must start at the maximum size
128 HANDLE *rgHandles;
129 DWORD cHandles;
130
131 // Requested things to monitor
132 MON_REQUEST *rgRequests;
133 DWORD cRequests;
134
135 // Number of pending notifications
136 DWORD cRequestsPending;
137
138 // Number of requests in a failed state (couldn't initiate wait)
139 DWORD cRequestsFailing;
140};
141
142// Info stored about each waiter by the coordinator
143struct MON_WAITER_INFO
144{
145 DWORD cMonitorCount;
146
147 MON_WAITER_CONTEXT *pWaiterContext;
148};
149
150// This struct is used when Thread A wants to send a task to another thread B (and get notified when the task finishes)
151// You typically declare this struct in a manner that a pointer to it is valid as long as a thread that could respond is still running
152// (even long after sender is no longer waiting, in case thread has huge message queue)
153// and you must send 2 parameters in the message:
154// 1) a pointer to this struct (which is always valid)
155// 2) the original value of dwIteration
156// The receiver of the message can compare the current value of dwSendIteration in the struct with what was sent in the message
157// If values are different, we're too late and thread A is no longer waiting on this response
158// otherwise, set dwResponseIteration to the same value, and call ::SetEvent() on hWait
159// Thread A will then wakeup, and must verify that dwResponseIteration == dwSendIteration to ensure it isn't an earlier out-of-date reply
160// replying to a newer wait
161// pvContext is used to send a misc parameter related to processing data
162struct MON_INTERNAL_TEMPORARY_WAIT
163{
164 // Should be incremented each time sender sends a pointer to this struct, so each request has a different iteration
165 DWORD dwSendIteration;
166 DWORD dwReceiveIteration;
167 HANDLE hWait;
168 void *pvContext;
169};
170
171struct MON_STRUCT
172{
173 HANDLE hCoordinatorThread;
174 DWORD dwCoordinatorThreadId;
175 BOOL fCoordinatorThreadMessageQueueInitialized;
176
177 // Invisible window for receiving network status & drive added/removal messages
178 HWND hwnd;
179 // Used by window procedure for sending request and waiting for response from waiter threads
180 // such as in event of a request to remove a device
181 MON_INTERNAL_TEMPORARY_WAIT internalWait;
182
183 // Callbacks
184 PFN_MONGENERAL vpfMonGeneral;
185 PFN_MONDRIVESTATUS vpfMonDriveStatus;
186 PFN_MONDIRECTORY vpfMonDirectory;
187 PFN_MONREGKEY vpfMonRegKey;
188
189 // Context for callbacks
190 LPVOID pvContext;
191
192 // Waiter thread array
193 MON_WAITER_INFO *rgWaiterThreads;
194 DWORD cWaiterThreads;
195};
196
197const int MON_HANDLE_BYTES = sizeof(MON_STRUCT);
198
199static DWORD WINAPI CoordinatorThread(
200 __in_bcount(sizeof(MON_STRUCT)) LPVOID pvContext
201 );
202// Initiates (or if *pHandle is non-null, continues) wait on the directory or subkey
203// if the directory or subkey doesn't exist, instead calls it on the first existing parent directory or subkey
204// writes to pRequest->dwPathHierarchyIndex with the array index that was waited on
205static HRESULT InitiateWait(
206 __inout MON_REQUEST *pRequest,
207 __inout HANDLE *pHandle
208 );
209static DWORD WINAPI WaiterThread(
210 __in_bcount(sizeof(MON_WAITER_CONTEXT)) LPVOID pvContext
211 );
212static void Notify(
213 __in HRESULT hr,
214 __in MON_WAITER_CONTEXT *pWaiterContext,
215 __in MON_REQUEST *pRequest
216 );
217static void MonRequestDestroy(
218 __in MON_REQUEST *pRequest
219 );
220static void MonAddMessageDestroy(
221 __in MON_ADD_MESSAGE *pMessage
222 );
223static void MonRemoveMessageDestroy(
224 __in MON_REMOVE_MESSAGE *pMessage
225 );
226static BOOL GetRecursiveFlag(
227 __in MON_REQUEST *pRequest,
228 __in DWORD dwIndex
229 );
230static HRESULT FindRequestIndex(
231 __in MON_WAITER_CONTEXT *pWaiterContext,
232 __in MON_REMOVE_MESSAGE *pMessage,
233 __out DWORD *pdwIndex
234 );
235static HRESULT RemoveRequest(
236 __inout MON_WAITER_CONTEXT *pWaiterContext,
237 __in DWORD dwRequestIndex
238 );
239static REGSAM GetRegKeyBitness(
240 __in MON_REQUEST *pRequest
241 );
242static HRESULT DuplicateRemoveMessage(
243 __in MON_REMOVE_MESSAGE *pMessage,
244 __out MON_REMOVE_MESSAGE **ppMessage
245 );
246static LRESULT CALLBACK MonWndProc(
247 __in HWND hWnd,
248 __in UINT uMsg,
249 __in WPARAM wParam,
250 __in LPARAM lParam
251 );
252static HRESULT CreateMonWindow(
253 __in MON_STRUCT *pm,
254 __out HWND *pHwnd
255 );
256// if *phMonitor is non-NULL, closes the old wait before re-starting the new wait
257static HRESULT WaitForNetworkChanges(
258 __inout HANDLE *phMonitor,
259 __in MON_STRUCT *pm
260 );
261static HRESULT UpdateWaitStatus(
262 __in HRESULT hrNewStatus,
263 __inout MON_WAITER_CONTEXT *pWaiterContext,
264 __in DWORD dwRequestIndex,
265 __out DWORD *pdwNewRequestIndex
266 );
267
268extern "C" HRESULT DAPI MonCreate(
269 __out_bcount(MON_HANDLE_BYTES) MON_HANDLE *pHandle,
270 __in PFN_MONGENERAL vpfMonGeneral,
271 __in_opt PFN_MONDRIVESTATUS vpfMonDriveStatus,
272 __in_opt PFN_MONDIRECTORY vpfMonDirectory,
273 __in_opt PFN_MONREGKEY vpfMonRegKey,
274 __in_opt LPVOID pvContext
275 )
276{
277 HRESULT hr = S_OK;
278 DWORD dwRetries = MON_THREAD_INIT_RETRIES;
279
280 ExitOnNull(pHandle, hr, E_INVALIDARG, "Pointer to handle not specified while creating monitor");
281
282 // Allocate the struct
283 *pHandle = static_cast<MON_HANDLE>(MemAlloc(sizeof(MON_STRUCT), TRUE));
284 ExitOnNull(*pHandle, hr, E_OUTOFMEMORY, "Failed to allocate monitor object");
285
286 MON_STRUCT *pm = static_cast<MON_STRUCT *>(*pHandle);
287
288 pm->vpfMonGeneral = vpfMonGeneral;
289 pm->vpfMonDriveStatus = vpfMonDriveStatus;
290 pm->vpfMonDirectory = vpfMonDirectory;
291 pm->vpfMonRegKey = vpfMonRegKey;
292 pm->pvContext = pvContext;
293
294 pm->hCoordinatorThread = ::CreateThread(NULL, 0, CoordinatorThread, pm, 0, &pm->dwCoordinatorThreadId);
295 if (!pm->hCoordinatorThread)
296 {
297 ExitWithLastError(hr, "Failed to create waiter thread.");
298 }
299
300 // Ensure the created thread initializes its message queue. It does this first thing, so if it doesn't within 10 seconds, there must be a huge problem.
301 while (!pm->fCoordinatorThreadMessageQueueInitialized && 0 < dwRetries)
302 {
303 ::Sleep(MON_THREAD_INIT_RETRY_PERIOD_IN_MS);
304 --dwRetries;
305 }
306
307 if (0 == dwRetries)
308 {
309 hr = E_UNEXPECTED;
310 ExitOnFailure(hr, "Waiter thread apparently never initialized its message queue.");
311 }
312
313LExit:
314 return hr;
315}
316
317extern "C" HRESULT DAPI MonAddDirectory(
318 __in_bcount(MON_HANDLE_BYTES) MON_HANDLE handle,
319 __in_z LPCWSTR wzDirectory,
320 __in BOOL fRecursive,
321 __in DWORD dwSilencePeriodInMs,
322 __in_opt LPVOID pvDirectoryContext
323 )
324{
325 HRESULT hr = S_OK;
326 MON_STRUCT *pm = static_cast<MON_STRUCT *>(handle);
327 LPWSTR sczDirectory = NULL;
328 LPWSTR sczOriginalPathRequest = NULL;
329 MON_ADD_MESSAGE *pMessage = NULL;
330
331 hr = StrAllocString(&sczOriginalPathRequest, wzDirectory, 0);
332 ExitOnFailure(hr, "Failed to convert directory string to UNC path");
333
334 hr = PathBackslashTerminate(&sczOriginalPathRequest);
335 ExitOnFailure(hr, "Failed to ensure directory ends in backslash");
336
337 pMessage = reinterpret_cast<MON_ADD_MESSAGE *>(MemAlloc(sizeof(MON_ADD_MESSAGE), TRUE));
338 ExitOnNull(pMessage, hr, E_OUTOFMEMORY, "Failed to allocate memory for message");
339
340 if (sczOriginalPathRequest[0] == L'\\' && sczOriginalPathRequest[1] == L'\\')
341 {
342 pMessage->request.fNetwork = TRUE;
343 }
344 else
345 {
346 hr = UncConvertFromMountedDrive(&sczDirectory, sczOriginalPathRequest);
347 if (SUCCEEDED(hr))
348 {
349 pMessage->request.fNetwork = TRUE;
350 }
351 }
352
353 if (NULL == sczDirectory)
354 {
355 // Likely not a mounted drive - just copy the request then
356 hr = S_OK;
357
358 hr = StrAllocString(&sczDirectory, sczOriginalPathRequest, 0);
359 ExitOnFailure(hr, "Failed to copy original path request: %ls", sczOriginalPathRequest);
360 }
361
362 pMessage->handle = INVALID_HANDLE_VALUE;
363 pMessage->request.type = MON_DIRECTORY;
364 pMessage->request.fRecursive = fRecursive;
365 pMessage->request.dwMaxSilencePeriodInMs = dwSilencePeriodInMs;
366 pMessage->request.hwnd = pm->hwnd;
367 pMessage->request.pvContext = pvDirectoryContext;
368 pMessage->request.sczOriginalPathRequest = sczOriginalPathRequest;
369 sczOriginalPathRequest = NULL;
370
371 hr = PathGetHierarchyArray(sczDirectory, &pMessage->request.rgsczPathHierarchy, reinterpret_cast<LPUINT>(&pMessage->request.cPathHierarchy));
372 ExitOnFailure(hr, "Failed to get hierarchy array for path %ls", sczDirectory);
373
374 if (0 < pMessage->request.cPathHierarchy)
375 {
376 pMessage->request.hrStatus = InitiateWait(&pMessage->request, &pMessage->handle);
377 if (!::PostThreadMessageW(pm->dwCoordinatorThreadId, MON_MESSAGE_ADD, reinterpret_cast<WPARAM>(pMessage), 0))
378 {
379 ExitWithLastError(hr, "Failed to send message to worker thread to add directory wait for path %ls", sczDirectory);
380 }
381 pMessage = NULL;
382 }
383
384LExit:
385 ReleaseStr(sczDirectory);
386 ReleaseStr(sczOriginalPathRequest);
387 MonAddMessageDestroy(pMessage);
388
389 return hr;
390}
391
392extern "C" HRESULT DAPI MonAddRegKey(
393 __in_bcount(MON_HANDLE_BYTES) MON_HANDLE handle,
394 __in HKEY hkRoot,
395 __in_z LPCWSTR wzSubKey,
396 __in REG_KEY_BITNESS kbKeyBitness,
397 __in BOOL fRecursive,
398 __in DWORD dwSilencePeriodInMs,
399 __in_opt LPVOID pvRegKeyContext
400 )
401{
402 HRESULT hr = S_OK;
403 MON_STRUCT *pm = static_cast<MON_STRUCT *>(handle);
404 LPWSTR sczSubKey = NULL;
405 MON_ADD_MESSAGE *pMessage = NULL;
406
407 hr = StrAllocString(&sczSubKey, wzSubKey, 0);
408 ExitOnFailure(hr, "Failed to copy subkey string");
409
410 hr = PathBackslashTerminate(&sczSubKey);
411 ExitOnFailure(hr, "Failed to ensure subkey path ends in backslash");
412
413 pMessage = reinterpret_cast<MON_ADD_MESSAGE *>(MemAlloc(sizeof(MON_ADD_MESSAGE), TRUE));
414 ExitOnNull(pMessage, hr, E_OUTOFMEMORY, "Failed to allocate memory for message");
415
416 pMessage->handle = ::CreateEventW(NULL, TRUE, FALSE, NULL);
417 ExitOnNullWithLastError(pMessage->handle, hr, "Failed to create anonymous event for regkey monitor");
418
419 pMessage->request.type = MON_REGKEY;
420 pMessage->request.regkey.hkRoot = hkRoot;
421 pMessage->request.regkey.kbKeyBitness = kbKeyBitness;
422 pMessage->request.fRecursive = fRecursive;
423 pMessage->request.dwMaxSilencePeriodInMs = dwSilencePeriodInMs,
424 pMessage->request.hwnd = pm->hwnd;
425 pMessage->request.pvContext = pvRegKeyContext;
426
427 hr = PathGetHierarchyArray(sczSubKey, &pMessage->request.rgsczPathHierarchy, reinterpret_cast<LPUINT>(&pMessage->request.cPathHierarchy));
428 ExitOnFailure(hr, "Failed to get hierarchy array for subkey %ls", sczSubKey);
429
430 if (0 < pMessage->request.cPathHierarchy)
431 {
432 pMessage->request.hrStatus = InitiateWait(&pMessage->request, &pMessage->handle);
433 ExitOnFailure(hr, "Failed to initiate wait");
434
435 if (!::PostThreadMessageW(pm->dwCoordinatorThreadId, MON_MESSAGE_ADD, reinterpret_cast<WPARAM>(pMessage), 0))
436 {
437 ExitWithLastError(hr, "Failed to send message to worker thread to add directory wait for regkey %ls", sczSubKey);
438 }
439 pMessage = NULL;
440 }
441
442LExit:
443 ReleaseStr(sczSubKey);
444 MonAddMessageDestroy(pMessage);
445
446 return hr;
447}
448
449extern "C" HRESULT DAPI MonRemoveDirectory(
450 __in_bcount(MON_HANDLE_BYTES) MON_HANDLE handle,
451 __in_z LPCWSTR wzDirectory,
452 __in BOOL fRecursive
453 )
454{
455 HRESULT hr = S_OK;
456 MON_STRUCT *pm = static_cast<MON_STRUCT *>(handle);
457 LPWSTR sczDirectory = NULL;
458 MON_REMOVE_MESSAGE *pMessage = NULL;
459
460 hr = StrAllocString(&sczDirectory, wzDirectory, 0);
461 ExitOnFailure(hr, "Failed to copy directory string");
462
463 hr = PathBackslashTerminate(&sczDirectory);
464 ExitOnFailure(hr, "Failed to ensure directory ends in backslash");
465
466 pMessage = reinterpret_cast<MON_REMOVE_MESSAGE *>(MemAlloc(sizeof(MON_REMOVE_MESSAGE), TRUE));
467 ExitOnNull(pMessage, hr, E_OUTOFMEMORY, "Failed to allocate memory for message");
468
469 pMessage->type = MON_DIRECTORY;
470 pMessage->fRecursive = fRecursive;
471
472 hr = StrAllocString(&pMessage->directory.sczDirectory, sczDirectory, 0);
473 ExitOnFailure(hr, "Failed to allocate copy of directory string");
474
475 if (!::PostThreadMessageW(pm->dwCoordinatorThreadId, MON_MESSAGE_REMOVE, reinterpret_cast<WPARAM>(pMessage), 0))
476 {
477 ExitWithLastError(hr, "Failed to send message to worker thread to add directory wait for path %ls", sczDirectory);
478 }
479 pMessage = NULL;
480
481LExit:
482 MonRemoveMessageDestroy(pMessage);
483
484 return hr;
485}
486
487extern "C" HRESULT DAPI MonRemoveRegKey(
488 __in_bcount(MON_HANDLE_BYTES) MON_HANDLE handle,
489 __in HKEY hkRoot,
490 __in_z LPCWSTR wzSubKey,
491 __in REG_KEY_BITNESS kbKeyBitness,
492 __in BOOL fRecursive
493 )
494{
495 HRESULT hr = S_OK;
496 MON_STRUCT *pm = static_cast<MON_STRUCT *>(handle);
497 LPWSTR sczSubKey = NULL;
498 MON_REMOVE_MESSAGE *pMessage = NULL;
499
500 hr = StrAllocString(&sczSubKey, wzSubKey, 0);
501 ExitOnFailure(hr, "Failed to copy subkey string");
502
503 hr = PathBackslashTerminate(&sczSubKey);
504 ExitOnFailure(hr, "Failed to ensure subkey path ends in backslash");
505
506 pMessage = reinterpret_cast<MON_REMOVE_MESSAGE *>(MemAlloc(sizeof(MON_REMOVE_MESSAGE), TRUE));
507 ExitOnNull(pMessage, hr, E_OUTOFMEMORY, "Failed to allocate memory for message");
508
509 pMessage->type = MON_REGKEY;
510 pMessage->regkey.hkRoot = hkRoot;
511 pMessage->regkey.kbKeyBitness = kbKeyBitness;
512 pMessage->fRecursive = fRecursive;
513
514 hr = StrAllocString(&pMessage->regkey.sczSubKey, sczSubKey, 0);
515 ExitOnFailure(hr, "Failed to allocate copy of directory string");
516
517 if (!::PostThreadMessageW(pm->dwCoordinatorThreadId, MON_MESSAGE_REMOVE, reinterpret_cast<WPARAM>(pMessage), 0))
518 {
519 ExitWithLastError(hr, "Failed to send message to worker thread to add directory wait for path %ls", sczSubKey);
520 }
521 pMessage = NULL;
522
523LExit:
524 ReleaseStr(sczSubKey);
525 MonRemoveMessageDestroy(pMessage);
526
527 return hr;
528}
529
530extern "C" void DAPI MonDestroy(
531 __in_bcount(MON_HANDLE_BYTES) MON_HANDLE handle
532 )
533{
534 HRESULT hr = S_OK;
535 DWORD er = ERROR_SUCCESS;
536 MON_STRUCT *pm = static_cast<MON_STRUCT *>(handle);
537
538 if (!::PostThreadMessageW(pm->dwCoordinatorThreadId, MON_MESSAGE_STOP, 0, 0))
539 {
540 er = ::GetLastError();
541 if (ERROR_INVALID_THREAD_ID == er)
542 {
543 // It already halted, or doesn't exist for some other reason, so let's just ignore it and clean up
544 er = ERROR_SUCCESS;
545 }
546 ExitOnWin32Error(er, hr, "Failed to send message to background thread to halt");
547 }
548
549 if (pm->hCoordinatorThread)
550 {
551 ::WaitForSingleObject(pm->hCoordinatorThread, INFINITE);
552 ::CloseHandle(pm->hCoordinatorThread);
553 }
554
555LExit:
556 return;
557}
558
559static void MonRequestDestroy(
560 __in MON_REQUEST *pRequest
561 )
562{
563 if (NULL != pRequest)
564 {
565 if (MON_REGKEY == pRequest->type)
566 {
567 ReleaseRegKey(pRequest->regkey.hkSubKey);
568 }
569 else if (MON_DIRECTORY == pRequest->type && pRequest->hNotify)
570 {
571 UnregisterDeviceNotification(pRequest->hNotify);
572 pRequest->hNotify = NULL;
573 }
574 ReleaseStr(pRequest->sczOriginalPathRequest);
575 ReleaseStrArray(pRequest->rgsczPathHierarchy, pRequest->cPathHierarchy);
576 }
577}
578
579static void MonAddMessageDestroy(
580 __in MON_ADD_MESSAGE *pMessage
581 )
582{
583 if (NULL != pMessage)
584 {
585 MonRequestDestroy(&pMessage->request);
586 if (MON_DIRECTORY == pMessage->request.type && INVALID_HANDLE_VALUE != pMessage->handle)
587 {
588 ::FindCloseChangeNotification(pMessage->handle);
589 }
590 else if (MON_REGKEY == pMessage->request.type)
591 {
592 ReleaseHandle(pMessage->handle);
593 }
594
595 ReleaseMem(pMessage);
596 }
597}
598
599static void MonRemoveMessageDestroy(
600 __in MON_REMOVE_MESSAGE *pMessage
601 )
602{
603 if (NULL != pMessage)
604 {
605 switch (pMessage->type)
606 {
607 case MON_DIRECTORY:
608 ReleaseStr(pMessage->directory.sczDirectory);
609 break;
610 case MON_REGKEY:
611 ReleaseStr(pMessage->regkey.sczSubKey);
612 break;
613 default:
614 Assert(false);
615 }
616
617 ReleaseMem(pMessage);
618 }
619}
620
621static DWORD WINAPI CoordinatorThread(
622 __in_bcount(sizeof(MON_STRUCT)) LPVOID pvContext
623 )
624{
625 HRESULT hr = S_OK;
626 MSG msg = { };
627 DWORD dwThreadIndex = DWORD_MAX;
628 DWORD dwRetries;
629 DWORD dwFailingNetworkWaits = 0;
630 MON_WAITER_CONTEXT *pWaiterContext = NULL;
631 MON_REMOVE_MESSAGE *pRemoveMessage = NULL;
632 MON_REMOVE_MESSAGE *pTempRemoveMessage = NULL;
633 MON_STRUCT *pm = reinterpret_cast<MON_STRUCT*>(pvContext);
634 WSADATA wsaData = { };
635 HANDLE hMonitor = NULL;
636 BOOL fRet = FALSE;
637 UINT_PTR uTimerSuccessfulNetworkRetry = 0;
638 UINT_PTR uTimerFailedNetworkRetry = 0;
639
640 // Ensure the thread has a message queue
641 ::PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
642 pm->fCoordinatorThreadMessageQueueInitialized = TRUE;
643
644 hr = CreateMonWindow(pm, &pm->hwnd);
645 ExitOnFailure(hr, "Failed to create window for status update thread");
646
647 ::WSAStartup(MAKEWORD(2, 2), &wsaData);
648
649 hr = WaitForNetworkChanges(&hMonitor, pm);
650 ExitOnFailure(hr, "Failed to wait for network changes");
651
652 uTimerSuccessfulNetworkRetry = ::SetTimer(NULL, 1, MON_THREAD_NETWORK_SUCCESSFUL_RETRY_IN_MS, NULL);
653 if (0 == uTimerSuccessfulNetworkRetry)
654 {
655 ExitWithLastError(hr, "Failed to set timer for network successful retry");
656 }
657
658 while (0 != (fRet = ::GetMessageW(&msg, NULL, 0, 0)))
659 {
660 if (-1 == fRet)
661 {
662 hr = E_UNEXPECTED;
663 ExitOnRootFailure(hr, "Unexpected return value from message pump.");
664 }
665 else
666 {
667 switch (msg.message)
668 {
669 case MON_MESSAGE_ADD:
670 dwThreadIndex = DWORD_MAX;
671 for (DWORD i = 0; i < pm->cWaiterThreads; ++i)
672 {
673 if (pm->rgWaiterThreads[i].cMonitorCount < MON_MAX_MONITORS_PER_THREAD)
674 {
675 dwThreadIndex = i;
676 break;
677 }
678 }
679
680 if (dwThreadIndex < pm->cWaiterThreads)
681 {
682 pWaiterContext = pm->rgWaiterThreads[dwThreadIndex].pWaiterContext;
683 }
684 else
685 {
686 hr = MemEnsureArraySize(reinterpret_cast<void **>(&pm->rgWaiterThreads), pm->cWaiterThreads + 1, sizeof(MON_WAITER_INFO), MON_THREAD_GROWTH);
687 ExitOnFailure(hr, "Failed to grow waiter thread array size");
688 ++pm->cWaiterThreads;
689
690 dwThreadIndex = pm->cWaiterThreads - 1;
691 pm->rgWaiterThreads[dwThreadIndex].pWaiterContext = reinterpret_cast<MON_WAITER_CONTEXT*>(MemAlloc(sizeof(MON_WAITER_CONTEXT), TRUE));
692 ExitOnNull(pm->rgWaiterThreads[dwThreadIndex].pWaiterContext, hr, E_OUTOFMEMORY, "Failed to allocate waiter context struct");
693 pWaiterContext = pm->rgWaiterThreads[dwThreadIndex].pWaiterContext;
694 pWaiterContext->dwCoordinatorThreadId = ::GetCurrentThreadId();
695 pWaiterContext->vpfMonGeneral = pm->vpfMonGeneral;
696 pWaiterContext->vpfMonDirectory = pm->vpfMonDirectory;
697 pWaiterContext->vpfMonRegKey = pm->vpfMonRegKey;
698 pWaiterContext->pvContext = pm->pvContext;
699
700 hr = MemEnsureArraySize(reinterpret_cast<void **>(&pWaiterContext->rgHandles), MON_MAX_MONITORS_PER_THREAD + 1, sizeof(HANDLE), 0);
701 ExitOnFailure(hr, "Failed to allocate first handle");
702 pWaiterContext->cHandles = 1;
703
704 pWaiterContext->rgHandles[0] = ::CreateEventW(NULL, FALSE, FALSE, NULL);
705 ExitOnNullWithLastError(pWaiterContext->rgHandles[0], hr, "Failed to create general event");
706
707 pWaiterContext->hWaiterThread = ::CreateThread(NULL, 0, WaiterThread, pWaiterContext, 0, &pWaiterContext->dwWaiterThreadId);
708 if (!pWaiterContext->hWaiterThread)
709 {
710 ExitWithLastError(hr, "Failed to create waiter thread.");
711 }
712
713 dwRetries = MON_THREAD_INIT_RETRIES;
714 while (!pWaiterContext->fWaiterThreadMessageQueueInitialized && 0 < dwRetries)
715 {
716 ::Sleep(MON_THREAD_INIT_RETRY_PERIOD_IN_MS);
717 --dwRetries;
718 }
719
720 if (0 == dwRetries)
721 {
722 hr = E_UNEXPECTED;
723 ExitOnFailure(hr, "Waiter thread apparently never initialized its message queue.");
724 }
725 }
726
727 ++pm->rgWaiterThreads[dwThreadIndex].cMonitorCount;
728 if (!::PostThreadMessageW(pWaiterContext->dwWaiterThreadId, MON_MESSAGE_ADD, msg.wParam, 0))
729 {
730 ExitWithLastError(hr, "Failed to send message to waiter thread to add monitor");
731 }
732
733 if (!::SetEvent(pWaiterContext->rgHandles[0]))
734 {
735 ExitWithLastError(hr, "Failed to set event to notify waiter thread of incoming message");
736 }
737 break;
738
739 case MON_MESSAGE_REMOVE:
740 // Send remove to all waiter threads. They'll ignore it if they don't have that monitor.
741 // If they do have that monitor, they'll remove it from their list, and tell coordinator they have another
742 // empty slot via MON_MESSAGE_REMOVED message
743 for (DWORD i = 0; i < pm->cWaiterThreads; ++i)
744 {
745 pWaiterContext = pm->rgWaiterThreads[i].pWaiterContext;
746 pRemoveMessage = reinterpret_cast<MON_REMOVE_MESSAGE *>(msg.wParam);
747
748 hr = DuplicateRemoveMessage(pRemoveMessage, &pTempRemoveMessage);
749 ExitOnFailure(hr, "Failed to duplicate remove message");
750
751 if (!::PostThreadMessageW(pWaiterContext->dwWaiterThreadId, MON_MESSAGE_REMOVE, reinterpret_cast<WPARAM>(pTempRemoveMessage), msg.lParam))
752 {
753 ExitWithLastError(hr, "Failed to send message to waiter thread to add monitor");
754 }
755 pTempRemoveMessage = NULL;
756
757 if (!::SetEvent(pWaiterContext->rgHandles[0]))
758 {
759 ExitWithLastError(hr, "Failed to set event to notify waiter thread of incoming remove message");
760 }
761 }
762 MonRemoveMessageDestroy(pRemoveMessage);
763 pRemoveMessage = NULL;
764 break;
765
766 case MON_MESSAGE_REMOVED:
767 for (DWORD i = 0; i < pm->cWaiterThreads; ++i)
768 {
769 if (pm->rgWaiterThreads[i].pWaiterContext->dwWaiterThreadId == static_cast<DWORD>(msg.wParam))
770 {
771 Assert(pm->rgWaiterThreads[i].cMonitorCount > 0);
772 --pm->rgWaiterThreads[i].cMonitorCount;
773 if (0 == pm->rgWaiterThreads[i].cMonitorCount)
774 {
775 if (!::PostThreadMessageW(pm->rgWaiterThreads[i].pWaiterContext->dwWaiterThreadId, MON_MESSAGE_STOP, msg.wParam, msg.lParam))
776 {
777 ExitWithLastError(hr, "Failed to send message to waiter thread to stop");
778 }
779 MemRemoveFromArray(reinterpret_cast<LPVOID>(pm->rgWaiterThreads), i, 1, pm->cWaiterThreads, sizeof(MON_WAITER_INFO), TRUE);
780 --pm->cWaiterThreads;
781 --i; // reprocess this index in the for loop, which will now contain the item after the one we removed
782 }
783 }
784 }
785 break;
786
787 case MON_MESSAGE_NETWORK_WAIT_FAILED:
788 if (0 == dwFailingNetworkWaits)
789 {
790 uTimerFailedNetworkRetry = ::SetTimer(NULL, uTimerSuccessfulNetworkRetry + 1, MON_THREAD_NETWORK_FAIL_RETRY_IN_MS, NULL);
791 if (0 == uTimerFailedNetworkRetry)
792 {
793 ExitWithLastError(hr, "Failed to set timer for network fail retry");
794 }
795 }
796 ++dwFailingNetworkWaits;
797 break;
798
799 case MON_MESSAGE_NETWORK_WAIT_SUCCEEDED:
800 --dwFailingNetworkWaits;
801 if (0 == dwFailingNetworkWaits)
802 {
803 if (!::KillTimer(NULL, uTimerFailedNetworkRetry))
804 {
805 ExitWithLastError(hr, "Failed to kill timer for network fail retry");
806 }
807 uTimerFailedNetworkRetry = 0;
808 }
809 break;
810
811 case MON_MESSAGE_NETWORK_STATUS_UPDATE:
812 hr = WaitForNetworkChanges(&hMonitor, pm);
813 ExitOnFailure(hr, "Failed to re-wait for network changes");
814
815 // Propagate any network status update messages to all waiter threads
816 for (DWORD i = 0; i < pm->cWaiterThreads; ++i)
817 {
818 pWaiterContext = pm->rgWaiterThreads[i].pWaiterContext;
819
820 if (!::PostThreadMessageW(pWaiterContext->dwWaiterThreadId, MON_MESSAGE_NETWORK_STATUS_UPDATE, 0, 0))
821 {
822 ExitWithLastError(hr, "Failed to send message to waiter thread to notify of network status update");
823 }
824
825 if (!::SetEvent(pWaiterContext->rgHandles[0]))
826 {
827 ExitWithLastError(hr, "Failed to set event to notify waiter thread of incoming network status update message");
828 }
829 }
830 break;
831
832 case WM_TIMER:
833 // Timer means some network wait is failing, and we need to retry every so often in case a remote server goes back up
834 for (DWORD i = 0; i < pm->cWaiterThreads; ++i)
835 {
836 pWaiterContext = pm->rgWaiterThreads[i].pWaiterContext;
837
838 if (!::PostThreadMessageW(pWaiterContext->dwWaiterThreadId, msg.wParam == uTimerFailedNetworkRetry ? MON_MESSAGE_NETWORK_RETRY_FAILED_NETWORK_WAITS : MON_MESSAGE_NETWORK_RETRY_SUCCESSFUL_NETWORK_WAITS, 0, 0))
839 {
840 ExitWithLastError(hr, "Failed to send message to waiter thread to notify of network status update");
841 }
842
843 if (!::SetEvent(pWaiterContext->rgHandles[0]))
844 {
845 ExitWithLastError(hr, "Failed to set event to notify waiter thread of incoming network status update message");
846 }
847 }
848 break;
849
850 case MON_MESSAGE_DRIVE_STATUS_UPDATE:
851 // If user requested to be notified of drive status updates, notify!
852 if (pm->vpfMonDriveStatus)
853 {
854 pm->vpfMonDriveStatus(static_cast<WCHAR>(msg.wParam), static_cast<BOOL>(msg.lParam), pm->pvContext);
855 }
856
857 // Propagate any drive status update messages to all waiter threads
858 for (DWORD i = 0; i < pm->cWaiterThreads; ++i)
859 {
860 pWaiterContext = pm->rgWaiterThreads[i].pWaiterContext;
861
862 if (!::PostThreadMessageW(pWaiterContext->dwWaiterThreadId, MON_MESSAGE_DRIVE_STATUS_UPDATE, msg.wParam, msg.lParam))
863 {
864 ExitWithLastError(hr, "Failed to send message to waiter thread to notify of drive status update");
865 }
866
867 if (!::SetEvent(pWaiterContext->rgHandles[0]))
868 {
869 ExitWithLastError(hr, "Failed to set event to notify waiter thread of incoming drive status update message");
870 }
871 }
872 break;
873
874 case MON_MESSAGE_STOP:
875 ExitFunction1(hr = static_cast<HRESULT>(msg.wParam));
876
877 default:
878 // This thread owns a window, so this handles all the other random messages we get
879 ::TranslateMessage(&msg);
880 ::DispatchMessageW(&msg);
881 break;
882 }
883 }
884 }
885
886LExit:
887 if (uTimerFailedNetworkRetry)
888 {
889 fRet = ::KillTimer(NULL, uTimerFailedNetworkRetry);
890 }
891 if (uTimerSuccessfulNetworkRetry)
892 {
893 fRet = ::KillTimer(NULL, uTimerSuccessfulNetworkRetry);
894 }
895
896 if (pm->hwnd)
897 {
898 ::CloseWindow(pm->hwnd);
899 }
900
901 // Tell all waiter threads to shutdown
902 for (DWORD i = 0; i < pm->cWaiterThreads; ++i)
903 {
904 pWaiterContext = pm->rgWaiterThreads[i].pWaiterContext;
905 if (NULL != pWaiterContext->rgHandles[0])
906 {
907 if (!::PostThreadMessageW(pWaiterContext->dwWaiterThreadId, MON_MESSAGE_STOP, msg.wParam, msg.lParam))
908 {
909 TraceError(HRESULT_FROM_WIN32(::GetLastError()), "Failed to send message to waiter thread to stop");
910 }
911
912 if (!::SetEvent(pWaiterContext->rgHandles[0]))
913 {
914 TraceError(HRESULT_FROM_WIN32(::GetLastError()), "Failed to set event to notify waiter thread of incoming message");
915 }
916 }
917 }
918
919 if (hMonitor != NULL)
920 {
921 ::WSALookupServiceEnd(hMonitor);
922 }
923
924 // Now confirm they're actually shut down before returning
925 for (DWORD i = 0; i < pm->cWaiterThreads; ++i)
926 {
927 pWaiterContext = pm->rgWaiterThreads[i].pWaiterContext;
928 if (NULL != pWaiterContext->hWaiterThread)
929 {
930 ::WaitForSingleObject(pWaiterContext->hWaiterThread, INFINITE);
931 ::CloseHandle(pWaiterContext->hWaiterThread);
932 }
933
934 // Waiter thread can't release these, because coordinator thread uses it to try communicating with waiter thread
935 ReleaseHandle(pWaiterContext->rgHandles[0]);
936 ReleaseMem(pWaiterContext->rgHandles);
937
938 ReleaseMem(pWaiterContext);
939 }
940
941 if (FAILED(hr))
942 {
943 // If coordinator thread fails, notify general callback of an error
944 Assert(pm->vpfMonGeneral);
945 pm->vpfMonGeneral(hr, pm->pvContext);
946 }
947 MonRemoveMessageDestroy(pRemoveMessage);
948 MonRemoveMessageDestroy(pTempRemoveMessage);
949
950 ::WSACleanup();
951
952 return hr;
953}
954
955static HRESULT InitiateWait(
956 __inout MON_REQUEST *pRequest,
957 __inout HANDLE *pHandle
958 )
959{
960 HRESULT hr = S_OK;
961 HRESULT hrTemp = S_OK;
962 DEV_BROADCAST_HANDLE dev = { };
963 BOOL fRedo = FALSE;
964 BOOL fHandleFound;
965 DWORD er = ERROR_SUCCESS;
966 DWORD dwIndex = 0;
967 HKEY hk = NULL;
968 HANDLE hTemp = INVALID_HANDLE_VALUE;
969
970 if (pRequest->hNotify)
971 {
972 UnregisterDeviceNotification(pRequest->hNotify);
973 pRequest->hNotify = NULL;
974 }
975
976 do
977 {
978 fRedo = FALSE;
979 fHandleFound = FALSE;
980
981 for (DWORD i = 0; i < pRequest->cPathHierarchy && !fHandleFound; ++i)
982 {
983 dwIndex = pRequest->cPathHierarchy - i - 1;
984 switch (pRequest->type)
985 {
986 case MON_DIRECTORY:
987 if (INVALID_HANDLE_VALUE != *pHandle)
988 {
989 ::FindCloseChangeNotification(*pHandle);
990 *pHandle = INVALID_HANDLE_VALUE;
991 }
992
993 *pHandle = ::FindFirstChangeNotificationW(pRequest->rgsczPathHierarchy[dwIndex], GetRecursiveFlag(pRequest, dwIndex), FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SECURITY);
994 if (INVALID_HANDLE_VALUE == *pHandle)
995 {
996 hr = HRESULT_FROM_WIN32(::GetLastError());
997 if (E_FILENOTFOUND == hr || E_PATHNOTFOUND == hr || E_ACCESSDENIED == hr)
998 {
999 continue;
1000 }
1001 ExitOnWin32Error(er, hr, "Failed to wait on path %ls", pRequest->rgsczPathHierarchy[dwIndex]);
1002 }
1003 else
1004 {
1005 fHandleFound = TRUE;
1006 hr = S_OK;
1007 }
1008 break;
1009 case MON_REGKEY:
1010 ReleaseRegKey(pRequest->regkey.hkSubKey);
1011 hr = RegOpen(pRequest->regkey.hkRoot, pRequest->rgsczPathHierarchy[dwIndex], KEY_NOTIFY | GetRegKeyBitness(pRequest), &pRequest->regkey.hkSubKey);
1012 if (E_FILENOTFOUND == hr || E_PATHNOTFOUND == hr)
1013 {
1014 continue;
1015 }
1016 ExitOnFailure(hr, "Failed to open regkey %ls", pRequest->rgsczPathHierarchy[dwIndex]);
1017
1018 er = ::RegNotifyChangeKeyValue(pRequest->regkey.hkSubKey, GetRecursiveFlag(pRequest, dwIndex), REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_SECURITY, *pHandle, TRUE);
1019 ReleaseRegKey(hk);
1020 hr = HRESULT_FROM_WIN32(er);
1021 if (E_FILENOTFOUND == hr || E_PATHNOTFOUND == hr || HRESULT_FROM_WIN32(ERROR_KEY_DELETED) == hr)
1022 {
1023 continue;
1024 }
1025 else
1026 {
1027 ExitOnWin32Error(er, hr, "Failed to wait on subkey %ls", pRequest->rgsczPathHierarchy[dwIndex]);
1028
1029 fHandleFound = TRUE;
1030 }
1031
1032 break;
1033 default:
1034 return E_INVALIDARG;
1035 }
1036 }
1037
1038 pRequest->dwPathHierarchyIndex = dwIndex;
1039
1040 // If we're monitoring a parent instead of the real path because the real path didn't exist, double-check the child hasn't been created since.
1041 // If it has, restart the whole loop
1042 if (dwIndex < pRequest->cPathHierarchy - 1)
1043 {
1044 switch (pRequest->type)
1045 {
1046 case MON_DIRECTORY:
1047 hTemp = ::FindFirstChangeNotificationW(pRequest->rgsczPathHierarchy[dwIndex + 1], GetRecursiveFlag(pRequest, dwIndex + 1), FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SECURITY);
1048 if (INVALID_HANDLE_VALUE != hTemp)
1049 {
1050 ::FindCloseChangeNotification(hTemp);
1051 fRedo = TRUE;
1052 }
1053 break;
1054 case MON_REGKEY:
1055 hrTemp = RegOpen(pRequest->regkey.hkRoot, pRequest->rgsczPathHierarchy[dwIndex + 1], KEY_NOTIFY | GetRegKeyBitness(pRequest), &hk);
1056 ReleaseRegKey(hk);
1057 fRedo = SUCCEEDED(hrTemp);
1058 break;
1059 default:
1060 Assert(false);
1061 }
1062 }
1063 } while (fRedo);
1064
1065 ExitOnFailure(hr, "Didn't get a successful wait after looping through all available options %ls", pRequest->rgsczPathHierarchy[pRequest->cPathHierarchy - 1]);
1066
1067 if (MON_DIRECTORY == pRequest->type)
1068 {
1069 dev.dbch_size = sizeof(dev);
1070 dev.dbch_devicetype = DBT_DEVTYP_HANDLE;
1071 dev.dbch_handle = *pHandle;
1072 // Ignore failure on this - some drives by design don't support it (like network paths), and the worst that can happen is a
1073 // removable device will be left in use so user cannot gracefully remove
1074 pRequest->hNotify = RegisterDeviceNotification(pRequest->hwnd, &dev, DEVICE_NOTIFY_WINDOW_HANDLE);
1075 }
1076
1077LExit:
1078 ReleaseRegKey(hk);
1079
1080 return hr;
1081}
1082
1083static DWORD WINAPI WaiterThread(
1084 __in_bcount(sizeof(MON_WAITER_CONTEXT)) LPVOID pvContext
1085 )
1086{
1087 HRESULT hr = S_OK;
1088 HRESULT hrTemp = S_OK;
1089 DWORD dwRet = 0;
1090 BOOL fAgain = FALSE;
1091 BOOL fContinue = TRUE;
1092 BOOL fNotify = FALSE;
1093 BOOL fRet = FALSE;
1094 MSG msg = { };
1095 MON_ADD_MESSAGE *pAddMessage = NULL;
1096 MON_REMOVE_MESSAGE *pRemoveMessage = NULL;
1097 MON_WAITER_CONTEXT *pWaiterContext = reinterpret_cast<MON_WAITER_CONTEXT *>(pvContext);
1098 DWORD dwRequestIndex;
1099 DWORD dwNewRequestIndex;
1100 // If we have one or more requests pending notification, this is the period we intend to wait for multiple objects (shortest amount of time to next potential notify)
1101 DWORD dwWait = 0;
1102 DWORD uCurrentTime = 0;
1103 DWORD uLastTimeInMs = ::GetTickCount();
1104 DWORD uDeltaInMs = 0;
1105 DWORD cRequestsPendingBeforeLoop = 0;
1106 LPWSTR sczDirectory = NULL;
1107 bool rgfProcessedIndex[MON_MAX_MONITORS_PER_THREAD + 1] = { };
1108 MON_INTERNAL_TEMPORARY_WAIT * pInternalWait = NULL;
1109
1110 // Ensure the thread has a message queue
1111 ::PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
1112 pWaiterContext->fWaiterThreadMessageQueueInitialized = TRUE;
1113
1114 do
1115 {
1116 dwRet = ::WaitForMultipleObjects(pWaiterContext->cHandles - pWaiterContext->cRequestsFailing, pWaiterContext->rgHandles, FALSE, pWaiterContext->cRequestsPending > 0 ? dwWait : INFINITE);
1117
1118 uCurrentTime = ::GetTickCount();
1119 uDeltaInMs = uCurrentTime - uLastTimeInMs;
1120 uLastTimeInMs = uCurrentTime;
1121
1122 if (WAIT_OBJECT_0 == dwRet)
1123 {
1124 do
1125 {
1126 fRet = ::PeekMessage(&msg, reinterpret_cast<HWND>(-1), 0, 0, PM_REMOVE);
1127 fAgain = fRet;
1128 if (fRet)
1129 {
1130 switch (msg.message)
1131 {
1132 case MON_MESSAGE_ADD:
1133 pAddMessage = reinterpret_cast<MON_ADD_MESSAGE *>(msg.wParam);
1134
1135 // Don't just blindly put it at the end of the array - it must be before any failing requests
1136 // for WaitForMultipleObjects() to succeed
1137 dwNewRequestIndex = pWaiterContext->cRequests - pWaiterContext->cRequestsFailing;
1138 if (FAILED(pAddMessage->request.hrStatus))
1139 {
1140 ++pWaiterContext->cRequestsFailing;
1141 }
1142
1143 hr = MemInsertIntoArray(reinterpret_cast<void **>(&pWaiterContext->rgHandles), dwNewRequestIndex + 1, 1, pWaiterContext->cHandles, sizeof(HANDLE), MON_ARRAY_GROWTH);
1144 ExitOnFailure(hr, "Failed to insert additional handle");
1145 ++pWaiterContext->cHandles;
1146
1147 // Ugh - directory types start with INVALID_HANDLE_VALUE instead of NULL
1148 if (MON_DIRECTORY == pAddMessage->request.type)
1149 {
1150 pWaiterContext->rgHandles[dwNewRequestIndex + 1] = INVALID_HANDLE_VALUE;
1151 }
1152
1153 hr = MemInsertIntoArray(reinterpret_cast<void **>(&pWaiterContext->rgRequests), dwNewRequestIndex, 1, pWaiterContext->cRequests, sizeof(MON_REQUEST), MON_ARRAY_GROWTH);
1154 ExitOnFailure(hr, "Failed to insert additional request struct");
1155 ++pWaiterContext->cRequests;
1156
1157 pWaiterContext->rgRequests[dwNewRequestIndex] = pAddMessage->request;
1158 pWaiterContext->rgHandles[dwNewRequestIndex + 1] = pAddMessage->handle;
1159
1160 ReleaseNullMem(pAddMessage);
1161 break;
1162
1163 case MON_MESSAGE_REMOVE:
1164 pRemoveMessage = reinterpret_cast<MON_REMOVE_MESSAGE *>(msg.wParam);
1165
1166 // Find the request to remove
1167 hr = FindRequestIndex(pWaiterContext, pRemoveMessage, &dwRequestIndex);
1168 if (E_NOTFOUND == hr)
1169 {
1170 // Coordinator sends removes blindly to all waiter threads, so maybe this one wasn't intended for us
1171 hr = S_OK;
1172 }
1173 else
1174 {
1175 ExitOnFailure(hr, "Failed to find request index for remove message");
1176
1177 hr = RemoveRequest(pWaiterContext, dwRequestIndex);
1178 ExitOnFailure(hr, "Failed to remove request after request from coordinator thread.");
1179 }
1180
1181 MonRemoveMessageDestroy(pRemoveMessage);
1182 pRemoveMessage = NULL;
1183 break;
1184
1185 case MON_MESSAGE_NETWORK_RETRY_FAILED_NETWORK_WAITS:
1186 if (::PeekMessage(&msg, NULL, MON_MESSAGE_NETWORK_RETRY_FAILED_NETWORK_WAITS, MON_MESSAGE_NETWORK_RETRY_FAILED_NETWORK_WAITS, PM_NOREMOVE))
1187 {
1188 // If there is another a pending retry failed wait message, skip this one
1189 continue;
1190 }
1191
1192 ZeroMemory(rgfProcessedIndex, sizeof(rgfProcessedIndex));
1193 for (DWORD i = 0; i < pWaiterContext->cRequests; ++i)
1194 {
1195 if (rgfProcessedIndex[i])
1196 {
1197 // if we already processed this item due to UpdateWaitStatus swapping array indices, then skip it
1198 continue;
1199 }
1200
1201 if (MON_DIRECTORY == pWaiterContext->rgRequests[i].type && pWaiterContext->rgRequests[i].fNetwork && FAILED(pWaiterContext->rgRequests[i].hrStatus))
1202 {
1203 // This is not a failure, just record this in the request's status
1204 hrTemp = InitiateWait(pWaiterContext->rgRequests + i, pWaiterContext->rgHandles + i + 1);
1205
1206 hr = UpdateWaitStatus(hrTemp, pWaiterContext, i, &dwNewRequestIndex);
1207 ExitOnFailure(hr, "Failed to update wait status");
1208 hrTemp = S_OK;
1209
1210 if (dwNewRequestIndex != i)
1211 {
1212 // If this request was moved to the end of the list, reprocess this index and mark the new index for skipping
1213 rgfProcessedIndex[dwNewRequestIndex] = true;
1214 --i;
1215 }
1216 }
1217 }
1218 break;
1219
1220 case MON_MESSAGE_NETWORK_RETRY_SUCCESSFUL_NETWORK_WAITS:
1221 if (::PeekMessage(&msg, NULL, MON_MESSAGE_NETWORK_RETRY_SUCCESSFUL_NETWORK_WAITS, MON_MESSAGE_NETWORK_RETRY_SUCCESSFUL_NETWORK_WAITS, PM_NOREMOVE))
1222 {
1223 // If there is another a pending retry successful wait message, skip this one
1224 continue;
1225 }
1226
1227 ZeroMemory(rgfProcessedIndex, sizeof(rgfProcessedIndex));
1228 for (DWORD i = 0; i < pWaiterContext->cRequests; ++i)
1229 {
1230 if (rgfProcessedIndex[i])
1231 {
1232 // if we already processed this item due to UpdateWaitStatus swapping array indices, then skip it
1233 continue;
1234 }
1235
1236 if (MON_DIRECTORY == pWaiterContext->rgRequests[i].type && pWaiterContext->rgRequests[i].fNetwork && SUCCEEDED(pWaiterContext->rgRequests[i].hrStatus))
1237 {
1238 // This is not a failure, just record this in the request's status
1239 hrTemp = InitiateWait(pWaiterContext->rgRequests + i, pWaiterContext->rgHandles + i + 1);
1240
1241 hr = UpdateWaitStatus(hrTemp, pWaiterContext, i, &dwNewRequestIndex);
1242 ExitOnFailure(hr, "Failed to update wait status");
1243 hrTemp = S_OK;
1244
1245 if (dwNewRequestIndex != i)
1246 {
1247 // If this request was moved to the end of the list, reprocess this index and mark the new index for skipping
1248 rgfProcessedIndex[dwNewRequestIndex] = true;
1249 --i;
1250 }
1251 }
1252 }
1253 break;
1254
1255 case MON_MESSAGE_NETWORK_STATUS_UPDATE:
1256 if (::PeekMessage(&msg, NULL, MON_MESSAGE_NETWORK_STATUS_UPDATE, MON_MESSAGE_NETWORK_STATUS_UPDATE, PM_NOREMOVE))
1257 {
1258 // If there is another a pending network status update message, skip this one
1259 continue;
1260 }
1261
1262 ZeroMemory(rgfProcessedIndex, sizeof(rgfProcessedIndex));
1263 for (DWORD i = 0; i < pWaiterContext->cRequests; ++i)
1264 {
1265 if (rgfProcessedIndex[i])
1266 {
1267 // if we already processed this item due to UpdateWaitStatus swapping array indices, then skip it
1268 continue;
1269 }
1270
1271 if (MON_DIRECTORY == pWaiterContext->rgRequests[i].type && pWaiterContext->rgRequests[i].fNetwork)
1272 {
1273 // Failures here get recorded in the request's status
1274 hrTemp = InitiateWait(pWaiterContext->rgRequests + i, pWaiterContext->rgHandles + i + 1);
1275
1276 hr = UpdateWaitStatus(hrTemp, pWaiterContext, i, &dwNewRequestIndex);
1277 ExitOnFailure(hr, "Failed to update wait status");
1278 hrTemp = S_OK;
1279
1280 if (dwNewRequestIndex != i)
1281 {
1282 // If this request was moved to the end of the list, reprocess this index and mark the new index for skipping
1283 rgfProcessedIndex[dwNewRequestIndex] = true;
1284 --i;
1285 }
1286 }
1287 }
1288 break;
1289
1290 case MON_MESSAGE_DRIVE_STATUS_UPDATE:
1291 ZeroMemory(rgfProcessedIndex, sizeof(rgfProcessedIndex));
1292 for (DWORD i = 0; i < pWaiterContext->cRequests; ++i)
1293 {
1294 if (rgfProcessedIndex[i])
1295 {
1296 // if we already processed this item due to UpdateWaitStatus swapping array indices, then skip it
1297 continue;
1298 }
1299
1300 if (MON_DIRECTORY == pWaiterContext->rgRequests[i].type && pWaiterContext->rgRequests[i].sczOriginalPathRequest[0] == static_cast<WCHAR>(msg.wParam))
1301 {
1302 // Failures here get recorded in the request's status
1303 if (static_cast<BOOL>(msg.lParam))
1304 {
1305 hrTemp = InitiateWait(pWaiterContext->rgRequests + i, pWaiterContext->rgHandles + i + 1);
1306 }
1307 else
1308 {
1309 // If the message says the drive is disconnected, don't even try to wait, just mark it as gone
1310 hrTemp = E_PATHNOTFOUND;
1311 }
1312
1313 hr = UpdateWaitStatus(hrTemp, pWaiterContext, i, &dwNewRequestIndex);
1314 ExitOnFailure(hr, "Failed to update wait status");
1315 hrTemp = S_OK;
1316
1317 if (dwNewRequestIndex != i)
1318 {
1319 // If this request was moved to the end of the list, reprocess this index and mark the new index for skipping
1320 rgfProcessedIndex[dwNewRequestIndex] = true;
1321 --i;
1322 }
1323 }
1324 }
1325 break;
1326
1327 case MON_MESSAGE_DRIVE_QUERY_REMOVE:
1328 pInternalWait = reinterpret_cast<MON_INTERNAL_TEMPORARY_WAIT *>(msg.wParam);
1329 // Only do any work if message is not yet out of date
1330 // While it could become out of date while doing this processing, sending thread will check response to guard against this
1331 if (pInternalWait->dwSendIteration == static_cast<DWORD>(msg.lParam))
1332 {
1333 for (DWORD i = 0; i < pWaiterContext->cRequests; ++i)
1334 {
1335 if (MON_DIRECTORY == pWaiterContext->rgRequests[i].type && pWaiterContext->rgHandles[i + 1] == reinterpret_cast<HANDLE>(pInternalWait->pvContext))
1336 {
1337 // Release handles ASAP so the remove request will succeed
1338 if (pWaiterContext->rgRequests[i].hNotify)
1339 {
1340 UnregisterDeviceNotification(pWaiterContext->rgRequests[i].hNotify);
1341 pWaiterContext->rgRequests[i].hNotify = NULL;
1342 }
1343 ::FindCloseChangeNotification(pWaiterContext->rgHandles[i + 1]);
1344 pWaiterContext->rgHandles[i + 1] = INVALID_HANDLE_VALUE;
1345
1346 // Reply to unblock our reply to the remove request
1347 pInternalWait->dwReceiveIteration = static_cast<DWORD>(msg.lParam);
1348 if (!::SetEvent(pInternalWait->hWait))
1349 {
1350 TraceError(HRESULT_FROM_WIN32(::GetLastError()), "Failed to set event to notify coordinator thread that removable device handle was released, this could be due to wndproc no longer waiting for waiter thread's response");
1351 }
1352
1353 // Drive is disconnecting, don't even try to wait, just mark it as gone
1354 hrTemp = E_PATHNOTFOUND;
1355
1356 hr = UpdateWaitStatus(hrTemp, pWaiterContext, i, &dwNewRequestIndex);
1357 ExitOnFailure(hr, "Failed to update wait status");
1358 hrTemp = S_OK;
1359 break;
1360 }
1361 }
1362 }
1363 break;
1364
1365 case MON_MESSAGE_STOP:
1366 // Stop requested, so abort the whole thread
1367 Trace(REPORT_DEBUG, "Waiter thread was told to stop");
1368 fAgain = FALSE;
1369 fContinue = FALSE;
1370 ExitFunction1(hr = static_cast<HRESULT>(msg.wParam));
1371
1372 default:
1373 Assert(false);
1374 break;
1375 }
1376 }
1377 } while (fAgain);
1378 }
1379 else if (dwRet > WAIT_OBJECT_0 && dwRet - WAIT_OBJECT_0 < pWaiterContext->cHandles)
1380 {
1381 // OK a handle fired - only notify if it's the actual target, and not just some parent waiting for the target child to exist
1382 dwRequestIndex = dwRet - WAIT_OBJECT_0 - 1;
1383 fNotify = (pWaiterContext->rgRequests[dwRequestIndex].dwPathHierarchyIndex == pWaiterContext->rgRequests[dwRequestIndex].cPathHierarchy - 1);
1384
1385 // Initiate re-waits before we notify callback, to ensure we don't miss a single update
1386 hrTemp = InitiateWait(pWaiterContext->rgRequests + dwRequestIndex, pWaiterContext->rgHandles + dwRequestIndex + 1);
1387 hr = UpdateWaitStatus(hrTemp, pWaiterContext, dwRequestIndex, &dwRequestIndex);
1388 ExitOnFailure(hr, "Failed to update wait status");
1389 hrTemp = S_OK;
1390
1391 // If there were no errors and we were already waiting on the right target, or if we weren't yet but are able to now, it's a successful notify
1392 if (SUCCEEDED(pWaiterContext->rgRequests[dwRequestIndex].hrStatus) && (fNotify || (pWaiterContext->rgRequests[dwRequestIndex].dwPathHierarchyIndex == pWaiterContext->rgRequests[dwRequestIndex].cPathHierarchy - 1)))
1393 {
1394 Trace(REPORT_DEBUG, "Changes detected, waiting for silence period index %u", dwRequestIndex);
1395
1396 if (0 < pWaiterContext->rgRequests[dwRequestIndex].dwMaxSilencePeriodInMs)
1397 {
1398 pWaiterContext->rgRequests[dwRequestIndex].dwSilencePeriodInMs = 0;
1399 pWaiterContext->rgRequests[dwRequestIndex].fSkipDeltaAdd = TRUE;
1400
1401 if (!pWaiterContext->rgRequests[dwRequestIndex].fPendingFire)
1402 {
1403 pWaiterContext->rgRequests[dwRequestIndex].fPendingFire = TRUE;
1404 ++pWaiterContext->cRequestsPending;
1405 }
1406 }
1407 else
1408 {
1409 // If no silence period, notify immediately
1410 Notify(S_OK, pWaiterContext, pWaiterContext->rgRequests + dwRequestIndex);
1411 }
1412 }
1413 }
1414 else if (WAIT_TIMEOUT != dwRet)
1415 {
1416 ExitWithLastError(hr, "Failed to wait for multiple objects with return code %u", dwRet);
1417 }
1418
1419 // OK, now that we've checked all triggered handles (resetting silence period timers appropriately), check for any pending notifications that we can finally fire
1420 // And set dwWait appropriately so we awaken at the right time to fire the next pending notification (in case no further writes occur during that time)
1421 if (0 < pWaiterContext->cRequestsPending)
1422 {
1423 // Start at max value and find the lowest wait we can below that
1424 dwWait = DWORD_MAX;
1425 cRequestsPendingBeforeLoop = pWaiterContext->cRequestsPending;
1426
1427 for (DWORD i = 0; i < pWaiterContext->cRequests; ++i)
1428 {
1429 if (pWaiterContext->rgRequests[i].fPendingFire)
1430 {
1431 if (0 == cRequestsPendingBeforeLoop)
1432 {
1433 Assert(FALSE);
1434 hr = HRESULT_FROM_WIN32(ERROR_EA_LIST_INCONSISTENT);
1435 ExitOnFailure(hr, "Phantom pending fires were found!");
1436 }
1437 --cRequestsPendingBeforeLoop;
1438
1439 dwRequestIndex = i;
1440
1441 if (pWaiterContext->rgRequests[dwRequestIndex].fSkipDeltaAdd)
1442 {
1443 pWaiterContext->rgRequests[dwRequestIndex].fSkipDeltaAdd = FALSE;
1444 }
1445 else
1446 {
1447 pWaiterContext->rgRequests[dwRequestIndex].dwSilencePeriodInMs += uDeltaInMs;
1448 }
1449
1450 // silence period has elapsed without further notifications, so reset pending-related variables, and finally fire a notify!
1451 if (pWaiterContext->rgRequests[dwRequestIndex].dwSilencePeriodInMs >= pWaiterContext->rgRequests[dwRequestIndex].dwMaxSilencePeriodInMs)
1452 {
1453 Trace(REPORT_DEBUG, "Silence period surpassed, notifying %u ms late", pWaiterContext->rgRequests[dwRequestIndex].dwSilencePeriodInMs - pWaiterContext->rgRequests[dwRequestIndex].dwMaxSilencePeriodInMs);
1454 Notify(S_OK, pWaiterContext, pWaiterContext->rgRequests + dwRequestIndex);
1455 }
1456 else
1457 {
1458 // set dwWait to the shortest interval period so that if no changes occur, WaitForMultipleObjects
1459 // wakes the thread back up when it's time to fire the next pending notification
1460 if (dwWait > pWaiterContext->rgRequests[dwRequestIndex].dwMaxSilencePeriodInMs - pWaiterContext->rgRequests[dwRequestIndex].dwSilencePeriodInMs)
1461 {
1462 dwWait = pWaiterContext->rgRequests[dwRequestIndex].dwMaxSilencePeriodInMs - pWaiterContext->rgRequests[dwRequestIndex].dwSilencePeriodInMs;
1463 }
1464 }
1465 }
1466 }
1467
1468 // Some post-loop list validation for sanity checking
1469 if (0 < cRequestsPendingBeforeLoop)
1470 {
1471 Assert(FALSE);
1472 hr = HRESULT_FROM_WIN32(PEERDIST_ERROR_MISSING_DATA);
1473 ExitOnFailure(hr, "Missing %u pending fires! Total pending fires: %u, wait: %u", cRequestsPendingBeforeLoop, pWaiterContext->cRequestsPending, dwWait);
1474 }
1475 if (0 < pWaiterContext->cRequestsPending && DWORD_MAX == dwWait)
1476 {
1477 Assert(FALSE);
1478 hr = HRESULT_FROM_WIN32(ERROR_CANT_WAIT);
1479 ExitOnFailure(hr, "Pending fires exist, but wait was infinite", cRequestsPendingBeforeLoop);
1480 }
1481 }
1482 } while (fContinue);
1483
1484 // Don't bother firing pending notifications. We were told to stop monitoring, so client doesn't care.
1485
1486LExit:
1487 ReleaseStr(sczDirectory);
1488 MonAddMessageDestroy(pAddMessage);
1489 MonRemoveMessageDestroy(pRemoveMessage);
1490
1491 for (DWORD i = 0; i < pWaiterContext->cRequests; ++i)
1492 {
1493 MonRequestDestroy(pWaiterContext->rgRequests + i);
1494
1495 switch (pWaiterContext->rgRequests[i].type)
1496 {
1497 case MON_DIRECTORY:
1498 if (INVALID_HANDLE_VALUE != pWaiterContext->rgHandles[i + 1])
1499 {
1500 ::FindCloseChangeNotification(pWaiterContext->rgHandles[i + 1]);
1501 }
1502 break;
1503 case MON_REGKEY:
1504 ReleaseHandle(pWaiterContext->rgHandles[i + 1]);
1505 break;
1506 default:
1507 Assert(false);
1508 }
1509 }
1510
1511 if (FAILED(hr))
1512 {
1513 // If waiter thread fails, notify general callback of an error
1514 Assert(pWaiterContext->vpfMonGeneral);
1515 pWaiterContext->vpfMonGeneral(hr, pWaiterContext->pvContext);
1516
1517 // And tell coordinator to shut all other waiters down
1518 if (!::PostThreadMessageW(pWaiterContext->dwCoordinatorThreadId, MON_MESSAGE_STOP, 0, 0))
1519 {
1520 TraceError(HRESULT_FROM_WIN32(::GetLastError()), "Failed to send message to coordinator thread to stop (due to general failure).");
1521 }
1522 }
1523
1524 return hr;
1525}
1526
1527static void Notify(
1528 __in HRESULT hr,
1529 __in MON_WAITER_CONTEXT *pWaiterContext,
1530 __in MON_REQUEST *pRequest
1531 )
1532{
1533 if (pRequest->fPendingFire)
1534 {
1535 --pWaiterContext->cRequestsPending;
1536 }
1537
1538 pRequest->fPendingFire = FALSE;
1539 pRequest->fSkipDeltaAdd = FALSE;
1540 pRequest->dwSilencePeriodInMs = 0;
1541
1542 switch (pRequest->type)
1543 {
1544 case MON_DIRECTORY:
1545 Assert(pWaiterContext->vpfMonDirectory);
1546 pWaiterContext->vpfMonDirectory(hr, pRequest->sczOriginalPathRequest, pRequest->fRecursive, pWaiterContext->pvContext, pRequest->pvContext);
1547 break;
1548 case MON_REGKEY:
1549 Assert(pWaiterContext->vpfMonRegKey);
1550 pWaiterContext->vpfMonRegKey(hr, pRequest->regkey.hkRoot, pRequest->rgsczPathHierarchy[pRequest->cPathHierarchy - 1], pRequest->regkey.kbKeyBitness, pRequest->fRecursive, pWaiterContext->pvContext, pRequest->pvContext);
1551 break;
1552 default:
1553 Assert(false);
1554 }
1555}
1556
1557static BOOL GetRecursiveFlag(
1558 __in MON_REQUEST *pRequest,
1559 __in DWORD dwIndex
1560 )
1561{
1562 if (pRequest->cPathHierarchy - 1 == dwIndex)
1563 {
1564 return pRequest->fRecursive;
1565 }
1566 else
1567 {
1568 return FALSE;
1569 }
1570}
1571
1572static HRESULT FindRequestIndex(
1573 __in MON_WAITER_CONTEXT *pWaiterContext,
1574 __in MON_REMOVE_MESSAGE *pMessage,
1575 __out DWORD *pdwIndex
1576 )
1577{
1578 HRESULT hr = S_OK;
1579
1580 for (DWORD i = 0; i < pWaiterContext->cRequests; ++i)
1581 {
1582 if (pWaiterContext->rgRequests[i].type == pMessage->type)
1583 {
1584 switch (pWaiterContext->rgRequests[i].type)
1585 {
1586 case MON_DIRECTORY:
1587 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pWaiterContext->rgRequests[i].rgsczPathHierarchy[pWaiterContext->rgRequests[i].cPathHierarchy - 1], -1, pMessage->directory.sczDirectory, -1) && pWaiterContext->rgRequests[i].fRecursive == pMessage->fRecursive)
1588 {
1589 *pdwIndex = i;
1590 ExitFunction1(hr = S_OK);
1591 }
1592 break;
1593 case MON_REGKEY:
1594 if (reinterpret_cast<DWORD_PTR>(pMessage->regkey.hkRoot) == reinterpret_cast<DWORD_PTR>(pWaiterContext->rgRequests[i].regkey.hkRoot) && CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pWaiterContext->rgRequests[i].rgsczPathHierarchy[pWaiterContext->rgRequests[i].cPathHierarchy - 1], -1, pMessage->regkey.sczSubKey, -1) && pWaiterContext->rgRequests[i].fRecursive == pMessage->fRecursive && pWaiterContext->rgRequests[i].regkey.kbKeyBitness == pMessage->regkey.kbKeyBitness)
1595 {
1596 *pdwIndex = i;
1597 ExitFunction1(hr = S_OK);
1598 }
1599 break;
1600 default:
1601 Assert(false);
1602 }
1603 }
1604 }
1605
1606 hr = E_NOTFOUND;
1607
1608LExit:
1609 return hr;
1610}
1611
1612static HRESULT RemoveRequest(
1613 __inout MON_WAITER_CONTEXT *pWaiterContext,
1614 __in DWORD dwRequestIndex
1615 )
1616{
1617 HRESULT hr = S_OK;
1618
1619 MonRequestDestroy(pWaiterContext->rgRequests + dwRequestIndex);
1620
1621 switch (pWaiterContext->rgRequests[dwRequestIndex].type)
1622 {
1623 case MON_DIRECTORY:
1624 if (pWaiterContext->rgHandles[dwRequestIndex + 1] != INVALID_HANDLE_VALUE)
1625 {
1626 ::FindCloseChangeNotification(pWaiterContext->rgHandles[dwRequestIndex + 1]);
1627 }
1628 break;
1629 case MON_REGKEY:
1630 ReleaseHandle(pWaiterContext->rgHandles[dwRequestIndex + 1]);
1631 break;
1632 default:
1633 Assert(false);
1634 }
1635
1636 if (pWaiterContext->rgRequests[dwRequestIndex].fPendingFire)
1637 {
1638 --pWaiterContext->cRequestsPending;
1639 }
1640
1641 if (FAILED(pWaiterContext->rgRequests[dwRequestIndex].hrStatus))
1642 {
1643 --pWaiterContext->cRequestsFailing;
1644 }
1645
1646 MemRemoveFromArray(reinterpret_cast<void *>(pWaiterContext->rgHandles), dwRequestIndex + 1, 1, pWaiterContext->cHandles, sizeof(HANDLE), TRUE);
1647 --pWaiterContext->cHandles;
1648 MemRemoveFromArray(reinterpret_cast<void *>(pWaiterContext->rgRequests), dwRequestIndex, 1, pWaiterContext->cRequests, sizeof(MON_REQUEST), TRUE);
1649 --pWaiterContext->cRequests;
1650
1651 // Notify coordinator thread that a wait was removed
1652 if (!::PostThreadMessageW(pWaiterContext->dwCoordinatorThreadId, MON_MESSAGE_REMOVED, static_cast<WPARAM>(::GetCurrentThreadId()), 0))
1653 {
1654 ExitWithLastError(hr, "Failed to send message to coordinator thread to confirm directory was removed.");
1655 }
1656
1657LExit:
1658 return hr;
1659}
1660
1661static REGSAM GetRegKeyBitness(
1662 __in MON_REQUEST *pRequest
1663 )
1664{
1665 if (REG_KEY_32BIT == pRequest->regkey.kbKeyBitness)
1666 {
1667 return KEY_WOW64_32KEY;
1668 }
1669 else if (REG_KEY_64BIT == pRequest->regkey.kbKeyBitness)
1670 {
1671 return KEY_WOW64_64KEY;
1672 }
1673 else
1674 {
1675 return 0;
1676 }
1677}
1678
1679static HRESULT DuplicateRemoveMessage(
1680 __in MON_REMOVE_MESSAGE *pMessage,
1681 __out MON_REMOVE_MESSAGE **ppMessage
1682 )
1683{
1684 HRESULT hr = S_OK;
1685
1686 *ppMessage = reinterpret_cast<MON_REMOVE_MESSAGE *>(MemAlloc(sizeof(MON_REMOVE_MESSAGE), TRUE));
1687 ExitOnNull(*ppMessage, hr, E_OUTOFMEMORY, "Failed to allocate copy of remove message");
1688
1689 (*ppMessage)->type = pMessage->type;
1690 (*ppMessage)->fRecursive = pMessage->fRecursive;
1691
1692 switch (pMessage->type)
1693 {
1694 case MON_DIRECTORY:
1695 hr = StrAllocString(&(*ppMessage)->directory.sczDirectory, pMessage->directory.sczDirectory, 0);
1696 ExitOnFailure(hr, "Failed to copy directory");
1697 break;
1698 case MON_REGKEY:
1699 (*ppMessage)->regkey.hkRoot = pMessage->regkey.hkRoot;
1700 (*ppMessage)->regkey.kbKeyBitness = pMessage->regkey.kbKeyBitness;
1701 hr = StrAllocString(&(*ppMessage)->regkey.sczSubKey, pMessage->regkey.sczSubKey, 0);
1702 ExitOnFailure(hr, "Failed to copy subkey");
1703 break;
1704 default:
1705 Assert(false);
1706 break;
1707 }
1708
1709LExit:
1710 return hr;
1711}
1712
1713static LRESULT CALLBACK MonWndProc(
1714 __in HWND hWnd,
1715 __in UINT uMsg,
1716 __in WPARAM wParam,
1717 __in LPARAM lParam
1718 )
1719{
1720 HRESULT hr = S_OK;
1721 DEV_BROADCAST_HDR *pHdr = NULL;
1722 DEV_BROADCAST_HANDLE *pHandle = NULL;
1723 DEV_BROADCAST_VOLUME *pVolume = NULL;
1724 DWORD dwUnitMask = 0;
1725 DWORD er = ERROR_SUCCESS;
1726 WCHAR chDrive = L'\0';
1727 BOOL fArrival = FALSE;
1728 BOOL fReturnTrue = FALSE;
1729 CREATESTRUCT *pCreateStruct = NULL;
1730 MON_WAITER_CONTEXT *pWaiterContext = NULL;
1731 MON_STRUCT *pm = NULL;
1732
1733 // keep track of the MON_STRUCT pointer that was passed in on init, associate it with the window
1734 if (WM_CREATE == uMsg)
1735 {
1736 pCreateStruct = reinterpret_cast<CREATESTRUCT *>(lParam);
1737 if (pCreateStruct)
1738 {
1739 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pCreateStruct->lpCreateParams));
1740 }
1741 }
1742 else if (WM_NCDESTROY == uMsg)
1743 {
1744 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0);
1745 }
1746
1747 // Note this message ONLY comes in through WndProc, it isn't visible from the GetMessage loop.
1748 else if (WM_DEVICECHANGE == uMsg)
1749 {
1750 if (DBT_DEVICEARRIVAL == wParam || DBT_DEVICEREMOVECOMPLETE == wParam)
1751 {
1752 fArrival = DBT_DEVICEARRIVAL == wParam;
1753
1754 pHdr = reinterpret_cast<DEV_BROADCAST_HDR*>(lParam);
1755 if (DBT_DEVTYP_VOLUME == pHdr->dbch_devicetype)
1756 {
1757 pVolume = reinterpret_cast<DEV_BROADCAST_VOLUME*>(lParam);
1758 dwUnitMask = pVolume->dbcv_unitmask;
1759 chDrive = L'a';
1760 while (0 < dwUnitMask)
1761 {
1762 if (dwUnitMask & 0x1)
1763 {
1764 // This drive had a status update, so send it out to all threads
1765 if (!::PostThreadMessageW(::GetCurrentThreadId(), MON_MESSAGE_DRIVE_STATUS_UPDATE, static_cast<WPARAM>(chDrive), static_cast<LPARAM>(fArrival)))
1766 {
1767 ExitWithLastError(hr, "Failed to send drive status update with drive %wc and arrival %ls", chDrive, fArrival ? L"TRUE" : L"FALSE");
1768 }
1769 }
1770 dwUnitMask >>= 1;
1771 ++chDrive;
1772
1773 if (chDrive == 'z')
1774 {
1775 hr = E_UNEXPECTED;
1776 ExitOnFailure(hr, "UnitMask showed drives beyond z:. Remaining UnitMask at this point: %u", dwUnitMask);
1777 }
1778 }
1779 }
1780 }
1781 // We can only process device query remove messages if we have a MON_STRUCT pointer
1782 else if (DBT_DEVICEQUERYREMOVE == wParam)
1783 {
1784 pm = reinterpret_cast<MON_STRUCT*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA));
1785 if (!pm)
1786 {
1787 hr = E_POINTER;
1788 ExitOnFailure(hr, "DBT_DEVICEQUERYREMOVE message received with no MON_STRUCT pointer, so message was ignored");
1789 }
1790
1791 fReturnTrue = TRUE;
1792
1793 pHdr = reinterpret_cast<DEV_BROADCAST_HDR*>(lParam);
1794 if (DBT_DEVTYP_HANDLE == pHdr->dbch_devicetype)
1795 {
1796 // We must wait for the actual wait handle to be released by waiter thread before telling windows to proceed with device removal, otherwise it could fail
1797 // due to handles still being open, so use a MON_INTERNAL_TEMPORARY_WAIT struct to send and receive a reply from a waiter thread
1798 pm->internalWait.hWait = ::CreateEventW(NULL, TRUE, FALSE, NULL);
1799 ExitOnNullWithLastError(pm->internalWait.hWait, hr, "Failed to create anonymous event for waiter to notify wndproc device can be removed");
1800
1801 pHandle = reinterpret_cast<DEV_BROADCAST_HANDLE*>(lParam);
1802 pm->internalWait.pvContext = pHandle->dbch_handle;
1803 pm->internalWait.dwReceiveIteration = pm->internalWait.dwSendIteration - 1;
1804 // This drive had a status update, so send it out to all threads
1805 for (DWORD i = 0; i < pm->cWaiterThreads; ++i)
1806 {
1807 pWaiterContext = pm->rgWaiterThreads[i].pWaiterContext;
1808
1809 if (!::PostThreadMessageW(pWaiterContext->dwWaiterThreadId, MON_MESSAGE_DRIVE_QUERY_REMOVE, reinterpret_cast<WPARAM>(&pm->internalWait), static_cast<LPARAM>(pm->internalWait.dwSendIteration)))
1810 {
1811 ExitWithLastError(hr, "Failed to send message to waiter thread to notify of drive query remove");
1812 }
1813
1814 if (!::SetEvent(pWaiterContext->rgHandles[0]))
1815 {
1816 ExitWithLastError(hr, "Failed to set event to notify waiter thread of incoming drive query remove message");
1817 }
1818 }
1819
1820 er = ::WaitForSingleObject(pm->internalWait.hWait, MON_THREAD_WAIT_REMOVE_DEVICE);
1821 // Make sure any waiter thread processing really old messages can immediately know that we're no longer waiting for a response
1822 if (WAIT_OBJECT_0 == er)
1823 {
1824 // If the response ID matches what we sent, we actually got a valid reply!
1825 if (pm->internalWait.dwReceiveIteration != pm->internalWait.dwSendIteration)
1826 {
1827 TraceError(HRESULT_FROM_WIN32(er), "Waiter thread received wrong ID reply");
1828 }
1829 }
1830 else if (WAIT_TIMEOUT == er)
1831 {
1832 TraceError(HRESULT_FROM_WIN32(er), "No response from any waiter thread for query remove message");
1833 }
1834 else
1835 {
1836 ExitWithLastError(hr, "WaitForSingleObject failed with non-timeout reason while waiting for response from waiter thread");
1837 }
1838 ++pm->internalWait.dwSendIteration;
1839 }
1840 }
1841 }
1842
1843LExit:
1844 if (pm)
1845 {
1846 ReleaseHandle(pm->internalWait.hWait);
1847 }
1848
1849 if (fReturnTrue)
1850 {
1851 return TRUE;
1852 }
1853 else
1854 {
1855 return ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
1856 }
1857}
1858
1859static HRESULT CreateMonWindow(
1860 __in MON_STRUCT *pm,
1861 __out HWND *pHwnd
1862 )
1863{
1864 HRESULT hr = S_OK;
1865 WNDCLASSW wc = { };
1866
1867 wc.lpfnWndProc = MonWndProc;
1868 wc.hInstance = ::GetModuleHandleW(NULL);
1869 wc.lpszClassName = MONUTIL_WINDOW_CLASS;
1870 if (!::RegisterClassW(&wc))
1871 {
1872 if (ERROR_CLASS_ALREADY_EXISTS != ::GetLastError())
1873 {
1874 ExitWithLastError(hr, "Failed to register MonUtil window class.");
1875 }
1876 }
1877
1878 *pHwnd = ::CreateWindowExW(0, wc.lpszClassName, L"", 0, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, HWND_DESKTOP, NULL, wc.hInstance, pm);
1879 ExitOnNullWithLastError(*pHwnd, hr, "Failed to create window.");
1880
1881 // Rumor has it that drive arrival / removal events can be lost in the rare event that some other application higher up in z-order is hanging if we don't make our window topmost
1882 // SWP_NOACTIVATE is important so the currently active window doesn't lose focus
1883 SetWindowPos(*pHwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_DEFERERASE | SWP_NOACTIVATE);
1884
1885LExit:
1886 return hr;
1887}
1888
1889static HRESULT WaitForNetworkChanges(
1890 __inout HANDLE *phMonitor,
1891 __in MON_STRUCT *pm
1892 )
1893{
1894 HRESULT hr = S_OK;
1895 int nResult = 0;
1896 DWORD dwBytesReturned = 0;
1897 WSACOMPLETION wsaCompletion = { };
1898 WSAQUERYSET qsRestrictions = { };
1899
1900 qsRestrictions.dwSize = sizeof(WSAQUERYSET);
1901 qsRestrictions.dwNameSpace = NS_NLA;
1902
1903 if (NULL != *phMonitor)
1904 {
1905 ::WSALookupServiceEnd(*phMonitor);
1906 *phMonitor = NULL;
1907 }
1908
1909 if (::WSALookupServiceBegin(&qsRestrictions, LUP_RETURN_ALL, phMonitor))
1910 {
1911 hr = HRESULT_FROM_WIN32(::WSAGetLastError());
1912 ExitOnFailure(hr, "WSALookupServiceBegin() failed");
1913 }
1914
1915 wsaCompletion.Type = NSP_NOTIFY_HWND;
1916 wsaCompletion.Parameters.WindowMessage.hWnd = pm->hwnd;
1917 wsaCompletion.Parameters.WindowMessage.uMsg = MON_MESSAGE_NETWORK_STATUS_UPDATE;
1918 nResult = ::WSANSPIoctl(*phMonitor, SIO_NSP_NOTIFY_CHANGE, NULL, 0, NULL, 0, &dwBytesReturned, &wsaCompletion);
1919 if (SOCKET_ERROR != nResult || WSA_IO_PENDING != ::WSAGetLastError())
1920 {
1921 hr = HRESULT_FROM_WIN32(::WSAGetLastError());
1922 if (SUCCEEDED(hr))
1923 {
1924 hr = E_FAIL;
1925 }
1926 ExitOnFailure(hr, "WSANSPIoctl() failed with return code %i, wsa last error %u", nResult, ::WSAGetLastError());
1927 }
1928
1929LExit:
1930 return hr;
1931}
1932
1933static HRESULT UpdateWaitStatus(
1934 __in HRESULT hrNewStatus,
1935 __inout MON_WAITER_CONTEXT *pWaiterContext,
1936 __in DWORD dwRequestIndex,
1937 __out_opt DWORD *pdwNewRequestIndex
1938 )
1939{
1940 HRESULT hr = S_OK;
1941 DWORD dwNewRequestIndex;
1942 MON_REQUEST *pRequest = pWaiterContext->rgRequests + dwRequestIndex;
1943
1944 if (NULL != pdwNewRequestIndex)
1945 {
1946 *pdwNewRequestIndex = dwRequestIndex;
1947 }
1948
1949 if (SUCCEEDED(pRequest->hrStatus) || SUCCEEDED(hrNewStatus))
1950 {
1951 // If it's a network wait, notify as long as it's new status is successful because we *may* have lost some changes
1952 // before the wait was re-initiated. Otherwise, only notify if there was an interesting status change
1953 if (SUCCEEDED(pRequest->hrStatus) != SUCCEEDED(hrNewStatus) || (pRequest->fNetwork && SUCCEEDED(hrNewStatus)))
1954 {
1955 Notify(hrNewStatus, pWaiterContext, pRequest);
1956 }
1957
1958 if (SUCCEEDED(pRequest->hrStatus) && FAILED(hrNewStatus))
1959 {
1960 // If it's a network wait, notify coordinator thread that a network wait is failing
1961 if (pRequest->fNetwork && !::PostThreadMessageW(pWaiterContext->dwCoordinatorThreadId, MON_MESSAGE_NETWORK_WAIT_FAILED, 0, 0))
1962 {
1963 ExitWithLastError(hr, "Failed to send message to coordinator thread to notify a network wait started to fail");
1964 }
1965
1966 // Move the failing wait to the end of the list of waits and increment cRequestsFailing so WaitForMultipleObjects isn't passed an invalid handle
1967 ++pWaiterContext->cRequestsFailing;
1968 dwNewRequestIndex = pWaiterContext->cRequests - 1;
1969 MemArraySwapItems(reinterpret_cast<void *>(pWaiterContext->rgHandles), dwRequestIndex + 1, dwNewRequestIndex + 1, sizeof(*pWaiterContext->rgHandles));
1970 MemArraySwapItems(reinterpret_cast<void *>(pWaiterContext->rgRequests), dwRequestIndex, dwNewRequestIndex, sizeof(*pWaiterContext->rgRequests));
1971 // Reset pRequest to the newly swapped item
1972 pRequest = pWaiterContext->rgRequests + dwNewRequestIndex;
1973 if (NULL != pdwNewRequestIndex)
1974 {
1975 *pdwNewRequestIndex = dwNewRequestIndex;
1976 }
1977 }
1978 else if (FAILED(pRequest->hrStatus) && SUCCEEDED(hrNewStatus))
1979 {
1980 Assert(pWaiterContext->cRequestsFailing > 0);
1981 // If it's a network wait, notify coordinator thread that a network wait is succeeding again
1982 if (pRequest->fNetwork && !::PostThreadMessageW(pWaiterContext->dwCoordinatorThreadId, MON_MESSAGE_NETWORK_WAIT_SUCCEEDED, 0, 0))
1983 {
1984 ExitWithLastError(hr, "Failed to send message to coordinator thread to notify a network wait is succeeding again");
1985 }
1986
1987 --pWaiterContext->cRequestsFailing;
1988 dwNewRequestIndex = 0;
1989 MemArraySwapItems(reinterpret_cast<void *>(pWaiterContext->rgHandles), dwRequestIndex + 1, dwNewRequestIndex + 1, sizeof(*pWaiterContext->rgHandles));
1990 MemArraySwapItems(reinterpret_cast<void *>(pWaiterContext->rgRequests), dwRequestIndex, dwNewRequestIndex, sizeof(*pWaiterContext->rgRequests));
1991 // Reset pRequest to the newly swapped item
1992 pRequest = pWaiterContext->rgRequests + dwNewRequestIndex;
1993 if (NULL != pdwNewRequestIndex)
1994 {
1995 *pdwNewRequestIndex = dwNewRequestIndex;
1996 }
1997 }
1998 }
1999
2000 pRequest->hrStatus = hrNewStatus;
2001
2002LExit:
2003 return hr;
2004}