summaryrefslogtreecommitdiff
path: root/common.c
diff options
context:
space:
mode:
Diffstat (limited to 'common.c')
-rw-r--r--common.c1118
1 files changed, 0 insertions, 1118 deletions
diff --git a/common.c b/common.c
index d37192e..8470171 100644
--- a/common.c
+++ b/common.c
@@ -30,8 +30,6 @@
#endif // WANT_SYSLOG_LOGGING
#include "liberty/liberty.c"
-#include <setjmp.h>
-#include <inttypes.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
@@ -47,18 +45,6 @@
// --- To be moved to liberty --------------------------------------------------
-static void
-split_str (const char *s, const char *delimiters, struct str_vector *out)
-{
- const char *begin = s, *end;
- while ((end = strpbrk (begin, delimiters)))
- {
- str_vector_add_owned (out, xstrndup (begin, end - begin));
- begin = ++end;
- }
- str_vector_add (out, begin);
-}
-
static ssize_t
str_vector_find (const struct str_vector *v, const char *s)
{
@@ -1001,1107 +987,3 @@ ctcp_destroy (struct ctcp_chunk *list)
LIST_FOR_EACH (struct ctcp_chunk, iter, list)
ctcp_chunk_destroy (iter);
}
-
-// --- Advanced configuration --------------------------------------------------
-
-// This is a new configuration format, superseding the one currently present
-// in liberty. It's just a lot more complicated and allows key-value maps.
-// We need it in degesch to provide non-sucking user experience.
-
-enum config_item_type
-{
- CONFIG_ITEM_NULL, ///< No value
- CONFIG_ITEM_OBJECT, ///< Key-value map
- CONFIG_ITEM_BOOLEAN, ///< Truth value
- CONFIG_ITEM_INTEGER, ///< Integer
- CONFIG_ITEM_STRING, ///< Arbitrary string of characters
- CONFIG_ITEM_STRING_ARRAY ///< Comma-separated list of strings
-};
-
-struct config_item
-{
- enum config_item_type type; ///< Type of the item
- union
- {
- struct str_map object; ///< Key-value data
- bool boolean; ///< Boolean data
- int64_t integer; ///< Integer data
- struct str string; ///< String data
- }
- value; ///< The value of this item
-
- struct config_schema *schema; ///< Schema describing this value
- void *user_data; ///< User value attached by schema owner
-};
-
-struct config_schema
-{
- const char *name; ///< Name of the item
- const char *comment; ///< User-readable description
-
- enum config_item_type type; ///< Required type
- const char *default_; ///< Default as a configuration snippet
-
- /// Check if the new value can be accepted.
- /// In addition to this, "type" and having a default is considered.
- bool (*validate) (const struct config_item *, struct error **e);
-
- /// The value has changed
- void (*on_change) (struct config_item *);
-};
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static const char *
-config_item_type_name (enum config_item_type type)
-{
- switch (type)
- {
- case CONFIG_ITEM_NULL: return "null";
- case CONFIG_ITEM_BOOLEAN: return "boolean";
- case CONFIG_ITEM_INTEGER: return "integer";
- case CONFIG_ITEM_STRING: return "string";
- case CONFIG_ITEM_STRING_ARRAY: return "string array";
-
- default:
- hard_assert (!"invalid config item type value");
- return NULL;
- }
-}
-
-static bool
-config_item_type_is_string (enum config_item_type type)
-{
- return type == CONFIG_ITEM_STRING
- || type == CONFIG_ITEM_STRING_ARRAY;
-}
-
-static void
-config_item_free (struct config_item *self)
-{
- switch (self->type)
- {
- case CONFIG_ITEM_STRING:
- case CONFIG_ITEM_STRING_ARRAY:
- str_free (&self->value.string);
- break;
- case CONFIG_ITEM_OBJECT:
- str_map_free (&self->value.object);
- default:
- break;
- }
-}
-
-static void
-config_item_destroy (struct config_item *self)
-{
- config_item_free (self);
- free (self);
-}
-
-/// Doesn't do any validations or handle schemas, just moves source data
-/// to the target item and destroys the source item
-static void
-config_item_move (struct config_item *self, struct config_item *source)
-{
- // Not quite sure how to handle that
- hard_assert (!source->schema);
-
- config_item_free (self);
- self->type = source->type;
- memcpy (&self->value, &source->value, sizeof source->value);
- free (source);
-}
-
-static struct config_item *
-config_item_new (enum config_item_type type)
-{
- struct config_item *self = xcalloc (1, sizeof *self);
- self->type = type;
- return self;
-}
-
-static struct config_item *
-config_item_null (void)
-{
- return config_item_new (CONFIG_ITEM_NULL);
-}
-
-static struct config_item *
-config_item_boolean (bool b)
-{
- struct config_item *self = config_item_new (CONFIG_ITEM_BOOLEAN);
- self->value.boolean = b;
- return self;
-}
-
-static struct config_item *
-config_item_integer (int64_t i)
-{
- struct config_item *self = config_item_new (CONFIG_ITEM_INTEGER);
- self->value.integer = i;
- return self;
-}
-
-static struct config_item *
-config_item_string (const struct str *s)
-{
- struct config_item *self = config_item_new (CONFIG_ITEM_STRING);
- str_init (&self->value.string);
- hard_assert (utf8_validate
- (self->value.string.str, self->value.string.len));
- if (s) str_append_str (&self->value.string, s);
- return self;
-}
-
-static struct config_item *
-config_item_string_from_cstr (const char *s)
-{
- struct str tmp;
- str_init (&tmp);
- str_append (&tmp, s);
- struct config_item *self = config_item_string (&tmp);
- str_free (&tmp);
- return self;
-}
-
-static struct config_item *
-config_item_string_array (const struct str *s)
-{
- struct config_item *self = config_item_string (s);
- self->type = CONFIG_ITEM_STRING_ARRAY;
- return self;
-}
-
-static struct config_item *
-config_item_object (void)
-{
- struct config_item *self = config_item_new (CONFIG_ITEM_OBJECT);
- str_map_init (&self->value.object);
- self->value.object.free = (void (*)(void *)) config_item_destroy;
- return self;
-}
-
-static bool
-config_schema_accepts_type
- (struct config_schema *self, enum config_item_type type)
-{
- if (self->type == type)
- return true;
- // This is a bit messy but it has its purpose
- if (config_item_type_is_string (self->type)
- && config_item_type_is_string (type))
- return true;
- return !self->default_ && type == CONFIG_ITEM_NULL;
-}
-
-static bool
-config_item_validate_by_schema (struct config_item *self,
- struct config_schema *schema, struct error **e)
-{
- struct error *error = NULL;
- if (!config_schema_accepts_type (schema, self->type))
- error_set (e, "invalid type of value, expected: %s%s",
- config_item_type_name (schema->type),
- !schema->default_ ? " (or null)" : "");
- else if (schema->validate && !schema->validate (self, &error))
- {
- error_set (e, "%s: %s", "invalid value", error->message);
- error_free (error);
- }
- else
- return true;
- return false;
-}
-
-static bool
-config_item_set_from (struct config_item *self, struct config_item *source,
- struct error **e)
-{
- struct config_schema *schema = self->schema;
- if (!schema)
- {
- // Easy, we don't know what this item is
- config_item_move (self, source);
- return true;
- }
-
- if (!config_item_validate_by_schema (source, schema, e))
- return false;
-
- // Make sure the string subtype fits the schema
- if (config_item_type_is_string (source->type)
- && config_item_type_is_string (schema->type))
- source->type = schema->type;
-
- config_item_move (self, source);
-
- // Notify owner about the change so that they can apply it
- if (schema->on_change)
- schema->on_change (self);
- return true;
-}
-
-static struct config_item *
-config_item_get (struct config_item *self, const char *path, struct error **e)
-{
- hard_assert (self->type == CONFIG_ITEM_OBJECT);
-
- struct str_vector v;
- str_vector_init (&v);
- split_str (path, ".", &v);
-
- struct config_item *result = NULL;
- size_t i = 0;
- while (true)
- {
- const char *key = v.vector[i];
- if (!*key)
- error_set (e, "empty path element");
- else if (!(self = str_map_find (&self->value.object, key)))
- error_set (e, "`%s' not found in object", key);
- else if (++i == v.len)
- result = self;
- else if (self->type != CONFIG_ITEM_OBJECT)
- error_set (e, "`%s' is not an object", key);
- else
- continue;
- break;
- }
- str_vector_free (&v);
- return result;
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-struct config_writer
-{
- struct str *output;
- unsigned indent;
-};
-
-static void config_item_write_object_innards
- (struct config_writer *self, struct config_item *object);
-
-static void
-config_item_write_string (struct str *output, const struct str *s)
-{
- str_append_c (output, '"');
- for (size_t i = 0; i < s->len; i++)
- {
- unsigned char c = s->str[i];
- if (c == '\n') str_append (output, "\\n");
- else if (c == '\r') str_append (output, "\\r");
- else if (c == '\t') str_append (output, "\\t");
- else if (c == '\\') str_append (output, "\\\\");
- else if (c == '"') str_append (output, "\\\"");
- else if (c < 32) str_append_printf (output, "\\x%02x", c);
- else str_append_c (output, c);
- }
- str_append_c (output, '"');
-}
-
-static void
-config_item_write_object
- (struct config_writer *self, struct config_item *value)
-{
- char indent[self->indent + 1];
- memset (indent, '\t', self->indent);
- indent[self->indent] = 0;
-
- str_append_c (self->output, '{');
- if (value->value.object.len)
- {
- self->indent++;
- str_append_c (self->output, '\n');
- config_item_write_object_innards (self, value);
- self->indent--;
- str_append (self->output, indent);
- }
- str_append_c (self->output, '}');
-}
-
-static void
-config_item_write_value (struct config_writer *self, struct config_item *value)
-{
- switch (value->type)
- {
- case CONFIG_ITEM_NULL:
- str_append (self->output, "null");
- break;
- case CONFIG_ITEM_BOOLEAN:
- str_append (self->output, value->value.boolean ? "on" : "off");
- break;
- case CONFIG_ITEM_INTEGER:
- str_append_printf (self->output, "%" PRIi64, value->value.integer);
- break;
- case CONFIG_ITEM_STRING:
- case CONFIG_ITEM_STRING_ARRAY:
- config_item_write_string (self->output, &value->value.string);
- break;
- case CONFIG_ITEM_OBJECT:
- config_item_write_object (self, value);
- break;
- default:
- hard_assert (!"invalid item type");
- }
-}
-
-static void
-config_item_write_kv_pair (struct config_writer *self,
- const char *key, struct config_item *value)
-{
- char indent[self->indent + 1];
- memset (indent, '\t', self->indent);
- indent[self->indent] = 0;
-
- if (value->schema && value->schema->comment)
- str_append_printf (self->output,
- "%s# %s\n", indent, value->schema->comment);
-
- str_append_printf (self->output, "%s%s = ", indent, key);
- config_item_write_value (self, value);
- str_append_c (self->output, '\n');
-}
-
-static void
-config_item_write_object_innards
- (struct config_writer *self, struct config_item *object)
-{
- hard_assert (object->type == CONFIG_ITEM_OBJECT);
-
- struct str_map_iter iter;
- str_map_iter_init (&iter, &object->value.object);
-
- struct config_item *value;
- while ((value = str_map_iter_next (&iter)))
- config_item_write_kv_pair (self, iter.link->key, value);
-}
-
-static void
-config_item_write (struct config_item *value,
- bool object_innards, struct str *output)
-{
- struct config_writer writer = { .output = output, .indent = 0 };
- if (object_innards)
- config_item_write_object_innards (&writer, value);
- else
- config_item_write_value (&writer, value);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-enum config_token
-{
- CONFIG_T_ABORT, ///< EOF or error
-
- CONFIG_T_WORD, ///< [a-zA-Z0-9_]+
- CONFIG_T_EQUALS, ///< Equal sign
- CONFIG_T_LBRACE, ///< Left curly bracket
- CONFIG_T_RBRACE, ///< Right curly bracket
- CONFIG_T_NEWLINE, ///< New line
-
- CONFIG_T_NULL, ///< CONFIG_ITEM_NULL
- CONFIG_T_BOOLEAN, ///< CONFIG_ITEM_BOOLEAN
- CONFIG_T_INTEGER, ///< CONFIG_ITEM_INTEGER
- CONFIG_T_STRING ///< CONFIG_ITEM_STRING{,_LIST}
-};
-
-static const char *
-config_token_name (enum config_token token)
-{
- switch (token)
- {
- case CONFIG_T_ABORT: return "end of input";
-
- case CONFIG_T_WORD: return "word";
- case CONFIG_T_EQUALS: return "equal sign";
- case CONFIG_T_LBRACE: return "left brace";
- case CONFIG_T_RBRACE: return "right brace";
- case CONFIG_T_NEWLINE: return "newline";
-
- case CONFIG_T_NULL: return "null value";
- case CONFIG_T_BOOLEAN: return "boolean";
- case CONFIG_T_INTEGER: return "integer";
- case CONFIG_T_STRING: return "string";
-
- default:
- hard_assert (!"invalid token value");
- return NULL;
- }
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-struct config_tokenizer
-{
- const char *p; ///< Current position in input
- size_t len; ///< How many bytes of input are left
-
- bool report_line; ///< Whether to count lines at all
- unsigned line; ///< Current line
- unsigned column; ///< Current column
-
- int64_t integer; ///< Parsed boolean or integer value
- struct str string; ///< Parsed string value
-};
-
-/// Input has to be null-terminated anyway
-static void
-config_tokenizer_init (struct config_tokenizer *self, const char *p, size_t len)
-{
- memset (self, 0, sizeof *self);
- self->p = p;
- self->len = len;
- self->report_line = true;
- str_init (&self->string);
-}
-
-static void
-config_tokenizer_free (struct config_tokenizer *self)
-{
- str_free (&self->string);
-}
-
-static bool
-config_tokenizer_is_word_char (int c)
-{
- return isalnum_ascii (c) || c == '_';
-}
-
-static int
-config_tokenizer_advance (struct config_tokenizer *self)
-{
- int c = *self->p++;
- if (c == '\n' && self->report_line)
- {
- self->column = 0;
- self->line++;
- }
- else
- self->column++;
-
- self->len--;
- return c;
-}
-
-static void config_tokenizer_error (struct config_tokenizer *self,
- struct error **e, const char *format, ...) ATTRIBUTE_PRINTF (3, 4);
-
-static void
-config_tokenizer_error (struct config_tokenizer *self,
- struct error **e, const char *format, ...)
-{
- struct str description;
- str_init (&description);
-
- va_list ap;
- va_start (ap, format);
- str_append_vprintf (&description, format, ap);
- va_end (ap);
-
- if (self->report_line)
- error_set (e, "near line %u, column %u: %s",
- self->line + 1, self->column + 1, description.str);
- else if (self->len)
- error_set (e, "near character %u: %s",
- self->column + 1, description.str);
- else
- error_set (e, "near end: %s", description.str);
-
- str_free (&description);
-}
-
-static bool
-config_tokenizer_hexa_escape (struct config_tokenizer *self, struct str *output)
-{
- int i;
- unsigned char code = 0;
-
- for (i = 0; self->len && i < 2; i++)
- {
- unsigned char c = tolower_ascii (*self->p);
- if (c >= '0' && c <= '9')
- code = (code << 4) | (c - '0');
- else if (c >= 'a' && c <= 'f')
- code = (code << 4) | (c - 'a' + 10);
- else
- break;
-
- config_tokenizer_advance (self);
- }
-
- if (!i)
- return false;
-
- str_append_c (output, code);
- return true;
-}
-
-static bool
-config_tokenizer_octal_escape
- (struct config_tokenizer *self, struct str *output)
-{
- int i;
- unsigned char code = 0;
-
- for (i = 0; self->len && i < 3; i++)
- {
- unsigned char c = *self->p;
- if (c >= '0' && c <= '7')
- code = (code << 3) | (c - '0');
- else
- break;
-
- config_tokenizer_advance (self);
- }
-
- if (!i)
- return false;
-
- str_append_c (output, code);
- return true;
-}
-
-static bool
-config_tokenizer_escape_sequence
- (struct config_tokenizer *self, struct str *output, struct error **e)
-{
- if (!self->len)
- {
- config_tokenizer_error (self, e, "premature end of escape sequence");
- return false;
- }
-
- unsigned char c;
- switch ((c = *self->p))
- {
- case '"': break;
- case '\\': break;
- case 'a': c = '\a'; break;
- case 'b': c = '\b'; break;
- case 'f': c = '\f'; break;
- case 'n': c = '\n'; break;
- case 'r': c = '\r'; break;
- case 't': c = '\t'; break;
- case 'v': c = '\v'; break;
-
- case 'x':
- case 'X':
- config_tokenizer_advance (self);
- if (config_tokenizer_hexa_escape (self, output))
- return true;
-
- config_tokenizer_error (self, e, "invalid hexadecimal escape");
- return false;
-
- default:
- if (config_tokenizer_octal_escape (self, output))
- return true;
-
- config_tokenizer_error (self, e, "unknown escape sequence");
- return false;
- }
-
- str_append_c (output, c);
- config_tokenizer_advance (self);
- return true;
-}
-
-static bool
-config_tokenizer_string
- (struct config_tokenizer *self, struct str *output, struct error **e)
-{
- unsigned char c;
- while (self->len)
- {
- if ((c = config_tokenizer_advance (self)) == '"')
- return true;
- if (c != '\\')
- str_append_c (output, c);
- else if (!config_tokenizer_escape_sequence (self, output, e))
- return false;
- }
- config_tokenizer_error (self, e, "premature end of string");
- return false;
-}
-
-static enum config_token
-config_tokenizer_next (struct config_tokenizer *self, struct error **e)
-{
- // Skip over any whitespace between tokens
- while (self->len && isspace_ascii (*self->p) && *self->p != '\n')
- config_tokenizer_advance (self);
- if (!self->len)
- return CONFIG_T_ABORT;
-
- switch (*self->p)
- {
- case '\n': config_tokenizer_advance (self); return CONFIG_T_NEWLINE;
- case '=': config_tokenizer_advance (self); return CONFIG_T_EQUALS;
- case '{': config_tokenizer_advance (self); return CONFIG_T_LBRACE;
- case '}': config_tokenizer_advance (self); return CONFIG_T_RBRACE;
-
- case '#':
- // Comments go until newline
- while (self->len)
- if (config_tokenizer_advance (self) == '\n')
- return CONFIG_T_NEWLINE;
- return CONFIG_T_ABORT;
-
- case '"':
- config_tokenizer_advance (self);
- str_reset (&self->string);
- if (!config_tokenizer_string (self, &self->string, e))
- return CONFIG_T_ABORT;
- if (!utf8_validate (self->string.str, self->string.len))
- {
- config_tokenizer_error (self, e, "not a valid UTF-8 string");
- return CONFIG_T_ABORT;
- }
- return CONFIG_T_STRING;
- }
-
- char *end;
- errno = 0;
- self->integer = strtoll (self->p, &end, 10);
- if (errno == ERANGE)
- {
- config_tokenizer_error (self, e, "integer out of range");
- return CONFIG_T_ABORT;
- }
- if (end != self->p)
- {
- self->len -= end - self->p;
- self->p = end;
- return CONFIG_T_INTEGER;
- }
-
- if (!config_tokenizer_is_word_char (*self->p))
- {
- config_tokenizer_error (self, e, "invalid input");
- return CONFIG_T_ABORT;
- }
-
- str_reset (&self->string);
- do
- str_append_c (&self->string, config_tokenizer_advance (self));
- while (config_tokenizer_is_word_char (*self->p));
-
- if (!strcmp (self->string.str, "null"))
- return CONFIG_T_NULL;
-
- bool boolean;
- if (!set_boolean_if_valid (&boolean, self->string.str))
- return CONFIG_T_WORD;
-
- self->integer = boolean;
- return CONFIG_T_BOOLEAN;
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-struct config_parser
-{
- struct config_tokenizer tokenizer; ///< Tokenizer
-
- struct error *error; ///< Tokenizer error
- enum config_token token; ///< Current token in the tokenizer
- bool replace_token; ///< Replace the token
-};
-
-static void
-config_parser_init (struct config_parser *self, const char *script, size_t len)
-{
- memset (self, 0, sizeof *self);
- config_tokenizer_init (&self->tokenizer, script, len);
-
- // As reading in tokens may cause exceptions, we wait for the first peek()
- // to replace the initial CONFIG_T_ABORT.
- self->replace_token = true;
-}
-
-static void
-config_parser_free (struct config_parser *self)
-{
- config_tokenizer_free (&self->tokenizer);
- if (self->error)
- error_free (self->error);
-}
-
-static enum config_token
-config_parser_peek (struct config_parser *self, jmp_buf out)
-{
- if (self->replace_token)
- {
- self->token = config_tokenizer_next (&self->tokenizer, &self->error);
- if (self->error)
- longjmp (out, 1);
- self->replace_token = false;
- }
- return self->token;
-}
-
-static bool
-config_parser_accept
- (struct config_parser *self, enum config_token token, jmp_buf out)
-{
- return self->replace_token = (config_parser_peek (self, out) == token);
-}
-
-static void
-config_parser_expect
- (struct config_parser *self, enum config_token token, jmp_buf out)
-{
- if (config_parser_accept (self, token, out))
- return;
-
- config_tokenizer_error (&self->tokenizer, &self->error,
- "unexpected `%s', expected `%s'",
- config_token_name (self->token),
- config_token_name (token));
- longjmp (out, 1);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-// We don't need no generator, but a few macros will come in handy.
-// From time to time C just doesn't have the right features.
-
-#define PEEK() config_parser_peek (self, err)
-#define ACCEPT(token) config_parser_accept (self, token, err)
-#define EXPECT(token) config_parser_expect (self, token, err)
-#define SKIP_NL() do {} while (ACCEPT (CONFIG_T_NEWLINE))
-
-static struct config_item *config_parser_parse_object
- (struct config_parser *self, jmp_buf out);
-
-static struct config_item *
-config_parser_parse_value (struct config_parser *self, jmp_buf out)
-{
- struct config_item *volatile result = NULL;
- jmp_buf err;
-
- if (setjmp (err))
- {
- if (result)
- config_item_destroy (result);
- longjmp (out, 1);
- }
-
- if (ACCEPT (CONFIG_T_LBRACE))
- {
- result = config_parser_parse_object (self, out);
- SKIP_NL ();
- EXPECT (CONFIG_T_RBRACE);
- return result;
- }
- if (ACCEPT (CONFIG_T_NULL))
- return config_item_null ();
- if (ACCEPT (CONFIG_T_BOOLEAN))
- return config_item_boolean (self->tokenizer.integer);
- if (ACCEPT (CONFIG_T_INTEGER))
- return config_item_integer (self->tokenizer.integer);
- if (ACCEPT (CONFIG_T_STRING))
- return config_item_string (&self->tokenizer.string);
-
- config_tokenizer_error (&self->tokenizer, &self->error,
- "unexpected `%s', expected a value",
- config_token_name (self->token));
- longjmp (out, 1);
-}
-
-/// Parse a single "key = value" assignment into @a object
-static bool
-config_parser_parse_kv_pair (struct config_parser *self,
- struct config_item *object, jmp_buf out)
-{
- char *volatile key = NULL;
- jmp_buf err;
-
- if (setjmp (err))
- {
- free (key);
- longjmp (out, 1);
- }
-
- SKIP_NL ();
-
- // Either this object's closing right brace if called recursively,
- // or end of file when called on a whole configuration file
- if (PEEK () == CONFIG_T_RBRACE
- || PEEK () == CONFIG_T_ABORT)
- return false;
-
- EXPECT (CONFIG_T_WORD);
- key = xstrdup (self->tokenizer.string.str);
- SKIP_NL ();
-
- EXPECT (CONFIG_T_EQUALS);
- SKIP_NL ();
-
- str_map_set (&object->value.object, key,
- config_parser_parse_value (self, err));
-
- free (key);
- key = NULL;
-
- if (PEEK () == CONFIG_T_RBRACE
- || PEEK () == CONFIG_T_ABORT)
- return false;
-
- EXPECT (CONFIG_T_NEWLINE);
- return true;
-}
-
-/// Parse the inside of an object definition
-static struct config_item *
-config_parser_parse_object (struct config_parser *self, jmp_buf out)
-{
- struct config_item *volatile object = config_item_object ();
- jmp_buf err;
-
- if (setjmp (err))
- {
- config_item_destroy (object);
- longjmp (out, 1);
- }
-
- while (config_parser_parse_kv_pair (self, object, err))
- ;
- return object;
-}
-
-#undef PEEK
-#undef ACCEPT
-#undef EXPECT
-#undef SKIP_NL
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-/// Parse a configuration snippet either as an object or a bare value.
-/// If it's the latter (@a single_value_only), no newlines may follow.
-static struct config_item *
-config_item_parse (const char *script, size_t len,
- bool single_value_only, struct error **e)
-{
- struct config_parser parser;
- config_parser_init (&parser, script, len);
-
- struct config_item *volatile object = NULL;
- jmp_buf err;
-
- if (setjmp (err))
- {
- if (object)
- {
- config_item_destroy (object);
- object = NULL;
- }
-
- error_propagate (e, parser.error);
- parser.error = NULL;
- goto end;
- }
-
- if (single_value_only)
- {
- // This is really only intended for in-program configuration
- // and telling the line number would look awkward
- parser.tokenizer.report_line = false;
- object = config_parser_parse_value (&parser, err);
- }
- else
- object = config_parser_parse_object (&parser, err);
- config_parser_expect (&parser, CONFIG_T_ABORT, err);
-end:
- config_parser_free (&parser);
- return object;
-}
-
-/// Clone an item. Schema assignments aren't retained.
-struct config_item *
-config_item_clone (struct config_item *self)
-{
- // Oh well, it saves code
- struct str tmp;
- str_init (&tmp);
- config_item_write (self, false, &tmp);
- struct config_item *result =
- config_item_parse (tmp.str, tmp.len, true, NULL);
- str_free (&tmp);
- return result;
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static struct config_item *
-config_schema_initialize_item (struct config_schema *schema,
- struct config_item *parent, struct error **warning, struct error **e)
-{
- hard_assert (parent->type == CONFIG_ITEM_OBJECT);
- struct config_item *item =
- str_map_find (&parent->value.object, schema->name);
-
- struct error *error = NULL;
- if (item && config_item_validate_by_schema (item, schema, &error))
- goto keep_current;
-
- if (error)
- {
- error_set (warning, "resetting configuration item "
- "`%s' to default: %s", schema->name, error->message);
- error_free (error);
- error = NULL;
- }
-
- if (schema->default_)
- item = config_item_parse
- (schema->default_, strlen (schema->default_), true, &error);
- else
- item = config_item_null ();
-
- if (error || !config_item_validate_by_schema (item, schema, &error))
- {
- error_set (e, "invalid default for configuration item `%s': %s",
- schema->name, error->message);
- error_free (error);
-
- config_item_destroy (item);
- return NULL;
- }
-
- // This will free the old item if there was any
- str_map_set (&parent->value.object, schema->name, item);
-
-keep_current:
- // Make sure the string subtype fits the schema
- if (config_item_type_is_string (item->type)
- && config_item_type_is_string (schema->type))
- item->type = schema->type;
-
- item->schema = schema;
- return item;
-}
-
-/// Assign schemas and user_data to multiple items at once;
-/// feel free to copy over and modify to suit your particular needs
-static void
-config_schema_apply_to_object (struct config_schema *schema_array,
- struct config_item *object, void *user_data)
-{
- while (schema_array->name)
- {
- struct error *warning = NULL, *e = NULL;
- struct config_item *item = config_schema_initialize_item
- (schema_array++, object, &warning, &e);
-
- if (warning)
- {
- print_warning ("%s", warning->message);
- error_free (warning);
- }
- if (e)
- print_fatal ("%s", e->message);
-
- item->user_data = user_data;
- }
-}
-
-static void
-config_schema_call_changed (struct config_item *item)
-{
- if (item->type == CONFIG_ITEM_OBJECT)
- {
- struct str_map_iter iter;
- str_map_iter_init (&iter, &item->value.object);
-
- struct config_item *child;
- while ((child = str_map_iter_next (&iter)))
- config_schema_call_changed (child);
- }
- else if (item->schema && item->schema->on_change)
- item->schema->on_change (item);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-// XXX: the callbacks may be overdesigned and of little to no practical use
-
-typedef void (*config_module_load_fn)
- (struct config_item *subtree, void *user_data);
-
-struct config_module
-{
- char *name; ///< Name of the subtree
- config_module_load_fn loader; ///< Module config subtree loader
- void *user_data; ///< User data
-};
-
-static void
-config_module_destroy (struct config_module *self)
-{
- free (self->name);
- free (self);
-}
-
-struct config
-{
- struct str_map modules; ///< Toplevel modules
- struct config_item *root; ///< CONFIG_ITEM_OBJECT
-};
-
-static void
-config_init (struct config *self)
-{
- memset (self, 0, sizeof *self);
- str_map_init (&self->modules);
- self->modules.free = (str_map_free_fn) config_module_destroy;
-}
-
-static void
-config_free (struct config *self)
-{
- str_map_free (&self->modules);
- if (self->root)
- config_item_destroy (self->root);
-}
-
-static void
-config_register_module (struct config *self,
- const char *name, config_module_load_fn loader, void *user_data)
-{
- struct config_module *module = xcalloc (1, sizeof *module);
- module->name = xstrdup (name);
- module->loader = loader;
- module->user_data = user_data;
-
- str_map_set (&self->modules, name, module);
-}
-
-static void
-config_load (struct config *self, struct config_item *root)
-{
- hard_assert (root->type == CONFIG_ITEM_OBJECT);
- if (self->root)
- config_item_destroy (self->root);
- self->root = root;
-
- struct str_map_iter iter;
- str_map_iter_init (&iter, &self->modules);
-
- struct config_module *module;
- while ((module = str_map_iter_next (&iter)))
- {
- struct config_item *subtree = str_map_find
- (&root->value.object, module->name);
- // Silently fix inputs that only a lunatic user could create
- if (!subtree || subtree->type != CONFIG_ITEM_OBJECT)
- {
- subtree = config_item_object ();
- str_map_set (&root->value.object, module->name, subtree);
- }
- if (module->loader)
- module->loader (subtree, module->user_data);
- }
-}