//********************************************************* // // Copyright (c) Microsoft. All rights reserved. // This code is licensed under the MIT License. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A // PARTICULAR PURPOSE AND NONINFRINGEMENT. // //********************************************************* #ifndef __WIL_REGISTRY_INCLUDED #define __WIL_REGISTRY_INCLUDED #ifdef _KERNEL_MODE #error This header is not supported in kernel-mode. #endif #include #include // new(std::nothrow) #include "resource.h" // unique_hkey namespace wil { //! The key name includes the absolute path of the key in the registry, always starting at a //! base key, for example, HKEY_LOCAL_MACHINE. size_t const max_registry_key_name_length = 255; //! The maximum number of characters allowed in a registry value's name. size_t const max_registry_value_name_length = 16383; // unique_registry_watcher/unique_registry_watcher_nothrow/unique_registry_watcher_failfast // These classes make it easy to execute a provided function when a // registry key changes (optionally recursively). Specify the key // either as a root key + path, or an open registry handle as wil::unique_hkey // or a raw HKEY value (that will be duplicated). // // Example use with exceptions base error handling: // auto watcher = wil::make_registry_watcher(HKEY_CURRENT_USER, L"Software\\MyApp", true, wil::RegistryChangeKind changeKind[] // { // if (changeKind == RegistryChangeKind::Delete) // { // watcher.reset(); // } // // invalidate cached registry data here // }); // // Example use with error code base error handling: // auto watcher = wil::make_registry_watcher_nothrow(HKEY_CURRENT_USER, L"Software\\MyApp", true, wil::RegistryChangeKind[] // { // // invalidate cached registry data here // }); // RETURN_IF_NULL_ALLOC(watcher); enum class RegistryChangeKind { Modify = 0, Delete = 1, }; /// @cond namespace details { struct registry_watcher_state { registry_watcher_state(unique_hkey &&keyToWatch, bool isRecursive, wistd::function &&callback) : m_callback(wistd::move(callback)), m_keyToWatch(wistd::move(keyToWatch)), m_isRecursive(isRecursive) { } wistd::function m_callback; unique_hkey m_keyToWatch; unique_event_nothrow m_eventHandle; // While not strictly needed since this is ref counted the thread pool wait // should be last to ensure that the other members are valid // when it is destructed as it will reference them. unique_threadpool_wait m_threadPoolWait; bool m_isRecursive; volatile long m_refCount = 1; srwlock m_lock; // Returns true if the refcount can be increased from a non zero value, // false it was zero impling that the object is in or on the way to the destructor. // In this case ReleaseFromCallback() should not be called. bool TryAddRef() { return ::InterlockedIncrement(&m_refCount) > 1; } void Release() { auto lock = m_lock.lock_exclusive(); if (0 == ::InterlockedDecrement(&m_refCount)) { lock.reset(); // leave the lock before deleting it. delete this; } } void ReleaseFromCallback(bool rearm) { auto lock = m_lock.lock_exclusive(); if (0 == ::InterlockedDecrement(&m_refCount)) { // Destroy the thread pool wait now to avoid the wait that would occur in the // destructor. That wait would cause a deadlock since we are doing this from the callback. ::CloseThreadpoolWait(m_threadPoolWait.release()); lock.reset(); // leave the lock before deleting it. delete this; // Sleep(1); // Enable for testing to find use after free bugs. } else if (rearm) { ::SetThreadpoolWait(m_threadPoolWait.get(), m_eventHandle.get(), nullptr); } } }; inline void delete_registry_watcher_state(_In_opt_ registry_watcher_state *watcherStorage) { watcherStorage->Release(); } typedef resource_policy registry_watcher_state_resource_policy; } /// @endcond template class registry_watcher_t : public storage_t { public: // forward all base class constructors... template explicit registry_watcher_t(args_t&&... args) WI_NOEXCEPT : storage_t(wistd::forward(args)...) {} // HRESULT or void error handling... typedef typename err_policy::result result; // Exception-based constructors registry_watcher_t(HKEY rootKey, _In_ PCWSTR subKey, bool isRecursive, wistd::function &&callback) { static_assert(wistd::is_same::value, "this constructor requires exceptions; use the create method"); create(rootKey, subKey, isRecursive, wistd::move(callback)); } registry_watcher_t(unique_hkey &&keyToWatch, bool isRecursive, wistd::function &&callback) { static_assert(wistd::is_same::value, "this constructor requires exceptions; use the create method"); create(wistd::move(keyToWatch), isRecursive, wistd::move(callback)); } // Pass a root key, sub key pair or use an empty string to use rootKey as the key to watch. result create(HKEY rootKey, _In_ PCWSTR subKey, bool isRecursive, wistd::function &&callback) { // Most use will want to create the key, consider adding an option for open as a future design change. unique_hkey keyToWatch; HRESULT hr = HRESULT_FROM_WIN32(RegCreateKeyExW(rootKey, subKey, 0, nullptr, 0, KEY_NOTIFY, nullptr, &keyToWatch, nullptr)); if (FAILED(hr)) { return err_policy::HResult(hr); } return err_policy::HResult(create_common(wistd::move(keyToWatch), isRecursive, wistd::move(callback))); } result create(unique_hkey &&keyToWatch, bool isRecursive, wistd::function &&callback) { return err_policy::HResult(create_common(wistd::move(keyToWatch), isRecursive, wistd::move(callback))); } private: // Factored into a standalone function to support Clang which does not support conversion of stateless lambdas // to __stdcall static void __stdcall callback(PTP_CALLBACK_INSTANCE, void *context, TP_WAIT *, TP_WAIT_RESULT) { #ifndef __WIL_REGISTRY_CHANGE_CALLBACK_TEST #define __WIL_REGISTRY_CHANGE_CALLBACK_TEST #endif __WIL_REGISTRY_CHANGE_CALLBACK_TEST auto watcherState = static_cast(context); if (watcherState->TryAddRef()) { // using auto reset event so don't need to manually reset. // failure here is a programming error. const LSTATUS error = RegNotifyChangeKeyValue(watcherState->m_keyToWatch.get(), watcherState->m_isRecursive, REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_THREAD_AGNOSTIC, watcherState->m_eventHandle.get(), TRUE); // Call the client before re-arming to ensure that multiple callbacks don't // run concurrently. switch (error) { case ERROR_SUCCESS: case ERROR_ACCESS_DENIED: // Normal modification: send RegistryChangeKind::Modify and re-arm. watcherState->m_callback(RegistryChangeKind::Modify); watcherState->ReleaseFromCallback(true); break; case ERROR_KEY_DELETED: // Key deleted, send RegistryChangeKind::Delete, do not re-arm. watcherState->m_callback(RegistryChangeKind::Delete); watcherState->ReleaseFromCallback(false); break; case ERROR_HANDLE_REVOKED: // Handle revoked. This can occur if the user session ends before // the watcher shuts-down. Disarm silently since there is generally no way to respond. watcherState->ReleaseFromCallback(false); break; default: FAIL_FAST_HR(HRESULT_FROM_WIN32(error)); } } } // This function exists to avoid template expansion of this code based on err_policy. HRESULT create_common(unique_hkey &&keyToWatch, bool isRecursive, wistd::function &&callback) { wistd::unique_ptr watcherState(new(std::nothrow) details::registry_watcher_state( wistd::move(keyToWatch), isRecursive, wistd::move(callback))); RETURN_IF_NULL_ALLOC(watcherState); RETURN_IF_FAILED(watcherState->m_eventHandle.create()); RETURN_IF_WIN32_ERROR(RegNotifyChangeKeyValue(watcherState->m_keyToWatch.get(), watcherState->m_isRecursive, REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_THREAD_AGNOSTIC, watcherState->m_eventHandle.get(), TRUE)); watcherState->m_threadPoolWait.reset(CreateThreadpoolWait(®istry_watcher_t::callback, watcherState.get(), nullptr)); RETURN_LAST_ERROR_IF(!watcherState->m_threadPoolWait); storage_t::reset(watcherState.release()); // no more failures after this, pass ownership SetThreadpoolWait(storage_t::get()->m_threadPoolWait.get(), storage_t::get()->m_eventHandle.get(), nullptr); return S_OK; } }; typedef unique_any_t, err_returncode_policy>> unique_registry_watcher_nothrow; typedef unique_any_t, err_failfast_policy>> unique_registry_watcher_failfast; inline unique_registry_watcher_nothrow make_registry_watcher_nothrow(HKEY rootKey, _In_ PCWSTR subKey, bool isRecursive, wistd::function &&callback) WI_NOEXCEPT { unique_registry_watcher_nothrow watcher; watcher.create(rootKey, subKey, isRecursive, wistd::move(callback)); return watcher; // caller must test for success using if (watcher) } inline unique_registry_watcher_nothrow make_registry_watcher_nothrow(unique_hkey &&keyToWatch, bool isRecursive, wistd::function &&callback) WI_NOEXCEPT { unique_registry_watcher_nothrow watcher; watcher.create(wistd::move(keyToWatch), isRecursive, wistd::move(callback)); return watcher; // caller must test for success using if (watcher) } inline unique_registry_watcher_failfast make_registry_watcher_failfast(HKEY rootKey, _In_ PCWSTR subKey, bool isRecursive, wistd::function &&callback) { return unique_registry_watcher_failfast(rootKey, subKey, isRecursive, wistd::move(callback)); } inline unique_registry_watcher_failfast make_registry_watcher_failfast(unique_hkey &&keyToWatch, bool isRecursive, wistd::function &&callback) { return unique_registry_watcher_failfast(wistd::move(keyToWatch), isRecursive, wistd::move(callback)); } #ifdef WIL_ENABLE_EXCEPTIONS typedef unique_any_t, err_exception_policy >> unique_registry_watcher; inline unique_registry_watcher make_registry_watcher(HKEY rootKey, _In_ PCWSTR subKey, bool isRecursive, wistd::function &&callback) { return unique_registry_watcher(rootKey, subKey, isRecursive, wistd::move(callback)); } inline unique_registry_watcher make_registry_watcher(unique_hkey &&keyToWatch, bool isRecursive, wistd::function &&callback) { return unique_registry_watcher(wistd::move(keyToWatch), isRecursive, wistd::move(callback)); } #endif // WIL_ENABLE_EXCEPTIONS } // namespace wil #endif