Engine: Change overflow thresholds in subscription counts from GMAXUINT32 to GMAXUINT
[dconf.git] / engine / dconf-engine.c
blobdde8c183d1931e9512a573dfa6f356b1e967b85a
1 /*
2 * Copyright © 2010 Codethink Limited
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the licence, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 * Author: Ryan Lortie <desrt@desrt.ca>
20 #include "config.h"
22 #define _XOPEN_SOURCE 600
23 #include "dconf-engine.h"
25 #include "../common/dconf-enums.h"
26 #include "../common/dconf-paths.h"
27 #include "../gvdb/gvdb-reader.h"
28 #include <string.h>
29 #include <stdlib.h>
30 #include <errno.h>
31 #include <stdio.h>
32 #include <unistd.h>
33 #include <fcntl.h>
34 #include <sys/mman.h>
36 #include "dconf-engine-profile.h"
38 /* The engine has zero or more sources.
40 * If it has zero sources then things are very uninteresting. Nothing
41 * is writable, nothing will ever be written and reads will always
42 * return NULL.
44 * There are two interesting cases when there is a non-zero number of
45 * sources. Writing only ever occurs to the first source, if at all.
46 * Non-first sources are never writable.
48 * The first source may or may not be writable. In the usual case the
49 * first source is the one in the user's home directory and is writable,
50 * but it may be that the profile was setup for read-only access to
51 * system sources only.
53 * In the case that the first source is not writable (and therefore
54 * there are no writable sources), is_writable() will always return
55 * FALSE and no writes will ever be performed.
57 * It's possible to request changes in three ways:
59 * - synchronous: the D-Bus message is immediately sent to the
60 * dconf service and we block until we receive the reply. The change
61 * signal will follow soon thereafter (when we receive the signal on
62 * D-Bus).
64 * - asynchronous: typical asynchronous operation: we send the request
65 * and return immediately, notifying using a callback when the
66 * request is completed (and the new value is in the database). The
67 * change signal follows in the same way as with synchronous.
69 * - fast: we record the value locally and signal the change, returning
70 * immediately, as if the value is already in the database (from the
71 * viewpoint of the local process). We keep note of the new value
72 * locally until the service has confirmed that the write was
73 * successful. If the write fails, we emit a change signal. From
74 * the view of the program it looks like the value was successfully
75 * changed but then quickly changed back again by some external
76 * agent.
78 * In fast mode we have to do some management of the queue. If we
79 * immediately put all requests "in flight" then we can end up in a
80 * situation where the application writes many values for the same key
81 * and the service is kept (needlessly) busy writing over and over to
82 * the same key for some time after the requests stop coming in.
84 * If we limit the number of in-flight requests and put the other ones
85 * into a pending queue then we can perform merging of similar changes.
86 * If we notice that an item in the pending queue writes to the same
87 * keys as the newly-added request then we can simply drop the existing
88 * request (since its effect will be nullified by the new request).
90 * We want to keep the number of in-flight requests low in order to
91 * maximise our chance of dropping pending items, but we probably want
92 * it higher than 1 so that we can pipeline to hide latency.
94 * In order to minimise complexity, all changes go first to the pending
95 * queue. Changes are dispatched from the pending queue (and moved to
96 * the in-flight queue) when the number of requests in-flight is lower
97 * than the maximum.
99 * For both 'in_flight' and 'pending' queues we push to the tail and pop
100 * from the head. This puts the first operation on the head and the
101 * most recent operation on the tail.
103 * Since new operation go first to the pending queue, we find the most
104 * recent operations at the tail of that queue. Since we want to return
105 * the most-recently written value, we therefore scan for values
106 * starting at the tail of the pending queue and ending at the head of
107 * the in-flight queue.
109 * NB: I tell a lie. Async is not supported yet.
111 * Notes about threading:
113 * The engine is oblivious to threads and main contexts.
115 * What this means is that the engine has no interaction with GMainLoop
116 * and will not schedule idles or anything of the sort. All calls made
117 * by the engine to the client library will be made in response to
118 * incoming method calls, from the same thread as the incoming call.
120 * If dconf_engine_call_handle_reply() or
121 * dconf_engine_handle_dbus_signal() are called from 'exotic' threads
122 * (as will often be the case) then the resulting calls to
123 * dconf_engine_change_notify() will come from the same thread. That's
124 * left for the client library to deal with.
126 * All that said, the engine is completely threadsafe. The client
127 * library can call any method from any thread at any time -- as long as
128 * it is willing to deal with receiving the change notifies in those
129 * threads.
131 * Thread-safety is implemented using three locks.
133 * The first lock (sources_lock) protects the sources. Although the
134 * sources are only ever read from, it is necessary to lock them because
135 * it is not safe to read during a refresh (when the source is being
136 * closed and reopened). Accordingly, sources_lock need only be
137 * acquired when accessing the parts of the sources that are subject to
138 * change as a result of refreshes; the static parts (like bus type,
139 * object path, etc) can be accessed without holding the lock. The
140 * 'sources' array itself (and 'n_sources') are set at construction and
141 * never change after that.
143 * The second lock (queue_lock) protects the various queues that are
144 * used to implement the "fast" writes described above.
146 * The third lock (subscription_count_lock) protects the two hash tables
147 * that are used to keep track of the number of subscriptions held by
148 * the client library to each path.
150 * If sources_lock and queue_lock are held at the same time then then
151 * sources_lock must have been acquired first.
153 * subscription_count_lock is never held at the same time as
154 * sources_lock or queue_lock
157 #define MAX_IN_FLIGHT 2
159 static GSList *dconf_engine_global_list;
160 static GMutex dconf_engine_global_lock;
162 struct _DConfEngine
164 gpointer user_data; /* Set at construct time */
165 GDestroyNotify free_func;
166 gint ref_count;
168 GMutex sources_lock; /* This lock is for the sources (ie: refreshing) and state. */
169 guint64 state; /* Counter that changes every time a source is refreshed. */
170 DConfEngineSource **sources; /* Array never changes, but each source changes internally. */
171 gint n_sources;
173 GMutex queue_lock; /* This lock is for pending, in_flight, queue_cond */
174 GCond queue_cond; /* Signalled when the queues empty */
175 GQueue pending; /* DConfChangeset */
176 GQueue in_flight; /* DConfChangeset */
178 gchar *last_handled; /* reply tag from last item in in_flight */
181 * establishing and active, are hash tables storing the number
182 * of subscriptions to each path in the two possible states
184 /* This lock ensures that transactions involving subscription counts are atomic */
185 GMutex subscription_count_lock;
186 /* active on the client side, but awaiting confirmation from the writer */
187 GHashTable *establishing;
188 /* active on the client side, and with a D-Bus match rule established */
189 GHashTable *active;
192 /* When taking the sources lock we check if any of the databases have
193 * had updates.
195 * Anything that is accessing the database (even only reading) needs to
196 * be holding the lock (since refreshes could be happening in another
197 * thread), so this makes sense.
199 * We could probably optimise this to avoid checking some databases in
200 * certain cases (ie: we do not need to check the user's database when
201 * we are only interested in checking writability) but this works well
202 * enough for now and is less prone to errors.
204 * We could probably change to a reader/writer situation that is only
205 * holding the write lock when actually making changes during a refresh
206 * but the engine is probably only ever really in use by two threads at
207 * a given time (main thread doing reads, DBus worker thread clearing
208 * the queue) so it seems unlikely that lock contention will become an
209 * issue.
211 * If it does, we can revisit this...
213 static void
214 dconf_engine_acquire_sources (DConfEngine *engine)
216 gint i;
218 g_mutex_lock (&engine->sources_lock);
220 for (i = 0; i < engine->n_sources; i++)
221 if (dconf_engine_source_refresh (engine->sources[i]))
222 engine->state++;
225 static void
226 dconf_engine_release_sources (DConfEngine *engine)
228 g_mutex_unlock (&engine->sources_lock);
231 static void
232 dconf_engine_lock_queues (DConfEngine *engine)
234 g_mutex_lock (&engine->queue_lock);
237 static void
238 dconf_engine_unlock_queues (DConfEngine *engine)
240 g_mutex_unlock (&engine->queue_lock);
244 * Adds the count of subscriptions to @path in @from_table to the
245 * corresponding count in @to_table, creating it if it did not exist.
246 * Removes the count from @from_table.
248 static void
249 dconf_engine_move_subscriptions (GHashTable *from_counts,
250 GHashTable *to_counts,
251 const gchar *path)
253 guint from_count = GPOINTER_TO_UINT (g_hash_table_lookup (from_counts, path));
254 guint old_to_count = GPOINTER_TO_UINT (g_hash_table_lookup (to_counts, path));
255 // Detect overflows
256 g_assert (old_to_count <= G_MAXUINT - from_count);
257 guint new_to_count = old_to_count + from_count;
258 if (from_count != 0)
260 g_hash_table_remove (from_counts, path);
261 g_hash_table_replace (to_counts,
262 g_strdup (path),
263 GUINT_TO_POINTER (new_to_count));
268 * Increments the reference count for the subscription to @path, or sets
269 * it to 1 if it didn’t previously exist.
270 * Returns the new reference count.
272 static guint
273 dconf_engine_inc_subscriptions (GHashTable *counts,
274 const gchar *path)
276 guint old_count = GPOINTER_TO_UINT (g_hash_table_lookup (counts, path));
277 // Detect overflows
278 g_assert (old_count < G_MAXUINT);
279 guint new_count = old_count + 1;
280 g_hash_table_replace (counts, g_strdup (path), GUINT_TO_POINTER (new_count));
281 return new_count;
285 * Decrements the reference count for the subscription to @path, or
286 * removes it if the new value is 0. The count must exist and be greater
287 * than 0.
288 * Returns the new reference count, or 0 if it does not exist.
290 static guint
291 dconf_engine_dec_subscriptions (GHashTable *counts,
292 const gchar *path)
294 guint old_count = GPOINTER_TO_UINT (g_hash_table_lookup (counts, path));
295 g_assert (old_count > 0);
296 guint new_count = old_count - 1;
297 if (new_count == 0)
298 g_hash_table_remove (counts, path);
299 else
300 g_hash_table_replace (counts, g_strdup (path), GUINT_TO_POINTER (new_count));
301 return new_count;
305 * Returns the reference count for the subscription to @path, or 0 if it
306 * does not exist.
308 static guint
309 dconf_engine_count_subscriptions (GHashTable *counts,
310 const gchar *path)
312 return GPOINTER_TO_UINT (g_hash_table_lookup (counts, path));
316 * Acquires the subscription counts lock, which must be held when
317 * reading or writing to the subscription counts.
319 static void
320 dconf_engine_lock_subscription_counts (DConfEngine *engine)
322 g_mutex_lock (&engine->subscription_count_lock);
326 * Releases the subscription counts lock
328 static void
329 dconf_engine_unlock_subscription_counts (DConfEngine *engine)
331 g_mutex_unlock (&engine->subscription_count_lock);
334 DConfEngine *
335 dconf_engine_new (const gchar *profile,
336 gpointer user_data,
337 GDestroyNotify free_func)
339 DConfEngine *engine;
341 engine = g_slice_new0 (DConfEngine);
342 engine->user_data = user_data;
343 engine->free_func = free_func;
344 engine->ref_count = 1;
346 g_mutex_init (&engine->sources_lock);
347 g_mutex_init (&engine->queue_lock);
348 g_cond_init (&engine->queue_cond);
350 engine->sources = dconf_engine_profile_open (profile, &engine->n_sources);
352 g_mutex_lock (&dconf_engine_global_lock);
353 dconf_engine_global_list = g_slist_prepend (dconf_engine_global_list, engine);
354 g_mutex_unlock (&dconf_engine_global_lock);
356 g_mutex_init (&engine->subscription_count_lock);
357 engine->establishing = g_hash_table_new_full (g_str_hash,
358 g_str_equal,
359 g_free,
360 NULL);
361 engine->active = g_hash_table_new_full (g_str_hash,
362 g_str_equal,
363 g_free,
364 NULL);
366 return engine;
369 void
370 dconf_engine_unref (DConfEngine *engine)
372 gint ref_count;
374 again:
375 ref_count = engine->ref_count;
377 if (ref_count == 1)
379 gint i;
381 /* We are about to drop the last reference, but there is a chance
382 * that a signal may be happening at this very moment, causing the
383 * engine to gain another reference (due to its position in the
384 * global engine list).
386 * Acquiring the lock here means that either we will remove this
387 * engine from the list first or we will notice the reference
388 * count has increased (and skip the free).
390 g_mutex_lock (&dconf_engine_global_lock);
391 if (engine->ref_count != 1)
393 g_mutex_unlock (&dconf_engine_global_lock);
394 goto again;
396 dconf_engine_global_list = g_slist_remove (dconf_engine_global_list, engine);
397 g_mutex_unlock (&dconf_engine_global_lock);
399 g_mutex_clear (&engine->sources_lock);
400 g_mutex_clear (&engine->queue_lock);
401 g_cond_clear (&engine->queue_cond);
403 g_free (engine->last_handled);
405 while (!g_queue_is_empty (&engine->pending))
406 dconf_changeset_unref ((DConfChangeset *) g_queue_pop_head (&engine->pending));
408 while (!g_queue_is_empty (&engine->in_flight))
409 dconf_changeset_unref ((DConfChangeset *) g_queue_pop_head (&engine->in_flight));
411 for (i = 0; i < engine->n_sources; i++)
412 dconf_engine_source_free (engine->sources[i]);
414 g_free (engine->sources);
416 g_hash_table_unref (engine->establishing);
417 g_hash_table_unref (engine->active);
419 g_mutex_clear (&engine->subscription_count_lock);
421 if (engine->free_func)
422 engine->free_func (engine->user_data);
424 g_slice_free (DConfEngine, engine);
427 else if (!g_atomic_int_compare_and_exchange (&engine->ref_count, ref_count, ref_count - 1))
428 goto again;
431 static DConfEngine *
432 dconf_engine_ref (DConfEngine *engine)
434 g_atomic_int_inc (&engine->ref_count);
436 return engine;
439 guint64
440 dconf_engine_get_state (DConfEngine *engine)
442 guint64 state;
444 dconf_engine_acquire_sources (engine);
445 state = engine->state;
446 dconf_engine_release_sources (engine);
448 return state;
451 static gboolean
452 dconf_engine_is_writable_internal (DConfEngine *engine,
453 const gchar *key)
455 gint i;
457 /* We must check several things:
459 * - we have at least one source
461 * - the first source is writable
463 * - the key is not locked in a non-writable (ie: non-first) source
465 if (engine->n_sources == 0)
466 return FALSE;
468 if (engine->sources[0]->writable == FALSE)
469 return FALSE;
471 /* Ignore locks in the first source.
473 * Either it is writable and therefore ignoring locks is the right
474 * thing to do, or it's non-writable and we caught that case above.
476 for (i = 1; i < engine->n_sources; i++)
477 if (engine->sources[i]->locks && gvdb_table_has_value (engine->sources[i]->locks, key))
478 return FALSE;
480 return TRUE;
483 gboolean
484 dconf_engine_is_writable (DConfEngine *engine,
485 const gchar *key)
487 gboolean writable;
489 dconf_engine_acquire_sources (engine);
490 writable = dconf_engine_is_writable_internal (engine, key);
491 dconf_engine_release_sources (engine);
493 return writable;
496 gchar **
497 dconf_engine_list_locks (DConfEngine *engine,
498 const gchar *path,
499 gint *length)
501 gchar **strv;
503 if (dconf_is_dir (path, NULL))
505 GHashTable *set;
507 set = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
509 dconf_engine_acquire_sources (engine);
511 if (engine->n_sources > 0 && engine->sources[0]->writable)
513 gint i, j;
515 for (i = 1; i < engine->n_sources; i++)
517 if (engine->sources[i]->locks)
519 strv = gvdb_table_get_names (engine->sources[i]->locks, NULL);
521 for (j = 0; strv[j]; j++)
523 /* It is not currently possible to lock dirs, so we
524 * don't (yet) have to check the other direction.
526 if (g_str_has_prefix (strv[j], path))
527 g_hash_table_add (set, strv[j]);
528 else
529 g_free (strv[j]);
532 g_free (strv);
536 else
537 g_hash_table_add (set, g_strdup (path));
539 dconf_engine_release_sources (engine);
541 strv = (gchar **) g_hash_table_get_keys_as_array (set, (guint *) length);
542 g_hash_table_steal_all (set);
543 g_hash_table_unref (set);
545 else
547 if (dconf_engine_is_writable (engine, path))
549 strv = g_new0 (gchar *, 0 + 1);
551 else
553 strv = g_new0 (gchar *, 1 + 1);
554 strv[0] = g_strdup (path);
558 return strv;
561 static gboolean
562 dconf_engine_find_key_in_queue (const GQueue *queue,
563 const gchar *key,
564 GVariant **value)
566 GList *node;
568 /* Tail to head... */
569 for (node = queue->tail; node; node = node->prev)
570 if (dconf_changeset_get (node->data, key, value))
571 return TRUE;
573 return FALSE;
576 GVariant *
577 dconf_engine_read (DConfEngine *engine,
578 DConfReadFlags flags,
579 const GQueue *read_through,
580 const gchar *key)
582 GVariant *value = NULL;
583 gint lock_level = 0;
584 gint i;
586 dconf_engine_acquire_sources (engine);
588 /* There are a number of situations that this function has to deal
589 * with and they interact in unusual ways. We attempt to write the
590 * rules for all cases here:
592 * With respect to the steady-state condition with no locks:
594 * This is the case where there are no changes queued, no
595 * read_through and no locks.
597 * The value returned is the one from the lowest-index source that
598 * contains that value.
600 * With respect to locks:
602 * If a lock is present (except in source #0 where it is ignored)
603 * then we will only return a value found in the source where the
604 * lock was present, or a higher-index source (following the normal
605 * rule that sources with lower indexes take priority).
607 * This statement includes read_through and queued changes. If a
608 * lock is found, we will ignore those.
610 * With respect to flags:
612 * If DCONF_READ_USER_VALUE is given then we completely ignore all
613 * locks, returning the user value all the time, even if it is not
614 * visible (because of a lock). This includes any pending value
615 * that is in the read_through or pending queues.
617 * If DCONF_READ_DEFAULT_VALUE is given then we skip the writable
618 * database and the queues (including read_through, which is
619 * meaningless in this case) and skip directly to the non-writable
620 * databases. This is defined as the value that the user would see
621 * if they were to have just done a reset for that key.
623 * With respect to read_through and queued changed:
625 * We only consider read_through and queued changes in the event
626 * that we have a writable source. This will possibly cause us to
627 * ignore read_through and will have no real effect on the queues
628 * (since they will be empty anyway if we have no writable source).
630 * We only consider read_through and queued changes in the event
631 * that we have not found any locks.
633 * If there is a non-NULL value found in read_through or the queued
634 * changes then we will return that value.
636 * If there is a NULL value (ie: a reset) found in read_through or
637 * the queued changes then we will only ignore any value found in
638 * the first source (which must be writable, or else we would not
639 * have been considering read_through and the queues). This is
640 * consistent with the fact that a reset will unset any value found
641 * in this source but will not affect values found in lower sources.
643 * Put another way: if a non-writable source contains a value for a
644 * particular key then it is impossible for this function to return
645 * NULL.
647 * We implement the above rules as follows. We have three state
648 * tracking variables:
650 * - lock_level: records if and where we found a lock
652 * - found_key: records if we found the key in any queue
654 * - value: records the value of the found key (NULL for resets)
656 * We take these steps:
658 * 1. check for lockdown. If we find a lock then we prevent any
659 * other sources (including read_through and pending/in-flight)
660 * from affecting the value of the key.
662 * We record the result of this in the lock_level variable. Zero
663 * means that no locks were found. Non-zero means that a lock was
664 * found in the source with the index given by the variable.
666 * 2. check the uncommitted changes in the read_through list as the
667 * highest priority. This is only done if we have a writable
668 * source and no locks were found.
670 * If we found an entry in the read_through then we set
671 * 'found_key' to TRUE and set 'value' to the value that we found
672 * (which will be NULL in the case of finding a reset request).
674 * 3. check our pending and in-flight "fast" changes (in that order).
675 * This is only done if we have a writable source and no locks
676 * were found. It is also only done if we did not find the key in
677 * the read_through.
679 * 4. check the first source, if there is one.
681 * This is only done if 'found_key' is FALSE. If 'found_key' is
682 * TRUE then it means that the first database was writable and we
683 * either found a value that will replace it (value != NULL) or
684 * found a pending reset (value == NULL) that will unset it.
686 * We only actually do this step if we have a writable first
687 * source and no locks found, otherwise we just let step 5 do all
688 * the checking.
690 * 5. check the remaining sources.
692 * We do this until we have value != NULL. Even if found_key was
693 * TRUE, the reset that was requested will not have affected the
694 * lower-level databases.
697 /* Step 1. Check for locks.
699 * Note: i > 0 (strictly). Ignore locks for source #0.
701 if (~flags & DCONF_READ_USER_VALUE)
702 for (i = engine->n_sources - 1; i > 0; i--)
703 if (engine->sources[i]->locks && gvdb_table_has_value (engine->sources[i]->locks, key))
705 lock_level = i;
706 break;
709 /* Only do steps 2 to 4 if we have no locks and we have a writable source. */
710 if (!lock_level && engine->n_sources != 0 && engine->sources[0]->writable)
712 gboolean found_key = FALSE;
714 /* If the user has requested the default value only, then ensure
715 * that we "find" a NULL value here. This is equivalent to the
716 * user having reset the key, which is the definition of this
717 * flag.
719 if (flags & DCONF_READ_DEFAULT_VALUE)
720 found_key = TRUE;
722 /* Step 2. Check read_through. */
723 if (!found_key && read_through)
724 found_key = dconf_engine_find_key_in_queue (read_through, key, &value);
726 /* Step 3. Check queued changes if we didn't find it in read_through.
728 * NB: We may want to optimise this to avoid taking the lock in
729 * the case that we know both queues are empty.
731 if (!found_key)
733 dconf_engine_lock_queues (engine);
735 /* Check the pending queue first because those were submitted
736 * more recently.
738 found_key = dconf_engine_find_key_in_queue (&engine->pending, key, &value) ||
739 dconf_engine_find_key_in_queue (&engine->in_flight, key, &value);
741 dconf_engine_unlock_queues (engine);
744 /* Step 4. Check the first source. */
745 if (!found_key && engine->sources[0]->values)
746 value = gvdb_table_get_value (engine->sources[0]->values, key);
748 /* We already checked source #0 (or ignored it, as appropriate).
750 * Abuse the lock_level variable to get step 5 to skip this one.
752 lock_level = 1;
755 /* Step 5. Check the remaining sources, until value != NULL. */
756 if (~flags & DCONF_READ_USER_VALUE)
757 for (i = lock_level; value == NULL && i < engine->n_sources; i++)
759 if (engine->sources[i]->values == NULL)
760 continue;
762 if ((value = gvdb_table_get_value (engine->sources[i]->values, key)))
763 break;
766 dconf_engine_release_sources (engine);
768 return value;
771 gchar **
772 dconf_engine_list (DConfEngine *engine,
773 const gchar *dir,
774 gint *length)
776 GHashTable *results;
777 GHashTableIter iter;
778 gchar **list;
779 gint n_items;
780 gpointer key;
781 gint i;
783 /* This function is unreliable in the presence of pending changes.
784 * Here's why:
786 * Consider the case that we list("/a/") and a pending request has a
787 * reset request recorded for "/a/b/c". The question of if "b/"
788 * should appear in the output rests on if "/a/b/d" also exists.
790 * Put another way: If "/a/b/c" is the only key in "/a/b/" then
791 * resetting it would mean that "/a/b/" stops existing (and we should
792 * not include it in the output). If there are others keys then it
793 * will continue to exist and we should include it.
795 * Instead of trying to sort this out, we just ignore the pending
796 * requests and report what the on-disk file says.
799 results = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
801 dconf_engine_acquire_sources (engine);
803 for (i = 0; i < engine->n_sources; i++)
805 gchar **partial_list;
806 gint j;
808 if (engine->sources[i]->values == NULL)
809 continue;
811 partial_list = gvdb_table_list (engine->sources[i]->values, dir);
813 if (partial_list != NULL)
815 for (j = 0; partial_list[j]; j++)
816 /* Steal the keys from the list. */
817 g_hash_table_add (results, partial_list[j]);
819 /* Free only the list. */
820 g_free (partial_list);
824 dconf_engine_release_sources (engine);
826 n_items = g_hash_table_size (results);
827 list = g_new (gchar *, n_items + 1);
829 i = 0;
830 g_hash_table_iter_init (&iter, results);
831 while (g_hash_table_iter_next (&iter, &key, NULL))
833 g_hash_table_iter_steal (&iter);
834 list[i++] = key;
836 list[i] = NULL;
837 g_assert_cmpint (i, ==, n_items);
839 if (length)
840 *length = n_items;
842 g_hash_table_unref (results);
844 return list;
847 typedef void (* DConfEngineCallHandleCallback) (DConfEngine *engine,
848 gpointer handle,
849 GVariant *parameter,
850 const GError *error);
852 struct _DConfEngineCallHandle
854 DConfEngine *engine;
855 DConfEngineCallHandleCallback callback;
856 const GVariantType *expected_reply;
859 static gpointer
860 dconf_engine_call_handle_new (DConfEngine *engine,
861 DConfEngineCallHandleCallback callback,
862 const GVariantType *expected_reply,
863 gsize size)
865 DConfEngineCallHandle *handle;
867 g_assert (engine != NULL);
868 g_assert (callback != NULL);
869 g_assert (size >= sizeof (DConfEngineCallHandle));
871 handle = g_malloc0 (size);
872 handle->engine = dconf_engine_ref (engine);
873 handle->callback = callback;
874 handle->expected_reply = expected_reply;
876 return handle;
879 const GVariantType *
880 dconf_engine_call_handle_get_expected_type (DConfEngineCallHandle *handle)
882 if (handle)
883 return handle->expected_reply;
884 else
885 return NULL;
888 void
889 dconf_engine_call_handle_reply (DConfEngineCallHandle *handle,
890 GVariant *parameter,
891 const GError *error)
893 if (handle == NULL)
894 return;
896 (* handle->callback) (handle->engine, handle, parameter, error);
899 static void
900 dconf_engine_call_handle_free (DConfEngineCallHandle *handle)
902 dconf_engine_unref (handle->engine);
903 g_free (handle);
906 /* returns floating */
907 static GVariant *
908 dconf_engine_make_match_rule (DConfEngineSource *source,
909 const gchar *path)
911 GVariant *params;
912 gchar *rule;
914 rule = g_strdup_printf ("type='signal',"
915 "interface='ca.desrt.dconf.Writer',"
916 "path='%s',"
917 "arg0path='%s'",
918 source->object_path,
919 path);
921 params = g_variant_new ("(s)", rule);
923 g_free (rule);
925 return params;
928 typedef struct
930 DConfEngineCallHandle handle;
932 guint64 state;
933 gint pending;
934 gchar *path;
935 } OutstandingWatch;
937 static void
938 dconf_engine_watch_established (DConfEngine *engine,
939 gpointer handle,
940 GVariant *reply,
941 const GError *error)
943 OutstandingWatch *ow = handle;
945 /* ignore errors */
947 if (--ow->pending)
948 /* more on the way... */
949 return;
951 if (ow->state != dconf_engine_get_state (engine))
953 const gchar * const changes[] = { "", NULL };
955 /* Our recorded state does not match the current state. Something
956 * must have changed while our watch requests were on the wire.
958 * We don't know what changed, so we can just say that potentially
959 * everything under the path being watched changed. This case is
960 * very rare, anyway...
962 g_debug ("SHM invalidated while establishing subscription to %s - signalling change", ow->path);
963 dconf_engine_change_notify (engine, ow->path, changes, NULL, FALSE, NULL, engine->user_data);
966 dconf_engine_lock_subscription_counts (engine);
967 guint num_establishing = dconf_engine_count_subscriptions (engine->establishing,
968 ow->path);
969 g_debug ("watch_established: \"%s\" (establishing: %d)", ow->path, num_establishing);
970 if (num_establishing > 0)
971 // Subscription(s): establishing -> active
972 dconf_engine_move_subscriptions (engine->establishing,
973 engine->active,
974 ow->path);
976 dconf_engine_unlock_subscription_counts (engine);
977 dconf_engine_call_handle_free (handle);
980 void
981 dconf_engine_watch_fast (DConfEngine *engine,
982 const gchar *path)
984 dconf_engine_lock_subscription_counts (engine);
985 guint num_establishing = dconf_engine_count_subscriptions (engine->establishing, path);
986 guint num_active = dconf_engine_count_subscriptions (engine->active, path);
987 g_debug ("watch_fast: \"%s\" (establishing: %d, active: %d)", path, num_establishing, num_active);
988 if (num_active > 0)
989 // Subscription: inactive -> active
990 dconf_engine_inc_subscriptions (engine->active, path);
991 else
992 // Subscription: inactive -> establishing
993 num_establishing = dconf_engine_inc_subscriptions (engine->establishing,
994 path);
995 dconf_engine_unlock_subscription_counts (engine);
996 if (num_establishing > 1 || num_active > 0)
997 return;
999 OutstandingWatch *ow;
1000 gint i;
1002 if (engine->n_sources == 0)
1003 return;
1005 /* It's possible (although rare) that the dconf database could change
1006 * while our match rule is on the wire.
1008 * Since we returned immediately (suggesting to the user that the
1009 * watch was already established) we could have a race.
1011 * To deal with this, we use the current state counter to ensure that nothing
1012 * changes while the watch requests are on the wire.
1014 ow = dconf_engine_call_handle_new (engine, dconf_engine_watch_established,
1015 G_VARIANT_TYPE_UNIT, sizeof (OutstandingWatch));
1016 ow->state = dconf_engine_get_state (engine);
1017 ow->path = g_strdup (path);
1019 /* We start getting async calls returned as soon as we start dispatching them,
1020 * so we must not touch the 'ow' struct after we send the first one.
1022 for (i = 0; i < engine->n_sources; i++)
1023 if (engine->sources[i]->bus_type)
1024 ow->pending++;
1026 for (i = 0; i < engine->n_sources; i++)
1027 if (engine->sources[i]->bus_type)
1028 dconf_engine_dbus_call_async_func (engine->sources[i]->bus_type, "org.freedesktop.DBus",
1029 "/org/freedesktop/DBus", "org.freedesktop.DBus", "AddMatch",
1030 dconf_engine_make_match_rule (engine->sources[i], path),
1031 &ow->handle, NULL);
1034 void
1035 dconf_engine_unwatch_fast (DConfEngine *engine,
1036 const gchar *path)
1038 dconf_engine_lock_subscription_counts (engine);
1039 guint num_active = dconf_engine_count_subscriptions (engine->active, path);
1040 guint num_establishing = dconf_engine_count_subscriptions (engine->establishing, path);
1041 gint i;
1042 g_debug ("unwatch_fast: \"%s\" (active: %d, establishing: %d)", path, num_active, num_establishing);
1044 // Client code cannot unsubscribe if it is not subscribed
1045 g_assert (num_active > 0 || num_establishing > 0);
1046 if (num_active == 0)
1047 // Subscription: establishing -> inactive
1048 num_establishing = dconf_engine_dec_subscriptions (engine->establishing, path);
1049 else
1050 // Subscription: active -> inactive
1051 num_active = dconf_engine_dec_subscriptions (engine->active, path);
1053 dconf_engine_unlock_subscription_counts (engine);
1054 if (num_active > 0 || num_establishing > 0)
1055 return;
1057 for (i = 0; i < engine->n_sources; i++)
1058 if (engine->sources[i]->bus_type)
1059 dconf_engine_dbus_call_async_func (engine->sources[i]->bus_type, "org.freedesktop.DBus",
1060 "/org/freedesktop/DBus", "org.freedesktop.DBus", "RemoveMatch",
1061 dconf_engine_make_match_rule (engine->sources[i], path), NULL, NULL);
1064 static void
1065 dconf_engine_handle_match_rule_sync (DConfEngine *engine,
1066 const gchar *method_name,
1067 const gchar *path)
1069 gint i;
1071 /* We need not hold any locks here because we are only touching static
1072 * things: the number of sources, and static properties of each source
1073 * itself.
1075 * This function silently ignores all errors.
1078 for (i = 0; i < engine->n_sources; i++)
1080 GVariant *result;
1082 if (!engine->sources[i]->bus_type)
1083 continue;
1085 result = dconf_engine_dbus_call_sync_func (engine->sources[i]->bus_type, "org.freedesktop.DBus",
1086 "/org/freedesktop/DBus", "org.freedesktop.DBus", method_name,
1087 dconf_engine_make_match_rule (engine->sources[i], path),
1088 G_VARIANT_TYPE_UNIT, NULL);
1090 if (result)
1091 g_variant_unref (result);
1095 void
1096 dconf_engine_watch_sync (DConfEngine *engine,
1097 const gchar *path)
1099 dconf_engine_lock_subscription_counts (engine);
1100 guint num_active = dconf_engine_inc_subscriptions (engine->active, path);
1101 dconf_engine_unlock_subscription_counts (engine);
1102 g_debug ("watch_sync: \"%s\" (active: %d)", path, num_active - 1);
1103 if (num_active == 1)
1104 dconf_engine_handle_match_rule_sync (engine, "AddMatch", path);
1107 void
1108 dconf_engine_unwatch_sync (DConfEngine *engine,
1109 const gchar *path)
1111 dconf_engine_lock_subscription_counts (engine);
1112 guint num_active = dconf_engine_dec_subscriptions (engine->active, path);
1113 dconf_engine_unlock_subscription_counts (engine);
1114 g_debug ("unwatch_sync: \"%s\" (active: %d)", path, num_active + 1);
1115 if (num_active == 0)
1116 dconf_engine_handle_match_rule_sync (engine, "RemoveMatch", path);
1119 typedef struct
1121 DConfEngineCallHandle handle;
1123 DConfChangeset *change;
1124 } OutstandingChange;
1126 static GVariant *
1127 dconf_engine_prepare_change (DConfEngine *engine,
1128 DConfChangeset *change)
1130 GVariant *serialised;
1132 serialised = dconf_changeset_serialise (change);
1134 return g_variant_new_from_data (G_VARIANT_TYPE ("(ay)"),
1135 g_variant_get_data (serialised), g_variant_get_size (serialised), TRUE,
1136 (GDestroyNotify) g_variant_unref, g_variant_ref_sink (serialised));
1139 /* This function promotes changes from the pending queue to the
1140 * in-flight queue by sending the appropriate D-Bus message.
1142 * Of course, this is only possible when there are pending items and
1143 * room in the in-flight queue. For this reason, this function gets
1144 * called in two situations:
1146 * - an item has been added to the pending queue (due to an API call)
1148 * - an item has been removed from the inflight queue (due to a D-Bus
1149 * reply having been received)
1151 * It will move a maximum of one item.
1153 static void dconf_engine_manage_queue (DConfEngine *engine);
1155 static void
1156 dconf_engine_emit_changes (DConfEngine *engine,
1157 DConfChangeset *changeset,
1158 gpointer origin_tag)
1160 const gchar *prefix;
1161 const gchar * const *changes;
1163 if (dconf_changeset_describe (changeset, &prefix, &changes, NULL))
1164 dconf_engine_change_notify (engine, prefix, changes, NULL, FALSE, origin_tag, engine->user_data);
1167 static void
1168 dconf_engine_change_completed (DConfEngine *engine,
1169 gpointer handle,
1170 GVariant *reply,
1171 const GError *error)
1173 OutstandingChange *oc = handle;
1175 dconf_engine_lock_queues (engine);
1177 /* D-Bus guarantees ordered delivery of messages.
1179 * The dconf-service handles requests in-order.
1181 * The reply we just received should therefore be at the head of
1182 * our 'in flight' queue.
1184 * Due to https://bugs.freedesktop.org/show_bug.cgi?id=59780 it is
1185 * possible that we receive an out-of-sequence error message, however,
1186 * so only assume that messages are in-order for positive replies.
1188 if (reply)
1190 DConfChangeset *expected;
1192 expected = g_queue_pop_head (&engine->in_flight);
1193 g_assert (expected && oc->change == expected);
1195 else
1197 gboolean found;
1199 g_assert (error != NULL);
1201 found = g_queue_remove (&engine->in_flight, oc->change);
1202 g_assert (found);
1205 /* We just popped a change from the in-flight queue, possibly
1206 * making room for another to be added. Check that.
1208 dconf_engine_manage_queue (engine);
1209 dconf_engine_unlock_queues (engine);
1211 /* Deal with the reply we got. */
1212 if (reply)
1214 /* The write worked.
1216 * We already sent a change notification for this item when we
1217 * added it to the pending queue and we don't want to send another
1218 * one again. At the same time, it's very likely that we're just
1219 * about to receive a change signal from the service.
1221 * The tag sent as part of the reply to the Change call will be
1222 * the same tag as on the change notification signal. Record that
1223 * tag so that we can ignore the signal when it comes.
1225 * last_handled is only ever touched from the worker thread
1227 g_free (engine->last_handled);
1228 g_variant_get (reply, "(s)", &engine->last_handled);
1231 if (error)
1233 /* Some kind of unexpected failure occurred while attempting to
1234 * commit the change.
1236 * There's not much we can do here except to drop our local copy
1237 * of the change (and notify that it is gone) and print the error
1238 * message as a warning.
1240 g_warning ("failed to commit changes to dconf: %s", error->message);
1241 dconf_engine_emit_changes (engine, oc->change, NULL);
1244 dconf_changeset_unref (oc->change);
1245 dconf_engine_call_handle_free (handle);
1248 static void
1249 dconf_engine_manage_queue (DConfEngine *engine)
1251 if (!g_queue_is_empty (&engine->pending) && g_queue_get_length (&engine->in_flight) < MAX_IN_FLIGHT)
1253 OutstandingChange *oc;
1254 GVariant *parameters;
1256 oc = dconf_engine_call_handle_new (engine, dconf_engine_change_completed,
1257 G_VARIANT_TYPE ("(s)"), sizeof (OutstandingChange));
1259 oc->change = g_queue_pop_head (&engine->pending);
1261 parameters = dconf_engine_prepare_change (engine, oc->change);
1263 dconf_engine_dbus_call_async_func (engine->sources[0]->bus_type,
1264 engine->sources[0]->bus_name,
1265 engine->sources[0]->object_path,
1266 "ca.desrt.dconf.Writer", "Change",
1267 parameters, &oc->handle, NULL);
1269 g_queue_push_tail (&engine->in_flight, oc->change);
1272 if (g_queue_is_empty (&engine->in_flight))
1274 /* The in-flight queue should not be empty if we have changes
1275 * pending...
1277 g_assert (g_queue_is_empty (&engine->pending));
1279 g_cond_broadcast (&engine->queue_cond);
1283 static gboolean
1284 dconf_engine_is_writable_changeset_predicate (const gchar *key,
1285 GVariant *value,
1286 gpointer user_data)
1288 DConfEngine *engine = user_data;
1290 /* Resets absolutely always succeed -- even in the case that there is
1291 * not even a writable database.
1293 return value == NULL || dconf_engine_is_writable_internal (engine, key);
1296 static gboolean
1297 dconf_engine_changeset_changes_only_writable_keys (DConfEngine *engine,
1298 DConfChangeset *changeset,
1299 GError **error)
1301 gboolean success = TRUE;
1303 dconf_engine_acquire_sources (engine);
1305 if (!dconf_changeset_all (changeset, dconf_engine_is_writable_changeset_predicate, engine))
1307 g_set_error_literal (error, DCONF_ERROR, DCONF_ERROR_NOT_WRITABLE,
1308 "The operation attempted to modify one or more non-writable keys");
1309 success = FALSE;
1312 dconf_engine_release_sources (engine);
1314 return success;
1317 gboolean
1318 dconf_engine_change_fast (DConfEngine *engine,
1319 DConfChangeset *changeset,
1320 gpointer origin_tag,
1321 GError **error)
1323 GList *node;
1324 g_debug ("change_fast");
1325 if (dconf_changeset_is_empty (changeset))
1326 return TRUE;
1328 if (!dconf_engine_changeset_changes_only_writable_keys (engine, changeset, error))
1329 return FALSE;
1331 dconf_changeset_seal (changeset);
1333 /* Check for duplicates in the pending queue.
1335 * Note: order doesn't really matter here since "similarity" is an
1336 * equivalence class and we've ensured that there are no pairwise
1337 * similar changes in the queue already (ie: at most we will have only
1338 * one similar item to the one we are adding).
1340 dconf_engine_lock_queues (engine);
1342 for (node = g_queue_peek_head_link (&engine->pending); node; node = node->next)
1344 DConfChangeset *queued_change = node->data;
1346 if (dconf_changeset_is_similar_to (changeset, queued_change))
1348 /* We found a similar item in the queue.
1350 * We want to drop the one that's in the queue already since
1351 * we want our new (more recent) change to take precedence.
1353 * The pending queue owned the changeset, so free it.
1355 g_queue_delete_link (&engine->pending, node);
1356 dconf_changeset_unref (queued_change);
1358 /* There will only have been one, so stop looking. */
1359 break;
1363 /* No matter what we're going to queue up this change, so put it in
1364 * the pending queue now.
1366 * There may be room in the in_flight queue, so we try to manage the
1367 * queue right away in order to try to promote it there (which causes
1368 * the D-Bus message to actually be sent).
1370 * The change might get tossed before being sent if the loop above
1371 * finds it on a future call.
1373 g_queue_push_tail (&engine->pending, dconf_changeset_ref (changeset));
1374 dconf_engine_manage_queue (engine);
1376 dconf_engine_unlock_queues (engine);
1378 /* Emit the signal after dropping the lock to avoid deadlock on re-entry. */
1379 dconf_engine_emit_changes (engine, changeset, origin_tag);
1381 return TRUE;
1384 gboolean
1385 dconf_engine_change_sync (DConfEngine *engine,
1386 DConfChangeset *changeset,
1387 gchar **tag,
1388 GError **error)
1390 GVariant *reply;
1391 g_debug ("change_sync");
1393 if (dconf_changeset_is_empty (changeset))
1395 if (tag)
1396 *tag = g_strdup ("");
1398 return TRUE;
1401 if (!dconf_engine_changeset_changes_only_writable_keys (engine, changeset, error))
1402 return FALSE;
1404 dconf_changeset_seal (changeset);
1406 /* we know that we have at least one source because we checked writability */
1407 reply = dconf_engine_dbus_call_sync_func (engine->sources[0]->bus_type,
1408 engine->sources[0]->bus_name,
1409 engine->sources[0]->object_path,
1410 "ca.desrt.dconf.Writer", "Change",
1411 dconf_engine_prepare_change (engine, changeset),
1412 G_VARIANT_TYPE ("(s)"), error);
1414 if (reply == NULL)
1415 return FALSE;
1417 /* g_variant_get() is okay with NULL tag */
1418 g_variant_get (reply, "(s)", tag);
1419 g_variant_unref (reply);
1421 return TRUE;
1424 static gboolean
1425 dconf_engine_is_interested_in_signal (DConfEngine *engine,
1426 GBusType bus_type,
1427 const gchar *sender,
1428 const gchar *path)
1430 gint i;
1432 for (i = 0; i < engine->n_sources; i++)
1434 DConfEngineSource *source = engine->sources[i];
1436 if (source->bus_type == bus_type && g_str_equal (source->object_path, path))
1437 return TRUE;
1440 return FALSE;
1443 void
1444 dconf_engine_handle_dbus_signal (GBusType type,
1445 const gchar *sender,
1446 const gchar *object_path,
1447 const gchar *member,
1448 GVariant *body)
1450 if (g_str_equal (member, "Notify"))
1452 const gchar *prefix;
1453 const gchar **changes;
1454 const gchar *tag;
1455 GSList *engines;
1457 if (!g_variant_is_of_type (body, G_VARIANT_TYPE ("(sass)")))
1458 return;
1460 g_variant_get (body, "(&s^a&s&s)", &prefix, &changes, &tag);
1462 /* Reject junk */
1463 if (changes[0] == NULL)
1464 /* No changes? Do nothing. */
1465 goto junk;
1467 if (dconf_is_key (prefix, NULL))
1469 /* If the prefix is a key then the changes must be ['']. */
1470 if (changes[0][0] || changes[1])
1471 goto junk;
1473 else if (dconf_is_dir (prefix, NULL))
1475 /* If the prefix is a dir then we can have changes within that
1476 * dir, but they must be rel paths.
1478 * ie:
1480 * ('/a/', ['b', 'c/']) == ['/a/b', '/a/c/']
1482 gint i;
1484 for (i = 0; changes[i]; i++)
1485 if (!dconf_is_rel_path (changes[i], NULL))
1486 goto junk;
1488 else
1489 /* Not a key or a dir? */
1490 goto junk;
1492 g_mutex_lock (&dconf_engine_global_lock);
1493 engines = g_slist_copy_deep (dconf_engine_global_list, (GCopyFunc) dconf_engine_ref, NULL);
1494 g_mutex_unlock (&dconf_engine_global_lock);
1496 while (engines)
1498 DConfEngine *engine = engines->data;
1500 /* It's possible that this incoming change notify is for a
1501 * change that we already announced to the client when we
1502 * placed it in the pending queue.
1504 * Check last_handled to determine if we should ignore it.
1506 if (!engine->last_handled || !g_str_equal (engine->last_handled, tag))
1507 if (dconf_engine_is_interested_in_signal (engine, type, sender, object_path))
1508 dconf_engine_change_notify (engine, prefix, changes, tag, FALSE, NULL, engine->user_data);
1510 engines = g_slist_delete_link (engines, engines);
1512 dconf_engine_unref (engine);
1515 junk:
1516 g_free (changes);
1519 else if (g_str_equal (member, "WritabilityNotify"))
1521 const gchar *empty_str_list[] = { "", NULL };
1522 const gchar *path;
1523 GSList *engines;
1525 if (!g_variant_is_of_type (body, G_VARIANT_TYPE ("(s)")))
1526 return;
1528 g_variant_get (body, "(&s)", &path);
1530 /* Rejecting junk here is relatively straightforward */
1531 if (!dconf_is_path (path, NULL))
1532 return;
1534 g_mutex_lock (&dconf_engine_global_lock);
1535 engines = g_slist_copy_deep (dconf_engine_global_list, (GCopyFunc) dconf_engine_ref, NULL);
1536 g_mutex_unlock (&dconf_engine_global_lock);
1538 while (engines)
1540 DConfEngine *engine = engines->data;
1542 if (dconf_engine_is_interested_in_signal (engine, type, sender, object_path))
1543 dconf_engine_change_notify (engine, path, empty_str_list, "", TRUE, NULL, engine->user_data);
1545 engines = g_slist_delete_link (engines, engines);
1547 dconf_engine_unref (engine);
1552 gboolean
1553 dconf_engine_has_outstanding (DConfEngine *engine)
1555 gboolean has;
1557 /* The in-flight queue will never be empty unless the pending queue is
1558 * also empty, so we only really need to check one of them...
1560 dconf_engine_lock_queues (engine);
1561 has = !g_queue_is_empty (&engine->in_flight);
1562 dconf_engine_unlock_queues (engine);
1564 return has;
1567 void
1568 dconf_engine_sync (DConfEngine *engine)
1570 g_debug ("sync");
1571 dconf_engine_lock_queues (engine);
1572 while (!g_queue_is_empty (&engine->in_flight))
1573 g_cond_wait (&engine->queue_cond, &engine->queue_lock);
1574 dconf_engine_unlock_queues (engine);