[dconf/shm] Add "shm" region to avoid constantly rebuilding



commit 7f8dbae8ff1c0c9ff486d05e6bf9a54098b1c1da
Author: Ryan Lortie <desrt desrt ca>
Date:   Fri May 14 19:22:45 2010 +0200

    Add "shm" region to avoid constantly rebuilding

 gsettings/Makefile.am            |    4 +-
 gsettings/dconfdatabase.c        |   70 +++++++++-
 gsettings/dconfsettingsbackend.c |    1 +
 gsettings/dconfshmreader.c       |  217 ++++++++++++++++++++++++++++++
 gsettings/dconfshmreader.h       |   15 ++
 service/Makefile.am              |    7 +-
 service/dconf-rebuilder.c        |    2 +-
 service/dconfchangeset.c         |   78 +++++++++++
 service/dconfchangeset.h         |   11 ++
 service/dconfjournal.c           |  268 ++++++++++++++++++++++++++++++++++++++
 service/dconfjournal.h           |    9 ++
 service/dconfshmwriter.c         |  234 +++++++++++++++++++++++++++++++++
 service/dconfshmwriter.h         |   19 +++
 service/service.c                |   99 ++++++++++++++-
 14 files changed, 1021 insertions(+), 13 deletions(-)
---
diff --git a/gsettings/Makefile.am b/gsettings/Makefile.am
index 41b7cc2..a5831a1 100644
--- a/gsettings/Makefile.am
+++ b/gsettings/Makefile.am
@@ -1,10 +1,12 @@
-AM_CFLAGS = $(gio_CFLAGS) -I$(top_srcdir)/gvdb
+AM_CFLAGS = $(gio_CFLAGS) -I$(top_srcdir)/gvdb -Wall -Wmissing-prototypes -g
 
 giomodules_LTLIBRARIES = libdconfsettings.la
 
 libdconfsettings_la_LIBADD = $(gio_LIBS)
 libdconfsettings_la_SOURCES = \
 	../gvdb/gvdb-reader.c	\
+	dconfshmreader.h	\
+	dconfshmreader.c	\
 	dconfdatabase.h		\
 	dconfdatabase.c		\
 	dconfsettingsbackend.c
diff --git a/gsettings/dconfdatabase.c b/gsettings/dconfdatabase.c
index 01f983b..8173c7a 100644
--- a/gsettings/dconfdatabase.c
+++ b/gsettings/dconfdatabase.c
@@ -19,13 +19,16 @@
  * Author: Ryan Lortie <desrt desrt ca>
  */
 
+#include "dconfdatabase.h"
+
 #define G_SETTINGS_ENABLE_BACKEND
 #include <gio/gsettingsbackend.h>
-#include "dconfdatabase.h"
 
 #include <gio/gio.h>
 
 #include <string.h>
+
+#include "dconfshmreader.h"
 #include "gvdb-reader.h"
 
 typedef struct _Outstanding Outstanding;
