summaryrefslogtreecommitdiff
path: root/service
diff options
context:
space:
mode:
authorRyan Lortie <desrt@desrt.ca>2013-01-11 13:27:05 -0500
committerRyan Lortie <desrt@desrt.ca>2013-01-11 13:27:05 -0500
commit95af265b47eaec1ca4095fab750e03cde74b5114 (patch)
tree5019ec4f542650c034dfe22c4fe3ebe9b231374e /service
parente583885253b48aed82cb5a980aae705f772b1383 (diff)
downloaddconf-95af265b47eaec1ca4095fab750e03cde74b5114.tar.gz
service: add a keyfile writer
It is now possible to have keyfile-based dconf databases.
Diffstat (limited to 'service')
-rw-r--r--service/Makefile.am1
-rw-r--r--service/dconf-keyfile-writer.c361
-rw-r--r--service/dconf-service.c1
-rw-r--r--service/dconf-writer.h7
4 files changed, 368 insertions, 2 deletions
diff --git a/service/Makefile.am b/service/Makefile.am
index 7e27885..8d32517 100644
--- a/service/Makefile.am
+++ b/service/Makefile.am
@@ -21,6 +21,7 @@ dconf_service_SOURCES = \
dconf-service.h \
dconf-writer.h \
dconf-writer.c \
+ dconf-keyfile-writer.c \
dconf-shm-writer.c \
main.c
diff --git a/service/dconf-keyfile-writer.c b/service/dconf-keyfile-writer.c
new file mode 100644
index 0000000..4851c9e
--- /dev/null
+++ b/service/dconf-keyfile-writer.c
@@ -0,0 +1,361 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ * Copyright © 2012 Canonical Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "dconf-writer.h"
+
+#include <string.h>
+
+typedef DConfWriterClass DConfKeyfileWriterClass;
+
+typedef struct
+{
+ DConfWriter parent_instance;
+ gchar *filename;
+ GKeyFile *keyfile;
+} DConfKeyfileWriter;
+
+G_DEFINE_TYPE (DConfKeyfileWriter, dconf_keyfile_writer, DCONF_TYPE_WRITER)
+
+DConfChangeset *
+dconf_keyfile_to_changeset (GKeyFile *keyfile,
+ const gchar *filename_fyi)
+{
+ DConfChangeset *changeset;
+ gchar **groups;
+ gint i;
+
+ changeset = dconf_changeset_new_database (NULL);
+
+ groups = g_key_file_get_groups (keyfile, NULL);
+ for (i = 0; groups[i]; i++)
+ {
+ const gchar *group = groups[i];
+ gchar *key_prefix;
+ gchar **keys;
+ gint j;
+
+ /* Special case the [/] group to be able to contain keys at the
+ * root (/a, /b, etc.). All others must not start or end with a
+ * slash (ie: group [x/y] contains keys such as /x/y/z).
+ */
+ if (!g_str_equal (group, "/"))
+ {
+ if (g_str_has_prefix (group, "/") || g_str_has_suffix (group, "/") || strstr (group, "//"))
+ {
+ g_warning ("%s: ignoring invalid group name: %s\n", filename_fyi, group);
+ continue;
+ }
+
+ key_prefix = g_strconcat ("/", group, "/", NULL);
+ }
+ else
+ key_prefix = g_strdup ("/");
+
+ keys = g_key_file_get_keys (keyfile, group, NULL, NULL);
+ g_assert (keys != NULL);
+
+ for (j = 0; keys[j]; j++)
+ {
+ const gchar *key = keys[j];
+ GError *error = NULL;
+ gchar *value_str;
+ GVariant *value;
+ gchar *path;
+
+ if (strchr (key, '/'))
+ {
+ g_warning ("%s: [%s]: ignoring invalid key name: %s\n", filename_fyi, group, key);
+ continue;
+ }
+
+ value_str = g_key_file_get_value (keyfile, group, key, NULL);
+ g_assert (value_str != NULL);
+
+ value = g_variant_parse (NULL, value_str, NULL, NULL, &error);
+ g_free (value_str);
+
+ if (value == NULL)
+ {
+ g_warning ("%s: [%s]: %s: skipping invalid value: %s (%s)\n",
+ filename_fyi, group, key, value_str, error->message);
+ g_error_free (error);
+ continue;
+ }
+
+ path = g_strconcat (key_prefix, key, NULL);
+ dconf_changeset_set (changeset, path, value);
+ g_variant_unref (value);
+ g_free (path);
+ }
+
+ g_free (key_prefix);
+ g_strfreev (keys);
+ }
+
+ g_strfreev (groups);
+
+ return changeset;
+}
+
+static void
+dconf_keyfile_writer_list (GHashTable *set)
+{
+}
+
+static gboolean
+dconf_keyfile_writer_begin (DConfWriter *writer,
+ GError **error)
+{
+ DConfKeyfileWriter *kfw = (DConfKeyfileWriter *) writer;
+ GError *local_error = NULL;
+ DConfChangeset *contents;
+ DConfChangeset *changes;
+
+ if (kfw->filename == NULL)
+ kfw->filename = g_build_filename (g_get_user_config_dir (), "dconf-keyfile",
+ dconf_writer_get_name (writer), NULL);
+
+ kfw->keyfile = g_key_file_new ();
+
+ if (!g_key_file_load_from_file (kfw->keyfile, kfw->filename, G_KEY_FILE_KEEP_COMMENTS, &local_error))
+ {
+ if (!g_error_matches (local_error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
+ {
+ g_clear_pointer (&kfw->keyfile, g_key_file_free);
+ g_propagate_error (error, local_error);
+ return FALSE;
+ }
+
+ g_clear_error (&local_error);
+ }
+
+ if (!DCONF_WRITER_CLASS (dconf_keyfile_writer_parent_class)->begin (writer, error))
+ {
+ g_clear_pointer (&kfw->keyfile, g_key_file_free);
+ return FALSE;
+ }
+
+ /* Diff the keyfile to the current contents of the database and apply
+ * any changes that we notice.
+ *
+ * This will catch both the case of people outside of the service
+ * making changes to the file and also the case of starting for the
+ * first time.
+ */
+ contents = dconf_keyfile_to_changeset (kfw->keyfile, kfw->filename);
+ changes = dconf_writer_diff (writer, contents);
+
+ if (changes)
+ {
+ DCONF_WRITER_CLASS (dconf_keyfile_writer_parent_class)->change (writer, changes, "");
+ dconf_changeset_unref (changes);
+ }
+
+ dconf_changeset_unref (contents);
+
+ return TRUE;
+}
+
+static void
+dconf_keyfile_writer_change (DConfWriter *writer,
+ DConfChangeset *changeset,
+ const gchar *tag)
+{
+ DConfKeyfileWriter *kfw = (DConfKeyfileWriter *) writer;
+ const gchar *prefix;
+ const gchar * const *paths;
+ GVariant * const *values;
+ guint n, i;
+
+ DCONF_WRITER_CLASS (dconf_keyfile_writer_parent_class)->change (writer, changeset, tag);
+
+ n = dconf_changeset_describe (changeset, &prefix, &paths, &values);
+
+ for (i = 0; i < n; i++)
+ {
+ gchar *path = g_strconcat (prefix, paths[i], NULL);
+ GVariant *value = values[i];
+
+ if (g_str_equal (path, "/"))
+ {
+ g_assert (value == NULL);
+
+ /* This is a request to reset everything.
+ *
+ * Easiest way to do this:
+ */
+ g_key_file_free (kfw->keyfile);
+ kfw->keyfile = g_key_file_new ();
+ }
+ else if (g_str_has_suffix (path, "/"))
+ {
+ gchar *group_to_remove;
+ gchar **groups;
+ gint i;
+
+ g_assert (value == NULL);
+
+ /* Time to do a path reset.
+ *
+ * We must reset the group for the path plus any "subgroups".
+ *
+ * We dealt with the case of "/" above, so we know we have
+ * something with at least a separate leading and trailing slash,
+ * with the group name in the middle.
+ */
+ group_to_remove = g_strndup (path + 1, strlen (path) - 2);
+ g_key_file_remove_group (kfw->keyfile, group_to_remove, NULL);
+ g_free (group_to_remove);
+
+ /* Now the rest...
+ *
+ * For this case we check if the group is prefixed by the path
+ * given to us, including the trailing slash (but not the leading
+ * one). That means a reset on "/a/" (group "[a]") will match
+ * group "[a/b]" but not will not match group "[another]".
+ */
+ groups = g_key_file_get_groups (kfw->keyfile, NULL);
+ for (i = 0; groups[i]; i++)
+ if (g_str_has_prefix (groups[i], path + 1)) /* remove only leading slash */
+ g_key_file_remove_group (kfw->keyfile, groups[i], NULL);
+ g_strfreev (groups);
+ }
+ else
+ {
+ /* A simple set or reset of a single key. */
+ const gchar *last_slash;
+ gchar *group;
+ gchar *key;
+
+ last_slash = strrchr (path, '/');
+
+ /* If the last slash is the first one then the group will be the
+ * special case: [/]. Otherwise we remove the leading and
+ * trailing slashes.
+ */
+ if (last_slash != path)
+ group = g_strndup (path + 1, last_slash - (path + 1));
+ else
+ group = g_strdup ("/");
+
+ /* Key is the non-empty part following the last slash (we know
+ * that it's non-empty because we dealt with strings ending with
+ * '/' above).
+ */
+ key = g_strdup (last_slash + 1);
+
+ if (value != NULL)
+ {
+ gchar *printed;
+
+ printed = g_variant_print (value, TRUE);
+ g_key_file_set_value (kfw->keyfile, group, key, printed);
+ g_free (printed);
+ }
+ else
+ g_key_file_remove_key (kfw->keyfile, group, key, NULL);
+
+ g_free (group);
+ g_free (key);
+ }
+
+ g_free (path);
+ }
+}
+
+static gboolean
+dconf_keyfile_writer_commit (DConfWriter *writer,
+ GError **error)
+{
+ DConfKeyfileWriter *kfw = (DConfKeyfileWriter *) writer;
+
+ /* Pretty simple. Write the keyfile. */
+ {
+ gchar *data;
+ gsize size;
+
+ /* docs say: "Note that this function never reports an error" */
+ data = g_key_file_to_data (kfw->keyfile, &size, NULL);
+ if (!g_file_set_contents (kfw->filename, data, size, error))
+ {
+ gchar *dirname;
+
+ /* Maybe it failed because the directory doesn't exist. Try
+ * again, after mkdir().
+ */
+ dirname = g_path_get_dirname (kfw->filename);
+ g_mkdir_with_parents (dirname, 0777);
+ g_free (dirname);
+
+ g_clear_error (error);
+ if (!g_file_set_contents (kfw->filename, data, size, error))
+ {
+ g_free (data);
+ return FALSE;
+ }
+ }
+
+ g_free (data);
+ }
+
+ /* Failing to update the shm file after writing the keyfile is
+ * unlikely to occur. It can only happen if the runtime dir hits
+ * quota.
+ *
+ * If it does happen, we're in a bit of a bad spot because the on-disk
+ * keyfile is now out-of-sync with the contents of the shm file. We
+ * fail the write because the apps will see the old values in the shm
+ * file.
+ *
+ * Meanwhile we keep the on-disk keyfile as-is. The next time we open
+ * it we will notice that it's not in sync with the shm file and we'll
+ * try to merge the two as if the changes were made by an outsider.
+ * Eventually that may succeed... If it doesn't, what can we do?
+ */
+ return DCONF_WRITER_CLASS (dconf_keyfile_writer_parent_class)->commit (writer, error);
+}
+
+static void
+dconf_keyfile_writer_end (DConfWriter *writer)
+{
+ DConfKeyfileWriter *kfw = (DConfKeyfileWriter *) writer;
+
+ DCONF_WRITER_CLASS (dconf_keyfile_writer_parent_class)->end (writer);
+
+ g_clear_pointer (&kfw->keyfile, g_key_file_free);
+}
+
+static void
+dconf_keyfile_writer_init (DConfKeyfileWriter *writer)
+{
+ dconf_writer_set_basepath (DCONF_WRITER (writer), "keyfile");
+}
+
+static void
+dconf_keyfile_writer_class_init (DConfWriterClass *class)
+{
+ class->list = dconf_keyfile_writer_list;
+ class->begin = dconf_keyfile_writer_begin;
+ class->change = dconf_keyfile_writer_change;
+ class->commit = dconf_keyfile_writer_commit;
+ class->end = dconf_keyfile_writer_end;
+}
diff --git a/service/dconf-service.c b/service/dconf-service.c
index 950a4cf..d6bcefd 100644
--- a/service/dconf-service.c
+++ b/service/dconf-service.c
@@ -231,6 +231,7 @@ dconf_service_dbus_register (GApplication *application,
service->extension_point = g_io_extension_point_register ("dconf-backend");
g_io_extension_point_set_required_type (service->extension_point, DCONF_TYPE_WRITER);
g_io_extension_point_implement ("dconf-backend", DCONF_TYPE_WRITER, "Writer", 0);
+ g_io_extension_point_implement ("dconf-backend", DCONF_TYPE_KEYFILE_WRITER, "keyfile", 0);
g_io_extension_point_implement ("dconf-backend", DCONF_TYPE_SHM_WRITER, "shm", 0);
service->blame = dconf_blame_get ();
diff --git a/service/dconf-writer.h b/service/dconf-writer.h
index 32a7d4b..3435bdd 100644
--- a/service/dconf-writer.h
+++ b/service/dconf-writer.h
@@ -28,7 +28,6 @@
#include "dconf-generated.h"
#define DCONF_TYPE_WRITER (dconf_writer_get_type ())
-#define DCONF_TYPE_SHM_WRITER (dconf_shm_writer_get_type ())
#define DCONF_WRITER(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
DCONF_TYPE_WRITER, DConfWriter))
#define DCONF_WRITER_CLASS(class) (G_TYPE_CHECK_CLASS_CAST ((class), \
@@ -70,7 +69,6 @@ struct _DConfWriter
GType dconf_writer_get_type (void);
-GType dconf_shm_writer_get_type (void);
void dconf_writer_set_basepath (DConfWriter *writer,
const gchar *name);
@@ -83,4 +81,9 @@ void dconf_writer_list (GType
GDBusInterfaceSkeleton *dconf_writer_new (GType type,
const gchar *name);
+#define DCONF_TYPE_SHM_WRITER (dconf_shm_writer_get_type ())
+GType dconf_shm_writer_get_type (void);
+#define DCONF_TYPE_KEYFILE_WRITER (dconf_keyfile_writer_get_type ())
+GType dconf_keyfile_writer_get_type (void);
+
#endif /* __dconf_writer_h__ */