degesch: use the new configuration
This is a simple, almost 1:1 conversion. Needs further unfucking.
This commit is contained in:
parent
3b8e8cc97f
commit
c23898166c
642
degesch.c
642
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);
|
||||
|
@ -1893,34 +2073,17 @@ irc_send (struct server *s, const char *format, ...)
|
|||
return result;
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -4288,115 +4449,12 @@ 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)
|
||||
{
|
||||
FILE *fp = fopen (filename, "rb");
|
||||
if (!fp)
|
||||
{
|
||||
error_set (e, "could not open `%s' for reading: %s",
|
||||
filename, strerror (errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
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_configuration (struct app_context *ctx, struct error **e)
|
||||
{
|
||||
char *filename = resolve_config_filename (PROGRAM_NAME ".conf");
|
||||
if (!filename)
|
||||
{
|
||||
error_set (e, "cannot find configuration");
|
||||
return false;
|
||||
}
|
||||
|
||||
struct str data;
|
||||
str_init (&data);
|
||||
bool success = read_file (filename, &data, e);
|
||||
free (filename);
|
||||
if (!success)
|
||||
{
|
||||
str_free (&data);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct error *error = NULL;
|
||||
struct str_map_iter iter;
|
||||
str_map_iter_init (&iter, input);
|
||||
while (str_map_iter_next (&iter))
|
||||
struct config_item_ *root =
|
||||
config_item_parse (data.str, data.len, false, &error);
|
||||
str_free (&data);
|
||||
if (!root)
|
||||
{
|
||||
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_set (e, "configuration parse error: %s", error->message);
|
||||
error_free (error);
|
||||
return false;
|
||||
}
|
||||
|
||||
str_map_set (output, iter.link->key, str_steal (&value));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
config_load (&ctx->config, root);
|
||||
|
||||
static bool
|
||||
load_config (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;
|
||||
|
||||
bool success = read_config_file (&map, e) &&
|
||||
unescape_config (&map, &ctx->config, e) &&
|
||||
autofill_user_info (ctx, e);
|
||||
str_map_free (&map);
|
||||
if (!success)
|
||||
if (!autofill_user_info (ctx, e))
|
||||
return false;
|
||||
|
||||
const char *irc_host = str_map_find (&ctx->config, "irc_host");
|
||||
if (!irc_host)
|
||||
if (!get_config_string (ctx, "server.irc_host"))
|
||||
{
|
||||
error_set (e, "no hostname specified in configuration");
|
||||
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))
|
||||
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))
|
||||
{
|
||||
error_set (e, "invalid configuration value for `%s'",
|
||||
"reconnect_delay");
|
||||
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);
|
||||
|
|
Loading…
Reference in New Issue