@@ -34,10 +37,15 @@ struct _DConfDatabase
 {
   GStaticMutex lock;
 
+  gchar *filename;
+  gchar *logfile;
   GDBusConnection *bus;
+  DConfShmReader *shm;
   GvdbTable *value_table;
   GSList *backends;
   const gchar *context;
+  const gchar *log;
+  gsize log_size;
 
   Outstanding *outstanding;
   guint64 anti_expose;
@@ -246,6 +254,9 @@ dconf_database_read (DConfDatabase *database,
   if (dconf_database_scan_outstanding (database, key, &value))
     return value;
 
+  if (dconf_shm_reader_lookup (database->shm, key, &value))
+    return value;
+
   if (database->value_table == NULL)
     return NULL;
 
@@ -311,6 +322,23 @@ dconf_database_filter_function (GDBusConnection *connection,
     }
 }
 
+static void
+dconf_database_ensure_bus (DConfDatabase *database)
+{
+  if (database->bus != NULL)
+    return;
+
+  g_static_mutex_lock (&database->lock);
+  if (database->bus == NULL)
+    {
+      database->bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+      g_dbus_connection_add_filter (database->bus,
+                                    dconf_database_filter_function,
+                                    database, NULL);
+    }
+  g_static_mutex_unlock (&database->lock);
+}
+
 void
 dconf_database_write_tree (DConfDatabase  *database,
                            GTree          *tree,
@@ -321,6 +349,8 @@ dconf_database_write_tree (DConfDatabase  *database,
   GVariant **values;
   gchar *path;
 
+  dconf_database_ensure_bus (database);
+
   serial = dconf_database_new_outstanding (database, NULL, NULL, NULL, tree);
   g_settings_backend_flatten_tree (tree, &path, &keys, &values);
 
@@ -401,6 +431,8 @@ dconf_database_write (DConfDatabase *database,
   volatile guint32 *serial;
   GDBusMessage *message;
 
+  dconf_database_ensure_bus (database);
+
   serial = dconf_database_new_outstanding (database, NULL,
                                            path_or_key, value,
                                            NULL);
@@ -427,6 +459,8 @@ send_match_rule (DConfDatabase *database,
   GDBusMessage *message;
   gchar *rule;
 
+  dconf_database_ensure_bus (database);
+
   rule = g_strdup_printf ("interface='ca.desrt.dconf.Writer',"
                           "arg1path='%s'", name);
   message = g_dbus_message_new_method_call ("org.freedesktop.DBus", "/",
@@ -434,6 +468,13 @@ send_match_rule (DConfDatabase *database,
   g_dbus_message_set_body (message, g_variant_new ("(s)", (rule)));
   g_dbus_message_set_flags (message, G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED);
   g_dbus_connection_send_message (database->bus, message, NULL, NULL);
+  /* FIXME: there's a small race here.
+   * if an event occurs between the time that we send this message
+   * and it gets to the bus then we could miss a change
+   *
+   * we could fix that by blocking for the reply here, but maybe
+   * it is not worth it.
+   */
   g_object_unref (message);
   g_free (rule);
 }
@@ -452,6 +493,23 @@ dconf_database_unsubscribe (DConfDatabase *database,
   send_match_rule (database, "RemoveMatch", name);
 }
 
+static void
+dconf_database_ping (DConfDatabase *database)
+{
+  GDBusMessage *message, *reply;
+
+  dconf_database_ensure_bus (database);
+
+  message = g_dbus_message_new_method_call ("ca.desrt.dconf", "/",
+                                            "ca.desrt.dconf.Writer", "Ping");
+  reply = g_dbus_connection_send_message_with_reply_sync (database->bus,
+                                                          message, -1, NULL,
+                                                          NULL, NULL);
+  /* XXX: what if it failed? */
+  g_object_unref (message);
+  g_object_unref (reply);
+}
+
 DConfDatabase *
 dconf_database_get_for_backend (gpointer backend)
 {
@@ -465,11 +523,11 @@ dconf_database_get_for_backend (gpointer backend)
       g_static_mutex_init (&database->lock);
       database->outstanding = NULL;
       database->backends = g_slist_prepend (database->backends, backend);
-      database->bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
-      g_dbus_connection_add_filter (database->bus,
-                                    dconf_database_filter_function,
-                                    database, NULL);
-      dconf_database_reopen_file (database);
+      database->filename = g_build_filename (g_get_user_config_dir (), "dconf", NULL);
+      database->shm = dconf_shm_reader_new ("");
+
+      if (!dconf_shm_reader_service_online (database->shm))
+        dconf_database_ping (database);
 
       g_once_init_leave (&instance, (gsize) database);
     }
diff --git a/gsettings/dconfsettingsbackend.c b/gsettings/dconfsettingsbackend.c
index 7a7d69c..96ebc28 100644
--- a/gsettings/dconfsettingsbackend.c
+++ b/gsettings/dconfsettingsbackend.c
@@ -34,6 +34,7 @@ typedef struct
   DConfDatabase *database;
 } DConfSettingsBackend;
 
+static GType dconf_settings_backend_get_type (void);
 G_DEFINE_TYPE (DConfSettingsBackend,
                dconf_settings_backend,
                G_TYPE_SETTINGS_BACKEND)
diff --git a/gsettings/dconfshmreader.c b/gsettings/dconfshmreader.c
new file mode 100644
index 0000000..cb3a534
--- /dev/null
+++ b/gsettings/dconfshmreader.c
@@ -0,0 +1,217 @@
+#include "dconfshmreader.h"
+
+#include <gio/gio.h>
+
+#include <sys/mman.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#define SHM_SIZE (4 * 1024 * 1024)
+
+struct shm_header
+{
+  guint32 prev_offset;
+  guint32 hashval;
+  guint32 datalen;
+  guint32 strlen;
+};
+
+
+struct _DConfShmReader
+{
+  gpointer data;
+  gchar *name;
+};
+
+static const gchar *
+dconf_shm_reader_get_directory (void)
+{
+  static gsize initialised;
+  static gchar *path;
+
+  if (g_once_init_enter (&initialised))
+    {
+      const gchar *envdir;
+
+      envdir = getenv ("XDG_SESSION_TMPDIR");
+
+      if (envdir == NULL)
+        {
+          GDBusMessage *message, *reply;
+          GDBusConnection *connection;
+
+          connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+          message = g_dbus_message_new_method_call ("ca.desrt.dconf", "/",
+                                                    "ca.desrt.dconf.Service",
+                                                    "GetShmDirectory");
+          reply = g_dbus_connection_send_message_with_reply_sync (connection,
+                                                                  message, -1,
+                                                                  NULL, NULL,
+                                                                  NULL);
+          g_object_unref (message);
+
+          if (reply != NULL)
+            {
+              g_variant_get (g_dbus_message_get_body (reply), "(s)", &path);
+              g_object_unref (reply);
+            }
+        }
+
+      else
+        path = g_build_filename (envdir, "dconf", NULL);
+
+      g_once_init_leave (&initialised, 1);
+    }
+
+  return path;
+}
+
+gboolean
+dconf_shm_reader_service_online (DConfShmReader *shm)
+{
+  return shm->data && !!*(guint32 *) shm->data;
+}
+
+static void
+dconf_shm_reader_map (DConfShmReader *shm)
+{
+  gchar *filename;
+  gint fd;
+
+  shm->data = NULL;
+
+  filename = g_build_filename (dconf_shm_reader_get_directory (), "shm", NULL);
+  fd = open (filename, O_RDWR | O_CREAT);
+  g_free (filename);
+
+  if (fd < 0)
+    return;
+
+  /* XXX stat first? */
+  if (posix_fallocate (fd, 0, 8) == 0)
+    {
+      shm->data = mmap (NULL, SHM_SIZE, PROT_READ, MAP_SHARED, fd, 0);
+
+      if (shm->data == MAP_FAILED)
+        shm->data = NULL;
+    }
+
+  close (fd);
+}
+
+static guint32
+dconf_shm_reader_start (DConfShmReader *shm)
+{
+  guint32 offset;
+
+  if (shm->data != NULL)
+    {
+      offset = *(guint32 *) shm->data;
+
+      if (offset < SHM_SIZE)
+        {
+          if (offset == 1)
+            offset = 0;
+
+          return offset;
+        }
+
+      munmap (shm->data, SHM_SIZE);
+    }
+
+  dconf_shm_reader_map (shm);
+
+  if (shm->data == NULL)
+    return 0;
+
+  offset = *(guint32 *) shm->data;
+
+  if (offset < SHM_SIZE)
+    {
+      if (offset == 1)
+        offset = 0;
+
+      return offset;
+    }
+
+  return 0;
+}
+
+gboolean
+dconf_shm_reader_lookup (DConfShmReader  *shm,
+                         const gchar     *key,
+                         GVariant       **value)
+{
+  guint32 hashval;
+  guint32 offset;
+  gsize keylen;
+
+  offset = dconf_shm_reader_start (shm);
+
+  hashval = g_str_hash (key);
+  keylen = strlen (key) + 1;
+
+  if (hashval == 0)
+    hashval = 1;
+
+  while (offset)
+    {
+      struct shm_header *header;
+      const guchar *this_value;
+      const guchar *this_key;
+      const guchar *data;
+
+      data = shm->data;
+      data += offset;
+
+      header = (struct shm_header *) data;
+      this_value = data + sizeof *header;
+      this_key = this_value + header->datalen;
+
+      if (header->hashval == hashval && header->strlen == keylen &&
+          memcmp (this_key, key, keylen) == 0)
+        {
+          if (header->datalen)
+            {
+              GVariant *variant;
+              gpointer mydata;
+
+              mydata = g_memdup (this_value, header->datalen);
+              variant = g_variant_new_from_data (G_VARIANT_TYPE_VARIANT,
+                                                 mydata, header->datalen,
+                                                 TRUE, g_free, mydata);
+              *value = g_variant_get_variant (variant);
+              g_variant_unref (variant);
+            }
+          else
+            *value = NULL;
+
+          return TRUE;
+        }
+
+      else if (header->hashval == 0 && header->strlen < keylen &&
+               memcmp (this_key, key, header->strlen) == 0)
+        {
+          *value = NULL;
+          return TRUE;
+        }
+
+      offset = header->prev_offset;
+    }
+
+  return FALSE;
+}
+
+DConfShmReader *
+dconf_shm_reader_new (const gchar *name)
+{
+  DConfShmReader *shm;
+
+  shm = g_slice_new (DConfShmReader);
+  shm->name = g_strdup (name);
+  dconf_shm_reader_map (shm);
+
+  return shm;
+}
diff --git a/gsettings/dconfshmreader.h b/gsettings/dconfshmreader.h
new file mode 100644
index 0000000..52cf3a8
--- /dev/null
+++ b/gsettings/dconfshmreader.h
@@ -0,0 +1,15 @@
+#ifndef _dconfshmreader_h_
+#define _dconfshmreader_h_
+
+#include <glib.h>
+
+typedef struct _DConfShmReader DConfShmReader;
+
+DConfShmReader * dconf_shm_reader_new (const gchar *name);
+gboolean dconf_shm_reader_lookup (DConfShmReader *shm,
+                                  const gchar     *key,
+                                  GVariant       **value);
+gboolean
+dconf_shm_reader_service_online (DConfShmReader *shm);
+
+#endif /* _dconfshmreader_h_ */
diff --git a/service/Makefile.am b/service/Makefile.am
index dbd509e..e8f7330 100644
--- a/service/Makefile.am
+++ b/service/Makefile.am
@@ -1,4 +1,4 @@
-AM_CFLAGS = $(gio_CFLAGS) -I$(top_srcdir)/gvdb
+AM_CFLAGS = $(gio_CFLAGS) -I$(top_srcdir)/gvdb -Wall -Wmissing-prototypes -g
 
 libexec_PROGRAMS = dconf-service
 
@@ -9,7 +9,10 @@ dconf_service_LDADD = $(gio_LIBS)
 dconf_service_SOURCES = \
 	../gvdb/gvdb-builder.c	\
 	../gvdb/gvdb-reader.c	\
-	dconf-rebuilder.h	\
+	dconfshmwriter.h	\
+	dconfshmwriter.c	\
+	dconfchangeset.h	\
+	dconfchangeset.c	\
 	dconf-rebuilder.h	\
 	dconf-rebuilder.c	\
 	service.c
diff --git a/service/dconf-rebuilder.c b/service/dconf-rebuilder.c
index e9a8923..93ea3f6 100644
--- a/service/dconf-rebuilder.c
+++ b/service/dconf-rebuilder.c
@@ -23,8 +23,8 @@
 
 #include <string.h>
 
-#include "gvdb-reader.h"
 #include "gvdb-builder.h"
+#include "gvdb-reader.h"
 
 typedef struct
 {
diff --git a/service/dconfchangeset.c b/service/dconfchangeset.c
new file mode 100644
index 0000000..5750def
--- /dev/null
+++ b/service/dconfchangeset.c
@@ -0,0 +1,78 @@
+#include "dconfchangeset.h"
+
+#include <string.h>
+
+struct _DConfChangeset
+{
+  GTree *blocks;
+  GTree *items;
+};
+
+static gint
+prefixcmp (gconstpointer a,
+           gconstpointer b)
+{
+  if (g_str_has_prefix (a, b))
+    return 0;
+
+  return strcmp (a, b);
+}
+
+DConfChangeset *
+dconf_changeset_new (void)
+{
+  DConfChangeset *set;
+
+  set = g_slice_new (DConfChangeset);
+  set->blocks = g_tree_new_full ((GCompareDataFunc) prefixcmp,
+                            NULL, g_free, NULL);
+  set->items = g_tree_new_full ((GCompareDataFunc) strcmp, NULL,
+                                g_free, (GDestroyNotify) g_variant_unref);
+
+  return set;
+}
+
+void
+dconf_changeset_write (DConfChangeset *set,
+                       const gchar    *key,
+                       GVariant       *value)
+{
+  if (g_tree_lookup (set->blocks, key))
+    return;
+
+  if (g_str_has_suffix (key, "/"))
+    {
+      g_assert (value == NULL);
+
+      g_tree_insert (set->blocks, g_strdup (key), (void *) 1);
+
+      return;
+    }
+
+  if (g_tree_lookup_extended (set->items, key, NULL, NULL))
+    return;
+
+  if (value != NULL)
+    g_variant_ref_sink (value);
+
+  g_tree_insert (set->items, g_strdup (key), value);
+}
+
+void
+dconf_changeset_merge (DConfChangeset *set,
+                       const gchar    *path,
+                       GVariant       *array)
+{
+  GVariantIter iter;
+  const gchar *rel;
+  GVariant *value;
+
+  g_variant_iter_init (&iter, array);
+
+  while (g_variant_iter_next (&iter, "{&sm v}", &rel, &value))
+    {
+      gchar *key = g_strconcat (path, rel, NULL);
+      dconf_changeset_write (set, key, value);
+      g_free (key);
+    }
+}
diff --git a/service/dconfchangeset.h b/service/dconfchangeset.h
new file mode 100644
index 0000000..fbfce56
--- /dev/null
+++ b/service/dconfchangeset.h
@@ -0,0 +1,11 @@
+#include <glib.h>
+
+typedef struct _DConfChangeset DConfChangeset;
+
+DConfChangeset * dconf_changeset_new (void);
+void dconf_changeset_write (DConfChangeset *set,
+                            const gchar    *key,
+                            GVariant       *value);
+void dconf_changeset_merge (DConfChangeset *set,
+                            const gchar    *path,
+                            GVariant       *items);
diff --git a/service/dconfjournal.c b/service/dconfjournal.c
new file mode 100644
index 0000000..387e736
--- /dev/null
+++ b/service/dconfjournal.c
@@ -0,0 +1,268 @@
+#include "dconfjournal.h"
+
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+struct _DConfJournal
+{
+  GString *buffer;
+  gchar *filename;
+  gboolean locked;
+  gint fd;
+};
+
+static gboolean
+dconf_journal_fcntl_lock (DConfJournal *journal,
+                          gint          cmd,
+                          gshort        type,
+                          gshort        whence,
+                          goffset       start,
+                          goffset       length)
+{
+  struct flock lock;
+  gint s;
+
+  lock.l_type = type;
+  lock.l_whence = whence;
+  lock.l_start = start;
+  lock.l_len = length;
+
+  do
+    s = fcntl (journal->fd, cmd, &lock);
+  while (s == -1 && errno == EINTR);
+
+  return s == 0;
+}
+
+void
+dconf_journal_lock (DConfJournal *journal)
+{
+  g_assert (!journal->locked);
+  dconf_journal_fcntl_lock (journal, F_SETLKW, F_WRLCK, SEEK_SET, 0, 0);
+  journal->locked = TRUE;
+}
+
+void
+dconf_journal_unlock (DConfJournal *journal)
+{
+  g_assert (journal->locked);
+
+  fdatasync (journal->fd);
+  dconf_journal_fcntl_lock (journal, F_SETLKW, F_UNLCK, SEEK_SET, 0, 0);
+  journal->locked = FALSE;
+}
+
+void
+dconf_journal_unlink (DConfJournal *journal)
+{
+  g_assert (journal->locked);
+
+  fdatasync (journal->fd);
+  unlink (journal->filename);
+  close (journal->fd);
+  journal->fd = -1;
+
+  journal->locked = FALSE;
+}
+
+static gboolean
+dconf_journal_read_into_string (DConfJournal *journal,
+                                GString      *string)
+{
+  gsize length = string->len;
+  gssize s;
+
+  g_string_set_size (string, length + 4096);
+
+  do
+    s = read (journal->fd, string->str + length, 4096);
+  while (s == -1 && errno == EINTR);
+
+  if (s < 1)
+    {
+      g_string_set_size (string, length);
+      return FALSE;
+    }
+
+  g_string_set_size (string, length + s);
+
+  return TRUE;
+}
+
+static gboolean
+dconf_journal_incoming_line (DConfJournal *journal,
+                             const gchar  *start,
+                             const gchar  *end)
+{
+  gsize length = end - start;
+  gboolean handled = FALSE;
+  gsize i;
+
+  if (length == 7 && memcmp (start, "rebuild", 7) == 0)
+    return TRUE;
+
+  for (i = 0; i < length; i++)
+    if (start[i] == ' ')
+      break;
+
+  if (i == length)
+    i = 0;
+
+  if (i == 5 && memcmp (start, "write", 5) == 0)
+    {
+      const gchar *endptr;
+      GVariant *tuple;
+
+      tuple = g_variant_parse (G_VARIANT_TYPE ("(smv)"),
+                               start + i, end, &endptr, NULL);
+
+      if (tuple != NULL)
+        {
+          if (endptr == end)
+            {
+              g_print ("got a write\n");
+              handled = TRUE;
+            }
+
+          g_variant_unref (tuple);
+        }
+    }
+
+  else if (i == 5 && memcmp (start, "merge", 5) == 0)
+    {
+      const gchar *endptr;
+      GVariant *tuple;
+
+      tuple = g_variant_parse (G_VARIANT_TYPE ("(sa{smv})"),
+                               start + i, end, &endptr, NULL);
+
+      if (tuple != NULL)
+        {
+          if (endptr == end)
+            {
+              g_print ("got a merge\n");
+              handled = TRUE;
+            }
+
+          g_variant_unref (tuple);
+        }
+     }
+
+  g_warning ("unhandled journal entry (corrupt journal?)");
+
+  return FALSE;
+}
+
+void
+dconf_journal_read (DConfJournal *journal)
+{
+  GString *buffer;
+  gsize scanned;
+  gsize start;
+
+  buffer = g_string_new (NULL);
+  start = scanned = 0;
+
+  while (dconf_journal_read_into_string (journal, buffer))
+    {
+      while (scanned < buffer->len)
+        {
+          if (buffer->str[scanned] == '\n')
+            {
+              gboolean rebuilt;
+
+              if (dconf_journal_incoming_line (journal,
+                                               buffer->str + start,
+                                               buffer->str + scanned))
+                {
+                  g_print ("replaced by seq\n");
+                  return;
+                }
+
+              start = scanned + 1;
+            }
+
+          scanned++;
+        }
+    }
+
+  g_string_free (buffer, TRUE);
+}
+
+void
+dconf_journal_append (DConfJournal *journal,
+                      const gchar  *text)
+{
+  g_assert (journal->locked);
+
+  lseek (journal->fd, 0, SEEK_END);
+  write (journal->fd, text, strlen (text));
+}
+
+static gboolean
+dconf_journal_is_on_nfs (DConfJournal *journal)
+{
+  return FALSE;
+}
+
+DConfJournal *
+dconf_journal_new (const gchar *filename)
+{
+  DConfJournal *journal;
+
+  journal = g_slice_new (DConfJournal);
+  journal->fd = open (filename, O_RDWR | O_CREAT, 0600);
+  journal->filename = g_strdup (filename);
+
+  return journal;
+}
+
+static void
+dconf_journal_do_append (DConfJournal *journal,
+                         GString      *string)
+{
+  gssize s;
+
+  do
+    s = write (journal->fd, string->str, string->len);
+  while (s == -1 && errno == EINTR);
+
+  g_assert (s == string->len);
+
+  g_string_free (string, TRUE);
+}
+
+gboolean
+dconf_journal_add_write (DConfJournal *journal,
+                         GVariant     *tuple)
+{
+  GString *record;
+
+  record = g_string_new ("write ");
+  g_variant_print_string (tuple, record, FALSE);
+  g_string_append_c (record, '\n');
+
+  dconf_journal_do_append (journal, record);
+}
+
+gboolean
+dconf_journal_add_merge (DConfJournal *journal,
+                         GVariant     *tuple)
+{
+  GString *record;
+
+  record = g_string_new ("merge ");
+  g_variant_print_string (tuple, record, FALSE);
+  g_string_append_c (record, '\n');
+
+  dconf_journal_do_append (journal, record);
+}
+
+gboolean
+dconf_journal_rebuild (DConfJournal *journal)
+{
+}
+
+
diff --git a/service/dconfjournal.h b/service/dconfjournal.h
new file mode 100644
index 0000000..b384360
--- /dev/null
+++ b/service/dconfjournal.h
@@ -0,0 +1,9 @@
+#ifndef _dconfjournal_h_
+#define _dconfjournal_h_
+
+#include <glib.h>
+
+typedef struct _DConfJournal DConfJournal;
+
+
+#endif /* _dconfjournal_h_ */
diff --git a/service/dconfshmwriter.c b/service/dconfshmwriter.c
new file mode 100644
index 0000000..3da05f8
--- /dev/null
+++ b/service/dconfshmwriter.c
@@ -0,0 +1,234 @@
+#define SHM_SIZE  (4 * 1024 * 1024)
+#define SHM_ITEMS  1024
+
+#define _BSD_SOURCE
+#define _XOPEN_SOURCE 600
+
+#include "dconfshmwriter.h"
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+
+struct shm_header
+{
+  guint32 prev_offset;
+  guint32 hashval;
+  guint32 datalen;
+  guint32 strlen;
+};
+
+struct _DConfShmWriter
+{
+  guint32 last_ptr;
+  guint32 offset;
+  gpointer data;
+  gint n_items;
+  gint fd;
+};
+
+static inline gsize
+fill_iov (struct iovec  *vector,
+          gconstpointer  data,
+          gsize          size)
+{
+  vector->iov_base = (void *) data;
+  vector->iov_len = size;
+
+  return size;
+}
+
+gboolean
+dconf_shm_writer_append (DConfShmWriter *shm,
+                         const gchar    *name,
+                         GVariant       *value)
+{
+  static const gchar zero[7];
+  struct shm_header header;
+  struct iovec iov[4];
+  gconstpointer data;
+  gssize result;
+  gsize total;
+
+  if (shm->n_items == SHM_ITEMS)
+    return FALSE;
+
+  header.prev_offset = shm->last_ptr;
+  header.strlen = strlen (name) + 1;
+
+  if (name[header.strlen - 1] != '/')
+    {
+      header.hashval = g_str_hash (name);
+      if (header.hashval == 0)
+        header.hashval = 1;
+    }
+  else
+    header.hashval = 0;
+
+  if (value != NULL)
+    {
+      header.datalen = g_variant_get_size (value);
+      data = g_variant_get_data (value);
+    }
+  else
+    {
+      header.datalen = 0;
+      data = NULL;
+    }
+
+  total  = fill_iov (iov + 0, &header, sizeof header);
+  total += fill_iov (iov + 1, data, header.datalen);
+  total += fill_iov (iov + 2, name, header.strlen);
+  total += fill_iov (iov + 3, zero, (-total) & 7u);
+
+  if (shm->offset + total > SHM_SIZE)
+    return FALSE;
+
+  shm->last_ptr = shm->offset;
+  shm->offset += total;
+
+  result = writev (shm->fd, iov, 4);
+  g_assert (result == total);
+  shm->n_items++;
+
+  return TRUE;
+}
+
+void
+dconf_shm_writer_sync (DConfShmWriter *shm)
+{
+  guint32 output;
+  gssize result;
+
+  if (shm->last_ptr)
+    output = shm->last_ptr;
+  else
+    output = 1;
+
+  result = pwrite (shm->fd, &output, sizeof output, 0);
+  g_assert (result == sizeof output);
+}
+
+const gchar *
+dconf_shm_writer_get_path (void)
+{
+  static gsize initialised;
+  static gchar *path;
+
+  if (g_once_init_enter (&initialised))
+    {
+      gchar template[] = "/tmp/dconf-session.XXXXXX";
+      const gchar *envdir;
+
+      envdir = getenv ("XDG_SESSION_TMPDIR");
+
+      if (envdir != NULL)
+        {
+          path = g_build_filename (envdir, "dconf", NULL);
+          mkdir (path, 0700);
+        }
+      else
+        path = g_strdup (mkdtemp (template));
+
+      g_once_init_leave (&initialised, 1);
+    }
+
+  return path;
+}
+
+static gint
+prefixcmp (gconstpointer a,
+           gconstpointer b)
+{
+  if (g_str_has_prefix (a, b))
+    return 0;
+
+  return strcmp (a, b);
+}
+
+GTree *
+dconf_shm_writer_get_changes (DConfShmWriter *shm)
+{
+  guint32 offset;
+  GTree *blocks;
+  GTree *items;
+
+  items = g_tree_new_full ((GCompareDataFunc) strcmp, NULL,
+                           NULL, (GDestroyNotify) g_variant_unref);
+  blocks = g_tree_new (prefixcmp);
+  offset = shm->last_ptr;
+
+  while (offset)
+    {
+      struct shm_header *header;
+      const guchar *this_value;
+      const guchar *this_key;
+      const guchar *data;
+
+      data = shm->data;
+      data += offset;
+
+      header = (struct shm_header *) data;
+      this_value = data + sizeof *header;
+      this_key = this_value + header->datalen;
+
+      if (!g_tree_lookup (blocks, this_key))
+        {
+          if (this_key[header->strlen - 2] == '/')
+            g_tree_insert (blocks, (gpointer) this_key, (gpointer) this_key);
+
+          else if (!g_tree_lookup (items, this_key))
+            {
+              GVariant *value;
+
+              value = g_variant_new_from_data (G_VARIANT_TYPE_VARIANT,
+                                               this_value, header->datalen,
+                                               TRUE, NULL, NULL);
+              g_tree_insert (items, (gpointer) this_key,
+                             g_variant_ref_sink (value));
+            }
+        }
+
+      offset = header->prev_offset;
+    }
+
+  g_tree_unref (blocks);
+
+  return items;
+}
+
+DConfShmWriter *
+dconf_shm_writer_new (void)
+{
+  DConfShmWriter *shm;
+  gchar *filename;
+  gint fd;
+
+  filename = g_build_filename (dconf_shm_writer_get_path (), "shm", NULL);
+  /* NB: need O_RDWR here for emulated posix_fallocated()
+   */
+  fd = open (filename, O_RDWR | O_CREAT, 0600);
+  if (fd >= 0 && posix_fallocate (fd, 0, 8) != 0)
+    {
+      close (fd);
+      fd = -1;
+    }
+  lseek (fd, 8, SEEK_SET);
+  g_free (filename);
+
+  if (fd < 0)
+    return NULL;
+
+  shm = g_slice_new (DConfShmWriter);
+  shm->last_ptr = 0;
+  shm->offset = 8;
+  shm->data = mmap (NULL, SHM_SIZE, PROT_READ, MAP_SHARED, fd, 0);
+  shm->n_items = 0;
+  shm->fd = fd;
+
+  return shm;
+}
diff --git a/service/dconfshmwriter.h b/service/dconfshmwriter.h
new file mode 100644
index 0000000..3c225f2
--- /dev/null
+++ b/service/dconfshmwriter.h
@@ -0,0 +1,19 @@
+#ifndef _dconfshmwriter_h_
+#define _dconfshmwriter_h_
+
+#include <glib.h>
+
+typedef struct _DConfShmWriter DConfShmWriter;
+
+const gchar * dconf_shm_writer_get_path (void);
+DConfShmWriter * dconf_shm_writer_new (void);
+gboolean
+dconf_shm_writer_append (DConfShmWriter *shm,
+                         const gchar    *name,
+                         GVariant       *value);
+void dconf_shm_writer_sync (DConfShmWriter *shm);
+GTree *
+dconf_shm_writer_get_changes (DConfShmWriter *shm)
+     ;
+
+#endif /* _dconfshmwriter_h_ */
diff --git a/service/service.c b/service/service.c
index fe1fde2..3d59e72 100644
--- a/service/service.c
+++ b/service/service.c
@@ -24,6 +24,8 @@
 #include <stdio.h>
 
 #include "dconf-rebuilder.h"
+#include "dconfchangeset.h"
+#include "dconfshmwriter.h"
 
 static const GDBusArgInfo name_arg = { -1, "name", "s" };
 static const GDBusArgInfo names_arg = { -1, "names", "as" };
@@ -58,11 +60,63 @@ static const GDBusInterfaceInfo writer_interface = {
 
 typedef struct
 {
+  DConfShmWriter *shm;
   GMainLoop *loop;
   guint64 serial;
+  gchar *sessiondir;
   gchar *path;
+  guint rebuild_timeout_id;
 } DConfWriter;
 
+static gboolean
+do_rebuild (DConfWriter     *writer,
+            DConfChangeset  *changeset,
+            GError         **error)
+{
+  return TRUE;
+}
+
+static gboolean
+rebuild_in_timeout (gpointer data)
+{
+  DConfWriter *writer = data;
+
+  writer->rebuild_timeout_id = 0;
+  do_rebuild (writer, NULL, NULL);
+  /* what do we do if it fails? */
+
+  return FALSE;
+}
+
+static void
+cancel_scheduled_rebuild (DConfWriter *writer)
+{
+  if (writer->rebuild_timeout_id)
+    {
+      g_source_remove (writer->rebuild_timeout_id);
+      writer->rebuild_timeout_id = 0;
+    }
+}
+
+static void
+schedule_rebuild (DConfWriter *writer)
+{
+  cancel_scheduled_rebuild (writer);
+
+  writer->rebuild_timeout_id =
+    g_timeout_add (60000, rebuild_in_timeout, writer);
+}
+
+static gboolean
+rebuild (DConfWriter     *writer,
+         DConfChangeset  *changeset,
+         GError         **error)
+{
+  cancel_scheduled_rebuild (writer);
+
+  return do_rebuild (writer, changeset, error);
+}
+
 static void
 emit_notify_signal (GDBusConnection  *connection,
                     guint64           serial,
@@ -145,7 +199,7 @@ method_call (GDBusConnection       *connection,
       guint64 serial;
       GVariant *none;
 
-      g_variant_get (parameters, "(@smv)", &keyvalue, &value);
+      g_variant_get (parameters, "(@sm v)", &keyvalue, &value);
       key = g_variant_get_string (keyvalue, &key_length);
       g_variant_unref (keyvalue);
 
@@ -169,6 +223,34 @@ method_call (GDBusConnection       *connection,
           return;
         }
 
+      if (!dconf_shm_writer_append (writer->shm, key, value))
+        {
+          DConfChangeset *changeset;
+
+          /* the shm area is full.  we need to do a rebuild immediately.
+           */
+
+          changeset = dconf_changeset_new ();
+          dconf_changeset_write (changeset, key, value);
+
+          if (!rebuild (writer, changeset, &error))
+            {
+              /* ok.  that's bad news.
+               * we have no choice now.
+               */
+
+              g_variant_unref (value);
+
+              g_dbus_method_invocation_return_gerror (invocation, error);
+              g_error_free (error);
+            }
+        }
+
+      /* XXX record the journal entry here */
+
+      dconf_shm_writer_sync (writer->shm);
+      schedule_rebuild (writer);
+/*
       if (!dconf_rebuilder_rebuild (writer->path, "", &key,
                                     &value, 1, &error))
         {
@@ -176,7 +258,7 @@ method_call (GDBusConnection       *connection,
           g_error_free (error);
           return;
         }
-
+*/
       serial = writer->serial++;
       g_dbus_method_invocation_return_value (invocation,
                                              g_variant_new ("(t)", serial));
@@ -249,6 +331,13 @@ method_call (GDBusConnection       *connection,
       g_free (values);
       g_free (keys);
     }
+  else if (strcmp (method_name, "GetSessionDir") == 0)
+    {
+      GVariant *result;
+
+      result = g_variant_new ("(s)", writer->sessiondir);
+      g_dbus_method_invocation_return_value (invocation, result);
+    }
   else
     g_assert_not_reached ();
 }
@@ -270,8 +359,10 @@ fake_method_call (GDBusConnection       *connection,
 
   if (strcmp (method_name, "Merge") == 0)
     type = G_VARIANT_TYPE ("(sa{smv})");
-  else
+  else if (strcmp (method_name, "Write") == 0)
     type = G_VARIANT_TYPE ("(smv)");
+  else
+    type = G_VARIANT_TYPE ("()");
 
   g_variant_get (parameters, "(&s)", &printed);
   real_parameters = g_variant_parse (type, printed, NULL, NULL, &error);
@@ -338,6 +429,8 @@ main (void)
 
   writer.loop = g_main_loop_new (NULL, FALSE);
   writer.path = g_build_filename (g_get_user_config_dir (), "dconf", NULL);
+  writer.shm = dconf_shm_writer_new ();
+  dconf_shm_writer_sync (writer.shm);
 
   g_bus_own_name (G_BUS_TYPE_SESSION, "ca.desrt.dconf", 0,
                   bus_acquired, name_acquired, name_lost, &writer, NULL);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]