.gitignore updates
[dconf.git] / engine / dconf-engine.c
blob57bce965c03a644273077f227ae397fffe384c47
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-error.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 two 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 * If both locks are held at the same time then the sources lock must
147 * have been acquired first.
150 #define MAX_IN_FLIGHT 2
152 static GSList *dconf_engine_global_list;
153 static GMutex dconf_engine_global_lock;
155 struct _DConfEngine
157 gpointer user_data; /* Set at construct time */
158 GDestroyNotify free_func;
159 gint ref_count;
161 GMutex sources_lock; /* This lock is for the sources (ie: refreshing) and state. */
162 guint64 state; /* Counter that changes every time a source is refreshed. */
163 DConfEngineSource **sources; /* Array never changes, but each source changes internally. */
164 gint n_sources;
166 GMutex queue_lock; /* This lock is for pending, in_flight, queue_cond */
167 GCond queue_cond; /* Signalled when the queues empty */
168 GQueue pending; /* DConfChangeset */
169 GQueue in_flight; /* DConfChangeset */
171 gchar *last_handled; /* reply tag from last item in in_flight */
174 /* When taking the sources lock we check if any of the databases have
175 * had updates.
177 * Anything that is accessing the database (even only reading) needs to
178 * be holding the lock (since refreshes could be happening in another
179 * thread), so this makes sense.
181 * We could probably optimise this to avoid checking some databases in
182 * certain cases (ie: we do not need to check the user's database when
183 * we are only interested in checking writability) but this works well
184 * enough for now and is less prone to errors.
186 * We could probably change to a reader/writer situation that is only
187 * holding the write lock when actually making changes during a refresh
188 * but the engine is probably only ever really in use by two threads at
189 * a given time (main thread doing reads, DBus worker thread clearing
190 * the queue) so it seems unlikely that lock contention will become an
191 * issue.
193 * If it does, we can revisit this...
195 static void
196 dconf_engine_acquire_sources (DConfEngine *engine)
198 gint i;
200 g_mutex_lock (&engine->sources_lock);
202 for (i = 0; i < engine->n_sources; i++)
203 if (dconf_engine_source_refresh (engine->sources[i]))
204 engine->state++;
207 static void
208 dconf_engine_release_sources (DConfEngine *engine)
210 g_mutex_unlock (&engine->sources_lock);
213 static void
214 dconf_engine_lock_queues (DConfEngine *engine)
216 g_mutex_lock (&engine->queue_lock);
219 static void
220 dconf_engine_unlock_queues (DConfEngine *engine)
222 g_mutex_unlock (&engine->queue_lock);
225 DConfEngine *
226 dconf_engine_new (const gchar *profile,
227 gpointer user_data,
228 GDestroyNotify free_func)
230 DConfEngine *engine;
232 engine = g_slice_new0 (DConfEngine);
233 engine->user_data = user_data;
234 engine->free_func = free_func;
235 engine->ref_count = 1;
237 g_mutex_init (&engine->sources_lock);
238 g_mutex_init (&engine->queue_lock);
239 g_cond_init (&engine->queue_cond);
241 engine->sources = dconf_engine_profile_open (profile, &engine->n_sources);
243 g_mutex_lock (&dconf_engine_global_lock);
244 dconf_engine_global_list = g_slist_prepend (dconf_engine_global_list, engine);
245 g_mutex_unlock (&dconf_engine_global_lock);
247 return engine;
250 void
251 dconf_engine_unref (DConfEngine *engine)
253 gint ref_count;
255 again:
256 ref_count = engine->ref_count;
258 if (ref_count == 1)
260 gint i;
262 /* We are about to drop the last reference, but there is a chance
263 * that a signal may be happening at this very moment, causing the
264 * engine to gain another reference (due to its position in the
265 * global engine list).
267 * Acquiring the lock here means that either we will remove this
268 * engine from the list first or we will notice the reference
269 * count has increased (and skip the free).
271 g_mutex_lock (&dconf_engine_global_lock);
272 if (engine->ref_count != 1)
274 g_mutex_unlock (&dconf_engine_global_lock);
275 goto again;
277 dconf_engine_global_list = g_slist_remove (dconf_engine_global_list, engine);
278 g_mutex_unlock (&dconf_engine_global_lock);
280 g_mutex_clear (&engine->sources_lock);
281 g_mutex_clear (&engine->queue_lock);
282 g_cond_clear (&engine->queue_cond);
284 g_free (engine->last_handled);
286 for (i = 0; i < engine->n_sources; i++)
287 dconf_engine_source_free (engine->sources[i]);
289 g_free (engine->sources);
291 if (engine->free_func)
292 engine->free_func (engine->user_data);
294 g_slice_free (DConfEngine, engine);
297 else if (!g_atomic_int_compare_and_exchange (&engine->ref_count, ref_count, ref_count - 1))
298 goto again;
301 static DConfEngine *
302 dconf_engine_ref (DConfEngine *engine)
304 g_atomic_int_inc (&engine->ref_count);
306 return engine;
309 guint64
310 dconf_engine_get_state (DConfEngine *engine)
312 guint64 state;
314 dconf_engine_acquire_sources (engine);
315 state = engine->state;
316 dconf_engine_release_sources (engine);
318 return state;
321 static gboolean
322 dconf_engine_is_writable_internal (DConfEngine *engine,
323 const gchar *key)
325 gint i;
327 /* We must check several things:
329 * - we have at least one source
331 * - the first source is writable
333 * - the key is not locked in a non-writable (ie: non-first) source
335 if (engine->n_sources == 0)
336 return FALSE;
338 if (engine->sources[0]->writable == FALSE)
339 return FALSE;
341 /* Ignore locks in the first source.
343 * Either it is writable and therefore ignoring locks is the right
344 * thing to do, or it's non-writable and we caught that case above.
346 for (i = 1; i < engine->n_sources; i++)
347 if (engine->sources[i]->locks && gvdb_table_has_value (engine->sources[i]->locks, key))
348 return FALSE;
350 return TRUE;
353 gboolean
354 dconf_engine_is_writable (DConfEngine *engine,
355 const gchar *key)
357 gboolean writable;
359 dconf_engine_acquire_sources (engine);
360 writable = dconf_engine_is_writable_internal (engine, key);
361 dconf_engine_release_sources (engine);
363 return writable;
366 static gboolean
367 dconf_engine_find_key_in_queue (GQueue *queue,
368 const gchar *key,
369 GVariant **value)
371 GList *node;
373 /* Tail to head... */
374 for (node = g_queue_peek_tail_link (queue); node; node = node->prev)
375 if (dconf_changeset_get (node->data, key, value))
376 return TRUE;
378 return FALSE;
381 GVariant *
382 dconf_engine_read (DConfEngine *engine,
383 GQueue *read_through,
384 const gchar *key)
386 GVariant *value = NULL;
387 gint lock_level = 0;
388 gint i;
390 dconf_engine_acquire_sources (engine);
392 /* There are a number of situations that this function has to deal
393 * with and they interact in unusual ways. We attempt to write the
394 * rules for all cases here:
396 * With respect to the steady-state condition with no locks:
398 * This is the case where there are no changes queued, no
399 * read_through and no locks.
401 * The value returned is the one from the lowest-index source that
402 * contains that value.
404 * With respect to locks:
406 * If a lock is present (except in source #0 where it is ignored)
407 * then we will only return a value found in the source where the
408 * lock was present, or a higher-index source (following the normal
409 * rule that sources with lower indexes take priority).
411 * This statement includes read_through and queued changes. If a
412 * lock is found, we will ignore those.
414 * With respect to read_through and queued changed:
416 * We only consider read_through and queued changes in the event
417 * that we have a writable source. This will possibly cause us to
418 * ignore read_through and will have no real effect on the queues
419 * (since they will be empty anyway if we have no writable source).
421 * We only consider read_through and queued changes in the event
422 * that we have not found any locks.
424 * If there is a non-NULL value found in read_through or the queued
425 * changes then we will return that value.
427 * If there is a NULL value (ie: a reset) found in read_through or
428 * the queued changes then we will only ignore any value found in
429 * the first source (which must be writable, or else we would not
430 * have been considering read_through and the queues). This is
431 * consistent with the fact that a reset will unset any value found
432 * in this source but will not affect values found in lower sources.
434 * Put another way: if a non-writable source contains a value for a
435 * particular key then it is impossible for this function to return
436 * NULL.
438 * We implement the above rules as follows. We have three state
439 * tracking variables:
441 * - lock_level: records if and where we found a lock
443 * - found_key: records if we found the key in any queue
445 * - value: records the value of the found key (NULL for resets)
447 * We take these steps:
449 * 1. check for lockdown. If we find a lock then we prevent any
450 * other sources (including read_through and pending/in-flight)
451 * from affecting the value of the key.
453 * We record the result of this in the lock_level variable. Zero
454 * means that no locks were found. Non-zero means that a lock was
455 * found in the source with the index given by the variable.
457 * 2. check the uncommitted changes in the read_through list as the
458 * highest priority. This is only done if we have a writable
459 * source and no locks were found.
461 * If we found an entry in the read_through then we set
462 * 'found_key' to TRUE and set 'value' to the value that we found
463 * (which will be NULL in the case of finding a reset request).
465 * 3. check our pending and in-flight "fast" changes (in that order).
466 * This is only done if we have a writable source and no locks
467 * were found. It is also only done if we did not find the key in
468 * the read_through.
470 * 4. check the first source, if there is one.
472 * This is only done if 'found_key' is FALSE. If 'found_key' is
473 * TRUE then it means that the first database was writable and we
474 * either found a value that will replace it (value != NULL) or
475 * found a pending reset (value == NULL) that will unset it.
477 * We only actually do this step if we have a writable first
478 * source and no locks found, otherwise we just let step 5 do all
479 * the checking.
481 * 5. check the remaining sources.
483 * We do this until we have value != NULL. Even if found_key was
484 * TRUE, the reset that was requested will not have affected the
485 * lower-level databases.
488 /* Step 1. Check for locks.
490 * Note: i > 0 (strictly). Ignore locks for source #0.
492 for (i = engine->n_sources - 1; i > 0; i--)
493 if (engine->sources[i]->locks && gvdb_table_has_value (engine->sources[i]->locks, key))
495 lock_level = i;
496 break;
499 /* Only do steps 2 to 4 if we have no locks and we have a writable source. */
500 if (!lock_level && engine->n_sources != 0 && engine->sources[0]->writable)
502 gboolean found_key = FALSE;
504 /* Step 2. Check read_through. */
505 if (read_through)
506 found_key = dconf_engine_find_key_in_queue (read_through, key, &value);
508 /* Step 3. Check queued changes if we didn't find it in read_through.
510 * NB: We may want to optimise this to avoid taking the lock in
511 * the case that we know both queues are empty.
513 if (!found_key)
515 dconf_engine_lock_queues (engine);
517 /* Check the pending queue first because those were submitted
518 * more recently.
520 found_key = dconf_engine_find_key_in_queue (&engine->pending, key, &value) ||
521 dconf_engine_find_key_in_queue (&engine->in_flight, key, &value);
523 dconf_engine_unlock_queues (engine);
526 /* Step 4. Check the first source. */
527 if (!found_key && engine->sources[0]->values)
528 value = gvdb_table_get_value (engine->sources[0]->values, key);
530 /* We already checked source #0 (or ignored it, as appropriate).
532 * Abuse the lock_level variable to get step 5 to skip this one.
534 lock_level = 1;
537 /* Step 5. Check the remaining sources, until value != NULL. */
538 for (i = lock_level; value == NULL && i < engine->n_sources; i++)
540 if (engine->sources[i]->values == NULL)
541 continue;
543 if ((value = gvdb_table_get_value (engine->sources[i]->values, key)))
544 break;
547 dconf_engine_release_sources (engine);
549 return value;
552 GVariant *
553 dconf_engine_read_user_value (DConfEngine *engine,
554 GQueue *read_through,
555 const gchar *key)
557 gboolean found_key = FALSE;
558 GVariant *value = NULL;
560 /* This is a simplified version of the above. We get to ignore locks
561 * and system-level settings.
563 * NB: we may find "NULL", which is why we have a separate variable.
566 /* Ignore the queues if we don't have a writable database */
567 if (engine->n_sources == 0 || !engine->sources[0]->writable)
568 return NULL;
570 dconf_engine_acquire_sources (engine);
572 /* First check read-through */
573 if (read_through)
574 found_key = dconf_engine_find_key_in_queue (read_through, key, &value);
576 /* Next pending/in-flight */
577 if (!found_key)
579 dconf_engine_lock_queues (engine);
581 /* Check the pending queue first because those were submitted
582 * more recently.
584 found_key = dconf_engine_find_key_in_queue (&engine->pending, key, &value) ||
585 dconf_engine_find_key_in_queue (&engine->in_flight, key, &value);
587 dconf_engine_unlock_queues (engine);
590 /* Finally, check the user database */
591 if (!found_key && engine->sources[0]->values)
592 value = gvdb_table_get_value (engine->sources[0]->values, key);
594 dconf_engine_release_sources (engine);
596 return value;
599 gchar **
600 dconf_engine_list (DConfEngine *engine,
601 const gchar *dir,
602 gint *length)
604 GHashTable *results;
605 GHashTableIter iter;
606 gchar **list;
607 gint n_items;
608 gpointer key;
609 gint i;
611 /* This function is unreliable in the presence of pending changes.
612 * Here's why:
614 * Consider the case that we list("/a/") and a pending request has a
615 * reset request recorded for "/a/b/c". The question of if "b/"
616 * should appear in the output rests on if "/a/b/d" also exists.
618 * Put another way: If "/a/b/c" is the only key in "/a/b/" then
619 * resetting it would mean that "/a/b/" stops existing (and we should
620 * not include it in the output). If there are others keys then it
621 * will continue to exist and we should include it.
623 * Instead of trying to sort this out, we just ignore the pending
624 * requests and report what the on-disk file says.
627 results = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
629 dconf_engine_acquire_sources (engine);
631 for (i = 0; i < engine->n_sources; i++)
633 gchar **partial_list;
634 gint j;
636 if (engine->sources[i]->values == NULL)
637 continue;
639 partial_list = gvdb_table_list (engine->sources[i]->values, dir);
641 if (partial_list != NULL)
643 for (j = 0; partial_list[j]; j++)
644 /* Steal the keys from the list. */
645 g_hash_table_add (results, partial_list[j]);
647 /* Free only the list. */
648 g_free (partial_list);
652 dconf_engine_release_sources (engine);
654 n_items = g_hash_table_size (results);
655 list = g_new (gchar *, n_items + 1);
657 i = 0;
658 g_hash_table_iter_init (&iter, results);
659 while (g_hash_table_iter_next (&iter, &key, NULL))
661 g_hash_table_iter_steal (&iter);
662 list[i++] = key;
664 list[i] = NULL;
665 g_assert_cmpint (i, ==, n_items);
667 if (length)
668 *length = n_items;
670 g_hash_table_unref (results);
672 return list;
675 typedef void (* DConfEngineCallHandleCallback) (DConfEngine *engine,
676 gpointer handle,
677 GVariant *parameter,
678 const GError *error);
680 struct _DConfEngineCallHandle
682 DConfEngine *engine;
683 DConfEngineCallHandleCallback callback;
684 const GVariantType *expected_reply;
687 static gpointer
688 dconf_engine_call_handle_new (DConfEngine *engine,
689 DConfEngineCallHandleCallback callback,
690 const GVariantType *expected_reply,
691 gsize size)
693 DConfEngineCallHandle *handle;
695 g_assert (engine != NULL);
696 g_assert (callback != NULL);
697 g_assert (size >= sizeof (DConfEngineCallHandle));
699 handle = g_malloc0 (size);
700 handle->engine = dconf_engine_ref (engine);
701 handle->callback = callback;
702 handle->expected_reply = expected_reply;
704 return handle;
707 const GVariantType *
708 dconf_engine_call_handle_get_expected_type (DConfEngineCallHandle *handle)
710 if (handle)
711 return handle->expected_reply;
712 else
713 return NULL;
716 void
717 dconf_engine_call_handle_reply (DConfEngineCallHandle *handle,
718 GVariant *parameter,
719 const GError *error)
721 if (handle == NULL)
722 return;
724 (* handle->callback) (handle->engine, handle, parameter, error);
727 static void
728 dconf_engine_call_handle_free (DConfEngineCallHandle *handle)
730 dconf_engine_unref (handle->engine);
731 g_free (handle);
734 /* returns floating */
735 static GVariant *
736 dconf_engine_make_match_rule (DConfEngineSource *source,
737 const gchar *path)
739 GVariant *params;
740 gchar *rule;
742 rule = g_strdup_printf ("type='signal',"
743 "interface='ca.desrt.dconf.Writer',"
744 "path='%s',"
745 "arg0path='%s'",
746 source->object_path,
747 path);
749 params = g_variant_new ("(s)", rule);
751 g_free (rule);
753 return params;
756 typedef struct
758 DConfEngineCallHandle handle;
760 guint64 state;
761 gint pending;
762 } OutstandingWatch;
764 static void
765 dconf_engine_watch_established (DConfEngine *engine,
766 gpointer handle,
767 GVariant *reply,
768 const GError *error)
770 OutstandingWatch *ow = handle;
772 /* ignore errors */
774 if (--ow->pending)
775 /* more on the way... */
776 return;
778 if (ow->state != dconf_engine_get_state (engine))
780 const gchar * const changes[] = { "", NULL };
782 /* Our recorded state does not match the current state. Something
783 * must have changed while our watch requests were on the wire.
785 * We don't know what changed, so we can just say that potentially
786 * everything changed. This case is very rare, anyway...
788 dconf_engine_change_notify (engine, "/", changes, NULL, FALSE, NULL, engine->user_data);
791 dconf_engine_call_handle_free (handle);
794 void
795 dconf_engine_watch_fast (DConfEngine *engine,
796 const gchar *path)
798 OutstandingWatch *ow;
799 gint i;
801 if (engine->n_sources == 0)
802 return;
804 /* It's possible (although rare) that the dconf database could change
805 * while our match rule is on the wire.
807 * Since we returned immediately (suggesting to the user that the
808 * watch was already established) we could have a race.
810 * To deal with this, we use the current state counter to ensure that nothing
811 * changes while the watch requests are on the wire.
813 ow = dconf_engine_call_handle_new (engine, dconf_engine_watch_established,
814 G_VARIANT_TYPE_UNIT, sizeof (OutstandingWatch));
815 ow->state = dconf_engine_get_state (engine);
817 /* We start getting async calls returned as soon as we start dispatching them,
818 * so we must not touch the 'ow' struct after we send the first one.
820 for (i = 0; i < engine->n_sources; i++)
821 if (engine->sources[i]->bus_type)
822 ow->pending++;
824 for (i = 0; i < engine->n_sources; i++)
825 if (engine->sources[i]->bus_type)
826 dconf_engine_dbus_call_async_func (engine->sources[i]->bus_type, "org.freedesktop.DBus",
827 "/org/freedesktop/DBus", "org.freedesktop.DBus", "AddMatch",
828 dconf_engine_make_match_rule (engine->sources[i], path),
829 &ow->handle, NULL);
832 void
833 dconf_engine_unwatch_fast (DConfEngine *engine,
834 const gchar *path)
836 gint i;
838 for (i = 0; i < engine->n_sources; i++)
839 if (engine->sources[i]->bus_type)
840 dconf_engine_dbus_call_async_func (engine->sources[i]->bus_type, "org.freedesktop.DBus",
841 "/org/freedesktop/DBus", "org.freedesktop.DBus", "RemoveMatch",
842 dconf_engine_make_match_rule (engine->sources[i], path), NULL, NULL);
845 static void
846 dconf_engine_handle_match_rule_sync (DConfEngine *engine,
847 const gchar *method_name,
848 const gchar *path)
850 gint i;
852 /* We need not hold any locks here because we are only touching static
853 * things: the number of sources, and static properties of each source
854 * itself.
856 * This function silently ignores all errors.
859 for (i = 0; i < engine->n_sources; i++)
861 GVariant *result;
863 if (!engine->sources[i]->bus_type)
864 continue;
866 result = dconf_engine_dbus_call_sync_func (engine->sources[i]->bus_type, "org.freedesktop.DBus",
867 "/org/freedesktop/DBus", "org.freedesktop.DBus", method_name,
868 dconf_engine_make_match_rule (engine->sources[i], path),
869 G_VARIANT_TYPE_UNIT, NULL);
871 if (result)
872 g_variant_unref (result);
876 void
877 dconf_engine_watch_sync (DConfEngine *engine,
878 const gchar *path)
880 dconf_engine_handle_match_rule_sync (engine, "AddMatch", path);
883 void
884 dconf_engine_unwatch_sync (DConfEngine *engine,
885 const gchar *path)
887 dconf_engine_handle_match_rule_sync (engine, "RemoveMatch", path);
890 typedef struct
892 DConfEngineCallHandle handle;
894 DConfChangeset *change;
895 } OutstandingChange;
897 static GVariant *
898 dconf_engine_prepare_change (DConfEngine *engine,
899 DConfChangeset *change)
901 GVariant *serialised;
903 serialised = dconf_changeset_serialise (change);
905 return g_variant_new_from_data (G_VARIANT_TYPE ("(ay)"),
906 g_variant_get_data (serialised), g_variant_get_size (serialised), TRUE,
907 (GDestroyNotify) g_variant_unref, g_variant_ref_sink (serialised));
910 /* This function promotes changes from the pending queue to the
911 * in-flight queue by sending the appropriate D-Bus message.
913 * Of course, this is only possible when there are pending items and
914 * room in the in-flight queue. For this reason, this function gets
915 * called in two situations:
917 * - an item has been added to the pending queue (due to an API call)
919 * - an item has been removed from the inflight queue (due to a D-Bus
920 * reply having been received)
922 * It will move a maximum of one item.
924 static void dconf_engine_manage_queue (DConfEngine *engine);
926 static void
927 dconf_engine_emit_changes (DConfEngine *engine,
928 DConfChangeset *changeset,
929 gpointer origin_tag)
931 const gchar *prefix;
932 const gchar * const *changes;
934 if (dconf_changeset_describe (changeset, &prefix, &changes, NULL))
935 dconf_engine_change_notify (engine, prefix, changes, NULL, FALSE, origin_tag, engine->user_data);
938 static void
939 dconf_engine_change_completed (DConfEngine *engine,
940 gpointer handle,
941 GVariant *reply,
942 const GError *error)
944 OutstandingChange *oc = handle;
946 dconf_engine_lock_queues (engine);
948 /* D-Bus guarantees ordered delivery of messages.
950 * The dconf-service handles requests in-order.
952 * The reply we just received should therefore be at the head of
953 * our 'in flight' queue.
955 * Due to https://bugs.freedesktop.org/show_bug.cgi?id=59780 it is
956 * possible that we receive an out-of-sequence error message, however,
957 * so only assume that messages are in-order for positive replies.
959 if (reply)
961 DConfChangeset *expected;
963 expected = g_queue_pop_head (&engine->in_flight);
964 g_assert (expected && oc->change == expected);
966 else
968 gboolean found;
970 g_assert (error != NULL);
972 found = g_queue_remove (&engine->in_flight, oc->change);
973 g_assert (found);
976 /* We just popped a change from the in-flight queue, possibly
977 * making room for another to be added. Check that.
979 dconf_engine_manage_queue (engine);
980 dconf_engine_unlock_queues (engine);
982 /* Deal with the reply we got. */
983 if (reply)
985 /* The write worked.
987 * We already sent a change notification for this item when we
988 * added it to the pending queue and we don't want to send another
989 * one again. At the same time, it's very likely that we're just
990 * about to receive a change signal from the service.
992 * The tag sent as part of the reply to the Change call will be
993 * the same tag as on the change notification signal. Record that
994 * tag so that we can ignore the signal when it comes.
996 * last_handled is only ever touched from the worker thread
998 g_free (engine->last_handled);
999 g_variant_get (reply, "(s)", &engine->last_handled);
1002 if (error)
1004 /* Some kind of unexpected failure occurred while attempting to
1005 * commit the change.
1007 * There's not much we can do here except to drop our local copy
1008 * of the change (and notify that it is gone) and print the error
1009 * message as a warning.
1011 g_warning ("failed to commit changes to dconf: %s", error->message);
1012 dconf_engine_emit_changes (engine, oc->change, NULL);
1015 dconf_changeset_unref (oc->change);
1016 dconf_engine_call_handle_free (handle);
1019 static void
1020 dconf_engine_manage_queue (DConfEngine *engine)
1022 if (!g_queue_is_empty (&engine->pending) && g_queue_get_length (&engine->in_flight) < MAX_IN_FLIGHT)
1024 OutstandingChange *oc;
1025 GVariant *parameters;
1027 oc = dconf_engine_call_handle_new (engine, dconf_engine_change_completed,
1028 G_VARIANT_TYPE ("(s)"), sizeof (OutstandingChange));
1030 oc->change = g_queue_pop_head (&engine->pending);
1032 parameters = dconf_engine_prepare_change (engine, oc->change);
1034 dconf_engine_dbus_call_async_func (engine->sources[0]->bus_type,
1035 engine->sources[0]->bus_name,
1036 engine->sources[0]->object_path,
1037 "ca.desrt.dconf.Writer", "Change",
1038 parameters, &oc->handle, NULL);
1040 g_queue_push_tail (&engine->in_flight, oc->change);
1043 if (g_queue_is_empty (&engine->in_flight))
1045 /* The in-flight queue should not be empty if we have changes
1046 * pending...
1048 g_assert (g_queue_is_empty (&engine->pending));
1050 g_cond_broadcast (&engine->queue_cond);
1054 static gboolean
1055 dconf_engine_is_writable_changeset_predicate (const gchar *key,
1056 GVariant *value,
1057 gpointer user_data)
1059 DConfEngine *engine = user_data;
1061 /* Resets absolutely always succeed -- even in the case that there is
1062 * not even a writable database.
1064 return value == NULL || dconf_engine_is_writable_internal (engine, key);
1067 static gboolean
1068 dconf_engine_changeset_changes_only_writable_keys (DConfEngine *engine,
1069 DConfChangeset *changeset,
1070 GError **error)
1072 gboolean success = TRUE;
1074 dconf_engine_acquire_sources (engine);
1076 if (!dconf_changeset_all (changeset, dconf_engine_is_writable_changeset_predicate, engine))
1078 g_set_error_literal (error, DCONF_ERROR, DCONF_ERROR_NOT_WRITABLE,
1079 "The operation attempted to modify one or more non-writable keys");
1080 success = FALSE;
1083 dconf_engine_release_sources (engine);
1085 return success;
1088 gboolean
1089 dconf_engine_change_fast (DConfEngine *engine,
1090 DConfChangeset *changeset,
1091 gpointer origin_tag,
1092 GError **error)
1094 GList *node;
1096 if (dconf_changeset_is_empty (changeset))
1097 return TRUE;
1099 if (!dconf_engine_changeset_changes_only_writable_keys (engine, changeset, error))
1100 return FALSE;
1102 dconf_changeset_seal (changeset);
1104 /* Check for duplicates in the pending queue.
1106 * Note: order doesn't really matter here since "similarity" is an
1107 * equivalence class and we've ensured that there are no pairwise
1108 * similar changes in the queue already (ie: at most we will have only
1109 * one similar item to the one we are adding).
1111 dconf_engine_lock_queues (engine);
1113 for (node = g_queue_peek_head_link (&engine->pending); node; node = node->next)
1115 DConfChangeset *queued_change = node->data;
1117 if (dconf_changeset_is_similar_to (changeset, queued_change))
1119 /* We found a similar item in the queue.
1121 * We want to drop the one that's in the queue already since
1122 * we want our new (more recent) change to take precedence.
1124 * The pending queue owned the changeset, so free it.
1126 g_queue_delete_link (&engine->pending, node);
1127 dconf_changeset_unref (queued_change);
1129 /* There will only have been one, so stop looking. */
1130 break;
1134 /* No matter what we're going to queue up this change, so put it in
1135 * the pending queue now.
1137 * There may be room in the in_flight queue, so we try to manage the
1138 * queue right away in order to try to promote it there (which causes
1139 * the D-Bus message to actually be sent).
1141 * The change might get tossed before being sent if the loop above
1142 * finds it on a future call.
1144 g_queue_push_tail (&engine->pending, dconf_changeset_ref (changeset));
1145 dconf_engine_manage_queue (engine);
1147 dconf_engine_unlock_queues (engine);
1149 /* Emit the signal after dropping the lock to avoid deadlock on re-entry. */
1150 dconf_engine_emit_changes (engine, changeset, origin_tag);
1152 return TRUE;
1155 gboolean
1156 dconf_engine_change_sync (DConfEngine *engine,
1157 DConfChangeset *changeset,
1158 gchar **tag,
1159 GError **error)
1161 GVariant *reply;
1163 if (dconf_changeset_is_empty (changeset))
1165 if (tag)
1166 *tag = g_strdup ("");
1168 return TRUE;
1171 if (!dconf_engine_changeset_changes_only_writable_keys (engine, changeset, error))
1172 return FALSE;
1174 dconf_changeset_seal (changeset);
1176 /* we know that we have at least one source because we checked writability */
1177 reply = dconf_engine_dbus_call_sync_func (engine->sources[0]->bus_type,
1178 engine->sources[0]->bus_name,
1179 engine->sources[0]->object_path,
1180 "ca.desrt.dconf.Writer", "Change",
1181 dconf_engine_prepare_change (engine, changeset),
1182 G_VARIANT_TYPE ("(s)"), error);
1184 if (reply == NULL)
1185 return FALSE;
1187 /* g_variant_get() is okay with NULL tag */
1188 g_variant_get (reply, "(s)", tag);
1189 g_variant_unref (reply);
1191 return TRUE;
1194 static gboolean
1195 dconf_engine_is_interested_in_signal (DConfEngine *engine,
1196 GBusType bus_type,
1197 const gchar *sender,
1198 const gchar *path)
1200 gint i;
1202 for (i = 0; i < engine->n_sources; i++)
1204 DConfEngineSource *source = engine->sources[i];
1206 if (source->bus_type == bus_type && g_str_equal (source->object_path, path))
1207 return TRUE;
1210 return FALSE;
1213 void
1214 dconf_engine_handle_dbus_signal (GBusType type,
1215 const gchar *sender,
1216 const gchar *object_path,
1217 const gchar *member,
1218 GVariant *body)
1220 if (g_str_equal (member, "Notify"))
1222 const gchar *prefix;
1223 const gchar **changes;
1224 const gchar *tag;
1225 GSList *engines;
1227 if (!g_variant_is_of_type (body, G_VARIANT_TYPE ("(sass)")))
1228 return;
1230 g_variant_get (body, "(&s^a&s&s)", &prefix, &changes, &tag);
1232 /* Reject junk */
1233 if (changes[0] == NULL)
1234 /* No changes? Do nothing. */
1235 goto junk;
1237 if (dconf_is_key (prefix, NULL))
1239 /* If the prefix is a key then the changes must be ['']. */
1240 if (changes[0][0] || changes[1])
1241 goto junk;
1243 else if (dconf_is_dir (prefix, NULL))
1245 /* If the prefix is a dir then we can have changes within that
1246 * dir, but they must be rel paths.
1248 * ie:
1250 * ('/a/', ['b', 'c/']) == ['/a/b', '/a/c/']
1252 gint i;
1254 for (i = 0; changes[i]; i++)
1255 if (!dconf_is_rel_path (changes[i], NULL))
1256 goto junk;
1258 else
1259 /* Not a key or a dir? */
1260 goto junk;
1262 g_mutex_lock (&dconf_engine_global_lock);
1263 engines = g_slist_copy_deep (dconf_engine_global_list, (GCopyFunc) dconf_engine_ref, NULL);
1264 g_mutex_unlock (&dconf_engine_global_lock);
1266 while (engines)
1268 DConfEngine *engine = engines->data;
1270 /* It's possible that this incoming change notify is for a
1271 * change that we already announced to the client when we
1272 * placed it in the pending queue.
1274 * Check last_handled to determine if we should ignore it.
1276 if (!engine->last_handled || !g_str_equal (engine->last_handled, tag))
1277 if (dconf_engine_is_interested_in_signal (engine, type, sender, object_path))
1278 dconf_engine_change_notify (engine, prefix, changes, tag, FALSE, NULL, engine->user_data);
1280 engines = g_slist_delete_link (engines, engines);
1282 dconf_engine_unref (engine);
1285 junk:
1286 g_free (changes);
1289 else if (g_str_equal (member, "WritabilityNotify"))
1291 const gchar *empty_str_list[] = { "", NULL };
1292 const gchar *path;
1293 GSList *engines;
1295 if (!g_variant_is_of_type (body, G_VARIANT_TYPE ("(s)")))
1296 return;
1298 g_variant_get (body, "(&s)", &path);
1300 /* Rejecting junk here is relatively straightforward */
1301 if (!dconf_is_path (path, NULL))
1302 return;
1304 g_mutex_lock (&dconf_engine_global_lock);
1305 engines = g_slist_copy_deep (dconf_engine_global_list, (GCopyFunc) dconf_engine_ref, NULL);
1306 g_mutex_unlock (&dconf_engine_global_lock);
1308 while (engines)
1310 DConfEngine *engine = engines->data;
1312 if (dconf_engine_is_interested_in_signal (engine, type, sender, object_path))
1313 dconf_engine_change_notify (engine, path, empty_str_list, "", TRUE, NULL, engine->user_data);
1315 engines = g_slist_delete_link (engines, engines);
1317 dconf_engine_unref (engine);
1322 gboolean
1323 dconf_engine_has_outstanding (DConfEngine *engine)
1325 gboolean has;
1327 /* The in-flight queue will never be empty unless the pending queue is
1328 * also empty, so we only really need to check one of them...
1330 dconf_engine_lock_queues (engine);
1331 has = !g_queue_is_empty (&engine->in_flight);
1332 dconf_engine_unlock_queues (engine);
1334 return has;
1337 void
1338 dconf_engine_sync (DConfEngine *engine)
1340 dconf_engine_lock_queues (engine);
1341 while (!g_queue_is_empty (&engine->in_flight))
1342 g_cond_wait (&engine->queue_cond, &engine->queue_lock);
1343 dconf_engine_unlock_queues (engine);