aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPřemysl Janouch <p.janouch@gmail.com>2015-05-02 23:00:34 +0200
committerPřemysl Janouch <p.janouch@gmail.com>2015-05-02 23:06:19 +0200
commitc23898166c8a119bb5088be286b4b704ca4742a5 (patch)
tree34055d4fba9a23a9bf919fb7d63d9d017ead8ef9
parent3b8e8cc97f3e02b0313f58206e0d6043a668fca9 (diff)
downloadxK-c23898166c8a119bb5088be286b4b704ca4742a5.tar.gz
xK-c23898166c8a119bb5088be286b4b704ca4742a5.tar.xz
xK-c23898166c8a119bb5088be286b4b704ca4742a5.zip
degesch: use the new configuration
This is a simple, almost 1:1 conversion. Needs further unfucking.
-rw-r--r--degesch.c632
1 files changed, 394 insertions, 238 deletions
diff --git a/degesch.c b/degesch.c
index 27886f0..1235b5c 100644
--- a/degesch.c
+++ b/degesch.c
@@ -67,41 +67,6 @@ enum
#include <readline/readline.h>
#include <readline/history.h>
-// --- Configuration (application-specific) ------------------------------------
-
-// TODO: reject all junk present in the configuration; there can be newlines
-
-static struct config_item g_config_table[] =
-{
- { "nickname", NULL, "IRC nickname" },
- { "username", NULL, "IRC user name" },
- { "realname", NULL, "IRC real name/e-mail" },
-
- { "irc_host", NULL, "Address of the IRC server" },
- { "irc_port", "6667", "Port of the IRC server" },
- { "ssl", "off", "Whether to use SSL" },
- { "ssl_cert", NULL, "Client SSL certificate (PEM)" },
- { "ssl_verify", "on", "Whether to verify certificates" },
- { "ssl_ca_file", NULL, "OpenSSL CA bundle file" },
- { "ssl_ca_path", NULL, "OpenSSL CA bundle path" },
- { "autojoin", NULL, "Channels to join on start" },
- { "reconnect", "on", "Whether to reconnect on error" },
- { "reconnect_delay", "5", "Time between reconnecting" },
-
- { "socks_host", NULL, "Address of a SOCKS 4a/5 proxy" },
- { "socks_port", "1080", "SOCKS port number" },
- { "socks_username", NULL, "SOCKS auth. username" },
- { "socks_password", NULL, "SOCKS auth. password" },
-
- { "isolate_buffers", "off", "Isolate global/server buffers" },
-
-#define XX(x, y, z) { "attr_" y, NULL, z },
- ATTR_TABLE (XX)
-#undef XX
-
- { NULL, NULL, NULL }
-};
-
// --- Application data --------------------------------------------------------
// All text stored in our data structures is encoded in UTF-8.
@@ -492,7 +457,7 @@ struct app_context
{
// Configuration:
- struct str_map config; ///< User configuration
+ struct config config; ///< Program configuration
char *attrs[ATTR_COUNT]; ///< Terminal attributes
bool no_colors; ///< Colour output mode
bool reconnect; ///< Whether to reconnect on conn. fail.
@@ -546,10 +511,7 @@ app_context_init (struct app_context *self)
{
memset (self, 0, sizeof *self);
- str_map_init (&self->config);
- self->config.free = free;
- load_config_defaults (&self->config, g_config_table);
-
+ config_init (&self->config);
poller_init (&self->poller);
server_init (&self->server, &self->poller);
@@ -582,7 +544,7 @@ app_context_init (struct app_context *self)
static void
app_context_free (struct app_context *self)
{
- str_map_free (&self->config);
+ config_free (&self->config);
for (size_t i = 0; i < ATTR_COUNT; i++)
free (self->attrs[i]);
@@ -605,6 +567,224 @@ static void refresh_prompt (struct app_context *ctx);
static char *irc_cut_nickname (const char *prefix);
static const char *irc_find_userhost (const char *prefix);
+// --- Configuration -----------------------------------------------------------
+
+// TODO: eventually add "on_change" callbacks
+
+static bool
+config_validate_nonjunk_string
+ (const struct config_item_ *item, struct error **e)
+{
+ if (item->type == CONFIG_ITEM_NULL)
+ return true;
+
+ hard_assert (config_item_type_is_string (item->type));
+ for (size_t i = 0; i < item->value.string.len; i++)
+ {
+ // Not even a tabulator
+ unsigned char c = item->value.string.str[i];
+ if (c < 32)
+ {
+ error_set (e, "control characters are not allowed");
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool
+config_validate_nonnegative
+ (const struct config_item_ *item, struct error **e)
+{
+ if (item->type == CONFIG_ITEM_NULL)
+ return true;
+
+ hard_assert (item->type == CONFIG_ITEM_INTEGER);
+ if (item->value.integer >= 0)
+ return true;
+
+ error_set (e, "must be non-negative");
+ return false;
+}
+
+struct config_schema g_config_server[] =
+{
+ { .name = "nickname",
+ .comment = "IRC nickname",
+ .type = CONFIG_ITEM_STRING,
+ .validate = config_validate_nonjunk_string },
+ { .name = "username",
+ .comment = "IRC user name",
+ .type = CONFIG_ITEM_STRING,
+ .validate = config_validate_nonjunk_string },
+ { .name = "realname",
+ .comment = "IRC real name/e-mail",
+ .type = CONFIG_ITEM_STRING,
+ .validate = config_validate_nonjunk_string },
+
+ { .name = "irc_host",
+ .comment = "Address of the IRC server",
+ .type = CONFIG_ITEM_STRING,
+ .validate = config_validate_nonjunk_string },
+ { .name = "irc_port",
+ .comment = "Port of the IRC server",
+ .type = CONFIG_ITEM_INTEGER,
+ .validate = config_validate_nonnegative,
+ .default_ = "6667" },
+
+ { .name = "ssl",
+ .comment = "Whether to use SSL/TLS",
+ .type = CONFIG_ITEM_BOOLEAN,
+ .default_ = "off" },
+ { .name = "ssl_cert",
+ .comment = "Client SSL certificate (PEM)",
+ .type = CONFIG_ITEM_STRING },
+ { .name = "ssl_verify",
+ .comment = "Whether to verify certificates",
+ .type = CONFIG_ITEM_BOOLEAN,
+ .default_ = "on" },
+ { .name = "ssl_ca_file",
+ .comment = "OpenSSL CA bundle file",
+ .type = CONFIG_ITEM_STRING },
+ { .name = "ssl_ca_path",
+ .comment = "OpenSSL CA bundle path",
+ .type = CONFIG_ITEM_STRING },
+
+ { .name = "autojoin",
+ .comment = "Channels to join on start",
+ .type = CONFIG_ITEM_STRING_ARRAY,
+ .validate = config_validate_nonjunk_string },
+ { .name = "reconnect",
+ .comment = "Whether to reconnect on error",
+ .type = CONFIG_ITEM_BOOLEAN,
+ .default_ = "on" },
+ { .name = "reconnect_delay",
+ .comment = "Time between reconnecting",
+ .type = CONFIG_ITEM_INTEGER,
+ .default_ = "5" },
+
+ { .name = "socks_host",
+ .comment = "Address of a SOCKS 4a/5 proxy",
+ .type = CONFIG_ITEM_STRING,
+ .validate = config_validate_nonjunk_string },
+ { .name = "socks_port",
+ .comment = "SOCKS port number",
+ .type = CONFIG_ITEM_INTEGER,
+ .validate = config_validate_nonnegative,
+ .default_ = "1080" },
+ { .name = "socks_username",
+ .comment = "SOCKS auth. username",
+ .type = CONFIG_ITEM_STRING },
+ { .name = "socks_password",
+ .comment = "SOCKS auth. password",
+ .type = CONFIG_ITEM_STRING },
+ {}
+};
+
+struct config_schema g_config_behaviour[] =
+{
+ { .name = "isolate_buffers",
+ .comment = "Don't leak messages from the server and global buffers",
+ .type = CONFIG_ITEM_BOOLEAN,
+ .default_ = "off" },
+ {}
+};
+
+struct config_schema g_config_attributes[] =
+{
+#define XX(x, y, z) { .name = y, .comment = z, .type = CONFIG_ITEM_STRING },
+ ATTR_TABLE (XX)
+#undef XX
+ {}
+};
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static void
+load_config_server (struct config_item_ *subtree, void *user_data)
+{
+ (void) user_data;
+ // This will eventually iterate over the object and create servers
+ config_schema_apply_to_object (g_config_server, subtree);
+}
+
+static void
+load_config_behaviour (struct config_item_ *subtree, void *user_data)
+{
+ (void) user_data;
+ config_schema_apply_to_object (g_config_behaviour, subtree);
+}
+
+static void
+load_config_attributes (struct config_item_ *subtree, void *user_data)
+{
+ (void) user_data;
+ config_schema_apply_to_object (g_config_attributes, subtree);
+}
+
+static void
+register_config_modules (struct app_context *ctx)
+{
+ struct config *config = &ctx->config;
+ config_register_module (config,
+ "server", load_config_server, ctx);
+ config_register_module (config,
+ "behaviour", load_config_behaviour, ctx);
+ config_register_module (config,
+ "attributes", load_config_attributes, ctx);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static const char *
+get_config_string (struct app_context *ctx, const char *key)
+{
+ struct config_item_ *item = config_item_get (ctx->config.root, key, NULL);
+ hard_assert (item);
+ if (item->type == CONFIG_ITEM_NULL)
+ return NULL;
+ hard_assert (config_item_type_is_string (item->type));
+ return item->value.string.str;
+}
+
+static bool
+set_config_string (struct app_context *ctx, const char *key, const char *value)
+{
+ struct config_item_ *item = config_item_get (ctx->config.root, key, NULL);
+ hard_assert (item);
+
+ struct str s;
+ str_init (&s);
+ str_append (&s, value);
+ struct config_item_ *new_ = config_item_string (&s);
+ str_free (&s);
+
+ struct error *e = NULL;
+ if (config_item_set_from (item, new_, &e))
+ return true;
+
+ config_item_destroy (new_);
+ print_error ("couldn't set `%s' in configuration: %s", key, e->message);
+ error_free (e);
+ return false;
+}
+
+static int64_t
+get_config_integer (struct app_context *ctx, const char *key)
+{
+ struct config_item_ *item = config_item_get (ctx->config.root, key, NULL);
+ hard_assert (item && item->type == CONFIG_ITEM_INTEGER);
+ return item->value.integer;
+}
+
+static bool
+get_config_boolean (struct app_context *ctx, const char *key)
+{
+ struct config_item_ *item = config_item_get (ctx->config.root, key, NULL);
+ hard_assert (item && item->type == CONFIG_ITEM_BOOLEAN);
+ return item->value.boolean;
+}
+
// --- Attributed output -------------------------------------------------------
static struct
@@ -775,12 +955,12 @@ init_attribute (struct app_context *ctx, int id, const char *default_)
{
static const char *table[ATTR_COUNT] =
{
-#define XX(x, y, z) [ATTR_ ## x] = "attr_" y,
+#define XX(x, y, z) [ATTR_ ## x] = "attributes." y,
ATTR_TABLE (XX)
#undef XX
};
- const char *user = str_map_find (&ctx->config, table[id]);
+ const char *user = get_config_string (ctx, table[id]);
if (user)
ctx->attrs[id] = xstrdup (user);
else
@@ -1633,7 +1813,7 @@ init_buffers (struct app_context *ctx)
global->name = xstrdup (PROGRAM_NAME);
server->type = BUFFER_SERVER;
- server->name = xstrdup (str_map_find (&ctx->config, "irc_host"));
+ server->name = xstrdup (get_config_string (ctx, "server.irc_host"));
server->server = &ctx->server;
LIST_APPEND_WITH_TAIL (ctx->buffers, ctx->buffers_tail, global);
@@ -1894,33 +2074,16 @@ irc_send (struct server *s, const char *format, ...)
}
static bool
-irc_get_boolean_from_config
- (struct app_context *ctx, const char *name, bool *value, struct error **e)
-{
- const char *str = str_map_find (&ctx->config, name);
- hard_assert (str != NULL);
-
- if (set_boolean_if_valid (value, str))
- return true;
-
- error_set (e, "invalid configuration value for `%s'", name);
- return false;
-}
-
-static bool
irc_initialize_ssl_ctx (struct server *s, struct error **e)
{
// XXX: maybe we should call SSL_CTX_set_options() for some workarounds
- bool verify;
- if (!irc_get_boolean_from_config (s->ctx, "ssl_verify", &verify, e))
- return false;
-
+ bool verify = get_config_boolean (s->ctx, "server.ssl_verify");
if (!verify)
SSL_CTX_set_verify (s->ssl_ctx, SSL_VERIFY_NONE, NULL);
- const char *ca_file = str_map_find (&s->ctx->config, "ca_file");
- const char *ca_path = str_map_find (&s->ctx->config, "ca_path");
+ const char *ca_file = get_config_string (s->ctx, "server.ca_file");
+ const char *ca_path = get_config_string (s->ctx, "server.ca_path");
struct error *error = NULL;
if (ca_file || ca_path)
@@ -1970,7 +2133,7 @@ irc_initialize_ssl (struct server *s, struct error **e)
if (!s->ssl)
goto error_ssl_2;
- const char *ssl_cert = str_map_find (&s->ctx->config, "ssl_cert");
+ const char *ssl_cert = get_config_string (s->ctx, "server.ssl_cert");
if (ssl_cert)
{
char *path = resolve_config_filename (ssl_cert);
@@ -3147,7 +3310,7 @@ irc_process_message (const struct irc_message *msg,
// we can also use WHOIS if it's not supported (optional by RFC 2812)
irc_send (s, "USERHOST %s", s->irc_user->nickname);
- const char *autojoin = str_map_find (&s->ctx->config, "autojoin");
+ const char *autojoin = get_config_string (s->ctx, "server.autojoin");
if (autojoin)
irc_send (s, "JOIN :%s", autojoin);
}
@@ -4076,8 +4239,7 @@ on_irc_timeout (void *user_data)
{
// Provoke a response from the server
struct server *s = user_data;
- irc_send (s, "PING :%s",
- (char *) str_map_find (&s->ctx->config, "nickname"));
+ irc_send (s, "PING :%s", get_config_string (s->ctx, "server.nickname"));
}
static void
@@ -4146,30 +4308,29 @@ irc_connect (struct server *s, struct error **e)
{
struct app_context *ctx = s->ctx;
- const char *irc_host = str_map_find (&ctx->config, "irc_host");
- const char *irc_port = str_map_find (&ctx->config, "irc_port");
+ const char *irc_host = get_config_string (ctx, "server.irc_host");
+ int64_t irc_port_int = get_config_integer (ctx, "server.irc_port");
- const char *socks_host = str_map_find (&ctx->config, "socks_host");
- const char *socks_port = str_map_find (&ctx->config, "socks_port");
- const char *socks_username = str_map_find (&ctx->config, "socks_username");
- const char *socks_password = str_map_find (&ctx->config, "socks_password");
+ const char *socks_host = get_config_string (ctx, "server.socks_host");
+ int64_t socks_port_int = get_config_integer (ctx, "server.socks_port");
+ const char *socks_username =
+ get_config_string (ctx, "server.socks_username");
+ const char *socks_password =
+ get_config_string (ctx, "server.socks_password");
- const char *nickname = str_map_find (&ctx->config, "nickname");
- const char *username = str_map_find (&ctx->config, "username");
- const char *realname = str_map_find (&ctx->config, "realname");
+ // FIXME: use it as a number everywhere, there's no need for named services
+ // FIXME: memory leak
+ char *irc_port = xstrdup_printf ("%" PRIi64, irc_port_int);
+ char *socks_port = xstrdup_printf ("%" PRIi64, socks_port_int);
- // We have a default value for these
- hard_assert (irc_port && socks_port);
+ const char *nickname = get_config_string (ctx, "server.nickname");
+ const char *username = get_config_string (ctx, "server.username");
+ const char *realname = get_config_string (ctx, "server.realname");
// These are filled automatically if needed
hard_assert (nickname && username && realname);
- // TODO: again, get rid of `struct error' in here. The question is: how
- // do we tell our caller that he should not try to reconnect?
- bool use_ssl;
- if (!irc_get_boolean_from_config (ctx, "ssl", &use_ssl, e))
- return false;
-
+ bool use_ssl = get_config_boolean (ctx, "server.ssl");
if (socks_host)
{
char *address = format_host_port_pair (irc_host, irc_port);
@@ -4289,114 +4450,11 @@ on_readline_input (char *line)
// --- Configuration loading ---------------------------------------------------
static bool
-read_hexa_escape (const char **cursor, struct str *output)
-{
- int i;
- char c, code = 0;
-
- for (i = 0; i < 2; i++)
- {
- c = tolower (*(*cursor));
- if (c >= '0' && c <= '9')
- code = (code << 4) | (c - '0');
- else if (c >= 'a' && c <= 'f')
- code = (code << 4) | (c - 'a' + 10);
- else
- break;
-
- (*cursor)++;
- }
-
- if (!i)
- return false;
-
- str_append_c (output, code);
- return true;
-}
-
-static bool
-read_octal_escape (const char **cursor, struct str *output)
-{
- int i;
- char c, code = 0;
-
- for (i = 0; i < 3; i++)
- {
- c = *(*cursor);
- if (c < '0' || c > '7')
- break;
-
- code = (code << 3) | (c - '0');
- (*cursor)++;
- }
-
- if (!i)
- return false;
-
- str_append_c (output, code);
- return true;
-}
-
-static bool
-read_string_escape_sequence (const char **cursor,
- struct str *output, struct error **e)
-{
- int c;
- switch ((c = *(*cursor)++))
- {
- case '?': str_append_c (output, '?'); break;
- case '"': str_append_c (output, '"'); break;
- case '\\': str_append_c (output, '\\'); break;
- case 'a': str_append_c (output, '\a'); break;
- case 'b': str_append_c (output, '\b'); break;
- case 'f': str_append_c (output, '\f'); break;
- case 'n': str_append_c (output, '\n'); break;
- case 'r': str_append_c (output, '\r'); break;
- case 't': str_append_c (output, '\t'); break;
- case 'v': str_append_c (output, '\v'); break;
-
- case 'e':
- case 'E':
- str_append_c (output, '\x1b');
- break;
-
- case 'x':
- case 'X':
- if (!read_hexa_escape (cursor, output))
- FAIL ("invalid hexadecimal escape");
- break;
-
- case '\0':
- FAIL ("premature end of escape sequence");
-
- default:
- (*cursor)--;
- if (!read_octal_escape (cursor, output))
- FAIL ("unknown escape sequence");
- }
- return true;
-}
-
-static bool
-unescape_string (const char *s, struct str *output, struct error **e)
-{
- int c;
- while ((c = *s++))
- {
- if (c != '\\')
- str_append_c (output, c);
- else if (!read_string_escape_sequence (&s, output, e))
- return false;
- }
- return true;
-}
-
-static bool
autofill_user_info (struct app_context *ctx, struct error **e)
{
- const char *nickname = str_map_find (&ctx->config, "nickname");
- const char *username = str_map_find (&ctx->config, "username");
- const char *realname = str_map_find (&ctx->config, "realname");
+ const char *nickname = get_config_string (ctx, "server.nickname");
+ const char *username = get_config_string (ctx, "server.username");
+ const char *realname = get_config_string (ctx, "server.realname");
if (nickname && username && realname)
return true;
@@ -4407,9 +4465,9 @@ autofill_user_info (struct app_context *ctx, struct error **e)
FAIL ("cannot retrieve user information: %s", strerror (errno));
if (!nickname)
- str_map_set (&ctx->config, "nickname", xstrdup (pwd->pw_name));
+ set_config_string (ctx, "server.nickname", pwd->pw_name);
if (!username)
- str_map_set (&ctx->config, "username", xstrdup (pwd->pw_name));
+ set_config_string (ctx, "server.username", pwd->pw_name);
// Not all systems have the GECOS field but the vast majority does
if (!realname)
@@ -4421,76 +4479,174 @@ autofill_user_info (struct app_context *ctx, struct error **e)
if (comma)
*comma = '\0';
- str_map_set (&ctx->config, "realname", xstrdup (gecos));
+ set_config_string (ctx, "server.realname", gecos);
}
return true;
}
static bool
-unescape_config (struct str_map *input, struct str_map *output, struct error **e)
+read_file (const char *filename, struct str *output, struct error **e)
{
- struct error *error = NULL;
- struct str_map_iter iter;
- str_map_iter_init (&iter, input);
- while (str_map_iter_next (&iter))
+ FILE *fp = fopen (filename, "rb");
+ if (!fp)
{
- struct str value;
- str_init (&value);
- if (!unescape_string (iter.link->data, &value, &error))
- {
- error_set (e, "error reading configuration: %s: %s",
- iter.link->key, error->message);
- error_free (error);
- return false;
- }
-
- str_map_set (output, iter.link->key, str_steal (&value));
+ error_set (e, "could not open `%s' for reading: %s",
+ filename, strerror (errno));
+ return false;
}
- return true;
+
+ char buf[BUFSIZ];
+ size_t len;
+
+ while ((len = fread (buf, 1, sizeof buf, fp)) == sizeof buf)
+ str_append_data (output, buf, len);
+ str_append_data (output, buf, len);
+
+ bool success = !ferror (fp);
+ fclose (fp);
+
+ if (success)
+ return true;
+
+ error_set (e, "error while reading `%s': %s",
+ filename, strerror (errno));
+ return false;
}
static bool
-load_config (struct app_context *ctx, struct error **e)
+load_configuration (struct app_context *ctx, struct error **e)
{
- // TODO: employ a better configuration file format, so that we don't have
- // to do this convoluted post-processing anymore.
-
- struct str_map map;
- str_map_init (&map);
- map.free = free;
+ char *filename = resolve_config_filename (PROGRAM_NAME ".conf");
+ if (!filename)
+ {
+ error_set (e, "cannot find configuration");
+ return false;
+ }
- bool success = read_config_file (&map, e) &&
- unescape_config (&map, &ctx->config, e) &&
- autofill_user_info (ctx, e);
- str_map_free (&map);
+ struct str data;
+ str_init (&data);
+ bool success = read_file (filename, &data, e);
+ free (filename);
if (!success)
+ {
+ str_free (&data);
return false;
+ }
- const char *irc_host = str_map_find (&ctx->config, "irc_host");
- if (!irc_host)
+ struct error *error = NULL;
+ struct config_item_ *root =
+ config_item_parse (data.str, data.len, false, &error);
+ str_free (&data);
+ if (!root)
{
- error_set (e, "no hostname specified in configuration");
+ error_set (e, "configuration parse error: %s", error->message);
+ error_free (error);
return false;
}
- if (!irc_get_boolean_from_config (ctx,
- "reconnect", &ctx->reconnect, e)
- || !irc_get_boolean_from_config (ctx,
- "isolate_buffers", &ctx->isolate_buffers, e))
+ config_load (&ctx->config, root);
+
+ if (!autofill_user_info (ctx, e))
return false;
- const char *delay_str = str_map_find (&ctx->config, "reconnect_delay");
- hard_assert (delay_str != NULL); // We have a default value for this
- if (!xstrtoul (&ctx->reconnect_delay, delay_str, 10))
+ if (!get_config_string (ctx, "server.irc_host"))
{
- error_set (e, "invalid configuration value for `%s'",
- "reconnect_delay");
+ error_set (e, "no hostname specified in configuration");
return false;
}
+
+ ctx->reconnect =
+ get_config_boolean (ctx, "server.reconnect");
+ ctx->isolate_buffers =
+ get_config_boolean (ctx, "behaviour.isolate_buffers");
+ ctx->reconnect_delay =
+ get_config_integer (ctx, "server.reconnect_delay");
return true;
}
+static char *
+write_configuration_file (const struct str *data, struct error **e)
+{
+ struct str path;
+ str_init (&path);
+ get_xdg_home_dir (&path, "XDG_CONFIG_HOME", ".config");
+ str_append (&path, "/" PROGRAM_NAME);
+
+ if (!mkdir_with_parents (path.str, e))
+ goto error;
+
+ str_append (&path, "/" PROGRAM_NAME ".conf");
+ FILE *fp = fopen (path.str, "w");
+ if (!fp)
+ {
+ error_set (e, "could not open `%s' for writing: %s",
+ path.str, strerror (errno));
+ goto error;
+ }
+
+ errno = 0;
+ fwrite (data->str, data->len, 1, fp);
+ fclose (fp);
+
+ if (errno)
+ {
+ error_set (e, "writing to `%s' failed: %s", path.str, strerror (errno));
+ goto error;
+ }
+ return str_steal (&path);
+
+error:
+ str_free (&path);
+ return NULL;
+}
+
+static void
+serialize_configuration (struct app_context *ctx, struct str *output)
+{
+ str_append (output,
+ "# " PROGRAM_NAME " " PROGRAM_VERSION " configuration file\n"
+ "#\n"
+ "# Relative paths are searched for in ${XDG_CONFIG_HOME:-~/.config}\n"
+ "# /" PROGRAM_NAME " as well as in $XDG_CONFIG_DIRS/" PROGRAM_NAME "\n"
+ "#\n"
+ "# Everything is in UTF-8. Any custom comments will be overwritten.\n"
+ "\n");
+
+ config_item_write (ctx->config.root, true, output);
+}
+
+static void
+write_default_configuration ()
+{
+ // XXX: this is a hack before we remove this awkward functionality
+ // altogether; the user will want to do this from the user interface
+
+ struct app_context ctx = {};
+ config_init (&ctx.config);
+ register_config_modules (&ctx);
+ config_load (&ctx.config, config_item_object ());
+
+ struct str data;
+ str_init (&data);
+ serialize_configuration (&ctx, &data);
+
+ struct error *e = NULL;
+ char *filename = write_configuration_file (&data, &e);
+ str_free (&data);
+ config_free (&ctx.config);
+
+ if (!filename)
+ {
+ print_error ("%s", e->message);
+ error_free (e);
+ exit (EXIT_FAILURE);
+ }
+ print_status ("configuration written to `%s'", filename);
+ free (filename);
+}
+
+
// --- Main program ------------------------------------------------------------
static void
@@ -4519,8 +4675,7 @@ main (int argc, char *argv[])
{ 'd', "debug", NULL, 0, "run in debug mode" },
{ 'h', "help", NULL, 0, "display this help and exit" },
{ 'V', "version", NULL, 0, "output version information and exit" },
- { 'w', "write-default-cfg", "FILENAME",
- OPT_OPTIONAL_ARG | OPT_LONG_ONLY,
+ { 'w', "write-default-cfg", NULL, OPT_LONG_ONLY,
"write a default configuration file and exit" },
{ 0, NULL, NULL, 0, NULL }
};
@@ -4542,7 +4697,7 @@ main (int argc, char *argv[])
printf (PROGRAM_NAME " " PROGRAM_VERSION "\n");
exit (EXIT_SUCCESS);
case 'w':
- call_write_default_config (optarg, g_config_table);
+ write_default_configuration ();
exit (EXIT_SUCCESS);
default:
print_error ("wrong options");
@@ -4571,9 +4726,10 @@ main (int argc, char *argv[])
stifle_history (HISTORY_LIMIT);
setup_signal_handlers ();
+ register_config_modules (&ctx);
struct error *e = NULL;
- if (!load_config (&ctx, &e))
+ if (!load_configuration (&ctx, &e))
{
print_error ("%s", e->message);
error_free (e);