5657 lines
144 KiB
C
5657 lines
144 KiB
C
/*
|
|
* 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)
|
|
{
|
|
size_t len = strlen (s) + 1;
|
|
return memcpy (xmalloc (len), s, len);
|
|
}
|
|
|
|
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 ((a ## _alloc) = (n), sizeof *(a)); \
|
|
(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 (self.alloc, sizeof *self.vector);
|
|
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
|
|
|
|
const 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
|
|
(const 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,
|
|
const 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)
|
|
{
|
|
const 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);
|
|
|
|
char *end = NULL;
|
|
bool can_use_word = ((void) strtoll (key, &end, 10), end == key);
|
|
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 (const 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 (const 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"
|