/* * liberty.c: the ultimate C unlibrary * * Copyright (c) 2014 - 2024, Přemysl Eric Janouch * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __unix__ // This file may define the "BSD" macro... #include // ...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 #include #include #endif // LIBERTY_WANT_SSL #include #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 (sizeof *(a), (a ## _alloc) = (n)); \ (a ## _len) = 0; \ BLOCK_END #define ARRAY_INIT(a) ARRAY_INIT_SIZED (a, 16) #define ARRAY_RESERVE(a, n) \ BLOCK_START \ while ((a ## _alloc) - (a ## _len) < n) \ (a) = xreallocarray ((a), sizeof *(a), (a ## _alloc) <<= 1); \ BLOCK_END // --- Double-linked list helpers ---------------------------------------------- #define LIST_HEADER(type) \ type *next; \ type *prev; #define LIST_PREPEND(head, link) \ BLOCK_START \ (link)->prev = NULL; \ (link)->next = (head); \ if ((link)->next) \ (link)->next->prev = (link); \ (head) = (link); \ BLOCK_END #define LIST_UNLINK(head, link) \ BLOCK_START \ if ((link)->prev) \ (link)->prev->next = (link)->next; \ else \ (head) = (link)->next; \ if ((link)->next) \ (link)->next->prev = (link)->prev; \ BLOCK_END #define LIST_APPEND_WITH_TAIL(head, tail, link) \ BLOCK_START \ (link)->prev = (tail); \ (link)->next = NULL; \ if ((link)->prev) \ (link)->prev->next = (link); \ else \ (head) = (link); \ (tail) = (link); \ BLOCK_END #define LIST_INSERT_WITH_TAIL(head, tail, link, following) \ BLOCK_START \ if (following) \ LIST_APPEND_WITH_TAIL ((head), (following)->prev, (link)); \ else \ LIST_APPEND_WITH_TAIL ((head), (tail), (link)); \ (link)->next = (following); \ BLOCK_END #define LIST_UNLINK_WITH_TAIL(head, tail, link) \ BLOCK_START \ if ((tail) == (link)) \ (tail) = (link)->prev; \ LIST_UNLINK ((head), (link)); \ BLOCK_END #define LIST_FOR_EACH(type, iter, list) \ for (type *iter = (list), *next; \ (iter && (next = iter->next)) || iter; \ iter = next) // --- Dynamically allocated string array -------------------------------------- struct strv { char **vector; size_t len; size_t alloc; }; static struct strv strv_make (void) { struct strv self; self.alloc = 4; self.len = 0; self.vector = xcalloc (sizeof *self.vector, self.alloc); return self; } static void strv_free (struct strv *self) { unsigned i; for (i = 0; i < self->len; i++) free (self->vector[i]); free (self->vector); self->vector = NULL; } static void strv_reset (struct strv *self) { strv_free (self); *self = strv_make (); } static void strv_append_owned (struct strv *self, char *s) { self->vector[self->len] = s; if (++self->len >= self->alloc) self->vector = xreallocarray (self->vector, sizeof *self->vector, (self->alloc <<= 1)); self->vector[self->len] = NULL; } static void strv_append (struct strv *self, const char *s) { strv_append_owned (self, xstrdup (s)); } static void strv_append_args (struct strv *self, const char *s, ...) ATTRIBUTE_SENTINEL; static void strv_append_args (struct strv *self, const char *s, ...) { va_list ap; va_start (ap, s); while (s) { strv_append (self, s); s = va_arg (ap, const char *); } va_end (ap); } static void strv_append_vector (struct strv *self, char **vector) { while (*vector) strv_append (self, *vector++); } static char * strv_steal (struct strv *self, size_t i) { hard_assert (i < self->len); char *tmp = self->vector[i]; memmove (self->vector + i, self->vector + i + 1, (self->len-- - i) * sizeof *self->vector); return tmp; } static void strv_remove (struct strv *self, size_t i) { free (strv_steal (self, i)); } // --- Dynamically allocated strings ------------------------------------------- // Basically a string builder to abstract away manual memory management. struct str { char *str; ///< String data, null terminated size_t alloc; ///< How many bytes are allocated size_t len; ///< How long the string actually is }; /// We don't care about allocations that are way too large for the content, as /// long as the allocation is below the given threshold. (Trivial heuristics.) #define STR_SHRINK_THRESHOLD (1 << 20) static struct str str_make (void) { struct str self; self.alloc = 16; self.len = 0; self.str = strcpy (xmalloc (self.alloc), ""); return self; } static void str_free (struct str *self) { free (self->str); self->str = NULL; self->alloc = 0; self->len = 0; } static void str_reset (struct str *self) { str_free (self); *self = str_make (); } static char * str_steal (struct str *self) { char *str = self->str; self->str = NULL; str_free (self); return str; } static void str_reserve (struct str *self, size_t n) { // We allocate at least one more byte for the terminating null character size_t new_alloc = self->alloc; while (new_alloc <= self->len + n) new_alloc <<= 1; if (new_alloc != self->alloc) self->str = xrealloc (self->str, (self->alloc = new_alloc)); } static void str_append_data (struct str *self, const void *data, size_t n) { str_reserve (self, n); memcpy (self->str + self->len, data, n); self->len += n; self->str[self->len] = '\0'; } static void str_append_c (struct str *self, char c) { str_append_data (self, &c, 1); } static void str_append (struct str *self, const char *s) { str_append_data (self, s, strlen (s)); } static void str_append_str (struct str *self, const struct str *another) { str_append_data (self, another->str, another->len); } static int str_append_vprintf (struct str *self, const char *fmt, va_list va) { va_list ap; int size; va_copy (ap, va); size = vsnprintf (NULL, 0, fmt, ap); va_end (ap); if (size < 0) return -1; va_copy (ap, va); str_reserve (self, size); size = vsnprintf (self->str + self->len, self->alloc - self->len, fmt, ap); va_end (ap); if (size > 0) self->len += size; return size; } static int str_append_printf (struct str *self, const char *fmt, ...) ATTRIBUTE_PRINTF (2, 3); static int str_append_printf (struct str *self, const char *fmt, ...) { va_list ap; va_start (ap, fmt); int size = str_append_vprintf (self, fmt, ap); va_end (ap); return size; } static void str_remove_slice (struct str *self, size_t start, size_t length) { size_t end = start + length; hard_assert (end <= self->len); memmove (self->str + start, self->str + end, self->len - end); self->str[self->len -= length] = '\0'; // Shrink the string if the allocation becomes way too large if (self->alloc >= STR_SHRINK_THRESHOLD && self->len < (self->alloc >> 2)) self->str = xrealloc (self->str, self->alloc >>= 2); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static void str_pack_u8 (struct str *self, uint8_t x) { str_append_data (self, &x, 1); } static void str_pack_u16 (struct str *self, uint16_t x) { uint8_t tmp[2] = { x >> 8, x }; str_append_data (self, tmp, sizeof tmp); } static void str_pack_u32 (struct str *self, uint32_t x) { uint32_t u = x; uint8_t tmp[4] = { u >> 24, u >> 16, u >> 8, u }; str_append_data (self, tmp, sizeof tmp); } static void str_pack_u64 (struct str *self, uint64_t x) { uint8_t tmp[8] = { x >> 56, x >> 48, x >> 40, x >> 32, x >> 24, x >> 16, x >> 8, x }; str_append_data (self, tmp, sizeof tmp); } #define str_pack_i8(self, x) str_pack_u8 ((self), (uint8_t) (x)) #define str_pack_i16(self, x) str_pack_u16 ((self), (uint16_t) (x)) #define str_pack_i32(self, x) str_pack_u32 ((self), (uint32_t) (x)) #define str_pack_i64(self, x) str_pack_u64 ((self), (uint64_t) (x)) // --- Reading binary numbers -------------------------------------------------- // Doing this byte by byte prevents unaligned memory access issues. static uint64_t peek_u64be (const uint8_t *p) { return (uint64_t) p[0] << 56 | (uint64_t) p[1] << 48 | (uint64_t) p[2] << 40 | (uint64_t) p[3] << 32 | (uint64_t) p[4] << 24 | (uint64_t) p[5] << 16 | p[6] << 8 | p[7]; } static uint32_t peek_u32be (const uint8_t *p) { return (uint32_t) p[0] << 24 | (uint32_t) p[1] << 16 | p[2] << 8 | p[3]; } static uint16_t peek_u16be (const uint8_t *p) { return (uint16_t) p[0] << 8 | p[1]; } static uint64_t peek_u64le (const uint8_t *p) { return (uint64_t) p[7] << 56 | (uint64_t) p[6] << 48 | (uint64_t) p[5] << 40 | (uint64_t) p[4] << 32 | (uint64_t) p[3] << 24 | (uint64_t) p[2] << 16 | p[1] << 8 | p[0]; } static uint32_t peek_u32le (const uint8_t *p) { return (uint32_t) p[3] << 24 | (uint32_t) p[2] << 16 | p[1] << 8 | p[0]; } static uint16_t peek_u16le (const uint8_t *p) { return (uint16_t) p[1] << 8 | p[0]; } struct peeker { uint64_t (*u64) (const uint8_t *); uint32_t (*u32) (const uint8_t *); uint16_t (*u16) (const uint8_t *); }; static const struct peeker peeker_be = {peek_u64be, peek_u32be, peek_u16be}; static const struct peeker peeker_le = {peek_u64le, peek_u32le, peek_u16le}; // --- Errors ------------------------------------------------------------------ // Error reporting utilities. Inspired by GError, only much simpler. struct error { char *message; ///< Textual description of the event }; static bool error_set (struct error **e, const char *message, ...) ATTRIBUTE_PRINTF (2, 3); static bool error_set (struct error **e, const char *message, ...) { if (!e) return false; va_list ap; va_start (ap, message); int size = vsnprintf (NULL, 0, message, ap); va_end (ap); hard_assert (size >= 0); struct error *tmp = xmalloc (sizeof *tmp); tmp->message = xmalloc (size + 1); va_start (ap, message); size = vsnprintf (tmp->message, size + 1, message, ap); va_end (ap); hard_assert (size >= 0); soft_assert (*e == NULL); *e = tmp; return false; } static void error_free (struct error *e) { free (e->message); free (e); } static void error_propagate (struct error **destination, struct error *source) { if (!destination) { error_free (source); return; } soft_assert (*destination == NULL); *destination = source; } // --- File descriptor utilities ----------------------------------------------- static void set_cloexec (int fd) { soft_assert (fcntl (fd, F_SETFD, fcntl (fd, F_GETFD) | FD_CLOEXEC) != -1); } static bool set_blocking (int fd, bool blocking) { int flags = fcntl (fd, F_GETFL); if (flags == -1) exit_fatal ("%s: %s", "fcntl", strerror (errno)); bool prev = !(flags & O_NONBLOCK); if (blocking) flags &= ~O_NONBLOCK; else flags |= O_NONBLOCK; hard_assert (fcntl (fd, F_SETFL, flags) != -1); return prev; } static bool xwrite (int fd, const char *data, size_t len, struct error **e) { size_t written = 0; while (written < len) { ssize_t res = write (fd, data + written, len - written); if (res >= 0) written += res; else if (errno != EINTR) return error_set (e, "%s", strerror (errno)); } return true; } static void xclose (int fd) { while (close (fd) == -1) if (!soft_assert (errno == EINTR)) break; } // --- Randomness -------------------------------------------------------------- static bool random_bytes (void *output, size_t len, struct error **e) { bool result = false; int fd = open ("/dev/urandom", O_RDONLY); ssize_t got = 0; if (fd < 0) return error_set (e, "%s: %s", "open", strerror (errno)); else if ((got = read (fd, output, len)) < 0) error_set (e, "%s: %s", "read", strerror (errno)); else if (got != (ssize_t) len) error_set (e, "can't get enough bytes from the device"); else result = true; xclose (fd); return result; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static unsigned char g_siphash_key[16] = "SipHash 2-4 key!"; static inline void siphash_wrapper_randomize (void) { // I guess there's no real need to be this paranoic, so we ignore failures soft_assert (random_bytes (g_siphash_key, sizeof g_siphash_key, NULL)); } static inline uint64_t siphash_wrapper (const void *m, size_t len) { return siphash (g_siphash_key, m, len); } // --- String hash map --------------------------------------------------------- // The most basic 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 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 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 #include #include struct poller { int kqueue_fd; ///< The kqueue FD struct poller_fd **fds; ///< Information associated with each FD struct kevent *revents; ///< Output array for kevent() size_t len; ///< Number of polled descriptors size_t alloc; ///< Number of entries allocated struct poller_common common; ///< Stuff common to all backends int revents_len; ///< Number of entries in `revents' }; static void poller_init (struct poller *self) { self->kqueue_fd = kqueue (); hard_assert (self->kqueue_fd != -1); set_cloexec (self->kqueue_fd); self->len = 0; self->alloc = POLLER_MIN_ALLOC; self->fds = xcalloc (self->alloc, sizeof *self->fds); self->revents = xcalloc (self->alloc, sizeof *self->revents); self->revents_len = 0; poller_common_init (&self->common, self); } static void poller_free (struct poller *self) { xclose (self->kqueue_fd); free (self->fds); free (self->revents); poller_common_free (&self->common); } static void poller_ensure_space (struct poller *self) { if (self->len < self->alloc) return; self->alloc <<= 1; hard_assert (self->alloc != 0); self->revents = xreallocarray (self->revents, sizeof *self->revents, self->alloc); self->fds = xreallocarray (self->fds, sizeof *self->fds, self->alloc); } static void poller_set (struct poller *self, struct poller_fd *fd) { hard_assert (fd->poller == self); if (fd->index == -1) { poller_ensure_space (self); self->fds[fd->index = self->len++] = fd; } // We have to watch for readability and writeability separately; // to simplify matters, we can just disable what we don't desire to receive struct kevent changes[2]; EV_SET (&changes[0], fd->fd, EVFILT_READ, EV_ADD, 0, 0, fd); EV_SET (&changes[1], fd->fd, EVFILT_WRITE, EV_ADD, 0, 0, fd); changes[0].flags |= (fd->events & POLLIN) ? EV_ENABLE : EV_DISABLE; changes[1].flags |= (fd->events & POLLOUT) ? EV_ENABLE : EV_DISABLE; if (kevent (self->kqueue_fd, changes, N_ELEMENTS (changes), NULL, 0, NULL) == -1) exit_fatal ("%s: %s", "kevent", strerror (errno)); } static void poller_post_fork (struct poller *self) { // The kqueue FD isn't preserved across forks, need to recreate it self->kqueue_fd = kqueue (); hard_assert (self->kqueue_fd != -1); set_cloexec (self->kqueue_fd); for (size_t i = 0; i < self->len; i++) poller_set (self, self->fds[i]); } static int poller_compare_fds (const void *ax, const void *bx) { const struct kevent *ay = ax, *by = bx; return (int) ay->ident - (int) by->ident; } static void poller_dummify (struct kevent *fd_event) { fd_event->flags = USHRT_MAX; } static void poller_remove_from_dispatch (struct poller *self, const struct poller_fd *fd) { if (!self->revents_len) return; struct kevent key = { .ident = fd->fd }, *fd_event; if (!(fd_event = bsearch (&key, self->revents, self->revents_len, sizeof *self->revents, poller_compare_fds))) return; // The FD may appear twice -- both for reading and writing int index = fd_event - self->revents; if (index > 0 && !poller_compare_fds (&key, fd_event - 1)) poller_dummify (fd_event - 1); poller_dummify (fd_event); if (index < self->revents_len - 1 && !poller_compare_fds (&key, fd_event + 1)) poller_dummify (fd_event + 1); } static void poller_remove_at_index (struct poller *self, size_t index) { hard_assert (index < self->len); struct poller_fd *fd = self->fds[index]; fd->index = -1; poller_remove_from_dispatch (self, fd); if (index != --self->len) { self->fds[index] = self->fds[self->len]; self->fds[index]->index = index; } if (fd->closed) return; struct kevent changes[2]; EV_SET (&changes[0], fd->fd, EVFILT_READ, EV_DELETE, 0, 0, fd); EV_SET (&changes[1], fd->fd, EVFILT_WRITE, EV_DELETE, 0, 0, fd); if (kevent (self->kqueue_fd, changes, N_ELEMENTS (changes), NULL, 0, NULL) == -1) exit_fatal ("%s: %s", "kevent", strerror (errno)); } static struct timespec poller_timeout_to_timespec (int ms) { return (struct timespec) { .tv_sec = ms / 1000, .tv_nsec = (ms % 1000) * 1000 * 1000 }; } static short poller_kqueue_to_poll_events (struct kevent *event) { short result = 0; if (event->filter == EVFILT_READ) { result |= POLLIN; if ((event->flags & EV_EOF) && event->fflags) result |= POLLERR; } if (event->filter == EVFILT_WRITE) result |= POLLOUT; if (event->flags & EV_EOF) result |= POLLHUP; return result; } static void poller_run (struct poller *self) { // Not reentrant hard_assert (!self->revents_len); int n_fds; do { int timeout = poller_common_get_timeout (&self->common); struct timespec ts = poller_timeout_to_timespec (timeout); n_fds = kevent (self->kqueue_fd, NULL, 0, self->revents, self->len, timeout < 0 ? NULL : &ts); } while (n_fds == -1 && errno == EINTR); if (n_fds == -1) exit_fatal ("%s: %s", "kevent", strerror (errno)); // Sort them by file descriptor number for binary search qsort (self->revents, n_fds, sizeof *self->revents, poller_compare_fds); self->revents_len = n_fds; poller_common_dispatch (&self->common); for (int i = 0; i < n_fds; i++) { struct kevent *event = self->revents + i; if (event->flags == USHRT_MAX) continue; struct poller_fd *fd = event->udata; hard_assert (fd->index != -1); struct pollfd pfd; pfd.fd = fd->fd; pfd.revents = poller_kqueue_to_poll_events (event); pfd.events = fd->events; // Read and write events are separate in the kqueue API -- merge them int sibling = 1; while (i + sibling < n_fds && !poller_compare_fds (event, event + sibling)) pfd.revents |= poller_kqueue_to_poll_events (event + sibling++); if ((pfd.revents & POLLHUP) && (pfd.revents & POLLOUT)) pfd.revents &= ~POLLOUT; i += --sibling; fd->dispatcher (&pfd, fd->user_data); } self->revents_len = 0; } #else // ! BSD struct poller { struct pollfd *fds; ///< Polled descriptors struct poller_fd **fds_data; ///< Additional information for each FD size_t len; ///< Number of polled descriptors size_t alloc; ///< Number of entries allocated struct poller_common common; ///< Stuff common to all backends int dispatch_next; ///< The next dispatched FD or -1 }; static void poller_init (struct poller *self) { self->alloc = POLLER_MIN_ALLOC; self->len = 0; self->fds = xcalloc (self->alloc, sizeof *self->fds); self->fds_data = xcalloc (self->alloc, sizeof *self->fds_data); poller_common_init (&self->common, self); self->dispatch_next = -1; } static void poller_free (struct poller *self) { free (self->fds); free (self->fds_data); poller_common_free (&self->common); } static void poller_ensure_space (struct poller *self) { if (self->len < self->alloc) return; self->alloc <<= 1; self->fds = xreallocarray (self->fds, sizeof *self->fds, self->alloc); self->fds_data = xreallocarray (self->fds_data, sizeof *self->fds_data, self->alloc); } static void poller_set (struct poller *self, struct poller_fd *fd) { hard_assert (fd->poller == self); if (fd->index == -1) { poller_ensure_space (self); self->fds_data[fd->index = self->len++] = fd; } struct pollfd *new_entry = self->fds + fd->index; memset (new_entry, 0, sizeof *new_entry); new_entry->fd = fd->fd; new_entry->events = fd->events; } #define poller_post_fork(self) static void poller_remove_at_index (struct poller *self, size_t index) { hard_assert (index < self->len); struct poller_fd *fd = self->fds_data[index]; fd->index = -1; if (index == --self->len) return; // Make sure that we don't disrupt the dispatch loop; kind of crude if ((int) index < self->dispatch_next) { memmove (self->fds + index, self->fds + index + 1, (self->len - index) * sizeof *self->fds); memmove (self->fds_data + index, self->fds_data + index + 1, (self->len - index) * sizeof *self->fds_data); for (size_t i = index; i < self->len; i++) self->fds_data[i]->index = i; self->dispatch_next--; } else { self->fds[index] = self->fds [self->len]; self->fds_data[index] = self->fds_data[self->len]; self->fds_data[index]->index = index; } } static void poller_run (struct poller *self) { // Not reentrant hard_assert (self->dispatch_next == -1); int result; do result = poll (self->fds, self->len, poller_common_get_timeout (&self->common)); while (result == -1 && errno == EINTR); if (result == -1) exit_fatal ("%s: %s", "poll", strerror (errno)); poller_common_dispatch (&self->common); for (int i = 0; i < (int) self->len; ) { struct pollfd pfd = self->fds[i]; struct poller_fd *fd = self->fds_data[i]; self->dispatch_next = ++i; if (pfd.revents) fd->dispatcher (&pfd, fd->user_data); i = self->dispatch_next; } self->dispatch_next = -1; } #endif // ! BSD // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static struct poller_timer poller_timer_make (struct poller *poller) { return (struct poller_timer) { .timers = &poller->common.timers, .index = -1, }; } static void poller_timer_set (struct poller_timer *self, int timeout_ms) { self->when = poller_timers_get_current_time () + timeout_ms; poller_timers_set (self->timers, self); } static bool poller_timer_is_active (const struct poller_timer *self) { return self->index != -1; } static void poller_timer_reset (struct poller_timer *self) { if (self->index != -1) poller_timers_remove_at_index (self->timers, self->index); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static struct poller_idle poller_idle_make (struct poller *poller) { return (struct poller_idle) { .poller = poller }; } static void poller_idle_set (struct poller_idle *self) { if (self->active) return; LIST_PREPEND (self->poller->common.idle, self); self->active = true; } static void poller_idle_reset (struct poller_idle *self) { if (!self->active) return; LIST_UNLINK (self->poller->common.idle, self); self->prev = NULL; self->next = NULL; self->active = false; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static struct poller_fd poller_fd_make (struct poller *poller, int fd) { return (struct poller_fd) { .poller = poller, .index = -1, .fd = fd }; } static void poller_fd_set (struct poller_fd *self, short events) { self->events = events; poller_set (self->poller, self); } static void poller_fd_reset (struct poller_fd *self) { if (self->index != -1) poller_remove_at_index (self->poller, self->index); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static void poller_common_dummy_dispatcher (const struct pollfd *pfd, void *user_data) { (void) pfd; (void) user_data; // The async manager will empty the pipe when we invoke dispatch } static void poller_common_init (struct poller_common *self, struct poller *poller) { self->timers = poller_timers_make (); self->idle = NULL; #ifdef LIBERTY_WANT_ASYNC self->async = async_manager_make (); self->async_event = poller_fd_make (poller, self->async.finished_pipe[0]); poller_fd_set (&self->async_event, POLLIN); self->async_event.dispatcher = poller_common_dummy_dispatcher; self->async_event.user_data = self; #else // ! LIBERTY_WANT_ASYNC (void) poller; #endif // ! LIBERTY_WANT_ASYNC } static void poller_common_free (struct poller_common *self) { poller_timers_free (&self->timers); #ifdef LIBERTY_WANT_ASYNC async_manager_free (&self->async); #endif // LIBERTY_WANT_ASYNC } static int poller_common_get_timeout (const struct poller_common *self) { if (self->idle) return 0; int timeout = poller_timers_get_poll_timeout (&self->timers); #ifdef LIBERTY_WANT_ASYNC // This is completely arbitrary, in general we have no idea when to retry, // however one second doesn't sound like a particularly bad number if (self->async.delayed) timeout = MIN (timeout, 1000); #endif // LIBERTY_WANT_ASYNC return timeout; } static void poller_common_dispatch (struct poller_common *self) { poller_timers_dispatch (&self->timers); poller_idle_dispatch (self->idle); #ifdef LIBERTY_WANT_ASYNC async_manager_dispatch (&self->async); #endif // LIBERTY_WANT_ASYNC } #endif // LIBERTY_WANT_POLLER // --- Asynchronous jobs ------------------------------------------------------- #ifdef LIBERTY_WANT_ASYNC /// The callback takes ownership of the returned list typedef void (*async_getaddrinfo_fn) (int, struct addrinfo *, void *); struct async_getaddrinfo { struct async async; ///< Parent object int gai_result; ///< Direct result from getaddrinfo() char *host; ///< gai() argument: host char *service; ///< gai() argument: service struct addrinfo hints; ///< gai() argument: hints struct addrinfo *result; ///< Resulting addresses from gai() async_getaddrinfo_fn dispatcher; ///< Event dispatcher void *user_data; ///< User data }; static void async_getaddrinfo_execute (struct async *async) { struct async_getaddrinfo *self = (struct async_getaddrinfo *) async; self->gai_result = getaddrinfo (self->host, self->service, &self->hints, &self->result); } static void async_getaddrinfo_dispatch (struct async *async) { struct async_getaddrinfo *self = (struct async_getaddrinfo *) async; self->dispatcher (self->gai_result, self->result, self->user_data); self->result = NULL; } static void async_getaddrinfo_destroy (struct async *async) { struct async_getaddrinfo *self = (struct async_getaddrinfo *) async; free (self->host); free (self->service); if (self->result) freeaddrinfo (self->result); free (self); } static struct async_getaddrinfo * async_getaddrinfo (struct async_manager *manager, const char *host, const char *service, const struct addrinfo *hints) { struct async_getaddrinfo *self = xcalloc (1, sizeof *self); self->async = async_make (manager); if (host) self->host = xstrdup (host); if (service) self->service = xstrdup (service); if (hints) memcpy (&self->hints, hints, sizeof *hints); self->async.execute = async_getaddrinfo_execute; self->async.dispatcher = async_getaddrinfo_dispatch; self->async.destroy = async_getaddrinfo_destroy; async_run (&self->async); return self; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - typedef void (*async_getnameinfo_fn) (int, char *, char *, void *); struct async_getnameinfo { struct async async; ///< Parent object int gni_result; ///< Direct result from getnameinfo() char host[NI_MAXHOST]; ///< gni() result: host name char service[NI_MAXSERV]; ///< gni() result: service name struct sockaddr *address; ///< gni() argument: address socklen_t address_len; ///< Size of the address int flags; ///< gni() argument: flags async_getnameinfo_fn dispatcher; ///< Event dispatcher void *user_data; ///< User data }; static void async_getnameinfo_execute (struct async *async) { struct async_getnameinfo *self = (struct async_getnameinfo *) async; self->gni_result = getnameinfo (self->address, self->address_len, self->host, sizeof self->host, self->service, sizeof self->service, self->flags); } static void async_getnameinfo_dispatch (struct async *async) { struct async_getnameinfo *self = (struct async_getnameinfo *) async; self->dispatcher (self->gni_result, self->host, self->service, self->user_data); } static void async_getnameinfo_destroy (struct async *async) { struct async_getnameinfo *self = (struct async_getnameinfo *) async; free (self->address); free (self); } static struct async_getnameinfo * async_getnameinfo (struct async_manager *manager, const struct sockaddr *sa, socklen_t sa_len, int flags) { struct async_getnameinfo *self = xcalloc (1, sizeof *self); self->async = async_make (manager); self->address = memcpy (xmalloc (sa_len), sa, sa_len); self->address_len = sa_len; self->flags = flags; self->async.execute = async_getnameinfo_execute; self->async.dispatcher = async_getnameinfo_dispatch; self->async.destroy = async_getnameinfo_destroy; async_run (&self->async); return self; } #endif // LIBERTY_WANT_ASYNC // --- libuv-style write adaptor ----------------------------------------------- // Makes it possible to use iovec to write multiple data chunks at once. struct write_req { LIST_HEADER (struct write_req) struct iovec data; ///< Data to be written }; struct write_queue { struct write_req *head; ///< The head of the queue struct write_req *tail; ///< The tail of the queue size_t head_offset; ///< Offset into the head size_t len; }; static struct write_queue write_queue_make (void) { return (struct write_queue) {}; } static void write_queue_free (struct write_queue *self) { LIST_FOR_EACH (struct write_req, iter, self->head) { free (iter->data.iov_base); free (iter); } } static void write_queue_add (struct write_queue *self, struct write_req *req) { LIST_APPEND_WITH_TAIL (self->head, self->tail, req); self->len++; } static void write_queue_processed (struct write_queue *self, size_t len) { while (self->head && self->head_offset + len >= self->head->data.iov_len) { struct write_req *head = self->head; len -= (head->data.iov_len - self->head_offset); self->head_offset = 0; LIST_UNLINK_WITH_TAIL (self->head, self->tail, head); self->len--; free (head->data.iov_base); free (head); } self->head_offset += len; } static bool write_queue_is_empty (const struct write_queue *self) { return self->head == NULL; } // --- Message reader ---------------------------------------------------------- struct msg_reader { struct str buf; ///< Input buffer uint64_t offset; ///< Current offset in the buffer }; static struct msg_reader msg_reader_make (void) { return (struct msg_reader) { .buf = str_make (), .offset = 0 }; } static void msg_reader_free (struct msg_reader *self) { str_free (&self->buf); } static void msg_reader_compact (struct msg_reader *self) { str_remove_slice (&self->buf, 0, self->offset); self->offset = 0; } static void msg_reader_feed (struct msg_reader *self, const void *data, size_t len) { // TODO: have some mechanism to prevent flooding msg_reader_compact (self); str_append_data (&self->buf, data, len); } static void * msg_reader_get (struct msg_reader *self, size_t *len) { // Try to read in the length of the message if (self->offset + sizeof (uint64_t) > self->buf.len) return NULL; uint8_t *x = (uint8_t *) self->buf.str + self->offset; uint64_t msg_len = peek_u64be (x); if (msg_len < sizeof msg_len) { // The message is shorter than its header // TODO: have some mechanism to report errors return NULL; } if (self->offset + msg_len < self->offset) { // Trying to read an insane amount of data but whatever msg_reader_compact (self); return NULL; } // Check if we've got the full message in the buffer and return it if (self->offset + msg_len > self->buf.len) return NULL; // We have to subtract the header from the reported length void *data = self->buf.str + self->offset + sizeof msg_len; self->offset += msg_len; *len = msg_len - sizeof msg_len; return data; } // --- Message unpacker -------------------------------------------------------- struct msg_unpacker { const char *data; size_t offset; size_t len; }; static struct msg_unpacker msg_unpacker_make (const void *data, size_t len) { return (struct msg_unpacker) { .data = data, .len = len, .offset = 0 }; } static size_t msg_unpacker_get_available (const struct msg_unpacker *self) { return self->len - self->offset; } #define UNPACKER_INT_BEGIN \ if (self->len - self->offset < sizeof *value) \ return false; \ uint8_t *x = (uint8_t *) self->data + self->offset; \ self->offset += sizeof *value; static bool msg_unpacker_u8 (struct msg_unpacker *self, uint8_t *value) { UNPACKER_INT_BEGIN *value = x[0]; return true; } static bool msg_unpacker_u16 (struct msg_unpacker *self, uint16_t *value) { UNPACKER_INT_BEGIN *value = peek_u16be (x); return true; } static bool msg_unpacker_u32 (struct msg_unpacker *self, uint32_t *value) { UNPACKER_INT_BEGIN *value = peek_u32be (x); return true; } static bool msg_unpacker_u64 (struct msg_unpacker *self, uint64_t *value) { UNPACKER_INT_BEGIN *value = peek_u64be (x); return true; } #define msg_unpacker_i8(self, value) \ msg_unpacker_u8 ((self), (uint8_t *) (value)) #define msg_unpacker_i16(self, value) \ msg_unpacker_u16 ((self), (uint16_t *) (value)) #define msg_unpacker_i32(self, value) \ msg_unpacker_u32 ((self), (uint32_t *) (value)) #define msg_unpacker_i64(self, value) \ msg_unpacker_u64 ((self), (uint64_t *) (value)) #undef UNPACKER_INT_BEGIN // --- Message packer and writer ----------------------------------------------- // Use str_pack_*() or other methods to append to the internal buffer, then // flush it to get a nice frame. Handy for iovec. struct msg_writer { struct str buf; ///< Holds the message data }; static struct msg_writer msg_writer_make (void) { struct msg_writer self = { .buf = str_make () }; // Placeholder for message length str_append_data (&self.buf, "\x00\x00\x00\x00" "\x00\x00\x00\x00", 8); return self; } static void * msg_writer_flush (struct msg_writer *self, size_t *len) { // Update the message length uint64_t x = self->buf.len; uint8_t tmp[8] = { x >> 56, x >> 48, x >> 40, x >> 32, x >> 24, x >> 16, x >> 8, x }; memcpy (self->buf.str, tmp, sizeof tmp); *len = x; return str_steal (&self->buf); } // --- ASCII ------------------------------------------------------------------- #define TRIVIAL_STRXFRM(name, fn) \ static size_t \ name (char *dest, const char *src, size_t n) \ { \ size_t len = strlen (src); \ while (n-- && (*dest++ = (fn) (*src++))) \ ; \ return len; \ } static int tolower_ascii (int c) { return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c; } static int toupper_ascii (int c) { return c >= 'a' && c <= 'z' ? c - ('a' - 'A') : c; } TRIVIAL_STRXFRM (tolower_ascii_strxfrm, tolower_ascii) TRIVIAL_STRXFRM (toupper_ascii_strxfrm, toupper_ascii) static int strcasecmp_ascii (const char *a, const char *b) { int x; while (*a || *b) if ((x = tolower_ascii (*(const unsigned char *) a++) - tolower_ascii (*(const unsigned char *) b++))) return x; return 0; } static int strncasecmp_ascii (const char *a, const char *b, size_t n) { int x; while (n-- && (*a || *b)) if ((x = tolower_ascii (*(const unsigned char *) a++) - tolower_ascii (*(const unsigned char *) b++))) return x; return 0; } static bool iscntrl_ascii (int c) { return (c >= 0 && c < 32) || c == 0x7f; } static bool isalpha_ascii (int c) { c &= ~32; return c >= 'A' && c <= 'Z'; } static bool isdigit_ascii (int c) { return c >= '0' && c <= '9'; } static bool isalnum_ascii (int c) { return isalpha_ascii (c) || isdigit_ascii (c); } static bool isspace_ascii (int c) { return c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v'; } // --- UTF-8 ------------------------------------------------------------------- /// Return the value of the UTF-8 character at `*s` and advance the pointer /// to the next one. Returns -2 if there is only a partial but possibly valid /// character sequence, or -1 on other errors. Either way, `*s` is untouched. static int32_t utf8_decode (const char **s, size_t len) { // End of string, we go no further if (!len) return -1; // Find out how long the sequence is (0 for ASCII) unsigned mask = 0x80; unsigned sequence_len = 0; const uint8_t *p = (const uint8_t *) *s, *end = p + len; while ((*p & mask) == mask) { // Invalid start of sequence if (mask == 0xFE) return -1; mask |= mask >> 1; sequence_len++; } // In the middle of a character // or an overlong sequence (subset, possibly MUTF-8, not supported) if (sequence_len == 1 || *p == 0xC0 || *p == 0xC1) return -1; // Check the rest of the sequence uint32_t cp = *p++ & ~mask; while (sequence_len && --sequence_len) { if (p == end) return -2; if ((*p & 0xC0) != 0x80) return -1; cp = cp << 6 | (*p++ & 0x3F); } *s = (const char *) p; return cp; } static inline bool utf8_validate_cp (int32_t cp) { // RFC 3629, CESU-8 not allowed return cp >= 0 && cp <= 0x10FFFF && (cp < 0xD800 || cp > 0xDFFF); } /// Very rough UTF-8 validation, just makes sure codepoints can be iterated static bool utf8_validate (const char *s, size_t len) { const char *end = s + len; int32_t codepoint; while ((codepoint = utf8_decode (&s, end - s)) >= 0 && utf8_validate_cp (codepoint)) ; return s == end; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - struct utf8_iter { const char *s; ///< String iterator size_t len; ///< How many bytes remain }; static struct utf8_iter utf8_iter_make (const char *s) { return (struct utf8_iter) { .s = s, .len = strlen (s) }; } static int32_t utf8_iter_next (struct utf8_iter *self, size_t *len) { if (!self->len) return -1; const char *old = self->s; int32_t codepoint = utf8_decode (&self->s, self->len); if (!soft_assert (codepoint >= 0)) { // Invalid UTF-8 self->len = 0; return codepoint; } size_t advance = self->s - old; self->len -= advance; if (len) *len = advance; return codepoint; } // --- Base 64 ----------------------------------------------------------------- static uint8_t g_base64_table[256] = { 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 0, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, }; static inline bool base64_decode_group (const char **s, bool ignore_ws, struct str *output) { uint8_t input[4]; size_t loaded = 0; for (; loaded < 4; (*s)++) { if (!**s) return loaded == 0; if (!ignore_ws || !isspace_ascii (**s)) input[loaded++] = **s; } size_t len = 3; if (input[0] == '=' || input[1] == '=') return false; if (input[2] == '=' && input[3] != '=') return false; if (input[2] == '=') len--; if (input[3] == '=') len--; uint8_t a = g_base64_table[input[0]]; uint8_t b = g_base64_table[input[1]]; uint8_t c = g_base64_table[input[2]]; uint8_t d = g_base64_table[input[3]]; if (((a | b) | (c | d)) & 0x40) return false; uint32_t block = a << 18 | b << 12 | c << 6 | d; switch (len) { case 1: str_append_c (output, block >> 16); break; case 2: str_append_c (output, block >> 16); str_append_c (output, block >> 8); break; case 3: str_append_c (output, block >> 16); str_append_c (output, block >> 8); str_append_c (output, block); } return true; } static bool base64_decode (const char *s, bool ignore_ws, struct str *output) { while (*s) if (!base64_decode_group (&s, ignore_ws, output)) return false; return true; } static void base64_encode (const void *data, size_t len, struct str *output) { const char *alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; const uint8_t *p = data; size_t n_groups = len / 3; size_t tail = len - n_groups * 3; uint32_t group; for (; n_groups--; p += 3) { group = p[0] << 16 | p[1] << 8 | p[2]; str_append_c (output, alphabet[(group >> 18) & 63]); str_append_c (output, alphabet[(group >> 12) & 63]); str_append_c (output, alphabet[(group >> 6) & 63]); str_append_c (output, alphabet[ group & 63]); } switch (tail) { case 2: group = p[0] << 16 | p[1] << 8; str_append_c (output, alphabet[(group >> 18) & 63]); str_append_c (output, alphabet[(group >> 12) & 63]); str_append_c (output, alphabet[(group >> 6) & 63]); str_append_c (output, '='); break; case 1: group = p[0] << 16; str_append_c (output, alphabet[(group >> 18) & 63]); str_append_c (output, alphabet[(group >> 12) & 63]); str_append_c (output, '='); str_append_c (output, '='); default: break; } } // --- Utilities --------------------------------------------------------------- static void cstr_set (char **s, char *new) { free (*s); *s = new; } static void cstr_split (const char *s, const char *delimiters, bool ignore_empty, struct strv *out) { const char *begin = s, *end; while ((end = strpbrk (begin, delimiters))) { if (!ignore_empty || begin != end) strv_append_owned (out, xstrndup (begin, end - begin)); begin = ++end; } if (!ignore_empty || *begin) strv_append (out, begin); } static char * cstr_strip_in_place (char *s, const char *stripped_chars) { char *end = s + strlen (s); while (end > s && strchr (stripped_chars, end[-1])) *--end = '\0'; char *start = s + strspn (s, stripped_chars); if (start > s) memmove (s, start, end - start + 1); return s; } static void cstr_transform (char *s, int (*xform) (int c)) { for (; *s; s++) *s = xform (*s); } static char * cstr_cut_until (const char *s, const char *alphabet) { return xstrndup (s, strcspn (s, alphabet)); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static char * strv_join (const struct strv *v, const char *delimiter) { if (!v->len) return xstrdup (""); struct str result = str_make (); str_append (&result, v->vector[0]); for (size_t i = 1; i < v->len; i++) str_append_printf (&result, "%s%s", delimiter, v->vector[i]); return str_steal (&result); } static char *xstrdup_printf (const char *, ...) ATTRIBUTE_PRINTF (1, 2); static char * xstrdup_printf (const char *format, ...) { va_list ap; struct str tmp = str_make (); va_start (ap, format); str_append_vprintf (&tmp, format, ap); va_end (ap); return str_steal (&tmp); } static char * iconv_xstrdup (iconv_t conv, char *in, size_t in_len, size_t *out_len) { char *buf, *buf_ptr; size_t out_left, buf_alloc; buf = buf_ptr = xmalloc (out_left = buf_alloc = 64); char *in_ptr = in; if (in_len == (size_t) -1) // XXX: out_len will be one character longer than the string! in_len = strlen (in) + 1; while (iconv (conv, (char **) &in_ptr, &in_len, (char **) &buf_ptr, &out_left) == (size_t) -1) { if (errno != E2BIG) { free (buf); return NULL; } out_left += buf_alloc; char *new_buf = xrealloc (buf, buf_alloc <<= 1); buf_ptr += new_buf - buf; buf = new_buf; } if (out_len) *out_len = buf_alloc - out_left; return buf; } static bool set_boolean_if_valid (bool *out, const char *s) { if (!strcasecmp (s, "yes")) *out = true; else if (!strcasecmp (s, "no")) *out = false; else if (!strcasecmp (s, "on")) *out = true; else if (!strcasecmp (s, "off")) *out = false; else if (!strcasecmp (s, "true")) *out = true; else if (!strcasecmp (s, "false")) *out = false; else return false; return true; } static bool xstrtoul (unsigned long *out, const char *s, int base) { char *end; errno = 0; *out = strtoul (s, &end, base); return errno == 0 && !*end && end != s; } static bool read_line (FILE *fp, struct str *line) { str_reset (line); int c; while ((c = fgetc (fp)) != '\n') { if (c == EOF) return line->len != 0; if (c != '\r') str_append_c (line, c); } return true; } static char * format_host_port_pair (const char *host, const char *port) { // For when binding to the NULL address; would an asterisk be better? if (!host) host = ""; // IPv6 addresses mess with the "colon notation"; let's go with RFC 2732 if (strchr (host, ':')) return xstrdup_printf ("[%s]:%s", host, port); return xstrdup_printf ("%s:%s", host, port); } // --- File system ------------------------------------------------------------- static int lock_pid_file (const char *path, struct error **e) { // When using XDG_RUNTIME_DIR, the file needs to either have its // access time bumped every 6 hours, or have the sticky bit set int fd = open (path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH /* 644 */ | S_ISVTX /* sticky */); if (fd < 0) { error_set (e, "can't open `%s': %s", path, strerror (errno)); return -1; } set_cloexec (fd); struct flock lock = { .l_type = F_WRLCK, .l_start = 0, .l_whence = SEEK_SET, .l_len = 0, }; if (fcntl (fd, F_SETLK, &lock)) { error_set (e, "can't lock `%s': %s", path, strerror (errno)); xclose (fd); return -1; } struct str pid = str_make (); str_append_printf (&pid, "%ld", (long) getpid ()); if (ftruncate (fd, 0) || write (fd, pid.str, pid.len) != (ssize_t) pid.len) { error_set (e, "can't write to `%s': %s", path, strerror (errno)); xclose (fd); return -1; } str_free (&pid); // Intentionally not closing the file descriptor; it must stay alive // for the entire life of the application return fd; } static bool ensure_directory_existence (const char *path, struct error **e) { struct stat st; if (stat (path, &st)) { if (mkdir (path, S_IRWXU | S_IRWXG | S_IRWXO)) { return error_set (e, "cannot create directory `%s': %s", path, strerror (errno)); } } else if (!S_ISDIR (st.st_mode)) { return error_set (e, "cannot create directory `%s': %s", path, "file exists but is not a directory"); } return true; } static bool mkdir_with_parents (char *path, struct error **e) { char *p = path; // XXX: This is prone to the TOCTTOU problem. The solution would be to // rewrite the function using the {mkdir,fstat}at() functions from // POSIX.1-2008, ideally returning a file descriptor to the open // directory, with the current code as a fallback. Or to use chdir(). while ((p = strchr (p + 1, '/'))) { *p = '\0'; bool success = ensure_directory_existence (path, e); *p = '/'; if (!success) return false; } return ensure_directory_existence (path, e); } static bool str_append_env_path (struct str *output, const char *var, bool only_absolute) { const char *value = getenv (var); if (!value || (only_absolute && *value != '/')) return false; str_append (output, value); return true; } static void get_xdg_home_dir (struct str *output, const char *var, const char *def) { str_reset (output); if (!str_append_env_path (output, var, true)) { str_append_env_path (output, "HOME", false); str_append_c (output, '/'); str_append (output, def); } } static char * resolve_relative_filename_generic (struct strv *paths, const char *tail, const char *filename) { for (unsigned i = 0; i < paths->len; i++) { // As per XDG spec, relative paths are ignored if (*paths->vector[i] != '/') continue; char *file = xstrdup_printf ("%s/%s%s", paths->vector[i], tail, filename); struct stat st; if (!stat (file, &st)) return file; free (file); } return NULL; } static void get_xdg_config_dirs (struct strv *out) { struct str config_home = str_make (); get_xdg_home_dir (&config_home, "XDG_CONFIG_HOME", ".config"); strv_append (out, config_home.str); str_free (&config_home); const char *xdg_config_dirs; if (!(xdg_config_dirs = getenv ("XDG_CONFIG_DIRS")) || !*xdg_config_dirs) xdg_config_dirs = "/etc/xdg"; cstr_split (xdg_config_dirs, ":", true, out); } static char * resolve_relative_config_filename (const char *filename) { struct strv paths = strv_make (); get_xdg_config_dirs (&paths); char *result = resolve_relative_filename_generic (&paths, PROGRAM_NAME "/", filename); strv_free (&paths); return result; } static void get_xdg_data_dirs (struct strv *out) { struct str data_home = str_make (); get_xdg_home_dir (&data_home, "XDG_DATA_HOME", ".local/share"); strv_append (out, data_home.str); str_free (&data_home); const char *xdg_data_dirs; if (!(xdg_data_dirs = getenv ("XDG_DATA_DIRS")) || !*xdg_data_dirs) xdg_data_dirs = "/usr/local/share/:/usr/share/"; cstr_split (xdg_data_dirs, ":", true, out); } static char * resolve_relative_data_filename (const char *filename) { struct strv paths = strv_make (); get_xdg_data_dirs (&paths); char *result = resolve_relative_filename_generic (&paths, PROGRAM_NAME "/", filename); strv_free (&paths); return result; } static char * resolve_relative_runtime_filename_finish (struct str path) { // Try to create the file's ancestors; // typically the user will want to immediately create a file in there const char *last_slash = strrchr (path.str, '/'); if (last_slash && last_slash != path.str) { char *copy = xstrndup (path.str, last_slash - path.str); (void) mkdir_with_parents (copy, NULL); free (copy); } return str_steal (&path); } static char * resolve_relative_runtime_filename (const char *filename) { struct str path = str_make (); const char *runtime_dir = getenv ("XDG_RUNTIME_DIR"); if (runtime_dir && *runtime_dir == '/') str_append (&path, runtime_dir); else get_xdg_home_dir (&path, "XDG_DATA_HOME", ".local/share"); str_append_printf (&path, "/%s/%s", PROGRAM_NAME, filename); return resolve_relative_runtime_filename_finish (path); } /// This differs from resolve_relative_runtime_filename() in that we expect /// the filename to be something like a pattern for mkstemp(), so the resulting /// path can reside in a system-wide directory with no risk of a conflict. /// However, we have to take care about permissions. Do we even need this? static char * resolve_relative_runtime_template (const char *template) { struct str path = str_make (); const char *runtime_dir = getenv ("XDG_RUNTIME_DIR"); const char *tmpdir = getenv ("TMPDIR"); if (runtime_dir && *runtime_dir == '/') str_append_printf (&path, "%s/%s", runtime_dir, PROGRAM_NAME); else if (tmpdir && *tmpdir == '/') str_append_printf (&path, "%s/%s.%d", tmpdir, PROGRAM_NAME, geteuid ()); else str_append_printf (&path, "/tmp/%s.%d", PROGRAM_NAME, geteuid ()); str_append_printf (&path, "/%s", template); return resolve_relative_runtime_filename_finish (path); } static char * try_expand_tilde (const char *filename) { size_t until_slash = strcspn (filename, "/"); if (!until_slash) { struct str expanded = str_make (); str_append_env_path (&expanded, "HOME", false); str_append (&expanded, filename); return str_steal (&expanded); } int buf_len = sysconf (_SC_GETPW_R_SIZE_MAX); if (buf_len < 0) buf_len = 1024; struct passwd pwd, *success = NULL; char *user = xstrndup (filename, until_slash); char *buf = xmalloc (buf_len); while (getpwnam_r (user, &pwd, buf, buf_len, &success) == ERANGE) buf = xrealloc (buf, buf_len <<= 1); free (user); char *result = NULL; if (success) result = xstrdup_printf ("%s%s", pwd.pw_dir, filename + until_slash); free (buf); return result; } static char * resolve_filename (const char *filename, char *(*relative_cb) (const char *)) { // Absolute path is absolute if (*filename == '/') return xstrdup (filename); // We don't want to use wordexp() for this as it may execute /bin/sh if (*filename == '~') { // Paths to home directories ought to be absolute char *expanded = try_expand_tilde (filename + 1); if (expanded) return expanded; print_debug ("failed to expand the home directory in `%s'", filename); } return relative_cb (filename); } // --- OpenSSL ----------------------------------------------------------------- #ifdef LIBERTY_WANT_SSL #define XSSL_ERROR_TRY_AGAIN INT_MAX /// A small wrapper around SSL_get_error() to simplify further handling static int xssl_get_error (SSL *ssl, int result, const char **error_info) { int error = SSL_get_error (ssl, result); switch (error) { case SSL_ERROR_NONE: case SSL_ERROR_ZERO_RETURN: case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: return error; case SSL_ERROR_SYSCALL: if ((error = ERR_get_error ())) *error_info = ERR_reason_error_string (error); else if (result == 0) // An EOF that's not according to the protocol is still an EOF return SSL_ERROR_ZERO_RETURN; else { if (errno == EINTR) return XSSL_ERROR_TRY_AGAIN; *error_info = strerror (errno); } return SSL_ERROR_SSL; default: if ((error = ERR_get_error ())) *error_info = ERR_reason_error_string (error); else *error_info = "unknown error"; return SSL_ERROR_SSL; } } #endif // LIBERTY_WANT_SSL // --- Regular expressions ----------------------------------------------------- static regex_t * regex_compile (const char *regex, int flags, struct error **e) { regex_t *re = xmalloc (sizeof *re); int err = regcomp (re, regex, flags); if (!err) return re; char buf[regerror (err, re, NULL, 0)]; regerror (err, re, buf, sizeof buf); free (re); error_set (e, "%s: %s", "failed to compile regular expression", buf); return NULL; } static void regex_free (void *regex) { regfree (regex); free (regex); } // The cost of hashing a string is likely to be significantly smaller than that // of compiling the whole regular expression anew, so here is a simple cache. // Adding basic support for subgroups is easy: check `re_nsub' and output into // a `struct strv' (if all we want is the substrings). static struct str_map regex_cache_make (void) { return str_map_make (regex_free); } static bool regex_cache_match (struct str_map *cache, const char *regex, int flags, const char *s, struct error **e) { regex_t *re = str_map_find (cache, regex); if (!re) { re = regex_compile (regex, flags, e); if (!re) return false; str_map_set (cache, regex, re); } return regexec (re, s, 0, NULL, 0) != REG_NOMATCH; } // --- Simple file I/O --------------------------------------------------------- static bool read_file (const char *filename, struct str *output, struct error **e) { FILE *fp = fopen (filename, "rb"); if (!fp) { return error_set (e, "could not open `%s' for reading: %s", filename, strerror (errno)); } char buf[BUFSIZ]; size_t len; while ((len = fread (buf, 1, sizeof buf, fp)) == sizeof buf) str_append_data (output, buf, len); str_append_data (output, buf, len); bool success = !ferror (fp); fclose (fp); if (success) return true; return error_set (e, "error while reading `%s': %s", filename, strerror (errno)); } /// Overwrites filename contents with data; creates directories as needed static bool write_file (const char *filename, const void *data, size_t data_len, struct error **e) { char *dir = xstrdup (filename); bool parents_created = mkdir_with_parents (dirname (dir), e); free (dir); if (!parents_created) return false; FILE *fp = fopen (filename, "w"); if (!fp) { return error_set (e, "could not open `%s' for writing: %s", filename, strerror (errno)); } fwrite (data, data_len, 1, fp); bool success = !ferror (fp) && !fflush (fp) && (!fsync (fileno (fp)) || errno == EINVAL); fclose (fp); if (!success) { return error_set (e, "writing to `%s' failed: %s", filename, strerror (errno)); } return true; } /// Wrapper for write_file() that makes sure that the new data has been written /// to disk in its entirety before overriding the old file static bool write_file_safe (const char *filename, const void *data, size_t data_len, struct error **e) { // XXX: ideally we would also open the directory, use *at() versions // of functions and call fsync() on the directory as appropriate // FIXME: this should behave similarly to mkstemp(), just with 0666; // as it is, this function is not particularly safe char *temp = xstrdup_printf ("%s.new", filename); bool success = write_file (temp, data, data_len, e); if (success && !(success = !rename (temp, filename))) error_set (e, "could not rename `%s' to `%s': %s", temp, filename, strerror (errno)); free (temp); return success; } // --- Simple configuration ---------------------------------------------------- // This is the bare minimum to make an application configurable. // Keys are stripped of surrounding whitespace, values are not. struct simple_config_item { const char *key, *default_value, *description; }; static void simple_config_load_defaults (struct str_map *config, const struct simple_config_item *table) { for (; table->key != NULL; table++) if (table->default_value) str_map_set (config, table->key, xstrdup (table->default_value)); else str_map_set (config, table->key, NULL); } static bool simple_config_update_from_file (struct str_map *config, struct error **e) { char *filename = resolve_filename (PROGRAM_NAME ".conf", resolve_relative_config_filename); struct str s = str_make (); bool ok = !filename || read_file (filename, &s, e); size_t line_no = 0; for (char *x = strtok (s.str, "\r\n"); ok && x; x = strtok (NULL, "\r\n")) { line_no++; if (strchr ("#", *(x += strspn (x, " \t")))) continue; char *equals = strchr (x, '='); if (!equals || equals == x) ok = error_set (e, "%s: malformed line %zu", filename, line_no); else { char *end = equals++; do *end = '\0'; while (strchr (" \t", *--end)); str_map_set (config, x, xstrdup (equals)); } } str_free (&s); free (filename); return ok; } static char * write_configuration_file (const char *path_hint, const struct str *data, struct error **e) { struct str path = str_make (); if (path_hint) str_append (&path, path_hint); else { get_xdg_home_dir (&path, "XDG_CONFIG_HOME", ".config"); str_append (&path, "/" PROGRAM_NAME "/" PROGRAM_NAME ".conf"); } if (!write_file_safe (path.str, data->str, data->len, e)) { str_free (&path); return NULL; } return str_steal (&path); } static char * simple_config_write_default (const char *path_hint, const char *prolog, const struct simple_config_item *table, struct error **e) { struct str data = str_make (); if (prolog) str_append (&data, prolog); for (; table->key != NULL; table++) { str_append_printf (&data, "# %s\n", table->description); if (table->default_value) str_append_printf (&data, "%s=%s\n", table->key, table->default_value); else str_append_printf (&data, "#%s=\n", table->key); } char *path = write_configuration_file (path_hint, &data, e); str_free (&data); return path; } /// Convenience wrapper suitable for most simple applications static void call_simple_config_write_default (const char *path_hint, const struct simple_config_item *table) { static const char *prolog = "# " PROGRAM_NAME " " PROGRAM_VERSION " configuration file\n" "#\n" "# Relative paths are searched for in ${XDG_CONFIG_HOME:-~/.config}\n" "# /" PROGRAM_NAME " as well as in $XDG_CONFIG_DIRS/" PROGRAM_NAME "\n" "\n"; struct error *e = NULL; char *filename = simple_config_write_default (path_hint, prolog, table, &e); if (!filename) { print_error ("%s", e->message); error_free (e); exit (EXIT_FAILURE); } print_status ("configuration written to `%s'", filename); free (filename); } // --- Option handler ---------------------------------------------------------- // Simple wrapper for the getopt_long API to make it easier to use and maintain. #define OPT_USAGE_ALIGNMENT_COLUMN 30 ///< Alignment for option descriptions enum { OPT_OPTIONAL_ARG = (1 << 0), ///< The argument is optional OPT_LONG_ONLY = (1 << 1) ///< Ignore the short name in opt_string }; // All options need to have both a short name, and a long name. The short name // is what is returned from opt_handler_get(). It is possible to define a value // completely out of the character range combined with the OPT_LONG_ONLY flag. // // When `arg_hint' is defined, the option is assumed to have an argument. struct opt { int short_name; ///< The single-letter name const char *long_name; ///< The long name const char *arg_hint; ///< Option argument hint int flags; ///< Option flags const char *description; ///< Option description }; struct opt_handler { int argc; ///< The number of program arguments char **argv; ///< Program arguments const char *arg_hint; ///< Program arguments hint const char *description; ///< Description of the program const struct opt *opts; ///< The list of options size_t opts_len; ///< The length of the option array struct option *options; ///< The list of options for getopt char *opt_string; ///< The `optstring' for getopt }; static void opt_handler_free (struct opt_handler *self) { free (self->options); free (self->opt_string); } static struct opt_handler opt_handler_make (int argc, char **argv, const struct opt *opts, const char *arg_hint, const char *description) { struct opt_handler self = { .argc = argc, .argv = argv, .arg_hint = arg_hint, .description = description, }; size_t len = 0; for (const struct opt *iter = opts; iter->long_name; iter++) len++; self.opts = opts; self.opts_len = len; self.options = xcalloc (len + 1, sizeof *self.options); struct str opt_string = str_make (); for (size_t i = 0; i < len; i++) { const struct opt *opt = opts + i; struct option *mapped = self.options + i; mapped->name = opt->long_name; if (!opt->arg_hint) mapped->has_arg = no_argument; else if (opt->flags & OPT_OPTIONAL_ARG) mapped->has_arg = optional_argument; else mapped->has_arg = required_argument; mapped->val = opt->short_name; if (opt->flags & OPT_LONG_ONLY) continue; str_append_c (&opt_string, opt->short_name); if (opt->arg_hint) { str_append_c (&opt_string, ':'); if (opt->flags & OPT_OPTIONAL_ARG) str_append_c (&opt_string, ':'); } } self.opt_string = str_steal (&opt_string); return self; } static void opt_handler_usage (const struct opt_handler *self, FILE *stream) { struct str usage = str_make (); str_append_printf (&usage, "Usage: %s [OPTION]... %s\n", self->argv[0], self->arg_hint ? self->arg_hint : ""); str_append_printf (&usage, "%s\n\n", self->description); for (size_t i = 0; i < self->opts_len; i++) { struct str row = str_make (); const struct opt *opt = self->opts + i; if (!(opt->flags & OPT_LONG_ONLY)) str_append_printf (&row, " -%c, ", opt->short_name); else str_append (&row, " "); str_append_printf (&row, "--%s", opt->long_name); if (opt->arg_hint) str_append_printf (&row, (opt->flags & OPT_OPTIONAL_ARG) ? "[=%s]" : " %s", opt->arg_hint); // TODO: keep the indent if there are multiple lines if (row.len + 2 <= OPT_USAGE_ALIGNMENT_COLUMN) { str_append (&row, " "); str_append_printf (&usage, "%-*s%s\n", OPT_USAGE_ALIGNMENT_COLUMN, row.str, opt->description); } else str_append_printf (&usage, "%s\n%-*s%s\n", row.str, OPT_USAGE_ALIGNMENT_COLUMN, "", opt->description); str_free (&row); } fputs (usage.str, stream); str_free (&usage); } static int opt_handler_get (const struct opt_handler *self) { return getopt_long (self->argc, self->argv, self->opt_string, self->options, NULL); } // --- Unit tests -------------------------------------------------------------- // This is modeled after GTest, only remarkably simpler. typedef void (*test_fn) (const void *data, void *fixture); struct test_unit { LIST_HEADER (struct test_unit) char *name; ///< Name of the test size_t fixture_size; ///< Fixture size const void *user_data; ///< User data test_fn setup; ///< Fixture setup callback test_fn test; ///< The test test_fn teardown; ///< Fixture teardown callback }; struct test { struct test_unit *tests; ///< List of tests struct test_unit *tests_tail; ///< End of the list of tests struct str_map whitelist; ///< Whitelisted tests struct str_map blacklist; ///< Blacklisted tests unsigned list_only : 1; ///< Just list all tests unsigned can_fork : 1; ///< Forking doesn't break anything }; static void test_init (struct test *self, int argc, char **argv) { memset (self, 0, sizeof *self); self->whitelist = str_map_make (NULL); self->blacklist = str_map_make (NULL); // Usually this shouldn't pose a problem but let's make it optional self->can_fork = true; static const struct opt opts[] = { { 'd', "debug", NULL, 0, "run in debug mode" }, { 'h', "help", NULL, 0, "display this help and exit" }, { 'p', "pass", "NAME", 0, "only run tests glob-matching the name" }, { 's', "skip", "NAME", 0, "skip all tests glob-matching the name" }, { 'S', "single-process", NULL, 0, "don't fork for each test" }, { 'l', "list", NULL, 0, "list all available tests" }, { 0, NULL, NULL, 0, NULL } }; struct opt_handler oh = opt_handler_make (argc, argv, opts, NULL, "Unit test runner"); int c; while ((c = opt_handler_get (&oh)) != -1) switch (c) { case 'd': g_debug_mode = true; break; case 'h': opt_handler_usage (&oh, stdout); exit (EXIT_SUCCESS); case 'p': str_map_set (&self->whitelist, optarg, (void *) 1); break; case 's': str_map_set (&self->blacklist, optarg, (void *) 1); break; case 'S': self->can_fork = false; break; case 'l': self->list_only = true; break; default: print_error ("wrong options"); opt_handler_usage (&oh, stderr); exit (EXIT_FAILURE); } argc -= optind; argv += optind; if (argc) { opt_handler_usage (&oh, stderr); exit (EXIT_FAILURE); } opt_handler_free (&oh); } static void test_add_internal (struct test *self, const char *name, size_t fixture_size, const void *user_data, test_fn setup, test_fn test, test_fn teardown) { hard_assert (test != NULL); hard_assert (name != NULL); struct test_unit *unit = xcalloc (1, sizeof *unit); unit->name = xstrdup (name); unit->fixture_size = fixture_size; unit->user_data = user_data; unit->setup = setup; unit->test = test; unit->teardown = teardown; LIST_APPEND_WITH_TAIL (self->tests, self->tests_tail, unit); } #define test_add(self, name, fixture_type, user_data, setup, test, teardown) \ test_add_internal ((self), (name), sizeof (fixture_type), (user_data), \ (test_fn) (setup), (test_fn) (test), (test_fn) (teardown)) #define test_add_simple(self, name, user_data, test) \ test_add_internal ((self), (name), 0, (user_data), \ NULL, (test_fn) (test), NULL) static bool str_map_glob_match (struct str_map *self, const char *entry) { struct str_map_iter iter = str_map_iter_make (self); while (str_map_iter_next (&iter)) if (!fnmatch (iter.link->key, entry, 0)) return true; return false; } static bool test_is_allowed (struct test *self, const char *name) { bool allowed = true; if (self->whitelist.len) allowed = str_map_glob_match (&self->whitelist, name); if (self->blacklist.len) allowed &= !str_map_glob_match (&self->blacklist, name); return allowed; } static void test_unit_run (struct test_unit *self) { void *fixture = xcalloc (1, self->fixture_size); if (self->setup) self->setup (self->user_data, fixture); self->test (self->user_data, fixture); if (self->teardown) self->teardown (self->user_data, fixture); free (fixture); } static bool test_unit_run_forked (struct test_unit *self) { pid_t child = fork (); if (child == -1) { print_error ("%s: %s", "fork", strerror (errno)); return false; } else if (!child) { test_unit_run (self); _exit (EXIT_SUCCESS); } int status = 0; if (waitpid (child, &status, WUNTRACED) == -1) print_error ("%s: %s", "waitpid", strerror (errno)); else if (WIFSTOPPED (status)) { print_error ("test child has been stopped"); (void) kill (child, SIGKILL); } else if (WIFSIGNALED (status)) print_error ("test child was killed by signal %d", WTERMSIG (status)); else if (WEXITSTATUS (status) != 0) print_error ("test child exited with status %d", WEXITSTATUS (status)); else return true; return false; } static bool test_run_unit (struct test *self, struct test_unit *unit) { fprintf (stderr, "%s: ", unit->name); if (!self->can_fork) test_unit_run (unit); else if (!test_unit_run_forked (unit)) return false; fprintf (stderr, "OK\n"); return true; } static int test_run (struct test *self) { g_soft_asserts_are_deadly = true; bool failure = false; LIST_FOR_EACH (struct test_unit, iter, self->tests) { if (!test_is_allowed (self, iter->name)) continue; if (self->list_only) printf ("%s\n", iter->name); else if (!test_run_unit (self, iter)) failure = true; } LIST_FOR_EACH (struct test_unit, iter, self->tests) { free (iter->name); free (iter); } str_map_free (&self->whitelist); str_map_free (&self->blacklist); return failure; } // --- Connector --------------------------------------------------------------- #if defined LIBERTY_WANT_POLLER && defined LIBERTY_WANT_ASYNC // This is a helper that tries to establish a connection with any address on // a given list. Sadly it also introduces a bit of a callback hell. struct connector_target { LIST_HEADER (struct connector_target) struct connector *connector; ///< Parent connector char *hostname; ///< Target hostname or address char *service; ///< Target service name or port struct async *getaddrinfo_event; ///< Address resolution struct error *getaddrinfo_error; ///< Address resolution error struct addrinfo *results; ///< Resolved target struct addrinfo *iter; ///< Current endpoint }; static struct connector_target * connector_target_new (void) { struct connector_target *self = xcalloc (1, sizeof *self); return self; } static void connector_target_destroy (struct connector_target *self) { if (self->getaddrinfo_event) async_cancel (self->getaddrinfo_event); if (self->getaddrinfo_error) error_free (self->getaddrinfo_error); if (self->results) freeaddrinfo (self->results); free (self->hostname); free (self->service); free (self); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - struct connector { struct poller *poller; ///< Poller int socket; ///< Socket FD for the connection struct poller_fd connected_event; ///< We've connected or failed struct connector_target *targets; ///< Targets struct connector_target *targets_t; ///< Tail of targets void *user_data; ///< User data for callbacks // You may destroy the connector object in these two main callbacks: /// Connection has been successfully established; /// the hostname is mainly intended for TLS Server Name Indication void (*on_connected) (void *user_data, int socket, const char *hostname); /// Failed to establish a connection to either target void (*on_failure) (void *user_data); // Optional: /// Connecting to a new address void (*on_connecting) (void *user_data, const char *address); /// Connecting to the last address has failed void (*on_error) (void *user_data, const char *error); }; static void connector_notify_connecting (struct connector *self, struct connector_target *target, struct addrinfo *gai_iter) { if (!self->on_connecting) return; const char *real_host = target->hostname; char buf[NI_MAXHOST]; if (gai_iter) { // We don't really need this, so we can let it quietly fail int err = getnameinfo (gai_iter->ai_addr, gai_iter->ai_addrlen, buf, sizeof buf, NULL, 0, NI_NUMERICHOST); if (err) LOG_FUNC_FAILURE ("getnameinfo", gai_strerror (err)); else real_host = buf; } char *address = format_host_port_pair (real_host, target->service); self->on_connecting (self->user_data, address); free (address); } static void connector_notify_connected (struct connector *self, int fd) { set_blocking (fd, true); self->on_connected (self->user_data, fd, self->targets->hostname); } static void connector_prepare_next (struct connector *self) { struct connector_target *target = self->targets; if (!target->iter || !(target->iter = target->iter->ai_next)) { LIST_UNLINK_WITH_TAIL (self->targets, self->targets_t, target); connector_target_destroy (target); } } static void connector_handle_error (struct connector *self, const char *error); /// See if there's any target remaining at all -- it can however either still /// be waiting for address resolution to finish, or have already failed static bool connector_check_target (struct connector *self, struct connector_target *target) { if (!target) self->on_failure (self->user_data); else if (target->getaddrinfo_error) { connector_notify_connecting (self, target, NULL); connector_handle_error (self, target->getaddrinfo_error->message); } else if (target->results) return true; return false; } static void connector_step (struct connector *self) { struct connector_target *target = self->targets; if (!connector_check_target (self, target)) return; struct addrinfo *gai_iter = target->iter; hard_assert (gai_iter != NULL); connector_notify_connecting (self, target, gai_iter); int fd = socket (gai_iter->ai_family, gai_iter->ai_socktype, gai_iter->ai_protocol); if (fd == -1) { connector_handle_error (self, strerror (errno)); return; } set_cloexec (fd); set_blocking (fd, false); int yes = 1; soft_assert (setsockopt (fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof yes) != -1); if (!connect (fd, gai_iter->ai_addr, gai_iter->ai_addrlen)) connector_notify_connected (self, fd); else if (errno == EINPROGRESS) { self->connected_event.fd = self->socket = fd; poller_fd_set (&self->connected_event, POLLOUT); } else { connector_handle_error (self, strerror (errno)); xclose (fd); } } static void connector_handle_error (struct connector *self, const char *error) { if (self->on_error) self->on_error (self->user_data, error); connector_prepare_next (self); connector_step (self); } static void connector_on_ready (const struct pollfd *pfd, struct connector *self) { // See http://cr.yp.to/docs/connect.html if this doesn't work. // The second connect() method doesn't work with DragonflyBSD. int error = 0; socklen_t error_len = sizeof error; hard_assert (!getsockopt (pfd->fd, SOL_SOCKET, SO_ERROR, &error, &error_len)); if (error) { poller_fd_reset (&self->connected_event); xclose (self->socket); self->socket = -1; connector_handle_error (self, strerror (error)); } else { poller_fd_reset (&self->connected_event); self->socket = -1; connector_notify_connected (self, pfd->fd); } } static void connector_init (struct connector *self, struct poller *poller) { memset (self, 0, sizeof *self); self->poller = poller; self->socket = -1; self->connected_event = poller_fd_make (poller, self->socket); self->connected_event.user_data = self; self->connected_event.dispatcher = (poller_fd_fn) connector_on_ready; } static void connector_free (struct connector *self) { poller_fd_reset (&self->connected_event); if (self->socket != -1) xclose (self->socket); LIST_FOR_EACH (struct connector_target, iter, self->targets) connector_target_destroy (iter); } static void connector_on_getaddrinfo (int err, struct addrinfo *results, void *user_data) { struct connector_target *self = user_data; if (err) { error_set (&self->getaddrinfo_error, "%s: %s", "getaddrinfo", gai_strerror (err)); } self->results = self->iter = results; self->getaddrinfo_event = NULL; // We've been waiting for this address to be resolved if (self == self->connector->targets) connector_step (self->connector); } /// Connection will be attempted asynchronously once you add any target static void connector_add_target (struct connector *self, const char *hostname, const char *service) { struct connector_target *target = connector_target_new (); target->connector = self; target->hostname = xstrdup (hostname); target->service = xstrdup (service); struct addrinfo hints; memset (&hints, 0, sizeof hints); hints.ai_socktype = SOCK_STREAM; struct async_getaddrinfo *gai = async_getaddrinfo (&self->poller->common.async, hostname, service, &hints); gai->dispatcher = connector_on_getaddrinfo; gai->user_data = target; target->getaddrinfo_event = &gai->async; LIST_APPEND_WITH_TAIL (self->targets, self->targets_t, target); } #endif // defined LIBERTY_WANT_POLLER && defined LIBERTY_WANT_ASYNC // --- Simple network I/O ------------------------------------------------------ enum socket_io_result { SOCKET_IO_OK = 0, ///< Completed successfully SOCKET_IO_EOF, ///< Connection shut down by peer SOCKET_IO_ERROR ///< Connection error }; static enum socket_io_result socket_io_try_read (int socket_fd, struct str *rb) { // Flood protection, cannot afford to read too much at once size_t read_limit = rb->len + (1 << 20); if (read_limit < rb->len) read_limit = SIZE_MAX; ssize_t n_read; while (rb->len < read_limit) { str_reserve (rb, 1024); n_read = read (socket_fd, rb->str + rb->len, rb->alloc - rb->len - 1 /* null byte */); if (n_read > 0) { rb->str[rb->len += n_read] = '\0'; continue; } if (n_read == 0) return SOCKET_IO_EOF; if (errno == EAGAIN) return SOCKET_IO_OK; if (errno == EINTR) continue; int errno_save = errno; LOG_LIBC_FAILURE ("read"); errno = errno_save; return SOCKET_IO_ERROR; } return SOCKET_IO_OK; } static enum socket_io_result socket_io_try_write (int socket_fd, struct str *wb) { ssize_t n_written; while (wb->len) { n_written = write (socket_fd, wb->str, wb->len); if (n_written >= 0) { str_remove_slice (wb, 0, n_written); continue; } if (errno == EAGAIN) return SOCKET_IO_OK; if (errno == EINTR) continue; int errno_save = errno; LOG_LIBC_FAILURE ("write"); errno = errno_save; return SOCKET_IO_ERROR; } return SOCKET_IO_OK; } // --- Advanced configuration -------------------------------------------------- // This is a more powerful configuration format, adding key-value maps and // simplifying item validation and dynamic handling of changes. All strings // must be encoded in UTF-8. // // The syntax is roughly described by the following parsing expression grammar: // // config = entries eof # as if there were implicit curly braces around // entries = (newline* pair)* newline* // pair = key newline* lws '=' newline* value (&endobj / newline / eof) // key = string / !null !boolean lws [A-Za-z_][0-9A-Za-z_]* // value = object / string / integer / null / boolean // // object = lws '{' entries endobj // endobj = lws '}' // // quoted = lws '"' (!["\\] char / '\\' escape)* '"' // / lws '`' (![`] char)* '`' // string = (quoted)+ // char = [\0-\177] # or any Unicode codepoint in the UTF-8 encoding // escape = [\\"abfnrtv] / [xX][0-9A-Fa-f][0-9A-Fa-f]? / [0-7][0-7]?[0-7]? // // integer = lws [-+]? [0-9]+ # whatever strtoll() accepts on your system // null = lws 'null' // boolean = lws 'yes' / lws 'YES' / lws 'no' / lws 'NO' // / lws 'on' / lws 'ON' / lws 'off' / lws 'OFF' // / lws 'true' / lws 'TRUE' / lws 'false' / lws 'FALSE' // // newline = lws comment? '\n' // eof = lws comment? !. // lws = [ \t\r]* # linear whitespace (plus CR as it is insignificant) // comment = '#' (!'\n' .)* enum config_item_type { CONFIG_ITEM_NULL, ///< No value CONFIG_ITEM_OBJECT, ///< Key-value map CONFIG_ITEM_BOOLEAN, ///< Truth value CONFIG_ITEM_INTEGER, ///< Integer CONFIG_ITEM_STRING, ///< Arbitrary string of characters CONFIG_ITEM_STRING_ARRAY ///< Comma-separated list of strings }; struct config_item { enum config_item_type type; ///< Type of the item union { struct str_map object; ///< Key-value data bool boolean; ///< Boolean data int64_t integer; ///< Integer data struct str string; ///< String data } value; ///< The value of this item struct config_schema *schema; ///< Schema describing this value void *user_data; ///< User value attached by schema owner }; struct config_schema { const char *name; ///< Name of the item const char *comment; ///< User-readable description enum config_item_type type; ///< Required type const char *default_; ///< Default as a configuration snippet /// Check if the new value can be accepted. /// In addition to this, "type" and having a default is considered. bool (*validate) (const struct config_item *, struct error **e); /// The value has changed void (*on_change) (struct config_item *); }; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static const char * config_item_type_name (enum config_item_type type) { switch (type) { case CONFIG_ITEM_NULL: return "null"; case CONFIG_ITEM_BOOLEAN: return "boolean"; case CONFIG_ITEM_INTEGER: return "integer"; case CONFIG_ITEM_STRING: return "string"; case CONFIG_ITEM_STRING_ARRAY: return "string array"; default: hard_assert (!"invalid config item type value"); return NULL; } } static bool config_item_type_is_string (enum config_item_type type) { return type == CONFIG_ITEM_STRING || type == CONFIG_ITEM_STRING_ARRAY; } static void config_item_free (struct config_item *self) { switch (self->type) { case CONFIG_ITEM_STRING: case CONFIG_ITEM_STRING_ARRAY: str_free (&self->value.string); break; case CONFIG_ITEM_OBJECT: str_map_free (&self->value.object); default: break; } } static void config_item_destroy (struct config_item *self) { config_item_free (self); free (self); } /// Doesn't do any validations or handle schemas, just moves source data /// to the target item and destroys the source item static void config_item_move (struct config_item *self, struct config_item *source) { // Not quite sure how to handle that hard_assert (!source->schema); config_item_free (self); self->type = source->type; memcpy (&self->value, &source->value, sizeof source->value); free (source); } static struct config_item * config_item_new (enum config_item_type type) { struct config_item *self = xcalloc (1, sizeof *self); self->type = type; return self; } static struct config_item * config_item_null (void) { return config_item_new (CONFIG_ITEM_NULL); } static struct config_item * config_item_boolean (bool b) { struct config_item *self = config_item_new (CONFIG_ITEM_BOOLEAN); self->value.boolean = b; return self; } static struct config_item * config_item_integer (int64_t i) { struct config_item *self = config_item_new (CONFIG_ITEM_INTEGER); self->value.integer = i; return self; } static struct config_item * config_item_string (const struct str *s) { struct config_item *self = config_item_new (CONFIG_ITEM_STRING); self->value.string = str_make (); hard_assert (utf8_validate (self->value.string.str, self->value.string.len)); if (s) str_append_str (&self->value.string, s); return self; } static struct config_item * config_item_string_from_cstr (const char *s) { struct str tmp = str_make (); str_append (&tmp, s); struct config_item *self = config_item_string (&tmp); str_free (&tmp); return self; } static struct config_item * config_item_string_array (const struct str *s) { struct config_item *self = config_item_string (s); self->type = CONFIG_ITEM_STRING_ARRAY; return self; } static struct config_item * config_item_object (void) { struct config_item *self = config_item_new (CONFIG_ITEM_OBJECT); self->value.object = str_map_make ((str_map_free_fn) config_item_destroy); return self; } static bool config_schema_accepts_type (struct config_schema *self, enum config_item_type type) { if (self->type == type) return true; // This is a bit messy but it has its purpose if (config_item_type_is_string (self->type) && config_item_type_is_string (type)) return true; return !self->default_ && type == CONFIG_ITEM_NULL; } static bool config_item_validate_by_schema (struct config_item *self, struct config_schema *schema, struct error **e) { struct error *error = NULL; if (!config_schema_accepts_type (schema, self->type)) error_set (e, "invalid type of value, expected: %s%s", config_item_type_name (schema->type), !schema->default_ ? " (or null)" : ""); else if (schema->validate && !schema->validate (self, &error)) { error_set (e, "%s: %s", "invalid value", error->message); error_free (error); } else return true; return false; } static bool config_item_set_from (struct config_item *self, struct config_item *source, struct error **e) { struct config_schema *schema = self->schema; if (!schema) { // Easy, we don't know what this item is config_item_move (self, source); return true; } source->user_data = self->user_data; if (!config_item_validate_by_schema (source, schema, e)) return false; // Make sure the string subtype fits the schema if (config_item_type_is_string (source->type) && config_item_type_is_string (schema->type)) source->type = schema->type; config_item_move (self, source); // Notify owner about the change so that they can apply it if (schema->on_change) schema->on_change (self); return true; } static struct config_item * config_item_get (struct config_item *self, const char *path, struct error **e) { hard_assert (self->type == CONFIG_ITEM_OBJECT); struct strv v = strv_make (); cstr_split (path, ".", false, &v); struct config_item *result = NULL; size_t i = 0; while (true) { const char *key = v.vector[i]; if (!*key) error_set (e, "empty path element"); else if (!(self = str_map_find (&self->value.object, key))) error_set (e, "`%s' not found in object", key); else if (++i == v.len) result = self; else if (self->type != CONFIG_ITEM_OBJECT) error_set (e, "`%s' is not an object", key); else continue; break; } strv_free (&v); return result; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - struct config_writer { struct str *output; unsigned indent; }; static void config_item_write_object_innards (struct config_writer *self, struct config_item *object); static void config_item_write_string (struct str *output, const struct str *s) { str_append_c (output, '"'); for (size_t i = 0; i < s->len; i++) { unsigned char c = s->str[i]; if (c == '\n') str_append (output, "\\n"); else if (c == '\r') str_append (output, "\\r"); else if (c == '\t') str_append (output, "\\t"); else if (c == '\\') str_append (output, "\\\\"); else if (c == '"') str_append (output, "\\\""); else if (iscntrl_ascii (c)) str_append_printf (output, "\\x%02x", c); else str_append_c (output, c); } str_append_c (output, '"'); } static void config_item_write_object (struct config_writer *self, struct config_item *value) { char indent[self->indent + 1]; memset (indent, '\t', self->indent); indent[self->indent] = 0; str_append_c (self->output, '{'); if (value->value.object.len) { self->indent++; str_append_c (self->output, '\n'); config_item_write_object_innards (self, value); self->indent--; str_append (self->output, indent); } str_append_c (self->output, '}'); } static void config_item_write_value (struct config_writer *self, struct config_item *value) { switch (value->type) { case CONFIG_ITEM_NULL: str_append (self->output, "null"); break; case CONFIG_ITEM_BOOLEAN: str_append (self->output, value->value.boolean ? "on" : "off"); break; case CONFIG_ITEM_INTEGER: str_append_printf (self->output, "%" PRIi64, value->value.integer); break; case CONFIG_ITEM_STRING: case CONFIG_ITEM_STRING_ARRAY: config_item_write_string (self->output, &value->value.string); break; case CONFIG_ITEM_OBJECT: config_item_write_object (self, value); break; default: hard_assert (!"invalid item type"); } } // FIXME: shuffle code so that this isn't needed (serializer after the parser) static bool config_tokenizer_is_word_char (int c); static void config_item_write_kv_pair (struct config_writer *self, const char *key, struct config_item *value) { char indent[self->indent + 1]; memset (indent, '\t', self->indent); indent[self->indent] = 0; if (value->schema && value->schema->comment) str_append_printf (self->output, "%s# %s\n", indent, value->schema->comment); bool can_use_word = true; for (const char *p = key; *p; p++) if (!config_tokenizer_is_word_char (*p)) can_use_word = false; str_append (self->output, indent); if (can_use_word) str_append (self->output, key); else { struct str s = { .str = (char *) key, .len = strlen (key) }; config_item_write_string (self->output, &s); } str_append (self->output, " = "); config_item_write_value (self, value); str_append_c (self->output, '\n'); } static void config_item_write_object_innards (struct config_writer *self, struct config_item *object) { hard_assert (object->type == CONFIG_ITEM_OBJECT); struct str_map_iter iter = str_map_iter_make (&object->value.object); struct config_item *value; while ((value = str_map_iter_next (&iter))) config_item_write_kv_pair (self, iter.link->key, value); } static void config_item_write (struct config_item *value, bool object_innards, struct str *output) { struct config_writer writer = { .output = output, .indent = 0 }; if (object_innards) config_item_write_object_innards (&writer, value); else config_item_write_value (&writer, value); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - enum config_token { CONFIG_T_ABORT, ///< EOF or error CONFIG_T_WORD, ///< [a-zA-Z0-9_]+ CONFIG_T_EQUALS, ///< Equal sign CONFIG_T_LBRACE, ///< Left curly bracket CONFIG_T_RBRACE, ///< Right curly bracket CONFIG_T_NEWLINE, ///< New line CONFIG_T_NULL, ///< CONFIG_ITEM_NULL CONFIG_T_BOOLEAN, ///< CONFIG_ITEM_BOOLEAN CONFIG_T_INTEGER, ///< CONFIG_ITEM_INTEGER CONFIG_T_STRING ///< CONFIG_ITEM_STRING{,_LIST} }; static const char * config_token_name (enum config_token token) { switch (token) { case CONFIG_T_ABORT: return "end of input"; case CONFIG_T_WORD: return "word"; case CONFIG_T_EQUALS: return "equal sign"; case CONFIG_T_LBRACE: return "left brace"; case CONFIG_T_RBRACE: return "right brace"; case CONFIG_T_NEWLINE: return "newline"; case CONFIG_T_NULL: return "null value"; case CONFIG_T_BOOLEAN: return "boolean"; case CONFIG_T_INTEGER: return "integer"; case CONFIG_T_STRING: return "string"; default: hard_assert (!"invalid token value"); return NULL; } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - struct config_tokenizer { const char *p; ///< Current position in input size_t len; ///< How many bytes of input are left bool report_line; ///< Whether to count lines at all unsigned line; ///< Current line unsigned column; ///< Current column int64_t integer; ///< Parsed boolean or integer value struct str string; ///< Parsed string value }; /// Input has to be null-terminated anyway static struct config_tokenizer config_tokenizer_make (const char *p, size_t len) { return (struct config_tokenizer) { .p = p, .len = len, .report_line = true, .string = str_make () }; } static void config_tokenizer_free (struct config_tokenizer *self) { str_free (&self->string); } static bool config_tokenizer_is_word_char (int c) { return isalnum_ascii (c) || c == '_'; } static int config_tokenizer_advance (struct config_tokenizer *self) { int c = *self->p++; if (c == '\n' && self->report_line) { self->column = 0; self->line++; } else self->column++; self->len--; return c; } static void config_tokenizer_error (struct config_tokenizer *self, struct error **e, const char *format, ...) ATTRIBUTE_PRINTF (3, 4); static void config_tokenizer_error (struct config_tokenizer *self, struct error **e, const char *format, ...) { struct str description = str_make (); va_list ap; va_start (ap, format); str_append_vprintf (&description, format, ap); va_end (ap); if (self->report_line) error_set (e, "near line %u, column %u: %s", self->line + 1, self->column + 1, description.str); else if (self->len) error_set (e, "near character %u: %s", self->column + 1, description.str); else error_set (e, "near end: %s", description.str); str_free (&description); } static bool config_tokenizer_hexa_escape (struct config_tokenizer *self, struct str *output) { int i; unsigned char code = 0; for (i = 0; self->len && i < 2; i++) { unsigned char c = tolower_ascii (*self->p); if (c >= '0' && c <= '9') code = (code << 4) | (c - '0'); else if (c >= 'a' && c <= 'f') code = (code << 4) | (c - 'a' + 10); else break; config_tokenizer_advance (self); } if (!i) return false; str_append_c (output, code); return true; } static bool config_tokenizer_octal_escape (struct config_tokenizer *self, struct str *output) { int i; unsigned char code = 0; for (i = 0; self->len && i < 3; i++) { unsigned char c = *self->p; if (c >= '0' && c <= '7') code = (code << 3) | (c - '0'); else break; config_tokenizer_advance (self); } if (!i) return false; str_append_c (output, code); return true; } static bool config_tokenizer_escape_sequence (struct config_tokenizer *self, struct str *output, struct error **e) { if (!self->len) { config_tokenizer_error (self, e, "premature end of escape sequence"); return false; } unsigned char c; switch ((c = *self->p)) { case '"': break; case '\\': break; case 'a': c = '\a'; break; case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'v': c = '\v'; break; case 'x': case 'X': config_tokenizer_advance (self); if (config_tokenizer_hexa_escape (self, output)) return true; config_tokenizer_error (self, e, "invalid hexadecimal escape"); return false; default: if (config_tokenizer_octal_escape (self, output)) return true; config_tokenizer_error (self, e, "unknown escape sequence"); return false; } str_append_c (output, c); config_tokenizer_advance (self); return true; } static bool config_tokenizer_dq_string (struct config_tokenizer *self, struct str *output, struct error **e) { unsigned char c = config_tokenizer_advance (self); while (self->len) { if ((c = config_tokenizer_advance (self)) == '"') return true; if (c != '\\') str_append_c (output, c); else if (!config_tokenizer_escape_sequence (self, output, e)) return false; } config_tokenizer_error (self, e, "premature end of string"); return false; } static bool config_tokenizer_bt_string (struct config_tokenizer *self, struct str *output, struct error **e) { unsigned char c = config_tokenizer_advance (self); while (self->len) { if ((c = config_tokenizer_advance (self)) == '`') return true; str_append_c (output, c); } config_tokenizer_error (self, e, "premature end of string"); return false; } static bool config_tokenizer_string (struct config_tokenizer *self, struct str *output, struct error **e) { // Go-like strings, with C/AWK-like automatic concatenation while (self->len) { bool ok = true; if (isspace_ascii (*self->p) && *self->p != '\n') config_tokenizer_advance (self); else if (*self->p == '"') ok = config_tokenizer_dq_string (self, output, e); else if (*self->p == '`') ok = config_tokenizer_bt_string (self, output, e); else break; if (!ok) return false; } return true; } static enum config_token config_tokenizer_next (struct config_tokenizer *self, struct error **e) { // Skip over any whitespace between tokens while (self->len && isspace_ascii (*self->p) && *self->p != '\n') config_tokenizer_advance (self); if (!self->len) return CONFIG_T_ABORT; switch (*self->p) { case '\n': config_tokenizer_advance (self); return CONFIG_T_NEWLINE; case '=': config_tokenizer_advance (self); return CONFIG_T_EQUALS; case '{': config_tokenizer_advance (self); return CONFIG_T_LBRACE; case '}': config_tokenizer_advance (self); return CONFIG_T_RBRACE; case '#': // Comments go until newline while (self->len) if (config_tokenizer_advance (self) == '\n') return CONFIG_T_NEWLINE; return CONFIG_T_ABORT; case '"': case '`': str_reset (&self->string); if (!config_tokenizer_string (self, &self->string, e)) return CONFIG_T_ABORT; if (!utf8_validate (self->string.str, self->string.len)) { config_tokenizer_error (self, e, "not a valid UTF-8 string"); return CONFIG_T_ABORT; } return CONFIG_T_STRING; } // Our input doesn't need to be NUL-terminated but we want to use strtoll() char buf[48] = "", *end = buf; size_t buf_len = MIN (sizeof buf - 1, self->len); errno = 0; self->integer = strtoll (strncpy (buf, self->p, buf_len), &end, 10); if (errno == ERANGE) { config_tokenizer_error (self, e, "integer out of range"); return CONFIG_T_ABORT; } if (end != buf) { self->len -= end - buf; self->p += end - buf; return CONFIG_T_INTEGER; } if (!config_tokenizer_is_word_char (*self->p)) { config_tokenizer_error (self, e, "invalid input"); return CONFIG_T_ABORT; } str_reset (&self->string); do str_append_c (&self->string, config_tokenizer_advance (self)); while (self->len && config_tokenizer_is_word_char (*self->p)); if (!strcmp (self->string.str, "null")) return CONFIG_T_NULL; bool boolean; if (!set_boolean_if_valid (&boolean, self->string.str)) return CONFIG_T_WORD; self->integer = boolean; return CONFIG_T_BOOLEAN; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - struct config_parser { struct config_tokenizer tokenizer; ///< Tokenizer struct error *error; ///< Tokenizer error enum config_token token; ///< Current token in the tokenizer bool replace_token; ///< Replace the token }; static struct config_parser config_parser_make (const char *script, size_t len) { // As reading in tokens may cause exceptions, we wait for the first peek() // to replace the initial CONFIG_T_ABORT. return (struct config_parser) { .tokenizer = config_tokenizer_make (script, len), .replace_token = true, }; } static void config_parser_free (struct config_parser *self) { config_tokenizer_free (&self->tokenizer); if (self->error) error_free (self->error); } static enum config_token config_parser_peek (struct config_parser *self, jmp_buf out) { if (self->replace_token) { self->token = config_tokenizer_next (&self->tokenizer, &self->error); if (self->error) longjmp (out, 1); self->replace_token = false; } return self->token; } static bool config_parser_accept (struct config_parser *self, enum config_token token, jmp_buf out) { return self->replace_token = (config_parser_peek (self, out) == token); } static void config_parser_expect (struct config_parser *self, enum config_token token, jmp_buf out) { if (config_parser_accept (self, token, out)) return; config_tokenizer_error (&self->tokenizer, &self->error, "unexpected `%s', expected `%s'", config_token_name (self->token), config_token_name (token)); longjmp (out, 1); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // We don't need no generator, but a few macros will come in handy. // From time to time C just doesn't have the right features. #define PEEK() config_parser_peek (self, err) #define ACCEPT(token) config_parser_accept (self, token, err) #define EXPECT(token) config_parser_expect (self, token, err) #define SKIP_NL() do {} while (ACCEPT (CONFIG_T_NEWLINE)) static struct config_item *config_parser_parse_object (struct config_parser *self, jmp_buf out); static struct config_item * config_parser_parse_value (struct config_parser *self, jmp_buf out) { struct config_item *volatile result = NULL; jmp_buf err; if (setjmp (err)) { if (result) config_item_destroy (result); longjmp (out, 1); } if (ACCEPT (CONFIG_T_LBRACE)) { result = config_parser_parse_object (self, out); SKIP_NL (); EXPECT (CONFIG_T_RBRACE); return result; } if (ACCEPT (CONFIG_T_NULL)) return config_item_null (); if (ACCEPT (CONFIG_T_BOOLEAN)) return config_item_boolean (self->tokenizer.integer); if (ACCEPT (CONFIG_T_INTEGER)) return config_item_integer (self->tokenizer.integer); if (ACCEPT (CONFIG_T_STRING)) return config_item_string (&self->tokenizer.string); config_tokenizer_error (&self->tokenizer, &self->error, "unexpected `%s', expected a value", config_token_name (self->token)); longjmp (out, 1); } /// Parse a single "key = value" assignment into @a object static bool config_parser_parse_kv_pair (struct config_parser *self, struct config_item *object, jmp_buf out) { char *volatile key = NULL; jmp_buf err; if (setjmp (err)) { free (key); longjmp (out, 1); } SKIP_NL (); // Either this object's closing right brace if called recursively, // or end of file when called on a whole configuration file if (PEEK () == CONFIG_T_RBRACE || PEEK () == CONFIG_T_ABORT) return false; // I'm not sure how to feel about arbitrary keys but here they are if (!ACCEPT (CONFIG_T_STRING)) EXPECT (CONFIG_T_WORD); key = xstrdup (self->tokenizer.string.str); SKIP_NL (); EXPECT (CONFIG_T_EQUALS); SKIP_NL (); str_map_set (&object->value.object, key, config_parser_parse_value (self, err)); free (key); key = NULL; if (PEEK () == CONFIG_T_RBRACE || PEEK () == CONFIG_T_ABORT) return false; EXPECT (CONFIG_T_NEWLINE); return true; } /// Parse the inside of an object definition static struct config_item * config_parser_parse_object (struct config_parser *self, jmp_buf out) { struct config_item *volatile object = config_item_object (); jmp_buf err; if (setjmp (err)) { config_item_destroy (object); longjmp (out, 1); } while (config_parser_parse_kv_pair (self, object, err)) ; return object; } #undef PEEK #undef ACCEPT #undef EXPECT #undef SKIP_NL // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /// Parse a configuration snippet either as an object or a bare value. /// If it's the latter (@a single_value_only), no newlines may follow. static struct config_item * config_item_parse (const char *script, size_t len, bool single_value_only, struct error **e) { volatile struct config_parser parser = config_parser_make (script, len); struct config_parser *volatile self = (struct config_parser *) &parser; struct config_item *volatile object = NULL; jmp_buf err; if (setjmp (err)) { if (object) { config_item_destroy (object); object = NULL; } error_propagate (e, parser.error); parser.error = NULL; goto end; } if (single_value_only) { // This is really only intended for in-program configuration // and telling the line number would look awkward parser.tokenizer.report_line = false; object = config_parser_parse_value (self, err); } else object = config_parser_parse_object (self, err); config_parser_expect (self, CONFIG_T_ABORT, err); end: config_parser_free (self); return object; } /// Clone an item. Schema assignments aren't retained. static struct config_item * config_item_clone (struct config_item *self) { // Oh well, it saves code struct str tmp = str_make (); config_item_write (self, false, &tmp); struct config_item *result = config_item_parse (tmp.str, tmp.len, true, NULL); str_free (&tmp); return result; } static struct config_item * config_read_from_file (const char *filename, struct error **e) { struct config_item *root = NULL; struct str data = str_make (); if (!read_file (filename, &data, e)) goto end; struct error *error = NULL; if (!(root = config_item_parse (data.str, data.len, false, &error))) { error_set (e, "parse error in `%s': %s", filename, error->message); error_free (error); } end: str_free (&data); return root; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /// "user_data" is passed to allow its immediate use in validation callbacks static struct config_item * config_schema_initialize_item (struct config_schema *schema, struct config_item *parent, void *user_data, struct error **warning, struct error **e) { hard_assert (parent->type == CONFIG_ITEM_OBJECT); struct config_item *item = str_map_find (&parent->value.object, schema->name); if (item) { struct error *error = NULL; item->user_data = user_data; if (config_item_validate_by_schema (item, schema, &error)) goto keep_current; error_set (warning, "resetting configuration item " "`%s' to default: %s", schema->name, error->message); error_free (error); } struct error *error = NULL; if (schema->default_) item = config_item_parse (schema->default_, strlen (schema->default_), true, &error); else item = config_item_null (); if (item) item->user_data = user_data; if (error || !config_item_validate_by_schema (item, schema, &error)) { error_set (e, "invalid default for configuration item `%s': %s", schema->name, error->message); error_free (error); if (item) config_item_destroy (item); return NULL; } // This will free the old item if there was any str_map_set (&parent->value.object, schema->name, item); keep_current: // Make sure the string subtype fits the schema if (config_item_type_is_string (item->type) && config_item_type_is_string (schema->type)) item->type = schema->type; item->schema = schema; return item; } /// Assign schemas and user_data to multiple items at once; /// feel free to copy over and modify to suit your particular needs static void config_schema_apply_to_object (struct config_schema *schema_array, struct config_item *object, void *user_data) { while (schema_array->name) { struct error *warning = NULL, *e = NULL; config_schema_initialize_item (schema_array++, object, user_data, &warning, &e); if (warning) { print_warning ("%s", warning->message); error_free (warning); } if (e) exit_fatal ("%s", e->message); } } static void config_schema_call_changed (struct config_item *item) { if (item->type == CONFIG_ITEM_OBJECT) { struct str_map_iter iter = str_map_iter_make (&item->value.object); struct config_item *child; while ((child = str_map_iter_next (&iter))) config_schema_call_changed (child); } else if (item->schema && item->schema->on_change) item->schema->on_change (item); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // XXX: the callbacks may be overdesigned and of little to no practical use typedef void (*config_module_load_fn) (struct config_item *subtree, void *user_data); struct config_module { char *name; ///< Name of the subtree config_module_load_fn loader; ///< Module config subtree loader void *user_data; ///< User data }; static void config_module_destroy (struct config_module *self) { free (self->name); free (self); } struct config { struct str_map modules; ///< Toplevel modules struct config_item *root; ///< CONFIG_ITEM_OBJECT }; static struct config config_make (void) { return (struct config) { .modules = str_map_make ((str_map_free_fn) config_module_destroy) }; } static void config_free (struct config *self) { str_map_free (&self->modules); if (self->root) config_item_destroy (self->root); } static void config_register_module (struct config *self, const char *name, config_module_load_fn loader, void *user_data) { struct config_module *module = xcalloc (1, sizeof *module); module->name = xstrdup (name); module->loader = loader; module->user_data = user_data; str_map_set (&self->modules, name, module); } static void config_load (struct config *self, struct config_item *root) { hard_assert (root->type == CONFIG_ITEM_OBJECT); if (self->root) config_item_destroy (self->root); self->root = root; struct str_map_iter iter = str_map_iter_make (&self->modules); struct config_module *module; while ((module = str_map_iter_next (&iter))) { struct config_item *subtree = str_map_find (&root->value.object, module->name); // Silently fix inputs that only a lunatic user could create if (!subtree || subtree->type != CONFIG_ITEM_OBJECT) str_map_set (&root->value.object, module->name, (subtree = config_item_object ())); if (module->loader) module->loader (subtree, module->user_data); } } // --- Protocol modules -------------------------------------------------------- #include "liberty-proto.c"