Bump liberty, use newer configuration format

So that we don't need to maintain our own string parser for attribute strings.

More or less just plugging in what the old code has evolved into in degesch.
This commit is contained in:
Přemysl Eric Janouch 2015-12-13 22:31:06 +01:00
parent 50c8ef12ac
commit 3e2728443b
2 changed files with 233 additions and 208 deletions

View File

@ -21,18 +21,27 @@
/// Some arbitrary limit for the history file
#define HISTORY_LIMIT 10000
// String constants for all attributes we use for output
#define ATTR_PROMPT "attr_prompt"
#define ATTR_RESET "attr_reset"
#define ATTR_WARNING "attr_warning"
#define ATTR_ERROR "attr_error"
#define ATTR_INCOMING "attr_incoming"
#define ATTR_OUTGOING "attr_outgoing"
// A table of all attributes we use for output
#define ATTR_TABLE(XX) \
XX( PROMPT, "prompt", "Terminal attrs for the prompt" ) \
XX( RESET, "reset", "String to reset terminal attributes" ) \
XX( WARNING, "warning", "Terminal attrs for warnings" ) \
XX( ERROR, "error", "Terminal attrs for errors" ) \
XX( INCOMING, "incoming", "Terminal attrs for incoming traffic" ) \
XX( OUTGOING, "outgoing", "Terminal attrs for outgoing traffic" )
enum
{
#define XX(x, y, z) ATTR_ ## x,
ATTR_TABLE (XX)
#undef XX
ATTR_COUNT
};
// User data for logger functions to enable formatted logging
#define print_fatal_data ATTR_ERROR
#define print_error_data ATTR_ERROR
#define print_warning_data ATTR_WARNING
#define print_fatal_data ((void *) ATTR_ERROR)
#define print_error_data ((void *) ATTR_ERROR)
#define print_warning_data ((void *) ATTR_WARNING)
#define LIBERTY_WANT_SSL
#define LIBERTY_WANT_PROTO_HTTP
@ -63,23 +72,6 @@
return false; \
BLOCK_END
// --- Configuration (application-specific) ------------------------------------
static struct simple_config_item g_config_table[] =
{
{ ATTR_PROMPT, NULL, "Terminal attributes for the prompt" },
{ ATTR_RESET, NULL, "String to reset terminal attributes" },
{ ATTR_WARNING, NULL, "Terminal attributes for warnings" },
{ ATTR_ERROR, NULL, "Terminal attributes for errors" },
{ ATTR_INCOMING, NULL, "Terminal attributes for incoming traffic" },
{ ATTR_OUTGOING, NULL, "Terminal attributes for outgoing traffic" },
{ "ca_file", NULL, "OpenSSL trusted CA certificates file" },
{ "ca_path", NULL, "OpenSSL trusted CA certificates path" },
{ NULL, NULL, NULL }
};
// --- Terminal ----------------------------------------------------------------
static struct
@ -233,6 +225,8 @@ ws_context_init (struct ws_context *self)
str_vector_init (&self->extra_headers);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
struct curl_context
{
CURL *curl; ///< cURL handle
@ -262,7 +256,10 @@ static struct app_context
struct ws_context ws; ///< WebSockets backend data
struct curl_context curl; ///< cURL backend data
struct str_map config; ///< Program configuration
char *attrs_defaults[ATTR_COUNT]; ///< Default terminal attributes
char *attrs[ATTR_COUNT]; ///< Terminal attributes
struct config config; ///< Program configuration
enum color_mode color_mode; ///< Colour output mode
bool pretty_print; ///< Whether to pretty print
bool verbose; ///< Print requests
@ -279,6 +276,146 @@ static struct app_context
}
g_ctx;
// --- Configuration -----------------------------------------------------------
static void on_config_attribute_change (struct config_item *item);
static struct config_schema g_config_connection[] =
{
{ .name = "tls_ca_file",
.comment = "OpenSSL CA bundle file",
.type = CONFIG_ITEM_STRING },
{ .name = "tls_ca_path",
.comment = "OpenSSL CA bundle path",
.type = CONFIG_ITEM_STRING },
{}
};
static struct config_schema g_config_attributes[] =
{
#define XX(x, y, z) { .name = y, .comment = z, .type = CONFIG_ITEM_STRING, \
.on_change = on_config_attribute_change },
ATTR_TABLE (XX)
#undef XX
{}
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void
load_config_connection (struct config_item *subtree, void *user_data)
{
config_schema_apply_to_object (g_config_connection, subtree, user_data);
}
static void
load_config_attributes (struct config_item *subtree, void *user_data)
{
config_schema_apply_to_object (g_config_attributes, subtree, user_data);
}
static void
register_config_modules (struct app_context *ctx)
{
struct config *config = &ctx->config;
config_register_module (config, "connection", load_config_connection, ctx);
config_register_module (config, "attributes", load_config_attributes, ctx);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static const char *
get_config_string (struct config_item *root, const char *key)
{
struct config_item *item = config_item_get (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 void
save_configuration (struct config_item *root, const char *path_hint)
{
struct str data;
str_init (&data);
str_append (&data,
"# " 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"
"# All text must be in UTF-8.\n"
"\n");
config_item_write (root, true, &data);
struct error *e = NULL;
char *filename = write_configuration_file (path_hint, &data, &e);
str_free (&data);
if (!filename)
{
print_error ("%s: %s", "saving configuration failed", e->message);
error_free (e);
}
else
print_status ("configuration written to `%s'", filename);
free (filename);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// TODO: consider moving to liberty, degesch has exactly the same function
static struct config_item *
load_configuration_file (const char *filename, struct error **e)
{
struct config_item *root = NULL;
struct str data;
str_init (&data);
if (!read_file (filename, &data, e))
goto end;
struct error *error = NULL;
if (!(root = config_item_parse (data.str, data.len, false, &error)))
{
error_set (e, "parse error: %s", error->message);
error_free (error);
}
end:
str_free (&data);
return root;
}
static void
load_configuration (struct app_context *ctx)
{
char *filename = resolve_filename
(PROGRAM_NAME ".conf", resolve_relative_config_filename);
if (!filename)
return;
struct error *e = NULL;
struct config_item *root = load_configuration_file (filename, &e);
free (filename);
if (e)
{
print_error ("error loading configuration: %s", e->message);
error_free (e);
exit (EXIT_FAILURE);
}
if (root)
{
config_load (&ctx->config, root);
config_schema_call_changed (ctx->config.root);
}
}
// --- Attributed output -------------------------------------------------------
typedef int (*terminal_printer_fn) (int);
@ -301,30 +438,24 @@ get_attribute_printer (FILE *stream)
static void
vprint_attributed (struct app_context *ctx,
FILE *stream, const char *attribute, const char *fmt, va_list ap)
FILE *stream, intptr_t attribute, const char *fmt, va_list ap)
{
terminal_printer_fn printer = get_attribute_printer (stream);
if (!attribute)
printer = NULL;
if (printer)
{
const char *value = str_map_find (&ctx->config, attribute);
tputs (value, 1, printer);
}
tputs (ctx->attrs[attribute], 1, printer);
vfprintf (stream, fmt, ap);
if (printer)
{
const char *value = str_map_find (&ctx->config, ATTR_RESET);
tputs (value, 1, printer);
}
tputs (ctx->attrs[ATTR_RESET], 1, printer);
}
static void
print_attributed (struct app_context *ctx,
FILE *stream, const char *attribute, const char *fmt, ...)
FILE *stream, intptr_t attribute, const char *fmt, ...)
{
va_list ap;
va_start (ap, fmt);
@ -353,8 +484,8 @@ log_message_attributed (void *user_data, const char *quote, const char *fmt,
rl_redisplay ();
}
print_attributed (&g_ctx, stream, user_data, "%s", quote);
vprint_attributed (&g_ctx, stream, user_data, fmt, ap);
print_attributed (&g_ctx, stream, (intptr_t) user_data, "%s", quote);
vprint_attributed (&g_ctx, stream, (intptr_t) user_data, fmt, ap);
fputs ("\n", stream);
if (g_ctx.readline_prompt_shown)
@ -370,36 +501,31 @@ log_message_attributed (void *user_data, const char *quote, const char *fmt,
static void
init_colors (struct app_context *ctx)
{
char **defaults = ctx->attrs_defaults;
#define INIT_ATTR(id, ti) defaults[ATTR_ ## id] = xstrdup ((ti))
// Use escape sequences from terminfo if possible, and SGR as a fallback
if (init_terminal ())
{
const char *attrs[][2] =
{
{ ATTR_PROMPT, enter_bold_mode },
{ ATTR_RESET, exit_attribute_mode },
{ ATTR_WARNING, g_terminal.color_set[3] },
{ ATTR_ERROR, g_terminal.color_set[1] },
{ ATTR_INCOMING, "" },
{ ATTR_OUTGOING, "" },
};
for (size_t i = 0; i < N_ELEMENTS (attrs); i++)
str_map_set (&ctx->config, attrs[i][0], xstrdup (attrs[i][1]));
INIT_ATTR (PROMPT, enter_bold_mode);
INIT_ATTR (RESET, exit_attribute_mode);
INIT_ATTR (WARNING, g_terminal.color_set[COLOR_YELLOW]);
INIT_ATTR (ERROR, g_terminal.color_set[COLOR_RED]);
INIT_ATTR (INCOMING, "");
INIT_ATTR (OUTGOING, "");
}
else
{
const char *attrs[][2] =
{
{ ATTR_PROMPT, "\x1b[1m" },
{ ATTR_RESET, "\x1b[0m" },
{ ATTR_WARNING, "\x1b[33m" },
{ ATTR_ERROR, "\x1b[31m" },
{ ATTR_INCOMING, "" },
{ ATTR_OUTGOING, "" },
};
for (size_t i = 0; i < N_ELEMENTS (attrs); i++)
str_map_set (&ctx->config, attrs[i][0], xstrdup (attrs[i][1]));
INIT_ATTR (PROMPT, "\x1b[1m");
INIT_ATTR (RESET, "\x1b[0m");
INIT_ATTR (WARNING, "\x1b[33m");
INIT_ATTR (ERROR, "\x1b[31m");
INIT_ATTR (INCOMING, "");
INIT_ATTR (OUTGOING, "");
}
#undef INIT_ATTR
switch (ctx->color_mode)
{
case COLOR_ALWAYS:
@ -416,150 +542,42 @@ init_colors (struct app_context *ctx)
}
g_log_message_real = log_message_attributed;
// Apply the default values so that we start with any formatting at all
config_schema_call_changed
(config_item_get (ctx->config.root, "attributes", NULL));
}
// --- Configuration loading ---------------------------------------------------
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static bool
read_hexa_escape (const char **cursor, struct str *output)
static ssize_t
attr_by_name (const char *name)
{
int i;
char c, code = 0;
for (i = 0; i < 2; i++)
static const char *table[ATTR_COUNT] =
{
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;
#define XX(x, y, z) [ATTR_ ## x] = y,
ATTR_TABLE (XX)
#undef XX
};
(*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;
for (size_t i = 0; i < N_ELEMENTS (table); i++)
if (!strcmp (name, table[i]))
return i;
return -1;
}
static void
load_config (struct app_context *ctx)
on_config_attribute_change (struct config_item *item)
{
// 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;
struct error *e = NULL;
if (!simple_config_update_from_file (&map, &e))
struct app_context *ctx = item->user_data;
ssize_t id = attr_by_name (item->schema->name);
if (id != -1)
{
print_error ("error loading configuration: %s", e->message);
error_free (e);
exit (EXIT_FAILURE);
free (ctx->attrs[id]);
ctx->attrs[id] = xstrdup (item->type == CONFIG_ITEM_NULL
? ctx->attrs_defaults[id]
: item->value.string.str);
}
struct str_map_iter iter;
str_map_iter_init (&iter, &map);
while (str_map_iter_next (&iter))
{
struct error *e = NULL;
struct str value;
str_init (&value);
if (!unescape_string (iter.link->data, &value, &e))
{
print_error ("error reading configuration: %s: %s",
iter.link->key, e->message);
error_free (e);
exit (EXIT_FAILURE);
}
str_map_set (&ctx->config, iter.link->key, str_steal (&value));
}
str_map_free (&map);
}
// --- WebSockets backend ------------------------------------------------------
@ -995,8 +1013,11 @@ backend_ws_set_up_ssl_ctx (struct app_context *ctx)
return true;
}
const char *ca_file = str_map_find (&ctx->config, "ca_file");
const char *ca_path = str_map_find (&ctx->config, "ca_path");
// TODO: try to resolve filenames relative to configuration directories
const char *ca_file = get_config_string
(ctx->config.root, "connection.tls_ca_file");
const char *ca_path = get_config_string
(ctx->config.root, "connection.tls_ca_path");
if (ca_file || ca_path)
{
if (SSL_CTX_load_verify_locations (self->ssl_ctx, ca_file, ca_path))
@ -1567,8 +1588,11 @@ backend_curl_init (struct app_context *ctx,
if (!ctx->trust_all)
{
const char *ca_file = str_map_find (&ctx->config, "ca_file");
const char *ca_path = str_map_find (&ctx->config, "ca_path");
// TODO: try to resolve filenames relative to configuration directories
const char *ca_file = get_config_string
(ctx->config.root, "connection.tls_ca_file");
const char *ca_path = get_config_string
(ctx->config.root, "connection.tls_ca_path");
if ((ca_file && curl_easy_setopt (curl, CURLOPT_CAINFO, ca_file))
|| (ca_path && curl_easy_setopt (curl, CURLOPT_CAPATH, ca_path)))
exit_fatal ("cURL setup failed");
@ -2040,7 +2064,7 @@ parse_program_arguments (struct app_context *ctx, int argc, char **argv,
}
break;
case 'w':
call_simple_config_write_default (optarg, g_config_table);
save_configuration (ctx->config.root, optarg);
exit (EXIT_SUCCESS);
default:
@ -2065,15 +2089,16 @@ parse_program_arguments (struct app_context *ctx, int argc, char **argv,
int
main (int argc, char *argv[])
{
str_map_init (&g_ctx.config);
g_ctx.config.free = free;
config_init (&g_ctx.config);
register_config_modules (&g_ctx);
config_load (&g_ctx.config, config_item_object ());
char *origin = NULL;
char *endpoint = NULL;
parse_program_arguments (&g_ctx, argc, argv, &origin, &endpoint);
init_colors (&g_ctx);
load_config (&g_ctx);
load_configuration (&g_ctx);
struct http_parser_url url;
if (http_parser_parse_url (endpoint, strlen (endpoint), false, &url))
@ -2145,11 +2170,11 @@ main (int argc, char *argv[])
else
{
// XXX: to be completely correct, we should use tputs, but we cannot
const char *prompt_attrs = str_map_find (&g_ctx.config, ATTR_PROMPT);
const char *reset_attrs = str_map_find (&g_ctx.config, ATTR_RESET);
g_ctx.readline_prompt = xstrdup_printf ("%c%s%cjson-rpc> %c%s%c",
RL_PROMPT_START_IGNORE, prompt_attrs, RL_PROMPT_END_IGNORE,
RL_PROMPT_START_IGNORE, reset_attrs, RL_PROMPT_END_IGNORE);
RL_PROMPT_START_IGNORE, g_ctx.attrs[ATTR_PROMPT],
RL_PROMPT_END_IGNORE,
RL_PROMPT_START_IGNORE, g_ctx.attrs[ATTR_RESET],
RL_PROMPT_END_IGNORE);
}
// So that if the remote end closes the connection, attempts to write to
@ -2204,7 +2229,7 @@ main (int argc, char *argv[])
g_ctx.backend->destroy (&g_ctx);
free (origin);
free (g_ctx.readline_prompt);
str_map_free (&g_ctx.config);
config_free (&g_ctx.config);
free_terminal ();
ev_loop_destroy (loop);
return EXIT_SUCCESS;

@ -1 +1 @@
Subproject commit 91fca5cb054fc95fe2e7ac090cac2c3a302565ca
Subproject commit 8b2e41ed8ffac0494763495896c6a80a9e9db543