diff options
-rw-r--r-- | README | 2 | ||||
-rw-r--r-- | bin/dconf-dump.vala | 86 | ||||
-rw-r--r-- | bin/dconf-update.vala | 292 | ||||
-rw-r--r-- | bin/dconf.c | 1175 | ||||
-rw-r--r-- | bin/dconf.vala | 395 | ||||
-rw-r--r-- | bin/gvdb.vapi | 21 | ||||
-rw-r--r-- | bin/meson.build | 6 | ||||
-rw-r--r-- | dconf.doap | 1 | ||||
-rw-r--r-- | meson.build | 3 | ||||
-rwxr-xr-x | tests/test-dconf.py | 29 |
10 files changed, 1206 insertions, 804 deletions
@@ -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( @@ -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. |