* 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 of the License, or (at your option) any later version.
+ * 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
#include <ntstatus.h>
#include <winternl.h>
+#include "gstrfuncsprivate.h"
+
#ifndef _WDMDDK_
typedef enum _KEY_INFORMATION_CLASS {
KeyBasicInformation,
PULONG result_size);
typedef NTSTATUS
-(* NtNotifyChangeMultipleKeysFunc)(HANDLE key_handle,
- ULONG subkey_count,
- POBJECT_ATTRIBUTES subkeys,
- HANDLE event,
- PIO_APC_ROUTINE apc_routine,
- PVOID apc_closure,
- PIO_STATUS_BLOCK status_block,
- ULONG filter,
- BOOLEAN watch_tree,
- PVOID buffer,
- ULONG buffer_size,
- BOOLEAN async);
+(NTAPI * NtNotifyChangeMultipleKeysFunc)(HANDLE key_handle,
+ ULONG subkey_count,
+ POBJECT_ATTRIBUTES subkeys,
+ HANDLE event,
+ PIO_APC_ROUTINE apc_routine,
+ PVOID apc_closure,
+ PIO_STATUS_BLOCK status_block,
+ ULONG filter,
+ BOOLEAN watch_tree,
+ PVOID buffer,
+ ULONG buffer_size,
+ BOOLEAN async);
static NtQueryKeyFunc nt_query_key = NULL;
static NtNotifyChangeMultipleKeysFunc nt_notify_change_multiple_keys = NULL;
G_WIN32_REGISTRY_UPDATED_PATH = 1,
} GWin32RegistryKeyUpdateFlag;
+static gsize
+g_utf16_len (const gunichar2 *str)
+{
+ gsize result;
+
+ for (result = 0; str[0] != 0; str++, result++)
+ ;
+
+ return result;
+}
+
static gunichar2 *
-g_wcsdup (const gunichar2 *str,
- gssize str_size)
+g_wcsdup (const gunichar2 *str, gssize str_len)
{
- if (str_size == -1)
- {
- str_size = wcslen (str) + 1;
- str_size *= sizeof (gunichar2);
- }
- return g_memdup (str, str_size);
+ gsize str_len_unsigned;
+ gsize str_size;
+
+ g_return_val_if_fail (str != NULL, NULL);
+
+ if (str_len < 0)
+ str_len_unsigned = g_utf16_len (str);
+ else
+ str_len_unsigned = (gsize) str_len;
+
+ g_assert (str_len_unsigned <= G_MAXSIZE / sizeof (gunichar2) - 1);
+ str_size = (str_len_unsigned + 1) * sizeof (gunichar2);
+
+ return g_memdup2 (str, str_size);
}
/**
new_iter->value_name_size = iter->value_name_size;
if (iter->value_data != NULL)
- new_iter->value_data = g_memdup (iter->value_data, iter->value_data_size);
+ new_iter->value_data = g_memdup2 (iter->value_data, iter->value_data_size);
new_iter->value_data_size = iter->value_data_size;
new_iter->value_data_expanded_charsize = iter->value_data_expanded_charsize;
if (iter->value_data_expanded_u8 != NULL)
- new_iter->value_data_expanded_u8 = g_memdup (iter->value_data_expanded_u8,
- iter->value_data_expanded_charsize);
+ new_iter->value_data_expanded_u8 = g_memdup2 (iter->value_data_expanded_u8,
+ iter->value_data_expanded_charsize);
new_iter->value_data_expanded_u8_size = iter->value_data_expanded_charsize;
}
/**
+ * g_win32_registry_get_os_dirs_w:
+ *
+ * Returns a list of directories for DLL lookups.
+ * Can be used with g_win32_registry_key_get_value_w().
+ *
+ * Returns: (array zero-terminated=1) (transfer none): a %NULL-terminated array of UTF-16 strings.
+ *
+ * Since: 2.66
+ */
+const gunichar2 * const *
+g_win32_registry_get_os_dirs_w (void)
+{
+ static gunichar2 **mui_os_dirs = NULL;
+
+ if (g_once_init_enter (&mui_os_dirs))
+ {
+ gunichar2 **new_mui_os_dirs;
+ gunichar2 *system32 = NULL;
+ gunichar2 *syswow64 = NULL;
+ UINT buffer_size;
+ gsize array_index = 0;
+
+ buffer_size = GetSystemWow64DirectoryW (NULL, 0);
+
+ if (buffer_size > 0)
+ {
+ UINT copied;
+ syswow64 = g_malloc (buffer_size * sizeof (gunichar2));
+ copied = GetSystemWow64DirectoryW (syswow64, buffer_size);
+ if (copied <= 0)
+ g_clear_pointer (&syswow64, g_free);
+ }
+
+ buffer_size = GetSystemDirectoryW (NULL, 0);
+
+ if (buffer_size > 0)
+ {
+ UINT copied;
+ system32 = g_malloc (buffer_size * sizeof (gunichar2));
+ copied = GetSystemDirectoryW (system32, buffer_size);
+ if (copied <= 0)
+ g_clear_pointer (&system32, g_free);
+ }
+
+ new_mui_os_dirs = g_new0 (gunichar2 *, 3);
+
+ if (system32 != NULL)
+ new_mui_os_dirs[array_index++] = system32;
+
+ if (syswow64 != NULL)
+ new_mui_os_dirs[array_index++] = syswow64;
+
+ new_mui_os_dirs[array_index++] = NULL;
+
+ g_once_init_leave (&mui_os_dirs, new_mui_os_dirs);
+ }
+
+ return (const gunichar2 * const *) mui_os_dirs;
+}
+
+/**
+ * g_win32_registry_get_os_dirs:
+ *
+ * Returns a list of directories for DLL lookups.
+ * Can be used with g_win32_registry_key_get_value().
+ *
+ * Returns: (array zero-terminated=1) (transfer none): a %NULL-terminated array of UTF-8 strings.
+ *
+ * Since: 2.66
+ */
+const gchar * const *
+g_win32_registry_get_os_dirs (void)
+{
+ static gchar **mui_os_dirs = NULL;
+
+ if (g_once_init_enter (&mui_os_dirs))
+ {
+ gchar **new_mui_os_dirs;
+ gsize array_index;
+ gsize new_array_index;
+ const gunichar2 * const *mui_os_dirs_utf16 = g_win32_registry_get_os_dirs_w ();
+
+ for (array_index = 0; mui_os_dirs_utf16[array_index] != NULL; array_index++)
+ ;
+
+ new_mui_os_dirs = g_new0 (gchar *, array_index + 1);
+
+ for (array_index = 0, new_array_index = 0;
+ mui_os_dirs_utf16[array_index] != NULL;
+ array_index++)
+ {
+ new_mui_os_dirs[new_array_index] = g_utf16_to_utf8 (mui_os_dirs_utf16[array_index],
+ -1, NULL, NULL, NULL);
+ if (new_mui_os_dirs[new_array_index] != NULL)
+ new_array_index += 1;
+ else
+ g_critical ("Failed to convert to a system directory #%zu to UTF-8", array_index);
+ }
+
+ g_once_init_leave (&mui_os_dirs, new_mui_os_dirs);
+ }
+
+ return (const gchar * const *) mui_os_dirs;
+}
+
+/**
* g_win32_registry_key_get_value:
* @key: (in) (transfer none): a #GWin32RegistryKey
+ * @mui_dll_dirs: (in) (transfer none) (array zero-terminated=1) (optional): a %NULL-terminated
+ * array of directory names where the OS
+ * should look for a DLL indicated in a MUI string, if the
+ * DLL path in the string is not absolute
* @auto_expand: (in) %TRUE to automatically expand G_WIN32_REGISTRY_VALUE_EXPAND_STR
* to G_WIN32_REGISTRY_VALUE_STR.
* @value_name: (in) (transfer none): name of the value to get (in UTF-8).
* Get data from a value of a key. String data is guaranteed to be
* appropriately terminated and will be in UTF-8.
*
+ * When not %NULL, @mui_dll_dirs indicates that `RegLoadMUIStringW()` API
+ * should be used instead of the usual `RegQueryValueExW()`. This implies
+ * that the value being queried is of type `REG_SZ` or `REG_EXPAND_SZ` (if it is not, the function
+ * falls back to `RegQueryValueExW()`), and that this string must undergo special processing
+ * (see [`SHLoadIndirectString()` documentation](https://docs.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-shloadindirectstring) for an explanation on what
+ * kinds of strings are processed) to get the result.
+ *
+ * If no specific MUI DLL directories need to be used, pass
+ * the return value of g_win32_registry_get_os_dirs() as @mui_dll_dirs
+ * (as an bonus, the value from g_win32_registry_get_os_dirs()
+ * does not add any extra UTF8->UTF16 conversion overhead).
+ *
+ * @auto_expand works with @mui_dll_dirs, but only affects the processed
+ * string, making it somewhat useless. The unprocessed string is always expanded
+ * internally, if its type is `REG_EXPAND_SZ` - there is no need to enable
+ * @auto_expand for this to work.
+ *
+ * The API for this function changed in GLib 2.66 to add the @mui_dll_dirs argument.
+ *
* Returns: %TRUE on success, %FALSE on failure.
*
- * Since: 2.46
+ * Since: 2.66
**/
gboolean
g_win32_registry_key_get_value (GWin32RegistryKey *key,
+ const gchar * const *mui_dll_dirs,
gboolean auto_expand,
const gchar *value_name,
GWin32RegistryValueType *value_type,
gchar *value_data_u8;
gsize value_data_u8_len;
gboolean result;
+ gsize mui_dll_dirs_count;
+ gunichar2 **mui_dll_dirs_utf16;
+ const gchar * const *mui_os_dirs;
g_return_val_if_fail (G_IS_WIN32_REGISTRY_KEY (key), FALSE);
g_return_val_if_fail (value_name != NULL, FALSE);
if (value_name_w == NULL)
return FALSE;
+ mui_dll_dirs_utf16 = NULL;
+ mui_os_dirs = g_win32_registry_get_os_dirs ();
+
+ if (mui_dll_dirs != NULL &&
+ mui_dll_dirs != mui_os_dirs)
+ {
+ gsize i;
+
+ mui_dll_dirs_count = g_strv_length ((gchar **) mui_dll_dirs);
+ mui_dll_dirs_utf16 = g_new0 (gunichar2 *, mui_dll_dirs_count + 1);
+
+ for (i = 0; mui_dll_dirs[i] != NULL; i++)
+ {
+ mui_dll_dirs_utf16[i] = g_utf8_to_utf16 (mui_dll_dirs[i], -1, NULL, NULL, error);
+
+ if (mui_dll_dirs_utf16[i] == NULL)
+ break;
+ }
+
+ if (mui_dll_dirs[i] != NULL)
+ {
+ g_prefix_error (error,
+ "A mui_dll_dirs string #%zu `%s' failed to convert: ",
+ i, mui_dll_dirs[i]);
+
+ for (i = 0; i < mui_dll_dirs_count; i++)
+ g_free (mui_dll_dirs_utf16[i]);
+
+ g_free (mui_dll_dirs_utf16);
+ g_free (value_name_w);
+
+ return FALSE;
+ }
+ }
+ else if (mui_dll_dirs != NULL &&
+ mui_dll_dirs == mui_os_dirs)
+ {
+ mui_dll_dirs_utf16 = (gunichar2 **) g_win32_registry_get_os_dirs_w ();
+ }
+
result = g_win32_registry_key_get_value_w (key,
+ (const gunichar2 * const *) mui_dll_dirs_utf16,
auto_expand,
value_name_w,
&value_type_g,
error);
g_free (value_name_w);
+ if (mui_dll_dirs_utf16 != NULL &&
+ mui_dll_dirs != mui_os_dirs)
+ {
+ gsize array_index;
+ for (array_index = 0; mui_dll_dirs_utf16[array_index] != NULL; array_index++)
+ g_free (mui_dll_dirs_utf16[array_index]);
+ g_free (mui_dll_dirs_utf16);
+ }
if (!result)
return FALSE;
return TRUE;
}
+/* A wrapper that calls either RegQueryValueExW() or
+ * RegLoadMUIStringW() depending on the value of the
+ * last argument.
+ * Apart from the extra argument, the function behaves
+ * just like RegQueryValueExW(), with a few caveats.
+ */
+static LSTATUS
+MuiRegQueryValueExW (HKEY hKey,
+ LPCWSTR lpValueName,
+ LPDWORD lpReserved,
+ LPDWORD lpType,
+ LPBYTE lpData,
+ LPDWORD lpcbData,
+ const gunichar2 * const *mui_dll_dirs)
+{
+ gsize dir_index;
+ LSTATUS result = ERROR_PATH_NOT_FOUND;
+ DWORD bufsize;
+ DWORD data_size;
+ PVOID old_value;
+
+ if (mui_dll_dirs == NULL)
+ return RegQueryValueExW (hKey, lpValueName, lpReserved, lpType, lpData, lpcbData);
+
+ bufsize = 0;
+
+ if (lpcbData != NULL)
+ bufsize = *lpcbData;
+
+ if (mui_dll_dirs[0] != NULL)
+ {
+ /* Optimization: check that the value actually exists,
+ * before we start trying different mui dll dirs
+ */
+ result = RegQueryValueExW (hKey, lpValueName, NULL, NULL, NULL, 0);
+
+ if (result == ERROR_FILE_NOT_FOUND)
+ return result;
+ }
+
+ Wow64DisableWow64FsRedirection (&old_value);
+
+ /* Try with NULL dir first */
+ result = RegLoadMUIStringW (hKey,
+ lpValueName,
+ (wchar_t *) lpData,
+ bufsize,
+ &data_size,
+ 0,
+ NULL);
+
+ /* Not a MUI value, load normally */
+ if (result == ERROR_INVALID_DATA)
+ {
+ Wow64RevertWow64FsRedirection (old_value);
+
+ return RegQueryValueExW (hKey, lpValueName, lpReserved, lpType, lpData, lpcbData);
+ }
+
+ for (dir_index = 0;
+ result == ERROR_FILE_NOT_FOUND &&
+ mui_dll_dirs[dir_index] != NULL;
+ dir_index++)
+ result = RegLoadMUIStringW (hKey,
+ lpValueName,
+ (wchar_t *) lpData,
+ bufsize,
+ &data_size,
+ 0,
+ mui_dll_dirs[dir_index]);
+
+ Wow64RevertWow64FsRedirection (old_value);
+
+ if (lpcbData != NULL &&
+ result == ERROR_MORE_DATA)
+ *lpcbData = data_size;
+
+ if (lpType != NULL &&
+ result != ERROR_INVALID_DATA &&
+ result != ERROR_FILE_NOT_FOUND)
+ *lpType = REG_SZ;
+
+ return result;
+}
+
/**
* g_win32_registry_key_get_value_w:
* @key: (in) (transfer none): a #GWin32RegistryKey
+ * @mui_dll_dirs: (in) (transfer none) (array zero-terminated=1) (optional): a %NULL-terminated
+ * array of directory names where the OS
+ * should look for a DLL indicated in a MUI string, if the
+ * DLL path in the string is not absolute
* @auto_expand: (in) %TRUE to automatically expand G_WIN32_REGISTRY_VALUE_EXPAND_STR
* to G_WIN32_REGISTRY_VALUE_STR.
* @value_name: (in) (transfer none): name of the value to get (in UTF-16).
* by @value_data.
* @error: (nullable): a pointer to %NULL #GError, or %NULL
*
- * Get data from a value of a key.
- *
* Get data from a value of a key. String data is guaranteed to be
* appropriately terminated and will be in UTF-16.
*
* When calling with value_data == NULL (to get data size without getting
* the data itself) remember that returned size corresponds to possibly
* unterminated string data (if value is some kind of string), because
- * termination cannot be checked and fixed unless the data is retreived
+ * termination cannot be checked and fixed unless the data is retrieved
* too.
*
+ * When not %NULL, @mui_dll_dirs indicates that `RegLoadMUIStringW()` API
+ * should be used instead of the usual `RegQueryValueExW()`. This implies
+ * that the value being queried is of type `REG_SZ` or `REG_EXPAND_SZ` (if it is not, the function
+ * falls back to `RegQueryValueExW()`), and that this string must undergo special processing
+ * (see [`SHLoadIndirectString()` documentation](https://docs.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-shloadindirectstring) for an explanation on what
+ * kinds of strings are processed) to get the result.
+ *
+ * If no specific MUI DLL directories need to be used, pass
+ * the return value of g_win32_registry_get_os_dirs_w() as @mui_dll_dirs.
+ *
+ * @auto_expand works with @mui_dll_dirs, but only affects the processed
+ * string, making it somewhat useless. The unprocessed string is always expanded
+ * internally, if its type is `REG_EXPAND_SZ` - there is no need to enable
+ * @auto_expand for this to work.
+ *
+ * The API for this function changed in GLib 2.66 to add the @mui_dll_dirs argument.
+ *
* Returns: %TRUE on success, %FALSE on failure.
*
- * Since: 2.46
+ * Since: 2.66
**/
gboolean
g_win32_registry_key_get_value_w (GWin32RegistryKey *key,
+ const gunichar2 * const *mui_dll_dirs,
gboolean auto_expand,
const gunichar2 *value_name,
GWin32RegistryValueType *value_type,
value_data_size != NULL, FALSE);
req_value_data_size = 0;
- status = RegQueryValueExW (key->priv->handle,
- value_name,
- NULL,
- &value_type_w,
- NULL,
- &req_value_data_size);
+ status = MuiRegQueryValueExW (key->priv->handle,
+ value_name,
+ NULL,
+ &value_type_w,
+ NULL,
+ &req_value_data_size,
+ mui_dll_dirs);
if (status != ERROR_MORE_DATA && status != ERROR_SUCCESS)
{
req_value_data = g_malloc (req_value_data_size + sizeof (gunichar2) * 2);
req_value_data_size2 = req_value_data_size;
- status = RegQueryValueExW (key->priv->handle,
- value_name,
- NULL,
- &value_type_w2,
- (gpointer) req_value_data,
- &req_value_data_size2);
+ status = MuiRegQueryValueExW (key->priv->handle,
+ value_name,
+ NULL,
+ &value_type_w2,
+ (gpointer) req_value_data,
+ &req_value_data_size2,
+ mui_dll_dirs);
if (status != ERROR_SUCCESS)
{
switch (prop_id)
{
case PROP_PATH:
- g_assert (priv->absolute_path_w == NULL);
- g_assert (priv->absolute_path == NULL);
path = g_value_get_string (value);
if (path == NULL)
if (path_w == NULL)
break;
- g_free (priv->absolute_path_w);
- g_free (priv->absolute_path);
+ /* Construct only */
+ g_assert (priv->absolute_path_w == NULL);
+ g_assert (priv->absolute_path == NULL);
priv->absolute_path_w = path_w;
priv->absolute_path = g_value_dup_string (value);
break;
case PROP_PATH_UTF16:
- g_assert (priv->absolute_path_w == NULL);
- g_assert (priv->absolute_path == NULL);
path_w = (gunichar2 *) g_value_get_pointer (value);
if (path_w == NULL)
break;
+ /* Construct only */
+ g_assert (priv->absolute_path_w == NULL);
priv->absolute_path_w = g_wcsdup (path_w, -1);
break;