/* * 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.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 . * * 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. * * - 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 "giomodule.h" #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; 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; } 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; 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: %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 ("%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 (""); 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 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 ("\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 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; 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)); } #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 ("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; 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_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", 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; 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_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, "gregistrybackend: 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, "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); 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_backend_write (GSettingsBackend *backend, const gchar *key_name, GVariant *value, gpointer origin_tag) { GRegistryBackend *self = G_REGISTRY_BACKEND (backend); LONG result; HKEY hroot; RegistryWrite action; result = RegCreateKeyExW (HKEY_CURRENT_USER, self->base_pathw, 0, NULL, 0, KEY_WRITE, NULL, &hroot, NULL); if (result != ERROR_SUCCESS) { trace ("Error opening/creating key %s.\n", self->base_path); return FALSE; } action.self = self; action.hroot = 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; RegistryWrite action; result = RegCreateKeyExW (HKEY_CURRENT_USER, self->base_pathw, 0, NULL, 0, KEY_WRITE, NULL, &hroot, NULL); if (result != ERROR_SUCCESS) { trace ("Error opening/creating key %s.\n", self->base_path); return FALSE; } action.self = self; action.hroot = 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; 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 (HKEY_CURRENT_USER, path_namew, 0, KEY_SET_VALUE, &hpath); g_free (path_namew); if (result != ERROR_SUCCESS) { g_message_win32_error (result, "Registry: resetting key '%s'", 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'", path_name); g_free (path_name); return; } g_free (path_name); g_settings_backend_changed (backend, key_name, origin_tag); } static gboolean g_registry_backend_get_writable (GSettingsBackend *backend, const gchar *key_name) { GRegistryBackend *self = G_REGISTRY_BACKEND (backend); gchar *path_name; gunichar2 *path_namew; gchar *value_name; 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 (HKEY_CURRENT_USER, 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.\n", 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 { 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; 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_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, 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, "gregistrybackend: 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; if (partial_key_name == NULL) item = g_strdup (buffer); else item = g_build_path ("/", partial_key_name, buffer, NULL); g_ptr_array_add (event->items, item); } g_free (buffer); } if (result != ERROR_NO_MORE_ITEMS) g_message_win32_error (result, "gregistrybackend: 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_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_object_ref (self->owner); event->prefix = g_strdup (prefix); event->items = g_ptr_array_new_with_free_func (g_free); EnterCriticalSection (G_REGISTRY_BACKEND (self->owner)->cache_lock); registry_cache_update (G_REGISTRY_BACKEND (self->owner), hpath, prefix, NULL, cache_node, 0, event); LeaveCriticalSection (G_REGISTRY_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 (GRegistryBackend *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 (), "gregistrybackend: 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 (), "gregistrybackend: 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 (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); 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 (GRegistryBackend *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 (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", 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; 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 (HKEY_CURRENT_USER, path_namew, 0, NULL, 0, KEY_READ, NULL, &hpath, NULL); g_free (path_namew); 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."); 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_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 ********************************************************************************/ 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); 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_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->subscribe = g_registry_backend_subscribe; backend_class->unsubscribe = g_registry_backend_unsubscribe; } static void g_registry_backend_init (GRegistryBackend *self) { RegistryCacheItem *item; self->base_path = g_strdup_printf ("Software\\GSettings"); self->base_pathw = g_utf8_to_utf16 (self->base_path, -1, NULL, NULL, NULL); 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->cache_lock = g_slice_new (CRITICAL_SECTION); InitializeCriticalSection (self->cache_lock); self->watch = NULL; }