| 1 | // Copyright (C) Microsoft Corporation. All rights reserved. |
| 2 | |
| 3 | #pragma once |
| 4 | |
| 5 | #include <functional> |
| 6 | #include <windows.h> |
| 7 | #include <wil/stl.h> |
| 8 | #include <wil/resource.h> |
| 9 | #include <wil/registry.h> |
| 10 | |
| 11 | namespace wsl::windows::common { |
| 12 | |
| 13 | constexpr DWORD registry_notify_filter = REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_THREAD_AGNOSTIC; |
| 14 | |
| 15 | class slim_registry_watcher |
| 16 | { |
| 17 | public: |
| 18 | slim_registry_watcher() noexcept = default; |
| 19 | |
| 20 | // Pass a root key, sub key pair or use an empty string to use rootKey as the key to watch. |
| 21 | HRESULT create(HKEY rootKey, _In_ PCWSTR subKey, bool isRecursive, std::function<void(::wil::RegistryChangeKind)>&& callback) noexcept |
| 22 | { |
| 23 | ::wil::unique_hkey keyToWatch; |
| 24 | HRESULT hr = HRESULT_FROM_WIN32(::RegCreateKeyExW(rootKey, subKey, 0, nullptr, 0, KEY_NOTIFY, nullptr, &keyToWatch, nullptr)); |
| 25 | if (FAILED(hr)) |
| 26 | { |
| 27 | return hr; |
| 28 | } |
| 29 | return create_common(std::move(keyToWatch), isRecursive, std::move(callback)); |
| 30 | } |
| 31 | |
| 32 | HRESULT create(::wil::unique_hkey&& keyToWatch, bool isRecursive, std::function<void(::wil::RegistryChangeKind)>&& callback) noexcept |
| 33 | { |
| 34 | return create_common(std::move(keyToWatch), isRecursive, std::move(callback)); |
| 35 | } |
| 36 | |
| 37 | private: |
| 38 | // using the default d'tor, destruction must occur in this order |
| 39 | std::function<void(::wil::RegistryChangeKind)> m_callback; |
| 40 | ::wil::unique_hkey m_keyToWatch; |
| 41 | ::wil::unique_event_nothrow m_eventHandle; |
| 42 | ::wil::unique_threadpool_wait m_threadPoolWait; |
| 43 | bool m_isRecursive; |
| 44 | |
| 45 | static void __stdcall callback(PTP_CALLBACK_INSTANCE, void* context, TP_WAIT*, TP_WAIT_RESULT) noexcept |
| 46 | { |
| 47 | const auto this_ptr = static_cast<slim_registry_watcher*>(context); |
| 48 | |
| 49 | const LSTATUS error = ::RegNotifyChangeKeyValue( |
| 50 | this_ptr->m_keyToWatch.get(), this_ptr->m_isRecursive, registry_notify_filter, this_ptr->m_eventHandle.get(), TRUE); |
| 51 | |
| 52 | // Call the client before re-arming to ensure that multiple callbacks don't |
| 53 | // run concurrently. |
| 54 | switch (error) |
| 55 | { |
| 56 | case ERROR_SUCCESS: |
| 57 | case ERROR_ACCESS_DENIED: |
| 58 | // Normal modification: send RegistryChangeKind::Modify and re-arm. |
| 59 | this_ptr->m_callback(::wil::RegistryChangeKind::Modify); |
| 60 | ::SetThreadpoolWait(this_ptr->m_threadPoolWait.get(), this_ptr->m_eventHandle.get(), nullptr); |
| 61 | break; |
| 62 | |
| 63 | case ERROR_KEY_DELETED: |
| 64 | // Key deleted: send RegistryChangeKind::Delete but do not re-arm. |
| 65 | this_ptr->m_callback(::wil::RegistryChangeKind::Delete); |
| 66 | break; |
| 67 | |
| 68 | case ERROR_HANDLE_REVOKED: |
| 69 | // Handle revoked. This can occur if the user session ends before the watcher shuts-down. |
| 70 | // Do not re-arm since there is generally no way to respond. |
| 71 | break; |
| 72 | |
| 73 | default: |
| 74 | // failure here is a programming error. |
| 75 | FAIL_FAST_HR(HRESULT_FROM_WIN32(error)); |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | HRESULT create_common(::wil::unique_hkey&& keyToWatch, bool isRecursive, std::function<void(::wil::RegistryChangeKind)>&& callback) noexcept |
| 80 | { |
| 81 | RETURN_IF_FAILED(m_eventHandle.create()); |
| 82 | |
| 83 | m_threadPoolWait.reset(CreateThreadpoolWait(&slim_registry_watcher::callback, this, nullptr)); |
| 84 | RETURN_LAST_ERROR_IF(!m_threadPoolWait); |
| 85 | |
| 86 | // associate the notification handle with the threadpool before passing it to RegNotifyChangeKeyValue so we get immediate callbacks in the tp |
| 87 | SetThreadpoolWait(m_threadPoolWait.get(), m_eventHandle.get(), nullptr); |
| 88 | |
| 89 | // 'this' object must be fully created before calling RegNotifyChangeKeyValue, as callbacks can start immediately |
| 90 | m_keyToWatch = std::move(keyToWatch); |
| 91 | m_isRecursive = isRecursive; |
| 92 | m_callback = std::move(callback); |
| 93 | |
| 94 | // no failures after RegNotifyChangeKeyValue succeeds, |
| 95 | RETURN_IF_WIN32_ERROR(RegNotifyChangeKeyValue(m_keyToWatch.get(), m_isRecursive, registry_notify_filter, m_eventHandle.get(), TRUE)); |
| 96 | return S_OK; |
| 97 | } |
| 98 | }; |
| 99 | |
| 100 | } // namespace wsl::windows::common |