/* * Registry functions * * Copyright (C) 1999 Juergen Schmied * Copyright (C) 2000 Alexandre Julliard * Copyright 2005 Ivan Leo Puoti, Laurent Pinchart * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA * * NOTES: * HKEY_LOCAL_MACHINE \\REGISTRY\\MACHINE * HKEY_USERS \\REGISTRY\\USER * HKEY_CURRENT_CONFIG \\REGISTRY\\MACHINE\\SYSTEM\\CURRENTCONTROLSET\\HARDWARE PROFILES\\CURRENT * HKEY_CLASSES \\REGISTRY\\MACHINE\\SOFTWARE\\CLASSES */ #include "config.h" #include "wine/port.h" #include #include #include #include "ntstatus.h" #define WIN32_NO_STATUS #include "wine/library.h" #include "ntdll_misc.h" #include "wine/debug.h" #include "wine/unicode.h" WINE_DEFAULT_DEBUG_CHANNEL(reg); /* maximum length of a key name in bytes (without terminating null) */ #define MAX_NAME_LENGTH (255 * sizeof(WCHAR)) /* maximum length of a value name in bytes (without terminating null) */ #define MAX_VALUE_LENGTH (16383 * sizeof(WCHAR)) /****************************************************************************** * NtCreateKey [NTDLL.@] * ZwCreateKey [NTDLL.@] */ NTSTATUS WINAPI NtCreateKey( PHANDLE retkey, ACCESS_MASK access, const OBJECT_ATTRIBUTES *attr, ULONG TitleIndex, const UNICODE_STRING *class, ULONG options, PULONG dispos ) { NTSTATUS ret; if (!retkey || !attr) return STATUS_ACCESS_VIOLATION; if (attr->Length > sizeof(OBJECT_ATTRIBUTES)) return STATUS_INVALID_PARAMETER; if (attr->ObjectName->Length > MAX_NAME_LENGTH) return STATUS_BUFFER_OVERFLOW; TRACE( "(%p,%s,%s,%x,%x,%p)\n", attr->RootDirectory, debugstr_us(attr->ObjectName), debugstr_us(class), options, access, retkey ); SERVER_START_REQ( create_key ) { req->parent = wine_server_obj_handle( attr->RootDirectory ); req->access = access; req->attributes = attr->Attributes; req->options = options; req->namelen = attr->ObjectName->Length; wine_server_add_data( req, attr->ObjectName->Buffer, attr->ObjectName->Length ); if (class) wine_server_add_data( req, class->Buffer, class->Length ); if (!(ret = wine_server_call( req ))) { *retkey = wine_server_ptr_handle( reply->hkey ); if (dispos) *dispos = reply->created ? REG_CREATED_NEW_KEY : REG_OPENED_EXISTING_KEY; } } SERVER_END_REQ; TRACE("<- %p\n", *retkey); return ret; } /****************************************************************************** * RtlpNtCreateKey [NTDLL.@] * * See NtCreateKey. */ NTSTATUS WINAPI RtlpNtCreateKey( PHANDLE retkey, ACCESS_MASK access, const OBJECT_ATTRIBUTES *attr, ULONG TitleIndex, const UNICODE_STRING *class, ULONG options, PULONG dispos ) { OBJECT_ATTRIBUTES oa; if (attr) { oa = *attr; oa.Attributes &= ~(OBJ_PERMANENT|OBJ_EXCLUSIVE); attr = &oa; } return NtCreateKey(retkey, access, attr, 0, NULL, 0, dispos); } /****************************************************************************** * NtOpenKeyEx [NTDLL.@] * ZwOpenKeyEx [NTDLL.@] */ NTSTATUS WINAPI NtOpenKeyEx( PHANDLE retkey, ACCESS_MASK access, const OBJECT_ATTRIBUTES *attr, ULONG options ) { NTSTATUS ret; DWORD len; if (!retkey || !attr) return STATUS_ACCESS_VIOLATION; if (attr->Length > sizeof(OBJECT_ATTRIBUTES)) return STATUS_INVALID_PARAMETER; len = attr->ObjectName->Length; TRACE( "(%p,%s,%x,%p)\n", attr->RootDirectory, debugstr_us(attr->ObjectName), access, retkey ); if (options) FIXME("options %x not implemented\n", options); if (len > MAX_NAME_LENGTH) return STATUS_BUFFER_OVERFLOW; SERVER_START_REQ( open_key ) { req->parent = wine_server_obj_handle( attr->RootDirectory ); req->access = access; req->attributes = attr->Attributes; wine_server_add_data( req, attr->ObjectName->Buffer, len ); ret = wine_server_call( req ); *retkey = wine_server_ptr_handle( reply->hkey ); } SERVER_END_REQ; TRACE("<- %p\n", *retkey); return ret; } /****************************************************************************** * NtOpenKey [NTDLL.@] * ZwOpenKey [NTDLL.@] * * OUT HANDLE retkey (returns 0 when failure) * IN ACCESS_MASK access * IN POBJECT_ATTRIBUTES attr */ NTSTATUS WINAPI NtOpenKey( PHANDLE retkey, ACCESS_MASK access, const OBJECT_ATTRIBUTES *attr ) { return NtOpenKeyEx( retkey, access, attr, 0 ); } /****************************************************************************** * RtlpNtOpenKey [NTDLL.@] * * See NtOpenKey. */ NTSTATUS WINAPI RtlpNtOpenKey( PHANDLE retkey, ACCESS_MASK access, OBJECT_ATTRIBUTES *attr ) { if (attr) attr->Attributes &= ~(OBJ_PERMANENT|OBJ_EXCLUSIVE); return NtOpenKey(retkey, access, attr); } /****************************************************************************** * NtDeleteKey [NTDLL.@] * ZwDeleteKey [NTDLL.@] */ NTSTATUS WINAPI NtDeleteKey( HANDLE hkey ) { NTSTATUS ret; TRACE( "(%p)\n", hkey ); SERVER_START_REQ( delete_key ) { req->hkey = wine_server_obj_handle( hkey ); ret = wine_server_call( req ); } SERVER_END_REQ; return ret; } /****************************************************************************** * RtlpNtMakeTemporaryKey [NTDLL.@] * * See NtDeleteKey. */ NTSTATUS WINAPI RtlpNtMakeTemporaryKey( HANDLE hkey ) { return NtDeleteKey(hkey); } /****************************************************************************** * NtDeleteValueKey [NTDLL.@] * ZwDeleteValueKey [NTDLL.@] */ NTSTATUS WINAPI NtDeleteValueKey( HANDLE hkey, const UNICODE_STRING *name ) { NTSTATUS ret; TRACE( "(%p,%s)\n", hkey, debugstr_us(name) ); if (name->Length > MAX_VALUE_LENGTH) return STATUS_OBJECT_NAME_NOT_FOUND; SERVER_START_REQ( delete_key_value ) { req->hkey = wine_server_obj_handle( hkey ); wine_server_add_data( req, name->Buffer, name->Length ); ret = wine_server_call( req ); } SERVER_END_REQ; return ret; } /****************************************************************************** * enumerate_key * * Implementation of NtQueryKey and NtEnumerateKey */ static NTSTATUS enumerate_key( HANDLE handle, int index, KEY_INFORMATION_CLASS info_class, void *info, DWORD length, DWORD *result_len ) { NTSTATUS ret; void *data_ptr; size_t fixed_size; switch(info_class) { case KeyBasicInformation: data_ptr = ((KEY_BASIC_INFORMATION *)info)->Name; break; case KeyFullInformation: data_ptr = ((KEY_FULL_INFORMATION *)info)->Class; break; case KeyNodeInformation: data_ptr = ((KEY_NODE_INFORMATION *)info)->Name; break; case KeyNameInformation: data_ptr = ((KEY_NAME_INFORMATION *)info)->Name; break; default: FIXME( "Information class %d not implemented\n", info_class ); return STATUS_INVALID_PARAMETER; } fixed_size = (char *)data_ptr - (char *)info; SERVER_START_REQ( enum_key ) { req->hkey = wine_server_obj_handle( handle ); req->index = index; req->info_class = info_class; if (length > fixed_size) wine_server_set_reply( req, data_ptr, length - fixed_size ); if (!(ret = wine_server_call( req ))) { switch(info_class) { case KeyBasicInformation: { KEY_BASIC_INFORMATION keyinfo; fixed_size = (char *)keyinfo.Name - (char *)&keyinfo; keyinfo.LastWriteTime.QuadPart = reply->modif; keyinfo.TitleIndex = 0; keyinfo.NameLength = reply->namelen; memcpy( info, &keyinfo, min( length, fixed_size ) ); } break; case KeyFullInformation: { KEY_FULL_INFORMATION keyinfo; fixed_size = (char *)keyinfo.Class - (char *)&keyinfo; keyinfo.LastWriteTime.QuadPart = reply->modif; keyinfo.TitleIndex = 0; keyinfo.ClassLength = wine_server_reply_size(reply); keyinfo.ClassOffset = keyinfo.ClassLength ? fixed_size : -1; keyinfo.SubKeys = reply->subkeys; keyinfo.MaxNameLen = reply->max_subkey; keyinfo.MaxClassLen = reply->max_class; keyinfo.Values = reply->values; keyinfo.MaxValueNameLen = reply->max_value; keyinfo.MaxValueDataLen = reply->max_data; memcpy( info, &keyinfo, min( length, fixed_size ) ); } break; case KeyNodeInformation: { KEY_NODE_INFORMATION keyinfo; fixed_size = (char *)keyinfo.Name - (char *)&keyinfo; keyinfo.LastWriteTime.QuadPart = reply->modif; keyinfo.TitleIndex = 0; if (reply->namelen < wine_server_reply_size(reply)) { keyinfo.ClassLength = wine_server_reply_size(reply) - reply->namelen; keyinfo.ClassOffset = fixed_size + reply->namelen; } else { keyinfo.ClassLength = 0; keyinfo.ClassOffset = -1; } keyinfo.NameLength = reply->namelen; memcpy( info, &keyinfo, min( length, fixed_size ) ); } break; case KeyNameInformation: { KEY_NAME_INFORMATION keyinfo; fixed_size = (char *)keyinfo.Name - (char *)&keyinfo; keyinfo.NameLength = reply->namelen; memcpy( info, &keyinfo, min( length, fixed_size ) ); } break; } *result_len = fixed_size + reply->total; if (length < *result_len) ret = STATUS_BUFFER_OVERFLOW; } } SERVER_END_REQ; return ret; } /****************************************************************************** * NtEnumerateKey [NTDLL.@] * ZwEnumerateKey [NTDLL.@] * * NOTES * the name copied into the buffer is NOT 0-terminated */ NTSTATUS WINAPI NtEnumerateKey( HANDLE handle, ULONG index, KEY_INFORMATION_CLASS info_class, void *info, DWORD length, DWORD *result_len ) { /* -1 means query key, so avoid it here */ if (index == (ULONG)-1) return STATUS_NO_MORE_ENTRIES; return enumerate_key( handle, index, info_class, info, length, result_len ); } /****************************************************************************** * RtlpNtEnumerateSubKey [NTDLL.@] * */ NTSTATUS WINAPI RtlpNtEnumerateSubKey( HANDLE handle, UNICODE_STRING *out, ULONG index ) { KEY_BASIC_INFORMATION *info; DWORD dwLen, dwResultLen; NTSTATUS ret; if (out->Length) { dwLen = out->Length + sizeof(KEY_BASIC_INFORMATION); info = RtlAllocateHeap( GetProcessHeap(), 0, dwLen ); if (!info) return STATUS_NO_MEMORY; } else { dwLen = 0; info = NULL; } ret = NtEnumerateKey( handle, index, KeyBasicInformation, info, dwLen, &dwResultLen ); dwResultLen -= sizeof(KEY_BASIC_INFORMATION); if (ret == STATUS_BUFFER_OVERFLOW) out->Length = dwResultLen; else if (!ret) { if (out->Length < info->NameLength) { out->Length = dwResultLen; ret = STATUS_BUFFER_OVERFLOW; } else { out->Length = info->NameLength; memcpy(out->Buffer, info->Name, info->NameLength); } } RtlFreeHeap( GetProcessHeap(), 0, info ); return ret; } /****************************************************************************** * NtQueryKey [NTDLL.@] * ZwQueryKey [NTDLL.@] */ NTSTATUS WINAPI NtQueryKey( HANDLE handle, KEY_INFORMATION_CLASS info_class, void *info, DWORD length, DWORD *result_len ) { return enumerate_key( handle, -1, info_class, info, length, result_len ); } /* fill the key value info structure for a specific info class */ static void copy_key_value_info( KEY_VALUE_INFORMATION_CLASS info_class, void *info, DWORD length, int type, int name_len, int data_len ) { switch(info_class) { case KeyValueBasicInformation: { KEY_VALUE_BASIC_INFORMATION keyinfo; keyinfo.TitleIndex = 0; keyinfo.Type = type; keyinfo.NameLength = name_len; length = min( length, (char *)keyinfo.Name - (char *)&keyinfo ); memcpy( info, &keyinfo, length ); break; } case KeyValueFullInformation: { KEY_VALUE_FULL_INFORMATION keyinfo; keyinfo.TitleIndex = 0; keyinfo.Type = type; keyinfo.DataOffset = (char *)keyinfo.Name - (char *)&keyinfo + name_len; keyinfo.DataLength = data_len; keyinfo.NameLength = name_len; length = min( length, (char *)keyinfo.Name - (char *)&keyinfo ); memcpy( info, &keyinfo, length ); break; } case KeyValuePartialInformation: { KEY_VALUE_PARTIAL_INFORMATION keyinfo; keyinfo.TitleIndex = 0; keyinfo.Type = type; keyinfo.DataLength = data_len; length = min( length, (char *)keyinfo.Data - (char *)&keyinfo ); memcpy( info, &keyinfo, length ); break; } default: break; } } /****************************************************************************** * NtEnumerateValueKey [NTDLL.@] * ZwEnumerateValueKey [NTDLL.@] */ NTSTATUS WINAPI NtEnumerateValueKey( HANDLE handle, ULONG index, KEY_VALUE_INFORMATION_CLASS info_class, void *info, DWORD length, DWORD *result_len ) { NTSTATUS ret; void *ptr; size_t fixed_size; TRACE( "(%p,%u,%d,%p,%d)\n", handle, index, info_class, info, length ); /* compute the length we want to retrieve */ switch(info_class) { case KeyValueBasicInformation: ptr = ((KEY_VALUE_BASIC_INFORMATION *)info)->Name; break; case KeyValueFullInformation: ptr = ((KEY_VALUE_FULL_INFORMATION *)info)->Name; break; case KeyValuePartialInformation: ptr = ((KEY_VALUE_PARTIAL_INFORMATION *)info)->Data; break; default: FIXME( "Information class %d not implemented\n", info_class ); return STATUS_INVALID_PARAMETER; } fixed_size = (char *)ptr - (char *)info; SERVER_START_REQ( enum_key_value ) { req->hkey = wine_server_obj_handle( handle ); req->index = index; req->info_class = info_class; if (length > fixed_size) wine_server_set_reply( req, ptr, length - fixed_size ); if (!(ret = wine_server_call( req ))) { copy_key_value_info( info_class, info, length, reply->type, reply->namelen, wine_server_reply_size(reply) - reply->namelen ); *result_len = fixed_size + reply->total; if (length < *result_len) ret = STATUS_BUFFER_OVERFLOW; } } SERVER_END_REQ; return ret; } /****************************************************************************** * NtQueryValueKey [NTDLL.@] * ZwQueryValueKey [NTDLL.@] * * NOTES * the name in the KeyValueInformation is never set */ NTSTATUS WINAPI NtQueryValueKey( HANDLE handle, const UNICODE_STRING *name, KEY_VALUE_INFORMATION_CLASS info_class, void *info, DWORD length, DWORD *result_len ) { NTSTATUS ret; UCHAR *data_ptr; unsigned int fixed_size = 0, min_size = 0; TRACE( "(%p,%s,%d,%p,%d)\n", handle, debugstr_us(name), info_class, info, length ); if (name->Length > MAX_VALUE_LENGTH) return STATUS_OBJECT_NAME_NOT_FOUND; /* compute the length we want to retrieve */ switch(info_class) { case KeyValueBasicInformation: { KEY_VALUE_BASIC_INFORMATION *basic_info = info; min_size = FIELD_OFFSET(KEY_VALUE_BASIC_INFORMATION, Name); fixed_size = min_size + name->Length; if (min_size < length) memcpy(basic_info->Name, name->Buffer, min(length - min_size, name->Length)); data_ptr = NULL; break; } case KeyValueFullInformation: { KEY_VALUE_FULL_INFORMATION *full_info = info; min_size = FIELD_OFFSET(KEY_VALUE_FULL_INFORMATION, Name); fixed_size = min_size + name->Length; if (min_size < length) memcpy(full_info->Name, name->Buffer, min(length - min_size, name->Length)); data_ptr = (UCHAR *)full_info->Name + name->Length; break; } case KeyValuePartialInformation: min_size = fixed_size = FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data); data_ptr = ((KEY_VALUE_PARTIAL_INFORMATION *)info)->Data; break; default: FIXME( "Information class %d not implemented\n", info_class ); return STATUS_INVALID_PARAMETER; } SERVER_START_REQ( get_key_value ) { req->hkey = wine_server_obj_handle( handle ); wine_server_add_data( req, name->Buffer, name->Length ); if (length > fixed_size && data_ptr) wine_server_set_reply( req, data_ptr, length - fixed_size ); if (!(ret = wine_server_call( req ))) { copy_key_value_info( info_class, info, length, reply->type, name->Length, reply->total ); *result_len = fixed_size + (info_class == KeyValueBasicInformation ? 0 : reply->total); if (length < min_size) ret = STATUS_BUFFER_TOO_SMALL; else if (length < *result_len) ret = STATUS_BUFFER_OVERFLOW; } } SERVER_END_REQ; return ret; } /****************************************************************************** * RtlpNtQueryValueKey [NTDLL.@] * */ NTSTATUS WINAPI RtlpNtQueryValueKey( HANDLE handle, ULONG *result_type, PBYTE dest, DWORD *result_len, void *unknown ) { KEY_VALUE_PARTIAL_INFORMATION *info; UNICODE_STRING name; NTSTATUS ret; DWORD dwResultLen; DWORD dwLen = sizeof (KEY_VALUE_PARTIAL_INFORMATION) + (result_len ? *result_len : 0); info = RtlAllocateHeap( GetProcessHeap(), 0, dwLen ); if (!info) return STATUS_NO_MEMORY; name.Length = 0; ret = NtQueryValueKey( handle, &name, KeyValuePartialInformation, info, dwLen, &dwResultLen ); if (!ret || ret == STATUS_BUFFER_OVERFLOW) { if (result_len) *result_len = info->DataLength; if (result_type) *result_type = info->Type; if (ret != STATUS_BUFFER_OVERFLOW) memcpy( dest, info->Data, info->DataLength ); } RtlFreeHeap( GetProcessHeap(), 0, info ); return ret; } /****************************************************************************** * NtFlushKey [NTDLL.@] * ZwFlushKey [NTDLL.@] */ NTSTATUS WINAPI NtFlushKey(HANDLE key) { NTSTATUS ret; TRACE("key=%p\n", key); SERVER_START_REQ( flush_key ) { req->hkey = wine_server_obj_handle( key ); ret = wine_server_call( req ); } SERVER_END_REQ; return ret; } /****************************************************************************** * NtLoadKey [NTDLL.@] * ZwLoadKey [NTDLL.@] */ NTSTATUS WINAPI NtLoadKey( const OBJECT_ATTRIBUTES *attr, OBJECT_ATTRIBUTES *file ) { NTSTATUS ret; HANDLE hive; IO_STATUS_BLOCK io; TRACE("(%p,%p)\n", attr, file); ret = NtCreateFile(&hive, GENERIC_READ, file, &io, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN, 0, NULL, 0); if (ret) return ret; SERVER_START_REQ( load_registry ) { req->hkey = wine_server_obj_handle( attr->RootDirectory ); req->file = wine_server_obj_handle( hive ); wine_server_add_data(req, attr->ObjectName->Buffer, attr->ObjectName->Length); ret = wine_server_call( req ); } SERVER_END_REQ; NtClose(hive); return ret; } /****************************************************************************** * NtNotifyChangeKey [NTDLL.@] * ZwNotifyChangeKey [NTDLL.@] */ NTSTATUS WINAPI NtNotifyChangeKey( IN HANDLE KeyHandle, IN HANDLE Event, IN PIO_APC_ROUTINE ApcRoutine OPTIONAL, IN PVOID ApcContext OPTIONAL, OUT PIO_STATUS_BLOCK IoStatusBlock, IN ULONG CompletionFilter, IN BOOLEAN WatchSubtree, OUT PVOID ChangeBuffer, IN ULONG Length, IN BOOLEAN Asynchronous) { NTSTATUS ret; TRACE("(%p,%p,%p,%p,%p,0x%08x, 0x%08x,%p,0x%08x,0x%08x)\n", KeyHandle, Event, ApcRoutine, ApcContext, IoStatusBlock, CompletionFilter, Asynchronous, ChangeBuffer, Length, WatchSubtree); if (ApcRoutine || ApcContext || ChangeBuffer || Length) FIXME("Unimplemented optional parameter\n"); if (!Asynchronous) { OBJECT_ATTRIBUTES attr; InitializeObjectAttributes( &attr, NULL, 0, NULL, NULL ); ret = NtCreateEvent( &Event, EVENT_ALL_ACCESS, &attr, SynchronizationEvent, FALSE ); if (ret != STATUS_SUCCESS) return ret; } SERVER_START_REQ( set_registry_notification ) { req->hkey = wine_server_obj_handle( KeyHandle ); req->event = wine_server_obj_handle( Event ); req->subtree = WatchSubtree; req->filter = CompletionFilter; ret = wine_server_call( req ); } SERVER_END_REQ; if (!Asynchronous) { if (ret == STATUS_PENDING) ret = NtWaitForSingleObject( Event, FALSE, NULL ); NtClose( Event ); } return ret; } /****************************************************************************** * NtQueryMultipleValueKey [NTDLL] * ZwQueryMultipleValueKey */ NTSTATUS WINAPI NtQueryMultipleValueKey( HANDLE KeyHandle, PKEY_MULTIPLE_VALUE_INFORMATION ListOfValuesToQuery, ULONG NumberOfItems, PVOID MultipleValueInformation, ULONG Length, PULONG ReturnLength) { FIXME("(%p,%p,0x%08x,%p,0x%08x,%p) stub!\n", KeyHandle, ListOfValuesToQuery, NumberOfItems, MultipleValueInformation, Length,ReturnLength); return STATUS_SUCCESS; } /****************************************************************************** * NtReplaceKey [NTDLL.@] * ZwReplaceKey [NTDLL.@] */ NTSTATUS WINAPI NtReplaceKey( IN POBJECT_ATTRIBUTES ObjectAttributes, IN HANDLE Key, IN POBJECT_ATTRIBUTES ReplacedObjectAttributes) { FIXME("(%s,%p,%s),stub!\n", debugstr_ObjectAttributes(ObjectAttributes), Key, debugstr_ObjectAttributes(ReplacedObjectAttributes) ); return STATUS_SUCCESS; } /****************************************************************************** * NtRestoreKey [NTDLL.@] * ZwRestoreKey [NTDLL.@] */ NTSTATUS WINAPI NtRestoreKey( HANDLE KeyHandle, HANDLE FileHandle, ULONG RestoreFlags) { FIXME("(%p,%p,0x%08x) stub\n", KeyHandle, FileHandle, RestoreFlags); return STATUS_SUCCESS; } /****************************************************************************** * NtSaveKey [NTDLL.@] * ZwSaveKey [NTDLL.@] */ NTSTATUS WINAPI NtSaveKey(IN HANDLE KeyHandle, IN HANDLE FileHandle) { NTSTATUS ret; TRACE("(%p,%p)\n", KeyHandle, FileHandle); SERVER_START_REQ( save_registry ) { req->hkey = wine_server_obj_handle( KeyHandle ); req->file = wine_server_obj_handle( FileHandle ); ret = wine_server_call( req ); } SERVER_END_REQ; return ret; } /****************************************************************************** * NtSetInformationKey [NTDLL.@] * ZwSetInformationKey [NTDLL.@] */ NTSTATUS WINAPI NtSetInformationKey( IN HANDLE KeyHandle, IN const int KeyInformationClass, IN PVOID KeyInformation, IN ULONG KeyInformationLength) { FIXME("(%p,0x%08x,%p,0x%08x) stub\n", KeyHandle, KeyInformationClass, KeyInformation, KeyInformationLength); return STATUS_SUCCESS; } /****************************************************************************** * NtSetValueKey [NTDLL.@] * ZwSetValueKey [NTDLL.@] * * NOTES * win95 does not care about count for REG_SZ and finds out the len by itself (js) * NT does definitely care (aj) */ NTSTATUS WINAPI NtSetValueKey( HANDLE hkey, const UNICODE_STRING *name, ULONG TitleIndex, ULONG type, const void *data, ULONG count ) { NTSTATUS ret; TRACE( "(%p,%s,%d,%p,%d)\n", hkey, debugstr_us(name), type, data, count ); if (name->Length > MAX_VALUE_LENGTH) return STATUS_INVALID_PARAMETER; SERVER_START_REQ( set_key_value ) { req->hkey = wine_server_obj_handle( hkey ); req->type = type; req->namelen = name->Length; wine_server_add_data( req, name->Buffer, name->Length ); wine_server_add_data( req, data, count ); ret = wine_server_call( req ); } SERVER_END_REQ; return ret; } /****************************************************************************** * RtlpNtSetValueKey [NTDLL.@] * */ NTSTATUS WINAPI RtlpNtSetValueKey( HANDLE hkey, ULONG type, const void *data, ULONG count ) { UNICODE_STRING name; name.Length = 0; return NtSetValueKey( hkey, &name, 0, type, data, count ); } /****************************************************************************** * NtUnloadKey [NTDLL.@] * ZwUnloadKey [NTDLL.@] */ NTSTATUS WINAPI NtUnloadKey(IN POBJECT_ATTRIBUTES attr) { NTSTATUS ret; TRACE("(%p)\n", attr); SERVER_START_REQ( unload_registry ) { req->hkey = wine_server_obj_handle( attr->RootDirectory ); ret = wine_server_call(req); } SERVER_END_REQ; return ret; } /****************************************************************************** * RtlFormatCurrentUserKeyPath [NTDLL.@] * */ NTSTATUS WINAPI RtlFormatCurrentUserKeyPath( IN OUT PUNICODE_STRING KeyPath) { static const WCHAR pathW[] = {'\\','R','e','g','i','s','t','r','y','\\','U','s','e','r','\\'}; HANDLE token; NTSTATUS status; status = NtOpenThreadToken(GetCurrentThread(), TOKEN_READ, TRUE, &token); if (status == STATUS_NO_TOKEN) status = NtOpenProcessToken(GetCurrentProcess(), TOKEN_READ, &token); if (status == STATUS_SUCCESS) { char buffer[sizeof(TOKEN_USER) + sizeof(SID) + sizeof(DWORD)*SID_MAX_SUB_AUTHORITIES]; DWORD len = sizeof(buffer); status = NtQueryInformationToken(token, TokenUser, buffer, len, &len); if (status == STATUS_SUCCESS) { KeyPath->MaximumLength = 0; status = RtlConvertSidToUnicodeString(KeyPath, ((TOKEN_USER *)buffer)->User.Sid, FALSE); if (status == STATUS_BUFFER_OVERFLOW) { PWCHAR buf = RtlAllocateHeap(GetProcessHeap(), 0, sizeof(pathW) + KeyPath->Length + sizeof(WCHAR)); if (buf) { memcpy(buf, pathW, sizeof(pathW)); KeyPath->MaximumLength = KeyPath->Length + sizeof(WCHAR); KeyPath->Buffer = (PWCHAR)((LPBYTE)buf + sizeof(pathW)); status = RtlConvertSidToUnicodeString(KeyPath, ((TOKEN_USER *)buffer)->User.Sid, FALSE); KeyPath->Buffer = buf; KeyPath->Length += sizeof(pathW); KeyPath->MaximumLength += sizeof(pathW); } else status = STATUS_NO_MEMORY; } } NtClose(token); } return status; } /****************************************************************************** * RtlOpenCurrentUser [NTDLL.@] * * NOTES * If we return just HKEY_CURRENT_USER the advapi tries to find a remote * registry (odd handle) and fails. */ NTSTATUS WINAPI RtlOpenCurrentUser( IN ACCESS_MASK DesiredAccess, /* [in] */ OUT PHANDLE KeyHandle) /* [out] handle of HKEY_CURRENT_USER */ { OBJECT_ATTRIBUTES ObjectAttributes; UNICODE_STRING ObjectName; NTSTATUS ret; TRACE("(0x%08x, %p)\n",DesiredAccess, KeyHandle); if ((ret = RtlFormatCurrentUserKeyPath(&ObjectName))) return ret; InitializeObjectAttributes(&ObjectAttributes,&ObjectName,OBJ_CASE_INSENSITIVE,0, NULL); ret = NtCreateKey(KeyHandle, DesiredAccess, &ObjectAttributes, 0, NULL, 0, NULL); RtlFreeUnicodeString(&ObjectName); return ret; } static NTSTATUS RTL_ReportRegistryValue(PKEY_VALUE_FULL_INFORMATION pInfo, PRTL_QUERY_REGISTRY_TABLE pQuery, PVOID pContext, PVOID pEnvironment) { PUNICODE_STRING str; UNICODE_STRING src, dst; LONG *bin; ULONG offset; PWSTR wstr; DWORD res; NTSTATUS status = STATUS_SUCCESS; ULONG len; LPWSTR String; ULONG count = 0; if (pInfo == NULL) { if (pQuery->Flags & RTL_QUERY_REGISTRY_DIRECT) return STATUS_INVALID_PARAMETER; else { status = pQuery->QueryRoutine(pQuery->Name, pQuery->DefaultType, pQuery->DefaultData, pQuery->DefaultLength, pContext, pQuery->EntryContext); } return status; } len = pInfo->DataLength; if (pQuery->Flags & RTL_QUERY_REGISTRY_DIRECT) { str = pQuery->EntryContext; switch(pInfo->Type) { case REG_EXPAND_SZ: if (!(pQuery->Flags & RTL_QUERY_REGISTRY_NOEXPAND)) { RtlInitUnicodeString(&src, (WCHAR*)(((CHAR*)pInfo) + pInfo->DataOffset)); res = 0; dst.MaximumLength = 0; RtlExpandEnvironmentStrings_U(pEnvironment, &src, &dst, &res); dst.Length = 0; dst.MaximumLength = res; dst.Buffer = RtlAllocateHeap(GetProcessHeap(), 0, res * sizeof(WCHAR)); RtlExpandEnvironmentStrings_U(pEnvironment, &src, &dst, &res); status = pQuery->QueryRoutine(pQuery->Name, pInfo->Type, dst.Buffer, dst.Length, pContext, pQuery->EntryContext); RtlFreeHeap(GetProcessHeap(), 0, dst.Buffer); } case REG_SZ: case REG_LINK: if (str->Buffer == NULL) RtlCreateUnicodeString(str, (WCHAR*)(((CHAR*)pInfo) + pInfo->DataOffset)); else RtlAppendUnicodeToString(str, (WCHAR*)(((CHAR*)pInfo) + pInfo->DataOffset)); break; case REG_MULTI_SZ: if (!(pQuery->Flags & RTL_QUERY_REGISTRY_NOEXPAND)) return STATUS_INVALID_PARAMETER; if (str->Buffer == NULL) { str->Buffer = RtlAllocateHeap(GetProcessHeap(), 0, len); str->MaximumLength = len; } len = min(len, str->MaximumLength); memcpy(str->Buffer, ((CHAR*)pInfo) + pInfo->DataOffset, len); str->Length = len; break; default: bin = pQuery->EntryContext; if (pInfo->DataLength <= sizeof(ULONG)) memcpy(bin, ((CHAR*)pInfo) + pInfo->DataOffset, pInfo->DataLength); else { if (bin[0] <= sizeof(ULONG)) { memcpy(&bin[1], ((CHAR*)pInfo) + pInfo->DataOffset, min(-bin[0], pInfo->DataLength)); } else { len = min(bin[0], pInfo->DataLength); bin[1] = len; bin[2] = pInfo->Type; memcpy(&bin[3], ((CHAR*)pInfo) + pInfo->DataOffset, len); } } break; } } else { if((pQuery->Flags & RTL_QUERY_REGISTRY_NOEXPAND) || (pInfo->Type != REG_EXPAND_SZ && pInfo->Type != REG_MULTI_SZ)) { status = pQuery->QueryRoutine(pQuery->Name, pInfo->Type, ((CHAR*)pInfo) + pInfo->DataOffset, pInfo->DataLength, pContext, pQuery->EntryContext); } else if (pInfo->Type == REG_EXPAND_SZ) { RtlInitUnicodeString(&src, (WCHAR*)(((CHAR*)pInfo) + pInfo->DataOffset)); res = 0; dst.MaximumLength = 0; RtlExpandEnvironmentStrings_U(pEnvironment, &src, &dst, &res); dst.Length = 0; dst.MaximumLength = res; dst.Buffer = RtlAllocateHeap(GetProcessHeap(), 0, res * sizeof(WCHAR)); RtlExpandEnvironmentStrings_U(pEnvironment, &src, &dst, &res); status = pQuery->QueryRoutine(pQuery->Name, pInfo->Type, dst.Buffer, dst.Length, pContext, pQuery->EntryContext); RtlFreeHeap(GetProcessHeap(), 0, dst.Buffer); } else /* REG_MULTI_SZ */ { if(pQuery->Flags & RTL_QUERY_REGISTRY_NOEXPAND) { for (offset = 0; offset <= pInfo->DataLength; offset += len + sizeof(WCHAR)) { wstr = (WCHAR*)(((CHAR*)pInfo) + offset); len = strlenW(wstr) * sizeof(WCHAR); status = pQuery->QueryRoutine(pQuery->Name, pInfo->Type, wstr, len, pContext, pQuery->EntryContext); if(status != STATUS_SUCCESS && status != STATUS_BUFFER_TOO_SMALL) return status; } } else { while(count<=pInfo->DataLength) { String = (WCHAR*)(((CHAR*)pInfo) + pInfo->DataOffset)+count; count+=strlenW(String)+1; RtlInitUnicodeString(&src, (WCHAR*)(((CHAR*)pInfo) + pInfo->DataOffset)); res = 0; dst.MaximumLength = 0; RtlExpandEnvironmentStrings_U(pEnvironment, &src, &dst, &res); dst.Length = 0; dst.MaximumLength = res; dst.Buffer = RtlAllocateHeap(GetProcessHeap(), 0, res * sizeof(WCHAR)); RtlExpandEnvironmentStrings_U(pEnvironment, &src, &dst, &res); status = pQuery->QueryRoutine(pQuery->Name, pInfo->Type, dst.Buffer, dst.Length, pContext, pQuery->EntryContext); RtlFreeHeap(GetProcessHeap(), 0, dst.Buffer); if(status != STATUS_SUCCESS && status != STATUS_BUFFER_TOO_SMALL) return status; } } } } return status; } static NTSTATUS RTL_GetKeyHandle(ULONG RelativeTo, PCWSTR Path, PHANDLE handle) { UNICODE_STRING KeyString; OBJECT_ATTRIBUTES regkey; PCWSTR base; INT len; NTSTATUS status; static const WCHAR empty[] = {0}; static const WCHAR control[] = {'\\','R','e','g','i','s','t','r','y','\\','M','a','c','h','i','n','e', '\\','S','y','s','t','e','m','\\','C','u','r','r','e','n','t','C','o','n','t','r','o','l','S','e','t','\\', 'C','o','n','t','r','o','l','\\',0}; static const WCHAR devicemap[] = {'\\','R','e','g','i','s','t','r','y','\\','M','a','c','h','i','n','e','\\', 'H','a','r','d','w','a','r','e','\\','D','e','v','i','c','e','M','a','p','\\',0}; static const WCHAR services[] = {'\\','R','e','g','i','s','t','r','y','\\','M','a','c','h','i','n','e','\\', 'S','y','s','t','e','m','\\','C','u','r','r','e','n','t','C','o','n','t','r','o','l','S','e','t','\\', 'S','e','r','v','i','c','e','s','\\',0}; static const WCHAR user[] = {'\\','R','e','g','i','s','t','r','y','\\','U','s','e','r','\\', 'C','u','r','r','e','n','t','U','s','e','r','\\',0}; static const WCHAR windows_nt[] = {'\\','R','e','g','i','s','t','r','y','\\','M','a','c','h','i','n','e','\\', 'S','o','f','t','w','a','r','e','\\','M','i','c','r','o','s','o','f','t','\\', 'W','i','n','d','o','w','s',' ','N','T','\\','C','u','r','r','e','n','t','V','e','r','s','i','o','n','\\',0}; switch (RelativeTo & 0xff) { case RTL_REGISTRY_ABSOLUTE: base = empty; break; case RTL_REGISTRY_CONTROL: base = control; break; case RTL_REGISTRY_DEVICEMAP: base = devicemap; break; case RTL_REGISTRY_SERVICES: base = services; break; case RTL_REGISTRY_USER: base = user; break; case RTL_REGISTRY_WINDOWS_NT: base = windows_nt; break; default: return STATUS_INVALID_PARAMETER; } len = (strlenW(base) + strlenW(Path) + 1) * sizeof(WCHAR); KeyString.Buffer = RtlAllocateHeap(GetProcessHeap(), 0, len); if (KeyString.Buffer == NULL) return STATUS_NO_MEMORY; strcpyW(KeyString.Buffer, base); strcatW(KeyString.Buffer, Path); KeyString.Length = len - sizeof(WCHAR); KeyString.MaximumLength = len; InitializeObjectAttributes(®key, &KeyString, OBJ_CASE_INSENSITIVE, NULL, NULL); status = NtOpenKey(handle, KEY_ALL_ACCESS, ®key); RtlFreeHeap(GetProcessHeap(), 0, KeyString.Buffer); return status; } /************************************************************************* * RtlQueryRegistryValues [NTDLL.@] * * Query multiple registry values with a signle call. * * PARAMS * RelativeTo [I] Registry path that Path refers to * Path [I] Path to key * QueryTable [I] Table of key values to query * Context [I] Parameter to pass to the application defined QueryRoutine function * Environment [I] Optional parameter to use when performing expansion * * RETURNS * STATUS_SUCCESS or an appropriate NTSTATUS error code. */ NTSTATUS WINAPI RtlQueryRegistryValues(IN ULONG RelativeTo, IN PCWSTR Path, IN PRTL_QUERY_REGISTRY_TABLE QueryTable, IN PVOID Context, IN PVOID Environment OPTIONAL) { UNICODE_STRING Value; HANDLE handle, topkey; PKEY_VALUE_FULL_INFORMATION pInfo = NULL; ULONG len, buflen = 0; NTSTATUS status=STATUS_SUCCESS, ret = STATUS_SUCCESS; INT i; TRACE("(%d, %s, %p, %p, %p)\n", RelativeTo, debugstr_w(Path), QueryTable, Context, Environment); if(Path == NULL) return STATUS_INVALID_PARAMETER; /* get a valid handle */ if (RelativeTo & RTL_REGISTRY_HANDLE) topkey = handle = (HANDLE)Path; else { status = RTL_GetKeyHandle(RelativeTo, Path, &topkey); handle = topkey; } if(status != STATUS_SUCCESS) return status; /* Process query table entries */ for (; QueryTable->QueryRoutine != NULL || QueryTable->Name != NULL; ++QueryTable) { if (QueryTable->Flags & (RTL_QUERY_REGISTRY_SUBKEY | RTL_QUERY_REGISTRY_TOPKEY)) { /* topkey must be kept open just in case we will reuse it later */ if (handle != topkey) NtClose(handle); if (QueryTable->Flags & RTL_QUERY_REGISTRY_SUBKEY) { handle = 0; status = RTL_GetKeyHandle(PtrToUlong(QueryTable->Name), Path, &handle); if(status != STATUS_SUCCESS) { ret = status; goto out; } } else handle = topkey; } if (QueryTable->Flags & RTL_QUERY_REGISTRY_NOVALUE) { QueryTable->QueryRoutine(QueryTable->Name, REG_NONE, NULL, 0, Context, QueryTable->EntryContext); continue; } if (!handle) { if (QueryTable->Flags & RTL_QUERY_REGISTRY_REQUIRED) { ret = STATUS_OBJECT_NAME_NOT_FOUND; goto out; } continue; } if (QueryTable->Name == NULL) { if (QueryTable->Flags & RTL_QUERY_REGISTRY_DIRECT) { ret = STATUS_INVALID_PARAMETER; goto out; } /* Report all subkeys */ for (i = 0;; ++i) { status = NtEnumerateValueKey(handle, i, KeyValueFullInformation, pInfo, buflen, &len); if (status == STATUS_NO_MORE_ENTRIES) break; if (status == STATUS_BUFFER_OVERFLOW || status == STATUS_BUFFER_TOO_SMALL) { buflen = len; RtlFreeHeap(GetProcessHeap(), 0, pInfo); pInfo = RtlAllocateHeap(GetProcessHeap(), 0, buflen); NtEnumerateValueKey(handle, i, KeyValueFullInformation, pInfo, buflen, &len); } status = RTL_ReportRegistryValue(pInfo, QueryTable, Context, Environment); if(status != STATUS_SUCCESS && status != STATUS_BUFFER_TOO_SMALL) { ret = status; goto out; } if (QueryTable->Flags & RTL_QUERY_REGISTRY_DELETE) { RtlInitUnicodeString(&Value, pInfo->Name); NtDeleteValueKey(handle, &Value); } } if (i == 0 && (QueryTable->Flags & RTL_QUERY_REGISTRY_REQUIRED)) { ret = STATUS_OBJECT_NAME_NOT_FOUND; goto out; } } else { RtlInitUnicodeString(&Value, QueryTable->Name); status = NtQueryValueKey(handle, &Value, KeyValueFullInformation, pInfo, buflen, &len); if (status == STATUS_BUFFER_OVERFLOW || status == STATUS_BUFFER_TOO_SMALL) { buflen = len; RtlFreeHeap(GetProcessHeap(), 0, pInfo); pInfo = RtlAllocateHeap(GetProcessHeap(), 0, buflen); status = NtQueryValueKey(handle, &Value, KeyValueFullInformation, pInfo, buflen, &len); } if (status != STATUS_SUCCESS) { if (QueryTable->Flags & RTL_QUERY_REGISTRY_REQUIRED) { ret = STATUS_OBJECT_NAME_NOT_FOUND; goto out; } status = RTL_ReportRegistryValue(NULL, QueryTable, Context, Environment); if(status != STATUS_SUCCESS && status != STATUS_BUFFER_TOO_SMALL) { ret = status; goto out; } } else { status = RTL_ReportRegistryValue(pInfo, QueryTable, Context, Environment); if(status != STATUS_SUCCESS && status != STATUS_BUFFER_TOO_SMALL) { ret = status; goto out; } if (QueryTable->Flags & RTL_QUERY_REGISTRY_DELETE) NtDeleteValueKey(handle, &Value); } } } out: RtlFreeHeap(GetProcessHeap(), 0, pInfo); if (handle != topkey) NtClose(handle); NtClose(topkey); return ret; } /************************************************************************* * RtlCheckRegistryKey [NTDLL.@] * * Query multiple registry values with a signle call. * * PARAMS * RelativeTo [I] Registry path that Path refers to * Path [I] Path to key * * RETURNS * STATUS_SUCCESS if the specified key exists, or an NTSTATUS error code. */ NTSTATUS WINAPI RtlCheckRegistryKey(IN ULONG RelativeTo, IN PWSTR Path) { HANDLE handle; NTSTATUS status; TRACE("(%d, %s)\n", RelativeTo, debugstr_w(Path)); if((!RelativeTo) && Path == NULL) return STATUS_OBJECT_PATH_SYNTAX_BAD; if(RelativeTo & RTL_REGISTRY_HANDLE) return STATUS_SUCCESS; status = RTL_GetKeyHandle(RelativeTo, Path, &handle); if (handle) NtClose(handle); if (status == STATUS_INVALID_HANDLE) status = STATUS_OBJECT_NAME_NOT_FOUND; return status; } /************************************************************************* * RtlDeleteRegistryValue [NTDLL.@] * * Query multiple registry values with a signle call. * * PARAMS * RelativeTo [I] Registry path that Path refers to * Path [I] Path to key * ValueName [I] Name of the value to delete * * RETURNS * STATUS_SUCCESS if the specified key is successfully deleted, or an NTSTATUS error code. */ NTSTATUS WINAPI RtlDeleteRegistryValue(IN ULONG RelativeTo, IN PCWSTR Path, IN PCWSTR ValueName) { NTSTATUS status; HANDLE handle; UNICODE_STRING Value; TRACE("(%d, %s, %s)\n", RelativeTo, debugstr_w(Path), debugstr_w(ValueName)); RtlInitUnicodeString(&Value, ValueName); if(RelativeTo == RTL_REGISTRY_HANDLE) { return NtDeleteValueKey((HANDLE)Path, &Value); } status = RTL_GetKeyHandle(RelativeTo, Path, &handle); if (status) return status; status = NtDeleteValueKey(handle, &Value); NtClose(handle); return status; } /************************************************************************* * RtlWriteRegistryValue [NTDLL.@] * * Sets the registry value with provided data. * * PARAMS * RelativeTo [I] Registry path that path parameter refers to * path [I] Path to the key (or handle - see RTL_GetKeyHandle) * name [I] Name of the registry value to set * type [I] Type of the registry key to set * data [I] Pointer to the user data to be set * length [I] Length of the user data pointed by data * * RETURNS * STATUS_SUCCESS if the specified key is successfully set, * or an NTSTATUS error code. */ NTSTATUS WINAPI RtlWriteRegistryValue( ULONG RelativeTo, PCWSTR path, PCWSTR name, ULONG type, PVOID data, ULONG length ) { HANDLE hkey; NTSTATUS status; UNICODE_STRING str; TRACE( "(%d, %s, %s) -> %d: %p [%d]\n", RelativeTo, debugstr_w(path), debugstr_w(name), type, data, length ); RtlInitUnicodeString( &str, name ); if (RelativeTo == RTL_REGISTRY_HANDLE) return NtSetValueKey( (HANDLE)path, &str, 0, type, data, length ); status = RTL_GetKeyHandle( RelativeTo, path, &hkey ); if (status != STATUS_SUCCESS) return status; status = NtSetValueKey( hkey, &str, 0, type, data, length ); NtClose( hkey ); return status; } /************************************************************************* * NtQueryLicenseValue [NTDLL.@] * * NOTES * On Windows all license properties are stored in a single key, but * unless there is some app which explicitly depends on that, there is * no good reason to reproduce that. */ NTSTATUS WINAPI NtQueryLicenseValue( const UNICODE_STRING *name, ULONG *result_type, PVOID data, ULONG length, ULONG *result_len ) { static const WCHAR LicenseInformationW[] = {'M','a','c','h','i','n','e','\\', 'S','o','f','t','w','a','r','e','\\', 'W','i','n','e','\\','L','i','c','e','n','s','e', 'I','n','f','o','r','m','a','t','i','o','n',0}; KEY_VALUE_PARTIAL_INFORMATION *info; NTSTATUS status = STATUS_OBJECT_NAME_NOT_FOUND; DWORD info_length, count; OBJECT_ATTRIBUTES attr; UNICODE_STRING keyW; HANDLE hkey; if (!name || !name->Buffer || !name->Length || !result_len) return STATUS_INVALID_PARAMETER; info_length = FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data) + length; info = RtlAllocateHeap( GetProcessHeap(), 0, info_length ); if (!info) return STATUS_NO_MEMORY; attr.Length = sizeof(attr); attr.RootDirectory = 0; attr.ObjectName = &keyW; attr.Attributes = 0; attr.SecurityDescriptor = NULL; attr.SecurityQualityOfService = NULL; RtlInitUnicodeString( &keyW, LicenseInformationW ); /* @@ Wine registry key: HKLM\Software\Wine\LicenseInformation */ if (!NtOpenKey( &hkey, KEY_READ, &attr )) { status = NtQueryValueKey( hkey, name, KeyValuePartialInformation, info, info_length, &count ); if (!status || status == STATUS_BUFFER_OVERFLOW) { if (result_type) *result_type = info->Type; *result_len = info->DataLength; if (status == STATUS_BUFFER_OVERFLOW) status = STATUS_BUFFER_TOO_SMALL; else memcpy( data, info->Data, info->DataLength ); } NtClose( hkey ); } if (status == STATUS_OBJECT_NAME_NOT_FOUND) FIXME( "License key %s not found\n", debugstr_w(name->Buffer) ); RtlFreeHeap( GetProcessHeap(), 0, info ); return status; }