diff options
Diffstat (limited to 'bin/dconf.c')
-rw-r--r-- | bin/dconf.c | 1175 |
1 files changed, 1175 insertions, 0 deletions
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; + } +} |