diff --git a/.dconf.metadata b/.dconf.metadata new file mode 100644 index 0000000..0fda437 --- /dev/null +++ b/.dconf.metadata @@ -0,0 +1 @@ +e6b10da51df002a1661ce938941770c549cd5b87 SOURCES/dconf-0.28.0.tar.xz diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7125557 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +SOURCES/dconf-0.28.0.tar.xz diff --git a/SOURCES/0001-Engine-Change-overflow-thresholds-in-subscription-co.patch b/SOURCES/0001-Engine-Change-overflow-thresholds-in-subscription-co.patch new file mode 100644 index 0000000..a894f29 --- /dev/null +++ b/SOURCES/0001-Engine-Change-overflow-thresholds-in-subscription-co.patch @@ -0,0 +1,35 @@ +From 63c985f5afecc69eee3fe7a7e04cc8118097b3c3 Mon Sep 17 00:00:00 2001 +From: Daniel Playfair Cal +Date: Wed, 15 Aug 2018 08:46:49 +1000 +Subject: [PATCH] Engine: Change overflow thresholds in subscription counts + from GMAXUINT32 to GMAXUINT + +--- + engine/dconf-engine.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/engine/dconf-engine.c b/engine/dconf-engine.c +index ad891e6..dde8c18 100644 +--- a/engine/dconf-engine.c ++++ b/engine/dconf-engine.c +@@ -253,7 +253,7 @@ dconf_engine_move_subscriptions (GHashTable *from_counts, + guint from_count = GPOINTER_TO_UINT (g_hash_table_lookup (from_counts, path)); + guint old_to_count = GPOINTER_TO_UINT (g_hash_table_lookup (to_counts, path)); + // Detect overflows +- g_assert (old_to_count <= G_MAXUINT32 - from_count); ++ g_assert (old_to_count <= G_MAXUINT - from_count); + guint new_to_count = old_to_count + from_count; + if (from_count != 0) + { +@@ -275,7 +275,7 @@ dconf_engine_inc_subscriptions (GHashTable *counts, + { + guint old_count = GPOINTER_TO_UINT (g_hash_table_lookup (counts, path)); + // Detect overflows +- g_assert (old_count < G_MAXUINT32); ++ g_assert (old_count < G_MAXUINT); + guint new_count = old_count + 1; + g_hash_table_replace (counts, g_strdup (path), GUINT_TO_POINTER (new_count)); + return new_count; +-- +2.20.1 + diff --git a/SOURCES/0001-Engine-add-some-missing-objects-to-dconf_engine_unre.patch b/SOURCES/0001-Engine-add-some-missing-objects-to-dconf_engine_unre.patch new file mode 100644 index 0000000..b64096e --- /dev/null +++ b/SOURCES/0001-Engine-add-some-missing-objects-to-dconf_engine_unre.patch @@ -0,0 +1,37 @@ +From 6e6089791b38b4da84d5b49c0b24286be1526a2b Mon Sep 17 00:00:00 2001 +From: Daniel Playfair Cal +Date: Wed, 25 Jul 2018 23:58:26 +1000 +Subject: [PATCH 1/5] Engine: add some missing objects to dconf_engine_unref + +--- + engine/dconf-engine.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/engine/dconf-engine.c b/engine/dconf-engine.c +index c0ff12d..2a99eab 100644 +--- a/engine/dconf-engine.c ++++ b/engine/dconf-engine.c +@@ -289,11 +289,20 @@ dconf_engine_unref (DConfEngine *engine) + + g_free (engine->last_handled); + ++ while (!g_queue_is_empty (&engine->pending)) ++ dconf_changeset_unref ((DConfChangeset *) g_queue_pop_head (&engine->pending)); ++ ++ while (!g_queue_is_empty (&engine->in_flight)) ++ dconf_changeset_unref ((DConfChangeset *) g_queue_pop_head (&engine->in_flight)); ++ + for (i = 0; i < engine->n_sources; i++) + dconf_engine_source_free (engine->sources[i]); + + g_free (engine->sources); + ++ g_hash_table_unref(engine->pending_paths); ++ g_hash_table_unref(engine->watched_paths); ++ + if (engine->free_func) + engine->free_func (engine->user_data); + +-- +2.20.1 + diff --git a/SOURCES/0001-Engine-track-in-progress-watch-handles-to-avoid-spur.patch b/SOURCES/0001-Engine-track-in-progress-watch-handles-to-avoid-spur.patch new file mode 100644 index 0000000..12ebfd5 --- /dev/null +++ b/SOURCES/0001-Engine-track-in-progress-watch-handles-to-avoid-spur.patch @@ -0,0 +1,307 @@ +From d148fffb58935d69a20dcc42b3ce3d998742f0e7 Mon Sep 17 00:00:00 2001 +From: Daniel Playfair Cal +Date: Fri, 13 Jul 2018 14:47:45 +0000 +Subject: [PATCH] Engine: track in progress watch handles to avoid spurious + changed signals for the root path + +--- + engine/dconf-engine.c | 88 +++++++++++++++++++++++++++++++++++++------ + engine/dconf-engine.h | 11 ++++++ + tests/engine.c | 66 +++++++++++++++++++++++++++++++- + 3 files changed, 151 insertions(+), 14 deletions(-) + +diff --git a/engine/dconf-engine.c b/engine/dconf-engine.c +index bc36e52..c0ff12d 100644 +--- a/engine/dconf-engine.c ++++ b/engine/dconf-engine.c +@@ -158,17 +158,20 @@ struct _DConfEngine + GDestroyNotify free_func; + gint ref_count; + +- GMutex sources_lock; /* This lock is for the sources (ie: refreshing) and state. */ +- guint64 state; /* Counter that changes every time a source is refreshed. */ +- DConfEngineSource **sources; /* Array never changes, but each source changes internally. */ ++ GMutex sources_lock; /* This lock is for the sources (ie: refreshing) and state. */ ++ guint64 state; /* Counter that changes every time a source is refreshed. */ ++ DConfEngineSource **sources; /* Array never changes, but each source changes internally. */ + gint n_sources; + +- GMutex queue_lock; /* This lock is for pending, in_flight, queue_cond */ +- GCond queue_cond; /* Signalled when the queues empty */ +- GQueue pending; /* DConfChangeset */ +- GQueue in_flight; /* DConfChangeset */ ++ GMutex queue_lock; /* This lock is for pending, in_flight, queue_cond */ ++ GCond queue_cond; /* Signalled when the queues empty */ ++ GQueue pending; /* DConfChangeset */ ++ GQueue in_flight; /* DConfChangeset */ + +- gchar *last_handled; /* reply tag from last item in in_flight */ ++ gchar *last_handled; /* reply tag from last item in in_flight */ ++ ++ GHashTable *watched_paths; /* list of paths currently being watched for changes */ ++ GHashTable *pending_paths; /* list of paths waiting to enter watched state */ + }; + + /* When taking the sources lock we check if any of the databases have +@@ -244,6 +247,9 @@ dconf_engine_new (const gchar *profile, + dconf_engine_global_list = g_slist_prepend (dconf_engine_global_list, engine); + g_mutex_unlock (&dconf_engine_global_lock); + ++ engine->watched_paths = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); ++ engine->pending_paths = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); ++ + return engine; + } + +@@ -799,8 +805,9 @@ typedef struct + { + DConfEngineCallHandle handle; + +- guint64 state; +- gint pending; ++ guint64 state; ++ gint pending; ++ gchar *path; + } OutstandingWatch; + + static void +@@ -825,11 +832,13 @@ dconf_engine_watch_established (DConfEngine *engine, + * must have changed while our watch requests were on the wire. + * + * We don't know what changed, so we can just say that potentially +- * everything changed. This case is very rare, anyway... ++ * everything under the path being watched changed. This case is ++ * very rare, anyway... + */ +- dconf_engine_change_notify (engine, "/", changes, NULL, FALSE, NULL, engine->user_data); ++ dconf_engine_change_notify (engine, ow->path, changes, NULL, FALSE, NULL, engine->user_data); + } + ++ dconf_engine_set_watching (engine, ow->path, TRUE, TRUE); + dconf_engine_call_handle_free (handle); + } + +@@ -837,6 +846,15 @@ void + dconf_engine_watch_fast (DConfEngine *engine, + const gchar *path) + { ++ if (dconf_engine_is_watching (engine, path, TRUE)) ++ { ++ /** ++ * Either there is already a match rule in place for this exact path, ++ * or there is already a request in progress to add a match. ++ */ ++ return; ++ } ++ + OutstandingWatch *ow; + gint i; + +@@ -855,6 +873,7 @@ dconf_engine_watch_fast (DConfEngine *engine, + ow = dconf_engine_call_handle_new (engine, dconf_engine_watch_established, + G_VARIANT_TYPE_UNIT, sizeof (OutstandingWatch)); + ow->state = dconf_engine_get_state (engine); ++ ow->path = g_strdup (path); + + /* We start getting async calls returned as soon as we start dispatching them, + * so we must not touch the 'ow' struct after we send the first one. +@@ -869,6 +888,8 @@ dconf_engine_watch_fast (DConfEngine *engine, + "/org/freedesktop/DBus", "org.freedesktop.DBus", "AddMatch", + dconf_engine_make_match_rule (engine->sources[i], path), + &ow->handle, NULL); ++ ++ dconf_engine_set_watching (engine, ow->path, TRUE, FALSE); + } + + void +@@ -882,6 +903,8 @@ dconf_engine_unwatch_fast (DConfEngine *engine, + dconf_engine_dbus_call_async_func (engine->sources[i]->bus_type, "org.freedesktop.DBus", + "/org/freedesktop/DBus", "org.freedesktop.DBus", "RemoveMatch", + dconf_engine_make_match_rule (engine->sources[i], path), NULL, NULL); ++ ++ dconf_engine_set_watching (engine, path, FALSE, FALSE); + } + + static void +@@ -920,6 +943,7 @@ dconf_engine_watch_sync (DConfEngine *engine, + const gchar *path) + { + dconf_engine_handle_match_rule_sync (engine, "AddMatch", path); ++ dconf_engine_set_watching (engine, path, TRUE, TRUE); + } + + void +@@ -927,6 +951,7 @@ dconf_engine_unwatch_sync (DConfEngine *engine, + const gchar *path) + { + dconf_engine_handle_match_rule_sync (engine, "RemoveMatch", path); ++ dconf_engine_set_watching (engine, path, FALSE, FALSE); + } + + typedef struct +@@ -1384,3 +1409,42 @@ dconf_engine_sync (DConfEngine *engine) + g_cond_wait (&engine->queue_cond, &engine->queue_lock); + dconf_engine_unlock_queues (engine); + } ++ ++void ++dconf_engine_set_watching (DConfEngine *engine, ++ const gchar *path, ++ const gboolean is_watching, ++ const gboolean is_established) ++{ ++ if (is_watching) ++ { ++ if (is_established) ++ { ++ g_hash_table_add (engine->watched_paths, g_strdup (path)); ++ g_hash_table_remove (engine->pending_paths, path); ++ } ++ else ++ { ++ g_hash_table_add (engine->pending_paths, g_strdup (path)); ++ g_hash_table_remove (engine->watched_paths, path); ++ } ++ } ++ else ++ { ++ g_hash_table_remove (engine->watched_paths, path); ++ g_hash_table_remove (engine->pending_paths, path); ++ } ++} ++ ++gboolean ++dconf_engine_is_watching (DConfEngine *engine, const gchar *path, const gboolean only_established) ++{ ++ gconstpointer key = (gconstpointer) path; ++ if (g_hash_table_contains (engine->watched_paths, key)) ++ return TRUE; ++ ++ if (!only_established && g_hash_table_contains (engine->pending_paths, key)) ++ return TRUE; ++ ++ return FALSE; ++} +diff --git a/engine/dconf-engine.h b/engine/dconf-engine.h +index 2485423..06ed5a7 100644 +--- a/engine/dconf-engine.h ++++ b/engine/dconf-engine.h +@@ -104,6 +104,17 @@ DConfEngine * dconf_engine_new (const g + G_GNUC_INTERNAL + void dconf_engine_unref (DConfEngine *engine); + ++G_GNUC_INTERNAL ++void dconf_engine_set_watching (DConfEngine *engine, ++ const gchar *path, ++ const gboolean is_watching, ++ const gboolean is_established); ++ ++G_GNUC_INTERNAL ++gboolean dconf_engine_is_watching (DConfEngine *engine, ++ const gchar *path, ++ const gboolean only_established); ++ + /* Read API: always handled immediately */ + G_GNUC_INTERNAL + guint64 dconf_engine_get_state (DConfEngine *engine); +diff --git a/tests/engine.c b/tests/engine.c +index a804b9a..aa1db1c 100644 +--- a/tests/engine.c ++++ b/tests/engine.c +@@ -1153,7 +1153,7 @@ test_watch_fast (void) + DConfEngine *engine; + GvdbTable *table; + GVariant *triv; +- guint64 a, b; ++ guint64 a, b, c; + + change_log = g_string_new (NULL); + +@@ -1202,7 +1202,20 @@ test_watch_fast (void) + dconf_mock_dbus_assert_no_async (); + b = dconf_engine_get_state (engine); + g_assert_cmpuint (a, !=, b); +- g_assert_cmpstr (change_log->str, ==, "/:1::nil;"); ++ g_assert_cmpstr (change_log->str, ==, "/a/b/c:1::nil;"); ++ /* Try to establish a watch again for the same path */ ++ dconf_engine_watch_fast (engine, "/a/b/c"); ++ g_assert (!dconf_engine_has_outstanding (engine)); ++ dconf_engine_sync (engine); ++ c = dconf_engine_get_state (engine); ++ g_assert_cmpuint (b, ==, c); ++ /* The watch result was not sent, because the path was already watched */ ++ dconf_mock_dbus_assert_no_async(); ++ c = dconf_engine_get_state (engine); ++ g_assert_cmpuint (b, ==, c); ++ /* Since the path was already being watched, ++ * do not expect a second false change notification */ ++ g_assert_cmpstr (change_log->str, ==, "/a/b/c:1::nil;"); + dconf_engine_unwatch_fast (engine, "/a/b/c"); + dconf_mock_dbus_async_reply (triv, NULL); + dconf_mock_dbus_async_reply (triv, NULL); +@@ -1273,6 +1286,54 @@ test_watch_sync (void) + match_request_type = NULL; + } + ++static void ++test_watching (void) ++{ ++ DConfEngine *engine; ++ const gchar *apple = "apple"; ++ const gchar *orange = "orange"; ++ const gchar *banana = "banana"; ++ ++ engine = dconf_engine_new (SRCDIR "/profile/dos", NULL, NULL); ++ ++ g_assert (!dconf_engine_is_watching(engine, apple, TRUE)); ++ g_assert (!dconf_engine_is_watching(engine, apple, FALSE)); ++ g_assert (!dconf_engine_is_watching(engine, orange, TRUE)); ++ g_assert (!dconf_engine_is_watching(engine, orange, FALSE)); ++ g_assert (!dconf_engine_is_watching(engine, banana, TRUE)); ++ g_assert (!dconf_engine_is_watching(engine, banana, FALSE)); ++ ++ dconf_engine_set_watching (engine, apple, FALSE, FALSE); ++ dconf_engine_set_watching (engine, orange, TRUE, FALSE); ++ dconf_engine_set_watching (engine, banana, TRUE, TRUE); ++ ++ g_assert (!dconf_engine_is_watching(engine, apple, TRUE)); ++ g_assert (!dconf_engine_is_watching(engine, apple, FALSE)); ++ g_assert (!dconf_engine_is_watching(engine, orange, TRUE)); ++ g_assert (dconf_engine_is_watching(engine, orange, FALSE)); ++ g_assert (dconf_engine_is_watching(engine, banana, TRUE)); ++ g_assert (dconf_engine_is_watching(engine, banana, FALSE)); ++ ++ dconf_engine_set_watching (engine, orange, TRUE, TRUE); ++ dconf_engine_set_watching (engine, banana, FALSE, FALSE); ++ ++ g_assert (!dconf_engine_is_watching(engine, apple, TRUE)); ++ g_assert (!dconf_engine_is_watching(engine, apple, FALSE)); ++ g_assert (dconf_engine_is_watching(engine, orange, TRUE)); ++ g_assert (dconf_engine_is_watching(engine, orange, FALSE)); ++ g_assert (!dconf_engine_is_watching(engine, banana, TRUE)); ++ g_assert (!dconf_engine_is_watching(engine, banana, FALSE)); ++ ++ dconf_engine_set_watching (engine, orange, FALSE, FALSE); ++ ++ g_assert (!dconf_engine_is_watching(engine, apple, TRUE)); ++ g_assert (!dconf_engine_is_watching(engine, apple, FALSE)); ++ g_assert (!dconf_engine_is_watching(engine, orange, TRUE)); ++ g_assert (!dconf_engine_is_watching(engine, orange, FALSE)); ++ g_assert (!dconf_engine_is_watching(engine, banana, TRUE)); ++ g_assert (!dconf_engine_is_watching(engine, banana, FALSE)); ++} ++ + static void + test_change_fast (void) + { +@@ -1758,6 +1819,7 @@ main (int argc, char **argv) + g_test_add_func ("/engine/read", test_read); + g_test_add_func ("/engine/watch/fast", test_watch_fast); + g_test_add_func ("/engine/watch/sync", test_watch_sync); ++ g_test_add_func ("/engine/watch/watching", test_watching); + g_test_add_func ("/engine/change/fast", test_change_fast); + g_test_add_func ("/engine/change/sync", test_change_sync); + g_test_add_func ("/engine/signals", test_signals); +-- +2.20.1 + diff --git a/SOURCES/0002-Engine-extend-subscriptions-state-to-account-for-mul.patch b/SOURCES/0002-Engine-extend-subscriptions-state-to-account-for-mul.patch new file mode 100644 index 0000000..894a69b --- /dev/null +++ b/SOURCES/0002-Engine-extend-subscriptions-state-to-account-for-mul.patch @@ -0,0 +1,405 @@ +From 0986a258cc1df8c1e2aa17a0c2138d178405f902 Mon Sep 17 00:00:00 2001 +From: Daniel Playfair Cal +Date: Wed, 25 Jul 2018 00:52:24 +1000 +Subject: [PATCH 2/5] Engine: extend subscriptions state to account for + multiple client subscriptions to the same path + +Remove accidental whitespace change + +Simplify branching in watch_fast and unwatch_fast + +Indentation fixes + +Store the subscription counts directly in the hash table pointer instead of mallocing ints + +Add documentation comments for new utility functions +--- + engine/dconf-engine.c | 191 ++++++++++++++++++++++++++++-------------- + engine/dconf-engine.h | 11 --- + tests/engine.c | 54 +----------- + 3 files changed, 133 insertions(+), 123 deletions(-) + +diff --git a/engine/dconf-engine.c b/engine/dconf-engine.c +index 2a99eab..1963c34 100644 +--- a/engine/dconf-engine.c ++++ b/engine/dconf-engine.c +@@ -170,8 +170,14 @@ struct _DConfEngine + + gchar *last_handled; /* reply tag from last item in in_flight */ + +- GHashTable *watched_paths; /* list of paths currently being watched for changes */ +- GHashTable *pending_paths; /* list of paths waiting to enter watched state */ ++ /** ++ * establishing and active, are hash tables storing the number ++ * of subscriptions to each path in the two possible states ++ */ ++ /* active on the client side, but awaiting confirmation from the writer */ ++ GHashTable *establishing; ++ /* active on the client side, and with a D-Bus match rule established */ ++ GHashTable *active; + }; + + /* When taking the sources lock we check if any of the databases have +@@ -225,6 +231,78 @@ dconf_engine_unlock_queues (DConfEngine *engine) + g_mutex_unlock (&engine->queue_lock); + } + ++/** ++ * Adds the count of subscriptions to @path in @from_table to the ++ * corresponding count in @to_table, creating it if it did not exist. ++ * Removes the count from @from_table. ++ */ ++static void ++dconf_engine_move_subscriptions (GHashTable *from_counts, ++ GHashTable *to_counts, ++ const gchar *path) ++{ ++ guint from_count = GPOINTER_TO_UINT (g_hash_table_lookup (from_counts, path)); ++ guint old_to_count = GPOINTER_TO_UINT (g_hash_table_lookup (to_counts, path)); ++ // Detect overflows ++ g_assert (old_to_count <= G_MAXUINT32 - from_count); ++ guint new_to_count = old_to_count + from_count; ++ if (from_count != 0) ++ { ++ g_hash_table_remove (from_counts, path); ++ g_hash_table_replace (to_counts, ++ g_strdup (path), ++ GUINT_TO_POINTER (new_to_count)); ++ } ++} ++ ++/** ++ * Increments the reference count for the subscription to @path, or sets ++ * it to 1 if it didn’t previously exist. ++ * Returns the new reference count. ++ */ ++static guint ++dconf_engine_inc_subscriptions (GHashTable *counts, ++ const gchar *path) ++{ ++ guint old_count = GPOINTER_TO_UINT (g_hash_table_lookup (counts, path)); ++ // Detect overflows ++ g_assert (old_count < G_MAXUINT32); ++ guint new_count = old_count + 1; ++ g_hash_table_replace (counts, g_strdup (path), GUINT_TO_POINTER (new_count)); ++ return new_count; ++} ++ ++/** ++ * Decrements the reference count for the subscription to @path, or ++ * removes it if the new value is 0. The count must exist and be greater ++ * than 0. ++ * Returns the new reference count, or 0 if it does not exist. ++ */ ++static guint ++dconf_engine_dec_subscriptions (GHashTable *counts, ++ const gchar *path) ++{ ++ guint old_count = GPOINTER_TO_UINT (g_hash_table_lookup (counts, path)); ++ g_assert (old_count > 0); ++ guint new_count = old_count - 1; ++ if (new_count == 0) ++ g_hash_table_remove (counts, path); ++ else ++ g_hash_table_replace (counts, g_strdup (path), GUINT_TO_POINTER (new_count)); ++ return new_count; ++} ++ ++/** ++ * Returns the reference count for the subscription to @path, or 0 if it ++ * does not exist. ++ */ ++static guint ++dconf_engine_count_subscriptions (GHashTable *counts, ++ const gchar *path) ++{ ++ return GPOINTER_TO_UINT (g_hash_table_lookup (counts, path)); ++} ++ + DConfEngine * + dconf_engine_new (const gchar *profile, + gpointer user_data, +@@ -247,8 +325,14 @@ dconf_engine_new (const gchar *profile, + dconf_engine_global_list = g_slist_prepend (dconf_engine_global_list, engine); + g_mutex_unlock (&dconf_engine_global_lock); + +- engine->watched_paths = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); +- engine->pending_paths = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); ++ engine->establishing = g_hash_table_new_full (g_str_hash, ++ g_str_equal, ++ g_free, ++ NULL); ++ engine->active = g_hash_table_new_full (g_str_hash, ++ g_str_equal, ++ g_free, ++ NULL); + + return engine; + } +@@ -300,8 +384,8 @@ dconf_engine_unref (DConfEngine *engine) + + g_free (engine->sources); + +- g_hash_table_unref(engine->pending_paths); +- g_hash_table_unref(engine->watched_paths); ++ g_hash_table_unref (engine->establishing); ++ g_hash_table_unref (engine->active); + + if (engine->free_func) + engine->free_func (engine->user_data); +@@ -847,7 +931,14 @@ dconf_engine_watch_established (DConfEngine *engine, + dconf_engine_change_notify (engine, ow->path, changes, NULL, FALSE, NULL, engine->user_data); + } + +- dconf_engine_set_watching (engine, ow->path, TRUE, TRUE); ++ guint num_establishing = dconf_engine_count_subscriptions (engine->establishing, ++ ow->path); ++ if (num_establishing > 0) ++ // Subscription(s): establishing -> active ++ dconf_engine_move_subscriptions (engine->establishing, ++ engine->active, ++ ow->path); ++ + dconf_engine_call_handle_free (handle); + } + +@@ -855,14 +946,17 @@ void + dconf_engine_watch_fast (DConfEngine *engine, + const gchar *path) + { +- if (dconf_engine_is_watching (engine, path, TRUE)) +- { +- /** +- * Either there is already a match rule in place for this exact path, +- * or there is already a request in progress to add a match. +- */ +- return; +- } ++ guint num_establishing = dconf_engine_count_subscriptions (engine->establishing, path); ++ guint num_active = dconf_engine_count_subscriptions (engine->active, path); ++ if (num_active > 0) ++ // Subscription: inactive -> active ++ dconf_engine_inc_subscriptions (engine->active, path); ++ else ++ // Subscription: inactive -> establishing ++ num_establishing = dconf_engine_inc_subscriptions (engine->establishing, ++ path); ++ if (num_establishing > 1 || num_active > 0) ++ return; + + OutstandingWatch *ow; + gint i; +@@ -897,23 +991,33 @@ dconf_engine_watch_fast (DConfEngine *engine, + "/org/freedesktop/DBus", "org.freedesktop.DBus", "AddMatch", + dconf_engine_make_match_rule (engine->sources[i], path), + &ow->handle, NULL); +- +- dconf_engine_set_watching (engine, ow->path, TRUE, FALSE); + } + + void + dconf_engine_unwatch_fast (DConfEngine *engine, + const gchar *path) + { ++ guint num_active = dconf_engine_count_subscriptions (engine->active, path); ++ guint num_establishing = dconf_engine_count_subscriptions (engine->establishing, path); + gint i; + ++ // Client code cannot unsubscribe if it is not subscribed ++ g_assert (num_active > 0 || num_establishing > 0); ++ if (num_active == 0) ++ // Subscription: establishing -> inactive ++ num_establishing = dconf_engine_dec_subscriptions (engine->establishing, path); ++ else ++ // Subscription: active -> inactive ++ num_active = dconf_engine_dec_subscriptions (engine->active, path); ++ ++ if (num_active > 0 || num_establishing > 0) ++ return; ++ + for (i = 0; i < engine->n_sources; i++) + if (engine->sources[i]->bus_type) + dconf_engine_dbus_call_async_func (engine->sources[i]->bus_type, "org.freedesktop.DBus", + "/org/freedesktop/DBus", "org.freedesktop.DBus", "RemoveMatch", + dconf_engine_make_match_rule (engine->sources[i], path), NULL, NULL); +- +- dconf_engine_set_watching (engine, path, FALSE, FALSE); + } + + static void +@@ -951,16 +1055,18 @@ void + dconf_engine_watch_sync (DConfEngine *engine, + const gchar *path) + { +- dconf_engine_handle_match_rule_sync (engine, "AddMatch", path); +- dconf_engine_set_watching (engine, path, TRUE, TRUE); ++ guint num_active = dconf_engine_inc_subscriptions (engine->active, path); ++ if (num_active == 1) ++ dconf_engine_handle_match_rule_sync (engine, "AddMatch", path); + } + + void + dconf_engine_unwatch_sync (DConfEngine *engine, + const gchar *path) + { +- dconf_engine_handle_match_rule_sync (engine, "RemoveMatch", path); +- dconf_engine_set_watching (engine, path, FALSE, FALSE); ++ guint num_active = dconf_engine_dec_subscriptions (engine->active, path); ++ if (num_active == 0) ++ dconf_engine_handle_match_rule_sync (engine, "RemoveMatch", path); + } + + typedef struct +@@ -1418,42 +1524,3 @@ dconf_engine_sync (DConfEngine *engine) + g_cond_wait (&engine->queue_cond, &engine->queue_lock); + dconf_engine_unlock_queues (engine); + } +- +-void +-dconf_engine_set_watching (DConfEngine *engine, +- const gchar *path, +- const gboolean is_watching, +- const gboolean is_established) +-{ +- if (is_watching) +- { +- if (is_established) +- { +- g_hash_table_add (engine->watched_paths, g_strdup (path)); +- g_hash_table_remove (engine->pending_paths, path); +- } +- else +- { +- g_hash_table_add (engine->pending_paths, g_strdup (path)); +- g_hash_table_remove (engine->watched_paths, path); +- } +- } +- else +- { +- g_hash_table_remove (engine->watched_paths, path); +- g_hash_table_remove (engine->pending_paths, path); +- } +-} +- +-gboolean +-dconf_engine_is_watching (DConfEngine *engine, const gchar *path, const gboolean only_established) +-{ +- gconstpointer key = (gconstpointer) path; +- if (g_hash_table_contains (engine->watched_paths, key)) +- return TRUE; +- +- if (!only_established && g_hash_table_contains (engine->pending_paths, key)) +- return TRUE; +- +- return FALSE; +-} +diff --git a/engine/dconf-engine.h b/engine/dconf-engine.h +index 06ed5a7..2485423 100644 +--- a/engine/dconf-engine.h ++++ b/engine/dconf-engine.h +@@ -104,17 +104,6 @@ DConfEngine * dconf_engine_new (const g + G_GNUC_INTERNAL + void dconf_engine_unref (DConfEngine *engine); + +-G_GNUC_INTERNAL +-void dconf_engine_set_watching (DConfEngine *engine, +- const gchar *path, +- const gboolean is_watching, +- const gboolean is_established); +- +-G_GNUC_INTERNAL +-gboolean dconf_engine_is_watching (DConfEngine *engine, +- const gchar *path, +- const gboolean only_established); +- + /* Read API: always handled immediately */ + G_GNUC_INTERNAL + guint64 dconf_engine_get_state (DConfEngine *engine); +diff --git a/tests/engine.c b/tests/engine.c +index aa1db1c..038c04c 100644 +--- a/tests/engine.c ++++ b/tests/engine.c +@@ -1210,13 +1210,16 @@ test_watch_fast (void) + c = dconf_engine_get_state (engine); + g_assert_cmpuint (b, ==, c); + /* The watch result was not sent, because the path was already watched */ +- dconf_mock_dbus_assert_no_async(); ++ dconf_mock_dbus_assert_no_async (); + c = dconf_engine_get_state (engine); + g_assert_cmpuint (b, ==, c); + /* Since the path was already being watched, + * do not expect a second false change notification */ + g_assert_cmpstr (change_log->str, ==, "/a/b/c:1::nil;"); + dconf_engine_unwatch_fast (engine, "/a/b/c"); ++ /* nothing was done, because there is still a subscription left */ ++ dconf_mock_dbus_assert_no_async (); ++ dconf_engine_unwatch_fast (engine, "/a/b/c"); + dconf_mock_dbus_async_reply (triv, NULL); + dconf_mock_dbus_async_reply (triv, NULL); + dconf_mock_dbus_assert_no_async (); +@@ -1286,54 +1289,6 @@ test_watch_sync (void) + match_request_type = NULL; + } + +-static void +-test_watching (void) +-{ +- DConfEngine *engine; +- const gchar *apple = "apple"; +- const gchar *orange = "orange"; +- const gchar *banana = "banana"; +- +- engine = dconf_engine_new (SRCDIR "/profile/dos", NULL, NULL); +- +- g_assert (!dconf_engine_is_watching(engine, apple, TRUE)); +- g_assert (!dconf_engine_is_watching(engine, apple, FALSE)); +- g_assert (!dconf_engine_is_watching(engine, orange, TRUE)); +- g_assert (!dconf_engine_is_watching(engine, orange, FALSE)); +- g_assert (!dconf_engine_is_watching(engine, banana, TRUE)); +- g_assert (!dconf_engine_is_watching(engine, banana, FALSE)); +- +- dconf_engine_set_watching (engine, apple, FALSE, FALSE); +- dconf_engine_set_watching (engine, orange, TRUE, FALSE); +- dconf_engine_set_watching (engine, banana, TRUE, TRUE); +- +- g_assert (!dconf_engine_is_watching(engine, apple, TRUE)); +- g_assert (!dconf_engine_is_watching(engine, apple, FALSE)); +- g_assert (!dconf_engine_is_watching(engine, orange, TRUE)); +- g_assert (dconf_engine_is_watching(engine, orange, FALSE)); +- g_assert (dconf_engine_is_watching(engine, banana, TRUE)); +- g_assert (dconf_engine_is_watching(engine, banana, FALSE)); +- +- dconf_engine_set_watching (engine, orange, TRUE, TRUE); +- dconf_engine_set_watching (engine, banana, FALSE, FALSE); +- +- g_assert (!dconf_engine_is_watching(engine, apple, TRUE)); +- g_assert (!dconf_engine_is_watching(engine, apple, FALSE)); +- g_assert (dconf_engine_is_watching(engine, orange, TRUE)); +- g_assert (dconf_engine_is_watching(engine, orange, FALSE)); +- g_assert (!dconf_engine_is_watching(engine, banana, TRUE)); +- g_assert (!dconf_engine_is_watching(engine, banana, FALSE)); +- +- dconf_engine_set_watching (engine, orange, FALSE, FALSE); +- +- g_assert (!dconf_engine_is_watching(engine, apple, TRUE)); +- g_assert (!dconf_engine_is_watching(engine, apple, FALSE)); +- g_assert (!dconf_engine_is_watching(engine, orange, TRUE)); +- g_assert (!dconf_engine_is_watching(engine, orange, FALSE)); +- g_assert (!dconf_engine_is_watching(engine, banana, TRUE)); +- g_assert (!dconf_engine_is_watching(engine, banana, FALSE)); +-} +- + static void + test_change_fast (void) + { +@@ -1819,7 +1774,6 @@ main (int argc, char **argv) + g_test_add_func ("/engine/read", test_read); + g_test_add_func ("/engine/watch/fast", test_watch_fast); + g_test_add_func ("/engine/watch/sync", test_watch_sync); +- g_test_add_func ("/engine/watch/watching", test_watching); + g_test_add_func ("/engine/change/fast", test_change_fast); + g_test_add_func ("/engine/change/sync", test_change_sync); + g_test_add_func ("/engine/signals", test_signals); +-- +2.20.1 + diff --git a/SOURCES/0003-Engine-add-g_debug-statements-in-state-changing-inte.patch b/SOURCES/0003-Engine-add-g_debug-statements-in-state-changing-inte.patch new file mode 100644 index 0000000..cf89374 --- /dev/null +++ b/SOURCES/0003-Engine-add-g_debug-statements-in-state-changing-inte.patch @@ -0,0 +1,101 @@ +From d970e6a07e82449c7d93b0314403af321230e081 Mon Sep 17 00:00:00 2001 +From: Daniel Playfair Cal +Date: Wed, 25 Jul 2018 22:52:49 +1000 +Subject: [PATCH 3/5] Engine: add g_debug statements in state changing + interface functions + +--- + engine/dconf-engine.c | 10 +++++++++- + gsettings/dconfsettingsbackend.c | 1 + + 2 files changed, 10 insertions(+), 1 deletion(-) + +diff --git a/engine/dconf-engine.c b/engine/dconf-engine.c +index 1963c34..2911724 100644 +--- a/engine/dconf-engine.c ++++ b/engine/dconf-engine.c +@@ -928,11 +928,13 @@ dconf_engine_watch_established (DConfEngine *engine, + * everything under the path being watched changed. This case is + * very rare, anyway... + */ ++ g_debug ("SHM invalidated while establishing subscription to %s - signalling change", ow->path); + dconf_engine_change_notify (engine, ow->path, changes, NULL, FALSE, NULL, engine->user_data); + } + + guint num_establishing = dconf_engine_count_subscriptions (engine->establishing, + ow->path); ++ g_debug ("watch_established: \"%s\" (establishing: %d)", ow->path, num_establishing); + if (num_establishing > 0) + // Subscription(s): establishing -> active + dconf_engine_move_subscriptions (engine->establishing, +@@ -948,6 +950,7 @@ dconf_engine_watch_fast (DConfEngine *engine, + { + guint num_establishing = dconf_engine_count_subscriptions (engine->establishing, path); + guint num_active = dconf_engine_count_subscriptions (engine->active, path); ++ g_debug ("watch_fast: \"%s\" (establishing: %d, active: %d)", path, num_establishing, num_active); + if (num_active > 0) + // Subscription: inactive -> active + dconf_engine_inc_subscriptions (engine->active, path); +@@ -1000,6 +1003,7 @@ dconf_engine_unwatch_fast (DConfEngine *engine, + guint num_active = dconf_engine_count_subscriptions (engine->active, path); + guint num_establishing = dconf_engine_count_subscriptions (engine->establishing, path); + gint i; ++ g_debug ("unwatch_fast: \"%s\" (active: %d, establishing: %d)", path, num_active, num_establishing); + + // Client code cannot unsubscribe if it is not subscribed + g_assert (num_active > 0 || num_establishing > 0); +@@ -1056,6 +1060,7 @@ dconf_engine_watch_sync (DConfEngine *engine, + const gchar *path) + { + guint num_active = dconf_engine_inc_subscriptions (engine->active, path); ++ g_debug ("watch_sync: \"%s\" (active: %d)", path, num_active - 1); + if (num_active == 1) + dconf_engine_handle_match_rule_sync (engine, "AddMatch", path); + } +@@ -1065,6 +1070,7 @@ dconf_engine_unwatch_sync (DConfEngine *engine, + const gchar *path) + { + guint num_active = dconf_engine_dec_subscriptions (engine->active, path); ++ g_debug ("unwatch_sync: \"%s\" (active: %d)", path, num_active + 1); + if (num_active == 0) + dconf_engine_handle_match_rule_sync (engine, "RemoveMatch", path); + } +@@ -1274,7 +1280,7 @@ dconf_engine_change_fast (DConfEngine *engine, + GError **error) + { + GList *node; +- ++ g_debug ("change_fast"); + if (dconf_changeset_is_empty (changeset)) + return TRUE; + +@@ -1341,6 +1347,7 @@ dconf_engine_change_sync (DConfEngine *engine, + GError **error) + { + GVariant *reply; ++ g_debug ("change_sync"); + + if (dconf_changeset_is_empty (changeset)) + { +@@ -1519,6 +1526,7 @@ dconf_engine_has_outstanding (DConfEngine *engine) + void + dconf_engine_sync (DConfEngine *engine) + { ++ g_debug ("sync"); + dconf_engine_lock_queues (engine); + while (!g_queue_is_empty (&engine->in_flight)) + g_cond_wait (&engine->queue_cond, &engine->queue_lock); +diff --git a/gsettings/dconfsettingsbackend.c b/gsettings/dconfsettingsbackend.c +index 752e013..6c8179b 100644 +--- a/gsettings/dconfsettingsbackend.c ++++ b/gsettings/dconfsettingsbackend.c +@@ -232,6 +232,7 @@ dconf_engine_change_notify (DConfEngine *engine, + { + GWeakRef *weak_ref = user_data; + DConfSettingsBackend *dcsb; ++ g_debug ("change_notify: %s", prefix); + + dcsb = g_weak_ref_get (weak_ref); + +-- +2.20.1 + diff --git a/SOURCES/0004-Engine-Add-locks-around-access-to-subscription-count.patch b/SOURCES/0004-Engine-Add-locks-around-access-to-subscription-count.patch new file mode 100644 index 0000000..ea2db6b --- /dev/null +++ b/SOURCES/0004-Engine-Add-locks-around-access-to-subscription-count.patch @@ -0,0 +1,167 @@ +From 21711aa40bed4e61bba7d5f9fee141825fe76823 Mon Sep 17 00:00:00 2001 +From: Daniel Playfair Cal +Date: Thu, 26 Jul 2018 00:00:09 +1000 +Subject: [PATCH 4/5] Engine: Add locks around access to subscription counts to + ensure that each state transition is atomic + +Update comment about threading, documenting the new lock + +Add documentation comments for new utility functions +--- + engine/dconf-engine.c | 47 ++++++++++++++++++++++++++++++++++++++++--- + 1 file changed, 44 insertions(+), 3 deletions(-) + +diff --git a/engine/dconf-engine.c b/engine/dconf-engine.c +index 2911724..ad891e6 100644 +--- a/engine/dconf-engine.c ++++ b/engine/dconf-engine.c +@@ -128,7 +128,7 @@ + * it is willing to deal with receiving the change notifies in those + * threads. + * +- * Thread-safety is implemented using two locks. ++ * Thread-safety is implemented using three locks. + * + * The first lock (sources_lock) protects the sources. Although the + * sources are only ever read from, it is necessary to lock them because +@@ -143,8 +143,15 @@ + * The second lock (queue_lock) protects the various queues that are + * used to implement the "fast" writes described above. + * +- * If both locks are held at the same time then the sources lock must +- * have been acquired first. ++ * The third lock (subscription_count_lock) protects the two hash tables ++ * that are used to keep track of the number of subscriptions held by ++ * the client library to each path. ++ * ++ * If sources_lock and queue_lock are held at the same time then then ++ * sources_lock must have been acquired first. ++ * ++ * subscription_count_lock is never held at the same time as ++ * sources_lock or queue_lock + */ + + #define MAX_IN_FLIGHT 2 +@@ -174,6 +181,8 @@ struct _DConfEngine + * establishing and active, are hash tables storing the number + * of subscriptions to each path in the two possible states + */ ++ /* This lock ensures that transactions involving subscription counts are atomic */ ++ GMutex subscription_count_lock; + /* active on the client side, but awaiting confirmation from the writer */ + GHashTable *establishing; + /* active on the client side, and with a D-Bus match rule established */ +@@ -303,6 +312,25 @@ dconf_engine_count_subscriptions (GHashTable *counts, + return GPOINTER_TO_UINT (g_hash_table_lookup (counts, path)); + } + ++/** ++ * Acquires the subscription counts lock, which must be held when ++ * reading or writing to the subscription counts. ++ */ ++static void ++dconf_engine_lock_subscription_counts (DConfEngine *engine) ++{ ++ g_mutex_lock (&engine->subscription_count_lock); ++} ++ ++/** ++ * Releases the subscription counts lock ++ */ ++static void ++dconf_engine_unlock_subscription_counts (DConfEngine *engine) ++{ ++ g_mutex_unlock (&engine->subscription_count_lock); ++} ++ + DConfEngine * + dconf_engine_new (const gchar *profile, + gpointer user_data, +@@ -325,6 +353,7 @@ dconf_engine_new (const gchar *profile, + dconf_engine_global_list = g_slist_prepend (dconf_engine_global_list, engine); + g_mutex_unlock (&dconf_engine_global_lock); + ++ g_mutex_init (&engine->subscription_count_lock); + engine->establishing = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, +@@ -387,6 +416,8 @@ dconf_engine_unref (DConfEngine *engine) + g_hash_table_unref (engine->establishing); + g_hash_table_unref (engine->active); + ++ g_mutex_clear (&engine->subscription_count_lock); ++ + if (engine->free_func) + engine->free_func (engine->user_data); + +@@ -932,6 +963,7 @@ dconf_engine_watch_established (DConfEngine *engine, + dconf_engine_change_notify (engine, ow->path, changes, NULL, FALSE, NULL, engine->user_data); + } + ++ dconf_engine_lock_subscription_counts (engine); + guint num_establishing = dconf_engine_count_subscriptions (engine->establishing, + ow->path); + g_debug ("watch_established: \"%s\" (establishing: %d)", ow->path, num_establishing); +@@ -941,6 +973,7 @@ dconf_engine_watch_established (DConfEngine *engine, + engine->active, + ow->path); + ++ dconf_engine_unlock_subscription_counts (engine); + dconf_engine_call_handle_free (handle); + } + +@@ -948,6 +981,7 @@ void + dconf_engine_watch_fast (DConfEngine *engine, + const gchar *path) + { ++ dconf_engine_lock_subscription_counts (engine); + guint num_establishing = dconf_engine_count_subscriptions (engine->establishing, path); + guint num_active = dconf_engine_count_subscriptions (engine->active, path); + g_debug ("watch_fast: \"%s\" (establishing: %d, active: %d)", path, num_establishing, num_active); +@@ -958,6 +992,7 @@ dconf_engine_watch_fast (DConfEngine *engine, + // Subscription: inactive -> establishing + num_establishing = dconf_engine_inc_subscriptions (engine->establishing, + path); ++ dconf_engine_unlock_subscription_counts (engine); + if (num_establishing > 1 || num_active > 0) + return; + +@@ -1000,6 +1035,7 @@ void + dconf_engine_unwatch_fast (DConfEngine *engine, + const gchar *path) + { ++ dconf_engine_lock_subscription_counts (engine); + guint num_active = dconf_engine_count_subscriptions (engine->active, path); + guint num_establishing = dconf_engine_count_subscriptions (engine->establishing, path); + gint i; +@@ -1014,6 +1050,7 @@ dconf_engine_unwatch_fast (DConfEngine *engine, + // Subscription: active -> inactive + num_active = dconf_engine_dec_subscriptions (engine->active, path); + ++ dconf_engine_unlock_subscription_counts (engine); + if (num_active > 0 || num_establishing > 0) + return; + +@@ -1059,7 +1096,9 @@ void + dconf_engine_watch_sync (DConfEngine *engine, + const gchar *path) + { ++ dconf_engine_lock_subscription_counts (engine); + guint num_active = dconf_engine_inc_subscriptions (engine->active, path); ++ dconf_engine_unlock_subscription_counts (engine); + g_debug ("watch_sync: \"%s\" (active: %d)", path, num_active - 1); + if (num_active == 1) + dconf_engine_handle_match_rule_sync (engine, "AddMatch", path); +@@ -1069,7 +1108,9 @@ void + dconf_engine_unwatch_sync (DConfEngine *engine, + const gchar *path) + { ++ dconf_engine_lock_subscription_counts (engine); + guint num_active = dconf_engine_dec_subscriptions (engine->active, path); ++ dconf_engine_unlock_subscription_counts (engine); + g_debug ("unwatch_sync: \"%s\" (active: %d)", path, num_active + 1); + if (num_active == 0) + dconf_engine_handle_match_rule_sync (engine, "RemoveMatch", path); +-- +2.20.1 + diff --git a/SOURCES/0005-Engine-Add-comprehensive-unit-tests-for-subscription.patch b/SOURCES/0005-Engine-Add-comprehensive-unit-tests-for-subscription.patch new file mode 100644 index 0000000..af02660 --- /dev/null +++ b/SOURCES/0005-Engine-Add-comprehensive-unit-tests-for-subscription.patch @@ -0,0 +1,252 @@ +From 8760833820e03a21900afda2d2f5785610c59ac9 Mon Sep 17 00:00:00 2001 +From: Daniel Playfair Cal +Date: Sat, 28 Jul 2018 13:38:00 +1000 +Subject: [PATCH 5/5] Engine: Add comprehensive unit tests for subscription + counting behaviour + +Use g_assert_false instead of g_assert in unit tests +--- + tests/engine.c | 206 +++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 206 insertions(+) + +diff --git a/tests/engine.c b/tests/engine.c +index 038c04c..f2e57b2 100644 +--- a/tests/engine.c ++++ b/tests/engine.c +@@ -1232,6 +1232,185 @@ test_watch_fast (void) + g_variant_unref (triv); + } + ++static void ++test_watch_fast_simultaneous_subscriptions (void) ++{ ++ /** ++ * Test that creating multiple subscriptions to the same path ++ * simultaneously (before receiving replies from D-Bus) only results in ++ * a single D-Bus match rule, and that it is removed at the right time. ++ */ ++ DConfEngine *engine; ++ GvdbTable *table; ++ GVariant *triv; ++ ++ /* Set up */ ++ table = dconf_mock_gvdb_table_new (); ++ dconf_mock_gvdb_install ("/HOME/.config/dconf/user", table); ++ table = dconf_mock_gvdb_table_new (); ++ dconf_mock_gvdb_install ("/etc/dconf/db/site", table); ++ ++ triv = g_variant_ref_sink (g_variant_new ("()")); ++ ++ change_log = g_string_new (NULL); ++ ++ engine = dconf_engine_new (SRCDIR "/profile/dos", NULL, NULL); ++ ++ ++ /* Subscribe to the same path 3 times. Both AddMatch results succeed ++ * (one for each source). There is only one for each source*path. ++ */ ++ dconf_engine_watch_fast (engine, "/a/b/c"); ++ dconf_engine_watch_fast (engine, "/a/b/c"); ++ dconf_engine_watch_fast (engine, "/a/b/c"); ++ ++ dconf_mock_dbus_async_reply (triv, NULL); ++ dconf_mock_dbus_async_reply (triv, NULL); ++ dconf_mock_dbus_assert_no_async (); ++ ++ /* Unsubscribe twice, after the AddMatch succeeds. There is still one ++ * active subscription, so no RemoveMatch request is sent. */ ++ dconf_engine_unwatch_fast (engine, "/a/b/c"); ++ dconf_engine_unwatch_fast (engine, "/a/b/c"); ++ ++ dconf_mock_dbus_assert_no_async (); ++ ++ /* Unsubscribe once more. The number of active subscriptions falls to 0 ++ * and the D-Bus match rule is removed */ ++ dconf_engine_unwatch_fast (engine, "/a/b/c"); ++ ++ dconf_mock_dbus_async_reply (triv, NULL); ++ dconf_mock_dbus_async_reply (triv, NULL); ++ dconf_mock_dbus_assert_no_async (); ++ ++ /* The shm was not flagged at any point - so no change notifications ++ * should not have been sent */ ++ g_assert_cmpstr (change_log->str, ==, ""); ++ ++ /* Clean up */ ++ dconf_engine_unref (engine); ++ g_string_free (change_log, TRUE); ++ change_log = NULL; ++ g_variant_unref (triv); ++} ++ ++static void ++test_watch_fast_successive_subscriptions (void) ++{ ++ /** ++ * Test that subscribing to the same path multiple times successively ++ * (after waiting for any expected replies from D-Bus) results in only ++ * a single D-Bus match rule being created, and that it is created and ++ * destroyed at the right times. ++ */ ++ DConfEngine *engine; ++ GvdbTable *table; ++ GVariant *triv; ++ ++ /* Set up */ ++ table = dconf_mock_gvdb_table_new (); ++ dconf_mock_gvdb_install ("/HOME/.config/dconf/user", table); ++ table = dconf_mock_gvdb_table_new (); ++ dconf_mock_gvdb_install ("/etc/dconf/db/site", table); ++ ++ triv = g_variant_ref_sink (g_variant_new ("()")); ++ ++ change_log = g_string_new (NULL); ++ ++ engine = dconf_engine_new (SRCDIR "/profile/dos", NULL, NULL); ++ ++ /* Subscribe to a path, and simulate a change to the database while the ++ * AddMatch request is in progress */ ++ dconf_engine_watch_fast (engine, "/a/b/c"); ++ dconf_mock_shm_flag ("user"); ++ dconf_mock_dbus_async_reply (triv, NULL); ++ dconf_mock_dbus_async_reply (triv, NULL); ++ ++ /* When the AddMatch requests succeeds, expect a change notification ++ * for the path */ ++ dconf_mock_dbus_assert_no_async (); ++ g_assert_cmpstr (change_log->str, ==, "/a/b/c:1::nil;"); ++ ++ /* Subscribe to a path twice again, and simulate a change to the ++ * database */ ++ dconf_engine_watch_fast (engine, "/a/b/c"); ++ dconf_engine_watch_fast (engine, "/a/b/c"); ++ dconf_mock_shm_flag ("user"); ++ ++ /* There was already a match rule in place, so there should be no D-Bus ++ * requests and no change notifications */ ++ dconf_mock_dbus_assert_no_async (); ++ g_assert_cmpstr (change_log->str, ==, "/a/b/c:1::nil;"); ++ ++ /* Unsubscribe */ ++ dconf_engine_unwatch_fast (engine, "/a/b/c"); ++ dconf_engine_unwatch_fast (engine, "/a/b/c"); ++ dconf_mock_dbus_assert_no_async (); ++ dconf_engine_unwatch_fast (engine, "/a/b/c"); ++ dconf_mock_dbus_async_reply (triv, NULL); ++ dconf_mock_dbus_async_reply (triv, NULL); ++ dconf_mock_dbus_assert_no_async (); ++ ++ ++ /* Clean up */ ++ dconf_engine_unref (engine); ++ g_string_free (change_log, TRUE); ++ change_log = NULL; ++ g_variant_unref (triv); ++} ++ ++static void ++test_watch_fast_short_lived_subscriptions (void) ++{ ++ /** ++ * Test that subscribing and then immediately unsubscribing (without ++ * waiting for replies from D-Bus) multiple times to the same path ++ * correctly triggers D-Bus requests and change notifications in cases ++ * where the D-Bus match rule was not in place when the database was ++ * changed. ++ */ ++ DConfEngine *engine; ++ GvdbTable *table; ++ GVariant *triv; ++ ++ /* Set up */ ++ table = dconf_mock_gvdb_table_new (); ++ dconf_mock_gvdb_install ("/HOME/.config/dconf/user", table); ++ table = dconf_mock_gvdb_table_new (); ++ dconf_mock_gvdb_install ("/etc/dconf/db/site", table); ++ ++ triv = g_variant_ref_sink (g_variant_new ("()")); ++ ++ change_log = g_string_new (NULL); ++ ++ engine = dconf_engine_new (SRCDIR "/profile/dos", NULL, NULL); ++ ++ /* Subscribe to a path twice, and simulate a change to the database ++ * while the AddMatch request is in progress */ ++ dconf_engine_watch_fast (engine, "/a/b/c"); ++ dconf_engine_watch_fast (engine, "/a/b/c"); ++ dconf_mock_shm_flag ("user"); ++ dconf_engine_unwatch_fast (engine, "/a/b/c"); ++ dconf_engine_unwatch_fast (engine, "/a/b/c"); ++ dconf_mock_dbus_async_reply (triv, NULL); ++ dconf_mock_dbus_async_reply (triv, NULL); ++ dconf_mock_dbus_async_reply (triv, NULL); ++ dconf_mock_dbus_async_reply (triv, NULL); ++ dconf_mock_dbus_assert_no_async (); ++ ++ /* When the AddMatch requests succeed, expect a change notification ++ * to have been sent for the path, even though the client has since ++ * unsubscribed. */ ++ g_assert_cmpstr (change_log->str, ==, "/a/b/c:1::nil;"); ++ ++ ++ /* Clean up */ ++ dconf_engine_unref (engine); ++ g_string_free (change_log, TRUE); ++ change_log = NULL; ++ g_variant_unref (triv); ++} ++ + static const gchar *match_request_type; + static gboolean got_match_request[5]; + +@@ -1270,13 +1449,37 @@ test_watch_sync (void) + engine = dconf_engine_new (SRCDIR "/profile/dos", NULL, NULL); + + match_request_type = "AddMatch"; ++ ++ /* A match rule should be added when the first subscription is established */ + dconf_engine_watch_sync (engine, "/a/b/c"); + g_assert (got_match_request[G_BUS_TYPE_SESSION]); + g_assert (got_match_request[G_BUS_TYPE_SYSTEM]); + got_match_request[G_BUS_TYPE_SESSION] = FALSE; + got_match_request[G_BUS_TYPE_SYSTEM] = FALSE; + ++ /* The match rule is now already in place, so more are not needed */ ++ dconf_engine_watch_sync (engine, "/a/b/c"); ++ g_assert_false (got_match_request[G_BUS_TYPE_SESSION]); ++ g_assert_false (got_match_request[G_BUS_TYPE_SYSTEM]); ++ ++ dconf_engine_watch_sync (engine, "/a/b/c"); ++ g_assert_false (got_match_request[G_BUS_TYPE_SESSION]); ++ g_assert_false (got_match_request[G_BUS_TYPE_SYSTEM]); ++ + match_request_type = "RemoveMatch"; ++ ++ /* There are 3 subscriptions, so removing 2 should not remove ++ * the match rule */ ++ dconf_engine_unwatch_sync (engine, "/a/b/c"); ++ g_assert_false (got_match_request[G_BUS_TYPE_SESSION]); ++ g_assert_false (got_match_request[G_BUS_TYPE_SYSTEM]); ++ ++ dconf_engine_unwatch_sync (engine, "/a/b/c"); ++ g_assert_false (got_match_request[G_BUS_TYPE_SESSION]); ++ g_assert_false (got_match_request[G_BUS_TYPE_SYSTEM]); ++ ++ /* The match rule should be removed when the last subscription is ++ * removed */ + dconf_engine_unwatch_sync (engine, "/a/b/c"); + g_assert (got_match_request[G_BUS_TYPE_SESSION]); + g_assert (got_match_request[G_BUS_TYPE_SYSTEM]); +@@ -1773,6 +1976,9 @@ main (int argc, char **argv) + g_test_add_func ("/engine/sources/service", test_service_source); + g_test_add_func ("/engine/read", test_read); + g_test_add_func ("/engine/watch/fast", test_watch_fast); ++ g_test_add_func ("/engine/watch/fast/simultaneous", test_watch_fast_simultaneous_subscriptions); ++ g_test_add_func ("/engine/watch/fast/successive", test_watch_fast_successive_subscriptions); ++ g_test_add_func ("/engine/watch/fast/short_lived", test_watch_fast_short_lived_subscriptions); + g_test_add_func ("/engine/watch/sync", test_watch_sync); + g_test_add_func ("/engine/change/fast", test_change_fast); + g_test_add_func ("/engine/change/sync", test_change_sync); +-- +2.20.1 + diff --git a/SOURCES/dconf-0.28.0-db-mtime.patch b/SOURCES/dconf-0.28.0-db-mtime.patch new file mode 100644 index 0000000..69fe241 --- /dev/null +++ b/SOURCES/dconf-0.28.0-db-mtime.patch @@ -0,0 +1,77 @@ +From 6a6797446f13378035a2700253546b524d629c8a Mon Sep 17 00:00:00 2001 +From: Marek Kasik +Date: Tue, 10 Jul 2018 18:29:16 +0200 +Subject: [PATCH] Check mtimes of files when updating databases + +Do not check just mtimes of directories in /etc/dconf/db/ +but also mtimes of the files in those directories +to catch all modifications in them. + +https://bugzilla.gnome.org/show_bug.cgi?id=708258 +--- + bin/dconf-update.vala | 41 ++++++++++++++++++++++++++++++++++------- + 1 file changed, 34 insertions(+), 7 deletions(-) + +diff --git a/bin/dconf-update.vala b/bin/dconf-update.vala +index d452092..5aac6c7 100644 +--- a/bin/dconf-update.vala ++++ b/bin/dconf-update.vala +@@ -162,21 +162,48 @@ Gvdb.HashTable read_directory (string dirname) throws GLib.Error { + return table; + } + ++time_t get_directory_mtime (string dirname, Posix.Stat dir_buf) throws GLib.Error { ++ Posix.Stat lockdir_buf; ++ Posix.Stat file_buf; ++ time_t dir_mtime = dir_buf.st_mtime; ++ ++ var files = list_directory (dirname, Posix.S_IFREG); ++ files.sort (strcmp); ++ files.reverse (); ++ ++ foreach (var filename in files) { ++ if (Posix.stat (filename, out file_buf) == 0 && file_buf.st_mtime > dir_mtime) ++ dir_mtime = file_buf.st_mtime; ++ } ++ ++ if (Posix.stat (dirname + "/locks", out lockdir_buf) == 0 && Posix.S_ISDIR (lockdir_buf.st_mode)) { ++ if (lockdir_buf.st_mtime > dir_mtime) { ++ // if the lock directory has been updated more recently then consider its timestamp instead ++ dir_mtime = lockdir_buf.st_mtime; ++ } ++ ++ files = list_directory (dirname + "/locks", Posix.S_IFREG); ++ files.sort (strcmp); ++ files.reverse (); ++ ++ foreach (var filename in files) { ++ if (Posix.stat (filename, out file_buf) == 0 && file_buf.st_mtime > dir_mtime) ++ dir_mtime = file_buf.st_mtime; ++ } ++ } ++ ++ return dir_mtime; ++} ++ + void maybe_update_from_directory (string dirname) throws GLib.Error { + Posix.Stat dir_buf; + + if (Posix.stat (dirname, out dir_buf) == 0 && Posix.S_ISDIR (dir_buf.st_mode)) { +- Posix.Stat lockdir_buf; + Posix.Stat file_buf; + + var filename = dirname.substring (0, dirname.length - 2); + +- if (Posix.stat (dirname + "/locks", out lockdir_buf) == 0 && lockdir_buf.st_mtime > dir_buf.st_mtime) { +- // if the lock directory has been updated more recently then consider its timestamp instead +- dir_buf.st_mtime = lockdir_buf.st_mtime; +- } +- +- if (Posix.stat (filename, out file_buf) == 0 && file_buf.st_mtime > dir_buf.st_mtime) { ++ if (Posix.stat (filename, out file_buf) == 0 && file_buf.st_mtime > get_directory_mtime (dirname, dir_buf)) { + return; + } + +-- +2.17.1 + diff --git a/SPECS/dconf.spec b/SPECS/dconf.spec new file mode 100644 index 0000000..ed00543 --- /dev/null +++ b/SPECS/dconf.spec @@ -0,0 +1,392 @@ +%define glib2_version 2.44.0 + +Name: dconf +Version: 0.28.0 +Release: 3%{?dist} +Summary: A configuration system + +License: LGPLv2+ and GPLv2+ and GPLv3+ +URL: http://live.gnome.org/dconf +Source0: http://download.gnome.org/sources/dconf/0.28/dconf-%{version}.tar.xz + +Patch0: dconf-0.28.0-db-mtime.patch +Patch1: 0001-Engine-track-in-progress-watch-handles-to-avoid-spur.patch +Patch2: 0001-Engine-add-some-missing-objects-to-dconf_engine_unre.patch +Patch3: 0002-Engine-extend-subscriptions-state-to-account-for-mul.patch +Patch4: 0003-Engine-add-g_debug-statements-in-state-changing-inte.patch +Patch5: 0004-Engine-Add-locks-around-access-to-subscription-count.patch +Patch6: 0005-Engine-Add-comprehensive-unit-tests-for-subscription.patch +Patch7: 0001-Engine-Change-overflow-thresholds-in-subscription-co.patch + +BuildRequires: glib2-devel >= %{glib2_version} +BuildRequires: gtk-doc +BuildRequires: meson +BuildRequires: vala + +Requires: dbus +Requires: glib2%{?_isa} >= %{glib2_version} + +%description +dconf is a low-level configuration system. Its main purpose is to provide a +backend to the GSettings API in GLib. + +%package devel +Summary: Header files and libraries for dconf development +Requires: %{name}%{?_isa} = %{version}-%{release} + +%description devel +dconf development package. Contains files needed for doing software +development using dconf. + +%prep +%autosetup -p1 + +%build +%meson -Denable-gtk-doc=true +%meson_build + +%install +%meson_install + +mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/dconf/profile + +cat << EOF > $RPM_BUILD_ROOT%{_sysconfdir}/dconf/profile/user +user-db:user +system-db:local +system-db:site +system-db:distro +EOF + +mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/dconf/db/local.d/locks +mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/dconf/db/site.d/locks +mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/dconf/db/distro.d/locks + +%posttrans +dconf update + +%files +%license COPYING +%dir %{_sysconfdir}/dconf +%dir %{_sysconfdir}/dconf/db +%dir %{_sysconfdir}/dconf/db/local.d +%dir %{_sysconfdir}/dconf/db/local.d/locks +%dir %{_sysconfdir}/dconf/db/site.d +%dir %{_sysconfdir}/dconf/db/site.d/locks +%dir %{_sysconfdir}/dconf/db/distro.d +%dir %{_sysconfdir}/dconf/db/distro.d/locks +%dir %{_sysconfdir}/dconf/profile +%{_libdir}/gio/modules/libdconfsettings.so +%{_libexecdir}/dconf-service +%{_datadir}/dbus-1/services/ca.desrt.dconf.service +%{_bindir}/dconf +%{_libdir}/libdconf.so.1* +%{_datadir}/bash-completion/completions/dconf +%{_mandir}/man1/dconf-service.1.gz +%{_mandir}/man1/dconf.1.gz +%{_mandir}/man7/dconf.7.gz +%config(noreplace) %{_sysconfdir}/dconf/profile/user + +%files devel +%{_includedir}/dconf +%{_libdir}/libdconf.so +%{_libdir}/pkgconfig/dconf.pc +%dir %{_datadir}/gtk-doc +%dir %{_datadir}/gtk-doc/html +%{_datadir}/gtk-doc/html/dconf +%{_datadir}/vala + +%changelog +* Fri Jan 18 2019 Olivier Fourdan - 0.28.0-3 +- Backport fix for emission of spurious changed signals +- for all keys at once crashing gnome-shell +- Resolves: #1666176 + +* Wed Jul 25 2018 Marek Kasik - 0.28.0-2 +- Check mtimes of files in /etc/dconf/db/*.d/ directories +- when running "dconf update" +- Related: #1570569 + +* Tue Mar 13 2018 Kalev Lember - 0.28.0-1 +- Update to 0.28.0 + +* Mon Mar 12 2018 Kalev Lember - 0.27.1-1 +- Update to 0.27.1 +- Switch to the meson build system +- Don't set group tags +- Remove obsolete rpm scriptlets +- Fix gtk-doc directory ownership +- Tighten soname glob + +* Mon Feb 19 2018 Ray Strode - 0.26.1-3 +- Add systemd dbs for distro, site, and machine local dconf databases + Resolves: #1546644 + +* Wed Feb 07 2018 Fedora Release Engineering - 0.26.1-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild + +* Mon Oct 09 2017 Kalev Lember - 0.26.1-1 +- Update to 0.26.1 + +* Wed Aug 02 2017 Fedora Release Engineering - 0.26.0-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild + +* Wed Jul 26 2017 Fedora Release Engineering - 0.26.0-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild + +* Tue Mar 21 2017 Colin Walters - 0.26.0-3 +- Backport patch to work around gtype threading + +* Fri Feb 10 2017 Fedora Release Engineering - 0.26.0-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild + +* Wed Mar 23 2016 Kalev Lember - 0.26.0-1 +- Update to 0.26.0 + +* Wed Feb 03 2016 Fedora Release Engineering - 0.25.1-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild + +* Wed Dec 16 2015 Kalev Lember - 0.25.1-1 +- Update to 0.25.1 + +* Wed Jun 17 2015 Fedora Release Engineering - 0.24.0-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild + +* Mon Mar 23 2015 Kalev Lember - 0.24.0-1 +- Update to 0.24.0 + +* Tue Mar 17 2015 Kalev Lember - 0.23.2-1 +- Update to 0.23.2 + +* Mon Mar 02 2015 Kalev Lember - 0.23.1-1 +- Update to 0.23.1 +- This drops the -editor subpackage which now lives in a separate + dconf-editor SRPM. +- Use the %%license macro for the COPYING file + +* Sat Feb 21 2015 Till Maas - 0.22.0-2 +- Rebuilt for Fedora 23 Change + https://fedoraproject.org/wiki/Changes/Harden_all_packages_with_position-independent_code + +* Fri Sep 19 2014 Kalev Lember - 0.22.0-1 +- Update to 0.22.0 + +* Sat Aug 16 2014 Fedora Release Engineering - 0.21.0-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_22_Mass_Rebuild + +* Tue Jul 22 2014 Kalev Lember - 0.21.0-1 +- Update to 0.21.0 + +* Fri Jul 11 2014 Parag - 0.20.0-4 +- Fix the directory ownership (rh#1056020) + +* Sat Jun 07 2014 Fedora Release Engineering - 0.20.0-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild + +* Sat Apr 05 2014 Kalev Lember - 0.20.0-2 +- Specify minimum glib version + +* Mon Mar 24 2014 Richard Hughes - 0.20.0-1 +- Update to 0.20.0 + +* Tue Mar 18 2014 Richard Hughes - 0.19.92-1 +- Update to 0.19.92 + +* Tue Mar 04 2014 Richard Hughes - 0.19.91-1 +- Update to 0.19.91 + +* Tue Feb 18 2014 Richard Hughes - 0.19.90-1 +- Update to 0.19.90 + +* Tue Jan 14 2014 Richard Hughes - 0.19.3-1 +- Update to 0.19.3 + +* Thu Nov 14 2013 Richard Hughes - 0.19.2-1 +- Update to 0.19.2 + +* Thu Sep 26 2013 Kalev Lember - 0.18.0-2 +- Add missing glib-compile-schemas scriptlets to the -editor subpackage + +* Tue Sep 24 2013 Kalev Lember - 0.18.0-1 +- Update to 0.18.0 + +* Wed Sep 18 2013 Kalev Lember - 0.17.1-1 +- Update to 0.17.1 + +* Mon Aug 05 2013 Parag Nemade - 0.17.0-3 +- Fix bogus date in %%changelog +- Compilation should be more verbose, add V=1 +- Upstream does not install dconf-editor ui files + +* Sat Aug 03 2013 Fedora Release Engineering - 0.17.0-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_20_Mass_Rebuild + +* Tue Jul 16 2013 Richard Hughes - 0.17.0-1 +- Update to 0.17.0 + +* Sat Jun 8 2013 Matthias Clasen - 0.16.0-2 +- Move the editor schema to the right subpackage + +* Mon Mar 25 2013 Kalev Lember - 0.16.0-1 +- Update to 0.16.0 + +* Mon Feb 11 2013 Kalev Lember - 0.15.3-1 +- Update to 0.15.3 +- Install the HighContrast icons and update the icon cache scriptlets to take + this into account + +* Sat Dec 22 2012 Rex Dieter - 0.15.2-2 +- -devel: drop Requires: glib2-devel, already gets pulled in via pkgconfig deps +- -editor: add icon scriptlets +- tighten subpkg deps via %%{_isa} + +* Tue Nov 20 2012 Richard Hughes - 0.15.2-1 +- Update to 0.15.2 + +* Fri Nov 09 2012 Kalev Lember - 0.15.0-3 +- Move some of the rpm scriptlets back to %%posttrans + (glib-compile-schemas, icon cache) + +* Thu Nov 8 2012 Marek Kasik - 0.15.0-2 +- Move dconf-editor's man page to the dconf-editor sub-package + +* Wed Nov 7 2012 Marek Kasik - 0.15.0-1 +- Update to 0.15.0 +- Remove upstreamed patch + +* Wed Nov 7 2012 Marek Kasik - 0.14.0-4 +- Move %%posttrans commands to %%post (rpmlint related) + +* Wed Nov 7 2012 Marek Kasik - 0.14.0-3 +- Update License field +- Update Source URL +- Add link of corresponding bug for the memory leak patch + +* Wed Nov 7 2012 Marek Kasik - 0.14.0-2.1 +- Merge spec-file fixes from f18 branch + +* Sun Oct 21 2012 Matthias Clasen - 0.14.0-2 +- Fix a memory leak +- Update to 0.14.0 + +* Wed Jul 18 2012 Fedora Release Engineering - 0.13.4-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_18_Mass_Rebuild + +* Tue Jul 17 2012 Richard Hughes - 0.13.4-1 +- Update to 0.13.4 + +* Thu Jun 07 2012 Richard Hughes - 0.13.0-2 +- Add missing file to file list. + +* Thu Jun 07 2012 Richard Hughes - 0.13.0-1 +- Update to 0.13.0 + +* Sat May 05 2012 Kalev Lember - 0.12.1-1 +- Update to 0.12.1 + +* Tue Mar 27 2012 Kalev Lember - 0.12.0-1 +- Update to 0.12.0 + +* Tue Mar 20 2012 Kalev Lember - 0.11.7-1 +- Update to 0.11.7 + +* Fri Mar 9 2012 Matthias Clasen - 0.11.6-1 +- Update to 0.11.6 + +* Mon Feb 6 2012 Matthias Clasen - 0.11.5-1 +- Update to 0.11.5 + +* Fri Jan 13 2012 Fedora Release Engineering - 0.11.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_17_Mass_Rebuild + +* Mon Nov 21 2011 Matthias Clasen - 0.11.2-1 +- Update to 0.11.2 + +* Fri Nov 4 2011 Matthias Clasen - 0.11.0-2 +- Fix a typo (#710700) + +* Wed Nov 2 2011 Matthias Clasen - 0.11.0-1 +- Update to 0.11.0 + +* Mon Sep 26 2011 Ray - 0.10.0-1 +- Update to 0.10.0 + +* Mon Sep 19 2011 Matthias Clasen - 0.9.1-1 +- Update to 0.9.1 + +* Tue Jul 26 2011 Matthias Clasen - 0.9.0-1 +- Update to 0.9.0 + +* Wed May 11 2011 Tomas Bzatek - 0.7.5-1 +- Update to 0.7.5 + +* Fri May 6 2011 Matthias Clasen - 0.7.4-1 +- Update to 0.7.4 + +* Wed Apr 6 2011 Matthias Clasen - 0.7.3-2 +- Fix a crash in dconf-editor + +* Tue Mar 22 2011 Tomas Bzatek - 0.7.3-1 +- Update to 0.7.3 + +* Thu Feb 10 2011 Matthias Clasen - 0.7.2-3 +- Rebuild for newer gtk + +* Tue Feb 08 2011 Fedora Release Engineering - 0.7.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild + +* Sat Feb 5 2011 Matthias Clasen - 0.7.2-1 +- Update to 0.7.2 + +* Wed Feb 2 2011 Matthias Clasen - 0.7.1-1 +- Update to 0.7.1 + +* Mon Jan 17 2011 Matthias Clasen - 0.7-1 +- Update to 0.7 + +* Wed Sep 29 2010 jkeating - 0.5.1-2 +- Rebuilt for gcc bug 634757 + +* Tue Sep 21 2010 Matthias Clasen - 0.5.1-1 +- Update to 0.5.1 + +* Thu Aug 5 2010 Matthias Clasen - 0.5-2 +- Fix up shared library symlinks (#621733) + +* Tue Aug 3 2010 Matthias Clasen - 0.5-1 +- Update to 0.5 + +* Mon Jul 12 2010 Matthias Clasen - 0.4.2-1 +- Update to 0.4.2 + +* Wed Jun 30 2010 Colin Walters - 0.4.1-2 +- Changes to support snapshot builds + +* Sat Jun 26 2010 Matthias Clasen 0.4.1-1 +- Update to 0.4.1 +- Include dconf-editor (in a subpackage) + +* Wed Jun 23 2010 Matthias Clasen 0.4-2 +- Rebuild against glib 2.25.9 + +* Sat Jun 12 2010 Matthias Clasen 0.4-1 +- Update to 0.4 + +* Tue Jun 08 2010 Richard Hughes 0.3.2-0.1.20100608 +- Update to a git snapshot so that users do not get a segfault in every + application using GSettings. + +* Wed Jun 02 2010 Bastien Nocera 0.3.1-2 +- Rebuild against latest glib2 + +* Mon May 24 2010 Matthias Clasen 0.3.1-1 +- Update to 0.3.1 +- Add a -devel subpackage + +* Fri May 21 2010 Matthias Clasen 0.3-3 +- Make batched writes (e.g. with delayed settings) work + +* Thu May 20 2010 Matthias Clasen 0.3-2 +- Make the registration of the backend work + +* Wed May 19 2010 Matthias Clasen 0.3-1 +- Initial packaging