From 3209024c3ba730df9551074819672756477bd5aa Mon Sep 17 00:00:00 2001 From: Sam Thursfield Date: Thu, 12 Aug 2010 16:10:23 +0100 Subject: [PATCH] Add GSettings Windows Registry backend --- gio/Makefile.am | 7 +- gio/giomodule.c | 4 + gio/gregistrysettingsbackend.c | 1966 ++++++++++++++++++++++++++++++++++++++++ gio/gregistrysettingsbackend.h | 31 + 4 files changed, 2007 insertions(+), 1 deletion(-) create mode 100644 gio/gregistrysettingsbackend.c create mode 100644 gio/gregistrysettingsbackend.h diff --git a/gio/Makefile.am b/gio/Makefile.am index b7479e3..4618a12 100644 --- a/gio/Makefile.am +++ b/gio/Makefile.am @@ -128,6 +128,12 @@ settings_sources = \ gsettings-mapping.c \ gsettings.c +if OS_WIN32 +settings_sources += \ + gregistrysettingsbackend.h \ + gregistrysettingsbackend.c +endif + application_headers = \ gactiongroup.h \ gsimpleactiongroup.h \ @@ -142,7 +148,6 @@ application_sources = \ gsimpleaction.c \ gapplication.c - local_sources = \ glocaldirectorymonitor.c \ glocaldirectorymonitor.h \ diff --git a/gio/giomodule.c b/gio/giomodule.c index 882e224..39a38ff 100644 --- a/gio/giomodule.c +++ b/gio/giomodule.c @@ -39,6 +39,9 @@ #ifdef G_OS_UNIX #include "gdesktopappinfo.h" #endif +#ifdef G_OS_WIN32 +#include "gregistrysettingsbackend.h" +#endif #include /** @@ -596,6 +599,7 @@ _g_io_modules_ensure_loaded (void) #ifdef G_OS_WIN32 _g_win32_volume_monitor_get_type (); g_win32_directory_monitor_get_type (); + g_registry_backend_get_type (); #endif #ifdef G_OS_UNIX _g_unix_volume_monitor_get_type (); diff --git a/gio/gregistrysettingsbackend.c b/gio/gregistrysettingsbackend.c new file mode 100644 index 0000000..3e3db2e --- /dev/null +++ b/gio/gregistrysettingsbackend.c @@ -0,0 +1,1966 @@ +/* + * Copyright © 2009-10 Sam Thursfield + * + * 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 licence, 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Sam Thursfield + */ + +/* GRegistryBackend implementation notes: + * + * - All settings are stored under the path: + * HKEY_CURRENT_USER\Software\GSettings\ + * This means all settings are per-user. 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. + * + * - RegCreateKeyA is used - Windows can also handle UTF16LE strings. + * GSettings doesn't pay any attention to encoding, so by using ANSI we + * hopefully avoid passing any invalid Unicode. + * + * - 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 "gsimplepermission.h" +#include "gsettingsbackend.h" +#include "giomodule.h" + + +#define _WIN32_WINNT 0x0500 +#define WIN32_LEAN_AND_MEAN +#include + +//#define TRACE + +/* GSettings' limit */ +#define MAX_KEY_NAME_LENGTH 32 + +/* 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_BACKEND (g_registry_backend_get_type ()) +#define G_REGISTRY_BACKEND(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \ + G_TYPE_REGISTRY_BACKEND, GRegistryBackend)) +#define G_IS_REGISTRY_BACKEND(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \ + G_TYPE_REGISTRY_BACKEND)) + + +typedef GSettingsBackendClass GRegistryBackendClass; + +typedef struct { + GSettingsBackend parent_instance; + + char *base_path; + + /* 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; +} GRegistryBackend; + +G_DEFINE_TYPE_WITH_CODE (GRegistryBackend, + g_registry_backend, + G_TYPE_SETTINGS_BACKEND, + g_io_extension_point_implement (G_SETTINGS_BACKEND_EXTENSION_POINT_NAME, + g_define_type_id, "registry", 90)) + + +/********************************************************************************** + * Utility functions + **********************************************************************************/ + +#include +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_message_win32_error (DWORD result_code, + const gchar *format, + ...) +{ + va_list va; + gint pos; + gchar win32_message[1024]; + + if (result_code == 0) + result_code = GetLastError (); + + va_start (va, format); + pos = g_vsnprintf (win32_message, 512, format, va); + + win32_message[pos++] = ':'; win32_message[pos++] = ' '; + + FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, result_code, 0, (LPTSTR)(win32_message+pos), + 1023 - pos, NULL); + + if (result_code == ERROR_KEY_DELETED) + trace ("(%s)", win32_message); + else + g_message (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 + * preceeding /. + */ + 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_uint16 (variant); + case 'q': return g_variant_get_int16 (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 't': return g_variant_get_uint64 (variant); + case 'x': return g_variant_get_int64 (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: %s.\n", + path_name, value_name); +} + +/*************************************************************************** + * 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 ("%i", value.dword); + else if (value.type == REG_QWORD) + return g_strdup_printf ("%I64i", 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 (""); + else + return g_strdup_printf (""); +} + +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; + + gint32 touched : 1; + RegistryValue value; +} RegistryCacheItem; + + + +static GNode * +registry_cache_add_item (GNode *parent, + gchar *name, + RegistryValue value, + gint ref_count) +{ + RegistryCacheItem *item = g_slice_new (RegistryCacheItem); + GNode *cache_node; + + g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (parent != NULL, NULL); + + /* 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->touched = FALSE; + trace ("\treg 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 +_free_cache_item (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) + { + _free_cache_item (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; + } +} + + +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; iname, 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)); +} + + +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, + *c = strchr (component, '/'); + + 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 ++; + + GNode *child = registry_cache_find_immediate_child (node, component); + if (child == NULL && create_if_not_found) + { + item = g_slice_new (RegistryCacheItem); + item->name = g_strdup (component); + item->value.type = REG_NONE; + item->value.ptr = NULL; + item->ref_count = n_parent_watches; + child = g_node_new (item); + g_node_append (node, child); + trace ("\tget node for key recursive: new %x = %s.\n", node, item->name); + } + + /* We are done if there are no more path components. Allow for a trailing /. */ + if (child==NULL || c == NULL || *(c+1)==0) + return child; + else + { + 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, + *result = NULL; + gchar *component, *c; + + g_return_val_if_fail (key_name != NULL, NULL); + + if (key_name[0] == '/') + key_name ++; + + /* Ignore preceeding / */ + component = g_strdup (key_name); + c = strchr (component, '/'); + if (c != NULL) + *c = 0; + + child = registry_cache_find_immediate_child (root, component); + if (child == NULL && create_if_not_found) + { + /* Reference count is set to 0, tree should be referenced by the caller */ + RegistryCacheItem *item = g_slice_new (RegistryCacheItem); + item->value.type = REG_NONE; + item->value.ptr = NULL; + item->name = g_strdup (component); + item->ref_count = 0; + trace ("get_node_for_key: New node for component '%s'\n", item->name); + child = g_node_new (item); + g_node_append (root, child); + } + + if (c == NULL) + result = root; + else 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 = cache_node->data; + + g_return_val_if_fail (cache_node != NULL, FALSE); + g_return_val_if_fail (cache_item != NULL, FALSE); + + 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 ("gregistrybackend: 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 notifcation 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; + + g_return_val_if_fail (p_value != NULL, FALSE); + + p_value->type = REG_NONE; + p_value->ptr = NULL; + + result = RegQueryValueExA (hpath, value_name, 0, &p_value->type, NULL, &value_data_size); + if (result != ERROR_SUCCESS) + { + handle_read_error (result, path_name, value_name); + return FALSE; + } + + if (p_value->type == REG_SZ && value_data_size == 0) + { + p_value->ptr = g_strdup (""); + 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 = RegQueryValueExA (hpath, value_name, 0, NULL, (LPBYTE)buffer, &value_data_size); + if (result != ERROR_SUCCESS) + { + handle_read_error (result, path_name, value_name); + return FALSE; + } + + return TRUE; +} + + +static GVariant * +g_registry_backend_read (GSettingsBackend *backend, + const gchar *key_name, + const GVariantType *expected_type, + gboolean default_value) +{ + GRegistryBackend *self = G_REGISTRY_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\n", + key_name, error->message); + } + } + break; + } + + g_free (gsettings_type); + + return gsettings_value; +} + + +typedef struct +{ + GRegistryBackend *self; + HKEY hroot; +} RegistryWrite; + +static gboolean +g_registry_backend_write_one (const char *key_name, + GVariant *variant, + gpointer user_data) +{ + GRegistryBackend *self; + RegistryWrite *action; + RegistryValue value; + + HKEY hroot, hpath; + gchar *path_name, *value_name = NULL; + DWORD value_data_size; + LPVOID value_data; + LONG result; + + GNode *node; + gboolean changed; + + action = user_data; + self = G_REGISTRY_BACKEND (action->self); + hroot = action->hroot; + + value.type = REG_NONE; + value.ptr = NULL; + + const gchar *type_string = g_variant_get_type_string (variant); + 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); + + /* Store the value in the registry */ + result = RegCreateKeyExA (hroot, path_name, 0, NULL, 0, KEY_WRITE, NULL, &hpath, NULL); + if (result != ERROR_SUCCESS) + { + g_message_win32_error (result, "gregistrybackend: opening key %s failed", path_name+1); + registry_value_free (value); + g_free (path_name); + return FALSE; + } + + result = RegSetValueExA (hpath, value_name, 0, value.type, value_data, value_data_size); + if (result != ERROR_SUCCESS) + g_message_win32_error (result, "gregistrybackend: setting value %s\%s\\%s failed.\n", + 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); + + 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_backend_write (GSettingsBackend *backend, + const gchar *key_name, + GVariant *value, + gpointer origin_tag) +{ + GRegistryBackend *self = G_REGISTRY_BACKEND (backend); + LONG result; + HKEY hroot; + + result = RegCreateKeyExA (HKEY_CURRENT_USER, self->base_path, 0, NULL, 0, + KEY_WRITE, NULL, &hroot, NULL); + if (result != ERROR_SUCCESS) { + trace ("Error opening/creating key %s.\n", self->base_path); + return FALSE; + } + + RegistryWrite action = { self, hroot }; + g_registry_backend_write_one (key_name, value, &action); + g_settings_backend_changed (backend, key_name, origin_tag); + + RegCloseKey (hroot); + + return TRUE; +} + +static gboolean +g_registry_backend_write_tree (GSettingsBackend *backend, + GTree *values, + gpointer origin_tag) +{ + GRegistryBackend *self = G_REGISTRY_BACKEND (backend); + LONG result; + HKEY hroot; + + result = RegCreateKeyExA (HKEY_CURRENT_USER, self->base_path, 0, NULL, 0, + KEY_WRITE, NULL, &hroot, NULL); + if (result != ERROR_SUCCESS) { + trace ("Error opening/creating key %s.\n", self->base_path); + return FALSE; + } + + RegistryWrite action = { self, hroot }; + g_tree_foreach (values, (GTraverseFunc)g_registry_backend_write_one, + &action); + + g_settings_backend_changed_tree (backend, values, origin_tag); + RegCloseKey (hroot); + + return TRUE; +} + +static void +g_registry_backend_reset (GSettingsBackend *backend, + const gchar *key_name, + gpointer origin_tag) +{ + GRegistryBackend *self = G_REGISTRY_BACKEND (backend); + gchar *path_name, *value_name = NULL; + 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); + + result = RegOpenKeyExA (HKEY_CURRENT_USER, path_name, 0, KEY_SET_VALUE, &hpath); + if (result != ERROR_SUCCESS) + { + g_message_win32_error (result, "Registry: resetting key '%s'", path_name); + g_free (path_name); + return; + } + + result = RegDeleteValueA (hpath, value_name); + RegCloseKey (hpath); + + if (result != ERROR_SUCCESS) + { + g_message_win32_error (result, "Registry: resetting key '%s'", path_name); + g_free (path_name); + return; + } + + g_free (path_name); + + + g_settings_backend_changed (backend, key_name, origin_tag); +} + +/* Not implemented and probably beyond the scope of this backend */ +static gboolean +g_registry_backend_get_writable (GSettingsBackend *backend, + const gchar *key_name) +{ + return TRUE; +} + +static GPermission * +g_registry_backend_get_permission (GSettingsBackend *backend, + const gchar *key_name) +{ + return g_simple_permission_new (TRUE); +} + + +/******************************************************************************** + * Spot-the-difference engine + ********************************************************************************/ + +static void +_free_watch (WatchThreadState *self, + gint index, + GNode *cache_node); + +static void +registry_cache_item_reset_touched (GNode *node, + gpointer data) +{ + RegistryCacheItem *item = node->data; + item->touched = 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) + { + /* 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! */ + gint i; + for (i=1; icache_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); + } + } + _free_cache_item (node->data); + g_node_destroy (node); +} + +static void +registry_cache_remove_deleted (GNode *node, + gpointer data) +{ + RegistryCacheItem *item = node->data; + + if (!item->touched) + registry_cache_destroy_tree (node, data); +} + +/* 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_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 (GRegistryBackend *self, + HKEY hpath, + const gchar *prefix, + const gchar *partial_key_name, + GNode *cache_node, + int n_watches, + GPtrArray *changes) +{ + gchar buffer[MAX_KEY_NAME_LENGTH + 1]; + gchar *key_name; + gint i; + LONG result; + + RegistryCacheItem *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 'touched' flag. When the registry traversal is done, any untouched nodes + * must have been deleted from the registry. + */ + g_node_children_foreach (cache_node, G_TRAVERSE_ALL, + registry_cache_item_reset_touched, NULL); + + /* Recurse into each subpath at the current level, if any */ + i = 0; + while (1) + { + DWORD buffer_size = MAX_KEY_NAME_LENGTH; + HKEY hsubpath; + + result = RegEnumKeyEx (hpath, i++, buffer, &buffer_size, NULL, NULL, NULL, NULL); + if (result != ERROR_SUCCESS) + break; + + result = RegOpenKeyEx (hpath, buffer, 0, KEY_READ, &hsubpath); + if (result == ERROR_SUCCESS) + { + GNode *subkey_node; + RegistryCacheItem *child_item; + + 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); + } + + + registry_cache_update (self, hsubpath, prefix, buffer, subkey_node, + n_watches, changes); + child_item = subkey_node->data; + child_item->touched = TRUE; + } + RegCloseKey (hsubpath); + } + + if (result != ERROR_NO_MORE_ITEMS) + g_message_win32_error (result, "gregistrybackend: error enumerating subkeys for cache."); + + /* Enumerate each value at 'path' and check if it has changed */ + i = 0; + while (1) + { + DWORD buffer_size = MAX_KEY_NAME_LENGTH; + GNode *cache_child_node; + RegistryCacheItem *child_item; + RegistryValue value; + gboolean changed = FALSE; + + result = RegEnumValue (hpath, i++, buffer, &buffer_size, NULL, NULL, NULL, NULL); + if (result != ERROR_SUCCESS) + break; + + if (buffer[0]==0) + /* This is the key's 'default' value, for which we have no use. */ + continue; + + cache_child_node = registry_cache_find_immediate_child (cache_node, buffer); + + if (!registry_read (hpath, key_name, buffer, &value)) + 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->touched = TRUE; + if (changed == TRUE && changes != NULL) + { + gchar *item; + if (partial_key_name == NULL) + item = g_strdup (buffer); + else + item = g_build_path ("/", partial_key_name, buffer, NULL); + g_ptr_array_add (changes, item); + } + } + + if (result != ERROR_NO_MORE_ITEMS) + g_message_win32_error (result, "gregistrybackend: error enumerating values for cache"); + + /* Any nodes now left untouched must have been deleted, remove them from cache */ + g_node_children_foreach (cache_node, G_TRAVERSE_ALL, + registry_cache_remove_deleted, self->watch); + + 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); +} + + +/* One of these is sent down the pipe when something happens in the registry. */ +typedef struct +{ + GRegistryBackend *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; + +/* This handler runs in the main thread to emit the changed signals */ +static gboolean +watch_handler (RegistryEvent *event) +{ + gint i; + 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); + + for (i=0; iitems->len; i++) + g_free (g_ptr_array_index (event->items, i)); + g_ptr_array_free (event->items, TRUE); + + g_free (event->prefix); + g_object_unref (event->self); + + g_slice_free (RegistryEvent, event); + return FALSE; +}; + + +static void +_free_watch (WatchThreadState *self, + gint 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_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; + gint i; + + for (i=1; iprefixes->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 %x, prefix %s, index %i\n", + (guint)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: + { + gint i; + + /* Free any remaining cache and watch handles */ + for (i=1; ievents->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; + + /* 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. + */ + gint 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_BACKEND (self->owner); + g_object_ref (self->owner); + + event->items = g_ptr_array_new (); + + EnterCriticalSection (G_REGISTRY_BACKEND (self->owner)->cache_lock); + registry_cache_update (G_REGISTRY_BACKEND (self->owner), hpath, + prefix, NULL, cache_node, 0, event->items); + LeaveCriticalSection (G_REGISTRY_BACKEND (self->owner)->cache_lock); + + if (event->items->len > 0) + { + event->prefix = g_strdup (prefix); + g_idle_add ((GSourceFunc) watch_handler, event); + } + else + { + 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 (GRegistryBackend *self) +{ + WatchThreadState *watch; + + g_return_val_if_fail (self->watch == NULL, FALSE); + + self->cache_lock = g_slice_new (CRITICAL_SECTION); + InitializeCriticalSection (self->cache_lock); + + 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 (0, "gregistrybackend: Failed to create sync objects."); + goto fail_1; + } + + /* 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 (0, "gregistrybackend: Failed to create notify watch thread."); + goto fail_2; + } + + self->watch = watch; + + return TRUE; + +fail_2: + DeleteCriticalSection (self->cache_lock); + g_slice_free (CRITICAL_SECTION, self->cache_lock); + DeleteCriticalSection (watch->message_lock); + g_slice_free (CRITICAL_SECTION, watch->message_lock); + CloseHandle (watch->message_sent_event); + CloseHandle (watch->message_received_event); +fail_1: + g_slice_free (WatchThreadState, watch); + return FALSE; +} + +/* This function assumes you hold the message lock! */ +static void +watch_stop_unlocked (GRegistryBackend *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 ("gregistrybackend: unable to stop watch thread."); + return; + } + + LeaveCriticalSection (watch->message_lock); + DeleteCriticalSection (watch->message_lock); + DeleteCriticalSection (self->cache_lock); + g_slice_free (CRITICAL_SECTION, watch->message_lock); + g_slice_free (CRITICAL_SECTION, self->cache_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 (GRegistryBackend *self, + HANDLE event, + HKEY hpath, + gchar *gsettings_prefix) +{ + WatchThreadState *watch = self->watch; + GNode *cache_node; + RegistryCacheItem *cache_item; + DWORD result; + + 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); + + g_return_val_if_fail (cache_node != NULL, FALSE); + g_return_val_if_fail (cache_node->data != NULL, 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); + 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. + */ + result = 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 (GRegistryBackend *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\n", 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_backend_subscribe (GSettingsBackend *backend, + const char *key_name) +{ + GRegistryBackend *self = G_REGISTRY_BACKEND (backend); + gchar *path_name, *value_name = NULL; + HKEY hpath; + HANDLE event; + LONG result; + + if (self->watch == NULL) + if (!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.\n", 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\n", key_name); + + trace ("Subscribing to %s [registry %s / %s] - watch %x\n", key_name, path_name, value_name, self->watch); + + + /* 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 = RegCreateKeyExA (HKEY_CURRENT_USER, path_name, 0, NULL, 0, KEY_READ, NULL, &hpath, + NULL); + g_free (path_name); + + if (result != ERROR_SUCCESS) + { + g_message_win32_error (result, "gregistrybackend: 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, "gregistrybackend: CreateEvent failed.\n"); + 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); +} + +static void +g_registry_backend_unsubscribe (GSettingsBackend *backend, + const char *key_name) +{ + trace ("unsubscribe: %s.\n", key_name); + + watch_remove_notify (G_REGISTRY_BACKEND (backend), key_name); +} + + +/******************************************************************************** + * Object management junk + ********************************************************************************/ + +GSettingsBackend * +g_registry_backend_new (void) { + return g_object_new (G_TYPE_REGISTRY_BACKEND, NULL); +} + +static void +g_registry_backend_finalize (GObject *object) +{ + GRegistryBackend *self = G_REGISTRY_BACKEND (object); + RegistryCacheItem *item; + + item = self->cache_root->data; + g_warn_if_fail (item->ref_count == 1); + + _free_cache_item (item); + g_node_destroy (self->cache_root); + + if (self->watch != NULL) + { + EnterCriticalSection (self->watch->message_lock); + watch_stop_unlocked (self); + } + + g_free (self->base_path); +} + +static void +g_registry_backend_class_init (GRegistryBackendClass *class) +{ + GSettingsBackendClass *backend_class = G_SETTINGS_BACKEND_CLASS (class); + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->finalize = g_registry_backend_finalize; + + backend_class->read = g_registry_backend_read; + backend_class->write = g_registry_backend_write; + backend_class->write_tree = g_registry_backend_write_tree; + backend_class->reset = g_registry_backend_reset; + backend_class->get_writable = g_registry_backend_get_writable; + backend_class->get_permission = g_registry_backend_get_permission; + backend_class->subscribe = g_registry_backend_subscribe; + backend_class->unsubscribe = g_registry_backend_unsubscribe; +} + +static void +g_registry_backend_init (GRegistryBackend *self) +{ + self->base_path = g_strdup_printf ("Software\\GSettings"); + + RegistryCacheItem *item = g_slice_new (RegistryCacheItem); + item->value.type = REG_NONE; + item->value.ptr = NULL; + item->name = g_strdup (""); + item->ref_count = 1; + self->cache_root = g_node_new (item); + + self->watch = NULL; +} diff --git a/gio/gregistrysettingsbackend.h b/gio/gregistrysettingsbackend.h new file mode 100644 index 0000000..cd7bd7c --- /dev/null +++ b/gio/gregistrysettingsbackend.h @@ -0,0 +1,31 @@ +/* + * Copyright © 2009-10 Sam Thursfield + * + * 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 licence, 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Sam Thursfield + */ + +#ifndef __G_REGISTRY_SETTINGS_BACKEND_H__ +#define __G_REGISTRY_SETTINGS_BACKEND_H__ + +#include + +G_GNUC_INTERNAL +GType g_registry_backend_get_type (void); + + +#endif /* __G_REGISTRY_SETTINGS_BACKEND_H__ */ -- 2.7.4