Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(249)

Side by Side Diff: chrome_elf/nt_registry/nt_registry.cc

Issue 1841573002: [Chrome ELF] New NT registry API. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Branch update, and CreateRegKey adjustment. Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome_elf/nt_registry/nt_registry.h"
6
7 namespace {
8
9 // Function pointers used for registry access.
10 RtlInitUnicodeStringFunction g_rtl_init_unicode_string = nullptr;
11 NtCreateKeyFunction g_nt_create_key = nullptr;
12 NtDeleteKeyFunction g_nt_delete_key = nullptr;
13 NtOpenKeyExFunction g_nt_open_key_ex = nullptr;
14 NtCloseFunction g_nt_close = nullptr;
15 NtQueryValueKeyFunction g_nt_query_value_key = nullptr;
16 NtSetValueKeyFunction g_nt_set_value_key = nullptr;
17
18 // Lazy init. No concern about concurrency in chrome_elf.
19 bool g_initialized = false;
20 bool g_system_install = false;
21 const size_t g_kMaxPathLen = 255;
22 wchar_t g_kRegPathHKLM[] = L"\\Registry\\Machine\\";
23 wchar_t g_kRegPathHKCU[g_kMaxPathLen] = L"";
24 wchar_t g_current_user_sid_string[g_kMaxPathLen] = L"";
25 wchar_t g_override_path[g_kMaxPathLen] = L"";
26
27 // Not using install_util, to prevent circular dependency.
28 bool IsThisProcSystem() {
29 wchar_t program_dir[MAX_PATH] = {};
30 wchar_t* cmd_line = GetCommandLineW();
31 // If our command line starts with the "Program Files" or
32 // "Program Files (x86)" path, we're system.
33 DWORD ret = ::GetEnvironmentVariable(L"PROGRAMFILES", program_dir, MAX_PATH);
34 if (ret && ret < MAX_PATH && !::wcsncmp(cmd_line, program_dir, ret))
35 return true;
36
37 ret = ::GetEnvironmentVariable(L"PROGRAMFILES(X86)", program_dir, MAX_PATH);
38 if (ret && ret < MAX_PATH && !::wcsncmp(cmd_line, program_dir, ret))
39 return true;
40
41 return false;
42 }
43
44 bool InitNativeRegApi() {
45 HMODULE ntdll = ::GetModuleHandleW(L"ntdll.dll");
46
47 // Setup the global function pointers for registry access.
48 g_rtl_init_unicode_string = reinterpret_cast<RtlInitUnicodeStringFunction>(
49 ::GetProcAddress(ntdll, "RtlInitUnicodeString"));
50
51 g_nt_create_key = reinterpret_cast<NtCreateKeyFunction>(
52 ::GetProcAddress(ntdll, "NtCreateKey"));
53
54 g_nt_delete_key = reinterpret_cast<NtDeleteKeyFunction>(
55 ::GetProcAddress(ntdll, "NtDeleteKey"));
56
57 g_nt_open_key_ex = reinterpret_cast<NtOpenKeyExFunction>(
58 ::GetProcAddress(ntdll, "NtOpenKeyEx"));
59
60 g_nt_close =
61 reinterpret_cast<NtCloseFunction>(::GetProcAddress(ntdll, "NtClose"));
62
63 g_nt_query_value_key = reinterpret_cast<NtQueryValueKeyFunction>(
64 ::GetProcAddress(ntdll, "NtQueryValueKey"));
65
66 g_nt_set_value_key = reinterpret_cast<NtSetValueKeyFunction>(
67 ::GetProcAddress(ntdll, "NtSetValueKey"));
68
69 if (!g_rtl_init_unicode_string || !g_nt_create_key || !g_nt_open_key_ex ||
70 !g_nt_delete_key || !g_nt_close || !g_nt_query_value_key ||
71 !g_nt_set_value_key)
72 return false;
73
74 // We need to set HKCU based on the sid of the current user account.
75 RtlFormatCurrentUserKeyPathFunction rtl_current_user_string =
76 reinterpret_cast<RtlFormatCurrentUserKeyPathFunction>(
77 ::GetProcAddress(ntdll, "RtlFormatCurrentUserKeyPath"));
78
79 RtlFreeUnicodeStringFunction rtl_free_unicode_str =
80 reinterpret_cast<RtlFreeUnicodeStringFunction>(
81 ::GetProcAddress(ntdll, "RtlFreeUnicodeString"));
82
83 if (!rtl_current_user_string || !rtl_free_unicode_str)
84 return false;
85
86 UNICODE_STRING current_user_reg_path;
87 if (!NT_SUCCESS(rtl_current_user_string(&current_user_reg_path)))
88 return false;
89
90 // Finish setting up global HKCU path.
91 ::wcsncat(g_kRegPathHKCU, current_user_reg_path.Buffer, (g_kMaxPathLen - 1));
92 ::wcsncat(g_kRegPathHKCU, L"\\",
93 (g_kMaxPathLen - ::wcslen(g_kRegPathHKCU) - 1));
94 // Keep the sid string as well.
95 wchar_t* ptr = ::wcsrchr(current_user_reg_path.Buffer, L'\\');
96 ptr++;
97 ::wcsncpy(g_current_user_sid_string, ptr, (g_kMaxPathLen - 1));
98 rtl_free_unicode_str(&current_user_reg_path);
99
100 // Figure out if we're a system or user install.
101 g_system_install = IsThisProcSystem();
102
103 g_initialized = true;
104 return true;
105 }
106
107 const wchar_t* ConvertRootKey(nt::ROOT_KEY root) {
108 nt::ROOT_KEY key = root;
109
110 if (!root) {
111 // AUTO
112 key = g_system_install ? nt::HKLM : nt::HKCU;
113 }
114
115 if ((key == nt::HKCU) && (::wcslen(nt::HKCU_override) != 0)) {
116 std::wstring temp(g_kRegPathHKCU);
117 temp.append(nt::HKCU_override);
118 temp.append(L"\\");
119 ::wcsncpy(g_override_path, temp.c_str(), g_kMaxPathLen - 1);
120 return g_override_path;
121 } else if ((key == nt::HKLM) && (::wcslen(nt::HKLM_override) != 0)) {
122 std::wstring temp(g_kRegPathHKCU);
123 temp.append(nt::HKLM_override);
124 temp.append(L"\\");
125 ::wcsncpy(g_override_path, temp.c_str(), g_kMaxPathLen - 1);
126 return g_override_path;
127 }
128
129 if (key == nt::HKCU)
130 return g_kRegPathHKCU;
131 else
132 return g_kRegPathHKLM;
133 }
134
135 // Turns a root and subkey path into the registry base hive and the rest of the
136 // subkey tokens.
137 // - |converted_root| should come directly out of ConvertRootKey function.
138 // - E.g. base hive: "\Registry\Machine\", "\Registry\User\<SID>\".
139 bool ParseFullRegPath(const wchar_t* converted_root,
140 const wchar_t* subkey_path,
141 std::wstring* out_base,
142 std::vector<std::wstring>* subkeys) {
robertshield 2016/07/12 04:26:23 I don't fully understand this function and feel li
penny 2016/07/13 00:27:55 It's a fair question Robert. It seems at first li
143 out_base->clear();
144 subkeys->clear();
145 std::wstring temp(converted_root);
146 temp.append(subkey_path);
147
148 // Tokenize the full path.
149 size_t find_start = 0;
150 size_t delimiter = temp.find_first_of(L'\\');
151 while (delimiter != std::wstring::npos) {
152 std::wstring token = temp.substr(find_start, delimiter - find_start);
153 if (!token.empty())
154 subkeys->push_back(token);
155 find_start = delimiter + 1;
156 delimiter = temp.find_first_of(L'\\', find_start);
157 }
158 if (!temp.empty() && find_start < temp.length())
159 // Get the last token.
160 subkeys->push_back(temp.substr(find_start));
161
162 // The base hive for HKCU needs to include the user SID.
163 uint32_t num_base_tokens = 2;
164 const wchar_t* hkcu = L"\\REGISTRY\\USER\\";
165 if (0 == ::wcsnicmp(converted_root, hkcu, ::wcslen(hkcu)))
166 num_base_tokens = 3;
167
168 if (subkeys->size() < num_base_tokens)
169 return false;
170
171 // Pull out the base hive.
172 out_base->push_back(L'\\');
173 for (size_t i = 0; i < num_base_tokens; i++) {
174 out_base->append((*subkeys)[i].c_str());
175 out_base->push_back(L'\\');
176 }
177 subkeys->erase(subkeys->begin(), subkeys->begin() + num_base_tokens);
178
179 return true;
180 }
181
182 NTSTATUS CreateKeyWrapper(const std::wstring& key_path,
183 ACCESS_MASK access,
184 HANDLE* out_handle,
185 ULONG* create_or_open OPTIONAL) {
186 UNICODE_STRING key_path_uni = {};
187 g_rtl_init_unicode_string(&key_path_uni, key_path.c_str());
188
189 OBJECT_ATTRIBUTES obj = {};
190 InitializeObjectAttributes(&obj, &key_path_uni, OBJ_CASE_INSENSITIVE, NULL,
191 nullptr);
192
193 return g_nt_create_key(out_handle, access, &obj, 0, nullptr,
194 REG_OPTION_NON_VOLATILE, create_or_open);
195 }
196
197 } // namespace
198
199 namespace nt {
200
201 const size_t g_kRegMaxPathLen = 255;
202 wchar_t HKLM_override[g_kRegMaxPathLen] = L"";
203 wchar_t HKCU_override[g_kRegMaxPathLen] = L"";
204
205 //------------------------------------------------------------------------------
206 // Create, open, delete, close functions
207 //------------------------------------------------------------------------------
208
209 bool CreateRegKey(ROOT_KEY root,
210 const wchar_t* key_path,
211 ACCESS_MASK access,
212 HANDLE* out_handle OPTIONAL) {
213 if (!g_initialized)
214 InitNativeRegApi();
215
216 std::wstring current_path;
217 std::vector<std::wstring> subkeys;
218 if (!ParseFullRegPath(ConvertRootKey(root), key_path, &current_path,
219 &subkeys))
220 return false;
221
222 // Open the base hive first. It should always exist already.
223 HANDLE last_handle = INVALID_HANDLE_VALUE;
224 NTSTATUS status =
225 CreateKeyWrapper(current_path, access, &last_handle, nullptr);
226 if (!NT_SUCCESS(status))
227 return false;
228
229 size_t subkeys_size = subkeys.size();
230 if (subkeys_size != 0)
231 g_nt_close(last_handle);
232
233 // Recursively open/create each subkey.
234 std::vector<HANDLE> rollback;
235 bool success = true;
236 for (size_t i = 0; i < subkeys_size; i++) {
237 current_path.append(subkeys[i]);
238 current_path.push_back(L'\\');
239
240 // Process the latest subkey.
241 ULONG created = 0;
242 HANDLE key_handle = INVALID_HANDLE_VALUE;
243 status =
244 CreateKeyWrapper(current_path.c_str(), access, &key_handle, &created);
245 if (!NT_SUCCESS(status)) {
246 success = false;
247 break;
248 }
249
250 if (i == subkeys_size - 1) {
251 last_handle = key_handle;
252 } else {
253 // Save any subkey handle created, in case of rollback.
254 if (created == REG_CREATED_NEW_KEY)
255 rollback.push_back(&key_handle);
256 else
257 g_nt_close(key_handle);
258 }
259 }
260
261 if (!success) {
262 // Delete any subkeys created.
263 for (HANDLE handle : rollback) {
264 g_nt_delete_key(handle);
265 }
266 }
267 for (HANDLE handle : rollback) {
268 // Close the rollback handles, on success or failure.
269 g_nt_close(handle);
270 }
271 if (!success)
272 return false;
273
274 // See if caller wants the handle left open.
275 if (out_handle)
276 *out_handle = last_handle;
277 else
278 g_nt_close(last_handle);
279
280 return true;
281 }
282
283 bool OpenRegKey(ROOT_KEY root,
284 const wchar_t* key_path,
285 ACCESS_MASK access,
286 HANDLE* out_handle,
287 NTSTATUS* error_code OPTIONAL) {
288 if (!g_initialized)
289 InitNativeRegApi();
290
291 NTSTATUS status = STATUS_UNSUCCESSFUL;
292 UNICODE_STRING key_path_uni = {};
293 OBJECT_ATTRIBUTES obj = {};
294 *out_handle = INVALID_HANDLE_VALUE;
295
296 std::wstring full_path(ConvertRootKey(root));
297 full_path.append(key_path);
298
299 g_rtl_init_unicode_string(&key_path_uni, full_path.c_str());
300 InitializeObjectAttributes(&obj, &key_path_uni, OBJ_CASE_INSENSITIVE, NULL,
301 NULL);
302
303 status = g_nt_open_key_ex(out_handle, access, &obj, 0);
304 // See if caller wants the NTSTATUS.
305 if (error_code)
306 *error_code = status;
307
308 if (NT_SUCCESS(status))
309 return true;
310
311 return false;
312 }
313
314 bool DeleteRegKey(HANDLE key) {
315 if (!g_initialized)
316 InitNativeRegApi();
317
318 NTSTATUS status = STATUS_UNSUCCESSFUL;
319
320 status = g_nt_delete_key(key);
321
322 if (NT_SUCCESS(status))
323 return true;
324
325 return false;
326 }
327
328 // wrapper function
329 bool DeleteRegKey(ROOT_KEY root, const wchar_t* key_path) {
330 HANDLE key = INVALID_HANDLE_VALUE;
331
332 if (!OpenRegKey(root, key_path, DELETE, &key, nullptr))
333 return false;
334
335 if (!DeleteRegKey(key)) {
336 CloseRegKey(key);
337 return false;
338 }
339
340 CloseRegKey(key);
341 return true;
342 }
343
344 void CloseRegKey(HANDLE key) {
345 if (!g_initialized)
346 InitNativeRegApi();
347 g_nt_close(key);
348 }
349
350 //------------------------------------------------------------------------------
351 // Getter functions
352 //------------------------------------------------------------------------------
353
354 bool QueryRegKeyValue(HANDLE key,
355 const wchar_t* value_name,
356 ULONG* out_type,
357 BYTE** out_buffer,
358 DWORD* out_size) {
359 if (!g_initialized)
360 InitNativeRegApi();
361
362 NTSTATUS ntstatus = STATUS_UNSUCCESSFUL;
363 UNICODE_STRING value_uni = {};
364 g_rtl_init_unicode_string(&value_uni, value_name);
365 DWORD size_needed = 0;
366 bool success = false;
367
368 // First call to find out how much room we need for the value!
369 ntstatus = g_nt_query_value_key(key, &value_uni, KeyValueFullInformation,
370 nullptr, 0, &size_needed);
371 if (ntstatus != STATUS_BUFFER_TOO_SMALL)
372 return false;
373
374 KEY_VALUE_FULL_INFORMATION* value_info =
375 reinterpret_cast<KEY_VALUE_FULL_INFORMATION*>(new BYTE[size_needed]);
376
377 // Second call to get the value.
378 ntstatus = g_nt_query_value_key(key, &value_uni, KeyValueFullInformation,
379 value_info, size_needed, &size_needed);
380 if (NT_SUCCESS(ntstatus)) {
381 *out_type = value_info->Type;
382 *out_size = value_info->DataLength;
383 *out_buffer = new BYTE[*out_size];
384 ::memcpy(*out_buffer,
385 (reinterpret_cast<BYTE*>(value_info) + value_info->DataOffset),
386 *out_size);
387 success = true;
388 }
389
390 delete[] value_info;
391 return success;
392 }
393
394 // wrapper function
395 bool QueryRegValueDWORD(HANDLE key,
396 const wchar_t* value_name,
397 DWORD* out_dword) {
398 ULONG type = REG_NONE;
399 BYTE* value_bytes = nullptr;
400 DWORD ret_size = 0;
401
402 if (!QueryRegKeyValue(key, value_name, &type, &value_bytes, &ret_size) ||
403 type != REG_DWORD)
404 return false;
405
406 *out_dword = *(reinterpret_cast<DWORD*>(value_bytes));
407
408 delete[] value_bytes;
409 return true;
410 }
411
412 // wrapper function
413 bool QueryRegValueDWORD(ROOT_KEY root,
414 const wchar_t* key_path,
415 const wchar_t* value_name,
416 DWORD* out_dword) {
417 HANDLE key = INVALID_HANDLE_VALUE;
418
419 if (!OpenRegKey(root, key_path, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &key,
420 NULL))
421 return false;
422
423 if (!QueryRegValueDWORD(key, value_name, out_dword)) {
424 CloseRegKey(key);
425 return false;
426 }
427
428 CloseRegKey(key);
429 return true;
430 }
431
432 // wrapper function
433 bool QueryRegValueSZ(HANDLE key,
434 const wchar_t* value_name,
435 std::wstring* out_sz) {
436 BYTE* value_bytes = nullptr;
437 DWORD ret_size = 0;
438 ULONG type = REG_NONE;
439
440 if (!QueryRegKeyValue(key, value_name, &type, &value_bytes, &ret_size) ||
441 type != REG_SZ)
442 return false;
443
444 *out_sz = reinterpret_cast<wchar_t*>(value_bytes);
445
446 delete[] value_bytes;
447 return true;
448 }
449
450 // wrapper function
451 bool QueryRegValueSZ(ROOT_KEY root,
452 const wchar_t* key_path,
453 const wchar_t* value_name,
454 std::wstring* out_sz) {
455 HANDLE key = INVALID_HANDLE_VALUE;
456
457 if (!OpenRegKey(root, key_path, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &key,
458 NULL))
459 return false;
460
461 if (!QueryRegValueSZ(key, value_name, out_sz)) {
462 CloseRegKey(key);
463 return false;
464 }
465
466 CloseRegKey(key);
467 return true;
468 }
469
470 // wrapper function
471 bool QueryRegValueMULTISZ(HANDLE key,
472 const wchar_t* value_name,
473 std::vector<std::wstring>* out_multi_sz) {
474 BYTE* value_bytes = nullptr;
475 DWORD ret_size = 0;
476 ULONG type = REG_NONE;
477
478 if (!QueryRegKeyValue(key, value_name, &type, &value_bytes, &ret_size) ||
479 type != REG_MULTI_SZ)
480 return false;
481
482 // Make sure the vector is empty to start.
483 (*out_multi_sz).resize(0);
484
485 wchar_t* pointer = reinterpret_cast<wchar_t*>(value_bytes);
486 std::wstring temp = pointer;
487 // Loop. Each string is separated by '\0'. Another '\0' at very end (so 2 in
488 // a row).
489 while (temp.length() != 0) {
490 (*out_multi_sz).push_back(temp);
491
492 pointer += temp.length() + 1;
493 temp = pointer;
494 }
495
496 // Handle the case of "empty multi_sz".
497 if (out_multi_sz->size() == 0)
498 out_multi_sz->push_back(L"");
499
500 delete[] value_bytes;
501 return true;
502 }
503
504 // wrapper function
505 bool QueryRegValueMULTISZ(ROOT_KEY root,
506 const wchar_t* key_path,
507 const wchar_t* value_name,
508 std::vector<std::wstring>* out_multi_sz) {
509 HANDLE key = INVALID_HANDLE_VALUE;
510
511 if (!OpenRegKey(root, key_path, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &key,
512 NULL))
513 return false;
514
515 if (!QueryRegValueMULTISZ(key, value_name, out_multi_sz)) {
516 CloseRegKey(key);
517 return false;
518 }
519
520 CloseRegKey(key);
521 return true;
522 }
523
524 //------------------------------------------------------------------------------
525 // Setter functions
526 //------------------------------------------------------------------------------
527
528 bool SetRegKeyValue(HANDLE key,
529 const wchar_t* value_name,
530 ULONG type,
531 const BYTE* data,
532 DWORD data_size) {
533 if (!g_initialized)
534 InitNativeRegApi();
535
536 NTSTATUS ntstatus = STATUS_UNSUCCESSFUL;
537 UNICODE_STRING value_uni = {};
538 g_rtl_init_unicode_string(&value_uni, value_name);
539
540 BYTE* non_const_data = const_cast<BYTE*>(data);
541 ntstatus =
542 g_nt_set_value_key(key, &value_uni, 0, type, non_const_data, data_size);
543
544 if (NT_SUCCESS(ntstatus))
545 return true;
546
547 return false;
548 }
549
550 // wrapper function
551 bool SetRegValueDWORD(HANDLE key, const wchar_t* value_name, DWORD value) {
552 return SetRegKeyValue(key, value_name, REG_DWORD,
553 reinterpret_cast<BYTE*>(&value), sizeof(value));
554 }
555
556 // wrapper function
557 bool SetRegValueDWORD(ROOT_KEY root,
558 const wchar_t* key_path,
559 const wchar_t* value_name,
560 DWORD value) {
561 HANDLE key = INVALID_HANDLE_VALUE;
562
563 if (!OpenRegKey(root, key_path, KEY_SET_VALUE | KEY_WOW64_32KEY, &key, NULL))
564 return false;
565
566 if (!SetRegValueDWORD(key, value_name, value)) {
567 CloseRegKey(key);
568 return false;
569 }
570
571 return true;
572 }
573
574 // wrapper function
575 bool SetRegValueSZ(HANDLE key,
576 const wchar_t* value_name,
577 const std::wstring& value) {
578 // Make sure the number of bytes in |value|, including EoS, fits in a DWORD.
579 if (std::numeric_limits<DWORD>::max() <
580 ((value.length() + 1) * sizeof(wchar_t)))
581 return false;
582
583 DWORD size = (static_cast<DWORD>((value.length() + 1) * sizeof(wchar_t)));
584 return SetRegKeyValue(key, value_name, REG_SZ,
585 reinterpret_cast<const BYTE*>(value.c_str()), size);
586 }
587
588 // wrapper function
589 bool SetRegValueSZ(ROOT_KEY root,
590 const wchar_t* key_path,
591 const wchar_t* value_name,
592 const std::wstring& value) {
593 HANDLE key = INVALID_HANDLE_VALUE;
594
595 if (!OpenRegKey(root, key_path, KEY_SET_VALUE | KEY_WOW64_32KEY, &key, NULL))
596 return false;
597
598 if (!SetRegValueSZ(key, value_name, value)) {
599 CloseRegKey(key);
600 return false;
601 }
602
603 return true;
604 }
605
606 // wrapper function
607 bool SetRegValueMULTISZ(HANDLE key,
608 const wchar_t* value_name,
609 const std::vector<std::wstring>& values) {
610 std::vector<wchar_t> builder;
611
612 for (auto& string : values) {
613 // Just in case someone is passing in an illegal empty string
614 // (not allowed in REG_MULTI_SZ), ignore it.
615 if (!string.empty()) {
616 for (const wchar_t& w : string) {
617 builder.push_back(w);
618 }
619 builder.push_back(L'\0');
620 }
621 }
622 // Add second null terminator to end REG_MULTI_SZ.
623 builder.push_back(L'\0');
624 // Handle rare case where the vector passed in was empty,
625 // or only had an empty string.
626 if (builder.size() == 1)
627 builder.push_back(L'\0');
628
629 if (std::numeric_limits<DWORD>::max() < builder.size())
630 return false;
631
632 return SetRegKeyValue(
633 key, value_name, REG_MULTI_SZ, reinterpret_cast<BYTE*>(builder.data()),
634 (static_cast<DWORD>(builder.size()) + 1) * sizeof(wchar_t));
635 }
636
637 // wrapper function
638 bool SetRegValueMULTISZ(ROOT_KEY root,
639 const wchar_t* key_path,
640 const wchar_t* value_name,
641 const std::vector<std::wstring>& values) {
642 HANDLE key = INVALID_HANDLE_VALUE;
643
644 if (!OpenRegKey(root, key_path, KEY_SET_VALUE | KEY_WOW64_32KEY, &key, NULL))
645 return false;
646
647 if (!SetRegValueMULTISZ(key, value_name, values)) {
648 CloseRegKey(key);
649 return false;
650 }
651
652 return true;
653 }
654
655 //------------------------------------------------------------------------------
656 // Utils
657 //------------------------------------------------------------------------------
658
659 const wchar_t* GetCurrentUserSidString() {
660 if (!g_initialized)
661 InitNativeRegApi();
662
663 return g_current_user_sid_string;
664 }
665
666 }; // namespace nt
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698