xK/common.c

1057 lines
26 KiB
C

/*
* common.c: common functionality
*
* Copyright (c) 2014, Přemysl Janouch <p.janouch@gmail.com>
* All rights reserved.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#define LIBERTY_WANT_SSL
#define LIBERTY_WANT_POLLER
#define LIBERTY_WANT_PROTO_IRC
#ifdef WANT_SYSLOG_LOGGING
#define print_fatal_data ((void *) LOG_ERR)
#define print_error_data ((void *) LOG_ERR)
#define print_warning_data ((void *) LOG_WARNING)
#define print_status_data ((void *) LOG_INFO)
#define print_debug_data ((void *) LOG_DEBUG)
#endif // WANT_SYSLOG_LOGGING
#include "liberty/liberty.c"
#include <setjmp.h>
#include <arpa/inet.h>
// --- Logging -----------------------------------------------------------------
static void
log_message_syslog (void *user_data, const char *quote, const char *fmt,
va_list ap)
{
int prio = (int) (intptr_t) user_data;
va_list va;
va_copy (va, ap);
int size = vsnprintf (NULL, 0, fmt, va);
va_end (va);
if (size < 0)
return;
char buf[size + 1];
if (vsnprintf (buf, sizeof buf, fmt, ap) >= 0)
syslog (prio, "%s%s", quote, buf);
}
// --- SOCKS 5/4a (blocking implementation) ------------------------------------
// These are awkward protocols. Note that the `username' is used differently
// in SOCKS 4a and 5. In the former version, it is the username that you can
// get ident'ed against. In the latter version, it forms a pair with the
// password field and doesn't need to be an actual user on your machine.
// TODO: make a non-blocking poller-based version of this;
// either use c-ares or (even better) start another thread to do resolution
struct socks_addr
{
enum socks_addr_type
{
SOCKS_IPV4 = 1, ///< IPv4 address
SOCKS_DOMAIN = 3, ///< Domain name to be resolved
SOCKS_IPV6 = 4 ///< IPv6 address
}
type; ///< The type of this address
union
{
uint8_t ipv4[4]; ///< IPv4 address, network octet order
const char *domain; ///< Domain name
uint8_t ipv6[16]; ///< IPv6 address, network octet order
}
data; ///< The address itself
};
struct socks_data
{
struct socks_addr address; ///< Target address
uint16_t port; ///< Target port
const char *username; ///< Authentication username
const char *password; ///< Authentication password
struct socks_addr bound_address; ///< Bound address at the server
uint16_t bound_port; ///< Bound port at the server
};
static bool
socks_get_socket (struct addrinfo *addresses, int *fd, struct error **e)
{
int sockfd;
for (; addresses; addresses = addresses->ai_next)
{
sockfd = socket (addresses->ai_family,
addresses->ai_socktype, addresses->ai_protocol);
if (sockfd == -1)
continue;
set_cloexec (sockfd);
int yes = 1;
soft_assert (setsockopt (sockfd, SOL_SOCKET, SO_KEEPALIVE,
&yes, sizeof yes) != -1);
if (!connect (sockfd, addresses->ai_addr, addresses->ai_addrlen))
break;
xclose (sockfd);
}
if (!addresses)
{
error_set (e, "couldn't connect to the SOCKS server");
return false;
}
*fd = sockfd;
return true;
}
#define SOCKS_FAIL(...) \
BLOCK_START \
error_set (e, __VA_ARGS__); \
goto fail; \
BLOCK_END
#define SOCKS_RECV(buf, len) \
BLOCK_START \
if ((n = recv (sockfd, (buf), (len), 0)) == -1) \
SOCKS_FAIL ("%s: %s", "recv", strerror (errno)); \
if (n != (len)) \
SOCKS_FAIL ("%s: %s", "protocol error", "unexpected EOF"); \
BLOCK_END
static bool
socks_4a_connect (struct addrinfo *addresses, struct socks_data *data,
int *fd, struct error **e)
{
int sockfd;
if (!socks_get_socket (addresses, &sockfd, e))
return false;
const void *dest_ipv4 = "\x00\x00\x00\x01";
const char *dest_domain = NULL;
char buf[INET6_ADDRSTRLEN];
switch (data->address.type)
{
case SOCKS_IPV4:
dest_ipv4 = data->address.data.ipv4;
break;
case SOCKS_IPV6:
// About the best thing we can do, not sure if it works anywhere at all
if (!inet_ntop (AF_INET6, &data->address.data.ipv6, buf, sizeof buf))
SOCKS_FAIL ("%s: %s", "inet_ntop", strerror (errno));
dest_domain = buf;
break;
case SOCKS_DOMAIN:
dest_domain = data->address.data.domain;
}
struct str req;
str_init (&req);
str_append_c (&req, 4); // version
str_append_c (&req, 1); // connect
str_append_c (&req, data->port >> 8); // higher bits of port
str_append_c (&req, data->port); // lower bits of port
str_append_data (&req, dest_ipv4, 4); // destination address
if (data->username)
str_append (&req, data->username);
str_append_c (&req, '\0');
if (dest_domain)
{
str_append (&req, dest_domain);
str_append_c (&req, '\0');
}
ssize_t n = send (sockfd, req.str, req.len, 0);
str_free (&req);
if (n == -1)
SOCKS_FAIL ("%s: %s", "send", strerror (errno));
uint8_t resp[8];
SOCKS_RECV (resp, sizeof resp);
if (resp[0] != 0)
SOCKS_FAIL ("protocol error");
switch (resp[1])
{
case 90:
break;
case 91:
SOCKS_FAIL ("request rejected or failed");
case 92:
SOCKS_FAIL ("%s: %s", "request rejected",
"SOCKS server cannot connect to identd on the client");
case 93:
SOCKS_FAIL ("%s: %s", "request rejected",
"identd reports different user-id");
default:
SOCKS_FAIL ("protocol error");
}
*fd = sockfd;
return true;
fail:
xclose (sockfd);
return false;
}
#undef SOCKS_FAIL
#define SOCKS_FAIL(...) \
BLOCK_START \
error_set (e, __VA_ARGS__); \
return false; \
BLOCK_END
static bool
socks_5_userpass_auth (int sockfd, struct socks_data *data, struct error **e)
{
size_t ulen = strlen (data->username);
if (ulen > 255)
ulen = 255;
size_t plen = strlen (data->password);
if (plen > 255)
plen = 255;
uint8_t req[3 + ulen + plen], *p = req;
*p++ = 0x01; // version
*p++ = ulen; // username length
memcpy (p, data->username, ulen);
p += ulen;
*p++ = plen; // password length
memcpy (p, data->password, plen);
p += plen;
ssize_t n = send (sockfd, req, p - req, 0);
if (n == -1)
SOCKS_FAIL ("%s: %s", "send", strerror (errno));
uint8_t resp[2];
SOCKS_RECV (resp, sizeof resp);
if (resp[0] != 0x01)
SOCKS_FAIL ("protocol error");
if (resp[1] != 0x00)
SOCKS_FAIL ("authentication failure");
return true;
}
static bool
socks_5_auth (int sockfd, struct socks_data *data, struct error **e)
{
bool can_auth = data->username && data->password;
uint8_t hello[4];
hello[0] = 0x05; // version
hello[1] = 1 + can_auth; // number of authentication methods
hello[2] = 0x00; // no authentication required
hello[3] = 0x02; // username/password
ssize_t n = send (sockfd, hello, 3 + can_auth, 0);
if (n == -1)
SOCKS_FAIL ("%s: %s", "send", strerror (errno));
uint8_t resp[2];
SOCKS_RECV (resp, sizeof resp);
if (resp[0] != 0x05)
SOCKS_FAIL ("protocol error");
switch (resp[1])
{
case 0x02:
if (!can_auth)
SOCKS_FAIL ("protocol error");
if (!socks_5_userpass_auth (sockfd, data, e))
return false;
case 0x00:
break;
case 0xFF:
SOCKS_FAIL ("no acceptable authentication methods");
default:
SOCKS_FAIL ("protocol error");
}
return true;
}
static bool
socks_5_send_req (int sockfd, struct socks_data *data, struct error **e)
{
uint8_t req[4 + 256 + 2], *p = req;
*p++ = 0x05; // version
*p++ = 0x01; // connect
*p++ = 0x00; // reserved
*p++ = data->address.type;
switch (data->address.type)
{
case SOCKS_IPV4:
memcpy (p, data->address.data.ipv4, sizeof data->address.data.ipv4);
p += sizeof data->address.data.ipv4;
break;
case SOCKS_DOMAIN:
{
size_t dlen = strlen (data->address.data.domain);
if (dlen > 255)
dlen = 255;
*p++ = dlen;
memcpy (p, data->address.data.domain, dlen);
p += dlen;
break;
}
case SOCKS_IPV6:
memcpy (p, data->address.data.ipv6, sizeof data->address.data.ipv6);
p += sizeof data->address.data.ipv6;
break;
}
*p++ = data->port >> 8;
*p++ = data->port;
if (send (sockfd, req, p - req, 0) == -1)
SOCKS_FAIL ("%s: %s", "send", strerror (errno));
return true;
}
static bool
socks_5_process_resp (int sockfd, struct socks_data *data, struct error **e)
{
uint8_t resp_header[4];
ssize_t n;
SOCKS_RECV (resp_header, sizeof resp_header);
if (resp_header[0] != 0x05)
SOCKS_FAIL ("protocol error");
switch (resp_header[1])
{
case 0x00:
break;
case 0x01: SOCKS_FAIL ("general SOCKS server failure");
case 0x02: SOCKS_FAIL ("connection not allowed by ruleset");
case 0x03: SOCKS_FAIL ("network unreachable");
case 0x04: SOCKS_FAIL ("host unreachable");
case 0x05: SOCKS_FAIL ("connection refused");
case 0x06: SOCKS_FAIL ("TTL expired");
case 0x07: SOCKS_FAIL ("command not supported");
case 0x08: SOCKS_FAIL ("address type not supported");
default: SOCKS_FAIL ("protocol error");
}
switch ((data->bound_address.type = resp_header[3]))
{
case SOCKS_IPV4:
SOCKS_RECV (data->bound_address.data.ipv4,
sizeof data->bound_address.data.ipv4);
break;
case SOCKS_IPV6:
SOCKS_RECV (data->bound_address.data.ipv6,
sizeof data->bound_address.data.ipv6);
break;
case SOCKS_DOMAIN:
{
uint8_t len;
SOCKS_RECV (&len, sizeof len);
char domain[len + 1];
SOCKS_RECV (domain, len);
domain[len] = '\0';
data->bound_address.data.domain = xstrdup (domain);
break;
}
default:
SOCKS_FAIL ("protocol error");
}
uint16_t port;
SOCKS_RECV (&port, sizeof port);
data->bound_port = ntohs (port);
return true;
}
#undef SOCKS_FAIL
#undef SOCKS_RECV
static bool
socks_5_connect (struct addrinfo *addresses, struct socks_data *data,
int *fd, struct error **e)
{
int sockfd;
if (!socks_get_socket (addresses, &sockfd, e))
return false;
if (!socks_5_auth (sockfd, data, e)
|| !socks_5_send_req (sockfd, data, e)
|| !socks_5_process_resp (sockfd, data, e))
{
xclose (sockfd);
return false;
}
*fd = sockfd;
return true;
}
static int
socks_connect (const char *socks_host, const char *socks_port,
const char *host, const char *port,
const char *username, const char *password, struct error **e)
{
int result = -1;
struct addrinfo gai_hints, *gai_result;
memset (&gai_hints, 0, sizeof gai_hints);
gai_hints.ai_socktype = SOCK_STREAM;
unsigned long port_no;
const struct servent *serv;
if ((serv = getservbyname (port, "tcp")))
port_no = (uint16_t) ntohs (serv->s_port);
else if (!xstrtoul (&port_no, port, 10) || !port_no || port_no > UINT16_MAX)
{
error_set (e, "invalid port number");
goto fail;
}
int err = getaddrinfo (socks_host, socks_port, &gai_hints, &gai_result);
if (err)
{
error_set (e, "%s: %s", "getaddrinfo", gai_strerror (err));
goto fail;
}
struct socks_data data =
{ .username = username, .password = password, .port = port_no };
if (inet_pton (AF_INET, host, &data.address.data.ipv4) == 1)
data.address.type = SOCKS_IPV4;
else if (inet_pton (AF_INET6, host, &data.address.data.ipv6) == 1)
data.address.type = SOCKS_IPV6;
else
{
data.address.type = SOCKS_DOMAIN;
data.address.data.domain = host;
}
if (!socks_5_connect (gai_result, &data, &result, NULL))
socks_4a_connect (gai_result, &data, &result, e);
if (data.bound_address.type == SOCKS_DOMAIN)
free ((char *) data.bound_address.data.domain);
freeaddrinfo (gai_result);
fail:
return result;
}
// --- To be moved to liberty --------------------------------------------------
static bool
isalpha_ascii (int c)
{
c &= ~32;
return c >= 'A' && c <= 'Z';
}
static bool
isdigit_ascii (int c)
{
return c >= '0' && c <= '9';
}
static bool
isalnum_ascii (int c)
{
return isalpha_ascii (c) || isdigit_ascii (c);
}
static int
toupper_ascii (int c)
{
return c >= 'A' && c <= 'Z' ? c : c - ('a' - 'A');
}
// --- 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
bool is_nullable; ///< Can be null?
const char *default_; ///< Default as a configuration snippet
/// Check if the new value can be accepted.
/// If this is not defined, only "type" and "is_nullable" is considered.
bool (*validate) (struct config_item_ *, const struct config_item_ *);
/// The value has changed. Only appliable to objects.
bool (*on_changed) (struct config_item_ *);
/// Free any resources located in "item->user_data"
void (*on_destroy) (struct config_item_ *item);
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void
config_item_destroy (struct config_item_ *self)
{
if (self->schema && self->schema->on_destroy)
self->schema->on_destroy (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;
}
free (self);
}
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);
if (s) str_append_str (&self->value.string, s);
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_BOOLEAN);
str_map_init (&self->value.object);
self->value.object.free = (void (*)(void *)) config_item_destroy;
return self;
}
/// Doesn't do any validations or such, only moves source data to the item
static void
config_item_move (struct config_item_ *self, struct config_item_ *source)
{
// TODO
}
static bool
config_item_set_from (struct config_item_ *self,
struct config_item_ *source, struct error **e)
{
hard_assert (self->type == CONFIG_ITEM_OBJECT);
// TODO
}
static struct config_item_ *
config_item_get (struct config_item_ *self, const char *path)
{
hard_assert (self->type == CONFIG_ITEM_OBJECT);
// TODO
}
static void
config_item_write (struct config_item_ *root, struct str *output)
{
// TODO
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void
config_schema_apply_to_object
(struct config_schema *schema_array, struct config_item_ *object)
{
hard_assert (object->type == CONFIG_ITEM_OBJECT);
// TODO
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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}
};
struct config_tokenizer
{
const char *p;
size_t len;
unsigned line;
unsigned column;
int64_t integer;
struct str string;
};
/// 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;
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->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 *description)
{
// FIXME: we don't always want to specify the line
error_set (e, "near line %u, column %u: %s",
self->line + 1, self->column + 1, description);
}
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 '"':
// TODO: string, validate as UTF-8
break;
}
bool is_word = false;
while (config_tokenizer_is_word_char (*self->p))
{
is_word = true;
str_reset (&self->string);
str_append_c (&self->string, config_tokenizer_advance (self));
}
if (is_word)
{
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;
}
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;
}
config_tokenizer_error (self, e, "invalid input");
return CONFIG_T_ABORT;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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;
// TODO: fill in "X" and "Y"
config_tokenizer_error (&self->tokenizer, &self->error,
"unexpected X, expected Y");
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);
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);
// TODO: fill in "X" as the token name
config_tokenizer_error (&self->tokenizer, &self->error,
"unexpected X, expected a value");
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)
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;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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
};
struct config
{
struct str_map modules; ///< Toplevel modules
struct config_item_ *root; ///< CONFIG_ITEM_OBJECT
};
static void
config_init (struct config *self)
{
// TODO
}
static void
config_free (struct config *self)
{
// TODO
}
static bool
config_register_module (const char *name,
config_module_load_fn loader, void *user_data)
{
// TODO
}
static bool
config_load (struct config_item_ *root, struct error **e)
{
// TODO
}