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