aboutsummaryrefslogtreecommitdiff
path: root/json-rpc-shell.c
diff options
context:
space:
mode:
authorPřemysl Janouch <p.janouch@gmail.com>2015-12-13 22:31:06 +0100
committerPřemysl Janouch <p.janouch@gmail.com>2015-12-13 22:54:14 +0100
commit3e2728443bd35160c844faccffd0d7e4e9ac429b (patch)
tree2cb1d47cf4042d4de3b95ba8fe775b2965ed76c6 /json-rpc-shell.c
parent50c8ef12acd11e001050588745685dccf76e8dbd (diff)
downloadjson-rpc-shell-3e2728443bd35160c844faccffd0d7e4e9ac429b.tar.gz
json-rpc-shell-3e2728443bd35160c844faccffd0d7e4e9ac429b.tar.xz
json-rpc-shell-3e2728443bd35160c844faccffd0d7e4e9ac429b.zip
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.
Diffstat (limited to 'json-rpc-shell.c')
-rw-r--r--json-rpc-shell.c437
1 files changed, 231 insertions, 206 deletions
diff --git a/json-rpc-shell.c b/json-rpc-shell.c
index 9ae8451..a1b3ae4 100644
--- a/json-rpc-shell.c
+++ b/json-rpc-shell.c
@@ -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;
-}
-
-// --- 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;
+ // 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));
}
-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)
+static ssize_t
+attr_by_name (const char *name)
{
- int c;
- switch ((c = *(*cursor)++))
+ static const char *table[ATTR_COUNT] =
{
- 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;
-}
+#define XX(x, y, z) [ATTR_ ## x] = y,
+ ATTR_TABLE (XX)
+#undef XX
+ };
-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))
- {
- print_error ("error loading configuration: %s", e->message);
- error_free (e);
- exit (EXIT_FAILURE);
- }
-
- struct str_map_iter iter;
- str_map_iter_init (&iter, &map);
- while (str_map_iter_next (&iter))
+ struct app_context *ctx = item->user_data;
+ ssize_t id = attr_by_name (item->schema->name);
+ if (id != -1)
{
- 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));
+ free (ctx->attrs[id]);
+ ctx->attrs[id] = xstrdup (item->type == CONFIG_ITEM_NULL
+ ? ctx->attrs_defaults[id]
+ : item->value.string.str);
}
-
- 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;