123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- //*********************************************************
- //
- // 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 <winreg.h>
- #include <new.h> // 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<void(RegistryChangeKind)> &&callback)
- : m_callback(wistd::move(callback)), m_keyToWatch(wistd::move(keyToWatch)), m_isRecursive(isRecursive)
- {
- }
- wistd::function<void(RegistryChangeKind)> 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 *, decltype(&details::delete_registry_watcher_state),
- details::delete_registry_watcher_state, details::pointer_access_none> registry_watcher_state_resource_policy;
- }
- /// @endcond
- template <typename storage_t, typename err_policy = err_exception_policy>
- class registry_watcher_t : public storage_t
- {
- public:
- // forward all base class constructors...
- template <typename... args_t>
- explicit registry_watcher_t(args_t&&... args) WI_NOEXCEPT : storage_t(wistd::forward<args_t>(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<void(RegistryChangeKind)> &&callback)
- {
- static_assert(wistd::is_same<void, result>::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<void(RegistryChangeKind)> &&callback)
- {
- static_assert(wistd::is_same<void, result>::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<void(RegistryChangeKind)> &&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<void(RegistryChangeKind)> &&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<details::registry_watcher_state *>(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<void(RegistryChangeKind)> &&callback)
- {
- wistd::unique_ptr<details::registry_watcher_state> 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<registry_watcher_t<details::unique_storage<details::registry_watcher_state_resource_policy>, err_returncode_policy>> unique_registry_watcher_nothrow;
- typedef unique_any_t<registry_watcher_t<details::unique_storage<details::registry_watcher_state_resource_policy>, 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<void(RegistryChangeKind)> &&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<void(RegistryChangeKind)> &&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<void(RegistryChangeKind)> &&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<void(RegistryChangeKind)> &&callback)
- {
- return unique_registry_watcher_failfast(wistd::move(keyToWatch), isRecursive, wistd::move(callback));
- }
- #ifdef WIL_ENABLE_EXCEPTIONS
- typedef unique_any_t<registry_watcher_t<details::unique_storage<details::registry_watcher_state_resource_policy>, err_exception_policy >> unique_registry_watcher;
- inline unique_registry_watcher make_registry_watcher(HKEY rootKey, _In_ PCWSTR subKey, bool isRecursive, wistd::function<void(RegistryChangeKind)> &&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<void(RegistryChangeKind)> &&callback)
- {
- return unique_registry_watcher(wistd::move(keyToWatch), isRecursive, wistd::move(callback));
- }
- #endif // WIL_ENABLE_EXCEPTIONS
- } // namespace wil
- #endif
|