/*
 * liberty.c: the ultimate C unlibrary
 *
 * Copyright (c) 2014 - 2024, Přemysl Eric Janouch <p@janouch.name>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted.
 *
 * 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.
 *
 */

#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200112L
#endif

#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 600
#endif

#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdbool.h>
#include <inttypes.h>
#include <ctype.h>
#include <time.h>
#include <limits.h>
#include <setjmp.h>

#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <strings.h>
#include <regex.h>
#include <libgen.h>
#include <syslog.h>
#include <fnmatch.h>
#include <iconv.h>
#include <pwd.h>
#include <pthread.h>

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>

#ifdef __unix__
// This file may define the "BSD" macro...
#include <sys/param.h>
// ...as well as these conflicting ones
#undef MIN
#undef MAX
#endif // __unix__

#ifndef NI_MAXHOST
#define NI_MAXHOST 1025
#endif // ! NI_MAXHOST

#ifndef NI_MAXSERV
#define NI_MAXSERV 32
#endif // ! NI_MAXSERV

#ifdef LIBERTY_WANT_SSL
#include <openssl/sha.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#endif // LIBERTY_WANT_SSL

#include <getopt.h>
#include "siphash.c"

extern char **environ;

#ifdef CLOCK_MONOTONIC_RAW
// This should be more accurate for shorter intervals
#define CLOCK_BEST CLOCK_MONOTONIC_RAW
#elif defined _POSIX_MONOTONIC_CLOCK
#define CLOCK_BEST CLOCK_MONOTONIC
#else // ! _POSIX_MONOTIC_CLOCK
#define CLOCK_BEST CLOCK_REALTIME
#endif // ! _POSIX_MONOTONIC_CLOCK

#if defined __GNUC__
#define ATTRIBUTE_PRINTF(x, y) __attribute__ ((format (printf, x, y)))
#else // ! __GNUC__
#define ATTRIBUTE_PRINTF(x, y)
#endif // ! __GNUC__

#if defined __GNUC__ && __GNUC__ >= 4
#define ATTRIBUTE_SENTINEL __attribute__ ((sentinel))
#else // ! __GNUC__ || __GNUC__ < 4
#define ATTRIBUTE_SENTINEL
#endif // ! __GNUC__ || __GNUC__ < 4

#define N_ELEMENTS(a) (sizeof (a) / sizeof ((a)[0]))

#define BLOCK_START  do {
#define BLOCK_END    } while (0)

#define MIN(a, b)  ((a) < (b) ? (a) : (b))
#define MAX(a, b)  ((a) > (b) ? (a) : (b))

#define STRINGIFY(x) #x
#define XSTRINGIFY(x) STRINGIFY (x)

#define CONTAINER_OF(pointer, type, member) \
	((type *) ((char *) pointer - offsetof (type, member)))

char *liberty = "They who can give up essential liberty to obtain a little "
	"temporary safety deserve neither liberty nor safety.";

// --- Logging -----------------------------------------------------------------

static void
log_message_stdio (void *user_data, const char *quote, const char *fmt,
	va_list ap)
{
	(void) user_data;
	FILE *stream = stderr;

	fputs (quote, stream);
	vfprintf (stream, fmt, ap);
	fputs ("\n", stream);
}

static void (*g_log_message_real) (void *, const char *, const char *, va_list)
	= log_message_stdio;

static void
log_message (void *user_data, const char *quote, const char *fmt, ...)
	ATTRIBUTE_PRINTF (3, 4);

static void
log_message (void *user_data, const char *quote, const char *fmt, ...)
{
	va_list ap;
	va_start (ap, fmt);
	g_log_message_real (user_data, quote, fmt, ap);
	va_end (ap);
}

// `fatal' is reserved for unexpected failures that would harm further operation

#ifndef print_fatal_data
#define print_fatal_data    NULL
#endif

#ifndef print_error_data
#define print_error_data    NULL
#endif

#ifndef print_warning_data
#define print_warning_data  NULL
#endif

#ifndef print_status_data
#define print_status_data   NULL
#endif

#define print_fatal(...) \
	log_message (print_fatal_data,   "fatal: ",   __VA_ARGS__)
#define print_error(...) \
	log_message (print_error_data,   "error: ",   __VA_ARGS__)
#define print_warning(...) \
	log_message (print_warning_data, "warning: ", __VA_ARGS__)
#define print_status(...) \
	log_message (print_status_data,  "-- ",       __VA_ARGS__)

#define exit_fatal(...)                                                        \
	BLOCK_START                                                                \
		print_fatal (__VA_ARGS__);                                             \
		exit (EXIT_FAILURE);                                                   \
	BLOCK_END

// --- Debugging and assertions ------------------------------------------------

// We should check everything that may possibly fail with at least a soft
// assertion, so that any causes for problems don't slip us by silently.
//
// `g_soft_asserts_are_deadly' may be useful while running inside a debugger.

static bool g_debug_mode;               ///< Debug messages are printed
static bool g_soft_asserts_are_deadly;  ///< soft_assert() aborts as well

#ifndef print_debug_data
#define print_debug_data   NULL
#endif

#define print_debug(...)                                                       \
	BLOCK_START                                                                \
		if (g_debug_mode)                                                      \
			log_message (print_debug_data, "debug: ", __VA_ARGS__);            \
	BLOCK_END

// A few other debugging shorthands for when failures are allowed
#define LOG_FUNC_FAILURE(name, desc)                                           \
	print_debug ("%s: %s: %s", __func__, (name), (desc))
#define LOG_LIBC_FAILURE(name)                                                 \
	print_debug ("%s: %s: %s", __func__, (name), strerror (errno))

static void
assertion_failure_handler (bool is_fatal, const char *file, int line,
	const char *function, const char *condition)
{
	const char *slash = strrchr (file, '/');
	if (slash)
		file = slash + 1;
	if (is_fatal)
	{
		print_fatal ("assertion failed [%s:%d in function %s]: %s",
			file, line, function, condition);
		abort ();
	}
	else
		print_debug ("assertion failed [%s:%d in function %s]: %s",
			file, line, function, condition);
}

