/*
* 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 .
*
* Author: Ryan Lortie
* Tomasz Miąsko
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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 gchar *
get_system_db_path ()
{
return g_build_filename (SYSCONFDIR, "dconf", "db", NULL);
}
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 = get_system_db_path ();
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",
" [DBDIR] "
},
{
"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");
if (strstr (cmd->synopsis, " [DBDIR] ") != NULL)
{
g_autofree gchar *path = get_system_db_path ();
g_string_append_printf (s, " DBDIR The databases directory. Default: %s\n", path);
}
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;
}
}