diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index 96e385a8a72..b8360927def 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -620,6 +620,7 @@ qt_internal_extend_target(Core CONDITION WIN32 text/qlatin1stringmatcher.cpp text/qunicodetools.cpp tools/qhash.cpp # Q_DECL_INIT_PRIORITY + kernel/qwinregistry.cpp # ntstatus.h clashes ) qt_internal_extend_target(Core CONDITION GCC AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "13" diff --git a/src/corelib/kernel/qwinregistry.cpp b/src/corelib/kernel/qwinregistry.cpp index cac5769e3b9..760e2d4e8df 100644 --- a/src/corelib/kernel/qwinregistry.cpp +++ b/src/corelib/kernel/qwinregistry.cpp @@ -1,13 +1,25 @@ // Copyright (C) 2019 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#define UMDF_USING_NTSTATUS // Avoid ntstatus redefinitions + #include "qwinregistry_p.h" #include +#include #include #include +#include + +// User mode version of ZwQueryKey, as per: +// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-zwquerykey +extern "C" NTSTATUS NTSYSCALLAPI NtQueryKey(HANDLE KeyHandle, int KeyInformationClass, + PVOID KeyInformation, ULONG Length, PULONG ResultLength); + QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + QWinRegistryKey::QWinRegistryKey() { } @@ -36,6 +48,39 @@ void QWinRegistryKey::close() } } +QString QWinRegistryKey::name() const +{ + if (!isValid()) + return {}; + + // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-_key_name_information + constexpr int kKeyNameInformation = 3; + struct KeyNameInformation { ULONG length = 0; WCHAR name[1] = {}; }; + + // Resolve name of key iteratively by first computing the needed size, + // and then querying the name, accounting for the possibility that the + // name length changes behind our back a few times. + DWORD keyInformationSize = 0; + for (int i = 0; i < 5; ++i) { + QByteArray buffer(keyInformationSize, 0u); + auto *keyNameInformation = reinterpret_cast(buffer.data()); + switch (NtQueryKey(m_key, kKeyNameInformation, keyNameInformation, + keyInformationSize, &keyInformationSize)) { + case STATUS_BUFFER_TOO_SMALL: + case STATUS_BUFFER_OVERFLOW: + continue; + case STATUS_SUCCESS: { + return QString::fromWCharArray(keyNameInformation->name, + keyNameInformation->length / sizeof(wchar_t)); + } + default: + return {}; + } + } + + return {}; +} + QVariant QWinRegistryKey::value(QStringView subKey) const { // NOTE: Empty value name is allowed in Windows registry, it means the default @@ -129,4 +174,17 @@ QString QWinRegistryKey::stringValue(QStringView subKey) const return value(subKey).value_or(QString()); } +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, const QWinRegistryKey &key) +{ + QDebugStateSaver saver(debug); + debug.nospace(); + debug << "QWinRegistryKey("; + if (key.isValid()) + debug << key.name(); + debug << ')'; + return debug; +} +#endif + QT_END_NAMESPACE diff --git a/src/corelib/kernel/qwinregistry_p.h b/src/corelib/kernel/qwinregistry_p.h index 8623ab1b1c6..4422b6a93b1 100644 --- a/src/corelib/kernel/qwinregistry_p.h +++ b/src/corelib/kernel/qwinregistry_p.h @@ -46,6 +46,8 @@ public: void close(); + QString name() const; + [[nodiscard]] QVariant value(QStringView subKey) const; template [[nodiscard]] std::optional value(QStringView subKey) const @@ -58,6 +60,10 @@ public: QString stringValue(QStringView subKey) const; +#ifndef QT_NO_DEBUG_STREAM + friend Q_CORE_EXPORT QDebug operator<<(QDebug dbg, const QWinRegistryKey &); +#endif + private: HKEY m_key = nullptr; }; diff --git a/tests/auto/corelib/kernel/qwinregistrykey/tst_qwinregistrykey.cpp b/tests/auto/corelib/kernel/qwinregistrykey/tst_qwinregistrykey.cpp index efaeed3300e..2d11d5b31f4 100644 --- a/tests/auto/corelib/kernel/qwinregistrykey/tst_qwinregistrykey.cpp +++ b/tests/auto/corelib/kernel/qwinregistrykey/tst_qwinregistrykey.cpp @@ -98,6 +98,7 @@ private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void qwinregistrykey(); + void name(); private: bool m_available = false; @@ -136,16 +137,7 @@ void tst_qwinregistrykey::cleanupTestCase() const LONG ret = RegOpenKeyExW(HKEY_CURRENT_USER, TEST_KEY, 0, KEY_READ | KEY_WRITE, &key); if (ret != ERROR_SUCCESS) return; - #define C_STR(View) reinterpret_cast(View.utf16()) - RegDeleteValueW(key, C_STR(TEST_STRING.first)); - RegDeleteValueW(key, C_STR(TEST_STRING_NULL.first)); - RegDeleteValueW(key, C_STR(TEST_STRINGLIST.first)); - RegDeleteValueW(key, C_STR(TEST_STRINGLIST_NULL.first)); - RegDeleteValueW(key, C_STR(TEST_DWORD.first)); - RegDeleteValueW(key, C_STR(TEST_QWORD.first)); - RegDeleteValueW(key, C_STR(TEST_BINARY.first)); - RegDeleteValueW(key, C_STR(TEST_DEFAULT.first)); - #undef C_STR + RegDeleteTree(key, nullptr); RegDeleteKeyW(HKEY_CURRENT_USER, TEST_KEY); RegCloseKey(key); } @@ -236,6 +228,44 @@ void tst_qwinregistrykey::qwinregistrykey() } } +void tst_qwinregistrykey::name() +{ + if (!m_available) + QSKIP("The test data is not ready."); + + QWinRegistryKey testKey(HKEY_CURRENT_USER, TEST_KEY); + QVERIFY(testKey.isValid()); + + // In practice: "\\REGISTRY\\USER\\S-1-5-21-4156955479-607706614-2054699034-1000\\Software\\tst_qwinregistrykey", + // or: "\\REGISTRY\\USER\\S-1-5-21-4156955479-607706614-2054699034-1000\\SOFTWARE\\tst_qwinregistrykey". + QVERIFY(testKey.name().toLower().endsWith("software\\tst_qwinregistrykey")); + + // Check that we can report the name of a key with a deep path + HKEY baseKey = testKey.handle(); + constexpr auto kKeyDepth = 500; + for (int i = 0; i < kKeyDepth; ++i) { + constexpr auto kChildKeyName = LR"(childKey)"; + + HKEY childKey = nullptr; + auto ret = RegCreateKeyEx(baseKey, kChildKeyName, 0, nullptr, 0, + KEY_READ | KEY_WRITE, nullptr, &childKey, nullptr); + QVERIFY(ret == ERROR_SUCCESS); + + if (i == kKeyDepth - 1) { + RegCloseKey(childKey); + QWinRegistryKey leafKey(baseKey, kChildKeyName); + QVERIFY(leafKey.isValid()); + const QString keyName = leafKey.name(); + QVERIFY(keyName.size() > 1000); + QVERIFY(keyName.endsWith("childKey\\childKey\\childKey")); + } else { + if (baseKey != testKey.handle()) + RegCloseKey(baseKey); + baseKey = childKey; + } + } +} + QTEST_MAIN(tst_qwinregistrykey) #include "tst_qwinregistrykey.moc"