| /* |
| * 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 <http://www.gnu.org/licenses/>. |
| * |
| * Author: Sam Thursfield <ssssam@gmail.com> |
| */ |
| |
| /* 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 <windows.h> |
| |
| //#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 <stdio.h> |
| static void |
| trace (const char *format, |
| ...) |
| { |
| #ifdef TRACE |
| va_list va; va_start (va, format); |
| vprintf (format, va); |
| fflush (stdout); |
| va_end (va); |
| #endif |
| } |
| |
| /* g_message including a windows error message. It is not useful to have an |
| * equivalent function for g_warning because none of the registry errors can |
| * result from programmer error (Microsoft programmers don't count), instead |
| * they will mostly occur from people messing with the registry by hand. */ |
| static void |
| g_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 ("<empty>"); |
| else |
| return g_strdup_printf ("<invalid>"); |
| } |
| |
| static void |
| registry_value_free (RegistryValue value) |
| { |
| if (value.type == REG_SZ || value.type == REG_QWORD) |
| g_free (value.ptr); |
| |
| value.type = REG_NONE; |
| value.ptr = NULL; |
| } |
| |
| /* The registry cache is stored as a tree, for easy traversal. Right now we |
| * don't sort it in a clever way. Each node corresponds to a path element |
| * ('key' in registry terms) or a value. |
| * |
| * Each subscription uses the same cache. Because GSettings can subscribe to |
| * the tree at any node any number of times, we need to reference count the |
| * nodes. |
| */ |
| typedef struct |
| { |
| /* Component of path that this node represents */ |
| gchar *name; |
| |
| /* If a watch is subscribed at this point (subscription_count > 0) we can |
| * block its next notification. This is useful because if two watches cover |
| * the same path, both will trigger when it changes. It also allows changes |
| * done by the application to be ignored by the watch thread. |
| */ |
| gint32 block_count : 8; |
| |
| /* Number of times g_settings_subscribe has been called for this location |
| * (I guess you can't subscribe more than 16383 times) */ |
| gint32 subscription_count : 14; |
| |
| gint32 ref_count : 9; |
| |
| 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; i<depth; i++) |
| g_print (" "); |
| if (item == NULL) |
| g_print ("*root*\n"); |
| else |
| g_print ("'%s' [%i] @ %x = %s\n", item->name, item->ref_count, (guint)cache_node, |
| registry_value_dump (item->value)); |
| g_node_children_foreach (cache_node, G_TRAVERSE_ALL, registry_cache_dump, |
| GINT_TO_POINTER (new_depth)); |
| } |
| #endif |
| |
| typedef struct |
| { |
| gchar *name; |
| GNode *result; |
| } RegistryCacheSearch; |
| |
| static gboolean |
| registry_cache_find_compare (GNode *node, |
| gpointer data) |
| { |
| RegistryCacheSearch *search = data; |
| RegistryCacheItem *item = node->data; |
| |
| if (item == NULL) /* root node */ |
| return FALSE; |
| |
| g_return_val_if_fail (search->name != NULL, FALSE); |
| g_return_val_if_fail (item->name != NULL, FALSE); |
| |
| if (strcmp (search->name, item->name) == 0) |
| { |
| search->result = node; |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| static GNode * |
| registry_cache_find_immediate_child (GNode *node, |
| gchar *name) |
| { |
| RegistryCacheSearch search; |
| |
| search.result = NULL; |
| search.name = name; |
| |
| g_node_traverse (node, G_POST_ORDER, G_TRAVERSE_ALL, 2, |
| registry_cache_find_compare, &search); |
| |
| return search.result; |
| } |
| |
| static GNode * |
| registry_cache_get_node_for_key_recursive (GNode *node, |
| gchar *key_name, |
| gboolean create_if_not_found, |
| gint n_parent_watches) |
| { |
| RegistryCacheItem *item; |
| gchar *component = key_name; |
| gchar *c = strchr (component, '/'); |
| GNode *child; |
| |
| if (c != NULL) |
| *c = 0; |
| |
| /* We count up how many watch points we travel through finding this node, |
| * because a new node should have as many references as there are watches at |
| * points above it in the tree. |
| */ |
| item = node->data; |
| if (item->subscription_count > 0) |
| n_parent_watches++; |
| |
| child = registry_cache_find_immediate_child (node, component); |
| if (child == NULL && create_if_not_found) |
| { |
| RegistryValue null_value = { REG_NONE, {0} }; |
| |
| child = registry_cache_add_item (node, component, |
| null_value, n_parent_watches); |
| |
| trace ("\tget node for key recursive: new %x = %s.\n", node, component); |
| } |
| |
| /* We are done if there are no more path components. Allow for a trailing /. */ |
| if (child == NULL || c == NULL || *(c + 1) == 0) |
| return child; |
| |
| trace ("get node for key recursive: next: %s.\n", c + 1); |
| |
| return registry_cache_get_node_for_key_recursive (child, c + 1, |
| create_if_not_found, |
| n_parent_watches); |
| } |
| |
| /* Look up a GSettings key in the cache. */ |
| static GNode * |
| registry_cache_get_node_for_key (GNode *root, |
| const gchar *key_name, |
| gboolean create_if_not_found) |
| { |
| GNode *child = NULL; |
| GNode *result = NULL; |
| gchar *component, *c; |
| |
| g_return_val_if_fail (key_name != NULL, NULL); |
| |
| if (key_name[0] == '/') |
| key_name++; |
| |
| /* Ignore preceding / */ |
| component = g_strdup (key_name); |
| c = strchr (component, '/'); |
| |
| if (c == NULL) |
| { |
| g_free (component); |
| return root; |
| } |
| |
| if (c != NULL) |
| *c = 0; |
| |
| child = registry_cache_find_immediate_child (root, component); |
| if (child == NULL && create_if_not_found) |
| { |
| RegistryValue null_value = { REG_NONE, {0} }; |
| |
| /* Reference count is set to 0, tree should be referenced by the caller */ |
| child = registry_cache_add_item (root, component, |
| null_value, 0); |
| |
| trace ("get_node_for_key: New node for component '%s'\n", component); |
| } |
| |
| if (*(c + 1) == 0) |
| result = child; |
| else if (child != NULL) |
| result = registry_cache_get_node_for_key_recursive (child, c + 1, |
| create_if_not_found, 0); |
| |
| g_free (component); |
| |
| return result; |
| } |
| |
| /* Check the cache node against the registry key it represents. Return TRUE if |
| * they differ, and update the cache with the new value. |
| */ |
| static gboolean |
| registry_cache_update_node (GNode *cache_node, |
| RegistryValue registry_value) |
| { |
| RegistryCacheItem *cache_item; |
| |
| g_return_val_if_fail (cache_node != NULL, FALSE); |
| g_return_val_if_fail (cache_node->data != NULL, FALSE); |
| |
| cache_item = cache_node->data; |
| |
| if (registry_value.type != cache_item->value.type) |
| { |
| /* The type has changed. Update cache item and register it as changed. |
| * Either the schema has changed and this is entirely legitimate, or |
| * whenever the app reads the key it will get the default value due to |
| * the type mismatch. |
| */ |
| cache_item->value = registry_value; |
| return TRUE; |
| } |
| |
| switch (registry_value.type) |
| { |
| case REG_DWORD: |
| { |
| if (cache_item->value.dword == registry_value.dword) |
| return FALSE; |
| else |
| { |
| cache_item->value.dword = registry_value.dword; |
| return TRUE; |
| } |
| } |
| case REG_QWORD: |
| { |
| g_return_val_if_fail (registry_value.ptr != NULL && |
| cache_item->value.ptr != NULL, FALSE); |
| |
| if (memcmp (registry_value.ptr, cache_item->value.ptr, 8)==0) |
| { |
| g_free (registry_value.ptr); |
| return FALSE; |
| } |
| else |
| { |
| g_free (cache_item->value.ptr); |
| cache_item->value.ptr = registry_value.ptr; |
| return TRUE; |
| } |
| } |
| case REG_SZ: |
| { |
| /* Value should not exist if it is NULL, an empty string is "" */ |
| g_return_val_if_fail (cache_item->value.ptr != NULL, FALSE); |
| g_return_val_if_fail (registry_value.ptr != NULL, FALSE); |
| |
| if (strcmp (registry_value.ptr, cache_item->value.ptr) == 0) |
| { |
| g_free (registry_value.ptr); |
| return FALSE; |
| } |
| else |
| { |
| g_free (cache_item->value.ptr); |
| cache_item->value.ptr = registry_value.ptr; |
| return TRUE; |
| } |
| } |
| default: |
| g_warning ("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_REGISTRY_BACKEND (g_object_ref (self->owner)); |
| event->prefix = g_strdup (prefix); |
| event->items = g_ptr_array_new_with_free_func (g_free); |
| |
| EnterCriticalSection (G_REGISTRY_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 ("<root>"); |
| item->ref_count = 1; |
| self->cache_root = g_node_new (item); |
| |
| self->cache_lock = g_slice_new (CRITICAL_SECTION); |
| InitializeCriticalSection (self->cache_lock); |
| |
| self->watch = NULL; |
| } |