summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README2
-rw-r--r--bin/dconf-dump.vala86
-rw-r--r--bin/dconf-update.vala292
-rw-r--r--bin/dconf.c1175
-rw-r--r--bin/dconf.vala395
-rw-r--r--bin/gvdb.vapi21
-rw-r--r--bin/meson.build6
-rw-r--r--dconf.doap1
-rw-r--r--meson.build3
-rwxr-xr-xtests/test-dconf.py29
10 files changed, 1206 insertions, 804 deletions
diff --git a/README b/README
index 049e1e3..d606d46 100644
--- a/README
+++ b/README
@@ -62,8 +62,6 @@ but still depends on libglib. It is not recommended to use this library
unless you have a legacy dependency on libdbus-1 (such as in Qt
applications).
-bin/ is written in Vala, hence the Vala compiler is required.
-
Installing dconf follows the typical meson dance:
meson builddir
diff --git a/bin/dconf-dump.vala b/bin/dconf-dump.vala
deleted file mode 100644
index 26f62d0..0000000
--- a/bin/dconf-dump.vala
+++ /dev/null
@@ -1,86 +0,0 @@
-void add_to_keyfile (KeyFile kf, DConf.Client client, string topdir, string? rel = "") {
- var this_dir = topdir + rel;
- string this_group;
-
- if (rel != "") {
- this_group = rel.slice (0, -1);
- } else {
- this_group = "/";
- }
-
- var items = client.list (this_dir);
- GLib.qsort_with_data<string> (items, sizeof (string), (a, b) => {
- var a_dir = a.has_suffix ("/");
- var b_dir = b.has_suffix ("/");
- if (a_dir != b_dir) {
- return (int) a_dir - (int) b_dir;
- } else {
- return GLib.strcmp (a, b);
- }
- });
-
- foreach (var item in items) {
- if (item.has_suffix ("/")) {
- add_to_keyfile (kf, client, topdir, rel + item);
- } else {
- var val = client.read (this_dir + item);
-
- if (val != null) {
- kf.set_value (this_group, item, val.print (true));
- }
- }
- }
-}
-
-void dconf_dump (string[] args) throws Error {
- var client = new DConf.Client ();
- var kf = new KeyFile ();
- var dir = args[2];
-
- DConf.verify_dir (dir);
-
- if (args[3] != null) {
- throw new OptionError.FAILED ("too many arguments");
- }
-
- add_to_keyfile (kf, client, dir);
- print ("%s", kf.to_data ());
-}
-
-KeyFile keyfile_from_stdin () throws Error {
- unowned string? tmp;
- char buffer[1024];
-
- var s = new StringBuilder ();
- while ((tmp = stdin.gets (buffer)) != null) {
- s.append (tmp);
- }
-
- var kf = new KeyFile ();
- kf.load_from_data (s.str, s.len, 0);
-
- return kf;
-}
-
-void dconf_load (string[] args) throws Error {
- var dir = args[2];
- DConf.verify_dir (dir);
-
- if (args[3] != null) {
- throw new OptionError.FAILED ("too many arguments");
- }
-
- var changeset = new DConf.Changeset ();
- var kf = keyfile_from_stdin ();
-
- foreach (var group in kf.get_groups ()) {
- foreach (var key in kf.get_keys (group)) {
- var path = dir + (group == "/" ? "" : group + "/") + key;
- DConf.verify_key (path);
- changeset.set (path, Variant.parse (null, kf.get_value (group, key)));
- }
- }
-
- var client = new DConf.Client ();
- client.change_sync (changeset);
-}
diff --git a/bin/dconf-update.vala b/bin/dconf-update.vala
deleted file mode 100644
index 9492adb..0000000
--- a/bin/dconf-update.vala
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
- * Copyright © 2010 Codethink 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, see <http://www.gnu.org/licenses/>.
- *
- * Author: Ryan Lortie <desrt@desrt.ca>
- */
-
-unowned Gvdb.Item get_parent (Gvdb.HashTable table, string name) {
- unowned Gvdb.Item parent;
-
- int end = 0;
-
- for (int i = 1; name[i] != '\0'; i++) {
- if (name[i - 1] == '/') {
- end = i;
- }
- }
-
- assert (end != 0);
-
- var parent_name = name.substring (0, end);
- parent = table.lookup (parent_name);
-
- if (parent == null) {
- parent = table.insert (parent_name);
- parent.set_parent (get_parent (table, parent_name));
- }
-
- return parent;
-}
-
-SList<string>? list_directory (string dirname, Posix.mode_t mode) throws GLib.Error {
- var list = new SList<string> ();
- var dir = Dir.open (dirname);
- unowned string? name;
-
- while ((name = dir.read_name ()) != null) {
- if (name.has_prefix (".")) {
- continue;
- }
-
- var filename = Path.build_filename (dirname, name);
- Posix.Stat buf;
-
- // only files of the requested type
- if (Posix.stat (filename, out buf) < 0 || (buf.st_mode & Posix.S_IFMT) != mode) {
- continue;
- }
-
- list.prepend (filename);
- }
-
- return list;
-}
-
-Gvdb.HashTable? read_locks_directory (string dirname) throws GLib.Error {
- SList<string>? files;
-
- try {
- files = list_directory (dirname, Posix.S_IFREG);
- } catch (FileError.NOENT e) {
- /* If locks directory is missing, there are just no locks... */
- return null;
- }
-
- var table = new Gvdb.HashTable ();
-
- foreach (var filename in files) {
- string contents;
- FileUtils.get_contents (filename, out contents, null);
-
- foreach (var line in contents.split ("\n")) {
- if (line.has_prefix ("/")) {
- table.insert_string (line, "");
- }
- }
- }
-
- return table;
-}
-
-Gvdb.HashTable read_directory (string dirname) throws GLib.Error {
- var table = new Gvdb.HashTable ();
- table.insert ("/");
-
- var files = list_directory (dirname, Posix.S_IFREG);
- files.sort (strcmp);
- files.reverse ();
-
- foreach (var filename in files) {
- var kf = new KeyFile ();
-
- try {
- kf.load_from_file (filename, KeyFileFlags.NONE);
- } catch (GLib.Error e) {
- e.message = "warning: Failed to read keyfile '%s': %s".printf (filename, e.message);
- throw e;
- }
-
- foreach (var group in kf.get_groups ()) {
- if (group.has_prefix ("/") || group.has_suffix ("/") || "//" in group) {
- throw new KeyFileError.PARSE ("%s: invalid group name: %s".printf (filename, group));
- }
-
- foreach (var key in kf.get_keys (group)) {
- if ("/" in key) {
- throw new KeyFileError.INVALID_VALUE ("%s: [%s]: invalid key name: %s",
- filename, group, key);
- }
-
- var path = "/" + group + "/" + key;
-
- if (table.lookup (path) != null) {
- /* We process the files in reverse alphabetical order. If the key is already set then
- * it must have been set from a file with higher precedence so we should ignore this
- * one.
- */
- continue;
- }
-
- var text = kf.get_value (group, key);
-
- try {
- var value = Variant.parse (null, text);
- unowned Gvdb.Item item = table.insert (path);
- item.set_parent (get_parent (table, path));
- item.set_value (value);
- } catch (VariantParseError e) {
- e.message = "%s: [%s]: %s: invalid value: %s (%s)".printf (filename, group, key, text, e.message);
- throw e;
- }
- }
- }
- }
-
- var locks = read_locks_directory (dirname + "/locks");
-
- if (locks != null) {
- unowned Gvdb.Item item = table.insert (".locks");
- item.set_hash_table (locks);
- }
-
- return table;
-}
-
-time_t get_directory_mtime (string dirname, Posix.Stat dir_buf) throws GLib.Error {
- Posix.Stat lockdir_buf;
- Posix.Stat file_buf;
- time_t latest_mtime = dir_buf.st_mtime;
-
- var files = list_directory (dirname, Posix.S_IFREG);
-
- foreach (var filename in files) {
- if (Posix.stat (filename, out file_buf) == 0 && file_buf.st_mtime > latest_mtime)
- latest_mtime = file_buf.st_mtime;
- }
-
- if (Posix.stat (dirname + "/locks", out lockdir_buf) == 0 && Posix.S_ISDIR (lockdir_buf.st_mode)) {
- if (lockdir_buf.st_mtime > latest_mtime) {
- // if the lock directory has been updated more recently then consider its timestamp instead
- latest_mtime = lockdir_buf.st_mtime;
- }
-
- files = list_directory (dirname + "/locks", Posix.S_IFREG);
-
- foreach (var filename in files) {
- if (Posix.stat (filename, out file_buf) == 0 && file_buf.st_mtime > latest_mtime)
- latest_mtime = file_buf.st_mtime;
- }
- }
-
- return latest_mtime;
-}
-
-void maybe_update_from_directory (string dirname) throws GLib.Error {
- Posix.Stat dir_buf;
-
- if (Posix.stat (dirname, out dir_buf) == 0 && Posix.S_ISDIR (dir_buf.st_mode)) {
- Posix.Stat file_buf;
-
- var filename = dirname.substring (0, dirname.length - 2);
-
- if (Posix.stat (filename, out file_buf) == 0 && file_buf.st_mtime > get_directory_mtime (dirname, dir_buf)) {
- return;
- }
-
- var table = read_directory (dirname);
-
- var fd = Posix.open (filename, Posix.O_WRONLY);
-
- if (fd < 0 && errno != Posix.ENOENT) {
- var saved_error = errno;
- printerr ("warning: Failed to open '%s' for replacement: %s\n", filename, strerror (saved_error));
- return;
- }
-
- // We expect that fd < 0 here if ENOENT (ie: the db merely didn't exist yet)
-
- try {
- table.write_contents (filename);
-
- if (fd >= 0) {
- Posix.write (fd, "\0\0\0\0\0\0\0\0", 8);
- }
- } catch (Error e) {
- printerr ("warning: %s\n", e.message);
- return;
- } finally {
- if (fd >= 0) {
- Posix.close (fd);
- }
- }
-
- try {
- var system_bus = Bus.get_sync (BusType.SYSTEM);
- system_bus.emit_signal (null, "/ca/desrt/dconf/Writer/" + Path.get_basename (filename),
- "ca.desrt.dconf.Writer", "WritabilityNotify", new Variant ("(s)", "/"));
- system_bus.flush_sync ();
- } catch {
- /* if we can't, ... don't. */
- }
- }
-}
-
-void update_all (string dirname) throws GLib.Error {
- var failed = false;
-
- foreach (var name in list_directory (dirname, Posix.S_IFDIR)) {
- if (name.has_suffix (".d")) {
- try {
- maybe_update_from_directory (name);
- } catch (Error e) {
- failed = true;
- printerr ("unable to compile %s: %s\n", name, e.message);
- }
- }
- }
-
- if (failed) {
- Process.exit (2);
- }
-}
-
-void dconf_compile (string[] args) throws GLib.Error {
- if (args[2] == null || args[3] == null || args[4] != null) {
- throw new OptionError.FAILED ("must give output file and .d dir");
- }
-
- try {
- // We always write the result of "dconf compile" as little endian
- // so that it can be installed in /usr/share
- var table = read_directory (args[3]);
- var should_byteswap = (BYTE_ORDER == ByteOrder.BIG_ENDIAN);
- table.write_contents (args[2], should_byteswap);
- } catch (Error e) {
- printerr ("%s\n", e.message);
- Process.exit (1);
- }
-}
-
-[CCode (cname = "SYSCONFDIR")]
-extern const string CONFIG_SYSCONFDIR;
-
-void dconf_update (string[] args) throws GLib.Error {
- var index = 2;
- var dbpath = CONFIG_SYSCONFDIR + "/dconf/db";
-
- if (args[index] != null) {
- dbpath = args[index];
- index ++;
- }
-
- if (args[index] != null) {
- throw new OptionError.FAILED ("too many arguments");
- }
-
- update_all (dbpath);
-}
-
-// vim:noet ts=4 sw=4
diff --git a/bin/dconf.c b/bin/dconf.c
new file mode 100644
index 0000000..2688ca7
--- /dev/null
+++ b/bin/dconf.c
@@ -0,0 +1,1175 @@
+/*
+ * Copyright © 2010, 2011 Codethink Limited
+ * Copyright © 2011 Canonical Limited
+ * Copyright © 2018 Tomasz Miąsko
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ * Tomasz Miąsko
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <glib/gstdio.h>
+#include <locale.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "client/dconf-client.h"
+#include "common/dconf-enums.h"
+#include "common/dconf-paths.h"
+#include "gvdb/gvdb-builder.h"
+#include "gvdb/gvdb-reader.h"
+
+static gboolean dconf_help (const gchar **argv, GError **error);
+
+static gboolean
+option_error_propagate (GError **dst, GError **src)
+{
+ g_assert (src != NULL && *src != NULL);
+
+ (*src)->domain = G_OPTION_ERROR;
+ (*src)->code = G_OPTION_ERROR_FAILED;
+ g_propagate_error (dst, g_steal_pointer (src));
+
+ return FALSE;
+}
+
+static gboolean
+option_error_set (GError **error, const char *message)
+{
+ g_assert (error != NULL);
+ g_assert (message != NULL);
+
+ g_set_error_literal (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, message);
+
+ return FALSE;
+}
+
+static gboolean
+dconf_read (const gchar **argv,
+ GError **error)
+{
+ gint index = 0;
+ const gchar *key;
+ DConfReadFlags flags = DCONF_READ_FLAGS_NONE;
+ g_autoptr(GError) local_error = NULL;
+ g_autoptr(DConfClient) client = NULL;
+ g_autoptr(GVariant) result = NULL;
+
+ if (argv[index] != NULL && strcmp (argv[index], "-d") == 0)
+ {
+ flags = DCONF_READ_DEFAULT_VALUE;
+ index += 1;
+ }
+
+ key = argv[index];
+ if (!dconf_is_key (key, &local_error))
+ return option_error_propagate (error, &local_error);
+
+ index += 1;
+
+ if (argv[index] != NULL)
+ return option_error_set (error, "too many arguments");
+
+ client = dconf_client_new ();
+ result = dconf_client_read_full (client, key, flags, NULL);
+
+ if (result != NULL)
+ {
+ g_autofree gchar *s = g_variant_print (result, TRUE);
+ g_printf ("%s\n", s);
+ }
+
+ return TRUE;
+}
+
+static gint
+string_compare (const void *a,
+ const void *b)
+{
+ return strcmp (*(const gchar **)a, *(const gchar **)b);
+}
+
+static gint
+string_rcompare (const void *a,
+ const void *b)
+{
+ return -strcmp (*(const gchar **)a, *(const gchar **)b);
+}
+
+static gboolean
+dconf_list (const gchar **argv,
+ GError **error)
+{
+ const char *dir;
+ gint length;
+ g_autoptr(GError) local_error = NULL;
+ g_autoptr(DConfClient) client = NULL;
+ g_auto(GStrv) items = NULL;
+
+ dir = argv[0];
+ if (!dconf_is_dir (dir, &local_error))
+ return option_error_propagate (error, &local_error);
+
+ client = dconf_client_new ();
+ items = dconf_client_list (client, dir, &length);
+ qsort (items, length, sizeof (items[0]), string_compare);
+
+ for (char **item = items; *item; ++item)
+ g_printf ("%s\n", *item);
+
+ return TRUE;
+}
+
+static gboolean
+dconf_list_locks (const gchar **argv,
+ GError **error)
+{
+ const char *dir;
+ gint length;
+ g_autoptr(GError) local_error = NULL;
+ g_autoptr(DConfClient) client = NULL;
+ g_auto(GStrv) items = NULL;
+
+ dir = argv[0];
+ if (!dconf_is_dir (dir, &local_error))
+ return option_error_propagate (error, &local_error);
+
+ if (argv[1] != NULL)
+ return option_error_set (error, "too many arguments");
+
+ client = dconf_client_new ();
+ items = dconf_client_list_locks (client, dir, &length);
+ qsort (items, length, sizeof (items[0]), string_compare);
+
+ for (char **item = items; *item; ++item)
+ g_printf ("%s\n", *item);
+
+ return TRUE;
+}
+
+static gboolean
+dconf_write (const gchar **argv,
+ GError **error)
+{
+ const char *key;
+ const char *value_str;
+ g_autoptr(GError) local_error = NULL;
+ g_autoptr(GVariant) value = NULL;
+ g_autoptr(DConfClient) client = NULL;
+
+ key = argv[0];
+ if (!dconf_is_key (key, &local_error))
+ return option_error_propagate (error, &local_error);
+
+ value_str = argv[1];
+ if (value_str == NULL)
+ return option_error_set (error, "value not specified");
+
+ value = g_variant_parse (NULL, value_str, NULL, NULL, &local_error);
+ if (value == NULL)
+ return option_error_propagate (error, &local_error);
+
+ if (argv[2] != NULL)
+ return option_error_set (error, "too many arguments");
+
+ client = dconf_client_new ();
+ return dconf_client_write_sync (client, key, value, NULL, NULL, error);
+}
+
+static gboolean
+dconf_reset (const gchar **argv,
+ GError **error)
+{
+ gboolean force = FALSE;
+ gint index = 0;
+ const gchar *path;
+ g_autoptr(GError) local_error = NULL;
+ g_autoptr(DConfClient) client = NULL;
+
+ if (argv[index] != NULL && strcmp (argv[index], "-f") == 0)
+ {
+ index += 1;
+ force = TRUE;
+ }
+
+ path = argv[index];
+ if (!dconf_is_path (path, &local_error))
+ return option_error_propagate (error, &local_error);
+
+ index += 1;
+
+ if (dconf_is_dir (path, NULL) && !force)
+ return option_error_set (error, "-f must be given to (recursively) reset entire directories");
+
+ if (argv[index] != NULL)
+ return option_error_set (error, "too many arguments");
+
+ client = dconf_client_new ();
+ return dconf_client_write_sync (client, path, NULL, NULL, NULL, error);
+}
+
+static void
+show_path (DConfClient *client, const gchar *path)
+{
+ if (dconf_is_key (path, NULL))
+ {
+ g_autoptr(GVariant) value = NULL;
+ g_autofree gchar *value_str = NULL;
+
+ value = dconf_client_read (client, path);
+
+ if (value != NULL)
+ value_str = g_variant_print (value, TRUE);
+
+ g_printf (" %s\n", value_str != NULL ? value_str : "unset");
+ }
+}
+
+static void
+watch_function (DConfClient *client,
+ const gchar *path,
+ const gchar **items,
+ const gchar *tag,
+ gpointer user_data)
+{
+ for (const gchar **item = items; *item; ++item)
+ {
+ g_autofree gchar *full = NULL;
+
+ full = g_strconcat (path, *item, NULL);
+ g_printf ("%s\n", full);
+ show_path (client, full);
+ }
+
+ g_printf ("\n");
+ fflush (stdout);
+}
+
+static gboolean
+dconf_watch (const char **argv,
+ GError **error)
+{
+ g_autoptr(GError) local_error = NULL;
+ g_autoptr(DConfClient) client = NULL;
+ g_autoptr(GMainLoop) loop = NULL;
+ const gchar *path;
+
+ path = argv[0];
+ if (!dconf_is_path (path, &local_error))
+ return option_error_propagate (error, &local_error);
+
+ if (argv[1] != NULL)
+ return option_error_set (error, "too many arguments");
+
+ client = dconf_client_new ();
+ g_signal_connect (client, "changed", G_CALLBACK (watch_function), NULL);
+ dconf_client_watch_sync (client, path);
+
+ loop = g_main_loop_new (NULL, FALSE);
+ g_main_loop_run (loop);
+
+ return TRUE;
+}
+
+static gboolean
+dconf_blame (const char **argv,
+ GError **error)
+{
+ g_autoptr(GVariant) reply = NULL;
+ g_autoptr(GVariant) child = NULL;
+ g_autoptr(GDBusConnection) connection = NULL;
+
+ if (argv[0] != NULL)
+ return option_error_set (error, "too many arguments");
+
+ connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error);
+ if (connection == NULL)
+ return FALSE;
+
+ reply = g_dbus_connection_call_sync (connection, "ca.desrt.dconf",
+ "/ca/desrt/dconf",
+ "ca.desrt.dconf.ServiceInfo",
+ "Blame", NULL, G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, error);
+ if (reply == NULL)
+ return FALSE;
+
+ child = g_variant_get_child_value (reply, 0);
+ g_printf ("%s", g_variant_get_string (child, NULL));
+
+ return TRUE;
+}
+
+/**
+ * Returns a parent dir that contains given path.
+ */
+static gchar *
+path_get_parent (const char *path)
+{
+ g_return_val_if_fail (path != NULL, NULL);
+ g_return_val_if_fail (strcmp (path, "/") != 0, NULL);
+
+ gsize last = 0;
+
+ /* Find the position of the last slash, other than the trailing one. */
+ for (gsize i = 0; path[i + 1] != '\0'; ++i)
+ if (path[i] == '/')
+ last = i;
+
+ return strndup (path, last + 1);
+}
+
+static gboolean
+dconf_complete (const gchar **argv,
+ GError **error)
+{
+ const gchar *suffix;
+ const gchar *path;
+
+ suffix = argv[0];
+ if (suffix == NULL)
+ return option_error_set (error, "suffix not specified");
+
+ path = argv[1];
+ if (path == NULL)
+ return option_error_set (error, "path not specified");
+
+ if (argv[2] != NULL)
+ return option_error_set (error, "too many arguments");
+
+ if (g_str_equal (path, ""))
+ {
+ g_printf ("/\n");
+ return TRUE;
+ }
+
+ if (path[0] == '/')
+ {
+ gint length;
+ g_autoptr(DConfClient) client = NULL;
+ g_autofree gchar *dir = NULL;
+ g_auto(GStrv) items = NULL;
+
+ if (g_str_has_suffix (path, "/"))
+ dir = g_strdup (path);
+ else
+ dir = path_get_parent (path);
+
+ client = dconf_client_new ();
+ items = dconf_client_list (client, dir, &length);
+ qsort (items, length, sizeof (items[0]), string_compare);
+
+ for (gchar **item = items; *item; ++item)
+ {
+ g_autofree gchar *full_item = NULL;
+
+ full_item = g_strconcat (dir, *item, NULL);
+ if (g_str_has_prefix (full_item, path) &&
+ g_str_has_suffix (*item, suffix))
+ {
+ g_printf ("%s%s\n", full_item,
+ g_str_has_suffix (full_item, "/") ? "" : " ");
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+/**
+ * Comparison function for paths that orders keys before dirs.
+ */
+static gint
+path_compare (const void *a,
+ const void *b)
+{
+ const gchar *as = *(const gchar **)a;
+ const gchar *bs = *(const gchar **)b;
+
+ const gboolean a_is_dir = !!g_str_has_suffix (as, "/");
+ const gboolean b_is_dir = !!g_str_has_suffix (bs, "/");
+
+ if (a_is_dir != b_is_dir)
+ return a_is_dir - b_is_dir;
+ else
+ return strcmp (as, bs);
+}
+
+/**
+ * add_to_keyfile:
+ * @dir_src: a dconf source dir
+ * @dir_dst: a key-file destination dir
+ *
+ * Copy directory contents from dconf to key-file.
+ **/
+static void
+add_to_keyfile (GKeyFile *kf,
+ DConfClient *client,
+ const gchar *dir_src,
+ const gchar *dir_dst)
+{
+ g_autofree gchar *group = NULL;
+ g_auto(GStrv) items = NULL;
+ gint length;
+ gsize n;
+
+ /* Key-file group names are formed by removing initial and trailing slash
+ * from dir name, with the singular exception of root dir whose group name
+ * is just "/". */
+
+ n = strlen (dir_dst);
+ g_assert (n >= 1 && dir_dst[n - 1] == '/');
+
+ if (g_str_equal (dir_dst, "/"))
+ group = g_strdup ("/");
+ else
+ group = g_strndup (dir_dst + 1, n - 2);
+
+ items = dconf_client_list (client, dir_src, &length);
+ qsort (items, length, sizeof (items[0]), path_compare);
+
+ for (gchar **item = items; *item; ++item)
+ {
+ g_autofree gchar *path = g_strconcat (dir_src, *item, NULL);
+
+ if (g_str_has_suffix (*item, "/"))
+ {
+ g_autofree gchar *subdir = g_strconcat (dir_dst, *item, NULL);
+ add_to_keyfile (kf, client, path, subdir);
+ }
+ else
+ {
+ g_autoptr(GVariant) value = dconf_client_read (client, path);
+ if (value != NULL)
+ {
+ g_autofree gchar *value_str = g_variant_print (value, TRUE);
+ g_key_file_set_value (kf, group, *item, value_str);
+ }
+ }
+ }
+}
+
+static gboolean
+dconf_dump (const gchar **argv,
+ GError **error)
+{
+ const gchar *dir;
+ g_autoptr(GError) local_error = NULL;
+ g_autoptr(GKeyFile) kf = NULL;
+ g_autoptr(DConfClient) client = NULL;
+ g_autofree gchar *data = NULL;
+
+ dir = argv[0];
+ if (!dconf_is_dir (dir, &local_error))
+ return option_error_propagate (error, &local_error);
+
+ if (argv[1] != NULL)
+ return option_error_set (error, "too many arguments");
+
+ kf = g_key_file_new ();
+ client = dconf_client_new ();
+
+ add_to_keyfile (kf, client, dir, "/");
+
+ data = g_key_file_to_data (kf, NULL, NULL);
+ g_printf ("%s", data);
+
+ return TRUE;
+}
+
+static GKeyFile *
+keyfile_from_stdin (GError **error)
+{
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ char buffer[1024];
+ g_autoptr(GString) s = NULL;
+ g_autoptr(GKeyFile) kf = NULL;
+
+ s = g_string_new (NULL);
+ while (fgets (buffer, sizeof (buffer), stdin) != NULL)
+ g_string_append (s, buffer);
+
+ kf = g_key_file_new ();
+ if (!g_key_file_load_from_data (kf, s->str, s->len, G_KEY_FILE_NONE, error))
+ return FALSE;
+
+ return g_steal_pointer (&kf);
+}
+
+typedef void (*KeyFileForeachFunc) (const gchar *path,
+ GVariant *value,
+ gpointer user_data);
+
+static gboolean
+keyfile_foreach (GKeyFile *kf,
+ const gchar *dir,
+ KeyFileForeachFunc func,
+ gpointer user_data,
+ GError **error)
+{
+ g_auto(GStrv) groups = NULL;
+
+ groups = g_key_file_get_groups (kf, NULL);
+
+ for (gchar **group = groups; *group; ++group)
+ {
+ g_auto(GStrv) keys = NULL;
+
+ keys = g_key_file_get_keys (kf, *group, NULL, NULL);
+
+ for (gchar **key = keys; *key; ++key)
+ {
+ g_autoptr(GString) s = NULL;
+ g_autofree gchar *value_str = NULL;
+ g_autoptr(GVariant) value = NULL;
+
+ /* Reconstruct dconf key path from the current dir,
+ * key-file group name and key-file key. */
+ s = g_string_new (dir);
+ if (strcmp (*group, "/") != 0)
+ {
+ g_string_append (s, *group);
+ g_string_append (s, "/");
+ }
+ g_string_append (s, *key);
+
+ if (!dconf_is_key (s->str, error))
+ {
+ g_prefix_error (error, "[%s]: %s: invalid path: ",
+ *group, *key);
+ return FALSE;
+ }
+
+ value_str = g_key_file_get_value (kf, *group, *key, NULL);
+ g_assert (value_str != NULL);
+
+ value = g_variant_parse (NULL, value_str, NULL, NULL, error);
+ if (value == NULL)
+ {
+ g_prefix_error (error, "[%s]: %s: invalid value: %s: ",
+ *group, *key, value_str);
+ return FALSE;
+ }
+
+ func (s->str, value, user_data);
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+changeset_set (const gchar *path,
+ GVariant *value,
+ gpointer user_data)
+{
+ DConfChangeset *changeset = user_data;
+
+ dconf_changeset_set (changeset, path, value);
+}
+
+static gboolean
+dconf_load (const gchar **argv,
+ GError **error)
+{
+ const gchar *dir;
+ g_autoptr(GError) local_error = NULL;
+ g_autoptr(GKeyFile) kf = NULL;
+ g_autoptr(DConfChangeset) changeset = NULL;
+ g_autoptr (DConfClient) client = NULL;
+
+ dir = argv[0];
+ if (!dconf_is_dir (dir, &local_error))
+ return option_error_propagate (error, &local_error);
+
+ if (argv[1] != NULL)
+ return option_error_set (error, "too many arguments");
+
+ kf = keyfile_from_stdin (error);
+ if (kf == NULL)
+ return FALSE;
+
+ changeset = dconf_changeset_new ();
+ if (!keyfile_foreach (kf, dir, changeset_set, changeset, error))
+ return FALSE;
+
+ client = dconf_client_new ();
+ return dconf_client_change_sync (client, changeset, NULL, NULL, error);
+}
+
+static GPtrArray *
+list_directory (const gchar *dirname,
+ mode_t ftype,
+ GError **error)
+{
+ const gchar *name;
+ g_autoptr(GDir) dir = NULL;
+ g_autoptr(GPtrArray) files = NULL;
+
+ dir = g_dir_open (dirname, 0, error);
+ if (dir == NULL)
+ return NULL;
+
+ files = g_ptr_array_new_full (0, g_free);
+
+ while ((name = g_dir_read_name (dir)) != NULL)
+ {
+ GStatBuf buf;
+ g_autofree gchar *filename = NULL;
+
+ /* Ignore swap files like .swp etc. */
+ if (g_str_has_prefix (name, "."))
+ continue;
+
+ filename = g_build_filename (dirname, name, NULL);
+
+ if (g_stat (filename, &buf) < 0)
+ {
+ gint saved_errno = errno;
+ g_debug ("ignoring file %s: %s",
+ filename, g_strerror (saved_errno));
+ continue;
+ }
+
+ if ((buf.st_mode & S_IFMT) != ftype)
+ continue;
+
+ g_ptr_array_add (files, g_steal_pointer (&filename));
+ }
+
+ return g_steal_pointer (&files);
+}
+
+static GHashTable *
+read_locks_directory (const gchar *dirname,
+ GError **error)
+{
+ g_autoptr(GError) local_error = NULL;
+ g_autoptr(GPtrArray) files = NULL;
+ g_autoptr(GHashTable) table = NULL;
+
+ files = list_directory (dirname, S_IFREG, &local_error);
+ if (files == NULL)
+ {
+ /* If locks directory is missing, there are just no locks... */
+ if (!g_error_matches (local_error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
+ g_propagate_error (error, g_steal_pointer (&local_error));
+ return NULL;
+ }
+
+ table = gvdb_hash_table_new (NULL, NULL);
+
+ for (guint i = 0; i != files->len; ++i)
+ {
+ const gchar *filename;
+ g_autofree gchar *contents = NULL;
+ g_auto(GStrv) lines = NULL;
+ gsize length;
+
+ filename = g_ptr_array_index (files, i);
+
+ if (!g_file_get_contents (filename, &contents, &length, error))
+ return NULL;
+
+ lines = g_strsplit (contents, "\n", 0);
+ for (gchar **line = lines; *line; ++line)
+ {
+ if (g_str_has_prefix (*line, "/"))
+ gvdb_hash_table_insert_string (table, *line, "");
+ }
+ }
+
+ return g_steal_pointer (&table);
+}
+
+static GvdbItem *
+table_get_parent (GHashTable *table,
+ const gchar *name)
+{
+ GvdbItem *parent = NULL;
+ g_autofree gchar *dir = NULL;
+
+ dir = path_get_parent (name);
+ parent = g_hash_table_lookup (table, dir);
+
+ if (parent == NULL)
+ {
+ parent = gvdb_hash_table_insert (table, dir);
+ gvdb_item_set_parent (parent, table_get_parent (table, dir));
+ }
+
+ return parent;
+}
+
+
+static void
+table_insert (const gchar *path,
+ GVariant *value,
+ gpointer user_data)
+{
+ GHashTable *table = user_data;
+ GvdbItem *item;
+
+ /* See FILES-PRECEDENCE 2 */
+ if (g_hash_table_lookup (table, path) != NULL)
+ return;
+
+ item = gvdb_hash_table_insert (table, path);
+ gvdb_item_set_parent (item, table_get_parent (table, path));
+ gvdb_item_set_value (item, value);
+}
+
+static GHashTable *
+read_directory (const gchar *dir,
+ GError **error)
+{
+ g_autoptr(GError) local_error = NULL;
+ g_autoptr(GHashTable) table = NULL;
+ g_autoptr(GPtrArray) files = NULL;
+ g_autofree gchar *locks_dir = NULL;
+ GHashTable *locks_table = NULL;
+
+ table = gvdb_hash_table_new (NULL, NULL);
+ gvdb_hash_table_insert (table, "/");
+
+ files = list_directory (dir, S_IFREG, error);
+ if (files == NULL)
+ return NULL;
+
+ /* FILES-PRECEDENCE: When a path is found in multiple files, value from the
+ * file lexicographically latest takes precedence. This is achieved by 1)
+ * processing files in reversed lexicographical order, 2) not overwriting
+ * existing paths.
+ */
+ g_ptr_array_sort (files, string_rcompare);
+
+ for (guint i = 0; i != files->len; ++i)
+ {
+ const gchar *filename;
+ g_autoptr(GKeyFile) kf = NULL;
+
+ filename = g_ptr_array_index (files, i);
+ kf = g_key_file_new ();
+
+ g_debug ("loading key-file: %s", filename);
+
+ if (!g_key_file_load_from_file (kf, filename, G_KEY_FILE_NONE, error))
+ {
+ g_autofree gchar *display_name = g_filename_display_basename (filename);
+ g_prefix_error (error, "%s: ", display_name);
+ return FALSE;
+ }
+
+ if (!keyfile_foreach (kf, "/", table_insert, table, error))
+ {
+ g_autofree gchar *display_name = g_filename_display_basename (filename);
+ g_prefix_error (error, "%s: ", display_name);
+ return FALSE;
+ }
+ }
+
+ locks_dir = g_build_filename (dir, "locks", NULL);
+ locks_table = read_locks_directory (locks_dir, &local_error);
+ if (local_error != NULL)
+ {
+ g_propagate_error (error, g_steal_pointer (&local_error));
+ return FALSE;
+ }
+
+ if (locks_table != NULL)
+ {
+ GvdbItem *item;
+
+ item = gvdb_hash_table_insert (table, ".locks");
+ gvdb_item_set_hash_table (item, locks_table);
+ }
+
+ return g_steal_pointer (&table);
+}
+
+static gboolean
+update_directory (const gchar *dir,
+ GError **error)
+{
+ gint fd = -1;
+ g_autofree gchar *filename = NULL;
+ g_autoptr(GHashTable) table = NULL;
+ g_autoptr(GDBusConnection) bus = NULL;
+
+ g_assert (g_str_has_suffix (dir, ".d"));
+ filename = strndup (dir, strlen (dir) - 2);
+
+ table = read_directory (dir, error);
+ if (table == NULL)
+ return FALSE;
+
+ fd = open (filename, O_WRONLY);
+ if (fd < 0 && errno != ENOENT)
+ {
+ gint saved_errno = errno;
+ g_autofree gchar *display_name = g_filename_display_name (filename);
+
+ g_fprintf (stderr, "warning: Failed to open '%s': for replacement: %s\n",
+ display_name, g_strerror (saved_errno));
+ }
+
+ if (!gvdb_table_write_contents (table, filename, FALSE, error))
+ {
+ if (fd >= 0)
+ close (fd);
+ return FALSE;
+ }
+
+ if (fd >= 0)
+ {
+ /* Mark previous database as invalid. */
+ write (fd, "\0\0\0\0\0\0\0\0", 8);
+ close (fd);
+ }
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL);
+
+ if (bus != NULL)
+ {
+ g_autofree gchar *object_name = NULL;
+ g_autofree gchar *object_path = NULL;
+
+ object_name = g_path_get_basename (filename);
+ object_path = g_strconcat ("/ca/desrt/dconf/Writer/", object_name, NULL);
+
+ /* Ignore all D-Bus errors. */
+ g_dbus_connection_emit_signal (bus, NULL, object_path,
+ "ca.desrt.dconf.Writer",
+ "WritabilityNotify",
+ g_variant_new ("(s)", "/"),
+ NULL);
+ g_dbus_connection_flush_sync (bus, NULL, NULL);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+update_all (const gchar *dirname,
+ GError **error)
+{
+ gboolean failed = FALSE;
+ g_autoptr(GPtrArray) files = NULL;
+
+ files = list_directory (dirname, S_IFDIR, error);
+ if (files == NULL)
+ return FALSE;
+
+ for (guint i = 0; i != files->len; ++i)
+ {
+ const gchar *name;
+ g_autoptr(GError) local_error = NULL;
+
+ name = g_ptr_array_index (files, i);
+ if (!g_str_has_suffix (name, ".d"))
+ continue;
+
+ if (!update_directory (name, &local_error))
+ {
+ g_autofree gchar *display_name = g_filename_display_name (name);
+ g_fprintf (stderr, "%s: %s\n",
+ display_name, local_error->message);
+ failed = TRUE;
+ }
+ }
+
+ if (failed)
+ {
+ g_set_error_literal (error, DCONF_ERROR, DCONF_ERROR_FAILED,
+ "failed to update at least one of the databases");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+dconf_compile (const gchar **argv,
+ GError **error)
+{
+ gboolean byteswap;
+ const gchar *output;
+ const gchar *dir;
+ g_autoptr(GHashTable) table = NULL;
+
+ output = argv[0];
+ if (output == NULL)
+ return option_error_set (error, "output file not specified");
+
+ dir = argv[1];
+ if (dir == NULL)
+ return option_error_set (error, "keyfile .d directory not specified");
+
+ if (argv[2] != NULL)
+ return option_error_set (error, "too many arguments");
+
+ table = read_directory (dir, error);
+ if (table == NULL)
+ return FALSE;
+
+ /* We always write the result of "dconf compile" as little endian so
+ * that it can be installed in /usr/share */
+ byteswap = (G_BYTE_ORDER == G_BIG_ENDIAN);
+ return gvdb_table_write_contents (table, output, byteswap, error);
+}
+
+static gboolean
+dconf_update (const gchar **argv,
+ GError **error)
+{
+ gint index = 0;
+ g_autofree gchar *dir = NULL;
+
+ if (argv[index] != NULL)
+ {
+ dir = g_strdup (argv[0]);
+ index += 1;
+ }
+ else
+ dir = g_build_filename (SYSCONFDIR, "dconf", "db", NULL);
+
+ if (argv[index] != NULL)
+ return option_error_set (error, "too many arguments");
+
+ return update_all (dir, error);
+}
+
+typedef struct {
+ const char *name;
+ gboolean (*func)(const gchar **, GError **);
+ const char *description;
+ const char *synopsis;
+} Command;
+
+static const Command commands[] = {
+ {
+ "help", dconf_help,
+ "Print help", " COMMAND "
+ },
+ {
+ "read", dconf_read,
+ "Read the value of a key. -d to read default values.",
+ " [-d] KEY "
+ },
+ {
+ "list", dconf_list,
+ "List the sub-keys and sub-dirs of a dir",
+ " DIR "
+ },
+ {
+ "list-locks", dconf_list_locks,
+ "List the locks under a dir",
+ " DIR "
+ },
+ {
+ "write", dconf_write,
+ "Write a new value to a key",
+ " KEY VALUE "
+ },
+ {
+ "reset", dconf_reset,
+ "Reset a key or dir. -f is required for dirs.",
+ " [-f] PATH "
+ },
+ {
+ "compile", dconf_compile,
+ "Compile a binary database from keyfiles",
+ " OUTPUT KEYFILEDIR "
+ },
+ {
+ "update", dconf_update,
+ "Update the system dconf databases",
+ ""
+ },
+ {
+ "watch", dconf_watch,
+ "Watch a path for key changes",
+ " PATH "
+ },
+ {
+ "dump", dconf_dump,
+ "Dump an entire subpath to stdout",
+ " DIR "
+ },
+ {
+ "load", dconf_load,
+ "Populate a subpath from stdin",
+ " DIR "
+ },
+ {
+ "blame", dconf_blame,
+ "",
+ ""
+ },
+ {
+ "_complete", dconf_complete,
+ "",
+ " SUFFIX PATH "
+ },
+ {},
+};
+
+static const gchar usage[] =
+ "Usage:\n"
+ " dconf COMMAND [ARGS...]\n"
+ "\n"
+ "Commands:\n"
+ " help Show this information\n"
+ " read Read the value of a key\n"
+ " list List the contents of a dir\n"
+ " write Change the value of a key\n"
+ " reset Reset the value of a key or dir\n"
+ " compile Compile a binary database from keyfiles\n"
+ " update Update the system databases\n"
+ " watch Watch a path for changes\n"
+ " dump Dump an entire subpath to stdout\n"
+ " load Populate a subpath from stdin\n"
+ "\n"
+ "Use 'dconf help COMMAND' to get detailed help.\n"
+ "\n";
+
+static const Command *
+command_with_name (const gchar *name)
+{
+ const Command *cmd;
+
+ for (cmd = commands; cmd->name != NULL; ++cmd)
+ if (g_strcmp0 (cmd->name, name) == 0)
+ return cmd;
+
+ return NULL;
+}
+
+static void
+command_show_help (const Command *cmd,
+ FILE *file)
+{
+ g_autoptr(GString) s = g_string_sized_new (1024);
+
+ if (cmd == NULL)
+ {
+ g_string_append (s, usage);
+ }
+ else
+ {
+ /* Generate command specific usage help text. */
+
+ g_string_append (s, "Usage:\n");
+ g_string_append_printf (s, " dconf %s%s\n\n", cmd->name, cmd->synopsis);
+
+ if (!g_str_equal (cmd->description, ""))
+ g_string_append_printf (s, "%s\n\n", cmd->description);
+
+ if (!g_str_equal (cmd->synopsis, ""))
+ {
+ g_string_append (s, "Arguments:\n");
+
+ if (strstr (cmd->synopsis, " COMMAND ") != NULL)
+ g_string_append (s, " COMMAND "
+ "The (optional) command to explain\n");
+
+ if (strstr (cmd->synopsis, " PATH ") != NULL)
+ g_string_append (s, " PATH Either a KEY or DIR\n");
+
+ if (strstr (cmd->synopsis, " PATH ") != NULL ||
+ strstr (cmd->synopsis, " KEY ") != NULL)
+ g_string_append (s, " KEY A key path (starting, but not ending with '/')\n");
+
+ if (strstr (cmd->synopsis, " PATH ") != NULL ||
+ strstr (cmd->synopsis, " DIR ") != NULL)
+ g_string_append (s, " DIR A directory path (starting and ending with '/')\n");
+
+ if (strstr (cmd->synopsis, " VALUE ") != NULL)
+ g_string_append (s, " VALUE The value to write (in GVariant format)\n");
+
+ if (strstr (cmd->synopsis, " OUTPUT ") != NULL)
+ g_string_append (s, " OUTPUT The filename of the (binary) output\n");
+
+ if (strstr (cmd->synopsis, " KEYFILEDIR ") != NULL)
+ g_string_append (s, " KEYFILEDIR The path to the .d directory containing keyfiles\n");
+
+ if (strstr (cmd->synopsis, " SUFFIX ") != NULL)
+ g_string_append (s, " SUFFIX An empty string '' or '/'.\n");
+
+ g_string_append (s, "\n");
+ }
+ }
+
+ g_fprintf (file, "%s", s->str);
+}
+
+static gboolean
+dconf_help (const gchar **argv, GError **error)
+{
+ const gchar *name = *argv;
+ command_show_help (command_with_name (name), stdout);
+ return TRUE;
+}
+
+int
+main (int argc, const char **argv)
+{
+ const Command *cmd;
+ g_autoptr(GError) error = NULL;
+
+ setlocale (LC_ALL, "");
+ g_set_prgname (argv[0]);
+
+ if (argc <= 1)
+ {
+ g_fprintf (stderr, "error: no command specified\n\n");
+ command_show_help (NULL, stderr);
+ return 2;
+ }
+
+ cmd = command_with_name (argv[1]);
+ if (cmd == NULL)
+ {
+ g_fprintf (stderr, "error: unknown command %s\n\n", argv[1]);
+ command_show_help (cmd, stderr);
+ return 2;
+ }
+
+ if (cmd->func (argv + 2, &error))
+ return 0;
+
+ g_assert (error != NULL);
+
+ /* Invalid arguments passed, show usage on stderr. */
+ if (error->domain == G_OPTION_ERROR)
+ {
+ g_fprintf (stderr, "error: %s\n\n", error->message);
+ command_show_help (cmd, stderr);
+ return 2;
+ }
+ else
+ {
+ g_fprintf (stderr, "error: %s\n", error->message);
+ return 1;
+ }
+}
diff --git a/bin/dconf.vala b/bin/dconf.vala
deleted file mode 100644
index 6448808..0000000
--- a/bin/dconf.vala
+++ /dev/null
@@ -1,395 +0,0 @@
-/*
- * Copyright © 2010, 2011 Codethink Limited
- * Copyright © 2011 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, see <http://www.gnu.org/licenses/>.
- *
- * Author: Ryan Lortie <desrt@desrt.ca>
- */
-
-void show_help (bool requested, string? command) {
- var str = new StringBuilder ();
- string? description = null;
- string? synopsis = null;
-
- switch (command) {
- case null:
- break;
-
- case "help":
- description = "Print help";
- synopsis = " COMMAND ";
- break;
-
- case "read":
- description = "Read the value of a key. -d to read default values.";
- synopsis = " [-d] KEY ";
- break;
-
- case "list":
- description = "List the sub-keys and sub-dirs of a dir";
- synopsis = " DIR ";
- break;
-
- case "list-locks":
- description = "List the locks under a dir";
- synopsis = " DIR ";
- break;
-
- case "write":
- description = "Write a new value to a key";
- synopsis = " KEY VALUE ";
- break;
-
- case "reset":
- description = "Reset a key or dir. -f is required for dirs.";
- synopsis = " [-f] PATH ";
- break;
-
- case "compile":
- description = "Compile a binary database from keyfiles";
- synopsis = " OUTPUT KEYFILEDIR ";
- break;
-
- case "update":
- description = "Update the system dconf databases";
- synopsis = " [DIR] ";
- break;
-
- case "watch":
- description = "Watch a path for key changes";
- synopsis = " PATH ";
- break;
-
- case "dump":
- description = "Dump an entire subpath to stdout";
- synopsis = " DIR ";
- break;
-
- case "load":
- description = "Populate a subpath from stdin";
- synopsis = " DIR ";
- break;
-
- default:
- str.append_printf ("Unknown command '%s'\n\n", command);
- command = null;
- break;
- }
-
- if (command == null) {
- str.append (
-"""Usage:
- dconf COMMAND [ARGS...]
-
-Commands:
- help Show this information
- read Read the value of a key
- list List the contents of a dir
- write Change the value of a key
- reset Reset the value of a key or dir
- compile Compile a binary database from keyfiles
- update Update the system databases
- watch Watch a path for changes
- dump Dump an entire subpath to stdout
- load Populate a subpath from stdin
-
-Use 'dconf help COMMAND' to get detailed help.
-
-""");
- } else {
- str.append ("Usage:\n");
- str.append_printf (" dconf %s%s\n\n", command, synopsis);
- str.append_printf ("%s\n\n", description);
-
- if (synopsis != "") {
- str.append ("Arguments:\n");
-
- if (" COMMAND " in synopsis) {
- str.append (" COMMAND The (optional) command to explain\n");
- }
-
- if (" PATH " in synopsis) {
- str.append (" PATH Either a KEY or DIR\n");
- }
-
- if (" PATH " in synopsis || " KEY " in synopsis) {
- str.append (" KEY A key path (starting, but not ending with '/')\n");
- }
-
- if (" PATH " in synopsis || " DIR " in synopsis) {
- str.append (" DIR A directory path (starting and ending with '/')\n");
- }
-
- if (" VALUE " in synopsis) {
- str.append (" VALUE The value to write (in GVariant format)\n");
- }
-
- if (" OUTPUT " in synopsis) {
- str.append (" OUTPUT The filename of the (binary) output\n");
- }
-
- if (" KEYFILEDIR " in synopsis) {
- str.append (" KEYFILEDIR The path to the .d directory containing keyfiles\n");
- }
- }
-
- str.append ("\n");
- }
-
- if (requested) {
- print ("%s", str.str);
- } else {
- printerr ("%s", str.str);
- }
-}
-
-void dconf_help (string[] args) throws Error {
- show_help (true, args[2]);
-}
-
-void dconf_read (string?[] args) throws Error {
- var client = new DConf.Client ();
- var flags = DConf.ReadFlags.NONE;
- var index = 2;
-
- if (args[index] == "-d") {
- flags = DConf.ReadFlags.DEFAULT_VALUE;
- index++;
- }
-
- var key = args[index];
-
- DConf.verify_key (key);
-
- if (args[index + 1] != null) {
- throw new OptionError.FAILED ("too many arguments");
- }
-
- var result = client.read_full (key, flags, null);
-
- if (result != null) {
- print ("%s\n", result.print (true));
- }
-}
-
-void dconf_list (string?[] args) throws Error {
- var client = new DConf.Client ();
- var dir = args[2];
-
- DConf.verify_dir (dir);
-
- var items = client.list (dir);
- GLib.qsort_with_data<string> (items, sizeof (string), (a, b) => GLib.strcmp (a, b));
-
- foreach (var item in items) {
- print ("%s\n", item);
- }
-}
-
-void dconf_list_locks (string?[] args) throws Error {
- var client = new DConf.Client ();
- var dir = args[2];
-
- DConf.verify_dir (dir);
-
- if (args[3] != null) {
- throw new OptionError.FAILED ("too many arguments");
- }
-
- foreach (var item in client.list_locks (dir)) {
- print ("%s\n", item);
- }
-}
-
-void dconf_write (string?[] args) throws Error {
- var client = new DConf.Client ();
- var key = args[2];
- if (key == null) {
- throw new OptionError.FAILED ("key not specified");
- }
-
- var val = args[3];
- if (val == null) {
- throw new OptionError.FAILED ("value not specified");
- }
-
- DConf.verify_key (key);
-
- if (args[4] != null) {
- throw new OptionError.FAILED ("too many arguments");
- }
-
- client.write_sync (key, Variant.parse (null, val));
-}
-
-void dconf_reset (string?[] args) throws Error {
- var client = new DConf.Client ();
- bool force = false;
- var index = 2;
-
- if (args[index] == "-f") {
- force = true;
- index++;
- }
-
- var path = args[index];
-
- DConf.verify_path (path);
-
- if (args[index + 1] != null) {
- throw new OptionError.FAILED ("too many arguments");
- }
-
- if (DConf.is_dir (path) && !force) {
- throw new OptionError.FAILED ("-f must be given to (recursively) reset entire dirs");
- }
-
- client.write_sync (path, null);
-}
-
-void show_path (DConf.Client client, string path) {
- if (DConf.is_key (path)) {
- var value = client.read (path);
-
- print (" %s\n", value != null ? value.print (true) : "unset");
- }
-}
-
-void watch_function (DConf.Client client, string path, string[] items, string? tag) {
- foreach (var item in items) {
- var full = path + item;
- print ("%s\n", full);
- show_path (client, full);
- }
- print ("\n");
-}
-
-void dconf_watch (string?[] args) throws Error {
- var client = new DConf.Client ();
- var path = args[2];
-
- DConf.verify_path (path);
-
- if (args[3] != null) {
- throw new OptionError.FAILED ("too many arguments");
- }
-
- client.changed.connect (watch_function);
- client.watch_sync (path);
-
- new MainLoop (null, false).run ();
-}
-
-void dconf_blame (string?[] args) throws Error {
- var connection = Bus.get_sync (BusType.SESSION, null);
- var reply = connection.call_sync ("ca.desrt.dconf", "/ca/desrt/dconf", "ca.desrt.dconf.ServiceInfo", "Blame",
- null, new VariantType ("(s)"), DBusCallFlags.NONE, -1, null);
- print ("%s", reply.get_child_value (0).get_string (null));
-}
-
-void dconf_complete (string[] args) throws Error {
- var suffix = args[2];
- if (suffix == null) {
- throw new OptionError.FAILED ("suffix not specified");
- }
-
- var path = args[3];
- if (path == null) {
- throw new OptionError.FAILED ("path not specified");
- }
-
- if (args[4] != null) {
- throw new OptionError.FAILED ("too many arguments");
- }
-
- if (path == "") {
- print ("/\n");
- return;
- }
-
- if (path[0] == '/') {
- var client = new DConf.Client ();
- var last = 0;
-
- for (var i = 1; path[i] != '\0'; i++) {
- if (path[i] == '/') {
- last = i;
- }
- }
-
- var dir = path.substring (0, last + 1);
- foreach (var item in client.list (dir)) {
- var full_item = dir + item;
-
- if (full_item.has_prefix (path) && item.has_suffix (suffix)) {
- print ("%s%s\n", full_item, full_item.has_suffix ("/") ? "" : " ");
- }
- }
- }
-}
-
-delegate void Command (string[] args) throws Error;
-
-struct CommandMapping {
- unowned Command func;
- string name;
-
- public CommandMapping (string name, Command func) {
- this.name = name;
- this.func = func;
- }
-}
-
-int main (string[] args) {
- assert (args.length != 0);
- Environment.set_prgname (args[0]);
-
- Intl.setlocale (LocaleCategory.ALL, "");
-
- var map = new CommandMapping[] {
- CommandMapping ("help", dconf_help),
- CommandMapping ("read", dconf_read),
- CommandMapping ("list", dconf_list),
- CommandMapping ("list-locks", dconf_list_locks),
- CommandMapping ("write", dconf_write),
- CommandMapping ("reset", dconf_reset),
- CommandMapping ("compile", dconf_compile),
- CommandMapping ("update", dconf_update),
- CommandMapping ("watch", dconf_watch),
- CommandMapping ("dump", dconf_dump),
- CommandMapping ("load", dconf_load),
- CommandMapping ("blame", dconf_blame),
- CommandMapping ("_complete", dconf_complete)
- };
-
- try {
- if (args[1] == null) {
- throw new OptionError.FAILED ("no command specified");
- }
-
- foreach (var mapping in map) {
- if (mapping.name == args[1]) {
- mapping.func (args);
- return 0;
- }
- }
-
- throw new OptionError.FAILED ("unknown command %s", args[1]);
- } catch (Error e) {
- stderr.printf ("error: %s\n\n", e.message);
- show_help (false, args[1]);
- return 1;
- }
-}
diff --git a/bin/gvdb.vapi b/bin/gvdb.vapi
deleted file mode 100644
index 1f3e92a..0000000
--- a/bin/gvdb.vapi
+++ /dev/null
@@ -1,21 +0,0 @@
-[CCode (cheader_filename = "../gvdb/gvdb-builder.h")]
-namespace Gvdb {
- [Compact]
- [CCode (cname = "GHashTable")]
- class HashTable : GLib.HashTable<string, Item> {
- public HashTable (HashTable? parent = null, string? key = null);
- public unowned Item insert (string key);
- public void insert_string (string key, string value);
- [CCode (cname = "gvdb_table_write_contents")]
- public void write_contents (string filename, bool byteswap = false) throws GLib.Error;
- }
-
- [Compact]
- class Item {
- public void set_value (GLib.Variant value);
- public void set_hash_table (HashTable table);
- public void set_parent (Item parent);
- }
-}
-
-// vim:noet ts=4 sw=4
diff --git a/bin/meson.build b/bin/meson.build
index 6fd4ca2..ceafa82 100644
--- a/bin/meson.build
+++ b/bin/meson.build
@@ -1,14 +1,10 @@
sources = gvdb_builder + libdconf_vapi + files(
- 'dconf-dump.vala',
- 'dconf-update.vala',
- 'dconf.vala',
- 'gvdb.vapi',
+ 'dconf.c',
)
bin_deps = [
libdconf_common_dep,
libdconf_dep,
- valac.find_library('posix'),
]
dconf = executable(
diff --git a/dconf.doap b/dconf.doap
index 0cfb476..6b6a1d8 100644
--- a/dconf.doap
+++ b/dconf.doap
@@ -13,7 +13,6 @@
<bug-database rdf:resource="https://gitlab.gnome.org/GNOME/dconf/issues/"/>
<category rdf:resource="http://api.gnome.org/doap-extensions#core" />
<programming-language>C</programming-language>
- <programming-language>Vala</programming-language>
<maintainer>
<foaf:Person>
diff --git a/meson.build b/meson.build
index 4ebdbb3..85bcc02 100644
--- a/meson.build
+++ b/meson.build
@@ -1,5 +1,5 @@
project(
- 'dconf', ['c', 'vala'],
+ 'dconf', ['c'],
version: '0.31.2',
license: 'LGPL2.1+',
meson_version: '>= 0.46.0',
@@ -20,7 +20,6 @@ revision = 0
libversion = '@0@.@1@.@2@'.format(soversion, current, revision)
cc = meson.get_compiler('c')
-valac = meson.get_compiler('vala')
# compiler flags
common_flags = ['-DSYSCONFDIR="@0@"'.format(dconf_sysconfdir)]
diff --git a/tests/test-dconf.py b/tests/test-dconf.py
index 88f6769..51e522b 100755
--- a/tests/test-dconf.py
+++ b/tests/test-dconf.py
@@ -105,6 +105,7 @@ class DBusTest(unittest.TestCase):
os.mkdir(self.dbus_dir, mode=0o700)
os.mkdir(os.path.join(self.config_home, 'dconf'))
+ os.environ['DCONF_BLAME'] = ''
os.environ['XDG_RUNTIME_DIR'] = self.runtime_dir
os.environ['XDG_CONFIG_HOME'] = self.config_home
@@ -236,6 +237,9 @@ class DBusTest(unittest.TestCase):
['write', '/key', 'not-a-gvariant-value'],
# Too many arguments:
['write', '/key', '1', '2'],
+
+ # Too many arguments:
+ ['update', 'a', 'b'],
]
for args in cases:
@@ -244,6 +248,15 @@ class DBusTest(unittest.TestCase):
dconf(*args, stderr=subprocess.PIPE)
self.assertRegex(cm.exception.stderr, 'Usage:')
+ def test_help(self):
+ """Help show usage information on stdout and exits with success."""
+
+ stdout = dconf('help', 'write').stdout
+ self.assertRegex(stdout, 'dconf write KEY VALUE')
+
+ stdout = dconf('help', 'help').stdout
+ self.assertRegex(stdout, 'dconf help COMMAND')
+
def test_read_nonexisiting(self):
"""Reading missing key produces no output. """
@@ -743,6 +756,22 @@ class DBusTest(unittest.TestCase):
env=env, stderr=subprocess.PIPE)
self.assertRegex(cm.exception.stderr, 'non-writable keys')
+ def test_dconf_blame(self):
+ """Blame returns recorded information about write operations.
+
+ Recorded information include sender bus name, sender process id and
+ object path the write operations was invoked on.
+ """
+
+ p = subprocess.Popen([dconf_exe, 'write', '/prime', '307'])
+ p.wait()
+
+ blame = dconf('blame').stdout
+ print(blame)
+
+ self.assertRegex(blame, 'Sender: ')
+ self.assertRegex(blame, 'PID: {}'.format(p.pid))
+ self.assertRegex(blame, 'Object path: /ca/desrt/dconf/Writer/user')
if __name__ == '__main__':
# Make sure we don't pick up mandatory profile.