Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions include/wil/registry.h
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,49 @@ namespace reg
return S_OK;
}

/**
* @brief Queries whether the specified registry key is volatile (created with REG_OPTION_VOLATILE and therefore not
* persisted across a reboot)
* @param key The HKEY to query
* @param[out] isVolatile Receives true if the key is volatile, false otherwise. Only valid on success.
* @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
* @remark There is no Win32 API that reports key volatility; this relies on NtQueryKey + KeyFlagsInformation, which
* are not declared by the public SDK. NtQueryKey is resolved dynamically from ntdll so that callers are not
* required to link against ntdll, and the minimal information class/struct are declared locally.
*/
inline HRESULT is_key_volatile_nothrow(HKEY key, _Out_ bool* isVolatile) WI_NOEXCEPT
{
*isVolatile = false;

// KEY_INFORMATION_CLASS::KeyFlagsInformation and KEY_FLAGS_INFORMATION (from wdm.h) are not part of the public
// SDK, so the minimal definitions are reproduced here.
constexpr int c_KeyFlagsInformation = 5;
struct KEY_FLAGS_INFORMATION_t
{
ULONG Wow64Flags;
ULONG KeyFlags;
ULONG ControlFlags;
};
using NtQueryKey_t = LONG(__stdcall*)(HANDLE, int, PVOID, ULONG, PULONG);

const auto ntdll = ::GetModuleHandleW(L"ntdll.dll");
RETURN_HR_IF_NULL(E_NOINTERFACE, ntdll);
#pragma warning(suppress : 4191) // unsafe conversion from FARPROC; the signature is known
const auto pfnNtQueryKey = reinterpret_cast<NtQueryKey_t>(::GetProcAddress(ntdll, "NtQueryKey"));
RETURN_HR_IF_NULL(E_NOINTERFACE, pfnNtQueryKey);

KEY_FLAGS_INFORMATION_t info{};
ULONG resultSize{};
const LONG status = pfnNtQueryKey(key, c_KeyFlagsInformation, &info, sizeof(info), &resultSize);
if (status < 0)
{
RETURN_HR(HRESULT_FROM_NT(status));
}

*isVolatile = WI_IsFlagSet(info.KeyFlags, REG_OPTION_VOLATILE);
return S_OK;
}

#if defined(WIL_ENABLE_EXCEPTIONS)
/**
* @brief Queries for number of sub-keys
Expand Down Expand Up @@ -376,6 +419,20 @@ namespace reg
THROW_IF_FAILED(::wil::reg::get_last_write_filetime_nothrow(key, &lastModified));
return lastModified;
}

/**
* @brief Queries whether the specified registry key is volatile (created with REG_OPTION_VOLATILE and therefore not
* persisted across a reboot)
* @param key The HKEY to query
* @return true if the key is volatile, false otherwise
* @exception std::exception (including wil::ResultException) will be thrown on all failures
*/
inline bool is_key_volatile(HKEY key)
{
bool isVolatile{};
THROW_IF_FAILED(::wil::reg::is_key_volatile_nothrow(key, &isVolatile));
return isVolatile;
}
#endif // #if defined(WIL_ENABLE_EXCEPTIONS)

#if defined(WIL_ENABLE_EXCEPTIONS)
Expand Down
55 changes: 55 additions & 0 deletions tests/RegistryTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1328,6 +1328,61 @@ using ThrowingTypesToTest = std::tuple<DwordFns, GenericDwordFns, QwordFns, Gene
#endif // defined(WIL_ENABLE_EXCEPTIONS)
} // namespace

TEST_CASE("BasicRegistryTests::is_key_volatile", "[registry]")
{
const auto deleteHr = HRESULT_FROM_WIN32(::RegDeleteTreeW(HKEY_CURRENT_USER, testSubkey));
if (deleteHr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
{
REQUIRE_SUCCEEDED(deleteHr);
}

SECTION("is_key_volatile_nothrow: non-volatile key")
{
wil::unique_hkey hkey;
DWORD disposition{};
REQUIRE_SUCCEEDED(HRESULT_FROM_WIN32(::RegCreateKeyExW(
HKEY_CURRENT_USER, testSubkey, 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_READ, nullptr, hkey.put(), &disposition)));

bool isVolatile = true;
REQUIRE_SUCCEEDED(wil::reg::is_key_volatile_nothrow(hkey.get(), &isVolatile));
REQUIRE(isVolatile == false);
}

SECTION("is_key_volatile_nothrow: volatile key")
{
wil::unique_hkey hkey;
DWORD disposition{};
REQUIRE_SUCCEEDED(HRESULT_FROM_WIN32(::RegCreateKeyExW(
HKEY_CURRENT_USER, testSubkey, 0, nullptr, REG_OPTION_VOLATILE, KEY_READ, nullptr, hkey.put(), &disposition)));
REQUIRE(disposition == REG_CREATED_NEW_KEY);

bool isVolatile = false;
REQUIRE_SUCCEEDED(wil::reg::is_key_volatile_nothrow(hkey.get(), &isVolatile));
REQUIRE(isVolatile == true);
}

#ifdef WIL_ENABLE_EXCEPTIONS
SECTION("is_key_volatile: throwing variant")
{
// non-volatile parent so that both a volatile and a non-volatile child can be created beneath it
wil::unique_hkey parent;
DWORD disposition{};
REQUIRE_SUCCEEDED(HRESULT_FROM_WIN32(::RegCreateKeyExW(
HKEY_CURRENT_USER, testSubkey, 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_READ, nullptr, parent.put(), &disposition)));

wil::unique_hkey volatileKey;
REQUIRE_SUCCEEDED(HRESULT_FROM_WIN32(::RegCreateKeyExW(
parent.get(), L"vol", 0, nullptr, REG_OPTION_VOLATILE, KEY_READ, nullptr, volatileKey.put(), &disposition)));
REQUIRE(wil::reg::is_key_volatile(volatileKey.get()) == true);

wil::unique_hkey persistentKey;
REQUIRE_SUCCEEDED(HRESULT_FROM_WIN32(::RegCreateKeyExW(
parent.get(), L"persist", 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_READ, nullptr, persistentKey.put(), &disposition)));
REQUIRE(wil::reg::is_key_volatile(persistentKey.get()) == false);
}
#endif
}

TEMPLATE_LIST_TEST_CASE("BasicRegistryTests::simple types typed nothrow gets/sets", "[registry]", NoThrowTypesToTest)
{
const auto deleteHr = HRESULT_FROM_WIN32(::RegDeleteTreeW(HKEY_CURRENT_USER, testSubkey));
Expand Down
Loading