[dconf: 2/4] bin: Rewrite dconf utility in C



commit d36936fe111147db6ca2cd890ecec48a74ae343a
Author: Tomasz Miąsko <tomasz miasko gmail com>
Date:   Tue Dec 11 00:00:00 2018 +0000

    bin: Rewrite dconf utility in C
    
    Intentional functional changes:
    
    * Update is no longer conditional on mtime.
    * Help information is shown on erroneous usage, but not otherwise.
    
    Fixes issue #38.

 README                |    2 -
 bin/dconf-dump.vala   |   86 ----
 bin/dconf-update.vala |  292 ------------
 bin/dconf.c           | 1175 +++++++++++++++++++++++++++++++++++++++++++++++++
 bin/dconf.vala        |  395 -----------------
 bin/gvdb.vapi         |   21 -
 bin/meson.build       |    6 +-
 dconf.doap            |    1 -
 meson.build           |    3 +-
 9 files changed, 1177 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.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/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)]


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