Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
Registry.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#if qStroika_Platform_Windows
7#include <Windows.h>
8#else
9#error "WINDOWS REQUIRED FOR THIS MODULE"
10#endif
11
12#include "Stroika/Foundation/Execution/DLLSupport.h"
13#include "Stroika/Foundation/Execution/Platform/Windows/Exception.h"
14
16
17#include "Registry.h"
18
19using namespace Stroika::Foundation;
21using namespace Stroika::Foundation::Common::Platform::Windows;
23using namespace Stroika::Foundation::Debug;
25
26using Memory::MakeSharedPtr;
27
28// Comment this in to turn on aggressive noisy DbgTrace in this module
29//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
30
31/*
32 ********************************************************************************
33 ********************************* RegistryKey **********************************
34 ********************************************************************************
35 */
36RegistryKey::RegistryKey (HKEY parentKey, const String& path, REGSAM samDesired)
37 : fKey_{OpenPath_ (parentKey, path, samDesired)}
38 , fOwned_{true}
39{
40}
41
42/*
43 * Walk the given path (in segments) - and make sure each exists, and create each segment if it doesn't
44 * exist.Finally - do a regular registry open with access permissions 'samDesired'.< / p>
45 */
46HKEY RegistryKey::OpenPath_ (HKEY parentKey, const String& path, REGSAM samDesired)
47{
48#if USE_NOISY_TRACE_IN_THIS_MODULE_
50 Stroika_Foundation_Debug_OptionalizeTraceArgs (L"{}::RegistryKey::OpenPath_", "parentKey={}, path='{}'"_f, parentKey, path)};
51#endif
52 Require (parentKey != nullptr);
53 Require (parentKey != INVALID_HANDLE_VALUE);
54 // KEY_READ - and which view of the registry to read (KEY_WOW64_64KEY or KEY_WOW64_32KEY), which grants nothing more
55 Require ((samDesired & ~(KEY_WOW64_64KEY | KEY_WOW64_32KEY)) == KEY_READ); // @todo - later allow others so long as they are non-destructive/readonly
56 HKEY result{};
57 ThrowIfNotERROR_SUCCESS (::RegOpenKeyEx (parentKey, path.AsSDKString ().c_str (), 0, samDesired, &result));
58 Ensure (result != nullptr and result != INVALID_HANDLE_VALUE);
59 return result;
60}
61
62namespace {
63 // missingReturnsEmpty flag because I happen to have defined the semantics for Lookup () that way, and its reasonable
64 // so lets not change now... --LGP 2020-07-05
65 VariantValue ExtractValue_ (HKEY key, const TCHAR* path, bool missingReturnsEmpty)
66 {
67 DWORD dwType = 0;
68 DWORD dwCountInBytes = 0;
69 LONG lResult = ::RegQueryValueExW (key, path, nullptr, &dwType, nullptr, &dwCountInBytes);
70 if (lResult == ERROR_SUCCESS) {
71 switch (dwType) {
72 case REG_SZ:
73 case REG_EXPAND_SZ: {
74 wstring strValue;
75 if (dwCountInBytes != 0) {
76 Assert (dwCountInBytes % sizeof (wchar_t) == 0); // @todo - we should bullet proof this code more but for now, assert if an issue
77 size_t nChars = dwCountInBytes / 2 - 1; // includes NUL-byte
78 strValue.resize (nChars);
79 ThrowIfNotERROR_SUCCESS (::RegQueryValueExW (key, path, nullptr, &dwType, (LPBYTE) & (*strValue.begin ()), &dwCountInBytes));
80 Assert (strValue[nChars] == '\0');
81 }
82 return VariantValue{strValue};
83 } break;
84 case REG_DWORD: {
85 DWORD result{};
86 Assert (dwCountInBytes == sizeof (DWORD));
87 ThrowIfNotERROR_SUCCESS (::RegQueryValueExW (key, path, nullptr, &dwType, (LPBYTE)&result, &dwCountInBytes));
88 Assert (dwCountInBytes == sizeof (DWORD));
89 return VariantValue{result};
90 } break;
91 default: {
92 WeakAssert (false); // NYI
93 Execution::Throw (Execution::Exception<> ("Unsupported registry format"sv));
94 } break;
95 }
96 }
97 // Just treat this as an error and revisit later if we get issues and maybe change / document behavior for Lookup()
98 else if (lResult == ERROR_FILE_NOT_FOUND and missingReturnsEmpty) {
99 return VariantValue{}; // @todo reconsider if we should throw here or not??? -- LGP 2020-07-04
100 }
101 else {
102 Execution::Throw (Execution::SystemErrorException{lResult, Execution::Platform::Windows::Win32_error_category ()});
103 }
104 }
105}
106
108{
109#if USE_NOISY_TRACE_IN_THIS_MODULE_
110 Debug::TraceContextBumper trcCtx{Stroika_Foundation_Debug_OptionalizeTraceArgs ("{}::RegistryKey::GetFullPathOfKey")};
111#endif
112 // Based on https://stackoverflow.com/questions/937044/determine-path-to-registry-key-from-hkey-handle-in-c
113
114 using NTSTATUS = LONG;
115#ifndef STATUS_SUCCESS
116 const auto STATUS_SUCCESS{((NTSTATUS)0x00000000L)};
117#endif
118#ifndef STATUS_BUFFER_TOO_SMALL
119 constexpr auto STATUS_BUFFER_TOO_SMALL{(NTSTATUS)0xC0000023L};
120#endif
121 std::wstring keyPath;
122 if (fKey_ != NULL) {
123 Execution::DLLLoader dll{L"ntdll.dll"};
124 using NtQueryKeyType = DWORD (__stdcall*) (HANDLE KeyHandle, int KeyInformationClass, PVOID KeyInformation, ULONG Length, PULONG ResultLength);
125 NtQueryKeyType func = reinterpret_cast<NtQueryKeyType> (dll.GetProcAddress ("NtQueryKey"));
126 DWORD size{0};
127 DWORD result = func (fKey_, 3, 0, 0, &size);
128 if (result == STATUS_BUFFER_TOO_SMALL) {
129 size = size + 2;
130 wchar_t* buffer = new (std::nothrow) wchar_t[size / sizeof (wchar_t)]; // size is in bytes
131 if (buffer != NULL) {
132 result = func (fKey_, 3, buffer, size, &size);
133 if (result == STATUS_SUCCESS) {
134 buffer[size / sizeof (wchar_t)] = L'\0';
135 keyPath = std::wstring (buffer + 2);
136 }
137 delete[] buffer;
138 }
139 }
140 }
141 return keyPath;
142}
143
145{
146 Require (fKey_ != INVALID_HANDLE_VALUE);
147 {
148 // RegQueryValueExW doesn't support this directly, but its quite handy, and we document we support this
149 // (allowing path syntax in the valuePath to lookup)
150 size_t lastBackSlash = valuePath.rfind ('\\');
151 if (lastBackSlash != SDKString::npos) {
152 // @todo - check on TYPE of exception and if cuz not there, return empty, and if cuz of permissions (etc)
153 // pass along exception (note https://docs.microsoft.com/en-us/windows/desktop/api/winreg/nf-winreg-regopenkeyexa doesnt document returned error codes for not found)
154 try {
155 return RegistryKey{fKey_, valuePath.substr (0, lastBackSlash)}.Lookup (valuePath.substr (lastBackSlash + 1));
156 }
157 catch (const system_error& e) {
158 // catch/translate because the part not found could be in the PATH and then RegistryKey would throw
159 if (Execution::IsA (e, errc::no_such_file_or_directory)) { // windows error ERROR_FILE_NOT_FOUND
160 return VariantValue{};
161 }
163 }
164 }
165 }
166 return ExtractValue_ (fKey_, valuePath.As<wstring> ().c_str (), true);
167}
168
169Traversal::Iterable<shared_ptr<RegistryKey>> RegistryKey::EnumerateSubKeys () const
170{
171 Require (fKey_ != INVALID_HANDLE_VALUE);
172
173 // Use a generator to avoid keeping tons of registry key objects in memory at the same time (in case this is a limited resource)
174 struct Context_ {
175 HKEY fParentKey;
176 int fCurIndex{0};
177 };
178 auto myContext = MakeSharedPtr<Context_> ();
179 myContext->fParentKey = fKey_;
180 auto getNext = [myContext] () -> optional<shared_ptr<RegistryKey>> {
181 Memory::StackBuffer<TCHAR> achKeyBuf{Memory::eUninitialized, 1024};
182 DWORD cbName = static_cast<DWORD> (achKeyBuf.size ()); // size of name string
183 retry:
184 auto retCode = ::RegEnumKeyEx (myContext->fParentKey, myContext->fCurIndex, achKeyBuf.begin (), &cbName, nullptr, nullptr, nullptr, nullptr);
185 if (retCode == ERROR_NO_MORE_ITEMS) {
186 return nullopt; // done
187 }
188 if (retCode == ERROR_MORE_DATA) {
189 achKeyBuf.GrowToSize (achKeyBuf.size () * 2);
190 goto retry;
191 }
192 ThrowIfNotERROR_SUCCESS (retCode);
193 myContext->fCurIndex++;
194 Assert (cbName <= achKeyBuf.size ());
195 achKeyBuf.resize (cbName);
196#if USE_NOISY_TRACE_IN_THIS_MODULE_
197 DbgTrace ("returning next child key: {}"_f, achKeyBuf);
198#endif
199 return MakeSharedPtr<RegistryKey> (myContext->fParentKey, String::FromSDKString (span{achKeyBuf})); // note bufsize right - not NUL terminated
200 };
201 return Traversal::CreateGenerator<shared_ptr<RegistryKey>> (getNext);
202}
203
205{
207 for (int i = 0;; ++i) {
208 Memory::StackBuffer<TCHAR> achKeyBuf{Memory::eUninitialized, 1024};
209 DWORD cbName = static_cast<DWORD> (achKeyBuf.size ()); // size of name string
210 retry:
211 auto retCode = ::RegEnumValue (fKey_, i, achKeyBuf.begin (), &cbName, nullptr, nullptr, nullptr, nullptr);
212 if (retCode == ERROR_NO_MORE_ITEMS) {
213 break;
214 }
215 if (retCode == ERROR_MORE_DATA) {
216 achKeyBuf.GrowToSize (achKeyBuf.size () * 2);
217 goto retry;
218 }
219 ThrowIfNotERROR_SUCCESS (retCode);
220 result.Add (String::FromSDKString (AdjustNulTerminatedStringSpan (span{achKeyBuf})), ExtractValue_ (fKey_, achKeyBuf.begin (), false));
221 }
222 return result;
223}
#define WeakAssert(c)
A WeakAssert() is for things that aren't guaranteed to be true, but are overwhelmingly likely to be t...
Definition Assertions.h:441
#define DbgTrace
Definition Trace.h:327
#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
Definition Trace.h:288
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
static String FromSDKString(const SDKChar *from)
Definition String.inl:449
nonvirtual size_t rfind(Character c) const
Definition String.inl:1073
nonvirtual SDKString AsSDKString() const
Definition String.inl:804
nonvirtual String substr(size_t from, size_t count=npos) const
Definition String.inl:1090
nonvirtual DataExchange::VariantValue Lookup(const Characters::String &valuePath) const
Definition Registry.cpp:144
nonvirtual Characters::String GetFullPathOfKey() const
Definition Registry.cpp:107
A Mapping uniquely associates two elements: a key and a value (use Assocation to allow multiple value...
nonvirtual bool Add(ArgByValueType< key_type > key, ArgByValueType< mapped_type > newElt, AddReplaceMode addReplaceMode=AddReplaceMode::eAddReplaces)
Definition Mapping.inl:186
Simple variant-value (case variant union) object, with (variant) basic types analogous to a value in ...
Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::excep...
Definition Exceptions.h:219
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:234
span< CHAR_T, EXTENT > AdjustNulTerminatedStringSpan(span< CHAR_T, EXTENT > s)
often apply this adjustment to return the non-nul string prefix from a span of characters.
Definition SDKString.inl:21
void Throw(T &&e2Throw)
like the builtin C++ 'throw', but stamps in the current Activity context and does helpful,...
Definition Throw.inl:54
bool IsA(const error_code &ec, error_condition cond) noexcept
Does this error MEAN the given condition - regardless of how it happens to be represented?