aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoropal hart <opal@wowana.me>2018-11-09 11:46:51 +0000
committeropal hart <opal@wowana.me>2018-11-09 11:46:51 +0000
commit4c76d2c767429ef90bd70d9ff40afdc996a6253d (patch)
tree873f8a5aa1d40264bf696713696d7157cd04a05c
parent29b2d3b9810a9c173e0801c0fa17cd0f969eb26c (diff)
src/core: initial commit
-rw-r--r--src/core/achlys.c61
-rw-r--r--src/core/achlys.h3
-rw-r--r--src/core/array.c62
-rw-r--r--src/core/array.h20
-rw-r--r--src/core/chat.h7
-rw-r--r--src/core/common.h57
-rw-r--r--src/core/config.h14
-rw-r--r--src/core/event.h50
-rw-r--r--src/core/event_epoll.c89
-rw-r--r--src/core/hook.c105
-rw-r--r--src/core/hook.h17
-rw-r--r--src/core/interp.c164
-rw-r--r--src/core/interp.h14
-rw-r--r--src/core/memmem.c62
-rw-r--r--src/core/memmem.h5
-rw-r--r--src/core/module.c29
-rw-r--r--src/core/module.h36
-rw-r--r--src/core/module_dynamic.c.inc29
-rw-r--r--src/core/module_static.c.inc39
-rw-r--r--src/core/module_static.h12
-rw-r--r--src/core/net.c178
-rw-r--r--src/core/net.h12
-rw-r--r--src/core/parse.c135
-rw-r--r--src/core/parse.h14
-rw-r--r--src/core/str.c212
-rw-r--r--src/core/str.h43
26 files changed, 1469 insertions, 0 deletions
diff --git a/src/core/achlys.c b/src/core/achlys.c
new file mode 100644
index 0000000..71f67cf
--- /dev/null
+++ b/src/core/achlys.c
@@ -0,0 +1,61 @@
+#include "core/achlys.h"
+#include <signal.h>
+#include <unistd.h>
+#include "core/common.h"
+#include "core/event.h"
+#include "core/hook.h"
+#include "core/module.h"
+#include "core/interp.h"
+
+bool irc_init (void);
+static void achlys_crash (int);
+
+int achlys_exited = -1;
+
+int main (int argc, char** argv)
+{
+ (void) argc;
+ (void) argv;
+
+ signal(SIGABRT, achlys_crash);
+ signal(SIGILL, achlys_crash);
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGQUIT, achlys_crash);
+ signal(SIGSEGV, achlys_crash);
+ /* Initiate the achlys client */
+ if (!(
+ hook_init() &&
+ event_init() &&
+ interp_init() &&
+ module_init() &&
+ module_add_all()
+ )) {
+ achlys_exited = 1;
+ goto end_main;
+ }
+
+ hook_fire_c("achlys init");
+
+ while (achlys_exited < 0) {
+ hook_fire_c("achlys loop begin");
+ event_loop();
+ }
+
+end_main:
+ hook_fire_c("achlys free");
+ event_free();
+ return achlys_exited;
+}
+
+void achlys_die (int status)
+{
+ achlys_exited = status;
+}
+
+void achlys_crash (int signo)
+{
+ hook_fire_c("achlys crash");
+ /* kill ourselves so we have the opportunity to gen a coredump */
+ signal(signo, SIG_DFL);
+ kill(getpid(), signo);
+}
diff --git a/src/core/achlys.h b/src/core/achlys.h
new file mode 100644
index 0000000..bf73943
--- /dev/null
+++ b/src/core/achlys.h
@@ -0,0 +1,3 @@
+#pragma once
+
+void achlys_die (int);
diff --git a/src/core/array.c b/src/core/array.c
new file mode 100644
index 0000000..d63b058
--- /dev/null
+++ b/src/core/array.c
@@ -0,0 +1,62 @@
+#include "core/array.h"
+#include "core/common.h"
+#define HINT_EXPAND_BY 8
+
+bool array_resize (struct ax_arr* arr, size_t hint)
+{
+ void* new_array;
+
+ /* array is already big enough */
+ if (arr->size >= arr->len + hint)
+ return true;
+
+ arr->size += hint;
+ new_array = ax_realloc(arr->data, arr->size * sizeof (void*));
+
+ if (!new_array) {
+ ax_free(arr->data);
+ return false;
+ }
+
+ arr->data = new_array;
+ return true;
+}
+
+bool array_push (struct ax_arr* arr, void* elem)
+{
+ if (!array_resize(arr, HINT_EXPAND_BY))
+ return false;
+
+ arr->data[arr->len++] = elem;
+ return true;
+}
+
+void* array_get (const struct ax_arr arr, size_t idx)
+{
+ if (idx > arr.len)
+ return NULL;
+
+ return arr.data[idx];
+}
+
+ax_str array_join (const struct ax_arr arr)
+{
+ ax_str* elem = array_get(arr, 0);
+ ax_str ret;
+ size_t i;
+
+ if (!elem)
+ return STR_EMPTY;
+ str_concat(&ret, *elem);
+ for (i = 1; ( elem = array_get(arr, i) ); ++i) {
+ str_concat_c(&ret, "\36", 1); /* record separator */
+ str_concat(&ret, *elem);
+ }
+ return ret;
+}
+
+void array_free (struct ax_arr* arr)
+{
+ ax_free(arr->data);
+ memset(arr, 0, sizeof *arr);
+}
diff --git a/src/core/array.h b/src/core/array.h
new file mode 100644
index 0000000..ff5fa4d
--- /dev/null
+++ b/src/core/array.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <stddef.h>
+#include "core/str.h"
+
+#define ARRAY_EMPTY (ax_arr) {0, 0, NULL}
+#define array_foreach(i, arr, data) \
+ for (i = 0; (data = array_get(arr, i)), (i < arr.len); ++i)
+
+typedef struct ax_arr {
+ size_t len;
+ size_t size;
+ void** data;
+} ax_arr;
+
+bool array_resize (struct ax_arr*, size_t);
+bool array_push (struct ax_arr*, void*);
+void* array_get (struct ax_arr, size_t);
+void array_free (struct ax_arr*);
+ax_str array_join (const struct ax_arr);
diff --git a/src/core/chat.h b/src/core/chat.h
new file mode 100644
index 0000000..259fadf
--- /dev/null
+++ b/src/core/chat.h
@@ -0,0 +1,7 @@
+#pragma once
+
+enum chat_msgtype {
+ MSG_DISCO,
+ MSG_ERROR,
+ MSG_JOIN,
+};
diff --git a/src/core/common.h b/src/core/common.h
new file mode 100644
index 0000000..b056a7d
--- /dev/null
+++ b/src/core/common.h
@@ -0,0 +1,57 @@
+#pragma once
+
+/* system headers and common macros */
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include "config.h"
+#include "str.h"
+#include "array.h"
+
+#define ax_free(ptr) (free(ptr), (ptr) = NULL)
+#define ax_malloc malloc
+#define ax_realloc realloc
+
+#include <string.h>
+static inline bool ax_strncpy (char* dst, const char* src,
+ size_t dstlen)
+{
+ size_t srclen = strlen(src);
+ if (srclen >= dstlen)
+ return false;
+ (void) memcpy(dst, src, srclen + 1);
+ return true;
+}
+
+#define IRC_BOLD "\002"
+#define IRC_COLOUR "\003"
+#define IRC_FLASH "\006"
+#define IRC_CLEAR "\017"
+#define IRC_VIDEO "\026"
+#define IRC_ITALIC "\035"
+#define IRC_UL "\037"
+
+#define IRC_WHITE IRC_COLOUR "00"
+#define IRC_BLACK IRC_COLOUR "01"
+#define IRC_NAVY IRC_COLOUR "02"
+#define IRC_GREEN IRC_COLOUR "03"
+#define IRC_RED IRC_COLOUR "04"
+#define IRC_MAROON IRC_COLOUR "05"
+#define IRC_PURPLE IRC_COLOUR "06"
+#define IRC_BROWN IRC_COLOUR "07"
+#define IRC_YELLOW IRC_COLOUR "08"
+#define IRC_LIME IRC_COLOUR "09"
+#define IRC_TEAL IRC_COLOUR "10"
+#define IRC_CYAN IRC_COLOUR "11"
+#define IRC_BLUE IRC_COLOUR "12"
+#define IRC_PINK IRC_COLOUR "13"
+#define IRC_DGREY IRC_COLOUR "14"
+#define IRC_LGREY IRC_COLOUR "15"
diff --git a/src/core/config.h b/src/core/config.h
new file mode 100644
index 0000000..8b1bbc9
--- /dev/null
+++ b/src/core/config.h
@@ -0,0 +1,14 @@
+#pragma once
+
+/* This is used in lib/event_*.c to determine how many events are acted
+ * upon in a single call to event_wait().
+ */
+#define EVENTS_PER_LOOP 4
+
+/* Maximum number of open file descriptors. This is compile-time because
+ * it is used by some static arrays and initialisers. If/when these are
+ * dynamically reallocatable / reinitialisable, this will be a dynamic
+ * runtime option, and the MAX_FD constant will serve as a default value
+ * if the dynamic option is unset.
+ */
+#define MAX_FD 1024
diff --git a/src/core/event.h b/src/core/event.h
new file mode 100644
index 0000000..41c9cb7
--- /dev/null
+++ b/src/core/event.h
@@ -0,0 +1,50 @@
+#pragma once
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+/*
+ * implementation-specific defines
+ */
+
+#if 0
+#ifdef USE_KQUEUE
+#error TODO
+#endif
+#endif
+
+#ifdef USE_EPOLL
+typedef uint32_t event_mask;
+#include <sys/epoll.h>
+#define EV_READ EPOLLIN
+#define EV_WRITE EPOLLOUT
+#endif
+
+#if 0
+#ifdef USE_POLL
+typedef ...
+#include <poll.h>
+#define EV_READ
+#define EV_WRITE
+#endif
+
+#ifdef USE_SELECT
+typedef ...
+#include <sys/select.h>
+#define EV_READ
+#define EV_WRITE
+#endif
+#endif
+
+/*
+ * generic defines
+ */
+
+typedef void (*event_cb) (int, uint32_t);
+
+bool event_init (void);
+bool event_add (int, event_cb, event_mask);
+bool event_mod (int, event_cb, event_mask);
+bool event_del (int);
+void event_loop (void);
+void event_free (void);
diff --git a/src/core/event_epoll.c b/src/core/event_epoll.c
new file mode 100644
index 0000000..a344b9b
--- /dev/null
+++ b/src/core/event_epoll.c
@@ -0,0 +1,89 @@
+/* Handle file descriptor events, on systems that support epoll(2) */
+#include "core/event.h"
+#include <unistd.h>
+#include "core/common.h"
+
+static int event_fd;
+static event_cb event_fds[MAX_FD];
+
+static bool event_addmod_internal (int, event_cb, event_mask, int);
+
+bool event_init (void)
+{
+ event_fd = epoll_create(MAX_FD);
+ if (event_fd < 0) {
+ perror("(in event_init) epoll_create");
+ return false;
+ }
+ //return hook_fire("event init");
+ return true;
+}
+
+static bool event_addmod_internal (int fd, event_cb cb, event_mask mask,
+ int op)
+{
+ struct epoll_event ev = {
+ .events = mask,
+ .data.fd = fd,
+ };
+
+ if (epoll_ctl(event_fd, op, fd, &ev) < 0) {
+ perror("(in event_addmod_internal) epoll_ctl");
+ return false;
+ }
+
+ event_fds[fd] = cb;
+ return true;
+}
+
+bool event_add (int fd, event_cb cb, event_mask mask)
+{
+ return event_addmod_internal(fd, cb, mask, EPOLL_CTL_ADD);
+}
+
+bool event_mod (int fd, event_cb cb, event_mask mask)
+{
+ return event_addmod_internal(fd, cb, mask, EPOLL_CTL_MOD);
+}
+
+bool event_del (int fd)
+{
+ if (epoll_ctl(event_fd, EPOLL_CTL_DEL, fd, NULL) < 0) {
+ perror("(in event_del) epoll_ctl");
+ return false;
+ }
+
+ event_fds[fd] = NULL;
+ return true;
+}
+
+inline static void event_call (int fd, uint32_t events)
+{
+ event_fds[fd](fd, events);
+}
+
+void event_loop (void)
+{
+ struct epoll_event events[EVENTS_PER_LOOP];
+ struct epoll_event ev;
+ int i;
+
+ i = epoll_wait(event_fd, events, EVENTS_PER_LOOP, -1);
+ if (i < 0) {
+ perror("epoll_wait");
+ return;
+ }
+ if (!i)
+ return;
+
+ do {
+ ev = events[--i];
+ event_call(ev.data.fd, ev.events);
+ } while (i);
+}
+
+void event_free (void)
+{
+ memset(event_fds, 0, sizeof event_fds);
+ close(event_fd);
+}
diff --git a/src/core/hook.c b/src/core/hook.c
new file mode 100644
index 0000000..913e2b2
--- /dev/null
+++ b/src/core/hook.c
@@ -0,0 +1,105 @@
+#include "core/hook.h"
+#include <stdarg.h>
+#include "core/common.h"
+
+struct hook_type {
+ ax_str name;
+ struct ax_hook* head;
+ struct ax_hook* tail;
+};
+
+struct ax_hook {
+ hook_cb callback;
+ struct ax_hook* prev;
+ struct ax_hook* next;
+};
+
+struct ax_arr hookmap;
+
+bool hook_init (void)
+{
+ if (!array_resize(&hookmap, 64))
+ return false;
+
+ return true;
+}
+
+struct hook_type* hook_add_type (ax_str name)
+{
+ struct hook_type* type = ax_malloc(sizeof *type);
+
+ if (!type)
+ return NULL;
+
+ type->name = name;
+ type->head = NULL;
+ type->tail = NULL;
+ array_push(&hookmap, type);
+ return type;
+}
+
+struct hook_type* hook_find_type (ax_str name)
+{
+ size_t i;
+ struct hook_type* type;
+
+ array_foreach (i, hookmap, type) {
+ if (str_isequal(type->name, name))
+ return type;
+ }
+
+ return NULL;
+}
+
+/* hold on to the returned ptr if you ever want to delete the hook */
+struct ax_hook* hook_add (ax_str name, hook_cb callback)
+{
+ struct hook_type* type = hook_find_type(name);
+ struct ax_hook* hook;
+
+ hook = ax_malloc(sizeof *hook);
+ hook->callback = callback;
+ hook->next = NULL;
+
+ if (!type) {
+ type = hook_add_type(name);
+ if (!type) {
+ ax_free(hook);
+ return NULL;
+ }
+ type->head = hook;
+ }
+ if (type->tail)
+ type->tail->next = hook;
+
+ type->tail = hook;
+ return hook;
+}
+
+bool hook_fire (ax_str name, ...)
+{
+ va_list va;
+ struct hook_type* hooks = hook_find_type(name);
+ struct ax_hook* hook;
+ bool ret;
+
+ if (!hooks)
+ return true;
+
+ for (hook = hooks->head; hook; hook = hook->next) {
+ va_start(va, name);
+ ret = hook->callback(va);
+ va_end(va);
+ if (!ret)
+ break;
+ }
+
+ return ret;
+}
+
+void hook_del (struct ax_hook* hook)
+{
+ hook->prev->next = hook->next;
+ hook->next->prev = hook->prev;
+ ax_free(hook);
+}
diff --git a/src/core/hook.h b/src/core/hook.h
new file mode 100644
index 0000000..714567e
--- /dev/null
+++ b/src/core/hook.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <stdbool.h>
+#include "core/str.h"
+
+typedef bool (*hook_cb) (/* optional va_list */);
+struct hook_type;
+struct ax_hook;
+
+bool hook_init (void);
+struct hook_type* hook_add_type (ax_str);
+struct hook_type* hook_find_type (ax_str);
+struct ax_hook* hook_add (ax_str, hook_cb);
+#define hook_add_c(name, cb) hook_add(STR(name), (cb))
+bool hook_fire (ax_str name, ...);
+#define hook_fire_c(name, ...) hook_fire(STR(name), ##__VA_ARGS__)
+void hook_del (struct ax_hook*);
diff --git a/src/core/interp.c b/src/core/interp.c
new file mode 100644
index 0000000..b604150
--- /dev/null
+++ b/src/core/interp.c
@@ -0,0 +1,164 @@
+#include "core/interp.h"
+#include "core/common.h"
+#include "core/achlys.h"
+#include "core/parse.h"
+#include "client/disp.h"
+
+struct interp_cmd {
+ ax_str name;
+ ax_cmd_cb cb;
+};
+
+static ax_cmd_cb interp_getcmd (ax_str);
+static bool interp_call (ax_str cmd, ax_str argv);
+AX_CMD_CB (noop);
+AX_CMD_CB (exit);
+AX_CMD_CB (help);
+AX_CMD_CB (say);
+
+struct ax_arr cmdmap;
+bool show_ans = true;
+
+bool interp_init (void)
+{
+ if (!array_resize(&cmdmap, 256))
+ return false;
+
+ return interp_bind(STR(""), cmd_say)
+ && interp_bind(STR("/"), cmd_noop)
+ && interp_bind(STR("exit"), cmd_exit)
+ && interp_bind(STR("help"), cmd_help)
+ && interp_bind(STR("say"), cmd_say);
+}
+
+ax_cmd_cb interp_getcmd (ax_str name)
+{
+ /* note: we'll be naïve for now, use a hashtable later */
+ size_t i;
+ struct interp_cmd* cmd;
+
+ array_foreach (i, cmdmap, cmd) {
+ if (str_isequal(cmd->name, name))
+ return cmd->cb;
+ }
+
+ return NULL;
+}
+
+bool interp_bind (ax_str name, ax_cmd_cb cb)
+{
+ struct interp_cmd* cmd;
+
+ if (interp_getcmd(name))
+ return false;
+
+ if (!( cmd = ax_malloc(sizeof *cmd) ))
+ return false;
+
+ cmd->name = name;
+ cmd->cb = cb;
+ array_push(&cmdmap, cmd);
+ return true;
+}
+
+bool interp_queue (ax_str input)
+{
+ if (!input.len)
+ return true;
+ if (input.data[0] == '/') {
+ size_t next = 0;
+ ax_str cmd = str_strtok(input, ' ', 1, &next);
+ ax_str argv = str_strtok(input, '\r', next, NULL);
+
+ return interp_call(cmd, argv);
+ }
+ else
+ return interp_call(STR(""), input);
+}
+
+bool interp_call (ax_str cmd, ax_str argv)
+{
+ ax_str argvdup = STR_EMPTY;
+ ax_str ans = STR_EMPTY;
+ ax_cmd_cb cb;
+
+ cb = interp_getcmd(cmd);
+ if (cb) {
+ str_concat(&argvdup, argv);
+ ans = cb(cmd, argvdup);
+ }
+ else
+ return false;
+
+ if (show_ans)
+ disp_line(ans);
+ show_ans = true;
+ str_free(&ans);
+ return true;
+}
+
+void interp_show_ans (bool show)
+{
+ show_ans = show;
+}
+
+
+/* Core commands
+ *
+ * Commands related to specific functionality should be bound in the
+ * appropriate file (e.g. alias.c for /alias)
+ */
+
+/* AX_CMD_CB(name) is a macro that transforms roughly into
+ * the following:
+ * ax_cmd_cb cmd_<name> (ax_str cmd, ax_str argv)
+ *
+ * a copy of argv is passed to these functions so be sure to free
+ * if necessary
+ */
+
+AX_CMD_CB (noop)
+{
+ (void) cmd;
+
+ str_free(&argv);
+ return STR_EMPTY;
+}
+
+AX_CMD_CB (exit)
+{
+ (void) cmd;
+ long status;
+
+ if (str_isempty(argv))
+ status = 0L;
+ else if (!parse_getint(argv, &status, NULL)
+ || status > INT_MAX)
+ status = 255L;
+ achlys_die((int) status);
+
+ str_free(&argv);
+ return STR_EMPTY;
+}
+
+AX_CMD_CB (help)
+{
+ extern struct ax_arr cmdmap;
+ size_t i;
+ struct interp_cmd* cmdp;
+ (void) cmd;
+ str_free(&argv);
+
+ array_foreach (i, cmdmap, cmdp) {
+ disp_line(cmdp->name);
+ }
+
+ return STR_EMPTY;
+}
+
+AX_CMD_CB (say)
+{
+ (void) cmd;
+
+ return argv;
+}
diff --git a/src/core/interp.h b/src/core/interp.h
new file mode 100644
index 0000000..158ad5b
--- /dev/null
+++ b/src/core/interp.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <stdbool.h>
+#include "core/str.h"
+
+#define AX_CMD_CB(fun) \
+ ax_str cmd_##fun (ax_str cmd, ax_str argv)
+
+typedef ax_str (*ax_cmd_cb)(ax_str, ax_str);
+
+bool interp_init (void);
+bool interp_bind (ax_str, ax_cmd_cb);
+bool interp_queue (ax_str);
+void interp_show_ans (bool);
diff --git a/src/core/memmem.c b/src/core/memmem.c
new file mode 100644
index 0000000..f176c1c
--- /dev/null
+++ b/src/core/memmem.c
@@ -0,0 +1,62 @@
+#include "memmem.h"
+#include "common.h"
+/* note: below code was verbatim from freebsd impl; not even code format
+ * has been changed. hopefully this file will only be temporary anyway
+ */
+
+/*-
+ * Copyright (c) 2005 Pascal Gloor <pascal.gloor@spale.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+void *
+memmem(const void *l, size_t l_len, const void *s, size_t s_len)
+{
+ register char *cur, *last;
+ const char *cl = (const char *)l;
+ const char *cs = (const char *)s;
+
+ /* we need something to compare */
+ if (l_len == 0 || s_len == 0)
+ return NULL;
+
+ /* "s" must be smaller or equal to "l" */
+ if (l_len < s_len)
+ return NULL;
+
+ /* special case where s_len == 1 */
+ if (s_len == 1)
+ return memchr(l, (int)*cs, l_len);
+
+ /* the last position where its possible to find "s" in "l" */
+ last = (char *)cl + l_len - s_len;
+
+ for (cur = (char *)cl; cur <= last; cur++)
+ if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0)
+ return cur;
+
+ return NULL;
+}
diff --git a/src/core/memmem.h b/src/core/memmem.h
new file mode 100644
index 0000000..38f7b53
--- /dev/null
+++ b/src/core/memmem.h
@@ -0,0 +1,5 @@
+#pragma once
+
+#include <stddef.h>
+
+void* memmem(const void *l, size_t l_len, const void *s, size_t s_len);
diff --git a/src/core/module.c b/src/core/module.c
new file mode 100644
index 0000000..246e626
--- /dev/null
+++ b/src/core/module.c
@@ -0,0 +1,29 @@
+#include "core/module.h"
+#include "core/common.h"
+
+struct ax_arr modmap;
+
+bool module_init (void)
+{
+ if (!array_resize(&modmap, 64))
+ return false;
+ return true;
+}
+
+struct module* module_find (ax_str name)
+{
+ struct module* mptr;
+ size_t i;
+
+ array_foreach (i, modmap, mptr) {
+ if (str_isequal(mptr->header->name, name))
+ return mptr;
+ }
+ return NULL;
+}
+
+#ifdef USE_DYNAMIC
+ #include "core/module_dynamic.c.inc"
+#else
+ #include "core/module_static.c.inc"
+#endif
diff --git a/src/core/module.h b/src/core/module.h
new file mode 100644
index 0000000..49de4ab
--- /dev/null
+++ b/src/core/module.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include <stdbool.h>
+#include "core/str.h"
+
+struct module_header {
+ unsigned api_ver;
+ ax_str name;
+ bool (*init_cb) (void);
+ void (*free_cb) (void);
+};
+
+struct module {
+ char* path;
+ void* address;
+ const struct module_header* header;
+};
+
+/* API version 0 is not set in stone and will be bumped to version 1
+ * once the API is stabilised. please do not rely on API stability while
+ * writing plugins for achlys, until I am sure how I want it (probably
+ * by achlys v1.0)
+ */
+#ifdef USE_DYNAMIC
+ #define MODULE_DECLARE0(name, init_cb, free_cb) \
+ const struct module_header _module_header = \
+ { 0, pp_STR(#name), init_cb, free_cb }
+#else
+ #define MODULE_DECLARE0(name, init_cb, free_cb) \
+ const struct module_header _module_header_##name = \
+ { 0, pp_STR(#name), init_cb, free_cb }
+#endif
+
+bool module_init (void);
+bool module_add (const char*);
+bool module_add_all (void);
diff --git a/src/core/module_dynamic.c.inc b/src/core/module_dynamic.c.inc
new file mode 100644
index 0000000..93b7f92
--- /dev/null
+++ b/src/core/module_dynamic.c.inc
@@ -0,0 +1,29 @@
+/* included from module.c, for dynamic linkage */
+
+#include <dlfcn.h>
+
+bool module_add (const char* path)
+{
+ struct module* mptr = ax_malloc(sizeof *mptr);
+
+ /* only one API version is supported yet, so let's keep the version
+ * check simple and expand on it later as necessary for new APIs
+ */
+ if (!mptr
+ || !( mptr->address = dlopen(path, RTLD_LAZY) )
+ || !( mptr->header = dlsym(mptr->address, "_module_header") )
+ || ( mptr->header->api_ver != 0)
+ || !( mptr->header->init_cb() ))
+ return false;
+
+ mptr->path = strdup(path);
+ array_push(&modmap, mptr);
+ return true;
+}
+
+
+bool module_add_all (void)
+{
+ return true;
+}
+
diff --git a/src/core/module_static.c.inc b/src/core/module_static.c.inc
new file mode 100644
index 0000000..105447b
--- /dev/null
+++ b/src/core/module_static.c.inc
@@ -0,0 +1,39 @@
+/* included from module.c, for static linkage */
+
+#include "core/module_static.h"
+
+bool module_add (const char* virtpath)
+{
+ /* I don't know whether it makes sense to load/unload static linked
+ * modules... it's probably useful in order to call init/free, but
+ * this isn't a use case I need yet, so marking this as TODO
+ *
+ * for now just fail to load any module
+ */
+ ax_str name = STR(virtpath);
+ (void) name;
+
+ return false;
+}
+
+bool module_add_all (void)
+{
+ unsigned i;
+ struct module* mptr;
+
+ for (i = 0;
+ (mptr = ax_malloc(sizeof *mptr)) &&
+ (mptr->header = static_module_headers[i]);
+ ++i) {
+ if (mptr->header->api_ver != 0
+ || !mptr->header->init_cb()) {
+ ax_free(mptr);
+ return false;
+ }
+ mptr->address = 0;
+ mptr->path = strdup("(static)");
+ array_push(&modmap, mptr);
+ }
+ return true;
+}
+
diff --git a/src/core/module_static.h b/src/core/module_static.h
new file mode 100644
index 0000000..f2042cc
--- /dev/null
+++ b/src/core/module_static.h
@@ -0,0 +1,12 @@
+extern struct module_header _module_header_client;
+extern struct module_header _module_header_irc;
+//extern struct module_header _module_header_relayc;
+extern struct module_header _module_header_relayd;
+
+const struct module_header* static_module_headers[] = {
+ &_module_header_client,
+ &_module_header_irc,
+ //&_module_header_relayc,
+ &_module_header_relayd,
+ NULL,
+};
diff --git a/src/core/net.c b/src/core/net.c
new file mode 100644
index 0000000..fe17ceb
--- /dev/null
+++ b/src/core/net.c
@@ -0,0 +1,178 @@
+/* Handle networking primitives and expose file descriptors for use in
+ * the event loop
+ */
+#include "core/net.h"
+#include <fcntl.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include "core/common.h"
+#include "core/event.h"
+#include "core/parse.h"
+
+struct net_aistate {
+ struct addrinfo* ai_first;
+ struct addrinfo* ai_cur;
+ event_cb w_cb;
+ event_cb r_cb;
+};
+
+static void net_writeready (int, uint32_t);
+static void net_free (int);
+static struct net_aistate waiting_fds[MAX_FD] =
+ {{NULL, NULL, NULL, NULL}};
+/* AF_INET / AF_INET6:
+ * [ bind_host '@' ] ip.or.hostname [ '/' plain_port | '+' tls_port ]
+ * bind_host:
+ * '0.0.0.0' and 'v4' are equal
+ * '::' and 'v6' are equal
+ * 'any' listens on both v4 and v6 if possible
+ * AF_UNIX:
+ * 'unix:' /absolute/or/relative/path
+ */
+struct addrinfo* net_parse (ax_str addr,
+ ax_str port, bool* tls, struct addrinfo hints)
+{
+ struct addrinfo* ret;
+ if (tls) *tls = false;
+
+ if (str_find_c(addr, "unix:", 0) == 0) {
+ struct sockaddr_un* sun = ax_malloc(sizeof *sun);
+
+ ret = ax_malloc(sizeof *ret);
+
+ memset(ret, 0, sizeof *ret);
+ memset(sun, 0, sizeof *sun);
+
+ ret->ai_family = AF_UNIX;
+ ret->ai_socktype = hints.ai_socktype?
+ hints.ai_socktype : SOCK_STREAM;
+ if (!ax_strncpy(sun->sun_path, str_sub(addr, sizeof "unix:"),
+ sizeof sun->sun_path)) {
+ ax_free(ret);
+ return NULL;
+ }
+
+ ret->ai_addr = (struct sockaddr*) sun;
+
+ return ret;
+ }
+ else if (!str_isempty(addr)) {
+ ax_str serv;
+ size_t next = 0;
+ int ai_errno;
+
+ switch ( parse_tiltok(addr, &serv, &next, STR("@/+")) ) {
+ case '@': /* bind host */
+ return NULL; /* TODO */
+ case '+': /* tls port */
+ if (tls) *tls = true;
+ /* fallthru */
+ case '/':
+ serv.size = next;
+ parse_tileol(addr, &port, &next);
+ str_nullterm(&serv);
+ /* fallthru */
+ default: /* default plain port */
+ ai_errno = getaddrinfo(str_sub(serv, 0), str_sub(port, 0),
+ &hints, &ret);
+ if (ai_errno)
+ return NULL;
+
+ return ret;
+ }
+ }
+ else return NULL;
+}
+
+int net_init (struct addrinfo* ai, event_cb w_cb, event_cb r_cb)
+{
+ struct sockaddr* sa = ai->ai_addr;
+ int fd;
+ int flags;
+
+ switch (sa->sa_family) {
+ case AF_UNIX:
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ break;
+ case AF_INET:
+ case AF_INET6:
+ fd = socket(AF_INET, SOCK_STREAM, 0);
+ break;
+ default:
+ /* shouldn't be here */
+ assert(0);
+ }
+ if (fd < 0)
+ return fd;
+
+ flags = fcntl(fd, F_GETFL, 0);
+ if (flags < 0
+ || fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
+ perror("(in net_init) fcntl");
+ close(fd);
+ return -1;
+ }
+ waiting_fds[fd].ai_first =
+ waiting_fds[fd].ai_cur = ai;
+ waiting_fds[fd].w_cb = w_cb;
+ waiting_fds[fd].r_cb = r_cb;
+ return net_connect(fd);
+}
+
+int net_connect (int fd)
+{
+ struct net_aistate* state = waiting_fds + fd;
+
+ while (state->ai_cur) {
+ struct addrinfo* ai = state->ai_cur;
+ struct sockaddr* sa = ai->ai_addr;
+
+ state->ai_cur = ai->ai_next;
+ if (connect(fd, sa, ai->ai_addrlen) < 0
+ && errno != EINPROGRESS) {
+ perror("connect");
+ continue;
+ }
+
+ event_add(fd, net_writeready, EV_WRITE);
+ break;
+ }
+ if (!state->ai_cur) {
+ net_free(fd);
+ fd = -1;
+ }
+ return fd;
+}
+
+void net_writeready (int fd, uint32_t events)
+{
+ if (events & EV_WRITE) {
+ int flags;
+
+ /* connected */
+ flags = fcntl(fd, F_GETFL, 0);
+ if (flags < 0
+ || fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) < 0) {
+ perror("(in net_writeready) fcntl");
+ close(fd);
+ }
+ else {
+ waiting_fds[fd].w_cb(fd, events);
+ event_mod(fd, waiting_fds[fd].r_cb, EV_READ);
+ }
+
+ net_free(fd);
+ }
+ else {
+ net_connect(fd);
+ }
+}
+
+void net_free (int fd)
+{
+ freeaddrinfo(waiting_fds[fd].ai_first);
+ memset(&waiting_fds[fd], 0, sizeof waiting_fds[fd]);
+}
diff --git a/src/core/net.h b/src/core/net.h
new file mode 100644
index 0000000..2f19e1d
--- /dev/null
+++ b/src/core/net.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include <stdbool.h>
+#include <sys/types.h>
+#include "core/event.h"
+#include "core/str.h"
+
+struct addrinfo* net_parse (ax_str, ax_str, bool*,
+ struct addrinfo);
+int net_listen ();
+int net_init (struct addrinfo*, event_cb, event_cb);
+int net_connect (int);
diff --git a/src/core/parse.c b/src/core/parse.c
new file mode 100644
index 0000000..af62bd6
--- /dev/null
+++ b/src/core/parse.c
@@ -0,0 +1,135 @@
+#include "common.h"
+#include "parse.h"
+
+bool parse_getint (const ax_str src, long* ret, size_t* next)
+{
+ size_t offset = next? *next : 0;
+ ax_str conv;
+
+ conv = str_strtok(src, ' ', offset, next);
+
+ errno = 0;
+ *ret = strtol(conv.data, NULL, 10);
+ return !errno;
+}
+
+static inline bool parse_getword (const ax_str src,
+ ax_str* ret, size_t* next, char delim)
+{
+ size_t offset = next? *next : 0;
+
+ *ret = str_strtok(src, delim, offset, next);
+ return true;
+}
+
+bool parse_getstr_internal (const ax_str src, ax_str* ret,
+ size_t* next, bool raw)
+{
+ if (str_isempty(src)) {
+ *ret = STR_EMPTY;
+ return false;
+ }
+ else switch (src.data[next? *next : 0]) {
+ case '"':
+ return raw?
+ parse_dquo_raw(src, ret, next, true)
+ : parse_dquo(src, ret, next);
+ case '\'':
+ return raw?
+ parse_squo_raw(src, ret, next, true)
+ : parse_squo(src, ret, next);
+ default:
+ return parse_getword(src, ret, next, ' ');
+ }
+}
+
+bool parse_getstr_raw (const ax_str src, ax_str* ret,
+ size_t* next)
+{
+ return parse_getstr_internal(src, ret, next, true);
+}
+
+bool parse_getstr (const ax_str src, ax_str* ret,
+ size_t* next)
+{
+ return parse_getstr_internal(src, ret, next, false);
+}
+
+bool parse_tileol (const ax_str src, ax_str* ret,
+ size_t* next)
+{
+ return parse_getword(src, ret, next, '\4');
+}
+
+/* read src until next occurence in chars, then return chars.
+ * careful when using NUL in chars, since this may return NUL on error
+ */
+char parse_tiltok (const ax_str src, ax_str* ret,
+ size_t* next, const ax_str chars)
+{
+ bool lookup[256] = { false };
+ size_t offset = next? *next : 0;
+ unsigned i;
+ size_t len = 0;
+ bool is_end_of_string;
+ char* ch;
+
+ if (offset >= src.len)
+ return '\0';
+
+ *ret = src;
+ ret->len -= offset;
+ ret->size = 0;
+ ret->data += offset;
+
+ str_foreach_char (i, chars, ch)
+ lookup[(unsigned char) *ch] = true;
+
+ while (len < ret->len && !lookup[ (unsigned char) ret->data[len] ])
+ ++len;
+ if (next)
+ *next += len + 1;
+
+ is_end_of_string = len < ret->len;
+ ret->len = len;
+ return is_end_of_string? ret->data[len] : '\0';
+}
+
+/* read a double-quoted string, paying respect to escapes */
+bool parse_dquo_raw (const ax_str src, ax_str* ret,
+ size_t* next, bool keep_quotes)
+{
+ size_t offset = next? *next + 1 : 1;
+
+ *ret = str_strtok(src, '"', offset, next);
+ if (keep_quotes) {
+ --ret->data;
+ ret->len += 2;
+ }
+ return true;
+}
+
+bool parse_squo_raw (const ax_str src, ax_str* ret,
+ size_t* next, bool keep_quotes)
+{
+ size_t offset = next? *next + 1 : 1;
+
+ *ret = str_strtok(src, '\'', offset, next);
+ if (keep_quotes) {
+ --ret->data;
+ ret->len += 2;
+ }
+ return true;
+}
+
+bool parse_dquo (const ax_str src, ax_str* ret,
+ size_t* next)
+{
+ return parse_dquo_raw(src, ret, next, false);
+}
+
+bool parse_squo (const ax_str src, ax_str* ret,
+ size_t* next)
+{
+ return parse_squo_raw(src, ret, next, false);
+}
diff --git a/src/core/parse.h b/src/core/parse.h
new file mode 100644
index 0000000..e52dc99
--- /dev/null
+++ b/src/core/parse.h
@@ -0,0 +1,14 @@
+#pragma once
+
+bool parse_getint (ax_str, long*, size_t*);
+bool parse_getstr_raw (ax_str, ax_str*, size_t*);
+bool parse_getstr (ax_str, ax_str*, size_t*);
+bool parse_tileol (ax_str, ax_str*, size_t*);
+char parse_tiltok (ax_str, ax_str*, size_t*,
+ const ax_str);
+bool parse_dquo_raw (ax_str, ax_str*, size_t*,
+ bool keep_quotes);
+bool parse_squo_raw (ax_str, ax_str*, size_t*,
+ bool keep_quotes);
+bool parse_dquo (ax_str, ax_str*, size_t*);
+bool parse_squo (ax_str, ax_str*, size_t*);
diff --git a/src/core/str.c b/src/core/str.c
new file mode 100644
index 0000000..8f4b2d4
--- /dev/null
+++ b/src/core/str.c
@@ -0,0 +1,212 @@
+#include "core/str.h"
+#include "core/common.h"
+/* #define _GNU_SOURCE
+ * we do this to use system provided memmem() but not all systems we
+ * target might support this, so for now just define our own memmem()
+ */
+#include <string.h>
+#include "memmem.h"
+/* #undef _GNU_SOURCE */
+
+/* append NUL terminator for when we have a nonbinary string and we need
+ * to easily find end of string
+ */
+void str_nullterm (ax_str* str)
+{
+ assert(str->size >= str->len + 1);
+
+ str->data[str->len] = '\0';
+}
+
+inline static size_t str_nextpow (size_t hint)
+{
+ size_t ret = 1;
+
+ if (hint >= SIZE_MAX / 2)
+ ret = SIZE_MAX;
+ else while (ret < hint)
+ ret <<= 1;
+
+ return ret;
+}
+
+/* reallocate string */
+bool str_resize (ax_str* str, size_t hint)
+{
+ char* new_str;
+
+ /* string is already big enough */
+ if (str->size >= hint + 1)
+ return true;
+
+ str->size = str_nextpow(hint + 1);
+ new_str = ax_realloc(str->data, str->size);
+
+ if (!new_str) {
+ str->data = NULL;
+ return false;
+ }
+
+ str->data = new_str;
+ return true;
+}
+
+/* concatenate src to end of dst */
+bool str_concat (ax_str* dst, const ax_str src)
+{
+ /* no use adding nothing to the end of the string */
+ if (str_isempty(src))
+ return true;
+ if (!str_resize(dst, src.len + dst->len))
+ return false;
+ memcpy(dst->data + dst->len, src.data, src.len);
+ dst->len += src.len;
+ str_nullterm(dst);
+ return true;
+}
+
+bool str_concat_c (ax_str* dst, const char* src, ssize_t len)
+{
+ if (len < 0)
+ len = strlen(src);
+ if (!str_resize(dst, len + dst->len))
+ return false;
+ memcpy(dst->data + dst->len, src, len);
+ dst->len += len;
+ str_nullterm(dst);
+ return true;
+}
+
+/* reallocate str->data "in-place", saves us from having to define
+ * temp variables everywhere
+ */
+bool str_dup (ax_str* str)
+{
+ ax_str newstr = STR_EMPTY;
+ bool ret = str_concat(&newstr, *str);
+
+ if (!ret)
+ return false;
+
+ str->len = newstr.len;
+ str->size = newstr.size;
+ str->data = newstr.data;
+ return true;
+}
+
+/* get pointer to underlying str.data at 'offset' bytes */
+char* str_sub (const ax_str str, size_t offset)
+{
+ if (offset >= str.len)
+ return NULL;
+
+ return str.data + offset;
+}
+
+bool str_isequal (const ax_str a, const ax_str b)
+{
+ if (a.len != b.len)
+ return false;
+ return memcmp(a.data, b.data, a.len) == 0;
+}
+
+int str_find (ax_str haystack, ax_str needle,
+ unsigned offset)
+{
+ char* idx;
+
+ if ((size_t) offset > haystack.len)
+ return -1;
+
+ idx = memmem(haystack.data + offset, haystack.len - offset,
+ needle.data, needle.len);
+ if (!idx)
+ return -1;
+ return (int) (idx - haystack.data);
+}
+
+/* replace all occurences of 'search' in 'str' with 'replace'.
+ * str_free() after use
+ */
+ax_str str_replace (const ax_str str,
+ const ax_str search, const ax_str replace)
+{
+ ax_str ret = STR_EMPTY;
+ const char* pos;
+ const char* ptr = str.data;
+
+ /* use sane initial buffer size */
+ str_resize(&ret, str.len);
+
+ assert(ptr >= str.data);
+ while (ptr && (unsigned)(ptr - str.data) < str.len) {
+ pos = memmem(ptr, str.len - (ptr - str.data),
+ search.data, search.len);
+ if (pos) {
+ str_concat_c(&ret, ptr, pos - ptr);
+ str_concat(&ret, replace);
+ pos += search.len;
+ }
+ else
+ str_concat_c(&ret, ptr, (str.data + str.len) - ptr);
+ ptr = pos;
+ }
+ str_nullterm(&ret);
+ return ret;
+}
+
+/* returned ax_str points to the original data in haystack, be careful
+ */
+ax_str str_strtok (const ax_str haystack, char needle,
+ size_t offset, size_t* next)
+{
+ ax_str ret = haystack;
+ size_t len = 0;
+
+ ret.len -= offset;
+ ret.size = 0;
+ ret.data += offset;
+
+ while (len < ret.len && ret.data[len] != needle)
+ ++len;
+ if (next) {
+ *next = offset + len;
+ while (*next < haystack.len && haystack.data[*next] == needle)
+ ++(*next);
+ }
+
+ ret.len = len;
+ return ret;
+}
+
+void str_lchop (ax_str* str, size_t offset)
+{
+ assert(offset <= str->len);
+ memmove(str->data, str->data + offset, str->len - offset + 1);
+ str->len -= offset;
+}
+
+size_t str_fwrite (const ax_str str, FILE* fd)
+{
+ return fwrite(str.data, sizeof (char), str.len, fd);
+}
+
+/* free underlying data structure */
+void str_free (ax_str* str)
+{
+ ax_free(str->data);
+ memset(str, 0, sizeof *str);
+}
+
+ax_str str_vsprintf (const char* fmt, va_list va)
+{
+ ax_str ret = STR_EMPTY;
+ int len;
+
+ len = vsnprintf(NULL, 0, fmt, va);
+ str_resize(&ret, len);
+ ret.len = (size_t) len;
+ (void) vsprintf(ret.data, fmt, va);
+
+ return ret;
+}
diff --git a/src/core/str.h b/src/core/str.h
new file mode 100644
index 0000000..89e81a0
--- /dev/null
+++ b/src/core/str.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#define pp_strlen(str) \
+ (sizeof (str) / sizeof (str)[0] - sizeof (str)[0])
+#define STR_EMPTY (ax_str) {0, 0, NULL}
+#define STR(cstr) \
+ (ax_str) {strlen(cstr), 0, (char*) cstr}
+#define pp_STR(cstr) \
+ (ax_str) {pp_strlen(cstr), 0, (char*) cstr}
+#define C(str) (int) (str).len, (str).data
+#define str_foreach_char(i, str, ch) \
+ for (i = 0; (ch = str_sub(str, i)), (i < str.len); ++i)
+
+typedef struct ax_str {
+ size_t len;
+ size_t size;
+ char* data;
+} ax_str;
+
+void str_nullterm (struct ax_str*);
+bool str_resize (struct ax_str*, size_t);
+bool str_concat (struct ax_str*, const struct ax_str);
+bool str_concat_c (struct ax_str*, const char*, ssize_t);
+struct ax_str str_vsprintf (const char*, va_list);
+bool str_dup (struct ax_str*);
+char* str_sub (struct ax_str, size_t);
+bool str_isequal (struct ax_str, struct ax_str);
+#define str_isempty(str) (!(str).len)
+#define str_isnull(str) (!(str).data)
+int str_find (struct ax_str haystack, struct ax_str needle,
+ unsigned offset);
+#define str_find_c(haystack, needle, offset) \
+ str_find(haystack, STR(needle), offset)
+struct ax_str str_replace (struct ax_str str, struct ax_str search,
+ struct ax_str replace);
+struct ax_str str_strtok (struct ax_str, char, size_t, size_t*);
+void str_lchop (ax_str*, size_t);
+size_t str_fwrite (struct ax_str, FILE*);
+void str_free (struct ax_str*);