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