engine: free multiple 'shm' properly
[dconf.git] / engine / dconf-engine.c
blob6f5c2afcca9ad79410f8ac096ca3957561e6afba
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, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
19 * Author: Ryan Lortie <desrt@desrt.ca>
22 #define _XOPEN_SOURCE 600
23 #include "dconf-shmdir.h"
24 #include "dconf-engine.h"
25 #include <gvdb-reader.h>
26 #include <string.h>
27 #include <stdlib.h>
28 #include <errno.h>
29 #include <stdio.h>
30 #include <unistd.h>
31 #include <fcntl.h>
32 #include <sys/mman.h>
34 void
35 dconf_engine_message_destroy (DConfEngineMessage *dcem)
37 gint i;
39 for (i = 0; dcem->parameters[i]; i++)
40 g_variant_unref (dcem->parameters[i]);
41 g_free (dcem->parameters);
44 void
45 dconf_engine_message_copy (DConfEngineMessage *orig,
46 DConfEngineMessage *copy)
48 gint i, n;
50 *copy = *orig;
52 for (n = 0; orig->parameters[n]; n++);
53 copy->parameters = g_new (GVariant *, n + 1);
54 for (i = 0; i < n; i++)
55 copy->parameters[i] = g_variant_ref (orig->parameters[i]);
56 copy->parameters[i] = NULL;
59 static const gchar *
60 dconf_engine_get_session_dir (void)
62 static const gchar *session_dir;
63 static gsize initialised;
65 if (g_once_init_enter (&initialised))
67 session_dir = dconf_shmdir_from_environment ();
68 g_once_init_leave (&initialised, 1);
71 return session_dir;
74 struct _DConfEngine
76 GMutex lock;
77 guint64 state;
80 GvdbTable **gvdbs;
81 GvdbTable **lock_tables;
82 guint8 **shm;
83 gchar **object_paths;
84 gchar *bus_types;
85 gchar **names;
86 gint n_dbs;
89 static void
90 dconf_engine_setup_user (DConfEngine *engine,
91 gint i)
93 /* invariant: we never have user gvdb without shm */
94 g_assert ((engine->gvdbs[i] == NULL) >= (engine->shm[i] == NULL));
96 if (engine->names[i])
98 const gchar *session_dir = dconf_engine_get_session_dir ();
100 if (session_dir)
102 gchar *filename;
103 gint fd;
105 filename = g_build_filename (session_dir,
106 engine->names[i],
107 NULL);
108 fd = open (filename, O_RDWR | O_CREAT, 0600);
109 g_free (filename);
111 if (fd >= 0)
113 if (ftruncate (fd, 1) == 0)
115 engine->shm[i] = mmap (NULL, 1, PROT_READ, MAP_SHARED, fd, 0);
117 if (engine->shm[i] == MAP_FAILED)
118 engine->shm[i] = NULL;
121 close (fd);
125 if (engine->shm[i])
127 gchar *filename;
129 filename = g_build_filename (g_get_user_config_dir (),
130 "dconf",
131 engine->names[i],
132 NULL);
133 engine->gvdbs[i] = gvdb_table_new (filename, FALSE, NULL);
134 g_free (filename);
138 g_assert ((engine->gvdbs[i] == NULL) >= (engine->shm[i] == NULL));
141 static void
142 dconf_engine_refresh_user (DConfEngine *engine,
143 gint i)
145 g_assert ((engine->gvdbs[i] == NULL) >= (engine->shm[i] == NULL));
147 /* if we failed the first time, fail forever */
148 if (engine->shm[i] && *engine->shm[i] == 1)
150 if (engine->gvdbs[i])
152 gvdb_table_unref (engine->gvdbs[i]);
153 engine->gvdbs[i] = NULL;
156 munmap (engine->shm[i], 1);
157 engine->shm[i] = NULL;
159 dconf_engine_setup_user (engine, i);
160 engine->state++;
163 g_assert ((engine->gvdbs[i] == NULL) >= (engine->shm[i] == NULL));
166 static void
167 dconf_engine_refresh_system (DConfEngine *engine,
168 gint i)
170 if (engine->gvdbs[i] && !gvdb_table_is_valid (engine->gvdbs[i]))
172 gvdb_table_unref (engine->gvdbs[i]);
173 engine->gvdbs[i] = NULL;
176 if (engine->gvdbs[i] == NULL)
178 gchar *filename = g_build_filename ("/etc/dconf/db",
179 engine->names[i], NULL);
180 engine->gvdbs[i] = gvdb_table_new (filename, TRUE, NULL);
181 if (engine->gvdbs[i] == NULL)
182 g_error ("Unable to open '%s', specified in dconf profile\n",
183 filename);
184 engine->lock_tables[i] = gvdb_table_get_table (engine->gvdbs[i],
185 ".locks");
186 g_free (filename);
187 engine->state++;
191 static void
192 dconf_engine_refresh (DConfEngine *engine)
194 gint i;
196 for (i = 0; i < engine->n_dbs; i++)
197 if (engine->bus_types[i] == 'e')
198 dconf_engine_refresh_user (engine, i);
199 else
200 dconf_engine_refresh_system (engine, i);
203 static void
204 dconf_engine_setup (DConfEngine *engine)
206 gint i;
208 for (i = 0; i < engine->n_dbs; i++)
209 if (engine->bus_types[i] == 'e')
210 dconf_engine_setup_user (engine, i);
211 else
212 dconf_engine_refresh_system (engine, i);
215 guint64
216 dconf_engine_get_state (DConfEngine *engine)
218 guint64 state;
220 g_mutex_lock (&engine->lock);
222 dconf_engine_refresh (engine);
223 state = engine->state;
225 g_mutex_unlock (&engine->lock);
227 return state;
230 static gboolean
231 dconf_engine_load_profile (const gchar *profile,
232 gchar **bus_types,
233 gchar ***names,
234 gint *n_dbs,
235 GError **error)
237 gchar *filename;
238 gint allocated;
239 char line[80];
240 FILE *f;
242 /* DCONF_PROFILE starting with '/' gives an absolute path to a profile */
243 if (profile[0] != '/')
244 filename = g_build_filename ("/etc/dconf/profile", profile, NULL);
245 else
246 filename = g_strdup (profile);
248 f = fopen (filename, "r");
250 if (f == NULL)
252 gint saved_errno = errno;
254 g_set_error (error, G_FILE_ERROR,
255 g_file_error_from_errno (saved_errno),
256 "open '%s': %s", filename, g_strerror (saved_errno));
257 g_free (filename);
258 return FALSE;
261 allocated = 4;
262 *bus_types = g_new (gchar, allocated);
263 *names = g_new (gchar *, allocated);
264 *n_dbs = 0;
266 /* quick and dirty is good enough for now */
267 while (fgets (line, sizeof line, f))
269 const gchar *end;
270 const gchar *sep;
272 end = strchr (line, '\n');
274 if (end == NULL)
275 g_error ("long line in %s", filename);
277 if (end == line)
278 continue;
280 if (line[0] == '#')
281 continue;
283 if (*n_dbs == allocated)
285 allocated *= 2;
286 *names = g_renew (gchar *, *names, allocated);
287 *bus_types = g_renew (gchar, *bus_types, allocated);
290 sep = strchr (line, ':');
292 if (sep)
294 /* strings MUST be 'user-db' or 'system-db'. we do the check
295 * this way here merely because it is the fastest.
297 (*bus_types)[*n_dbs] = (line[0] == 'u') ? 'e' : 'y';
298 (*names)[*n_dbs] = g_strndup (sep + 1, end - (sep + 1));
300 else
302 /* default is for first DB to be user and rest to be system */
303 (*bus_types)[*n_dbs] = (*n_dbs == 0) ? 'e' : 'y';
304 (*names)[*n_dbs] = g_strndup (line, end - line);
307 (*n_dbs)++;
310 *bus_types = g_renew (gchar, *bus_types, *n_dbs);
311 *names = g_renew (gchar *, *names, *n_dbs);
312 g_free (filename);
313 fclose (f);
315 return TRUE;
318 DConfEngine *
319 dconf_engine_new (const gchar *profile)
321 DConfEngine *engine;
322 gint i;
324 engine = g_slice_new (DConfEngine);
325 g_mutex_init (&engine->lock);
327 if (profile == NULL)
328 profile = getenv ("DCONF_PROFILE");
330 if (profile)
332 GError *error = NULL;
334 if (!dconf_engine_load_profile (profile, &engine->bus_types, &engine->names, &engine->n_dbs, &error))
335 g_error ("Error loading dconf profile '%s': %s\n",
336 profile, error->message);
338 else
340 if (!dconf_engine_load_profile ("user", &engine->bus_types, &engine->names, &engine->n_dbs, NULL))
342 engine->names = g_new (gchar *, 1);
343 engine->names[0] = g_strdup ("user");
344 engine->bus_types = g_strdup ("e");
345 engine->n_dbs = 1;
349 if (strcmp (engine->names[0], "-") == 0)
351 g_free (engine->names[0]);
352 engine->names[0] = NULL;
355 engine->object_paths = g_new (gchar *, engine->n_dbs);
356 engine->gvdbs = g_new0 (GvdbTable *, engine->n_dbs);
357 engine->lock_tables = g_new0 (GvdbTable *, engine->n_dbs);
358 engine->shm = g_new0 (guint8 *, engine->n_dbs);
359 engine->state = 0;
361 for (i = 0; i < engine->n_dbs; i++)
362 if (engine->names[i])
363 engine->object_paths[i] = g_strjoin (NULL,
364 "/ca/desrt/dconf/Writer/",
365 engine->names[i],
366 NULL);
367 else
368 engine->object_paths[i] = NULL;
370 dconf_engine_setup (engine);
372 return engine;
375 void
376 dconf_engine_free (DConfEngine *engine)
378 gint i;
380 for (i = 0; i < engine->n_dbs; i++)
382 g_free (engine->object_paths[i]);
383 g_free (engine->names[i]);
385 if (engine->gvdbs[i])
386 gvdb_table_unref (engine->gvdbs[i]);
388 if (engine->lock_tables[i])
389 gvdb_table_unref (engine->lock_tables[i]);
391 if (engine->shm[i])
392 munmap (engine->shm[i], 1);
396 g_mutex_clear (&engine->lock);
398 g_free (engine->object_paths);
399 g_free (engine->bus_types);
400 g_free (engine->names);
401 g_free (engine->gvdbs);
402 g_free (engine->lock_tables);
403 g_free (engine->shm);
405 g_slice_free (DConfEngine, engine);
408 static GVariant *
409 dconf_engine_read_internal (DConfEngine *engine,
410 const gchar *key,
411 gboolean user,
412 gboolean system)
414 GVariant *value = NULL;
415 gint lowest;
416 gint limit;
417 gint i;
419 g_mutex_lock (&engine->lock);
421 dconf_engine_refresh (engine);
423 /* Bound the search space depending on the databases that we are
424 * interested in.
426 limit = system ? engine->n_dbs : 1;
427 lowest = user ? 0 : 1;
429 /* We want i equal to the index of the highest database containing a
430 * lock, or i == lowest if there is no lock. For that reason, we
431 * don't actually check the lowest database for a lock. That makes
432 * sense, because even if it had a lock, it would not change our
433 * search policy (which would be to check the lowest one first).
435 * Note that we intentionally dishonour 'limit' here -- we want to
436 * ensure that values in the user database are always ignored when
437 * locks are present.
439 for (i = MAX (engine->n_dbs - 1, lowest); lowest < i; i--)
440 if (engine->lock_tables[i] != NULL &&
441 gvdb_table_has_value (engine->lock_tables[i], key))
442 break;
444 while (i < limit && value == NULL)
446 if (engine->gvdbs[i] != NULL)
447 value = gvdb_table_get_value (engine->gvdbs[i], key);
448 i++;
451 g_mutex_unlock (&engine->lock);
453 return value;
456 GVariant *
457 dconf_engine_read (DConfEngine *engine,
458 const gchar *key)
460 return dconf_engine_read_internal (engine, key, TRUE, TRUE);
463 GVariant *
464 dconf_engine_read_default (DConfEngine *engine,
465 const gchar *key)
467 return dconf_engine_read_internal (engine, key, FALSE, TRUE);
470 GVariant *
471 dconf_engine_read_no_default (DConfEngine *engine,
472 const gchar *key)
474 return dconf_engine_read_internal (engine, key, TRUE, FALSE);
477 static void
478 dconf_engine_make_match_rule (DConfEngine *engine,
479 DConfEngineMessage *dcem,
480 const gchar *name,
481 const gchar *method_name)
483 gint i;
485 dcem->bus_name = "org.freedesktop.DBus";
486 dcem->object_path = "/org/freedesktop/DBus";
487 dcem->interface_name = "org.freedesktop.DBus";
488 dcem->method_name = method_name;
490 dcem->parameters = g_new (GVariant *, engine->n_dbs + 1);
491 for (i = 0; i < engine->n_dbs; i++)
493 gchar *rule;
495 rule = g_strdup_printf ("type='signal',"
496 "interface='ca.desrt.dconf.Writer',"
497 "path='%s',"
498 "arg0path='%s'",
499 engine->object_paths[i],
500 name);
501 dcem->parameters[i] = g_variant_new ("(s)", rule);
502 g_variant_ref_sink (dcem->parameters[i]);
503 g_free (rule);
505 dcem->parameters[i] = NULL;
507 dcem->bus_types = engine->bus_types;
508 dcem->n_messages = engine->n_dbs;
509 dcem->reply_type = G_VARIANT_TYPE_UNIT;
512 void
513 dconf_engine_watch (DConfEngine *engine,
514 const gchar *name,
515 DConfEngineMessage *dcem)
517 dconf_engine_make_match_rule (engine, dcem, name, "AddMatch");
520 void
521 dconf_engine_unwatch (DConfEngine *engine,
522 const gchar *name,
523 DConfEngineMessage *dcem)
525 dconf_engine_make_match_rule (engine, dcem, name, "RemoveMatch");
528 gboolean
529 dconf_engine_is_writable (DConfEngine *engine,
530 const gchar *name)
532 gboolean writable = TRUE;
534 /* Only check if we have more than one database */
535 if (engine->n_dbs > 1)
537 gint i;
539 g_mutex_lock (&engine->lock);
541 dconf_engine_refresh (engine);
543 /* Don't check for locks in the top database (i == 0). */
544 for (i = engine->n_dbs - 1; 0 < i; i--)
545 if (engine->lock_tables[i] != NULL &&
546 gvdb_table_has_value (engine->lock_tables[i], name))
548 writable = FALSE;
549 break;
552 g_mutex_unlock (&engine->lock);
555 return writable;
558 /* be conservative and fast: false negatives are OK */
559 static gboolean
560 is_dbusable (GVariant *value)
562 const gchar *type;
564 type = g_variant_get_type_string (value);
566 /* maybe definitely won't work.
567 * variant? too lazy to check inside...
569 if (strchr (type, 'v') || strchr (type, 'm'))
570 return FALSE;
572 /* XXX: we could also check for '{}' not inside an array...
573 * but i'm not sure we want to support that anyway.
576 /* this will avoid any too-deeply-nested limits */
577 return strlen (type) < 32;
580 static GVariant *
581 fake_maybe (GVariant *value)
583 GVariantBuilder builder;
585 g_variant_builder_init (&builder, G_VARIANT_TYPE ("av"));
587 if (value != NULL)
589 if (is_dbusable (value))
590 g_variant_builder_add (&builder, "v", value);
592 else
594 GVariant *variant;
595 GVariant *ay;
597 variant = g_variant_new_variant (value);
598 ay = g_variant_new_from_data (G_VARIANT_TYPE_BYTESTRING,
599 g_variant_get_data (variant),
600 g_variant_get_size (variant),
601 TRUE,
602 (GDestroyNotify) g_variant_unref,
603 variant);
604 g_variant_builder_add (&builder, "v", ay);
606 g_variant_builder_add (&builder, "v",
607 g_variant_new_string ("serialised GVariant"));
611 return g_variant_builder_end (&builder);
614 static void
615 dconf_engine_dcem (DConfEngine *engine,
616 DConfEngineMessage *dcem,
617 const gchar *method_name,
618 const gchar *format_string,
619 ...)
621 va_list ap;
623 dcem->bus_name = "ca.desrt.dconf";
624 dcem->object_path = engine->object_paths[0];
625 dcem->interface_name = "ca.desrt.dconf.Writer";
626 dcem->method_name = method_name;
627 dcem->parameters = g_new (GVariant *, 2);
628 dcem->n_messages = 1;
630 va_start (ap, format_string);
631 dcem->parameters[0] = g_variant_new_va (format_string, NULL, &ap);
632 g_variant_ref_sink (dcem->parameters[0]);
633 dcem->parameters[1] = NULL;
634 va_end (ap);
636 dcem->bus_types = engine->bus_types;
637 dcem->reply_type = G_VARIANT_TYPE ("(s)");
640 gboolean
641 dconf_engine_write (DConfEngine *engine,
642 const gchar *name,
643 GVariant *value,
644 DConfEngineMessage *dcem,
645 GError **error)
647 dconf_engine_dcem (engine, dcem,
648 "Write", "(s@av)",
649 name, fake_maybe (value));
651 return TRUE;
654 gboolean
655 dconf_engine_write_many (DConfEngine *engine,
656 const gchar *prefix,
657 const gchar * const *keys,
658 GVariant **values,
659 DConfEngineMessage *dcem,
660 GError **error)
662 GVariantBuilder builder;
663 gsize i;
665 g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sav)"));
667 for (i = 0; keys[i]; i++)
668 g_variant_builder_add (&builder, "(s@av)",
669 keys[i], fake_maybe (values[i]));
671 dconf_engine_dcem (engine, dcem, "WriteMany", "(sa(sav))", prefix, &builder);
673 return TRUE;
676 gchar **
677 dconf_engine_list (DConfEngine *engine,
678 const gchar *dir,
679 gint *length)
681 gchar **list;
683 g_mutex_lock (&engine->lock);
685 dconf_engine_refresh (engine);
687 if (engine->gvdbs[0])
688 list = gvdb_table_list (engine->gvdbs[0], dir);
689 else
690 list = NULL;
692 if (list == NULL)
693 list = g_new0 (char *, 1);
695 if (length)
696 *length = g_strv_length (list);
698 g_mutex_unlock (&engine->lock);
700 return list;
703 gboolean
704 dconf_engine_decode_notify (DConfEngine *engine,
705 const gchar *anti_expose,
706 const gchar **path,
707 const gchar ***rels,
708 guint bus_type,
709 const gchar *sender,
710 const gchar *iface,
711 const gchar *method,
712 GVariant *body)
714 if (strcmp (iface, "ca.desrt.dconf.Writer") || strcmp (method, "Notify"))
715 return FALSE;
717 if (!g_variant_is_of_type (body, G_VARIANT_TYPE ("(sass)")))
718 return FALSE;
720 if (anti_expose)
722 const gchar *ae;
724 g_variant_get_child (body, 2, "&s", &ae);
726 if (strcmp (ae, anti_expose) == 0)
727 return FALSE;
730 g_variant_get (body, "(&s^a&ss)", path, rels, NULL);
732 return TRUE;
735 gboolean
736 dconf_engine_decode_writability_notify (const gchar **path,
737 const gchar *iface,
738 const gchar *method,
739 GVariant *body)
741 if (strcmp (iface, "ca.desrt.dconf.Writer") ||
742 strcmp (method, "WritabilityNotify"))
743 return FALSE;
745 if (!g_variant_is_of_type (body, G_VARIANT_TYPE ("(s)")))
746 return FALSE;
748 g_variant_get_child (body, 0, "&s", path);
750 return TRUE;