mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-26 23:46:15 +01:00
985cab7737
Fixes the following warnings on CLang: ../glib/gio/gregistrysettingsbackend.c:1503:32: warning: implicit truncation from 'int' to a one-bit wide bit-field changes value from 1 to -1 [-Wsingle-bit-bitfield-constant-conversion] child_item->readable = TRUE; ^ ~~~~ ../glib/gio/gregistrysettingsbackend.c:1567:28: warning: implicit truncation from 'int' to a one-bit wide bit-field changes value from 1 to -1 [-Wsingle-bit-bitfield-constant-conversion] child_item->readable = TRUE; ^ ~~~~
2367 lines
72 KiB
C
2367 lines
72 KiB
C
/*
|
|
* Copyright © 2009-10 Sam Thursfield
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
|
*
|
|
* 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, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Author: Sam Thursfield <ssssam@gmail.com>
|
|
*/
|
|
|
|
/* GRegistrySettingsBackend implementation notes:
|
|
*
|
|
* - All settings are stored under the registry path given at construction
|
|
* time. Permissions and system-wide defaults are not implemented and will
|
|
* probably always be out of scope of the Windows port of GLib.
|
|
*
|
|
* - The registry type system is limited. Most GVariant types are stored as
|
|
* literals via g_variant_print/parse(). Strings are stored without the
|
|
* quotes that GVariant requires. Integer types are stored as native
|
|
* REG_DWORD or REG_QWORD. The REG_MULTI_SZ (string array) type could be
|
|
* used to avoid flattening container types.
|
|
*
|
|
* - Notifications are handled; the change event is watched for in a separate
|
|
* thread (Windows does not provide a callback API) which sends them with
|
|
* g_idle_add to the GLib main loop. The threading is done using Windows
|
|
* API functions, so there is no dependence on GThread.
|
|
*
|
|
* - Windows doesn't tell us which value has changed. This means we have to
|
|
* maintain a cache of every stored value so we can play spot the
|
|
* difference. This should not be a performance issue because if you are
|
|
* storing thousands of values in GSettings, you are probably using it
|
|
* wrong.
|
|
*
|
|
* - The cache stores the value as a registry type. Because many variants are
|
|
* stored as string representations, values which have changed equality but
|
|
* not equivalence may trigger spurious change notifications. GSettings
|
|
* users must already deal with this possibility and converting all data to
|
|
* GVariant values would be more effort.
|
|
*
|
|
* - Because we have to cache every registry value locally, reads are done
|
|
* from the cache rather than directly from the registry. Writes update
|
|
* both. This means that the backend will not work if the watch thread is
|
|
* not running. A GSettings object always subscribes to changes so we can
|
|
* be sure that the watch thread will be running, but if for some reason
|
|
* the backend is being used directly you should bear that in mind.
|
|
*
|
|
* - The registry is totally user-editable, so we are very forgiving about
|
|
* errors in the data we get.
|
|
*
|
|
* - The registry uses backslashes as path separators. GSettings keys only
|
|
* allow [A-Za-z\-] so no escaping is needed. No attempt is made to solve
|
|
* clashes between keys differing only in case.
|
|
*
|
|
* - RegCreateKeyW is used - We should always make the UTF-8 -> UTF-16
|
|
* conversion ourselves to avoid problems when the system language changes.
|
|
*
|
|
* - The Windows registry has the following limitations: a key may not exceed
|
|
* 255 characters, an entry's value may not exceed 16,383 characters, and
|
|
* all the values of a key may not exceed 65,535 characters.
|
|
*
|
|
* - Terminology:
|
|
* * in GSettings, a 'key' is eg. /desktop/gnome/background/primary-color
|
|
* * in the registry, the 'key' is path, which contains some 'values'.
|
|
* * in this file, any GSettings key is a 'key', while a registry key is
|
|
* termed a 'path', which contains 'values'.
|
|
*
|
|
* - My set of tests for this backend are currently at:
|
|
* http://gitorious.org/gsettings-gtk/gsettings-test.git
|
|
*
|
|
* - There is an undocumented function in ntdll.dll which might be more
|
|
* than RegNotifyChangeKeyValue(), NtNotifyChangeKey:
|
|
* http://source.winehq.org/source/dlls/ntdll/reg.c#L618
|
|
* http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/Key/NtNotifyChangeKey.html
|
|
*
|
|
* - If updating the cache ever becomes a performance issue it may make sense
|
|
* to use a red-black tree, but I don't currently think it's worth the time
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "gregistrysettingsbackend.h"
|
|
#include "gsettingsbackend.h"
|
|
#include "gsettingsbackendinternal.h"
|
|
#include "giomodule-priv.h"
|
|
|
|
#include <glibintl.h>
|
|
|
|
#include <windows.h>
|
|
|
|
//#define TRACE
|
|
|
|
/* GSettings' limit */
|
|
#define MAX_KEY_NAME_LENGTH 128
|
|
|
|
/* Testing (on Windows XP SP3) shows that WaitForMultipleObjects fails with
|
|
* "The parameter is incorrect" after 64 watches. We need one for the
|
|
* message_sent cond, which is allowed for in the way the watches_remaining
|
|
* variable is used.
|
|
*/
|
|
#define MAX_WATCHES 64
|
|
|
|
/* A watch on one registry path and its subkeys */
|
|
typedef struct
|
|
{
|
|
HANDLE event;
|
|
HKEY hpath;
|
|
char *prefix;
|
|
GNode *cache_node;
|
|
} RegistryWatch;
|
|
|
|
/* Simple message passing for the watch thread. Not enough traffic to
|
|
* justify a queue.
|
|
*/
|
|
typedef enum
|
|
{
|
|
WATCH_THREAD_NONE,
|
|
WATCH_THREAD_ADD_WATCH,
|
|
WATCH_THREAD_REMOVE_WATCH,
|
|
WATCH_THREAD_STOP
|
|
} WatchThreadMessageType;
|
|
|
|
typedef struct
|
|
{
|
|
WatchThreadMessageType type;
|
|
RegistryWatch watch;
|
|
} WatchThreadMessage;
|
|
|
|
typedef struct
|
|
{
|
|
GSettingsBackend *owner;
|
|
HANDLE *thread;
|
|
|
|
/* Details of the things we are watching. */
|
|
int watches_remaining;
|
|
GPtrArray *events, *handles, *prefixes, *cache_nodes;
|
|
|
|
/* Communication with the main thread. Only one message is stored at a time,
|
|
* to make sure that messages are acknowledged before being overwritten we
|
|
* create two events - one is signalled when a new message is set, the
|
|
* other is signalled by the thread when it has processed the message.
|
|
*/
|
|
WatchThreadMessage message;
|
|
CRITICAL_SECTION *message_lock;
|
|
HANDLE message_sent_event, message_received_event;
|
|
} WatchThreadState;
|
|
|
|
#define G_TYPE_REGISTRY_SETTINGS_BACKEND (g_registry_settings_backend_get_type ())
|
|
#define G_REGISTRY_SETTINGS_BACKEND(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
|
|
G_TYPE_REGISTRY_SETTINGS_BACKEND, GRegistrySettingsBackend))
|
|
#define G_IS_REGISTRY_SETTINGS_BACKEND(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \
|
|
G_TYPE_REGISTRY_SETTINGS_BACKEND))
|
|
|
|
typedef enum {
|
|
PROP_REGISTRY_KEY = 1,
|
|
} GRegistrySettingsBackendProperty;
|
|
|
|
typedef GSettingsBackendClass GRegistrySettingsBackendClass;
|
|
|
|
typedef struct {
|
|
GSettingsBackend parent_instance;
|
|
|
|
HKEY base_key;
|
|
gchar *base_path;
|
|
gunichar2 *base_pathw;
|
|
|
|
/* A stored copy of the whole tree being watched. When we receive a change notification
|
|
* we have to check against this to see what has changed ... every time ...*/
|
|
CRITICAL_SECTION *cache_lock;
|
|
GNode *cache_root;
|
|
|
|
WatchThreadState *watch;
|
|
} GRegistrySettingsBackend;
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (GRegistrySettingsBackend,
|
|
g_registry_settings_backend,
|
|
G_TYPE_SETTINGS_BACKEND,
|
|
_g_io_modules_ensure_extension_points_registered ();
|
|
g_io_extension_point_implement (G_SETTINGS_BACKEND_EXTENSION_POINT_NAME,
|
|
g_define_type_id, "registry", 90))
|
|
|
|
/**********************************************************************************
|
|
* Utility functions
|
|
**********************************************************************************/
|
|
|
|
#include <stdio.h>
|
|
static void
|
|
trace (const char *format,
|
|
...)
|
|
{
|
|
#ifdef TRACE
|
|
va_list va; va_start (va, format);
|
|
vprintf (format, va);
|
|
fflush (stdout);
|
|
va_end (va);
|
|
#endif
|
|
}
|
|
|
|
/* g_message including a windows error message. It is not useful to have an
|
|
* equivalent function for g_warning because none of the registry errors can
|
|
* result from programmer error (Microsoft programmers don't count), instead
|
|
* they will mostly occur from people messing with the registry by hand. */
|
|
static void G_GNUC_PRINTF (2, 3)
|
|
g_message_win32_error (DWORD result_code,
|
|
const gchar *format,
|
|
...)
|
|
{
|
|
va_list va;
|
|
gchar *message;
|
|
gchar *win32_error;
|
|
gchar *win32_message;
|
|
|
|
g_return_if_fail (result_code != 0);
|
|
|
|
va_start (va, format);
|
|
message = g_strdup_vprintf (format, va);
|
|
win32_error = g_win32_error_message (result_code);
|
|
win32_message = g_strdup_printf ("%s: %s", message, win32_error);
|
|
g_free (message);
|
|
g_free (win32_error);
|
|
|
|
if (result_code == ERROR_KEY_DELETED)
|
|
trace ("(%s)", win32_message);
|
|
else
|
|
g_message ("%s", win32_message);
|
|
|
|
g_free (win32_message);
|
|
}
|
|
|
|
/* Make gsettings key into a registry path & value pair.
|
|
*
|
|
* Note that the return value *only* needs freeing - registry_value_name
|
|
* is a pointer to further inside the same block of memory.
|
|
*/
|
|
static gchar *
|
|
parse_key (const gchar *key_name,
|
|
const gchar *registry_prefix,
|
|
gchar **value_name)
|
|
{
|
|
gchar *path_name, *c;
|
|
|
|
/* All key paths are treated as absolute; gsettings doesn't seem to enforce a
|
|
* preceding /.
|
|
*/
|
|
if (key_name[0] == '/')
|
|
key_name++;
|
|
|
|
if (registry_prefix == NULL)
|
|
path_name = g_strdup (key_name);
|
|
else
|
|
path_name = g_strjoin ("/", registry_prefix, key_name, NULL);
|
|
|
|
/* Prefix is expected to be in registry format (\ separators) so don't escape that. */
|
|
for (c = path_name + (registry_prefix ? strlen (registry_prefix) : 0); *c != 0; c++)
|
|
{
|
|
if (*c == '/')
|
|
{
|
|
*c = '\\';
|
|
*value_name = c;
|
|
}
|
|
}
|
|
|
|
**value_name = 0;
|
|
(*value_name)++;
|
|
|
|
return path_name;
|
|
}
|
|
|
|
static DWORD
|
|
g_variant_get_as_dword (GVariant *variant)
|
|
{
|
|
switch (g_variant_get_type_string (variant)[0])
|
|
{
|
|
case 'b':
|
|
return g_variant_get_boolean (variant);
|
|
case 'y':
|
|
return g_variant_get_byte (variant);
|
|
case 'n':
|
|
return g_variant_get_int16 (variant);
|
|
case 'q':
|
|
return g_variant_get_uint16 (variant);
|
|
case 'i':
|
|
return g_variant_get_int32 (variant);
|
|
case 'u':
|
|
return g_variant_get_uint32 (variant);
|
|
default:
|
|
g_warn_if_reached ();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static DWORDLONG
|
|
g_variant_get_as_qword (GVariant *variant)
|
|
{
|
|
switch (g_variant_get_type_string (variant)[0])
|
|
{
|
|
case 'x':
|
|
return g_variant_get_int64 (variant);
|
|
case 't':
|
|
return g_variant_get_uint64 (variant);
|
|
default:
|
|
g_warn_if_reached ();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
handle_read_error (LONG result,
|
|
const gchar *path_name,
|
|
const gchar *value_name)
|
|
{
|
|
/* file not found means key value not set, this isn't an error for us. */
|
|
if (result != ERROR_FILE_NOT_FOUND)
|
|
g_message_win32_error (result, "Unable to query value %s/%s",
|
|
path_name, value_name);
|
|
}
|
|
|
|
typedef struct {
|
|
HKEY handle;
|
|
const char *name;
|
|
} HandleNamePair;
|
|
|
|
static const HandleNamePair predefined_key_names[] = {
|
|
{ HKEY_CLASSES_ROOT, "HKEY_CLASSES_ROOT" },
|
|
{ HKEY_CURRENT_CONFIG, "HKEY_CURRENT_CONFIG" },
|
|
{ HKEY_CURRENT_USER, "HKEY_CURRENT_USER" },
|
|
{ HKEY_LOCAL_MACHINE, "HKEY_LOCAL_MACHINE" },
|
|
{ HKEY_USERS, "HKEY_USERS" }
|
|
};
|
|
|
|
static const gchar*
|
|
predefined_key_to_string (HKEY key)
|
|
{
|
|
for (gsize i = 0; i < G_N_ELEMENTS (predefined_key_names); i++)
|
|
{
|
|
if (predefined_key_names[i].handle == key)
|
|
return predefined_key_names[i].name;
|
|
}
|
|
|
|
g_warning ("gregistrysettingsbackend: unexpected root key (%p)", key);
|
|
return "";
|
|
}
|
|
|
|
static const gchar*
|
|
split_registry_path (const gchar *full_path,
|
|
HKEY *root_key)
|
|
{
|
|
g_assert (full_path != NULL);
|
|
|
|
for (gsize i = 0; i < G_N_ELEMENTS (predefined_key_names); i++)
|
|
{
|
|
const gchar *root_name = predefined_key_names[i].name;
|
|
|
|
if (g_str_has_prefix (full_path, root_name))
|
|
{
|
|
const gchar *rest = full_path + strlen (root_name);
|
|
|
|
if (*rest == '\\')
|
|
{
|
|
if (root_key != NULL)
|
|
*root_key = predefined_key_names[i].handle;
|
|
|
|
return ++rest;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/***************************************************************************
|
|
* Cache of registry values
|
|
***************************************************************************/
|
|
|
|
/* Generic container for registry values */
|
|
typedef struct {
|
|
DWORD type;
|
|
|
|
union {
|
|
gint dword; /* FIXME: could inline QWORD on 64-bit systems too */
|
|
void *ptr;
|
|
};
|
|
} RegistryValue;
|
|
|
|
static char *
|
|
registry_value_dump (RegistryValue value)
|
|
{
|
|
if (value.type == REG_DWORD)
|
|
return g_strdup_printf ("%d", value.dword);
|
|
else if (value.type == REG_QWORD)
|
|
return g_strdup_printf ("%"G_GINT64_FORMAT, value.ptr == NULL ? 0: *(DWORDLONG *)value.ptr);
|
|
else if (value.type == REG_SZ)
|
|
return g_strdup_printf ("%s", (char *)value.ptr);
|
|
else if (value.type == REG_NONE)
|
|
return g_strdup_printf ("<empty>");
|
|
else
|
|
return g_strdup_printf ("<invalid>");
|
|
}
|
|
|
|
static void
|
|
registry_value_free (RegistryValue value)
|
|
{
|
|
if (value.type == REG_SZ || value.type == REG_QWORD)
|
|
g_free (value.ptr);
|
|
|
|
value.type = REG_NONE;
|
|
value.ptr = NULL;
|
|
}
|
|
|
|
/* The registry cache is stored as a tree, for easy traversal. Right now we
|
|
* don't sort it in a clever way. Each node corresponds to a path element
|
|
* ('key' in registry terms) or a value.
|
|
*
|
|
* Each subscription uses the same cache. Because GSettings can subscribe to
|
|
* the tree at any node any number of times, we need to reference count the
|
|
* nodes.
|
|
*/
|
|
typedef struct
|
|
{
|
|
/* Component of path that this node represents */
|
|
gchar *name;
|
|
|
|
/* If a watch is subscribed at this point (subscription_count > 0) we can
|
|
* block its next notification. This is useful because if two watches cover
|
|
* the same path, both will trigger when it changes. It also allows changes
|
|
* done by the application to be ignored by the watch thread.
|
|
*/
|
|
gint32 block_count : 8;
|
|
|
|
/* Number of times g_settings_subscribe has been called for this location
|
|
* (I guess you can't subscribe more than 16383 times) */
|
|
gint32 subscription_count : 14;
|
|
|
|
gint32 ref_count : 9;
|
|
|
|
guint32 readable : 1;
|
|
RegistryValue value;
|
|
} RegistryCacheItem;
|
|
|
|
static GNode *
|
|
registry_cache_add_item (GNode *parent,
|
|
gchar *name,
|
|
RegistryValue value,
|
|
gint ref_count)
|
|
{
|
|
RegistryCacheItem *item;
|
|
GNode *cache_node;
|
|
|
|
g_return_val_if_fail (name != NULL, NULL);
|
|
g_return_val_if_fail (parent != NULL, NULL);
|
|
|
|
item = g_slice_new (RegistryCacheItem);
|
|
|
|
/* Ref count should be the number of watch points above this node */
|
|
item->ref_count = ref_count;
|
|
|
|
item->name = g_strdup (name);
|
|
item->value = value;
|
|
item->subscription_count = 0;
|
|
item->block_count = 0;
|
|
item->readable = FALSE;
|
|
|
|
trace ("\tregistry cache: adding %s to %s\n",
|
|
name, ((RegistryCacheItem *)parent->data)->name);
|
|
|
|
cache_node = g_node_new (item);
|
|
g_node_append (parent, cache_node);
|
|
|
|
return cache_node;
|
|
}
|
|
|
|
/* The reference counting of cache tree nodes works like this: when a node is
|
|
* subscribed to (GSettings wants us to watch that path and everything below
|
|
* it) the reference count of that node and everything below is increased, as
|
|
* well as each parent up to the root.
|
|
*/
|
|
|
|
static void
|
|
_ref_down (GNode *node)
|
|
{
|
|
RegistryCacheItem *item = node->data;
|
|
|
|
g_node_children_foreach (node, G_TRAVERSE_ALL,
|
|
(GNodeForeachFunc)_ref_down, NULL);
|
|
item->ref_count++;
|
|
}
|
|
|
|
static void
|
|
registry_cache_ref_tree (GNode *tree)
|
|
{
|
|
RegistryCacheItem *item = tree->data;
|
|
GNode *node = tree->parent;
|
|
|
|
g_return_if_fail (tree != NULL);
|
|
|
|
item->ref_count++;
|
|
|
|
g_node_children_foreach (tree, G_TRAVERSE_ALL,
|
|
(GNodeForeachFunc)_ref_down, NULL);
|
|
|
|
for (node = tree->parent; node; node = node->parent)
|
|
{
|
|
item = node->data;
|
|
item->ref_count++;
|
|
}
|
|
}
|
|
|
|
static void
|
|
registry_cache_item_free (RegistryCacheItem *item)
|
|
{
|
|
trace ("\t -- Free node %s\n", item->name);
|
|
|
|
g_free (item->name);
|
|
registry_value_free (item->value);
|
|
g_slice_free (RegistryCacheItem, item);
|
|
}
|
|
|
|
/* Unreferencing has to be done bottom-up */
|
|
static void
|
|
_unref_node (GNode *node)
|
|
{
|
|
RegistryCacheItem *item = node->data;
|
|
|
|
item->ref_count--;
|
|
|
|
g_warn_if_fail (item->ref_count >= 0);
|
|
|
|
if (item->ref_count == 0)
|
|
{
|
|
registry_cache_item_free (item);
|
|
g_node_destroy (node);
|
|
}
|
|
}
|
|
|
|
static void
|
|
_unref_down (GNode *node)
|
|
{
|
|
g_node_children_foreach (node, G_TRAVERSE_ALL,
|
|
(GNodeForeachFunc)_unref_down, NULL);
|
|
_unref_node (node);
|
|
}
|
|
|
|
static void
|
|
registry_cache_unref_tree (GNode *tree)
|
|
{
|
|
GNode *parent = tree->parent, *next_parent;
|
|
|
|
_unref_down (tree);
|
|
|
|
while (parent)
|
|
{
|
|
next_parent = parent->parent;
|
|
_unref_node (parent);
|
|
parent = next_parent;
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
static void
|
|
registry_cache_dump (GNode *cache_node,
|
|
gpointer data)
|
|
{
|
|
RegistryCacheItem *item = cache_node->data;
|
|
|
|
int depth = GPOINTER_TO_INT(data),
|
|
new_depth = depth+1,
|
|
i;
|
|
|
|
g_return_if_fail (cache_node != NULL);
|
|
|
|
for (i=0; i<depth; i++)
|
|
g_print (" ");
|
|
if (item == NULL)
|
|
g_print ("*root*\n");
|
|
else
|
|
g_print ("'%s' [%i] @ %x = %s\n", item->name, item->ref_count, (guint)cache_node,
|
|
registry_value_dump (item->value));
|
|
g_node_children_foreach (cache_node, G_TRAVERSE_ALL, registry_cache_dump,
|
|
GINT_TO_POINTER (new_depth));
|
|
}
|
|
#endif
|
|
|
|
typedef struct
|
|
{
|
|
gchar *name;
|
|
GNode *result;
|
|
} RegistryCacheSearch;
|
|
|
|
static gboolean
|
|
registry_cache_find_compare (GNode *node,
|
|
gpointer data)
|
|
{
|
|
RegistryCacheSearch *search = data;
|
|
RegistryCacheItem *item = node->data;
|
|
|
|
if (item == NULL) /* root node */
|
|
return FALSE;
|
|
|
|
g_return_val_if_fail (search->name != NULL, FALSE);
|
|
g_return_val_if_fail (item->name != NULL, FALSE);
|
|
|
|
if (strcmp (search->name, item->name) == 0)
|
|
{
|
|
search->result = node;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static GNode *
|
|
registry_cache_find_immediate_child (GNode *node,
|
|
gchar *name)
|
|
{
|
|
RegistryCacheSearch search;
|
|
|
|
search.result = NULL;
|
|
search.name = name;
|
|
|
|
g_node_traverse (node, G_POST_ORDER, G_TRAVERSE_ALL, 2,
|
|
registry_cache_find_compare, &search);
|
|
|
|
return search.result;
|
|
}
|
|
|
|
static GNode *
|
|
registry_cache_get_node_for_key_recursive (GNode *node,
|
|
gchar *key_name,
|
|
gboolean create_if_not_found,
|
|
gint n_parent_watches)
|
|
{
|
|
RegistryCacheItem *item;
|
|
gchar *component = key_name;
|
|
gchar *c = strchr (component, '/');
|
|
GNode *child;
|
|
|
|
if (c != NULL)
|
|
*c = 0;
|
|
|
|
/* We count up how many watch points we travel through finding this node,
|
|
* because a new node should have as many references as there are watches at
|
|
* points above it in the tree.
|
|
*/
|
|
item = node->data;
|
|
if (item->subscription_count > 0)
|
|
n_parent_watches++;
|
|
|
|
child = registry_cache_find_immediate_child (node, component);
|
|
if (child == NULL && create_if_not_found)
|
|
{
|
|
RegistryValue null_value = { REG_NONE, {0} };
|
|
|
|
child = registry_cache_add_item (node, component,
|
|
null_value, n_parent_watches);
|
|
|
|
trace ("\tget node for key recursive: new %x = %s.\n", node, component);
|
|
}
|
|
|
|
/* We are done if there are no more path components. Allow for a trailing /. */
|
|
if (child == NULL || c == NULL || *(c + 1) == 0)
|
|
return child;
|
|
|
|
trace ("get node for key recursive: next: %s.\n", c + 1);
|
|
|
|
return registry_cache_get_node_for_key_recursive (child, c + 1,
|
|
create_if_not_found,
|
|
n_parent_watches);
|
|
}
|
|
|
|
/* Look up a GSettings key in the cache. */
|
|
static GNode *
|
|
registry_cache_get_node_for_key (GNode *root,
|
|
const gchar *key_name,
|
|
gboolean create_if_not_found)
|
|
{
|
|
GNode *child = NULL;
|
|
GNode *result = NULL;
|
|
gchar *component, *c;
|
|
|
|
g_return_val_if_fail (key_name != NULL, NULL);
|
|
|
|
if (key_name[0] == '/')
|
|
key_name++;
|
|
|
|
/* Ignore preceding / */
|
|
component = g_strdup (key_name);
|
|
c = strchr (component, '/');
|
|
|
|
if (c == NULL)
|
|
{
|
|
g_free (component);
|
|
return root;
|
|
}
|
|
|
|
if (c != NULL)
|
|
*c = 0;
|
|
|
|
child = registry_cache_find_immediate_child (root, component);
|
|
if (child == NULL && create_if_not_found)
|
|
{
|
|
RegistryValue null_value = { REG_NONE, {0} };
|
|
|
|
/* Reference count is set to 0, tree should be referenced by the caller */
|
|
child = registry_cache_add_item (root, component,
|
|
null_value, 0);
|
|
|
|
trace ("get_node_for_key: New node for component '%s'\n", component);
|
|
}
|
|
|
|
if (*(c + 1) == 0)
|
|
result = child;
|
|
else if (child != NULL)
|
|
result = registry_cache_get_node_for_key_recursive (child, c + 1,
|
|
create_if_not_found, 0);
|
|
|
|
g_free (component);
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Check the cache node against the registry key it represents. Return TRUE if
|
|
* they differ, and update the cache with the new value.
|
|
*/
|
|
static gboolean
|
|
registry_cache_update_node (GNode *cache_node,
|
|
RegistryValue registry_value)
|
|
{
|
|
RegistryCacheItem *cache_item;
|
|
|
|
g_return_val_if_fail (cache_node != NULL, FALSE);
|
|
g_return_val_if_fail (cache_node->data != NULL, FALSE);
|
|
|
|
cache_item = cache_node->data;
|
|
|
|
if (registry_value.type != cache_item->value.type)
|
|
{
|
|
/* The type has changed. Update cache item and register it as changed.
|
|
* Either the schema has changed and this is entirely legitimate, or
|
|
* whenever the app reads the key it will get the default value due to
|
|
* the type mismatch.
|
|
*/
|
|
cache_item->value = registry_value;
|
|
return TRUE;
|
|
}
|
|
|
|
switch (registry_value.type)
|
|
{
|
|
case REG_DWORD:
|
|
{
|
|
if (cache_item->value.dword == registry_value.dword)
|
|
return FALSE;
|
|
else
|
|
{
|
|
cache_item->value.dword = registry_value.dword;
|
|
return TRUE;
|
|
}
|
|
}
|
|
case REG_QWORD:
|
|
{
|
|
g_return_val_if_fail (registry_value.ptr != NULL &&
|
|
cache_item->value.ptr != NULL, FALSE);
|
|
|
|
if (memcmp (registry_value.ptr, cache_item->value.ptr, 8)==0)
|
|
{
|
|
g_free (registry_value.ptr);
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
g_free (cache_item->value.ptr);
|
|
cache_item->value.ptr = registry_value.ptr;
|
|
return TRUE;
|
|
}
|
|
}
|
|
case REG_SZ:
|
|
{
|
|
/* Value should not exist if it is NULL, an empty string is "" */
|
|
g_return_val_if_fail (cache_item->value.ptr != NULL, FALSE);
|
|
g_return_val_if_fail (registry_value.ptr != NULL, FALSE);
|
|
|
|
if (strcmp (registry_value.ptr, cache_item->value.ptr) == 0)
|
|
{
|
|
g_free (registry_value.ptr);
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
g_free (cache_item->value.ptr);
|
|
cache_item->value.ptr = registry_value.ptr;
|
|
return TRUE;
|
|
}
|
|
}
|
|
default:
|
|
g_warning ("gregistrysettingsbackend: registry_cache_update_node: Unhandled value type");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* Blocking notifications is a useful optimisation. When a change is made
|
|
* through GSettings we update the cache manually, but a notification is
|
|
* triggered as well. This function is also used for nested notifications,
|
|
* eg. if /test and /test/foo are watched, and /test/foo/value is changed then
|
|
* we will get notified both for /test/foo and /test and it is helpful to block
|
|
* the second.
|
|
*/
|
|
static void
|
|
registry_cache_block_notification (GNode *node)
|
|
{
|
|
RegistryCacheItem *item = node->data;
|
|
|
|
g_return_if_fail (node != NULL);
|
|
|
|
if (item->subscription_count > 0)
|
|
item->block_count++;
|
|
|
|
if (node->parent != NULL)
|
|
registry_cache_block_notification (node->parent);
|
|
}
|
|
|
|
static void registry_cache_destroy_tree (GNode *node,
|
|
WatchThreadState *self);
|
|
|
|
/***************************************************************************
|
|
* Reading and writing
|
|
***************************************************************************/
|
|
|
|
static gboolean
|
|
registry_read (HKEY hpath,
|
|
const gchar *path_name,
|
|
const gchar *value_name,
|
|
RegistryValue *p_value)
|
|
{
|
|
LONG result;
|
|
DWORD value_data_size;
|
|
gpointer *buffer;
|
|
gunichar2 *value_namew;
|
|
|
|
g_return_val_if_fail (p_value != NULL, FALSE);
|
|
|
|
p_value->type = REG_NONE;
|
|
p_value->ptr = NULL;
|
|
|
|
value_namew = g_utf8_to_utf16 (value_name, -1, NULL, NULL, NULL);
|
|
|
|
result = RegQueryValueExW (hpath, value_namew, 0, &p_value->type, NULL, &value_data_size);
|
|
if (result != ERROR_SUCCESS)
|
|
{
|
|
handle_read_error (result, path_name, value_name);
|
|
g_free (value_namew);
|
|
return FALSE;
|
|
}
|
|
|
|
if (p_value->type == REG_SZ && value_data_size == 0)
|
|
{
|
|
p_value->ptr = g_strdup ("");
|
|
g_free (value_namew);
|
|
return TRUE;
|
|
}
|
|
|
|
if (p_value->type == REG_DWORD)
|
|
/* REG_DWORD is inlined */
|
|
buffer = (void *)&p_value->dword;
|
|
else
|
|
buffer = p_value->ptr = g_malloc (value_data_size);
|
|
|
|
result = RegQueryValueExW (hpath, value_namew, 0, NULL, (LPBYTE)buffer, &value_data_size);
|
|
g_free (value_namew);
|
|
|
|
if (result != ERROR_SUCCESS)
|
|
{
|
|
handle_read_error (result, path_name, value_name);
|
|
|
|
if (p_value->type != REG_DWORD)
|
|
g_free (buffer);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (p_value->type == REG_SZ)
|
|
{
|
|
gchar *valueu8 = g_utf16_to_utf8 (p_value->ptr, -1, NULL, NULL, NULL);
|
|
g_free (p_value->ptr);
|
|
p_value->ptr = valueu8;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GVariant *
|
|
g_registry_settings_backend_read (GSettingsBackend *backend,
|
|
const gchar *key_name,
|
|
const GVariantType *expected_type,
|
|
gboolean default_value)
|
|
{
|
|
GRegistrySettingsBackend *self = G_REGISTRY_SETTINGS_BACKEND (backend);
|
|
GNode *cache_node;
|
|
RegistryValue registry_value;
|
|
GVariant *gsettings_value = NULL;
|
|
gchar *gsettings_type;
|
|
|
|
g_return_val_if_fail (expected_type != NULL, NULL);
|
|
|
|
if (default_value)
|
|
return NULL;
|
|
|
|
/* Simply read from the cache, which is updated from the registry by the
|
|
* watch thread as soon as changes can propagate. Any changes not yet in the
|
|
* cache will have the 'changed' signal emitted after this function returns.
|
|
*/
|
|
EnterCriticalSection (self->cache_lock);
|
|
cache_node = registry_cache_get_node_for_key (self->cache_root, key_name, FALSE);
|
|
LeaveCriticalSection (self->cache_lock);
|
|
|
|
trace ("Reading key %s, cache node %x\n", key_name, cache_node);
|
|
|
|
/* Maybe it's not set, we can return to default */
|
|
if (cache_node == NULL)
|
|
return NULL;
|
|
|
|
trace ("\t- cached value %s\n", registry_value_dump (((RegistryCacheItem *)cache_node->data)->value));
|
|
|
|
registry_value = ((RegistryCacheItem *)cache_node->data)->value;
|
|
|
|
gsettings_type = g_variant_type_dup_string (expected_type);
|
|
|
|
/* The registry is user-editable, so we need to be fault-tolerant here. */
|
|
switch (gsettings_type[0])
|
|
{
|
|
case 'b':
|
|
case 'y':
|
|
case 'n':
|
|
case 'q':
|
|
case 'i':
|
|
case 'u':
|
|
if (registry_value.type == REG_DWORD)
|
|
gsettings_value = g_variant_new (gsettings_type, registry_value.dword);
|
|
break;
|
|
|
|
case 't':
|
|
case 'x':
|
|
if (registry_value.type == REG_QWORD)
|
|
{
|
|
DWORDLONG qword_value = *(DWORDLONG *)registry_value.ptr;
|
|
gsettings_value = g_variant_new (gsettings_type, qword_value);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if (registry_value.type == REG_SZ)
|
|
{
|
|
if (gsettings_type[0] == 's')
|
|
gsettings_value = g_variant_new_string ((char *)registry_value.ptr);
|
|
else
|
|
{
|
|
GError *error = NULL;
|
|
|
|
gsettings_value = g_variant_parse (expected_type, registry_value.ptr,
|
|
NULL, NULL, &error);
|
|
|
|
if (error != NULL)
|
|
g_message ("gregistrysettingsbackend: error parsing key %s: %s",
|
|
key_name, error->message);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
g_free (gsettings_type);
|
|
|
|
return gsettings_value;
|
|
}
|
|
|
|
|
|
typedef struct
|
|
{
|
|
GRegistrySettingsBackend *self;
|
|
HKEY hroot;
|
|
} RegistryWrite;
|
|
|
|
static gboolean
|
|
g_registry_settings_backend_write_one (const char *key_name,
|
|
GVariant *variant,
|
|
gpointer user_data)
|
|
{
|
|
GRegistrySettingsBackend *self;
|
|
RegistryWrite *action;
|
|
RegistryValue value;
|
|
HKEY hroot;
|
|
HKEY hpath;
|
|
gchar *path_name;
|
|
gunichar2 *path_namew;
|
|
gchar *value_name = NULL;
|
|
gunichar2 *value_namew;
|
|
DWORD value_data_size;
|
|
LPVOID value_data;
|
|
gunichar2 *value_dataw;
|
|
LONG result;
|
|
GNode *node;
|
|
gboolean changed;
|
|
const gchar *type_string;
|
|
|
|
type_string = g_variant_get_type_string (variant);
|
|
action = user_data;
|
|
self = G_REGISTRY_SETTINGS_BACKEND (action->self);
|
|
hroot = action->hroot;
|
|
|
|
value.type = REG_NONE;
|
|
value.ptr = NULL;
|
|
|
|
switch (type_string[0])
|
|
{
|
|
case 'b':
|
|
case 'y':
|
|
case 'n':
|
|
case 'q':
|
|
case 'i':
|
|
case 'u':
|
|
value.type = REG_DWORD;
|
|
value.dword = g_variant_get_as_dword (variant);
|
|
value_data_size = 4;
|
|
value_data = &value.dword;
|
|
break;
|
|
|
|
case 'x':
|
|
case 't':
|
|
value.type = REG_QWORD;
|
|
value.ptr = g_malloc (8);
|
|
*(DWORDLONG *)value.ptr = g_variant_get_as_qword (variant);
|
|
value_data_size = 8;
|
|
value_data = value.ptr;
|
|
break;
|
|
|
|
default:
|
|
value.type = REG_SZ;
|
|
if (type_string[0] == 's')
|
|
{
|
|
gsize length;
|
|
value.ptr = g_strdup (g_variant_get_string (variant, &length));
|
|
value_data_size = length + 1;
|
|
value_data = value.ptr;
|
|
}
|
|
else
|
|
{
|
|
GString *value_string;
|
|
value_string = g_variant_print_string (variant, NULL, FALSE);
|
|
value_data_size = value_string->len + 1;
|
|
value.ptr = value_data = g_string_free (value_string, FALSE);
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* First update the cache, because the value may not have changed and we can
|
|
* save a write.
|
|
*
|
|
* If 'value' has changed then its memory will not be freed by update_node(),
|
|
* because it will be stored in the node.
|
|
*/
|
|
EnterCriticalSection (self->cache_lock);
|
|
node = registry_cache_get_node_for_key (self->cache_root, key_name, TRUE);
|
|
changed = registry_cache_update_node (node, value);
|
|
LeaveCriticalSection (self->cache_lock);
|
|
|
|
if (!changed)
|
|
return FALSE;
|
|
|
|
/* Block the next notification to any watch points above this location,
|
|
* because they will each get triggered on a change that is already updated
|
|
* in the cache.
|
|
*/
|
|
registry_cache_block_notification (node);
|
|
|
|
path_name = parse_key (key_name, NULL, &value_name);
|
|
|
|
trace ("Set key: %s / %s\n", path_name, value_name);
|
|
|
|
path_namew = g_utf8_to_utf16 (path_name, -1, NULL, NULL, NULL);
|
|
|
|
/* Store the value in the registry */
|
|
result = RegCreateKeyExW (hroot, path_namew, 0, NULL, 0, KEY_WRITE, NULL, &hpath, NULL);
|
|
if (result != ERROR_SUCCESS)
|
|
{
|
|
g_message_win32_error (result, "gregistrysettingsbackend: opening key %s failed",
|
|
path_name + 1);
|
|
registry_value_free (value);
|
|
g_free (path_namew);
|
|
g_free (path_name);
|
|
return FALSE;
|
|
}
|
|
|
|
g_free (path_namew);
|
|
|
|
value_namew = g_utf8_to_utf16 (value_name, -1, NULL, NULL, NULL);
|
|
|
|
value_dataw = NULL;
|
|
|
|
switch (type_string[0])
|
|
{
|
|
case 'b':
|
|
case 'y':
|
|
case 'n':
|
|
case 'q':
|
|
case 'i':
|
|
case 'u':
|
|
case 'x':
|
|
case 't':
|
|
break;
|
|
default:
|
|
value_dataw = g_utf8_to_utf16 (value_data, -1, NULL, NULL, NULL);
|
|
value_data = value_dataw;
|
|
value_data_size = (DWORD)((wcslen (value_data) + 1) * sizeof (gunichar2));
|
|
break;
|
|
}
|
|
|
|
result = RegSetValueExW (hpath, value_namew, 0, value.type, value_data, value_data_size);
|
|
|
|
if (result != ERROR_SUCCESS)
|
|
g_message_win32_error (result, "gregistrysettingsbackend: setting value %s\\%s\\%s\\%s failed.\n",
|
|
predefined_key_to_string (self->base_key),
|
|
self->base_path, path_name, value_name);
|
|
|
|
/* If the write fails then it will seem like the value has changed until the
|
|
* next execution (because we wrote to the cache first). There's no reason
|
|
* for it to fail unless something is weirdly broken, however.
|
|
*/
|
|
|
|
RegCloseKey (hpath);
|
|
g_free (path_name);
|
|
g_free (value_namew);
|
|
g_free (value_dataw);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* The dconf write policy is to do the write while making out it succeeded,
|
|
* and then backtrack if it didn't. The registry functions are synchronous so
|
|
* we can't do that. */
|
|
|
|
static gboolean
|
|
g_registry_settings_backend_write (GSettingsBackend *backend,
|
|
const gchar *key_name,
|
|
GVariant *value,
|
|
gpointer origin_tag)
|
|
{
|
|
GRegistrySettingsBackend *self = G_REGISTRY_SETTINGS_BACKEND (backend);
|
|
LONG result;
|
|
HKEY hroot;
|
|
RegistryWrite action;
|
|
|
|
result = RegCreateKeyExW (self->base_key, self->base_pathw, 0, NULL, 0,
|
|
KEY_WRITE, NULL, &hroot, NULL);
|
|
if (result != ERROR_SUCCESS)
|
|
{
|
|
trace ("Error opening/creating key %s\\%s.\n",
|
|
predefined_key_to_string (self->base_key),
|
|
self->base_path);
|
|
return FALSE;
|
|
}
|
|
|
|
action.self = self;
|
|
action.hroot = hroot;
|
|
g_registry_settings_backend_write_one (key_name, value, &action);
|
|
g_settings_backend_changed (backend, key_name, origin_tag);
|
|
|
|
RegCloseKey (hroot);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
g_registry_settings_backend_write_tree (GSettingsBackend *backend,
|
|
GTree *values,
|
|
gpointer origin_tag)
|
|
{
|
|
GRegistrySettingsBackend *self = G_REGISTRY_SETTINGS_BACKEND (backend);
|
|
LONG result;
|
|
HKEY hroot;
|
|
RegistryWrite action;
|
|
|
|
result = RegCreateKeyExW (self->base_key, self->base_pathw, 0, NULL, 0,
|
|
KEY_WRITE, NULL, &hroot, NULL);
|
|
if (result != ERROR_SUCCESS)
|
|
{
|
|
trace ("Error opening/creating key %s\\%s.\n",
|
|
predefined_key_to_string (self->base_key),
|
|
self->base_path);
|
|
return FALSE;
|
|
}
|
|
|
|
action.self = self;
|
|
action.hroot = hroot;
|
|
g_tree_foreach (values, (GTraverseFunc)g_registry_settings_backend_write_one,
|
|
&action);
|
|
|
|
g_settings_backend_changed_tree (backend, values, origin_tag);
|
|
RegCloseKey (hroot);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
g_registry_settings_backend_reset (GSettingsBackend *backend,
|
|
const gchar *key_name,
|
|
gpointer origin_tag)
|
|
{
|
|
GRegistrySettingsBackend *self = G_REGISTRY_SETTINGS_BACKEND (backend);
|
|
gchar *path_name;
|
|
gunichar2 *path_namew;
|
|
gchar *value_name = NULL;
|
|
gunichar2 *value_namew;
|
|
GNode *cache_node;
|
|
LONG result;
|
|
HKEY hpath;
|
|
|
|
/* Remove from cache */
|
|
EnterCriticalSection (self->cache_lock);
|
|
cache_node = registry_cache_get_node_for_key (self->cache_root, key_name, FALSE);
|
|
if (cache_node)
|
|
registry_cache_destroy_tree (cache_node, self->watch);
|
|
LeaveCriticalSection (self->cache_lock);
|
|
|
|
/* Remove from the registry */
|
|
path_name = parse_key (key_name, self->base_path, &value_name);
|
|
path_namew = g_utf8_to_utf16 (path_name, -1, NULL, NULL, NULL);
|
|
|
|
result = RegOpenKeyExW (self->base_key, path_namew, 0, KEY_SET_VALUE, &hpath);
|
|
g_free (path_namew);
|
|
|
|
if (result != ERROR_SUCCESS)
|
|
{
|
|
g_message_win32_error (result, "Registry: resetting key '%s\\%s'",
|
|
predefined_key_to_string (self->base_key),
|
|
path_name);
|
|
g_free (path_name);
|
|
return;
|
|
}
|
|
|
|
value_namew = g_utf8_to_utf16 (value_name, -1, NULL, NULL, NULL);
|
|
|
|
result = RegDeleteValueW (hpath, value_namew);
|
|
g_free (value_namew);
|
|
RegCloseKey (hpath);
|
|
|
|
if (result != ERROR_SUCCESS)
|
|
{
|
|
g_message_win32_error (result, "Registry: resetting key '%s\\%s'",
|
|
predefined_key_to_string (self->base_key),
|
|
path_name);
|
|
g_free (path_name);
|
|
return;
|
|
}
|
|
|
|
g_free (path_name);
|
|
|
|
g_settings_backend_changed (backend, key_name, origin_tag);
|
|
}
|
|
|
|
static gboolean
|
|
g_registry_settings_backend_get_writable (GSettingsBackend *backend,
|
|
const gchar *key_name)
|
|
{
|
|
GRegistrySettingsBackend *self = G_REGISTRY_SETTINGS_BACKEND (backend);
|
|
gchar *path_name;
|
|
gunichar2 *path_namew;
|
|
gchar *value_name = NULL;
|
|
HKEY hpath;
|
|
LONG result;
|
|
|
|
path_name = parse_key (key_name, self->base_path, &value_name);
|
|
path_namew = g_utf8_to_utf16 (path_name, -1, NULL, NULL, NULL);
|
|
|
|
/* Note: we create the key if it wasn't created yet, but it is not much
|
|
* of a problem since at the end of the day we have to create it anyway
|
|
* to read or to write from it
|
|
*/
|
|
result = RegCreateKeyExW (self->base_key, path_namew, 0, NULL, 0,
|
|
KEY_WRITE, NULL, &hpath, NULL);
|
|
g_free (path_namew);
|
|
|
|
if (result != ERROR_SUCCESS)
|
|
{
|
|
trace ("Error opening/creating key to check writability: %s\\%s.\n",
|
|
predefined_key_to_string (self->base_key),
|
|
path_name);
|
|
g_free (path_name);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
g_free (path_name);
|
|
RegCloseKey (hpath);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Spot-the-difference engine
|
|
********************************************************************************/
|
|
|
|
static void
|
|
_free_watch (WatchThreadState *self,
|
|
guint index,
|
|
GNode *cache_node);
|
|
|
|
static void
|
|
registry_cache_item_reset_readable (GNode *node,
|
|
gpointer data)
|
|
{
|
|
RegistryCacheItem *item = node->data;
|
|
item->readable = FALSE;
|
|
}
|
|
|
|
/* Delete a node and any children, for when it has been deleted from the registry */
|
|
static void
|
|
registry_cache_destroy_tree (GNode *node,
|
|
WatchThreadState *self)
|
|
{
|
|
RegistryCacheItem *item = node->data;
|
|
|
|
g_node_children_foreach (node, G_TRAVERSE_ALL,
|
|
(GNodeForeachFunc)registry_cache_destroy_tree, self);
|
|
|
|
if (item->subscription_count > 0)
|
|
{
|
|
guint i;
|
|
|
|
/* There must be some watches active if this node is a watch point */
|
|
g_warn_if_fail (self->cache_nodes->len > 1);
|
|
|
|
/* This is a watch point that has been deleted. Let's free the watch! */
|
|
for (i = 1; i < self->cache_nodes->len; i++)
|
|
{
|
|
if (g_ptr_array_index (self->cache_nodes, i) == node)
|
|
break;
|
|
}
|
|
|
|
if (i >= self->cache_nodes->len)
|
|
g_warning ("watch thread: a watch point was deleted, but unable to "
|
|
"find '%s' in the list of %i watch nodes\n", item->name,
|
|
self->cache_nodes->len - 1);
|
|
else
|
|
{
|
|
_free_watch (self, i, node);
|
|
g_atomic_int_inc (&self->watches_remaining);
|
|
}
|
|
}
|
|
registry_cache_item_free (node->data);
|
|
g_node_destroy (node);
|
|
}
|
|
|
|
/* One of these is sent down the pipe when something happens in the registry. */
|
|
typedef struct
|
|
{
|
|
GRegistrySettingsBackend *self;
|
|
gchar *prefix; /* prefix is a gsettings path, all items are subkeys of this. */
|
|
GPtrArray *items; /* each item is a subkey below prefix that has changed. */
|
|
} RegistryEvent;
|
|
|
|
typedef struct
|
|
{
|
|
RegistryEvent *event;
|
|
gchar *current_key_name;
|
|
} DeletedItemData;
|
|
|
|
static void
|
|
mark_all_subkeys_as_changed (GNode *node,
|
|
gpointer data)
|
|
{
|
|
RegistryCacheItem *item = node->data;
|
|
DeletedItemData *item_data = data;
|
|
|
|
if (item_data->current_key_name == NULL)
|
|
item_data->current_key_name = g_strdup (item->name);
|
|
else
|
|
{
|
|
gchar *name;
|
|
|
|
name = g_build_path ("/", item_data->current_key_name, item->name, NULL);
|
|
g_free (item_data->current_key_name);
|
|
item_data->current_key_name = name;
|
|
}
|
|
|
|
/* Iterate until we find an item that is a value */
|
|
if (item->value.type == REG_NONE)
|
|
g_node_children_foreach (node, G_TRAVERSE_ALL,
|
|
mark_all_subkeys_as_changed, data);
|
|
else
|
|
g_ptr_array_add (item_data->event->items, item_data->current_key_name);
|
|
}
|
|
|
|
static void
|
|
registry_cache_remove_deleted (GNode *node,
|
|
gpointer data)
|
|
{
|
|
RegistryCacheItem *item = node->data;
|
|
RegistryEvent *event = data;
|
|
|
|
if (!item->readable)
|
|
{
|
|
DeletedItemData item_data;
|
|
|
|
item_data.event = event;
|
|
item_data.current_key_name = NULL;
|
|
|
|
mark_all_subkeys_as_changed (node, &item_data);
|
|
registry_cache_destroy_tree (node, event->self->watch);
|
|
}
|
|
}
|
|
|
|
/* Update cache from registry, and optionally report on the changes.
|
|
*
|
|
* This function is sometimes called from the watch thread, with no locking. It
|
|
* does call g_registry_settings_backend functions, but this is okay because they only
|
|
* access self->base which is constant.
|
|
*
|
|
* When looking at this code bear in mind the terminology: in the registry, keys
|
|
* are containers that contain values, and other keys. Keys have a 'default'
|
|
* value which we always ignore.
|
|
*
|
|
* n_parent_watches: a counter used to set the reference count of any new nodes
|
|
* that are created - they should have as many references as
|
|
* there are notifications that are watching them.
|
|
*/
|
|
static void
|
|
registry_cache_update (GRegistrySettingsBackend *self,
|
|
HKEY hpath,
|
|
const gchar *prefix,
|
|
const gchar *partial_key_name,
|
|
GNode *cache_node,
|
|
int n_watches,
|
|
RegistryEvent *event)
|
|
{
|
|
gunichar2 bufferw[MAX_KEY_NAME_LENGTH + 1];
|
|
gchar *buffer;
|
|
gchar *key_name;
|
|
gint i;
|
|
LONG result;
|
|
RegistryCacheItem *item;
|
|
|
|
item = cache_node->data;
|
|
|
|
if (item->subscription_count > 0)
|
|
n_watches++;
|
|
|
|
/* prefix is the level that all changes occur below; partial_key_name should
|
|
* be NULL on the first call to this function */
|
|
key_name = g_build_path ("/", prefix, partial_key_name, NULL);
|
|
|
|
trace ("registry cache update: %s. Node %x has %i children\n", key_name,
|
|
cache_node, g_node_n_children (cache_node));
|
|
|
|
/* Start by zeroing 'readable' flag. When the registry traversal is done, any unreadable nodes
|
|
* must have been deleted from the registry.
|
|
*/
|
|
g_node_children_foreach (cache_node, G_TRAVERSE_ALL,
|
|
registry_cache_item_reset_readable, NULL);
|
|
|
|
/* Recurse into each subpath at the current level, if any */
|
|
i = 0;
|
|
while (1)
|
|
{
|
|
DWORD bufferw_size = MAX_KEY_NAME_LENGTH + 1;
|
|
HKEY hsubpath;
|
|
|
|
result = RegEnumKeyExW (hpath, i++, bufferw, &bufferw_size, NULL, NULL, NULL, NULL);
|
|
if (result != ERROR_SUCCESS)
|
|
break;
|
|
|
|
result = RegOpenKeyExW (hpath, bufferw, 0, KEY_READ, &hsubpath);
|
|
if (result == ERROR_SUCCESS)
|
|
{
|
|
GNode *subkey_node;
|
|
RegistryCacheItem *child_item;
|
|
gchar *new_partial_key_name;
|
|
|
|
buffer = g_utf16_to_utf8 (bufferw, -1, NULL, NULL, NULL);
|
|
if (buffer == NULL)
|
|
continue;
|
|
|
|
subkey_node = registry_cache_find_immediate_child (cache_node, buffer);
|
|
if (subkey_node == NULL)
|
|
{
|
|
RegistryValue null_value = {REG_NONE, {0}};
|
|
subkey_node = registry_cache_add_item (cache_node, buffer,
|
|
null_value, n_watches);
|
|
}
|
|
|
|
new_partial_key_name = g_build_path ("/", partial_key_name, buffer, NULL);
|
|
registry_cache_update (self, hsubpath, prefix, new_partial_key_name,
|
|
subkey_node, n_watches, event);
|
|
g_free (new_partial_key_name);
|
|
|
|
child_item = subkey_node->data;
|
|
child_item->readable = TRUE;
|
|
|
|
g_free (buffer);
|
|
RegCloseKey (hsubpath);
|
|
}
|
|
}
|
|
|
|
if (result != ERROR_NO_MORE_ITEMS)
|
|
g_message_win32_error (result, "gregistrysettingsbackend: error enumerating subkeys for cache.");
|
|
|
|
/* Enumerate each value at 'path' and check if it has changed */
|
|
i = 0;
|
|
while (1)
|
|
{
|
|
DWORD bufferw_size = MAX_KEY_NAME_LENGTH + 1;
|
|
GNode *cache_child_node;
|
|
RegistryCacheItem *child_item;
|
|
RegistryValue value;
|
|
gboolean changed = FALSE;
|
|
|
|
result = RegEnumValueW (hpath, i++, bufferw, &bufferw_size, NULL, NULL, NULL, NULL);
|
|
if (result != ERROR_SUCCESS)
|
|
break;
|
|
|
|
buffer = g_utf16_to_utf8 (bufferw, -1, NULL, NULL, NULL);
|
|
|
|
if (buffer == NULL || buffer[0] == 0)
|
|
{
|
|
/* This is the key's 'default' value, for which we have no use. */
|
|
g_free (buffer);
|
|
continue;
|
|
}
|
|
|
|
cache_child_node = registry_cache_find_immediate_child (cache_node, buffer);
|
|
|
|
if (!registry_read (hpath, key_name, buffer, &value))
|
|
{
|
|
g_free (buffer);
|
|
continue;
|
|
}
|
|
|
|
trace ("\tgot value %s for %s, node %x\n",
|
|
registry_value_dump (value), buffer, cache_child_node);
|
|
|
|
if (cache_child_node == NULL)
|
|
{
|
|
/* This is a new value */
|
|
cache_child_node = registry_cache_add_item (cache_node, buffer, value,
|
|
n_watches);
|
|
changed = TRUE;
|
|
}
|
|
else
|
|
{
|
|
/* For efficiency, instead of converting every value back to a GVariant to
|
|
* compare it, we compare them as registry values (integers, or string
|
|
* representations of the variant). The spurious change notifications that may
|
|
* result should not be a big issue.
|
|
*
|
|
* Note that 'value' is swallowed or freed.
|
|
*/
|
|
changed = registry_cache_update_node (cache_child_node, value);
|
|
}
|
|
|
|
child_item = cache_child_node->data;
|
|
child_item->readable = TRUE;
|
|
if (changed && event != NULL)
|
|
{
|
|
gchar *item_path;
|
|
|
|
if (partial_key_name == NULL)
|
|
item_path = g_strdup (buffer);
|
|
else
|
|
item_path = g_build_path ("/", partial_key_name, buffer, NULL);
|
|
|
|
g_ptr_array_add (event->items, item_path);
|
|
}
|
|
|
|
g_free (buffer);
|
|
}
|
|
|
|
if (result != ERROR_NO_MORE_ITEMS)
|
|
g_message_win32_error (result, "gregistrysettingsbackend: error enumerating values for cache");
|
|
|
|
/* Any nodes now left unreadable must have been deleted, remove them from cache */
|
|
g_node_children_foreach (cache_node, G_TRAVERSE_ALL,
|
|
registry_cache_remove_deleted, event);
|
|
|
|
trace ("registry cache update complete.\n");
|
|
|
|
g_free (key_name);
|
|
}
|
|
|
|
/***********************************************************************************
|
|
* Thread to watch for registry change events
|
|
***********************************************************************************/
|
|
|
|
/* Called by watch thread. Apply for notifications on a registry key and its subkeys. */
|
|
static DWORD
|
|
registry_watch_key (HKEY hpath,
|
|
HANDLE event)
|
|
{
|
|
return RegNotifyChangeKeyValue (hpath, TRUE,
|
|
REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_LAST_SET,
|
|
event, TRUE);
|
|
}
|
|
|
|
/* This handler runs in the main thread to emit the changed signals */
|
|
static gboolean
|
|
watch_handler (RegistryEvent *event)
|
|
{
|
|
trace ("Watch handler: got event in %s, items %i.\n", event->prefix, event->items->len);
|
|
|
|
/* GSettings requires us to NULL-terminate the array. */
|
|
g_ptr_array_add (event->items, NULL);
|
|
g_settings_backend_keys_changed (G_SETTINGS_BACKEND (event->self), event->prefix,
|
|
(gchar const **)event->items->pdata, NULL);
|
|
|
|
g_ptr_array_free (event->items, TRUE);
|
|
g_free (event->prefix);
|
|
g_object_unref (event->self);
|
|
g_slice_free (RegistryEvent, event);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
_free_watch (WatchThreadState *self,
|
|
guint index,
|
|
GNode *cache_node)
|
|
{
|
|
HKEY hpath;
|
|
HANDLE cond;
|
|
gchar *prefix;
|
|
|
|
g_return_if_fail (index > 0 && index < self->events->len);
|
|
|
|
cond = g_ptr_array_index (self->events, index);
|
|
hpath = g_ptr_array_index (self->handles, index);
|
|
prefix = g_ptr_array_index (self->prefixes, index);
|
|
|
|
trace ("Freeing watch %i [%s]\n", index, prefix);
|
|
|
|
/* These can be NULL if the watch was already dead, this can happen when eg.
|
|
* a key is deleted but GSettings is still subscribed to it - the watch is
|
|
* kept alive so that the unsubscribe function works properly, but does not
|
|
* do anything.
|
|
*/
|
|
if (hpath != NULL)
|
|
RegCloseKey (hpath);
|
|
|
|
if (cache_node != NULL)
|
|
{
|
|
//registry_cache_dump (G_REGISTRY_SETTINGS_BACKEND (self->owner)->cache_root, NULL);
|
|
registry_cache_unref_tree (cache_node);
|
|
}
|
|
|
|
CloseHandle (cond);
|
|
g_free (prefix);
|
|
|
|
/* As long as we remove from each array at the same time, it doesn't matter that
|
|
* their orders get messed up - they all get messed up the same.
|
|
*/
|
|
g_ptr_array_remove_index_fast (self->handles, index);
|
|
g_ptr_array_remove_index_fast (self->events, index);
|
|
g_ptr_array_remove_index_fast (self->prefixes, index);
|
|
g_ptr_array_remove_index_fast (self->cache_nodes, index);
|
|
}
|
|
|
|
static void
|
|
watch_thread_handle_message (WatchThreadState *self)
|
|
{
|
|
switch (self->message.type)
|
|
{
|
|
case WATCH_THREAD_NONE:
|
|
trace ("watch thread: you woke me up for nothin', man!");
|
|
break;
|
|
|
|
case WATCH_THREAD_ADD_WATCH:
|
|
{
|
|
RegistryWatch *watch = &self->message.watch;
|
|
LONG result;
|
|
|
|
result = registry_watch_key (watch->hpath, watch->event);
|
|
|
|
if (result == ERROR_SUCCESS)
|
|
{
|
|
g_ptr_array_add (self->events, watch->event);
|
|
g_ptr_array_add (self->handles, watch->hpath);
|
|
g_ptr_array_add (self->prefixes, watch->prefix);
|
|
g_ptr_array_add (self->cache_nodes, watch->cache_node);
|
|
|
|
trace ("watch thread: new watch on %s, %i total\n", watch->prefix,
|
|
self->events->len);
|
|
}
|
|
else
|
|
{
|
|
g_message_win32_error (result, "watch thread: could not watch %s", watch->prefix);
|
|
|
|
CloseHandle (watch->event);
|
|
RegCloseKey (watch->hpath);
|
|
g_free (watch->prefix);
|
|
registry_cache_unref_tree (watch->cache_node);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case WATCH_THREAD_REMOVE_WATCH:
|
|
{
|
|
GNode *cache_node;
|
|
RegistryCacheItem *cache_item;
|
|
guint i;
|
|
|
|
for (i = 1; i < self->prefixes->len; i++)
|
|
{
|
|
if (strcmp (g_ptr_array_index (self->prefixes, i),
|
|
self->message.watch.prefix) == 0)
|
|
break;
|
|
}
|
|
|
|
if (i >= self->prefixes->len)
|
|
{
|
|
/* Don't make a fuss if the prefix is not being watched because
|
|
* maybe the path was deleted so we removed the watch.
|
|
*/
|
|
trace ("unsubscribe: prefix %s is not being watched [%i things are]!\n",
|
|
self->message.watch.prefix, self->prefixes->len);
|
|
g_free (self->message.watch.prefix);
|
|
break;
|
|
}
|
|
|
|
cache_node = g_ptr_array_index (self->cache_nodes, i);
|
|
|
|
trace ("watch thread: unsubscribe: freeing node %p, prefix %s, index %i\n",
|
|
cache_node, self->message.watch.prefix, i);
|
|
|
|
if (cache_node != NULL)
|
|
{
|
|
cache_item = cache_node->data;
|
|
|
|
/* There may be more than one GSettings object subscribed to this
|
|
* path, only free the watch when the last one unsubscribes.
|
|
*/
|
|
cache_item->subscription_count--;
|
|
if (cache_item->subscription_count > 0)
|
|
break;
|
|
}
|
|
|
|
_free_watch (self, i, cache_node);
|
|
g_free (self->message.watch.prefix);
|
|
|
|
g_atomic_int_inc (&self->watches_remaining);
|
|
break;
|
|
}
|
|
|
|
case WATCH_THREAD_STOP:
|
|
{
|
|
guint i;
|
|
|
|
/* Free any remaining cache and watch handles */
|
|
for (i = 1; i < self->events->len; i++)
|
|
_free_watch (self, i, g_ptr_array_index (self->cache_nodes, i));
|
|
|
|
SetEvent (self->message_received_event);
|
|
ExitThread (0);
|
|
}
|
|
}
|
|
|
|
self->message.type = WATCH_THREAD_NONE;
|
|
SetEvent (self->message_received_event);
|
|
}
|
|
|
|
/* Thread which watches for win32 registry events */
|
|
static DWORD WINAPI
|
|
watch_thread_function (LPVOID parameter)
|
|
{
|
|
WatchThreadState *self = (WatchThreadState *)parameter;
|
|
DWORD result;
|
|
|
|
self->events = g_ptr_array_new ();
|
|
self->handles = g_ptr_array_new ();
|
|
self->prefixes = g_ptr_array_new ();
|
|
self->cache_nodes = g_ptr_array_new ();
|
|
g_ptr_array_add (self->events, self->message_sent_event);
|
|
g_ptr_array_add (self->handles, NULL);
|
|
g_ptr_array_add (self->prefixes, NULL);
|
|
g_ptr_array_add (self->cache_nodes, NULL);
|
|
|
|
while (1)
|
|
{
|
|
trace ("watch thread: going to sleep; %i events watched.\n", self->events->len);
|
|
result = WaitForMultipleObjects (self->events->len, self->events->pdata, FALSE, INFINITE);
|
|
|
|
if (result == WAIT_OBJECT_0)
|
|
{
|
|
/* A message to you. The sender (main thread) will block until we signal the received
|
|
* event, so there should be no danger of it sending another before we receive the
|
|
* first.
|
|
*/
|
|
watch_thread_handle_message (self);
|
|
}
|
|
else if (result > WAIT_OBJECT_0 && result <= WAIT_OBJECT_0 + self->events->len)
|
|
{
|
|
HKEY hpath;
|
|
HANDLE cond;
|
|
gchar *prefix;
|
|
GNode *cache_node;
|
|
RegistryCacheItem *cache_item;
|
|
RegistryEvent *event;
|
|
gint notify_index;
|
|
|
|
/* One of our notifications has triggered. All we know is which one, and which key
|
|
* this is for. We do most of the processing here, because we may as well. If the
|
|
* registry changes further while we are processing it doesn't matter - we will then
|
|
* receive another change notification from the OS anyway.
|
|
*/
|
|
notify_index = result - WAIT_OBJECT_0;
|
|
hpath = g_ptr_array_index (self->handles, notify_index);
|
|
cond = g_ptr_array_index (self->events, notify_index);
|
|
prefix = g_ptr_array_index (self->prefixes, notify_index);
|
|
cache_node = g_ptr_array_index (self->cache_nodes, notify_index);
|
|
|
|
trace ("Watch thread: notify received on prefix %i: %s.\n", notify_index, prefix);
|
|
|
|
if (cache_node == NULL)
|
|
{
|
|
/* This path has been deleted */
|
|
trace ("Notify received on a path that was deleted\n");
|
|
continue;
|
|
}
|
|
|
|
/* Firstly we need to reapply for the notification, because (what a
|
|
* sensible API) we won't receive any more. MSDN is pretty
|
|
* inconsistent on this matter:
|
|
* http://msdn.microsoft.com/en-us/library/ms724892%28VS.85%29.aspx
|
|
* http://support.microsoft.com/kb/236570
|
|
* But my tests (on Windows XP SP3) show that we need to reapply
|
|
* each time.
|
|
*/
|
|
result = registry_watch_key (hpath, cond);
|
|
|
|
if (result != ERROR_SUCCESS)
|
|
{
|
|
/* Watch failed, most likely because the key has just been
|
|
* deleted. Free the watch and unref the cache nodes.
|
|
*/
|
|
if (result != ERROR_KEY_DELETED)
|
|
g_message_win32_error (result, "watch thread: failed to watch %s", prefix);
|
|
|
|
_free_watch (self, notify_index, cache_node);
|
|
g_atomic_int_inc (&self->watches_remaining);
|
|
continue;
|
|
}
|
|
|
|
/* The notification may have been blocked because we just changed
|
|
* some data ourselves.
|
|
*/
|
|
cache_item = cache_node->data;
|
|
if (cache_item->block_count)
|
|
{
|
|
cache_item->block_count--;
|
|
trace ("Watch thread: notify blocked at %s\n", prefix);
|
|
continue;
|
|
}
|
|
|
|
/* Now we update our stored cache from registry data, and find which keys have
|
|
* actually changed. If more changes happen while we are processing, we will get
|
|
* another event because we have reapplied for change notifications already.
|
|
*
|
|
* Working here rather than in the main thread is preferable because the UI is less
|
|
* likely to block (only when changing notification subscriptions).
|
|
*/
|
|
event = g_slice_new (RegistryEvent);
|
|
event->self = G_REGISTRY_SETTINGS_BACKEND (g_object_ref (self->owner));
|
|
event->prefix = g_strdup (prefix);
|
|
event->items = g_ptr_array_new_with_free_func (g_free);
|
|
|
|
EnterCriticalSection (G_REGISTRY_SETTINGS_BACKEND (self->owner)->cache_lock);
|
|
registry_cache_update (G_REGISTRY_SETTINGS_BACKEND (self->owner), hpath,
|
|
prefix, NULL, cache_node, 0, event);
|
|
LeaveCriticalSection (G_REGISTRY_SETTINGS_BACKEND (self->owner)->cache_lock);
|
|
|
|
if (event->items->len > 0)
|
|
g_idle_add ((GSourceFunc) watch_handler, event);
|
|
else
|
|
{
|
|
g_object_unref (event->self);
|
|
g_free (event->prefix);
|
|
g_ptr_array_free (event->items, TRUE);
|
|
g_slice_free (RegistryEvent, event);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* God knows what has happened */
|
|
g_message_win32_error (GetLastError(), "watch thread: WaitForMultipleObjects error");
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static gboolean
|
|
watch_start (GRegistrySettingsBackend *self)
|
|
{
|
|
WatchThreadState *watch;
|
|
|
|
g_return_val_if_fail (self->watch == NULL, FALSE);
|
|
|
|
watch = g_slice_new (WatchThreadState);
|
|
watch->owner = G_SETTINGS_BACKEND (self);
|
|
|
|
watch->watches_remaining = MAX_WATCHES;
|
|
|
|
watch->message_lock = g_slice_new (CRITICAL_SECTION);
|
|
InitializeCriticalSection (watch->message_lock);
|
|
watch->message_sent_event = CreateEvent (NULL, FALSE, FALSE, NULL);
|
|
watch->message_received_event = CreateEvent (NULL, FALSE, FALSE, NULL);
|
|
if (watch->message_sent_event == NULL || watch->message_received_event == NULL)
|
|
{
|
|
g_message_win32_error (GetLastError (), "gregistrysettingsbackend: Failed to create sync objects.");
|
|
goto fail;
|
|
}
|
|
|
|
/* Use a small stack to make the thread more lightweight. */
|
|
watch->thread = CreateThread (NULL, 1024, watch_thread_function, watch, 0, NULL);
|
|
if (watch->thread == NULL)
|
|
{
|
|
g_message_win32_error (GetLastError (), "gregistrysettingsbackend: Failed to create notify watch thread.");
|
|
goto fail;
|
|
}
|
|
|
|
self->watch = watch;
|
|
|
|
return TRUE;
|
|
|
|
fail:
|
|
DeleteCriticalSection (watch->message_lock);
|
|
g_slice_free (CRITICAL_SECTION, watch->message_lock);
|
|
if (watch->message_sent_event != NULL)
|
|
CloseHandle (watch->message_sent_event);
|
|
if (watch->message_received_event != NULL)
|
|
CloseHandle (watch->message_received_event);
|
|
g_slice_free (WatchThreadState, watch);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* This function assumes you hold the message lock! */
|
|
static void
|
|
watch_stop_unlocked (GRegistrySettingsBackend *self)
|
|
{
|
|
WatchThreadState *watch = self->watch;
|
|
DWORD result;
|
|
|
|
g_return_if_fail (watch != NULL);
|
|
|
|
watch->message.type = WATCH_THREAD_STOP;
|
|
SetEvent (watch->message_sent_event);
|
|
|
|
/* This is signalled as soon as the message is received. We must not return
|
|
* while the watch thread is still firing off callbacks. Freeing all of the
|
|
* memory is done in the watch thread after this is signalled.
|
|
*/
|
|
result = WaitForSingleObject (watch->message_received_event, INFINITE);
|
|
if (result != WAIT_OBJECT_0)
|
|
{
|
|
g_warning ("gregistrysettingsbackend: unable to stop watch thread.");
|
|
return;
|
|
}
|
|
|
|
LeaveCriticalSection (watch->message_lock);
|
|
DeleteCriticalSection (watch->message_lock);
|
|
g_slice_free (CRITICAL_SECTION, watch->message_lock);
|
|
CloseHandle (watch->message_sent_event);
|
|
CloseHandle (watch->message_received_event);
|
|
CloseHandle (watch->thread);
|
|
g_slice_free (WatchThreadState, watch);
|
|
|
|
trace ("\nwatch thread: %x: all data freed.\n", self);
|
|
self->watch = NULL;
|
|
}
|
|
|
|
static gboolean
|
|
watch_add_notify (GRegistrySettingsBackend *self,
|
|
HANDLE event,
|
|
HKEY hpath,
|
|
gchar *gsettings_prefix)
|
|
{
|
|
WatchThreadState *watch = self->watch;
|
|
GNode *cache_node;
|
|
RegistryCacheItem *cache_item;
|
|
#ifdef TRACE
|
|
DWORD result;
|
|
#endif
|
|
|
|
g_return_val_if_fail (watch != NULL, FALSE);
|
|
|
|
trace ("watch_add_notify: prefix %s.\n", gsettings_prefix);
|
|
|
|
/* Duplicate tree into the cache in the main thread, before we add the notify: if we do it in the
|
|
* thread we can miss changes while we are caching.
|
|
*/
|
|
EnterCriticalSection (self->cache_lock);
|
|
cache_node = registry_cache_get_node_for_key (self->cache_root, gsettings_prefix, TRUE);
|
|
|
|
if (cache_node == NULL || cache_node->data == NULL)
|
|
{
|
|
LeaveCriticalSection (self->cache_lock);
|
|
g_warn_if_reached ();
|
|
return FALSE;
|
|
}
|
|
|
|
cache_item = cache_node->data;
|
|
|
|
cache_item->subscription_count++;
|
|
if (cache_item->subscription_count > 1)
|
|
{
|
|
trace ("watch_add_notify: prefix %s already watched, %i subscribers.\n",
|
|
gsettings_prefix, cache_item->subscription_count);
|
|
LeaveCriticalSection (self->cache_lock);
|
|
return FALSE;
|
|
}
|
|
|
|
registry_cache_ref_tree (cache_node);
|
|
registry_cache_update (self, hpath, gsettings_prefix, NULL, cache_node, 0, NULL);
|
|
//registry_cache_dump (self->cache_root, NULL);
|
|
LeaveCriticalSection (self->cache_lock);
|
|
|
|
EnterCriticalSection (watch->message_lock);
|
|
watch->message.type = WATCH_THREAD_ADD_WATCH;
|
|
watch->message.watch.event = event;
|
|
watch->message.watch.hpath = hpath;
|
|
watch->message.watch.prefix = gsettings_prefix;
|
|
watch->message.watch.cache_node = cache_node;
|
|
|
|
SetEvent (watch->message_sent_event);
|
|
|
|
/* Wait for the received event in return, to avoid sending another message before the first
|
|
* one was received. If it takes > 200ms there is a possible race but the worst outcome is
|
|
* a notification is ignored.
|
|
*/
|
|
#ifdef TRACE
|
|
result =
|
|
#endif
|
|
WaitForSingleObject (watch->message_received_event, 200);
|
|
#ifdef TRACE
|
|
if (result != WAIT_OBJECT_0)
|
|
trace ("watch thread is slow to respond - notification may not be added.");
|
|
#endif
|
|
|
|
LeaveCriticalSection (watch->message_lock);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
watch_remove_notify (GRegistrySettingsBackend *self,
|
|
const gchar *key_name)
|
|
{
|
|
WatchThreadState *watch = self->watch;
|
|
LONG result;
|
|
|
|
if (self->watch == NULL)
|
|
/* Here we assume that the unsubscribe message is for somewhere that was
|
|
* deleted, and so it has already been removed and the watch thread has
|
|
* stopped.
|
|
*/
|
|
return;
|
|
|
|
EnterCriticalSection (watch->message_lock);
|
|
watch->message.type = WATCH_THREAD_REMOVE_WATCH;
|
|
watch->message.watch.prefix = g_strdup (key_name);
|
|
|
|
SetEvent (watch->message_sent_event);
|
|
|
|
/* Wait for the received event in return, to avoid sending another message before the first
|
|
* one was received.
|
|
*/
|
|
result = WaitForSingleObject (watch->message_received_event, INFINITE);
|
|
|
|
if (result != ERROR_SUCCESS)
|
|
g_warning ("unsubscribe from %s: message not acknowledged", key_name);
|
|
|
|
if (g_atomic_int_get (&watch->watches_remaining) >= MAX_WATCHES)
|
|
/* Stop it before any new ones can get added and confuse things */
|
|
watch_stop_unlocked (self);
|
|
else
|
|
LeaveCriticalSection (watch->message_lock);
|
|
}
|
|
|
|
/* dconf semantics are: if the key ends in /, watch the keys underneath it - if not, watch that
|
|
* key. Our job is easier because keys and values are separate.
|
|
*/
|
|
static void
|
|
g_registry_settings_backend_subscribe (GSettingsBackend *backend,
|
|
const char *key_name)
|
|
{
|
|
GRegistrySettingsBackend *self = G_REGISTRY_SETTINGS_BACKEND (backend);
|
|
gchar *path_name;
|
|
gunichar2 *path_namew;
|
|
gchar *value_name = NULL;
|
|
HKEY hpath;
|
|
HANDLE event;
|
|
LONG result;
|
|
|
|
if (self->watch == NULL && !watch_start (self))
|
|
return;
|
|
|
|
if (g_atomic_int_dec_and_test (&self->watch->watches_remaining))
|
|
{
|
|
g_atomic_int_inc (&self->watch->watches_remaining);
|
|
g_warning ("subscribe() failed: only %i different paths may be watched.", MAX_WATCHES);
|
|
return;
|
|
}
|
|
|
|
path_name = parse_key (key_name, self->base_path, &value_name);
|
|
|
|
/* Must check for this, otherwise strange crashes occur because the cache
|
|
* node that is being watched gets freed. All path names to subscribe must
|
|
* end in a slash!
|
|
*/
|
|
if (value_name != NULL && *value_name != 0)
|
|
g_warning ("subscribe() failed: path must end in a /, got %s", key_name);
|
|
|
|
trace ("Subscribing to %s [registry %s / %s] - watch %x\n", key_name, path_name, value_name, self->watch);
|
|
|
|
path_namew = g_utf8_to_utf16 (path_name, -1, NULL, NULL, NULL);
|
|
g_free (path_name);
|
|
|
|
/* Give the caller the benefit of the doubt if the key doesn't exist and create it. The caller
|
|
* is almost certainly a new g_settings with this path as base path. */
|
|
result = RegCreateKeyExW (self->base_key, path_namew, 0, NULL, 0, KEY_READ, NULL, &hpath,
|
|
NULL);
|
|
g_free (path_namew);
|
|
|
|
if (result != ERROR_SUCCESS)
|
|
{
|
|
g_message_win32_error (result, "gregistrysettingsbackend: Unable to subscribe to key %s.", key_name);
|
|
g_atomic_int_inc (&self->watch->watches_remaining);
|
|
return;
|
|
}
|
|
|
|
event = CreateEvent (NULL, FALSE, FALSE, NULL);
|
|
if (event == NULL)
|
|
{
|
|
g_message_win32_error (result, "gregistrysettingsbackend: CreateEvent failed.");
|
|
g_atomic_int_inc (&self->watch->watches_remaining);
|
|
RegCloseKey (hpath);
|
|
return;
|
|
}
|
|
|
|
/* The actual watch is added by the thread, which has to re-subscribe each time it
|
|
* receives a change. */
|
|
if (!watch_add_notify (self, event, hpath, g_strdup (key_name)))
|
|
{
|
|
g_atomic_int_inc (&self->watch->watches_remaining);
|
|
RegCloseKey (hpath);
|
|
CloseHandle (event);
|
|
}
|
|
}
|
|
|
|
static void
|
|
g_registry_settings_backend_unsubscribe (GSettingsBackend *backend,
|
|
const char *key_name)
|
|
{
|
|
trace ("unsubscribe: %s.\n", key_name);
|
|
|
|
watch_remove_notify (G_REGISTRY_SETTINGS_BACKEND (backend), key_name);
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Object management junk
|
|
********************************************************************************/
|
|
|
|
static void
|
|
g_registry_settings_backend_finalize (GObject *object)
|
|
{
|
|
GRegistrySettingsBackend *self = G_REGISTRY_SETTINGS_BACKEND (object);
|
|
RegistryCacheItem *item;
|
|
|
|
item = self->cache_root->data;
|
|
g_warn_if_fail (item->ref_count == 1);
|
|
|
|
registry_cache_item_free (item);
|
|
g_node_destroy (self->cache_root);
|
|
|
|
if (self->watch != NULL)
|
|
{
|
|
EnterCriticalSection (self->watch->message_lock);
|
|
watch_stop_unlocked (self);
|
|
}
|
|
|
|
DeleteCriticalSection (self->cache_lock);
|
|
g_slice_free (CRITICAL_SECTION, self->cache_lock);
|
|
|
|
g_free (self->base_path);
|
|
g_free (self->base_pathw);
|
|
}
|
|
|
|
static void
|
|
g_registry_settings_backend_constructed (GObject *object)
|
|
{
|
|
GRegistrySettingsBackend *self = G_REGISTRY_SETTINGS_BACKEND (object);
|
|
|
|
if (self->base_key == NULL || self->base_path == NULL || self->base_pathw == NULL)
|
|
{
|
|
self->base_key = HKEY_CURRENT_USER;
|
|
self->base_path = g_strdup ("Software\\GSettings");
|
|
self->base_pathw = g_utf8_to_utf16 (self->base_path, -1, NULL, NULL, NULL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
g_registry_settings_backend_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GRegistrySettingsBackend *self = G_REGISTRY_SETTINGS_BACKEND (object);
|
|
|
|
switch ((GRegistrySettingsBackendProperty) prop_id)
|
|
{
|
|
case PROP_REGISTRY_KEY:
|
|
g_value_take_string (value,
|
|
g_strdup_printf ("%s\\%s",
|
|
predefined_key_to_string (self->base_key),
|
|
self->base_path));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
g_registry_settings_backend_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GRegistrySettingsBackend *self = G_REGISTRY_SETTINGS_BACKEND (object);
|
|
const gchar *reg_path;
|
|
|
|
switch ((GRegistrySettingsBackendProperty) prop_id)
|
|
{
|
|
case PROP_REGISTRY_KEY:
|
|
/* Construct only. */
|
|
g_assert (self->base_key == NULL);
|
|
g_assert (self->base_path == NULL);
|
|
g_assert (self->base_pathw == NULL);
|
|
|
|
reg_path = g_value_get_string (value);
|
|
if (reg_path != NULL)
|
|
{
|
|
HKEY base_key;
|
|
const gchar *base_path = split_registry_path (reg_path, &base_key);
|
|
|
|
if (base_path != NULL)
|
|
{
|
|
gunichar2 *base_pathw = g_utf8_to_utf16 (base_path, -1, NULL, NULL, NULL);
|
|
|
|
if (base_pathw != NULL)
|
|
{
|
|
self->base_key = g_steal_pointer (&base_key);
|
|
self->base_path = g_strdup (base_path);
|
|
self->base_pathw = g_steal_pointer (&base_pathw);
|
|
}
|
|
else
|
|
g_warning ("gregistrysettingsbackend: invalid base registry path '%s'", reg_path);
|
|
}
|
|
else
|
|
g_warning ("gregistrysettingsbackend: base registry path '%s' does not start with a valid root key", reg_path);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
g_registry_settings_backend_class_init (GRegistrySettingsBackendClass *class)
|
|
{
|
|
GSettingsBackendClass *backend_class = G_SETTINGS_BACKEND_CLASS (class);
|
|
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
|
|
|
object_class->finalize = g_registry_settings_backend_finalize;
|
|
object_class->constructed = g_registry_settings_backend_constructed;
|
|
object_class->get_property = g_registry_settings_backend_get_property;
|
|
object_class->set_property = g_registry_settings_backend_set_property;
|
|
|
|
backend_class->read = g_registry_settings_backend_read;
|
|
backend_class->write = g_registry_settings_backend_write;
|
|
backend_class->write_tree = g_registry_settings_backend_write_tree;
|
|
backend_class->reset = g_registry_settings_backend_reset;
|
|
backend_class->get_writable = g_registry_settings_backend_get_writable;
|
|
backend_class->subscribe = g_registry_settings_backend_subscribe;
|
|
backend_class->unsubscribe = g_registry_settings_backend_unsubscribe;
|
|
|
|
/**
|
|
* GRegistrySettingsBackend:registry-key:
|
|
*
|
|
* The location where settings are stored in the registry. Must
|
|
* start with one of the following:
|
|
* - `HKEY_CLASSES_ROOT`
|
|
* - `HKEY_CURRENT_CONFIG`
|
|
* - `HKEY_CURRENT_USER`
|
|
* - `HKEY_LOCAL_MACHINE`
|
|
* - `HKEY_USERS`
|
|
*
|
|
* Defaults to `HKEY_CURRENT_USER\Software\GSettings`.
|
|
*
|
|
* Since: 2.78
|
|
*/
|
|
g_object_class_install_property (object_class,
|
|
PROP_REGISTRY_KEY,
|
|
g_param_spec_string ("registry-key",
|
|
NULL, NULL,
|
|
"HKEY_CURRENT_USER\\Software\\GSettings",
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_STRINGS));
|
|
}
|
|
|
|
static void
|
|
g_registry_settings_backend_init (GRegistrySettingsBackend *self)
|
|
{
|
|
RegistryCacheItem *item;
|
|
|
|
item = g_slice_new (RegistryCacheItem);
|
|
item->value.type = REG_NONE;
|
|
item->value.ptr = NULL;
|
|
item->name = g_strdup ("<root>");
|
|
item->ref_count = 1;
|
|
self->cache_root = g_node_new (item);
|
|
|
|
self->cache_lock = g_slice_new (CRITICAL_SECTION);
|
|
InitializeCriticalSection (self->cache_lock);
|
|
|
|
self->watch = NULL;
|
|
}
|
|
|
|
/**
|
|
* g_registry_settings_backend_new:
|
|
* @registry_key: (nullable): the path to the registry key where
|
|
* settings are stored, or %NULL.
|
|
*
|
|
* If @registry_key is %NULL then the default path
|
|
* `HKEY_CURRENT_USER\Software\GSettings` is used.
|
|
*
|
|
* Returns: (transfer full): a registry-backed #GSettingsBackend
|
|
*
|
|
* Since: 2.78
|
|
**/
|
|
GSettingsBackend *
|
|
g_registry_settings_backend_new (const gchar *registry_key)
|
|
{
|
|
g_return_val_if_fail (registry_key == NULL || split_registry_path (registry_key, NULL), NULL);
|
|
|
|
return G_SETTINGS_BACKEND (g_object_new (G_TYPE_REGISTRY_SETTINGS_BACKEND,
|
|
"registry-key", registry_key,
|
|
NULL));
|
|
}
|