#define soft_assert(condition)                                                 \
	((condition) ? true :                                                      \
		(assertion_failure_handler (g_soft_asserts_are_deadly,                 \
		__FILE__, __LINE__, __func__, #condition), false))

#define hard_assert(condition)                                                 \
	((condition) ? (void) 0 :                                                  \
		assertion_failure_handler (true,                                       \
		__FILE__, __LINE__, __func__, #condition))

// --- Safe memory management --------------------------------------------------

// When a memory allocation fails and we need the memory, we're usually pretty
// much fucked.  Use the non-prefixed versions when there's a legitimate
// worry that an unrealistic amount of memory may be requested for allocation.

// XXX: it's not a good idea to use print_message() as it may want to allocate
//   further memory for printf() and the output streams.  That may fail.

static void *
xmalloc (size_t n)
{
	void *p = malloc (n);
	if (!p)
		exit_fatal ("malloc: %s", strerror (errno));
	return p;
}

static void *
xcalloc (size_t n, size_t m)
{
	void *p = calloc (n, m);
	if (!p && n && m)
		exit_fatal ("calloc: %s", strerror (errno));
	return p;
}

static void *
xrealloc (void *o, size_t n)
{
	void *p = realloc (o, n);
	if (!p && n)
		exit_fatal ("realloc: %s", strerror (errno));
	return p;
}

static void *
xreallocarray (void *o, size_t n, size_t m)
{
	if (m && n > SIZE_MAX / m)
	{
		errno = ENOMEM;
		exit_fatal ("reallocarray: %s", strerror (errno));
	}
	return xrealloc (o, n * m);
}

static char *
xstrdup (const char *s)
{
	return strcpy (xmalloc (strlen (s) + 1), s);
}

static char *
xstrndup (const char *s, size_t n)
{
	size_t size = strlen (s);
	if (n > size)
		n = size;

	char *copy = xmalloc (n + 1);
	memcpy (copy, s, n);
	copy[n] = '\0';
	return copy;
}

// --- Simple array support ----------------------------------------------------

// The most basic helper macros to make working with arrays not suck

#define ARRAY(type, name) type *name; size_t name ## _len, name ## _alloc;
#define ARRAY_INIT_SIZED(a, n)                                                 \
	BLOCK_START                                                                \
		(a) = xcalloc (sizeof *(a), (a ## _alloc) = (n));                      \
		(a ## _len) = 0;                                                       \
	BLOCK_END
#define ARRAY_INIT(a) ARRAY_INIT_SIZED (a, 16)
#define ARRAY_RESERVE(a, n)                                                    \
	BLOCK_START                                                                \
		while ((a ## _alloc) - (a ## _len) < n)                                \
			(a) = xreallocarray ((a), sizeof *(a), (a ## _alloc) <<= 1);       \
	BLOCK_END

// --- Double-linked list helpers ----------------------------------------------

#define LIST_HEADER(type)                                                      \
	type *next;                                                                \
	type *prev;

#define LIST_PREPEND(head, link)                                               \
	BLOCK_START                                                                \
		(link)->prev = NULL;                                                   \
		(link)->next = (head);                                                 \
		if ((link)->next)                                                      \
			(link)->next->prev = (link);                                       \
		(head) = (link);                                                       \
	BLOCK_END

#define LIST_UNLINK(head, link)                                                \
	BLOCK_START                                                                \
		if ((link)->prev)                                                      \
			(link)->prev->next = (link)->next;                                 \
		else                                                                   \
			(head) = (link)->next;                                             \
		if ((link)->next)                                                      \
			(link)->next->prev = (link)->prev;                                 \
	BLOCK_END

#define LIST_APPEND_WITH_TAIL(head, tail, link)                                \
	BLOCK_START                                                                \
		(link)->prev = (tail);                                                 \
		(link)->next = NULL;                                                   \
		if ((link)->prev)                                                      \
			(link)->prev->next = (link);                                       \
		else                                                                   \
			(head) = (link);                                                   \
		(tail) = (link);                                                       \
	BLOCK_END

#define LIST_INSERT_WITH_TAIL(head, tail, link, following)                     \
	BLOCK_START                                                                \
		if (following)                                                         \
			LIST_APPEND_WITH_TAIL ((head), (following)->prev, (link));         \
		else                                                                   \
			LIST_APPEND_WITH_TAIL ((head), (tail), (link));                    \
		(link)->next = (following);                                            \
	BLOCK_END

#define LIST_UNLINK_WITH_TAIL(head, tail, link)                                \
	BLOCK_START                                                                \
		if ((tail) == (link))                                                  \
			(tail) = (link)->prev;                                             \
		LIST_UNLINK ((head), (link));                                          \
	BLOCK_END

#define LIST_FOR_EACH(type, iter, list)                                        \
	for (type *iter = (list), *next;                                           \
		(iter && (next = iter->next)) || iter;                                 \
		iter = next)

// --- Dynamically allocated string array --------------------------------------

struct strv
{
	char **vector;
	size_t len;
	size_t alloc;
};

static struct strv
strv_make (void)
{
	struct strv self;
	self.alloc = 4;
	self.len = 0;
	self.vector = xcalloc (sizeof *self.vector, self.alloc);
	return self;
}

static void
strv_free (struct strv *self)
{
	unsigned i;
	for (i = 0; i < self->len; i++)
		free (self->vector[i]);

	free (self->vector);
	self->vector = NULL;
}

static void
strv_reset (struct strv *self)
{
	strv_free (self);
	*self = strv_make ();
}

static void
strv_append_owned (struct strv *self, char *s)
{
	self->vector[self->len] = s;
	if (++self->len >= self->alloc)
		self->vector = xreallocarray (self->vector,
			sizeof *self->vector, (self->alloc <<= 1));
	self->vector[self->len] = NULL;
}

static void
strv_append (struct strv *self, const char *s)
{
	strv_append_owned (self, xstrdup (s));
}

static void
strv_append_args (struct strv *self, const char *s, ...)
	ATTRIBUTE_SENTINEL;

static void
strv_append_args (struct strv *self, const char *s, ...)
{
	va_list ap;

	va_start (ap, s);
	while (s)
	{
		strv_append (self, s);
		s = va_arg (ap, const char *);
	}
	va_end (ap);
}

static void
strv_append_vector (struct strv *self, char **vector)
{
	while (*vector)
		strv_append (self, *vector++);
}

static char *
strv_steal (struct strv *self, size_t i)
{
	hard_assert (i < self->len);
	char *tmp = self->vector[i];
	memmove (self->vector + i, self->vector + i + 1,
		(self->len-- - i) * sizeof *self->vector);
	return tmp;
}

static void
strv_remove (struct strv *self, size_t i)
{
	free (strv_steal (self, i));
}

// --- Dynamically allocated strings -------------------------------------------

// Basically a string builder to abstract away manual memory management.

struct str
{
	char *str;                          ///< String data, null terminated
	size_t alloc;                       ///< How many bytes are allocated
	size_t len;                         ///< How long the string actually is
};

/// We don't care about allocations that are way too large for the content, as
/// long as the allocation is below the given threshold.  (Trivial heuristics.)
#define STR_SHRINK_THRESHOLD (1 << 20)

static struct str
str_make (void)
{
	struct str self;
	self.alloc = 16;
	self.len = 0;
	self.str = strcpy (xmalloc (self.alloc), "");
	return self;
}

static void
str_free (struct str *self)
{
	free (self->str);
	self->str = NULL;
	self->alloc = 0;
	self->len = 0;
}

static void
str_reset (struct str *self)
{
	str_free (self);
	*self = str_make ();
}

static char *
str_steal (struct str *self)
{
	char *str = self->str;
	self->str = NULL;
	str_free (self);
	return str;
}

static void
str_reserve (struct str *self, size_t n)
{
	// We allocate at least one more byte for the terminating null character
	size_t new_alloc = self->alloc;
	while (new_alloc <= self->len + n)
		new_alloc <<= 1;
	if (new_alloc != self->alloc)
		self->str = xrealloc (self->str, (self->alloc = new_alloc));
}

static void
str_append_data (struct str *self, const void *data, size_t n)
{
	str_reserve (self, n);
	memcpy (self->str + self->len, data, n);
	self->len += n;
	self->str[self->len] = '\0';
}

static void
str_append_c (struct str *self, char c)
{
	str_append_data (self, &c, 1);
}

static void
str_append (struct str *self, const char *s)
{
	str_append_data (self, s, strlen (s));
}

static void
str_append_str (struct str *self, const struct str *another)
{
	str_append_data (self, another->str, another->len);
}

static int
str_append_vprintf (struct str *self, const char *fmt, va_list va)
{
	va_list ap;
	int size;

	va_copy (ap, va);
	size = vsnprintf (NULL, 0, fmt, ap);
	va_end (ap);

	if (size < 0)
		return -1;

	va_copy (ap, va);
	str_reserve (self, size);
	size = vsnprintf (self->str + self->len, self->alloc - self->len, fmt, ap);
	va_end (ap);

	if (size > 0)
		self->len += size;

	return size;
}

static int
str_append_printf (struct str *self, const char *fmt, ...)
	ATTRIBUTE_PRINTF (2, 3);

static int
str_append_printf (struct str *self, const char *fmt, ...)
{
	va_list ap;

	va_start (ap, fmt);
	int size = str_append_vprintf (self, fmt, ap);
	va_end (ap);
	return size;
}

static void
str_remove_slice (struct str *self, size_t start, size_t length)
{
	size_t end = start + length;
	hard_assert (end <= self->len);
	memmove (self->str + start, self->str + end, self->len - end);
	self->str[self->len -= length] = '\0';

	// Shrink the string if the allocation becomes way too large
	if (self->alloc >= STR_SHRINK_THRESHOLD && self->len < (self->alloc >> 2))
		self->str = xrealloc (self->str, self->alloc >>= 2);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

static void
str_pack_u8 (struct str *self, uint8_t x)
{
	str_append_data (self, &x, 1);
}

static void
str_pack_u16 (struct str *self, uint16_t x)
{
	uint8_t tmp[2] = { x >> 8, x };
	str_append_data (self, tmp, sizeof tmp);
}

static void
str_pack_u32 (struct str *self, uint32_t x)
{
	uint32_t u = x;
	uint8_t tmp[4] = { u >> 24, u >> 16, u >> 8, u };
	str_append_data (self, tmp, sizeof tmp);
}

static void
str_pack_u64 (struct str *self, uint64_t x)
{
	uint8_t tmp[8] =
		{ x >> 56, x >> 48, x >> 40, x >> 32, x >> 24, x >> 16, x >> 8, x };
	str_append_data (self, tmp, sizeof tmp);
}

#define str_pack_i8(self, x)   str_pack_u8  ((self), (uint8_t)  (x))
#define str_pack_i16(self, x)  str_pack_u16 ((self), (uint16_t) (x))
#define str_pack_i32(self, x)  str_pack_u32 ((self), (uint32_t) (x))
#define str_pack_i64(self, x)  str_pack_u64 ((self), (uint64_t) (x))

// --- Reading binary numbers --------------------------------------------------

// Doing this byte by byte prevents unaligned memory access issues.

static uint64_t
peek_u64be (const uint8_t *p)
{
	return (uint64_t) p[0] << 56 | (uint64_t) p[1] << 48
		| (uint64_t) p[2] << 40 | (uint64_t) p[3] << 32
		| (uint64_t) p[4] << 24 | (uint64_t) p[5] << 16 | p[6] << 8 | p[7];
}

static uint32_t
peek_u32be (const uint8_t *p)
{
	return (uint32_t) p[0] << 24 | (uint32_t) p[1] << 16 | p[2] << 8 | p[3];
}

static uint16_t
peek_u16be (const uint8_t *p)
{
	return (uint16_t) p[0] << 8 | p[1];
}

static uint64_t
peek_u64le (const uint8_t *p)
{
	return (uint64_t) p[7] << 56 | (uint64_t) p[6] << 48
		| (uint64_t) p[5] << 40 | (uint64_t) p[4] << 32
		| (uint64_t) p[3] << 24 | (uint64_t) p[2] << 16 | p[1] << 8 | p[0];
}

static uint32_t
peek_u32le (const uint8_t *p)
{
	return (uint32_t) p[3] << 24 | (uint32_t) p[2] << 16 | p[1] << 8 | p[0];
}

static uint16_t
peek_u16le (const uint8_t *p)
{
	return (uint16_t) p[1] << 8 | p[0];
}

struct peeker
{
	uint64_t (*u64) (const uint8_t *);
	uint32_t (*u32) (const uint8_t *);
	uint16_t (*u16) (const uint8_t *);
};

static const struct peeker peeker_be = {peek_u64be, peek_u32be, peek_u16be};
static const struct peeker peeker_le = {peek_u64le, peek_u32le, peek_u16le};

// --- Errors ------------------------------------------------------------------

// Error reporting utilities.  Inspired by GError, only much simpler.

struct error
{
	char *message;                      ///< Textual description of the event
};

static bool
error_set (struct error **e, const char *message, ...) ATTRIBUTE_PRINTF (2, 3);

static bool
error_set (struct error **e, const char *message, ...)
{
	if (!e)
		return false;

	va_list ap;
	va_start (ap, message);
	int size = vsnprintf (NULL, 0, message, ap);
	va_end (ap);

	hard_assert (size >= 0);

	struct error *tmp = xmalloc (sizeof *tmp);
	tmp->message = xmalloc (size + 1);

	va_start (ap, message);
	size = vsnprintf (tmp->message, size + 1, message, ap);
	va_end (ap);

	hard_assert (size >= 0);

	soft_assert (*e == NULL);
	*e = tmp;
	return false;
}

static void
error_free (struct error *e)
{
	free (e->message);
	free (e);
}

static void
error_propagate (struct error **destination, struct error *source)
{
	if (!destination)
	{
		error_free (source);
		return;
	}

	soft_assert (*destination == NULL);
	*destination = source;
}

// --- File descriptor utilities -----------------------------------------------

static void
set_cloexec (int fd)
{
	soft_assert (fcntl (fd, F_SETFD, fcntl (fd, F_GETFD) | FD_CLOEXEC) != -1);
}

static bool
set_blocking (int fd, bool blocking)
{
	int flags = fcntl (fd, F_GETFL);
	if (flags == -1)
		exit_fatal ("%s: %s", "fcntl", strerror (errno));

	bool prev = !(flags & O_NONBLOCK);
	if (blocking)
		flags &= ~O_NONBLOCK;
	else
		flags |=  O_NONBLOCK;

	hard_assert (fcntl (fd, F_SETFL, flags) != -1);
	return prev;
}

static bool
xwrite (int fd, const char *data, size_t len, struct error **e)
{
	size_t written = 0;
	while (written < len)
	{
		ssize_t res = write (fd, data + written, len - written);
		if (res >= 0)
			written += res;
		else if (errno != EINTR)
			return error_set (e, "%s", strerror (errno));
	}
	return true;
}

static void
xclose (int fd)
{
	while (close (fd) == -1)
		if (!soft_assert (errno == EINTR))
			break;
}

// --- Randomness --------------------------------------------------------------

static bool
random_bytes (void *output, size_t len, struct error **e)
{
	bool result = false;
	int fd = open ("/dev/urandom", O_RDONLY);
	ssize_t got = 0;

	if (fd < 0)
		return error_set (e, "%s: %s", "open", strerror (errno));
	else if ((got = read (fd, output, len)) < 0)
		error_set (e, "%s: %s", "read", strerror (errno));
	else if (got != (ssize_t) len)
		error_set (e, "can't get enough bytes from the device");
	else
		result = true;

	xclose (fd);
	return result;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

static unsigned char g_siphash_key[16] = "SipHash 2-4 key!";

static inline void
siphash_wrapper_randomize (void)
{
	// I guess there's no real need to be this paranoic, so we ignore failures
	soft_assert (random_bytes (g_siphash_key, sizeof g_siphash_key, NULL));
}

static inline uint64_t
siphash_wrapper (const void *m, size_t len)
{
	return siphash (g_siphash_key, m, len);
}

// --- String hash map ---------------------------------------------------------

// The most basic <string, managed pointer> map (or associative array).

struct str_map_link
{
	LIST_HEADER (struct str_map_link)

	void *data;                         ///< Payload
	size_t key_length;                  ///< Length of the key without '\0'
	char key[];                         ///< The key for this link
};

struct str_map
{
	struct str_map_link **map;          ///< The hash table data itself
	size_t alloc;                       ///< Number of allocated entries
	size_t len;                         ///< Number of entries in the table
	void (*free) (void *);              ///< Callback to destruct the payload

	/// Callback that transforms all key values for storage and comparison;
	/// has to behave exactly like strxfrm().
	size_t (*key_xfrm) (char *dest, const char *src, size_t n);

	bool shrink_lock;                   ///< Lock against autoshrinking
};

#define STR_MAP_MIN_ALLOC 16

typedef void (*str_map_free_fn) (void *);

static struct str_map
str_map_make (str_map_free_fn free)
{
	struct str_map self;
	self.alloc = STR_MAP_MIN_ALLOC;
	self.len = 0;
	self.free = free;
	self.key_xfrm = NULL;
	self.map = xcalloc (self.alloc, sizeof *self.map);
	self.shrink_lock = false;
	return self;
}

static void
str_map_clear (struct str_map *self)
{
	struct str_map_link **iter, **end = self->map + self->alloc;
	struct str_map_link *link, *tmp;

	for (iter = self->map; iter < end; iter++)
		for (link = *iter; link; link = tmp)
		{
			tmp = link->next;
			if (self->free)
				self->free (link->data);
			free (link);
		}

	self->len = 0;
	memset (self->map, 0, self->alloc * sizeof *self->map);
}

static void
str_map_free (struct str_map *self)
{
	str_map_clear (self);
	free (self->map);
	self->map = NULL;
}

static uint64_t
str_map_pos (const struct str_map *self, const char *s)
{
	size_t mask = self->alloc - 1;
	return siphash_wrapper (s, strlen (s)) & mask;
}

static uint64_t
str_map_link_hash (const struct str_map_link *self)
{
	return siphash_wrapper (self->key, self->key_length);
}

static void
str_map_resize (struct str_map *self, size_t new_size)
{
	struct str_map_link **old_map = self->map;
	size_t i, old_size = self->alloc;

	// Only powers of two, so that we don't need to compute the modulo
	hard_assert ((new_size & (new_size - 1)) == 0);
	size_t mask = new_size - 1;

	self->alloc = new_size;
	self->map = xcalloc (self->alloc, sizeof *self->map);
	for (i = 0; i < old_size; i++)
	{
		struct str_map_link *iter = old_map[i], *next_iter;
		while (iter)
		{
			next_iter = iter->next;
			uint64_t pos = str_map_link_hash (iter) & mask;
			LIST_PREPEND (self->map[pos], iter);
			iter = next_iter;
		}
	}

	free (old_map);
}

static void
str_map_shrink (struct str_map *self)
{
	if (self->shrink_lock)
		return;

	// The array should be at least 1/4 full
	size_t new_alloc = self->alloc;
	while (self->len < (new_alloc >> 2)
		&& new_alloc >= (STR_MAP_MIN_ALLOC << 1))
		new_alloc >>= 1;
	if (new_alloc != self->alloc)
		str_map_resize (self, new_alloc);
}

static void
str_map_set_real (struct str_map *self, const char *key, void *value)
{
	uint64_t pos = str_map_pos (self, key);
	struct str_map_link *iter = self->map[pos];
	for (; iter; iter = iter->next)
	{
		if (strcmp (key, iter->key))
			continue;

		// Storing the same data doesn't destroy it
		if (self->free && value != iter->data)
			self->free (iter->data);

		if (value)
		{
			iter->data = value;
			return;
		}

		LIST_UNLINK (self->map[pos], iter);
		free (iter);
		self->len--;

		str_map_shrink (self);
		return;
	}

	if (!value)
		return;

	if (self->len >= self->alloc)
	{
		str_map_resize (self, self->alloc << 1);
		pos = str_map_pos (self, key);
	}

	// Link in a new element for the given <key, value> pair
	size_t key_length = strlen (key);
	struct str_map_link *link = xmalloc (sizeof *link + key_length + 1);
	link->data = value;
	link->key_length = key_length;
	memcpy (link->key, key, key_length + 1);

	LIST_PREPEND (self->map[pos], link);
	self->len++;
}

static void
str_map_set (struct str_map *self, const char *key, void *value)
{
	if (!self->key_xfrm)
	{
		str_map_set_real (self, key, value);
		return;
	}
	char tmp[self->key_xfrm (NULL, key, 0) + 1];
	self->key_xfrm (tmp, key, sizeof tmp);
	str_map_set_real (self, tmp, value);
}

static void *
str_map_find_real (const struct str_map *self, const char *key)
{
	struct str_map_link *iter = self->map[str_map_pos (self, key)];
	for (; iter; iter = iter->next)
		if (!strcmp (key, (const char *) iter + sizeof *iter))
			return iter->data;
	return NULL;
}

static void *
str_map_find (const struct str_map *self, const char *key)
{
	if (!self->key_xfrm)
		return str_map_find_real (self, key);

	char tmp[self->key_xfrm (NULL, key, 0) + 1];
	self->key_xfrm (tmp, key, sizeof tmp);
	return str_map_find_real (self, tmp);
}

static void *
str_map_steal (struct str_map *self, const char *key)
{
	void *value = str_map_find (self, key);
	if (value)
	{
		str_map_free_fn free_saved = self->free;
		self->free = NULL;
		str_map_set (self, key, NULL);
		self->free = free_saved;
	}
	return value;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// This iterator is intended for accessing and eventually adding links.
// Use `link' directly to access the data.

struct str_map_iter
{
	const struct str_map *map;          ///< The map we're iterating
	size_t next_index;                  ///< Next table index to search
	struct str_map_link *link;          ///< Current link
};

static struct str_map_iter
str_map_iter_make (const struct str_map *map)
{
	return (struct str_map_iter) { .map = map, .next_index = 0, .link = NULL };
}

static void *
str_map_iter_next (struct str_map_iter *self)
{
	const struct str_map *map = self->map;
	if (self->link)
		self->link = self->link->next;
	while (!self->link)
	{
		if (self->next_index >= map->alloc)
			return NULL;
		self->link = map->map[self->next_index++];
	}
	return self->link->data;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// This iterator is intended for accessing and eventually removing links.
// Use `link' directly to access the data.

struct str_map_unset_iter
{
	struct str_map_iter iter;           ///< Regular iterator
	struct str_map_link *link;          ///< Current link
	struct str_map_link *next;          ///< Next link
};

static struct str_map_unset_iter
str_map_unset_iter_make (struct str_map *map)
{
	struct str_map_unset_iter self;
	self.iter = str_map_iter_make (map);
	map->shrink_lock = true;
	(void) str_map_iter_next (&self.iter);
	self.next = self.iter.link;
	return self;
}

static void *
str_map_unset_iter_next (struct str_map_unset_iter *self)
{
	if (!(self->link = self->next))
		return NULL;
	(void) str_map_iter_next (&self->iter);
	self->next = self->iter.link;
	return self->link->data;
}

static void
str_map_unset_iter_free (struct str_map_unset_iter *self)
{
	// So that we don't have to store another non-const pointer
	struct str_map *map = (struct str_map *) self->iter.map;

	map->shrink_lock = false;
	str_map_shrink (map);
}

// --- Asynchronous jobs -------------------------------------------------------

// For operations that can block execution but can be run independently on the
// rest of the program, such as getaddrinfo(), read(), write(), fsync().
//
// The async structure is meant to be extended for the various usages with
// new fields and provide an appropriate callback for its destruction.
//
// This is designed so that it can be used in other event loops than poller.

#ifdef LIBERTY_WANT_ASYNC

struct async;
typedef void (*async_fn) (struct async *);

struct async
{
	LIST_HEADER (struct async)
	struct async_manager *manager;      ///< Our manager object

	// "cancelled" may not be accessed or modified by the worker thread

	pthread_t worker;                   ///< Worker thread ID
	bool started;                       ///< Worker thread ID is valid
	bool cancelled;                     ///< Task has been cancelled

	async_fn execute;                   ///< Worker main function
	async_fn dispatcher;                ///< Main thread result dispatcher
	async_fn destroy;                   ///< Destroys the whole object
};

struct async_manager
{
	pthread_mutex_t lock;               ///< Lock for the queues
	struct async *running;              ///< Queue of running jobs
	struct async *finished;             ///< Queue of completed/cancelled jobs

	// It's upon the user to call async_manager_dispatch() to retry the delayed.
	// It's somewhat questionable if this feature is of any use. Possibly if we
	// provide a means of actively limiting the amount of running async jobs.

	struct async *delayed;              ///< Resource exhaustion queue

	// We need the pipe in order to abort polling (instead of using EINTR)

	pthread_cond_t finished_cond;       ///< Signals that a task has finished
	int finished_pipe[2];               ///< Signals that a task has finished
};

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

static struct async
async_make (struct async_manager *manager)
{
	return (struct async) { .manager = manager };
}

/// Only allowed from the main thread once the job has been started but before
/// the results have been dispatched
static void
async_cancel (struct async *self)
{
	if (self->started)
		soft_assert (!pthread_cancel (self->worker));
	self->cancelled = true;
}

static void
async_cleanup (void *user_data)
{
	struct async *self = user_data;

	hard_assert (!pthread_mutex_lock (&self->manager->lock));
	LIST_UNLINK (self->manager->running, self);
	LIST_PREPEND (self->manager->finished, self);
	hard_assert (!pthread_mutex_unlock (&self->manager->lock));

	hard_assert (!pthread_cond_broadcast (&self->manager->finished_cond));
	hard_assert (write (self->manager->finished_pipe[1], "", 1) > 0);
}

static void *
async_routine (void *user_data)
{
	// Beware that we mustn't trigger any cancellation point before we set up
	// the cleanup handler, otherwise we'd need to disable it first
	struct async *self = user_data;
	pthread_cleanup_push (async_cleanup, self);

	self->execute (self);

	pthread_cleanup_pop (true);
	return NULL;
}

static bool
async_run (struct async *self)
{
	hard_assert (!pthread_mutex_lock (&self->manager->lock));
	LIST_PREPEND (self->manager->running, self);
	hard_assert (!pthread_mutex_unlock (&self->manager->lock));

	// Block all signals so that the new thread doesn't receive any (inherited)
	sigset_t all_blocked, old_blocked;
	hard_assert (!sigfillset (&all_blocked));
	hard_assert (!pthread_sigmask (SIG_SETMASK, &all_blocked, &old_blocked));

	int error = pthread_create (&self->worker, NULL, async_routine, self);

	// Now that we've created the thread, resume signal processing as usual
	hard_assert (!pthread_sigmask (SIG_SETMASK, &old_blocked, NULL));

	if (error)
	{
		hard_assert (error == EAGAIN);

		hard_assert (!pthread_mutex_lock (&self->manager->lock));
		LIST_UNLINK (self->manager->running, self);
		hard_assert (!pthread_mutex_unlock (&self->manager->lock));

		// FIXME: we probably want to have some kind of a limit on the queue
		LIST_PREPEND (self->manager->delayed, self);
		return false;
	}
	return (self->started = true);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

static struct async *
async_manager_dispatch_fetch (struct async_manager *self)
{
	// We don't want to hold the mutex for too long, mainly to prevent
	// a deadlock when trying to add an async job while dispatching another
	hard_assert (!pthread_mutex_lock (&self->lock));
	struct async *result;
	if ((result = self->finished))
		LIST_UNLINK (self->finished, result);
	hard_assert (!pthread_mutex_unlock (&self->lock));
	return result;
}

static void
async_manager_dispatch (struct async_manager *self)
{
	char dummy;
	while (read (self->finished_pipe[0], &dummy, 1) > 0)
		;  // Just emptying the signalling pipe

	struct async *iter;
	while ((iter = async_manager_dispatch_fetch (self)))
	{
		// The thread has finished execution already
		soft_assert (!pthread_join (iter->worker, NULL));

		if (iter->dispatcher && !iter->cancelled)
			iter->dispatcher (iter);
		if (iter->destroy)
			iter->destroy (iter);
	}

	LIST_FOR_EACH (struct async, iter, self->delayed)
	{
		LIST_UNLINK (self->delayed, iter);
		if (iter->cancelled)
		{
			if (iter->destroy)
				iter->destroy (iter);
		}
		else if (!async_run (iter))
			break;
	}
}

static void
async_manager_cancel_all (struct async_manager *self)
{
	hard_assert (!pthread_mutex_lock (&self->lock));

	// Cancel all running jobs
	LIST_FOR_EACH (struct async, iter, self->running)
		soft_assert (!pthread_cancel (iter->worker));

	// Wait until no jobs are running anymore (we need to release the lock
	// here so that worker threads can move their jobs to the finished queue)
	while (self->running)
		hard_assert (!pthread_cond_wait (&self->finished_cond, &self->lock));

	// Mark everything cancelled so that it's not actually dispatched
	LIST_FOR_EACH (struct async, iter, self->finished)
		iter->cancelled = true;
	LIST_FOR_EACH (struct async, iter, self->delayed)
		iter->cancelled = true;

	hard_assert (!pthread_mutex_unlock (&self->lock));
	async_manager_dispatch (self);
}

static struct async_manager
async_manager_make (void)
{
	struct async_manager self = {};
	hard_assert (!pthread_mutex_init (&self.lock, NULL));
	hard_assert (!pthread_cond_init (&self.finished_cond, NULL));

	hard_assert (!pipe (self.finished_pipe));
	hard_assert (set_blocking (self.finished_pipe[0], false));
	set_cloexec (self.finished_pipe[0]);
	set_cloexec (self.finished_pipe[1]);
	return self;
}

static void
async_manager_free (struct async_manager *self)
{
	async_manager_cancel_all (self);
	hard_assert (!pthread_cond_destroy (&self->finished_cond));
	hard_assert (!pthread_mutex_destroy (&self->lock));

	xclose (self->finished_pipe[0]);
	xclose (self->finished_pipe[1]);
}

#endif // LIBERTY_WANT_ASYNC

// --- Event loop --------------------------------------------------------------

#ifdef LIBERTY_WANT_POLLER

// Basically the poor man's GMainLoop/libev/libuv.  It might make some sense
// to instead use those tested and proven libraries but we don't need much
// and it's interesting to implement.

// We sacrifice some memory to allow for O(1) and O(log n) operations.

typedef void (*poller_fd_fn) (const struct pollfd *, void *);
typedef void (*poller_timer_fn) (void *);
typedef void (*poller_idle_fn) (void *);

#define POLLER_MIN_ALLOC 16

struct poller_timer
{
	struct poller_timers *timers;       ///< The timers part of our poller
	ssize_t index;                      ///< Where we are in the array, or -1

	int64_t when;                       ///< When is the timer to expire

	poller_timer_fn dispatcher;         ///< Event dispatcher
	void *user_data;                    ///< User data
};

struct poller_fd
{
	struct poller *poller;              ///< Our poller
	ssize_t index;                      ///< Where we are in the array, or -1

	int fd;                             ///< Our file descriptor
	short events;                       ///< The poll() events we registered for

	// Make triple sure that no forked child is keeping the FD,
	// otherwise we may access freed memory on Linux (poor epoll design)
	bool closed;                        ///< Whether fd has been closed already

	poller_fd_fn dispatcher;            ///< Event dispatcher
	void *user_data;                    ///< User data
};

struct poller_idle
{
	LIST_HEADER (struct poller_idle)
	struct poller *poller;              ///< Our poller

	bool active;                        ///< Whether we're on the list

	poller_idle_fn dispatcher;          ///< Event dispatcher
	void *user_data;                    ///< User data
};

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// The heap could definitely be made faster but we'll prefer simplicity
struct poller_timers
{
	struct poller_timer **heap;         ///< Min-heap of timers
	size_t len;                         ///< Number of scheduled timers
	size_t alloc;                       ///< Number of timers allocated
};

static struct poller_timers
poller_timers_make (void)
{
	struct poller_timers self;
	self.alloc = POLLER_MIN_ALLOC;
	self.len = 0;
	self.heap = xmalloc (self.alloc * sizeof *self.heap);
	return self;
}

static void
poller_timers_free (struct poller_timers *self)
{
	free (self->heap);
}

static int64_t
poller_timers_get_current_time (void)
{
#ifdef _POSIX_TIMERS
	struct timespec tp;
	hard_assert (clock_gettime (CLOCK_BEST, &tp) != -1);
	return (int64_t) tp.tv_sec * 1000 + (int64_t) tp.tv_nsec / 1000000;
#else
	struct timeval tp;
	gettimeofday (&tp, NULL);
	return (int64_t) tp.tv_sec * 1000 + (int64_t) tp.tv_usec / 1000;
#endif
}

static void
poller_timers_heapify_down (struct poller_timers *self, size_t index)
{
	typedef struct poller_timer *timer_t;
	timer_t *end = self->heap + self->len;

	while (true)
	{
		timer_t *parent = self->heap + index;
		timer_t *left   = self->heap + 2 * index + 1;
		timer_t *right  = self->heap + 2 * index + 2;

		timer_t *lowest = parent;
		if (left  < end && (*left) ->when < (*lowest)->when)
			lowest = left;
		if (right < end && (*right)->when < (*lowest)->when)
			lowest = right;
		if (parent == lowest)
			break;

		timer_t tmp = *parent;
		*parent = *lowest;
		*lowest = tmp;

		(*parent)->index = parent - self->heap;
		(*lowest)->index = lowest - self->heap;
		index = lowest - self->heap;
	}
}

static void
poller_timers_remove_at_index (struct poller_timers *self, size_t index)
{
	hard_assert (index < self->len);
	self->heap[index]->index = -1;
	if (index == --self->len)
		return;

	self->heap[index] = self->heap[self->len];
	self->heap[index]->index = index;
	poller_timers_heapify_down (self, index);
}

static void
poller_timers_dispatch (struct poller_timers *self)
{
	int64_t now = poller_timers_get_current_time ();
	while (self->len && self->heap[0]->when <= now)
	{
		struct poller_timer *timer = self->heap[0];
		poller_timers_remove_at_index (self, 0);
		timer->dispatcher (timer->user_data);
	}
}

static void
poller_timers_heapify_up (struct poller_timers *self, size_t index)
{
	while (index != 0)
	{
		size_t parent = (index - 1) / 2;
		if (self->heap[parent]->when <= self->heap[index]->when)
			break;

		struct poller_timer *tmp = self->heap[parent];
		self->heap[parent] = self->heap[index];
		self->heap[index] = tmp;

		self->heap[parent]->index = parent;
		self->heap[index] ->index = index;
		index = parent;
	}
}

static void
poller_timers_set (struct poller_timers *self, struct poller_timer *timer)
{
	hard_assert (timer->timers == self);
	if (timer->index != -1)
	{
		poller_timers_heapify_down (self, timer->index);
		poller_timers_heapify_up (self, timer->index);
		return;
	}

	if (self->len == self->alloc)
		self->heap = xreallocarray (self->heap,
			self->alloc <<= 1, sizeof *self->heap);
	self->heap[self->len] = timer;
	timer->index = self->len;
	poller_timers_heapify_up (self, self->len++);
}

static int
poller_timers_get_poll_timeout (const struct poller_timers *self)
{
	if (!self->len)
		return -1;

	int64_t timeout = self->heap[0]->when - poller_timers_get_current_time ();
	if (timeout <= 0)
		return 0;
	if (timeout > INT_MAX)
		return INT_MAX;
	return timeout;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

static void
poller_idle_dispatch (const struct poller_idle *list)
{
	const struct poller_idle *iter, *next;
	for (iter = list; iter; iter = next)
	{
		next = iter->next;
		iter->dispatcher (iter->user_data);
	}
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

struct poller_common
{
	struct poller_timers timers;        ///< Timeouts
	struct poller_idle *idle;           ///< Idle events
#ifdef LIBERTY_WANT_ASYNC
	struct async_manager async;         ///< Asynchronous jobs
	struct poller_fd async_event;       ///< Asynchronous jobs have finished
#endif // LIBERTY_WANT_ASYNC
};

static void poller_common_init (struct poller_common *, struct poller *);
static void poller_common_free (struct poller_common *);
static int poller_common_get_timeout (const struct poller_common *);
static void poller_common_dispatch (struct poller_common *);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

#ifdef __linux__
#include <sys/epoll.h>

struct poller
{
	int epoll_fd;                       ///< The epoll FD
	struct poller_fd **fds;             ///< Information associated with each FD
	int *dummy;                         ///< For poller_remove_from_dispatch()
	struct epoll_event *revents;        ///< Output array for epoll_wait()
	size_t len;                         ///< Number of polled descriptors
	size_t alloc;                       ///< Number of entries allocated
	struct poller_common common;        ///< Stuff common to all backends
	int revents_len;                    ///< Number of entries in `revents'
};

static void
poller_init (struct poller *self)
{
	self->epoll_fd = epoll_create (POLLER_MIN_ALLOC);
	hard_assert (self->epoll_fd != -1);
	set_cloexec (self->epoll_fd);

	self->len = 0;
	self->alloc = POLLER_MIN_ALLOC;
	self->fds = xcalloc (self->alloc, sizeof *self->fds);
	self->dummy = xcalloc (self->alloc, sizeof *self->dummy);
	self->revents = xcalloc (self->alloc, sizeof *self->revents);
	self->revents_len = 0;

	poller_common_init (&self->common, self);
}

static void
poller_free (struct poller *self)
{
	for (size_t i = 0; i < self->len; i++)
	{
		struct poller_fd *fd = self->fds[i];
		hard_assert (epoll_ctl (self->epoll_fd,
			EPOLL_CTL_DEL, fd->fd, (void *) "") != -1);
	}

	poller_common_free (&self->common);

	xclose (self->epoll_fd);
	free (self->fds);
	free (self->dummy);
	free (self->revents);
}

static void
poller_ensure_space (struct poller *self)
{
	if (self->len < self->alloc)
		return;

	self->alloc <<= 1;
	hard_assert (self->alloc != 0);

	self->revents = xreallocarray
		(self->revents, sizeof *self->revents, self->alloc);
	self->fds = xreallocarray
		(self->fds, sizeof *self->fds, self->alloc);
	self->dummy = xreallocarray
		(self->dummy, sizeof *self->dummy, self->alloc);
}

static short
poller_epoll_to_poll_events (uint32_t events)
{
	short result = 0;
	if (events & EPOLLIN)   result |= POLLIN;
	if (events & EPOLLOUT)  result |= POLLOUT;
	if (events & EPOLLERR)  result |= POLLERR;
	if (events & EPOLLHUP)  result |= POLLHUP;
	if (events & EPOLLPRI)  result |= POLLPRI;
	return result;
}

static uint32_t
poller_poll_to_epoll_events (short events)
{
	uint32_t result = 0;
	if (events & POLLIN)   result |= EPOLLIN;
	if (events & POLLOUT)  result |= EPOLLOUT;
	if (events & POLLERR)  result |= EPOLLERR;
	if (events & POLLHUP)  result |= EPOLLHUP;
	if (events & POLLPRI)  result |= EPOLLPRI;
	return result;
}

static void
poller_set (struct poller *self, struct poller_fd *fd)
{
	hard_assert (fd->poller == self);
	bool modifying = true;
	if (fd->index == -1)
	{
		poller_ensure_space (self);
		self->fds[fd->index = self->len++] = fd;
		modifying = false;
	}

	struct epoll_event event;
	event.events = poller_poll_to_epoll_events (fd->events);
	event.data.ptr = fd;
	hard_assert (epoll_ctl (self->epoll_fd,
		modifying ? EPOLL_CTL_MOD : EPOLL_CTL_ADD, fd->fd, &event) != -1);
}

#define poller_post_fork(self)

static int
poller_compare_fds (const void *ax, const void *bx)
{
	const struct epoll_event *ay = ax, *by = bx;
	struct poller_fd *a = ay->data.ptr, *b = by->data.ptr;
	return a->fd - b->fd;
}

static void
poller_remove_from_dispatch (struct poller *self, const struct poller_fd *fd)
{
	if (!self->revents_len)
		return;

	struct epoll_event key = { .data.ptr = (void *) fd }, *fd_event;
	if ((fd_event = bsearch (&key, self->revents,
		self->revents_len, sizeof *self->revents, poller_compare_fds)))
	{
		fd_event->events = -1;

		// Don't let any further bsearch()'s touch possibly freed memory
		int *dummy = self->dummy + (fd_event - self->revents);
		*dummy = fd->fd;
		fd_event->data.ptr =
			(uint8_t *) dummy - offsetof (struct poller_fd, fd);
	}
}

static void
poller_remove_at_index (struct poller *self, size_t index)
{
	hard_assert (index < self->len);
	struct poller_fd *fd = self->fds[index];
	fd->index = -1;

	poller_remove_from_dispatch (self, fd);
	if (!fd->closed)
		hard_assert (epoll_ctl (self->epoll_fd,
			EPOLL_CTL_DEL, fd->fd, (void *) "") != -1);

	if (index != --self->len)
	{
		self->fds[index] = self->fds[self->len];
		self->fds[index]->index = index;
	}
}

static void
poller_run (struct poller *self)
{
	// Not reentrant
	hard_assert (!self->revents_len);

	int n_fds;
	do
		n_fds = epoll_wait (self->epoll_fd, self->revents, self->alloc,
			poller_common_get_timeout (&self->common));
	while (n_fds == -1 && errno == EINTR);

	if (n_fds == -1)
		exit_fatal ("%s: %s", "epoll", strerror (errno));

	// Sort them by file descriptor number for binary search
	qsort (self->revents, n_fds, sizeof *self->revents, poller_compare_fds);
	self->revents_len = n_fds;

	poller_common_dispatch (&self->common);

	for (int i = 0; i < n_fds; i++)
	{
		struct epoll_event *revents = self->revents + i;
		if (revents->events == (uint32_t) -1)
			continue;

		struct poller_fd *fd = revents->data.ptr;
		hard_assert (fd->index != -1);

		struct pollfd pfd;
		pfd.fd = fd->fd;
		pfd.revents = poller_epoll_to_poll_events (revents->events);
		pfd.events = fd->events;

		fd->dispatcher (&pfd, fd->user_data);
	}

	self->revents_len = 0;
}

// Sort of similar to the epoll version.  Let's hope Darwin isn't broken,
// that'd mean reimplementing this in terms of select() just because of Crapple.
#elif defined (BSD) || defined (__APPLE__)

#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>

struct poller
{
	int kqueue_fd;                      ///< The kqueue FD
	struct poller_fd **fds;             ///< Information associated with each FD
	struct kevent *revents;             ///< Output array for kevent()
	size_t len;                         ///< Number of polled descriptors
	size_t alloc;                       ///< Number of entries allocated
	struct poller_common common;        ///< Stuff common to all backends
	int revents_len;                    ///< Number of entries in `revents'
};

static void
poller_init (struct poller *self)
{
	self->kqueue_fd = kqueue ();
	hard_assert (self->kqueue_fd != -1);
	set_cloexec (self->kqueue_fd);

	self->len = 0;
	self->alloc = POLLER_MIN_ALLOC;
	self->fds = xcalloc (self->alloc, sizeof *self->fds);
	self->revents = xcalloc (self->alloc, sizeof *self->revents);
	self->revents_len = 0;
	poller_common_init (&self->common, self);
}

static void
poller_free (struct poller *self)
{
	xclose (self->kqueue_fd);
	free (self->fds);
	free (self->revents);
	poller_common_free (&self->common);
}

static void
poller_ensure_space (struct poller *self)
{
	if (self->len < self->alloc)
		return;

	self->alloc <<= 1;
	hard_assert (self->alloc != 0);

	self->revents = xreallocarray
		(self->revents, sizeof *self->revents, self->alloc);
	self->fds = xreallocarray
		(self->fds, sizeof *self->fds, self->alloc);
}

static void
poller_set (struct poller *self, struct poller_fd *fd)
{
	hard_assert (fd->poller == self);
	if (fd->index == -1)
	{
		poller_ensure_space (self);
		self->fds[fd->index = self->len++] = fd;
	}

	// We have to watch for readability and writeability separately;
	// to simplify matters, we can just disable what we don't desire to receive
	struct kevent changes[2];
	EV_SET (&changes[0], fd->fd, EVFILT_READ,  EV_ADD, 0, 0, fd);
	EV_SET (&changes[1], fd->fd, EVFILT_WRITE, EV_ADD, 0, 0, fd);
	changes[0].flags |= (fd->events & POLLIN)  ? EV_ENABLE : EV_DISABLE;
	changes[1].flags |= (fd->events & POLLOUT) ? EV_ENABLE : EV_DISABLE;

	if (kevent (self->kqueue_fd,
		changes, N_ELEMENTS (changes), NULL, 0, NULL) == -1)
		exit_fatal ("%s: %s", "kevent", strerror (errno));
}

static void
poller_post_fork (struct poller *self)
{
	// The kqueue FD isn't preserved across forks, need to recreate it
	self->kqueue_fd = kqueue ();
	hard_assert (self->kqueue_fd != -1);
	set_cloexec (self->kqueue_fd);

	for (size_t i = 0; i < self->len; i++)
		poller_set (self, self->fds[i]);
}

static int
poller_compare_fds (const void *ax, const void *bx)
{
	const struct kevent *ay = ax, *by = bx;
	return (int) ay->ident - (int) by->ident;
}

static void
poller_dummify (struct kevent *fd_event)
{
	fd_event->flags = USHRT_MAX;
}

static void
poller_remove_from_dispatch (struct poller *self, const struct poller_fd *fd)
{
	if (!self->revents_len)
		return;

	struct kevent key = { .ident = fd->fd }, *fd_event;
	if (!(fd_event = bsearch (&key, self->revents,
		self->revents_len, sizeof *self->revents, poller_compare_fds)))
		return;

	// The FD may appear twice -- both for reading and writing
	int index = fd_event - self->revents;

	if (index > 0
	 && !poller_compare_fds (&key, fd_event - 1))
		poller_dummify (fd_event - 1);

	poller_dummify (fd_event);

	if (index < self->revents_len - 1
	 && !poller_compare_fds (&key, fd_event + 1))
		poller_dummify (fd_event + 1);
}

static void
poller_remove_at_index (struct poller *self, size_t index)
{
	hard_assert (index < self->len);
	struct poller_fd *fd = self->fds[index];
	fd->index = -1;

	poller_remove_from_dispatch (self, fd);

	if (index != --self->len)
	{
		self->fds[index] = self->fds[self->len];
		self->fds[index]->index = index;
	}

	if (fd->closed)
		return;

	struct kevent changes[2];
	EV_SET (&changes[0], fd->fd, EVFILT_READ,  EV_DELETE, 0, 0, fd);
	EV_SET (&changes[1], fd->fd, EVFILT_WRITE, EV_DELETE, 0, 0, fd);

	if (kevent (self->kqueue_fd,
		changes, N_ELEMENTS (changes), NULL, 0, NULL) == -1)
		exit_fatal ("%s: %s", "kevent", strerror (errno));
}

static struct timespec
poller_timeout_to_timespec (int ms)
{
	return (struct timespec)
	{
		.tv_sec = ms / 1000,
		.tv_nsec = (ms % 1000) * 1000 * 1000
	};
}

static short
poller_kqueue_to_poll_events (struct kevent *event)
{
	short result = 0;
	if (event->filter == EVFILT_READ)
	{
		result |= POLLIN;
		if ((event->flags & EV_EOF) && event->fflags)
			result |= POLLERR;
	}
	if (event->filter == EVFILT_WRITE)  result |= POLLOUT;
	if (event->flags & EV_EOF)          result |= POLLHUP;
	return result;
}

static void
poller_run (struct poller *self)
{
	// Not reentrant
	hard_assert (!self->revents_len);

	int n_fds;
	do
	{
		int timeout = poller_common_get_timeout (&self->common);
		struct timespec ts = poller_timeout_to_timespec (timeout);
		n_fds = kevent (self->kqueue_fd,
			NULL, 0, self->revents, self->len, timeout < 0 ? NULL : &ts);
	}
	while (n_fds == -1 && errno == EINTR);

	if (n_fds == -1)
		exit_fatal ("%s: %s", "kevent", strerror (errno));

	// Sort them by file descriptor number for binary search
	qsort (self->revents, n_fds, sizeof *self->revents, poller_compare_fds);
	self->revents_len = n_fds;

	poller_common_dispatch (&self->common);

	for (int i = 0; i < n_fds; i++)
	{
		struct kevent *event = self->revents + i;
		if (event->flags == USHRT_MAX)
			continue;

		struct poller_fd *fd = event->udata;
		hard_assert (fd->index != -1);

		struct pollfd pfd;
		pfd.fd = fd->fd;
		pfd.revents = poller_kqueue_to_poll_events (event);
		pfd.events = fd->events;

		// Read and write events are separate in the kqueue API -- merge them
		int sibling = 1;
		while (i + sibling < n_fds
			&& !poller_compare_fds (event, event + sibling))
			pfd.revents |= poller_kqueue_to_poll_events (event + sibling++);
		if ((pfd.revents & POLLHUP) && (pfd.revents & POLLOUT))
			pfd.revents &= ~POLLOUT;
		i += --sibling;

		fd->dispatcher (&pfd, fd->user_data);
	}

	self->revents_len = 0;
}

#else  // ! BSD

struct poller
{
	struct pollfd *fds;                 ///< Polled descriptors
	struct poller_fd **fds_data;        ///< Additional information for each FD
	size_t len;                         ///< Number of polled descriptors
	size_t alloc;                       ///< Number of entries allocated
	struct poller_common common;        ///< Stuff common to all backends
	int dispatch_next;                  ///< The next dispatched FD or -1
};

static void
poller_init (struct poller *self)
{
	self->alloc = POLLER_MIN_ALLOC;
	self->len = 0;
	self->fds = xcalloc (self->alloc, sizeof *self->fds);
	self->fds_data = xcalloc (self->alloc, sizeof *self->fds_data);
	poller_common_init (&self->common, self);
	self->dispatch_next = -1;
}

static void
poller_free (struct poller *self)
{
	free (self->fds);
	free (self->fds_data);
	poller_common_free (&self->common);
}

static void
poller_ensure_space (struct poller *self)
{
	if (self->len < self->alloc)
		return;

	self->alloc <<= 1;
	self->fds = xreallocarray (self->fds, sizeof *self->fds, self->alloc);
	self->fds_data = xreallocarray
		(self->fds_data, sizeof *self->fds_data, self->alloc);
}

static void
poller_set (struct poller *self, struct poller_fd *fd)
{
	hard_assert (fd->poller == self);
	if (fd->index == -1)
	{
		poller_ensure_space (self);
		self->fds_data[fd->index = self->len++] = fd;
	}

	struct pollfd *new_entry = self->fds + fd->index;
	memset (new_entry, 0, sizeof *new_entry);
	new_entry->fd = fd->fd;
	new_entry->events = fd->events;
}

#define poller_post_fork(self)

static void
poller_remove_at_index (struct poller *self, size_t index)
{
	hard_assert (index < self->len);
	struct poller_fd *fd = self->fds_data[index];
	fd->index = -1;

	if (index == --self->len)
		return;

	// Make sure that we don't disrupt the dispatch loop; kind of crude
	if ((int) index < self->dispatch_next)
	{
		memmove (self->fds + index, self->fds + index + 1,
			(self->len - index) * sizeof *self->fds);
		memmove (self->fds_data + index, self->fds_data + index + 1,
			(self->len - index) * sizeof *self->fds_data);
		for (size_t i = index; i < self->len; i++)
			self->fds_data[i]->index = i;

		self->dispatch_next--;
	}
	else
	{
		self->fds[index]      = self->fds     [self->len];
		self->fds_data[index] = self->fds_data[self->len];
		self->fds_data[index]->index = index;
	}
}

static void
poller_run (struct poller *self)
{
	// Not reentrant
	hard_assert (self->dispatch_next == -1);

	int result;
	do
		result = poll (self->fds, self->len,
			poller_common_get_timeout (&self->common));
	while (result == -1 && errno == EINTR);

	if (result == -1)
		exit_fatal ("%s: %s", "poll", strerror (errno));

	poller_common_dispatch (&self->common);

	for (int i = 0; i < (int) self->len; )
	{
		struct pollfd pfd = self->fds[i];
		struct poller_fd *fd = self->fds_data[i];
		self->dispatch_next = ++i;
		if (pfd.revents)
			fd->dispatcher (&pfd, fd->user_data);
		i = self->dispatch_next;
	}

	self->dispatch_next = -1;
}

#endif  // ! BSD

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

static struct poller_timer
poller_timer_make (struct poller *poller)
{
	return (struct poller_timer)
		{ .timers = &poller->common.timers, .index = -1, };
}

static void
poller_timer_set (struct poller_timer *self, int timeout_ms)
{
	self->when = poller_timers_get_current_time () + timeout_ms;
	poller_timers_set (self->timers, self);
}

static bool
poller_timer_is_active (const struct poller_timer *self)
{
	return self->index != -1;
}

static void
poller_timer_reset (struct poller_timer *self)
{
	if (self->index != -1)
		poller_timers_remove_at_index (self->timers, self->index);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

static struct poller_idle
poller_idle_make (struct poller *poller)
{
	return (struct poller_idle) { .poller = poller };
}

static void
poller_idle_set (struct poller_idle *self)
{
	if (self->active)
		return;

	LIST_PREPEND (self->poller->common.idle, self);
	self->active = true;
}

static void
poller_idle_reset (struct poller_idle *self)
{
	if (!self->active)
		return;

	LIST_UNLINK (self->poller->common.idle, self);
	self->prev = NULL;
	self->next = NULL;
	self->active = false;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

static struct poller_fd
poller_fd_make (struct poller *poller, int fd)
{
	return (struct poller_fd) { .poller = poller, .index = -1, .fd = fd };
}

static void
poller_fd_set (struct poller_fd *self, short events)
{
	self->events = events;
	poller_set (self->poller, self);
}

static void
poller_fd_reset (struct poller_fd *self)
{
	if (self->index != -1)
		poller_remove_at_index (self->poller, self->index);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

static void
poller_common_dummy_dispatcher (const struct pollfd *pfd, void *user_data)
{
	(void) pfd;
	(void) user_data;

	// The async manager will empty the pipe when we invoke dispatch
}

static void
poller_common_init (struct poller_common *self, struct poller *poller)
{
	self->timers = poller_timers_make ();
	self->idle = NULL;
#ifdef LIBERTY_WANT_ASYNC
	self->async = async_manager_make ();

	self->async_event = poller_fd_make (poller, self->async.finished_pipe[0]);
	poller_fd_set (&self->async_event, POLLIN);
	self->async_event.dispatcher = poller_common_dummy_dispatcher;
	self->async_event.user_data = self;
#else // ! LIBERTY_WANT_ASYNC
	(void) poller;
#endif // ! LIBERTY_WANT_ASYNC
}

static void
poller_common_free (struct poller_common *self)
{
	poller_timers_free (&self->timers);
#ifdef LIBERTY_WANT_ASYNC
	async_manager_free (&self->async);
#endif // LIBERTY_WANT_ASYNC
}

static int
poller_common_get_timeout (const struct poller_common *self)
{
	if (self->idle)
		return 0;

	int timeout = poller_timers_get_poll_timeout (&self->timers);
#ifdef LIBERTY_WANT_ASYNC
	// This is completely arbitrary, in general we have no idea when to retry,
	// however one second doesn't sound like a particularly bad number
	if (self->async.delayed)
		timeout = MIN (timeout, 1000);
#endif // LIBERTY_WANT_ASYNC
	return timeout;
}

static void
poller_common_dispatch (struct poller_common *self)
{
	poller_timers_dispatch (&self->timers);
	poller_idle_dispatch (self->idle);
#ifdef LIBERTY_WANT_ASYNC
	async_manager_dispatch (&self->async);
#endif // LIBERTY_WANT_ASYNC
}

#endif // LIBERTY_WANT_POLLER

// --- Asynchronous jobs -------------------------------------------------------

#ifdef LIBERTY_WANT_ASYNC

/// The callback takes ownership of the returned list
typedef void (*async_getaddrinfo_fn) (int, struct addrinfo *, void *);

struct async_getaddrinfo
{
	struct async async;                 ///< Parent object

	int gai_result;                     ///< Direct result from getaddrinfo()
	char *host;                         ///< gai() argument: host
	char *service;                      ///< gai() argument: service
	struct addrinfo hints;              ///< gai() argument: hints
	struct addrinfo *result;            ///< Resulting addresses from gai()

	async_getaddrinfo_fn dispatcher;    ///< Event dispatcher
	void *user_data;                    ///< User data
};

static void
async_getaddrinfo_execute (struct async *async)
{
	struct async_getaddrinfo *self = (struct async_getaddrinfo *) async;
	self->gai_result =
		getaddrinfo (self->host, self->service, &self->hints, &self->result);
}

static void
async_getaddrinfo_dispatch (struct async *async)
{
	struct async_getaddrinfo *self = (struct async_getaddrinfo *) async;
	self->dispatcher (self->gai_result, self->result, self->user_data);
	self->result = NULL;
}

static void
async_getaddrinfo_destroy (struct async *async)
{
	struct async_getaddrinfo *self = (struct async_getaddrinfo *) async;
	free (self->host);
	free (self->service);

	if (self->result)
		freeaddrinfo (self->result);

	free (self);
}

static struct async_getaddrinfo *
async_getaddrinfo (struct async_manager *manager,
	const char *host, const char *service, const struct addrinfo *hints)
{
	struct async_getaddrinfo *self = xcalloc (1, sizeof *self);
	self->async = async_make (manager);

	if (host)     self->host = xstrdup (host);
	if (service)  self->service = xstrdup (service);
	if (hints)    memcpy (&self->hints, hints, sizeof *hints);

	self->async.execute    = async_getaddrinfo_execute;
	self->async.dispatcher = async_getaddrinfo_dispatch;
	self->async.destroy    = async_getaddrinfo_destroy;

	async_run (&self->async);
	return self;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

typedef void (*async_getnameinfo_fn) (int, char *, char *, void *);

struct async_getnameinfo
{
	struct async async;                 ///< Parent object

	int gni_result;                     ///< Direct result from getnameinfo()
	char host[NI_MAXHOST];              ///< gni() result: host name
	char service[NI_MAXSERV];           ///< gni() result: service name

	struct sockaddr *address;           ///< gni() argument: address
	socklen_t address_len;              ///< Size of the address
	int flags;                          ///< gni() argument: flags

	async_getnameinfo_fn dispatcher;    ///< Event dispatcher
	void *user_data;                    ///< User data
};

static void
async_getnameinfo_execute (struct async *async)
{
	struct async_getnameinfo *self = (struct async_getnameinfo *) async;
	self->gni_result = getnameinfo (self->address, self->address_len,
		self->host, sizeof self->host,
		self->service, sizeof self->service, self->flags);
}

static void
async_getnameinfo_dispatch (struct async *async)
{
	struct async_getnameinfo *self = (struct async_getnameinfo *) async;
	self->dispatcher (self->gni_result, self->host, self->service,
		self->user_data);
}

static void
async_getnameinfo_destroy (struct async *async)
{
	struct async_getnameinfo *self = (struct async_getnameinfo *) async;
	free (self->address);
	free (self);
}

static struct async_getnameinfo *
async_getnameinfo (struct async_manager *manager,
	const struct sockaddr *sa, socklen_t sa_len, int flags)
{
	struct async_getnameinfo *self = xcalloc (1, sizeof *self);
	self->async = async_make (manager);

	self->address = memcpy (xmalloc (sa_len), sa, sa_len);
	self->address_len = sa_len;
	self->flags = flags;

	self->async.execute    = async_getnameinfo_execute;
	self->async.dispatcher = async_getnameinfo_dispatch;
	self->async.destroy    = async_getnameinfo_destroy;

	async_run (&self->async);
	return self;
}

#endif // LIBERTY_WANT_ASYNC

// --- libuv-style write adaptor -----------------------------------------------

// Makes it possible to use iovec to write multiple data chunks at once.

struct write_req
{
	LIST_HEADER (struct write_req)
	struct iovec data;                  ///< Data to be written
};

struct write_queue
{
	struct write_req *head;             ///< The head of the queue
	struct write_req *tail;             ///< The tail of the queue
	size_t head_offset;                 ///< Offset into the head
	size_t len;
};

static struct write_queue
write_queue_make (void)
{
	return (struct write_queue) {};
}

static void
write_queue_free (struct write_queue *self)
{
	LIST_FOR_EACH (struct write_req, iter, self->head)
	{
		free (iter->data.iov_base);
		free (iter);
	}
}

static void
write_queue_add (struct write_queue *self, struct write_req *req)
{
	LIST_APPEND_WITH_TAIL (self->head, self->tail, req);
	self->len++;
}

static void
write_queue_processed (struct write_queue *self, size_t len)
{
	while (self->head
		&& self->head_offset + len >= self->head->data.iov_len)
	{
		struct write_req *head = self->head;
		len -= (head->data.iov_len - self->head_offset);
		self->head_offset = 0;

		LIST_UNLINK_WITH_TAIL (self->head, self->tail, head);
		self->len--;
		free (head->data.iov_base);
		free (head);
	}
	self->head_offset += len;
}

static bool
write_queue_is_empty (const struct write_queue *self)
{
	return self->head == NULL;
}

// --- Message reader ----------------------------------------------------------

struct msg_reader
{
	struct str buf;                     ///< Input buffer
	uint64_t offset;                    ///< Current offset in the buffer
};

static struct msg_reader
msg_reader_make (void)
{
	return (struct msg_reader) { .buf = str_make (), .offset = 0 };
}

static void
msg_reader_free (struct msg_reader *self)
{
	str_free (&self->buf);
}

static void
msg_reader_compact (struct msg_reader *self)
{
	str_remove_slice (&self->buf, 0, self->offset);
	self->offset = 0;
}

static void
msg_reader_feed (struct msg_reader *self, const void *data, size_t len)
{
	// TODO: have some mechanism to prevent flooding
	msg_reader_compact (self);
	str_append_data (&self->buf, data, len);
}

static void *
msg_reader_get (struct msg_reader *self, size_t *len)
{
	// Try to read in the length of the message
	if (self->offset + sizeof (uint64_t) > self->buf.len)
		return NULL;

	uint8_t *x = (uint8_t *) self->buf.str + self->offset;
	uint64_t msg_len = peek_u64be (x);
	if (msg_len < sizeof msg_len)
	{
		// The message is shorter than its header
		// TODO: have some mechanism to report errors
		return NULL;
	}

	if (self->offset + msg_len < self->offset)
	{
		// Trying to read an insane amount of data but whatever
		msg_reader_compact (self);
		return NULL;
	}

	// Check if we've got the full message in the buffer and return it
	if (self->offset + msg_len > self->buf.len)
		return NULL;

	// We have to subtract the header from the reported length
	void *data = self->buf.str + self->offset + sizeof msg_len;
	self->offset += msg_len;
	*len = msg_len - sizeof msg_len;
	return data;
}

// --- Message unpacker --------------------------------------------------------

struct msg_unpacker
{
	const char *data;
	size_t offset;
	size_t len;
};

static struct msg_unpacker
msg_unpacker_make (const void *data, size_t len)
{
	return (struct msg_unpacker) { .data = data, .len = len, .offset = 0 };
}

static size_t
msg_unpacker_get_available (const struct msg_unpacker *self)
{
	return self->len - self->offset;
}

#define UNPACKER_INT_BEGIN                                                     \
	if (self->len - self->offset < sizeof *value)                              \
		return false;                                                          \
	uint8_t *x = (uint8_t *) self->data + self->offset;                        \
	self->offset += sizeof *value;

static bool
msg_unpacker_u8 (struct msg_unpacker *self, uint8_t *value)
{
	UNPACKER_INT_BEGIN
	*value = x[0];
	return true;
}

static bool
msg_unpacker_u16 (struct msg_unpacker *self, uint16_t *value)
{
	UNPACKER_INT_BEGIN
	*value = peek_u16be (x);
	return true;
}

static bool
msg_unpacker_u32 (struct msg_unpacker *self, uint32_t *value)
{
	UNPACKER_INT_BEGIN
	*value = peek_u32be (x);
	return true;
}

static bool
msg_unpacker_u64 (struct msg_unpacker *self, uint64_t *value)
{
	UNPACKER_INT_BEGIN
	*value = peek_u64be (x);
	return true;
}

#define msg_unpacker_i8(self, value)                                           \
	msg_unpacker_u8  ((self), (uint8_t *) (value))
#define msg_unpacker_i16(self, value)                                          \
	msg_unpacker_u16 ((self), (uint16_t *) (value))
#define msg_unpacker_i32(self, value)                                          \
	msg_unpacker_u32 ((self), (uint32_t *) (value))
#define msg_unpacker_i64(self, value)                                          \
	msg_unpacker_u64 ((self), (uint64_t *) (value))

#undef UNPACKER_INT_BEGIN

// --- Message packer and writer -----------------------------------------------

// Use str_pack_*() or other methods to append to the internal buffer, then
// flush it to get a nice frame.  Handy for iovec.

struct msg_writer
{
	struct str buf;                     ///< Holds the message data
};

static struct msg_writer
msg_writer_make (void)
{
	struct msg_writer self = { .buf = str_make () };
	// Placeholder for message length
	str_append_data (&self.buf, "\x00\x00\x00\x00" "\x00\x00\x00\x00", 8);
	return self;
}

static void *
msg_writer_flush (struct msg_writer *self, size_t *len)
{
	// Update the message length
	uint64_t x = self->buf.len;
	uint8_t tmp[8] =
		{ x >> 56, x >> 48, x >> 40, x >> 32, x >> 24, x >> 16, x >> 8, x };
	memcpy (self->buf.str, tmp, sizeof tmp);

	*len = x;
	return str_steal (&self->buf);
}

// --- ASCII -------------------------------------------------------------------

#define TRIVIAL_STRXFRM(name, fn)                                              \
	static size_t                                                              \
	name (char *dest, const char *src, size_t n)                               \
	{                                                                          \
		size_t len = strlen (src);                                             \
		while (n-- && (*dest++ = (fn) (*src++)))                               \
			;                                                                  \
		return len;                                                            \
	}

static int
tolower_ascii (int c)
{
	return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c;
}

static int
toupper_ascii (int c)
{
	return c >= 'a' && c <= 'z' ? c - ('a' - 'A') : c;
}

TRIVIAL_STRXFRM (tolower_ascii_strxfrm, tolower_ascii)
TRIVIAL_STRXFRM (toupper_ascii_strxfrm, toupper_ascii)

static int
strcasecmp_ascii (const char *a, const char *b)
{
	int x;
	while (*a || *b)
		if ((x = tolower_ascii (*(const unsigned char *) a++)
			- tolower_ascii (*(const unsigned char *) b++)))
			return x;
	return 0;
}

static int
strncasecmp_ascii (const char *a, const char *b, size_t n)
{
	int x;
	while (n-- && (*a || *b))
		if ((x = tolower_ascii (*(const unsigned char *) a++)
			- tolower_ascii (*(const unsigned char *) b++)))
			return x;
	return 0;
}

static bool
iscntrl_ascii (int c)
{
	return (c >= 0 && c < 32) || c == 0x7f;
}

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 bool
isspace_ascii (int c)
{
	return c == ' '  || c == '\f' || c == '\n'
		|| c == '\r' || c == '\t' || c == '\v';
}

// --- UTF-8 -------------------------------------------------------------------

/// Return the value of the UTF-8 character at `*s` and advance the pointer
/// to the next one.  Returns -2 if there is only a partial but possibly valid
/// character sequence, or -1 on other errors.  Either way, `*s` is untouched.
static int32_t
utf8_decode (const char **s, size_t len)
{
	// End of string, we go no further
	if (!len)
		return -1;

	// Find out how long the sequence is (0 for ASCII)
	unsigned mask = 0x80;
	unsigned sequence_len = 0;

	const uint8_t *p = (const uint8_t *) *s, *end = p + len;
	while ((*p & mask) == mask)
	{
		// Invalid start of sequence
		if (mask == 0xFE)
			return -1;

		mask |= mask >> 1;
		sequence_len++;
	}

	// In the middle of a character
	// or an overlong sequence (subset, possibly MUTF-8, not supported)
	if (sequence_len == 1 || *p == 0xC0 || *p == 0xC1)
		return -1;

	// Check the rest of the sequence
	uint32_t cp = *p++ & ~mask;
	while (sequence_len && --sequence_len)
	{
		if (p == end)
			return -2;
		if ((*p & 0xC0) != 0x80)
			return -1;
		cp = cp << 6 | (*p++ & 0x3F);
	}
	*s = (const char *) p;
	return cp;
}

static inline bool
utf8_validate_cp (int32_t cp)
{
	// RFC 3629, CESU-8 not allowed
	return cp >= 0 && cp <= 0x10FFFF && (cp < 0xD800 || cp > 0xDFFF);
}

/// Very rough UTF-8 validation, just makes sure codepoints can be iterated
static bool
utf8_validate (const char *s, size_t len)
{
	const char *end = s + len;
	int32_t codepoint;
	while ((codepoint = utf8_decode (&s, end - s)) >= 0
		&& utf8_validate_cp (codepoint))
		;
	return s == end;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

struct utf8_iter
{
	const char *s;                      ///< String iterator
	size_t len;                         ///< How many bytes remain
};

static struct utf8_iter
utf8_iter_make (const char *s)
{
	return (struct utf8_iter) { .s = s, .len = strlen (s) };
}

static int32_t
utf8_iter_next (struct utf8_iter *self, size_t *len)
{
	if (!self->len)
		return -1;

	const char *old = self->s;
	int32_t codepoint = utf8_decode (&self->s, self->len);
	if (!soft_assert (codepoint >= 0))
	{
		// Invalid UTF-8
		self->len = 0;
		return codepoint;
	}

	size_t advance = self->s - old;
	self->len -= advance;
	if (len) *len = advance;
	return codepoint;
}

// --- Base 64 -----------------------------------------------------------------

static uint8_t g_base64_table[256] =
{
	64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 64, 64, 64, 64, 64,
	64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 64, 64, 64, 64, 64,
	64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 62, 64, 64, 64, 63,
	52, 53, 54, 55, 56, 57, 58, 59,  60, 61, 64, 64, 64,  0, 64, 64,
	64,  0,  1,  2,  3,  4,  5,  6,   7,  8,  9, 10, 11, 12, 13, 14,
	15, 16, 17, 18, 19, 20, 21, 22,  23, 24, 25, 64, 64, 64, 64, 64,
	64, 26, 27, 28, 29, 30, 31, 32,  33, 34, 35, 36, 37, 38, 39, 40,
	41, 42, 43, 44, 45, 46, 47, 48,  49, 50, 51, 64, 64, 64, 64, 64,

	64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 64, 64, 64, 64, 64,
	64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 64, 64, 64, 64, 64,
	64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 64, 64, 64, 64, 64,
	64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 64, 64, 64, 64, 64,
	64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 64, 64, 64, 64, 64,
	64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 64, 64, 64, 64, 64,
	64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 64, 64, 64, 64, 64,
	64, 64, 64, 64, 64, 64, 64, 64,  64, 64, 64, 64, 64, 64, 64, 64,
};

static inline bool
base64_decode_group (const char **s, bool ignore_ws, struct str *output)
{
	uint8_t input[4];
	size_t loaded = 0;
	for (; loaded < 4; (*s)++)
	{
		if (!**s)
			return loaded == 0;
		if (!ignore_ws || !isspace_ascii (**s))
			input[loaded++] = **s;
	}

	size_t len = 3;
	if (input[0] == '=' || input[1] == '=')
		return false;
	if (input[2] == '=' && input[3] != '=')
		return false;
	if (input[2] == '=')
		len--;
	if (input[3] == '=')
		len--;

	uint8_t a = g_base64_table[input[0]];
	uint8_t b = g_base64_table[input[1]];
	uint8_t c = g_base64_table[input[2]];
	uint8_t d = g_base64_table[input[3]];

	if (((a | b) | (c | d)) & 0x40)
		return false;

	uint32_t block = a << 18 | b << 12 | c << 6 | d;
	switch (len)
	{
	case 1:
		str_append_c (output, block >> 16);
		break;
	case 2:
		str_append_c (output, block >> 16);
		str_append_c (output, block >> 8);
		break;
	case 3:
		str_append_c (output, block >> 16);
		str_append_c (output, block >> 8);
		str_append_c (output, block);
	}
	return true;
}

static bool
base64_decode (const char *s, bool ignore_ws, struct str *output)
{
	while (*s)
		if (!base64_decode_group (&s, ignore_ws, output))
			return false;
	return true;
}

static void
base64_encode (const void *data, size_t len, struct str *output)
{
	const char *alphabet =
		"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

	const uint8_t *p = data;
	size_t n_groups = len / 3;
	size_t tail = len - n_groups * 3;
	uint32_t group;

	for (; n_groups--; p += 3)
	{
		group = p[0] << 16 | p[1] << 8 | p[2];
		str_append_c (output, alphabet[(group >> 18) & 63]);
		str_append_c (output, alphabet[(group >> 12) & 63]);
		str_append_c (output, alphabet[(group >>  6) & 63]);
		str_append_c (output, alphabet[ group        & 63]);
	}

	switch (tail)
	{
	case 2:
		group = p[0] << 16 | p[1] << 8;
		str_append_c (output, alphabet[(group >> 18) & 63]);
		str_append_c (output, alphabet[(group >> 12) & 63]);
		str_append_c (output, alphabet[(group >>  6) & 63]);
		str_append_c (output, '=');
		break;
	case 1:
		group = p[0] << 16;
		str_append_c (output, alphabet[(group >> 18) & 63]);
		str_append_c (output, alphabet[(group >> 12) & 63]);
		str_append_c (output, '=');
		str_append_c (output, '=');
	default:
		break;
	}
}

// --- Utilities ---------------------------------------------------------------

static void
cstr_set (char **s, char *new)
{
	free (*s);
	*s = new;
}

static void
cstr_split (const char *s, const char *delimiters, bool ignore_empty,
	struct strv *out)
{
	const char *begin = s, *end;
	while ((end = strpbrk (begin, delimiters)))
	{
		if (!ignore_empty || begin != end)
			strv_append_owned (out, xstrndup (begin, end - begin));
		begin = ++end;
	}
	if (!ignore_empty || *begin)
		strv_append (out, begin);
}

static char *
cstr_strip_in_place (char *s, const char *stripped_chars)
{
	char *end = s + strlen (s);
	while (end > s && strchr (stripped_chars, end[-1]))
		*--end = '\0';

	char *start = s + strspn (s, stripped_chars);
	if (start > s)
		memmove (s, start, end - start + 1);
	return s;
}

static void
cstr_transform (char *s, int (*xform) (int c))
{
	for (; *s; s++)
		*s = xform (*s);
}

static char *
cstr_cut_until (const char *s, const char *alphabet)
{
	return xstrndup (s, strcspn (s, alphabet));
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

static char *
strv_join (const struct strv *v, const char *delimiter)
{
	if (!v->len)
		return xstrdup ("");

	struct str result = str_make ();
	str_append (&result, v->vector[0]);
	for (size_t i = 1; i < v->len; i++)
		str_append_printf (&result, "%s%s", delimiter, v->vector[i]);
	return str_steal (&result);
}

static char *xstrdup_printf (const char *, ...) ATTRIBUTE_PRINTF (1, 2);

static char *
xstrdup_printf (const char *format, ...)
{
	va_list ap;
	struct str tmp = str_make ();
	va_start (ap, format);
	str_append_vprintf (&tmp, format, ap);
	va_end (ap);
	return str_steal (&tmp);
}

static char *
iconv_xstrdup (iconv_t conv, char *in, size_t in_len, size_t *out_len)
{
	char *buf, *buf_ptr;
	size_t out_left, buf_alloc;

	buf = buf_ptr = xmalloc (out_left = buf_alloc = 64);

	char *in_ptr = in;
	if (in_len == (size_t) -1)
		// XXX: out_len will be one character longer than the string!
		in_len = strlen (in) + 1;

	while (iconv (conv, (char **) &in_ptr, &in_len,
		(char **) &buf_ptr, &out_left) == (size_t) -1)
	{
		if (errno != E2BIG)
		{
			free (buf);
			return NULL;
		}
		out_left += buf_alloc;
		char *new_buf = xrealloc (buf, buf_alloc <<= 1);
		buf_ptr += new_buf - buf;
		buf = new_buf;
	}
	if (out_len)
		*out_len = buf_alloc - out_left;
	return buf;
}

static bool
set_boolean_if_valid (bool *out, const char *s)
{
	if      (!strcasecmp (s, "yes"))    *out = true;
	else if (!strcasecmp (s, "no"))     *out = false;
	else if (!strcasecmp (s, "on"))     *out = true;
	else if (!strcasecmp (s, "off"))    *out = false;
	else if (!strcasecmp (s, "true"))   *out = true;
	else if (!strcasecmp (s, "false"))  *out = false;
	else return false;

	return true;
}

static bool
xstrtoul (unsigned long *out, const char *s, int base)
{
	char *end;
	errno = 0;
	*out = strtoul (s, &end, base);
	return errno == 0 && !*end && end != s;
}

static bool
read_line (FILE *fp, struct str *line)
{
	str_reset (line);

	int c;
	while ((c = fgetc (fp)) != '\n')
	{
		if (c == EOF)
			return line->len != 0;
		if (c != '\r')
			str_append_c (line, c);
	}
	return true;
}

static char *
format_host_port_pair (const char *host, const char *port)
{
	// For when binding to the NULL address; would an asterisk be better?
	if (!host)
		host = "";

	// IPv6 addresses mess with the "colon notation"; let's go with RFC 2732
	if (strchr (host, ':'))
		return xstrdup_printf ("[%s]:%s", host, port);
	return xstrdup_printf ("%s:%s", host, port);
}

// --- File system -------------------------------------------------------------

static int
lock_pid_file (const char *path, struct error **e)
{
	// When using XDG_RUNTIME_DIR, the file needs to either have its
	// access time bumped every 6 hours, or have the sticky bit set
	int fd = open (path, O_RDWR | O_CREAT,
		S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH /* 644 */ | S_ISVTX /* sticky */);
	if (fd < 0)
	{
		error_set (e, "can't open `%s': %s", path, strerror (errno));
		return -1;
	}

	set_cloexec (fd);

	struct flock lock =
	{
		.l_type = F_WRLCK,
		.l_start = 0,
		.l_whence = SEEK_SET,
		.l_len = 0,
	};
	if (fcntl (fd, F_SETLK, &lock))
	{
		error_set (e, "can't lock `%s': %s", path, strerror (errno));
		xclose (fd);
		return -1;
	}

	struct str pid = str_make ();
	str_append_printf (&pid, "%ld", (long) getpid ());

	if (ftruncate (fd, 0)
	 || write (fd, pid.str, pid.len) != (ssize_t) pid.len)
	{
		error_set (e, "can't write to `%s': %s", path, strerror (errno));
		xclose (fd);
		return -1;
	}
	str_free (&pid);

	// Intentionally not closing the file descriptor; it must stay alive
	// for the entire life of the application
	return fd;
}

static bool
ensure_directory_existence (const char *path, struct error **e)
{
	struct stat st;

	if (stat (path, &st))
	{
		if (mkdir (path, S_IRWXU | S_IRWXG | S_IRWXO))
		{
			return error_set (e, "cannot create directory `%s': %s",
				path, strerror (errno));
		}
	}
	else if (!S_ISDIR (st.st_mode))
	{
		return error_set (e, "cannot create directory `%s': %s",
			path, "file exists but is not a directory");
	}
	return true;
}

static bool
mkdir_with_parents (char *path, struct error **e)
{
	char *p = path;

	// XXX: This is prone to the TOCTTOU problem.  The solution would be to
	//   rewrite the function using the {mkdir,fstat}at() functions from
	//   POSIX.1-2008, ideally returning a file descriptor to the open
	//   directory, with the current code as a fallback.  Or to use chdir().
	while ((p = strchr (p + 1, '/')))
	{
		*p = '\0';
		bool success = ensure_directory_existence (path, e);
		*p = '/';

		if (!success)
			return false;
	}

	return ensure_directory_existence (path, e);
}

static bool
str_append_env_path (struct str *output, const char *var, bool only_absolute)
{
	const char *value = getenv (var);

	if (!value || (only_absolute && *value != '/'))
		return false;

	str_append (output, value);
	return true;
}

static void
get_xdg_home_dir (struct str *output, const char *var, const char *def)
{
	str_reset (output);
	if (!str_append_env_path (output, var, true))
	{
		str_append_env_path (output, "HOME", false);
		str_append_c (output, '/');
		str_append (output, def);
	}
}

static char *
resolve_relative_filename_generic
	(struct strv *paths, const char *tail, const char *filename)
{
	for (unsigned i = 0; i < paths->len; i++)
	{
		// As per XDG spec, relative paths are ignored
		if (*paths->vector[i] != '/')
			continue;

		char *file = xstrdup_printf
			("%s/%s%s", paths->vector[i], tail, filename);

		struct stat st;
		if (!stat (file, &st))
			return file;
		free (file);
	}
	return NULL;
}

static void
get_xdg_config_dirs (struct strv *out)
{
	struct str config_home = str_make ();
	get_xdg_home_dir (&config_home, "XDG_CONFIG_HOME", ".config");
	strv_append (out, config_home.str);
	str_free (&config_home);

	const char *xdg_config_dirs;
	if (!(xdg_config_dirs = getenv ("XDG_CONFIG_DIRS")) || !*xdg_config_dirs)
		xdg_config_dirs = "/etc/xdg";
	cstr_split (xdg_config_dirs, ":", true, out);
}

static char *
resolve_relative_config_filename (const char *filename)
{
	struct strv paths = strv_make ();
	get_xdg_config_dirs (&paths);
	char *result = resolve_relative_filename_generic
		(&paths, PROGRAM_NAME "/", filename);
	strv_free (&paths);
	return result;
}

static void
get_xdg_data_dirs (struct strv *out)
{
	struct str data_home = str_make ();
	get_xdg_home_dir (&data_home, "XDG_DATA_HOME", ".local/share");
	strv_append (out, data_home.str);
	str_free (&data_home);

	const char *xdg_data_dirs;
	if (!(xdg_data_dirs = getenv ("XDG_DATA_DIRS")) || !*xdg_data_dirs)
		xdg_data_dirs = "/usr/local/share/:/usr/share/";
	cstr_split (xdg_data_dirs, ":", true, out);
}

static char *
resolve_relative_data_filename (const char *filename)
{
	struct strv paths = strv_make ();
	get_xdg_data_dirs (&paths);
	char *result = resolve_relative_filename_generic
		(&paths, PROGRAM_NAME "/", filename);
	strv_free (&paths);
	return result;
}

static char *
resolve_relative_runtime_filename_finish (struct str path)
{
	// Try to create the file's ancestors;
	// typically the user will want to immediately create a file in there
	const char *last_slash = strrchr (path.str, '/');
	if (last_slash && last_slash != path.str)
	{
		char *copy = xstrndup (path.str, last_slash - path.str);
		(void) mkdir_with_parents (copy, NULL);
		free (copy);
	}
	return str_steal (&path);
}

static char *
resolve_relative_runtime_filename (const char *filename)
{
	struct str path = str_make ();
	const char *runtime_dir = getenv ("XDG_RUNTIME_DIR");
	if (runtime_dir && *runtime_dir == '/')
		str_append (&path, runtime_dir);
	else
		get_xdg_home_dir (&path, "XDG_DATA_HOME", ".local/share");

	str_append_printf (&path, "/%s/%s", PROGRAM_NAME, filename);
	return resolve_relative_runtime_filename_finish (path);
}

/// This differs from resolve_relative_runtime_filename() in that we expect
/// the filename to be something like a pattern for mkstemp(), so the resulting
/// path can reside in a system-wide directory with no risk of a conflict.
/// However, we have to take care about permissions.  Do we even need this?
static char *
resolve_relative_runtime_template (const char *template)
{
	struct str path = str_make ();
	const char *runtime_dir = getenv ("XDG_RUNTIME_DIR");
	const char *tmpdir = getenv ("TMPDIR");
	if (runtime_dir && *runtime_dir == '/')
		str_append_printf (&path, "%s/%s", runtime_dir, PROGRAM_NAME);
	else if (tmpdir && *tmpdir == '/')
		str_append_printf (&path, "%s/%s.%d", tmpdir, PROGRAM_NAME, geteuid ());
	else
		str_append_printf (&path, "/tmp/%s.%d", PROGRAM_NAME, geteuid ());

	str_append_printf (&path, "/%s", template);
	return resolve_relative_runtime_filename_finish (path);
}

static char *
try_expand_tilde (const char *filename)
{
	size_t until_slash = strcspn (filename, "/");
	if (!until_slash)
	{
		struct str expanded = str_make ();
		str_append_env_path (&expanded, "HOME", false);
		str_append (&expanded, filename);
		return str_steal (&expanded);
	}

	int buf_len = sysconf (_SC_GETPW_R_SIZE_MAX);
	if (buf_len < 0)
		buf_len = 1024;
	struct passwd pwd, *success = NULL;

	char *user = xstrndup (filename, until_slash);
	char *buf = xmalloc (buf_len);
	while (getpwnam_r (user, &pwd, buf, buf_len, &success) == ERANGE)
		buf = xrealloc (buf, buf_len <<= 1);
	free (user);

	char *result = NULL;
	if (success)
		result = xstrdup_printf ("%s%s", pwd.pw_dir, filename + until_slash);
	free (buf);
	return result;
}

static char *
resolve_filename (const char *filename, char *(*relative_cb) (const char *))
{
	// Absolute path is absolute
	if (*filename == '/')
		return xstrdup (filename);

	// We don't want to use wordexp() for this as it may execute /bin/sh
	if (*filename == '~')
	{
		// Paths to home directories ought to be absolute
		char *expanded = try_expand_tilde (filename + 1);
		if (expanded)
			return expanded;
		print_debug ("failed to expand the home directory in `%s'", filename);
	}
	return relative_cb (filename);
}

// --- OpenSSL -----------------------------------------------------------------

#ifdef LIBERTY_WANT_SSL

#define XSSL_ERROR_TRY_AGAIN INT_MAX

/// A small wrapper around SSL_get_error() to simplify further handling
static int
xssl_get_error (SSL *ssl, int result, const char **error_info)
{
	int error = SSL_get_error (ssl, result);
	switch (error)
	{
	case SSL_ERROR_NONE:
	case SSL_ERROR_ZERO_RETURN:
	case SSL_ERROR_WANT_READ:
	case SSL_ERROR_WANT_WRITE:
		return error;
	case SSL_ERROR_SYSCALL:
		if ((error = ERR_get_error ()))
			*error_info = ERR_reason_error_string (error);
		else if (result == 0)
			// An EOF that's not according to the protocol is still an EOF
			return SSL_ERROR_ZERO_RETURN;
		else
		{
			if (errno == EINTR)
				return XSSL_ERROR_TRY_AGAIN;
			*error_info = strerror (errno);
		}
		return SSL_ERROR_SSL;
	default:
		if ((error = ERR_get_error ()))
			*error_info = ERR_reason_error_string (error);
		else
			*error_info = "unknown error";
		return SSL_ERROR_SSL;
	}
}

#endif  // LIBERTY_WANT_SSL

// --- Regular expressions -----------------------------------------------------

static regex_t *
regex_compile (const char *regex, int flags, struct error **e)
{
	regex_t *re = xmalloc (sizeof *re);
	int err = regcomp (re, regex, flags);
	if (!err)
		return re;

	char buf[regerror (err, re, NULL, 0)];
	regerror (err, re, buf, sizeof buf);

	free (re);
	error_set (e, "%s: %s", "failed to compile regular expression", buf);
	return NULL;
}

static void
regex_free (void *regex)
{
	regfree (regex);
	free (regex);
}

// The cost of hashing a string is likely to be significantly smaller than that
// of compiling the whole regular expression anew, so here is a simple cache.
// Adding basic support for subgroups is easy: check `re_nsub' and output into
// a `struct strv' (if all we want is the substrings).

static struct str_map
regex_cache_make (void)
{
	return str_map_make (regex_free);
}

static bool
regex_cache_match (struct str_map *cache, const char *regex, int flags,
	const char *s, struct error **e)
{
	regex_t *re = str_map_find (cache, regex);
	if (!re)
	{
		re = regex_compile (regex, flags, e);
		if (!re)
			return false;
		str_map_set (cache, regex, re);
	}
	return regexec (re, s, 0, NULL, 0) != REG_NOMATCH;
}

// --- Simple file I/O ---------------------------------------------------------

static bool
read_file (const char *filename, struct str *output, struct error **e)
{
	FILE *fp = fopen (filename, "rb");
	if (!fp)
	{
		return error_set (e, "could not open `%s' for reading: %s",
			filename, strerror (errno));
	}

	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;

	return error_set (e, "error while reading `%s': %s",
		filename, strerror (errno));
}

/// Overwrites filename contents with data; creates directories as needed
static bool
write_file (const char *filename, const void *data, size_t data_len,
	struct error **e)
{
	char *dir = xstrdup (filename);
	bool parents_created = mkdir_with_parents (dirname (dir), e);
	free (dir);
	if (!parents_created)
		return false;

	FILE *fp = fopen (filename, "w");
	if (!fp)
	{
		return error_set (e, "could not open `%s' for writing: %s",
			filename, strerror (errno));
	}

	fwrite (data, data_len, 1, fp);
	bool success = !ferror (fp) && !fflush (fp)
		&& (!fsync (fileno (fp)) || errno == EINVAL);
	fclose (fp);

	if (!success)
	{
		return error_set (e, "writing to `%s' failed: %s",
			filename, strerror (errno));
	}
	return true;
}

/// Wrapper for write_file() that makes sure that the new data has been written
/// to disk in its entirety before overriding the old file
static bool
write_file_safe (const char *filename, const void *data, size_t data_len,
	struct error **e)
{
	// XXX: ideally we would also open the directory, use *at() versions
	//   of functions and call fsync() on the directory as appropriate
	// FIXME: this should behave similarly to mkstemp(), just with 0666;
	//   as it is, this function is not particularly safe
	char *temp = xstrdup_printf ("%s.new", filename);
	bool success = write_file (temp, data, data_len, e);
	if (success && !(success = !rename (temp, filename)))
		error_set (e, "could not rename `%s' to `%s': %s",
			temp, filename, strerror (errno));
	free (temp);
	return success;
}

// --- Simple configuration ----------------------------------------------------

// This is the bare minimum to make an application configurable.
// Keys are stripped of surrounding whitespace, values are not.

struct simple_config_item { const char *key, *default_value, *description; };

static void
simple_config_load_defaults
	(struct str_map *config, const struct simple_config_item *table)
{
	for (; table->key != NULL; table++)
		if (table->default_value)
			str_map_set (config, table->key, xstrdup (table->default_value));
		else
			str_map_set (config, table->key, NULL);
}

static bool
simple_config_update_from_file (struct str_map *config, struct error **e)
{
	char *filename = resolve_filename
		(PROGRAM_NAME ".conf", resolve_relative_config_filename);
	struct str s = str_make ();
	bool ok = !filename || read_file (filename, &s, e);
	size_t line_no = 0;
	for (char *x = strtok (s.str, "\r\n"); ok && x; x = strtok (NULL, "\r\n"))
	{
		line_no++;
		if (strchr ("#", *(x += strspn (x, " \t"))))
			continue;

		char *equals = strchr (x, '=');
		if (!equals || equals == x)
			ok = error_set (e, "%s: malformed line %zu", filename, line_no);
		else
		{
			char *end = equals++;
			do *end = '\0'; while (strchr (" \t", *--end));
			str_map_set (config, x, xstrdup (equals));
		}
	}
	str_free (&s);
	free (filename);
	return ok;
}

static char *
write_configuration_file (const char *path_hint, const struct str *data,
	struct error **e)
{
	struct str path = str_make ();
	if (path_hint)
		str_append (&path, path_hint);
	else
	{
		get_xdg_home_dir (&path, "XDG_CONFIG_HOME", ".config");
		str_append (&path, "/" PROGRAM_NAME "/" PROGRAM_NAME ".conf");
	}

	if (!write_file_safe (path.str, data->str, data->len, e))
	{
		str_free (&path);
		return NULL;
	}
	return str_steal (&path);
}

static char *
simple_config_write_default (const char *path_hint, const char *prolog,
	const struct simple_config_item *table, struct error **e)
{
	struct str data = str_make ();
	if (prolog)
		str_append (&data, prolog);

	for (; table->key != NULL; table++)
	{
		str_append_printf (&data, "# %s\n", table->description);
		if (table->default_value)
			str_append_printf (&data, "%s=%s\n",
				table->key, table->default_value);
		else
			str_append_printf (&data, "#%s=\n", table->key);
	}

	char *path = write_configuration_file (path_hint, &data, e);
	str_free (&data);
	return path;
}

/// Convenience wrapper suitable for most simple applications
static void
call_simple_config_write_default
	(const char *path_hint, const struct simple_config_item *table)
{
	static const char *prolog =
	"# " 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";

	struct error *e = NULL;
	char *filename = simple_config_write_default (path_hint, prolog, table, &e);
	if (!filename)
	{
		print_error ("%s", e->message);
		error_free (e);
		exit (EXIT_FAILURE);
	}
	print_status ("configuration written to `%s'", filename);
	free (filename);
}

// --- Option handler ----------------------------------------------------------

// Simple wrapper for the getopt_long API to make it easier to use and maintain.

#define OPT_USAGE_ALIGNMENT_COLUMN 30   ///< Alignment for option descriptions

enum
{
	OPT_OPTIONAL_ARG  = (1 << 0),       ///< The argument is optional
	OPT_LONG_ONLY     = (1 << 1)        ///< Ignore the short name in opt_string
};

// All options need to have both a short name, and a long name.  The short name
// is what is returned from opt_handler_get().  It is possible to define a value
// completely out of the character range combined with the OPT_LONG_ONLY flag.
//
// When `arg_hint' is defined, the option is assumed to have an argument.

struct opt
{
	int short_name;                     ///< The single-letter name
	const char *long_name;              ///< The long name
	const char *arg_hint;               ///< Option argument hint
	int flags;                          ///< Option flags
	const char *description;            ///< Option description
};

struct opt_handler
{
	int argc;                           ///< The number of program arguments
	char **argv;                        ///< Program arguments

	const char *arg_hint;               ///< Program arguments hint
	const char *description;            ///< Description of the program

	const struct opt *opts;             ///< The list of options
	size_t opts_len;                    ///< The length of the option array

	struct option *options;             ///< The list of options for getopt
	char *opt_string;                   ///< The `optstring' for getopt
};

static void
opt_handler_free (struct opt_handler *self)
{
	free (self->options);
	free (self->opt_string);
}

static struct opt_handler
opt_handler_make (int argc, char **argv,
	const struct opt *opts, const char *arg_hint, const char *description)
{
	struct opt_handler self =
	{
		.argc = argc,
		.argv = argv,
		.arg_hint = arg_hint,
		.description = description,
	};

	size_t len = 0;
	for (const struct opt *iter = opts; iter->long_name; iter++)
		len++;

	self.opts = opts;
	self.opts_len = len;
	self.options = xcalloc (len + 1, sizeof *self.options);

	struct str opt_string = str_make ();
	for (size_t i = 0; i < len; i++)
	{
		const struct opt *opt = opts + i;
		struct option *mapped = self.options + i;

		mapped->name = opt->long_name;
		if (!opt->arg_hint)
			mapped->has_arg = no_argument;
		else if (opt->flags & OPT_OPTIONAL_ARG)
			mapped->has_arg = optional_argument;
		else
			mapped->has_arg = required_argument;
		mapped->val = opt->short_name;

		if (opt->flags & OPT_LONG_ONLY)
			continue;

		str_append_c (&opt_string, opt->short_name);
		if (opt->arg_hint)
		{
			str_append_c (&opt_string, ':');
			if (opt->flags & OPT_OPTIONAL_ARG)
				str_append_c (&opt_string, ':');
		}
	}
	self.opt_string = str_steal (&opt_string);
	return self;
}

static void
opt_handler_usage (const struct opt_handler *self, FILE *stream)
{
	struct str usage = str_make ();
	str_append_printf (&usage, "Usage: %s [OPTION]... %s\n",
		self->argv[0], self->arg_hint ? self->arg_hint : "");
	str_append_printf (&usage, "%s\n\n", self->description);

	for (size_t i = 0; i < self->opts_len; i++)
	{
		struct str row = str_make ();
		const struct opt *opt = self->opts + i;
		if (!(opt->flags & OPT_LONG_ONLY))
			str_append_printf (&row, "  -%c, ", opt->short_name);
		else
			str_append (&row, "      ");
		str_append_printf (&row, "--%s", opt->long_name);
		if (opt->arg_hint)
			str_append_printf (&row, (opt->flags & OPT_OPTIONAL_ARG)
				? "[=%s]" : " %s", opt->arg_hint);

		// TODO: keep the indent if there are multiple lines
		if (row.len + 2 <= OPT_USAGE_ALIGNMENT_COLUMN)
		{
			str_append (&row, "  ");
			str_append_printf (&usage, "%-*s%s\n",
				OPT_USAGE_ALIGNMENT_COLUMN, row.str, opt->description);
		}
		else
			str_append_printf (&usage, "%s\n%-*s%s\n", row.str,
				OPT_USAGE_ALIGNMENT_COLUMN, "", opt->description);

		str_free (&row);
	}

	fputs (usage.str, stream);
	str_free (&usage);
}

static int
opt_handler_get (const struct opt_handler *self)
{
	return getopt_long (self->argc, self->argv,
		self->opt_string, self->options, NULL);
}

// --- Unit tests --------------------------------------------------------------

// This is modeled after GTest, only remarkably simpler.

typedef void (*test_fn) (const void *data, void *fixture);

struct test_unit
{
	LIST_HEADER (struct test_unit)

	char *name;                         ///< Name of the test
	size_t fixture_size;                ///< Fixture size
	const void *user_data;              ///< User data

	test_fn setup;                      ///< Fixture setup callback
	test_fn test;                       ///< The test
	test_fn teardown;                   ///< Fixture teardown callback
};

struct test
{
	struct test_unit *tests;            ///< List of tests
	struct test_unit *tests_tail;       ///< End of the list of tests

	struct str_map whitelist;           ///< Whitelisted tests
	struct str_map blacklist;           ///< Blacklisted tests

	unsigned list_only : 1;             ///< Just list all tests
	unsigned can_fork  : 1;             ///< Forking doesn't break anything
};

static void
test_init (struct test *self, int argc, char **argv)
{
	memset (self, 0, sizeof *self);
	self->whitelist = str_map_make (NULL);
	self->blacklist = str_map_make (NULL);

	// Usually this shouldn't pose a problem but let's make it optional
	self->can_fork = true;

	static const struct opt opts[] =
	{
		{ 'd', "debug", NULL, 0, "run in debug mode" },
		{ 'h', "help", NULL, 0, "display this help and exit" },
		{ 'p', "pass", "NAME", 0, "only run tests glob-matching the name" },
		{ 's', "skip", "NAME", 0, "skip all tests glob-matching the name" },
		{ 'S', "single-process", NULL, 0, "don't fork for each test" },
		{ 'l', "list", NULL, 0, "list all available tests" },
		{ 0, NULL, NULL, 0, NULL }
	};

	struct opt_handler oh =
		opt_handler_make (argc, argv, opts, NULL, "Unit test runner");

	int c;
	while ((c = opt_handler_get (&oh)) != -1)
	switch (c)
	{
	case 'd':
		g_debug_mode = true;
		break;
	case 'h':
		opt_handler_usage (&oh, stdout);
		exit (EXIT_SUCCESS);
	case 'p':
		str_map_set (&self->whitelist, optarg, (void *) 1);
		break;
	case 's':
		str_map_set (&self->blacklist, optarg, (void *) 1);
		break;

	case 'S':  self->can_fork = false;  break;
	case 'l':  self->list_only = true;  break;

	default:
		print_error ("wrong options");
		opt_handler_usage (&oh, stderr);
		exit (EXIT_FAILURE);
	}

	argc -= optind;
	argv += optind;

	if (argc)
	{
		opt_handler_usage (&oh, stderr);
		exit (EXIT_FAILURE);
	}

	opt_handler_free (&oh);
}

static void
test_add_internal (struct test *self, const char *name, size_t fixture_size,
	const void *user_data, test_fn setup, test_fn test, test_fn teardown)
{
	hard_assert (test != NULL);
	hard_assert (name != NULL);

	struct test_unit *unit = xcalloc (1, sizeof *unit);
	unit->name = xstrdup (name);
	unit->fixture_size = fixture_size;
	unit->user_data = user_data;

	unit->setup = setup;
	unit->test = test;
	unit->teardown = teardown;

	LIST_APPEND_WITH_TAIL (self->tests, self->tests_tail, unit);
}

#define test_add(self, name, fixture_type, user_data, setup, test, teardown)   \
	test_add_internal ((self), (name), sizeof (fixture_type), (user_data),     \
		(test_fn) (setup), (test_fn) (test), (test_fn) (teardown))

#define test_add_simple(self, name, user_data, test)                           \
	test_add_internal ((self), (name), 0, (user_data),                         \
		NULL, (test_fn) (test), NULL)

static bool
str_map_glob_match (struct str_map *self, const char *entry)
{
	struct str_map_iter iter = str_map_iter_make (self);
	while (str_map_iter_next (&iter))
		if (!fnmatch (iter.link->key, entry, 0))
			return true;
	return false;
}

static bool
test_is_allowed (struct test *self, const char *name)
{
	bool allowed = true;
	if (self->whitelist.len)
		allowed  =  str_map_glob_match (&self->whitelist, name);
	if (self->blacklist.len)
		allowed &= !str_map_glob_match (&self->blacklist, name);
	return allowed;
}

static void
test_unit_run (struct test_unit *self)
{
	void *fixture = xcalloc (1, self->fixture_size);
	if (self->setup)
		self->setup (self->user_data, fixture);

	self->test (self->user_data, fixture);

	if (self->teardown)
		self->teardown (self->user_data, fixture);
	free (fixture);
}

static bool
test_unit_run_forked (struct test_unit *self)
{
	pid_t child = fork ();
	if (child == -1)
	{
		print_error ("%s: %s", "fork", strerror (errno));
		return false;
	}
	else if (!child)
	{
		test_unit_run (self);
		_exit (EXIT_SUCCESS);
	}

	int status = 0;
	if (waitpid (child, &status, WUNTRACED) == -1)
		print_error ("%s: %s", "waitpid", strerror (errno));
	else if (WIFSTOPPED (status))
	{
		print_error ("test child has been stopped");
		(void) kill (child, SIGKILL);
	}
	else if (WIFSIGNALED (status))
		print_error ("test child was killed by signal %d", WTERMSIG (status));
	else if (WEXITSTATUS (status) != 0)
		print_error ("test child exited with status %d", WEXITSTATUS (status));
	else
		return true;
	return false;
}

static bool
test_run_unit (struct test *self, struct test_unit *unit)
{
	fprintf (stderr, "%s: ", unit->name);

	if (!self->can_fork)
		test_unit_run (unit);
	else if (!test_unit_run_forked (unit))
		return false;

	fprintf (stderr, "OK\n");
	return true;
}

static int
test_run (struct test *self)
{
	g_soft_asserts_are_deadly = true;

	bool failure = false;
	LIST_FOR_EACH (struct test_unit, iter, self->tests)
	{
		if (!test_is_allowed (self, iter->name))
			continue;
		if (self->list_only)
			printf ("%s\n", iter->name);
		else if (!test_run_unit (self, iter))
			failure = true;
	}

	LIST_FOR_EACH (struct test_unit, iter, self->tests)
	{
		free (iter->name);
		free (iter);
	}

	str_map_free (&self->whitelist);
	str_map_free (&self->blacklist);
	return failure;
}

// --- Connector ---------------------------------------------------------------

#if defined LIBERTY_WANT_POLLER && defined LIBERTY_WANT_ASYNC

// This is a helper that tries to establish a connection with any address on
// a given list.  Sadly it also introduces a bit of a callback hell.

struct connector_target
{
	LIST_HEADER (struct connector_target)
	struct connector *connector;        ///< Parent connector

	char *hostname;                     ///< Target hostname or address
	char *service;                      ///< Target service name or port

	struct async *getaddrinfo_event;    ///< Address resolution
	struct error *getaddrinfo_error;    ///< Address resolution error

	struct addrinfo *results;           ///< Resolved target
	struct addrinfo *iter;              ///< Current endpoint
};

static struct connector_target *
connector_target_new (void)
{
	struct connector_target *self = xcalloc (1, sizeof *self);
	return self;
}

static void
connector_target_destroy (struct connector_target *self)
{
	if (self->getaddrinfo_event)
		async_cancel (self->getaddrinfo_event);
	if (self->getaddrinfo_error)
		error_free (self->getaddrinfo_error);
	if (self->results)
		freeaddrinfo (self->results);

	free (self->hostname);
	free (self->service);
	free (self);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

struct connector
{
	struct poller *poller;              ///< Poller
	int socket;                         ///< Socket FD for the connection
	struct poller_fd connected_event;   ///< We've connected or failed
	struct connector_target *targets;   ///< Targets
	struct connector_target *targets_t; ///< Tail of targets

	void *user_data;                    ///< User data for callbacks

	// You may destroy the connector object in these two main callbacks:

	/// Connection has been successfully established;
	/// the hostname is mainly intended for TLS Server Name Indication
	void (*on_connected) (void *user_data, int socket, const char *hostname);
	/// Failed to establish a connection to either target
	void (*on_failure) (void *user_data);

	// Optional:

	/// Connecting to a new address
	void (*on_connecting) (void *user_data, const char *address);
	/// Connecting to the last address has failed
	void (*on_error) (void *user_data, const char *error);
};

static void
connector_notify_connecting (struct connector *self,
	struct connector_target *target, struct addrinfo *gai_iter)
{
	if (!self->on_connecting)
		return;

	const char *real_host = target->hostname;
	char buf[NI_MAXHOST];

	if (gai_iter)
	{
		// We don't really need this, so we can let it quietly fail
		int err = getnameinfo (gai_iter->ai_addr, gai_iter->ai_addrlen,
			buf, sizeof buf, NULL, 0, NI_NUMERICHOST);
		if (err)
			LOG_FUNC_FAILURE ("getnameinfo", gai_strerror (err));
		else
			real_host = buf;
	}

	char *address = format_host_port_pair (real_host, target->service);
	self->on_connecting (self->user_data, address);
	free (address);
}

static void
connector_notify_connected (struct connector *self, int fd)
{
	set_blocking (fd, true);
	self->on_connected (self->user_data, fd, self->targets->hostname);
}

static void
connector_prepare_next (struct connector *self)
{
	struct connector_target *target = self->targets;
	if (!target->iter || !(target->iter = target->iter->ai_next))
	{
		LIST_UNLINK_WITH_TAIL (self->targets, self->targets_t, target);
		connector_target_destroy (target);
	}
}

static void connector_handle_error (struct connector *self, const char *error);

/// See if there's any target remaining at all -- it can however either still
/// be waiting for address resolution to finish, or have already failed
static bool
connector_check_target (struct connector *self, struct connector_target *target)
{
	if (!target)
		self->on_failure (self->user_data);
	else if (target->getaddrinfo_error)
	{
		connector_notify_connecting (self, target, NULL);
		connector_handle_error (self, target->getaddrinfo_error->message);
	}
	else if (target->results)
		return true;
	return false;
}

static void
connector_step (struct connector *self)
{
	struct connector_target *target = self->targets;
	if (!connector_check_target (self, target))
		return;

	struct addrinfo *gai_iter = target->iter;
	hard_assert (gai_iter != NULL);

	connector_notify_connecting (self, target, gai_iter);

	int fd = socket (gai_iter->ai_family,
		gai_iter->ai_socktype, gai_iter->ai_protocol);
	if (fd == -1)
	{
		connector_handle_error (self, strerror (errno));
		return;
	}

	set_cloexec (fd);
	set_blocking (fd, false);

	int yes = 1;
	soft_assert (setsockopt (fd, SOL_SOCKET, SO_KEEPALIVE,
		&yes, sizeof yes) != -1);

	if (!connect (fd, gai_iter->ai_addr, gai_iter->ai_addrlen))
		connector_notify_connected (self, fd);
	else if (errno == EINPROGRESS)
	{
		self->connected_event.fd = self->socket = fd;
		poller_fd_set (&self->connected_event, POLLOUT);
	}
	else
	{
		connector_handle_error (self, strerror (errno));
		xclose (fd);
	}
}

static void
connector_handle_error (struct connector *self, const char *error)
{
	if (self->on_error)
		self->on_error (self->user_data, error);

	connector_prepare_next (self);
	connector_step (self);
}

static void
connector_on_ready (const struct pollfd *pfd, struct connector *self)
{
	// See http://cr.yp.to/docs/connect.html if this doesn't work.
	// The second connect() method doesn't work with DragonflyBSD.

	int error = 0;
	socklen_t error_len = sizeof error;
	hard_assert (!getsockopt (pfd->fd,
		SOL_SOCKET, SO_ERROR, &error, &error_len));

	if (error)
	{
		poller_fd_reset (&self->connected_event);
		xclose (self->socket);
		self->socket = -1;

		connector_handle_error (self, strerror (error));
	}
	else
	{
		poller_fd_reset (&self->connected_event);
		self->socket = -1;
		connector_notify_connected (self, pfd->fd);
	}
}

static void
connector_init (struct connector *self, struct poller *poller)
{
	memset (self, 0, sizeof *self);
	self->poller = poller;
	self->socket = -1;
	self->connected_event = poller_fd_make (poller, self->socket);
	self->connected_event.user_data = self;
	self->connected_event.dispatcher = (poller_fd_fn) connector_on_ready;
}

static void
connector_free (struct connector *self)
{
	poller_fd_reset (&self->connected_event);
	if (self->socket != -1)
		xclose (self->socket);

	LIST_FOR_EACH (struct connector_target, iter, self->targets)
		connector_target_destroy (iter);
}

static void
connector_on_getaddrinfo (int err, struct addrinfo *results, void *user_data)
{
	struct connector_target *self = user_data;

	if (err)
	{
		error_set (&self->getaddrinfo_error,
			"%s: %s", "getaddrinfo", gai_strerror (err));
	}

	self->results = self->iter = results;
	self->getaddrinfo_event = NULL;

	// We've been waiting for this address to be resolved
	if (self == self->connector->targets)
		connector_step (self->connector);
}

/// Connection will be attempted asynchronously once you add any target
static void
connector_add_target (struct connector *self,
	const char *hostname, const char *service)
{
	struct connector_target *target = connector_target_new ();
	target->connector = self;
	target->hostname = xstrdup (hostname);
	target->service = xstrdup (service);

	struct addrinfo hints;
	memset (&hints, 0, sizeof hints);
	hints.ai_socktype = SOCK_STREAM;

	struct async_getaddrinfo *gai = async_getaddrinfo
		(&self->poller->common.async, hostname, service, &hints);

	gai->dispatcher = connector_on_getaddrinfo;
	gai->user_data = target;
	target->getaddrinfo_event = &gai->async;

	LIST_APPEND_WITH_TAIL (self->targets, self->targets_t, target);
}

#endif // defined LIBERTY_WANT_POLLER && defined LIBERTY_WANT_ASYNC

// --- Simple network I/O ------------------------------------------------------

enum socket_io_result
{
	SOCKET_IO_OK = 0,                   ///< Completed successfully
	SOCKET_IO_EOF,                      ///< Connection shut down by peer
	SOCKET_IO_ERROR                     ///< Connection error
};

static enum socket_io_result
socket_io_try_read (int socket_fd, struct str *rb)
{
	// Flood protection, cannot afford to read too much at once
	size_t read_limit = rb->len + (1 << 20);
	if (read_limit < rb->len)
		read_limit = SIZE_MAX;

	ssize_t n_read;
	while (rb->len < read_limit)
	{
		str_reserve (rb, 1024);
		n_read = read (socket_fd, rb->str + rb->len,
			rb->alloc - rb->len - 1 /* null byte */);

		if (n_read > 0)
		{
			rb->str[rb->len += n_read] = '\0';
			continue;
		}
		if (n_read == 0)
			return SOCKET_IO_EOF;

		if (errno == EAGAIN)
			return SOCKET_IO_OK;
		if (errno == EINTR)
			continue;

		int errno_save = errno;
		LOG_LIBC_FAILURE ("read");
		errno = errno_save;
		return SOCKET_IO_ERROR;
	}
	return SOCKET_IO_OK;
}

static enum socket_io_result
socket_io_try_write (int socket_fd, struct str *wb)
{
	ssize_t n_written;
	while (wb->len)
	{
		n_written = write (socket_fd, wb->str, wb->len);
		if (n_written >= 0)
		{
			str_remove_slice (wb, 0, n_written);
			continue;
		}

		if (errno == EAGAIN)
			return SOCKET_IO_OK;
		if (errno == EINTR)
			continue;

		int errno_save = errno;
		LOG_LIBC_FAILURE ("write");
		errno = errno_save;
		return SOCKET_IO_ERROR;
	}
	return SOCKET_IO_OK;
}

// --- Advanced configuration --------------------------------------------------

// This is a more powerful configuration format, adding key-value maps and
// simplifying item validation and dynamic handling of changes.  All strings
// must be encoded in UTF-8.
//
// The syntax is roughly described by the following parsing expression grammar:
//
//   config  = entries eof  # as if there were implicit curly braces around
//   entries = (newline* pair)* newline*
//   pair    = key newline* lws '=' newline* value (&endobj / newline / eof)
//   key     = string / !null !boolean lws [A-Za-z_][0-9A-Za-z_]*
//   value   = object / string / integer / null / boolean
//
//   object  = lws '{' entries endobj
//   endobj  = lws '}'
//
//   quoted  = lws '"' (!["\\] char / '\\' escape)* '"'
//           / lws '`' (![`] char)* '`'
//   string  = (quoted)+
//   char    = [\0-\177]  # or any Unicode codepoint in the UTF-8 encoding
//   escape  = [\\"abfnrtv] / [xX][0-9A-Fa-f][0-9A-Fa-f]? / [0-7][0-7]?[0-7]?
//
//   integer = lws [-+]? [0-9]+  # whatever strtoll() accepts on your system
//   null    = lws 'null'
//   boolean = lws 'yes'  / lws 'YES'  / lws 'no'    / lws 'NO'
//           / lws 'on'   / lws 'ON'   / lws 'off'   / lws 'OFF'
//           / lws 'true' / lws 'TRUE' / lws 'false' / lws 'FALSE'
//
//   newline = lws comment? '\n'
//   eof     = lws comment? !.
//   lws     = [ \t\r]*  # linear whitespace (plus CR as it is insignificant)
//   comment = '#' (!'\n' .)*

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);
	self->value.string = str_make ();
	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_make ();
	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);
	self->value.object = str_map_make ((str_map_free_fn) 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;
	}

	source->user_data = self->user_data;
	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 strv v = strv_make ();
	cstr_split (path, ".", false, &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;
	}
	strv_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 (iscntrl_ascii (c))
			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");
	}
}

// FIXME: shuffle code so that this isn't needed (serializer after the parser)
static bool config_tokenizer_is_word_char (int c);

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);

	bool can_use_word = true;
	for (const char *p = key; *p; p++)
		if (!config_tokenizer_is_word_char (*p))
			can_use_word = false;

	str_append (self->output, indent);
	if (can_use_word)
		str_append (self->output, key);
	else
	{
		struct str s = { .str = (char *) key, .len = strlen (key) };
		config_item_write_string (self->output, &s);
	}

	str_append (self->output, " = ");
	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_make (&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 struct config_tokenizer
config_tokenizer_make (const char *p, size_t len)
{
	return (struct config_tokenizer)
		{ .p = p, .len = len, .report_line = true, .string = str_make () };
}

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_make ();

	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_dq_string (struct config_tokenizer *self, struct str *output,
	struct error **e)
{
	unsigned char c = config_tokenizer_advance (self);
	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 bool
config_tokenizer_bt_string (struct config_tokenizer *self, struct str *output,
	struct error **e)
{
	unsigned char c = config_tokenizer_advance (self);
	while (self->len)
	{
		if ((c = config_tokenizer_advance (self)) == '`')
			return true;
		str_append_c (output, c);
	}
	config_tokenizer_error (self, e, "premature end of string");
	return false;
}

static bool
config_tokenizer_string (struct config_tokenizer *self, struct str *output,
	struct error **e)
{
	// Go-like strings, with C/AWK-like automatic concatenation
	while (self->len)
	{
		bool ok = true;
		if (isspace_ascii (*self->p) && *self->p != '\n')
			config_tokenizer_advance (self);
		else if (*self->p == '"')
			ok = config_tokenizer_dq_string (self, output, e);
		else if (*self->p == '`')
			ok = config_tokenizer_bt_string (self, output, e);
		else
			break;

		if (!ok)
			return false;
	}
	return true;
}

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 '"':
	case '`':
		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;
	}

	// Our input doesn't need to be NUL-terminated but we want to use strtoll()
	char buf[48] = "", *end = buf;
	size_t buf_len = MIN (sizeof buf - 1, self->len);

	errno = 0;
	self->integer = strtoll (strncpy (buf, self->p, buf_len), &end, 10);
	if (errno == ERANGE)
	{
		config_tokenizer_error (self, e, "integer out of range");
		return CONFIG_T_ABORT;
	}
	if (end != buf)
	{
		self->len -= end - buf;
		self->p += end - buf;
		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 (self->len && 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 struct config_parser
config_parser_make (const char *script, size_t len)
{
	// As reading in tokens may cause exceptions, we wait for the first peek()
	// to replace the initial CONFIG_T_ABORT.
	return (struct config_parser)
	{
		.tokenizer = config_tokenizer_make (script, len),
		.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;

	// I'm not sure how to feel about arbitrary keys but here they are
	if (!ACCEPT (CONFIG_T_STRING))
		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)
{
	volatile struct config_parser parser = config_parser_make (script, len);
	struct config_parser *volatile self = (struct config_parser *) &parser;

	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 (self, err);
	}
	else
		object = config_parser_parse_object (self, err);
	config_parser_expect (self, CONFIG_T_ABORT, err);
end:
	config_parser_free (self);
	return object;
}

/// Clone an item.  Schema assignments aren't retained.
static struct config_item *
config_item_clone (struct config_item *self)
{
	// Oh well, it saves code
	struct str tmp = str_make ();
	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_read_from_file (const char *filename, struct error **e)
{
	struct config_item *root = NULL;

	struct str data = str_make ();
	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 in `%s': %s", filename, error->message);
		error_free (error);
	}
end:
	str_free (&data);
	return root;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

/// "user_data" is passed to allow its immediate use in validation callbacks
static struct config_item *
config_schema_initialize_item (struct config_schema *schema,
	struct config_item *parent, void *user_data, 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);

	if (item)
	{
		struct error *error = NULL;
		item->user_data = user_data;
		if (config_item_validate_by_schema (item, schema, &error))
			goto keep_current;

		error_set (warning, "resetting configuration item "
			"`%s' to default: %s", schema->name, error->message);
		error_free (error);
	}

	struct error *error = NULL;
	if (schema->default_)
		item = config_item_parse
			(schema->default_, strlen (schema->default_), true, &error);
	else
		item = config_item_null ();

	if (item)
		item->user_data = user_data;

	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);

		if (item)
			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;
		config_schema_initialize_item
			(schema_array++, object, user_data, &warning, &e);

		if (warning)
		{
			print_warning ("%s", warning->message);
			error_free (warning);
		}
		if (e)
			exit_fatal ("%s", e->message);
	}
}

static void
config_schema_call_changed (struct config_item *item)
{
	if (item->type == CONFIG_ITEM_OBJECT)
	{
		struct str_map_iter iter = str_map_iter_make (&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 struct config
config_make (void)
{
	return (struct config)
		{ .modules = str_map_make ((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_make (&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)
			str_map_set (&root->value.object, module->name,
				(subtree = config_item_object ()));
		if (module->loader)
			module->loader (subtree, module->user_data);
	}
}

// --- Protocol modules --------------------------------------------------------

#include "liberty-proto.c"
