rough framework master
authorJustin Wind <justin.wind+git@gmail.com>
Thu, 25 Mar 2021 18:21:58 +0000 (11:21 -0700)
committerJustin Wind <justin.wind+git@gmail.com>
Tue, 13 Apr 2021 17:57:30 +0000 (10:57 -0700)
24 files changed:
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
README.md [new file with mode: 0644]
bsd_queue.3 [new file with mode: 0644]
bsd_queue.h [new file with mode: 0644]
command.c [new file with mode: 0644]
command.h [new file with mode: 0644]
common.c [new file with mode: 0644]
common.h [new file with mode: 0644]
connections.c [new file with mode: 0644]
connections.h [new file with mode: 0644]
db.c [new file with mode: 0644]
db.h [new file with mode: 0644]
lua_interface.c [new file with mode: 0644]
lua_interface.h [new file with mode: 0644]
main.c [new file with mode: 0644]
notify.c [new file with mode: 0644]
notify.h [new file with mode: 0644]
server.c [new file with mode: 0644]
server.h [new file with mode: 0644]
version.h [new file with mode: 0644]
version.sh [new file with mode: 0755]
workqueue.c [new file with mode: 0644]
workqueue.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..ce4f229
--- /dev/null
@@ -0,0 +1,5 @@
+*.o
+lemu
+conf
+.vscode
+*.depend
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..e20df4b
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,88 @@
+#
+#
+#
+
+# hard paths to development versions of libraries
+LIBEVENT_PATH ?= /usr
+LIBEVENT_H_PATH ?= $(LIBEVENT_PATH)/include
+LIBEVENT_CPPFLAGS = -I$(LIBEVENT_H_PATH)
+LIBEVENT_LIB_PATH ?= $(LIBEVENT_PATH)/lib
+LIBEVENT_LDFLAGS = -L$(LIBEVENT_LIB_PATH) -levent -levent_pthreads -levent_openssl
+
+# another library
+LIBUUID_PATH ?= /usr
+LIBUUID_H_PATH ?= $(LIBUUID_PATH)/include
+LIBUUID_CPPFLAGS = -I$(LIBUUID_H_PATH)
+LIBUUID_LIB_PATH ?= $(LIBUUID_PATH)/lib
+LIBUUID_LDFLAGS = -L$(LIBUUID_LIB_PATH) -lossp-uuid
+
+# yet another
+OPENSSL_PATH ?= /usr
+OPENSSL_H_PATH ?= $(OPENSSL_PATH)/include
+OPENSSL_CPPFLAGS = 
+OPENSSL_LIB_PATH ?= $(OPENSSL_PATH)/lib
+OPENSSL_LDFLAGS = -lssl -lcrypto
+
+# barking moon lib
+LUA_PATH ?= /usr
+LUA_H_PATH ?= $(LUA_PATH)/include/lua5.4
+LUA_CPPFLAGS = -I$(LUA_H_PATH)
+LUA_LIB_PATH ?= $(LUA_PATH)/lib
+LUA_LDFLAGS = -L$(LUA_LIB_PATH) -llua5.4
+
+# backing store
+SQLITE_PATH ?= /usr
+SQLITE_H_PATH ?= $(SQLITE_PATH)/include
+SQLITE_CPPFLAGS = -I$(SQLITE_H_PATH)
+SQLITE_LIB_PATH ?= $(SQLITE_PATH)/lib
+SQLITE_LDFLAGS = -L$(SQLITE_LIB_PATH) -lsqlite3
+
+# so many libraries, it's like playing with Lego bricks
+
+SOURCES = command.c common.c connections.c db.c lua_interface.c main.c notify.c server.c workqueue.c
+OBJECTS = $(SOURCES:.c=.o)
+TARGET = lemu
+
+# In order to use the OSSP uuid library on the darwin platform,
+# _POSIX_SOURCE needs to be defined so that the platform doesn't
+# attempt to provide its own uuid_t type.
+CPPFLAGS += -D_POSIX_C_SOURCE=200112L
+# Linux needs additional specifications when _POSIX_C_SOURCE is
+# defined.
+CPPFLAGS += -D_XOPEN_SOURCE=700
+
+CPPFLAGS += -D_REENTRANT
+
+CFLAGS += -DDEBUG_SILLY
+
+CFLAGS += -g -Wall -Wextra -Werror
+CPPFLAGS += $(LIBEVENT_CPPFLAGS) $(LIBUUID_CPPFLAGS) $(OPENSSL_CPPFLAGS) $(LUA_CPPFLAGS) $(SQLITE_CPPFLAGS)
+LDFLAGS += $(LIBEVENT_LDFLAGS) $(LIBUUID_LDFLAGS) $(OPENSSL_LDFLAGS) $(LUA_LDFLAGS) $(SQLITE_LDFLAGS)
+
+ifeq ($(shell uname), Linux)
+# Linux needs librt to see some clock and pthread functions.
+LDFLAGS += -lrt
+# Linux needs -pthread for pthread_mutexattr_init
+LDFLAGS += -pthread
+CFLAGS += -pthread
+endif
+
+MAKEDEPEND = $(CC) $(CPPFLAGS) -MM
+
+.PHONY:        all clean
+
+all:   $(TARGET) Makefile.depend
+
+version.h:     
+       ./version.sh 0 0 prototype
+
+Makefile.depend:       $(SOURCES) version.h
+       $(MAKEDEPEND) $^ > $@
+
+$(TARGET):     $(OBJECTS)
+       $(CC) -o $@ $^ $(LDFLAGS)
+
+clean: 
+       rm -f $(TARGET) *.o Makefile.depend
+
+-include Makefile.depend
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..7321bf5
--- /dev/null
+++ b/README.md
@@ -0,0 +1,28 @@
+# LEMU
+
+This is a (work-in-progress) text-based multi-user server engine, utilizing lua, libevent, and a concurrent work queue.
+The intent is for client interaction to be primarily driven by lua scripting, with the server code itself only handling the heavy-lifting of IO, DB, et cetera.
+
+Nota bene: this is currently a toy implementation, as an architecture for the lua-side has yet to be fleshed out, and is being developed by feel, trial, and error.
+
+## Overview
+
+The main thread runs the event loop, which primarily accepts new connections and feeds their line-based inputs into the thread pool for processing.  Connection hostname resolution and signal handling are also in the main thread.
+
+Each connection has its own queue of input.  When a new line of input is added to a connection's queue and there were no other lines already queued, a new job is added to the workqueue to process that connection's input.
+
+The main unresolved issue here is consistent server state-keeping between each worker-thread's lua processor.  The current stopgap solution is that each worker is totally independent and any global changes to the lua state will be queued as a task to update the common state.  The imagined ideal is a lock-enable native lua environment/table which would be shared by all workers.
+
+## File Tour
+
+       bsd_queue.h      - BSD's sys/queue.h, included here because Linux's version is dreadful
+       command.c        - processes user input
+       common.c         - utility functions
+       connections.c    - manage and interact with active clients
+       db.c             - persistent data storage
+       lua_interface.c  - lua bindings to server and connections
+       main.c           - cli
+       notify.c         - logging abstraction
+       server.c         - event loop, network stuff, glues everything together
+    workqueue.c      - concurrent task runner
+
diff --git a/bsd_queue.3 b/bsd_queue.3
new file mode 100644 (file)
index 0000000..d9b0daf
--- /dev/null
@@ -0,0 +1,1196 @@
+.\"    $OpenBSD: queue.3,v 1.68 2020/12/30 13:33:38 millert Exp $
+.\"    $NetBSD: queue.3,v 1.4 1995/07/03 00:25:36 mycroft Exp $
+.\"
+.\" Copyright (c) 1993 The Regents of the University of California.
+.\" All rights reserved.
+.\"
+.\" 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. Neither the name of the University nor the names of its contributors
+.\"    may be used to endorse or promote products derived from this software
+.\"    without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
+.\"
+.\"    @(#)queue.3     8.1 (Berkeley) 12/13/93
+.\"
+.Dd $Mdocdate: December 30 2020 $
+.Dt SLIST_INIT 3
+.Os
+.Sh NAME
+.Nm SLIST_ENTRY ,
+.Nm SLIST_HEAD ,
+.Nm SLIST_HEAD_INITIALIZER ,
+.Nm SLIST_FIRST ,
+.Nm SLIST_NEXT ,
+.Nm SLIST_EMPTY ,
+.Nm SLIST_FOREACH ,
+.Nm SLIST_FOREACH_SAFE ,
+.Nm SLIST_INIT ,
+.Nm SLIST_INSERT_AFTER ,
+.Nm SLIST_INSERT_HEAD ,
+.Nm SLIST_REMOVE_AFTER ,
+.Nm SLIST_REMOVE_HEAD ,
+.Nm SLIST_REMOVE ,
+.Nm LIST_ENTRY ,
+.Nm LIST_HEAD ,
+.Nm LIST_HEAD_INITIALIZER ,
+.Nm LIST_FIRST ,
+.Nm LIST_NEXT ,
+.Nm LIST_EMPTY ,
+.Nm LIST_FOREACH ,
+.Nm LIST_FOREACH_SAFE ,
+.Nm LIST_INIT ,
+.Nm LIST_INSERT_AFTER ,
+.Nm LIST_INSERT_BEFORE ,
+.Nm LIST_INSERT_HEAD ,
+.Nm LIST_REMOVE ,
+.Nm LIST_REPLACE ,
+.Nm SIMPLEQ_ENTRY ,
+.Nm SIMPLEQ_HEAD ,
+.Nm SIMPLEQ_HEAD_INITIALIZER ,
+.Nm SIMPLEQ_FIRST ,
+.Nm SIMPLEQ_NEXT ,
+.Nm SIMPLEQ_EMPTY ,
+.Nm SIMPLEQ_FOREACH ,
+.Nm SIMPLEQ_FOREACH_SAFE ,
+.Nm SIMPLEQ_INIT ,
+.Nm SIMPLEQ_INSERT_AFTER ,
+.Nm SIMPLEQ_INSERT_HEAD ,
+.Nm SIMPLEQ_INSERT_TAIL ,
+.Nm SIMPLEQ_REMOVE_AFTER ,
+.Nm SIMPLEQ_REMOVE_HEAD ,
+.Nm SIMPLEQ_CONCAT ,
+.Nm STAILQ_ENTRY ,
+.Nm STAILQ_HEAD ,
+.Nm STAILQ_HEAD_INITIALIZER ,
+.Nm STAILQ_FIRST ,
+.Nm STAILQ_NEXT ,
+.Nm STAILQ_LAST ,
+.Nm STAILQ_EMPTY ,
+.Nm STAILQ_FOREACH ,
+.Nm STAILQ_FOREACH_SAFE ,
+.Nm STAILQ_INIT ,
+.Nm STAILQ_INSERT_AFTER ,
+.Nm STAILQ_INSERT_HEAD ,
+.Nm STAILQ_INSERT_TAIL ,
+.Nm STAILQ_REMOVE ,
+.Nm STAILQ_REMOVE_AFTER ,
+.Nm STAILQ_REMOVE_HEAD ,
+.Nm STAILQ_CONCAT ,
+.Nm TAILQ_ENTRY ,
+.Nm TAILQ_HEAD ,
+.Nm TAILQ_HEAD_INITIALIZER ,
+.Nm TAILQ_FIRST ,
+.Nm TAILQ_NEXT ,
+.Nm TAILQ_LAST ,
+.Nm TAILQ_PREV ,
+.Nm TAILQ_EMPTY ,
+.Nm TAILQ_FOREACH ,
+.Nm TAILQ_FOREACH_SAFE ,
+.Nm TAILQ_FOREACH_REVERSE ,
+.Nm TAILQ_FOREACH_REVERSE_SAFE ,
+.Nm TAILQ_INIT ,
+.Nm TAILQ_INSERT_AFTER ,
+.Nm TAILQ_INSERT_BEFORE ,
+.Nm TAILQ_INSERT_HEAD ,
+.Nm TAILQ_INSERT_TAIL ,
+.Nm TAILQ_REMOVE ,
+.Nm TAILQ_REPLACE ,
+.Nm TAILQ_CONCAT
+.Nd intrusive singly-linked and doubly-linked lists, simple queues, singly-linked and doubly-linked tail queues
+.Sh SYNOPSIS
+.In sys/queue.h
+.Pp
+.Fn SLIST_ENTRY "TYPE"
+.Fn SLIST_HEAD "HEADNAME" "TYPE"
+.Fn SLIST_HEAD_INITIALIZER "SLIST_HEAD head"
+.Ft "struct TYPE *"
+.Fn SLIST_FIRST "SLIST_HEAD *head"
+.Ft "struct TYPE *"
+.Fn SLIST_NEXT "struct TYPE *listelm" "FIELDNAME"
+.Ft int
+.Fn SLIST_EMPTY "SLIST_HEAD *head"
+.Fn SLIST_FOREACH "VARNAME" "SLIST_HEAD *head" "FIELDNAME"
+.Fn SLIST_FOREACH_SAFE "VARNAME" "SLIST_HEAD *head" "FIELDNAME" "TEMP_VARNAME"
+.Ft void
+.Fn SLIST_INIT "SLIST_HEAD *head"
+.Ft void
+.Fn SLIST_INSERT_AFTER "struct TYPE *listelm" "struct TYPE *elm" "FIELDNAME"
+.Ft void
+.Fn SLIST_INSERT_HEAD "SLIST_HEAD *head" "struct TYPE *elm" "FIELDNAME"
+.Ft void
+.Fn SLIST_REMOVE_AFTER "struct TYPE *elm" "FIELDNAME"
+.Ft void
+.Fn SLIST_REMOVE_HEAD "SLIST_HEAD *head" "FIELDNAME"
+.Ft void
+.Fn SLIST_REMOVE "SLIST_HEAD *head" "struct TYPE *elm" "TYPE" "FIELDNAME"
+.Pp
+.Fn LIST_ENTRY "TYPE"
+.Fn LIST_HEAD "HEADNAME" "TYPE"
+.Fn LIST_HEAD_INITIALIZER "LIST_HEAD head"
+.Ft "struct TYPE *"
+.Fn LIST_FIRST "LIST_HEAD *head"
+.Ft "struct TYPE *"
+.Fn LIST_NEXT "struct TYPE *listelm" "FIELDNAME"
+.Ft int
+.Fn LIST_EMPTY "LIST_HEAD *head"
+.Fn LIST_FOREACH "VARNAME" "LIST_HEAD *head" "FIELDNAME"
+.Fn LIST_FOREACH_SAFE "VARNAME" "LIST_HEAD *head" "FIELDNAME" "TEMP_VARNAME"
+.Ft void
+.Fn LIST_INIT "LIST_HEAD *head"
+.Ft void
+.Fn LIST_INSERT_AFTER "struct TYPE *listelm" "struct TYPE *elm" "FIELDNAME"
+.Ft void
+.Fn LIST_INSERT_BEFORE "struct TYPE *listelm" "struct TYPE *elm" "FIELDNAME"
+.Ft void
+.Fn LIST_INSERT_HEAD "LIST_HEAD *head" "struct TYPE *elm" "FIELDNAME"
+.Ft void
+.Fn LIST_REMOVE "struct TYPE *elm" "FIELDNAME"
+.Ft void
+.Fn LIST_REPLACE "struct TYPE *elm" "struct TYPE *elm2" "FIELDNAME"
+.Pp
+.Fn SIMPLEQ_ENTRY "TYPE"
+.Fn SIMPLEQ_HEAD "HEADNAME" "TYPE"
+.Fn SIMPLEQ_HEAD_INITIALIZER "SIMPLEQ_HEAD head"
+.Ft "struct TYPE *"
+.Fn SIMPLEQ_FIRST "SIMPLEQ_HEAD *head"
+.Ft "struct TYPE *"
+.Fn SIMPLEQ_NEXT "struct TYPE *listelm" "FIELDNAME"
+.Ft int
+.Fn SIMPLEQ_EMPTY "SIMPLEQ_HEAD *head"
+.Fn SIMPLEQ_FOREACH "VARNAME" "SIMPLEQ_HEAD *head" "FIELDNAME"
+.Fn SIMPLEQ_FOREACH_SAFE "VARNAME" "SIMPLEQ_HEAD *head" "FIELDNAME" "TEMP_VARNAME"
+.Ft void
+.Fn SIMPLEQ_INIT "SIMPLEQ_HEAD *head"
+.Ft void
+.Fn SIMPLEQ_INSERT_AFTER "SIMPLEQ_HEAD *head" "struct TYPE *listelm" "struct TYPE *elm" "FIELDNAME"
+.Ft void
+.Fn SIMPLEQ_INSERT_HEAD "SIMPLEQ_HEAD *head" "struct TYPE *elm" "FIELDNAME"
+.Ft void
+.Fn SIMPLEQ_INSERT_TAIL "SIMPLEQ_HEAD *head" "struct TYPE *elm" "FIELDNAME"
+.Ft void
+.Fn SIMPLEQ_REMOVE_AFTER "SIMPLEQ_HEAD *head" "struct TYPE *elm" "FIELDNAME"
+.Ft void
+.Fn SIMPLEQ_REMOVE_HEAD "SIMPLEQ_HEAD *head" "FIELDNAME"
+.Fn SIMPLEQ_CONCAT "SIMPLEQ_HEAD *head1" "SIMPLEQ_HEAD *head2"
+.Pp
+.Fn STAILQ_ENTRY "TYPE"
+.Fn STAILQ_HEAD "HEADNAME" "TYPE"
+.Fn STAILQ_HEAD_INITIALIZER "STAILQ_HEAD head"
+.Fn STAILQ_FIRST "STAILQ_HEAD *head"
+.Fn STAILQ_NEXT "TYPE *elm" "STAILQ_ENTRY NAME"
+.Fn STAILQ_LAST "STAILQ_HEAD *head" "TYPE *elm" "STAILQ_ENTRY NAME"
+.Fn STAILQ_EMPTY "STAILQ_HEAD *head"
+.Fn STAILQ_FOREACH "TYPE *var" "STAILQ_HEAD *head" "STAILQ_ENTRY NAME"
+.Fn STAILQ_FOREACH_SAFE "TYPE *var" "STAILQ_HEAD *head" "STAILQ_ENTRY NAME" "TYPE *temp_var"
+.Fn STAILQ_INIT "STAILQ_HEAD *head"
+.Fn STAILQ_INSERT_AFTER "STAILQ_HEAD *head" "TYPE *listelm" "TYPE *elm" "STAILQ_ENTRY NAME"
+.Fn STAILQ_INSERT_HEAD "STAILQ_HEAD *head" "TYPE *elm" "STAILQ_ENTRY NAME"
+.Fn STAILQ_INSERT_TAIL "STAILQ_HEAD *head" "TYPE *elm" "STAILQ_ENTRY NAME"
+.Fn STAILQ_REMOVE "STAILQ_HEAD *head" "TYPE *elm" "TYPE" "STAILQ_ENTRY NAME"
+.Fn STAILQ_REMOVE_AFTER "STAILQ_HEAD *head" "TYPE *elm" "STAILQ_ENTRY NAME"
+.Fn STAILQ_REMOVE_HEAD "STAILQ_HEAD *head" "STAILQ_ENTRY NAME"
+.Fn STAILQ_CONCAT "STAILQ_HEAD *head1" "STAILQ_HEAD *head2"
+.Pp
+.Fn TAILQ_ENTRY "TYPE"
+.Fn TAILQ_HEAD "HEADNAME" "TYPE"
+.Fn TAILQ_HEAD_INITIALIZER "TAILQ_HEAD head"
+.Ft "struct TYPE *"
+.Fn TAILQ_FIRST "TAILQ_HEAD *head"
+.Ft "struct TYPE *"
+.Fn TAILQ_NEXT "struct TYPE *listelm" "FIELDNAME"
+.Ft "struct TYPE *"
+.Fn TAILQ_LAST "TAILQ_HEAD *head" "HEADNAME"
+.Ft "struct TYPE *"
+.Fn TAILQ_PREV "struct TYPE *listelm" "HEADNAME" "FIELDNAME"
+.Ft int
+.Fn TAILQ_EMPTY "TAILQ_HEAD *head"
+.Fn TAILQ_FOREACH "VARNAME" "TAILQ_HEAD *head" "FIELDNAME"
+.Fn TAILQ_FOREACH_SAFE "VARNAME" "TAILQ_HEAD *head" "FIELDNAME" "TEMP_VARNAME"
+.Fn TAILQ_FOREACH_REVERSE "VARNAME" "TAILQ_HEAD *head" "HEADNAME" "FIELDNAME"
+.Fn TAILQ_FOREACH_REVERSE_SAFE "VARNAME" "TAILQ_HEAD *head" "HEADNAME" "FIELDNAME" "TEMP_VARNAME"
+.Ft void
+.Fn TAILQ_INIT "TAILQ_HEAD *head"
+.Ft void
+.Fn TAILQ_INSERT_AFTER "TAILQ_HEAD *head" "struct TYPE *listelm" "struct TYPE *elm" "FIELDNAME"
+.Ft void
+.Fn TAILQ_INSERT_BEFORE "struct TYPE *listelm" "struct TYPE *elm" "FIELDNAME"
+.Ft void
+.Fn TAILQ_INSERT_HEAD "TAILQ_HEAD *head" "struct TYPE *elm" "FIELDNAME"
+.Ft void
+.Fn TAILQ_INSERT_TAIL "TAILQ_HEAD *head" "struct TYPE *elm" "FIELDNAME"
+.Ft void
+.Fn TAILQ_REMOVE "TAILQ_HEAD *head" "struct TYPE *elm" "FIELDNAME"
+.Ft void
+.Fn TAILQ_REPLACE "TAILQ_HEAD *head" "struct TYPE *elm" "struct TYPE *elm2" "FIELDNAME"
+.Fn TAILQ_CONCAT "TAILQ_HEAD *head1" "TAILQ_HEAD *head2" "FIELDNAME"
+.Sh DESCRIPTION
+These macros define and operate on five types of data structures:
+singly-linked lists, simple queues, lists, singly-linked tail queues,
+and tail queues.
+All five structures support the following functionality:
+.Pp
+.Bl -enum -compact -offset indent
+.It
+Insertion of a new entry at the head of the list.
+.It
+Insertion of a new entry after any element in the list.
+.It
+Removal of an entry from the head of the list.
+.It
+Forward traversal through the list.
+.El
+.Pp
+The following table provides a quick overview
+of which types support which additional macros:
+.Bl -column -offset 6n "LAST, PREV, FOREACH_REVERSE" SLIST LIST SIMPLEQ STAILQ TAILQ
+.It LAST, PREV, FOREACH_REVERSE Ta -     Ta -    Ta -       Ta -      Ta TAILQ
+.It INSERT_BEFORE, REPLACE      Ta -     Ta LIST Ta -       Ta -      Ta TAILQ
+.It INSERT_TAIL, CONCAT         Ta -     Ta -    Ta SIMPLEQ Ta STAILQ Ta TAILQ
+.It REMOVE_AFTER, REMOVE_HEAD   Ta SLIST Ta -    Ta SIMPLEQ Ta STAILQ Ta -
+.It REMOVE                      Ta SLIST Ta LIST Ta -       Ta STAILQ Ta TAILQ
+.El
+.Pp
+Singly-linked lists are the simplest of the five data structures
+and support only the above functionality.
+Singly-linked lists are ideal for applications with large datasets
+and few or no removals, or for implementing a LIFO queue.
+.Pp
+Simple queues and singly-linked tail queues add the following functionality:
+.Pp
+.Bl -enum -compact -offset indent
+.It
+Entries can be added at the end of a list.
+.El
+.Pp
+However:
+.Pp
+.Bl -enum -compact -offset indent
+.It
+All list insertions must specify the head of the list.
+.It
+Each head entry requires two pointers rather than one.
+.It
+Code size is about 15% greater and operations run about 20% slower
+than singly-linked lists.
+.El
+.Pp
+Simple queues and singly-linked tail queues are ideal for applications with
+large datasets and few or no removals, or for implementing a FIFO queue.
+.Pp
+All doubly linked types of data structures (lists and tail queues)
+additionally allow:
+.Pp
+.Bl -enum -compact -offset indent
+.It
+Insertion of a new entry before any element in the list.
+.It
+Removal of any entry in the list.
+.El
+.Pp
+However:
+.Pp
+.Bl -enum -compact -offset indent
+.It
+Each element requires two pointers rather than one.
+.It
+Code size and execution time of operations (except for removal) is about
+twice that of the singly-linked data-structures.
+.El
+.Pp
+Lists are the simplest of the doubly linked data structures and support
+only the above functionality over singly-linked lists.
+.Pp
+Tail queues add the following functionality:
+.Pp
+.Bl -enum -compact -offset indent
+.It
+Entries can be added at the end of a list.
+.It
+They may be traversed backwards, at a cost.
+.El
+.Pp
+However:
+.Pp
+.Bl -enum -compact -offset indent
+.It
+All list insertions and removals must specify the head of the list.
+.It
+Each head entry requires two pointers rather than one.
+.It
+Code size is about 15% greater and operations run about 20% slower
+than singly-linked lists.
+.El
+.Pp
+An additional type of data structure, circular queues, violated the C
+language aliasing rules and were miscompiled as a result.
+All code using them should be converted to another structure;
+tail queues are usually the easiest to convert to.
+.Pp
+All these lists and queues are intrusive: they link together user
+defined structures containing a field of type
+.Li SLIST_ENTRY ,
+.Li LIST_ENTRY ,
+.Li SIMPLEQ_ENTRY ,
+.Li STAILQ_ENTRY ,
+or
+.Li TAILQ_ENTRY .
+In the macro definitions,
+.Fa TYPE
+is the name tag of the user defined structure and
+.Fa FIELDNAME
+is the name of the
+.Li *_ENTRY
+field.
+If an instance of the user defined structure needs to be a member of
+multiple lists at the same time, the structure requires multiple
+.Li *_ENTRY
+fields, one for each list.
+.Pp
+The argument
+.Fa HEADNAME
+is the name tag of a user defined structure that must be declared
+using the macros
+.Fn SLIST_HEAD ,
+.Fn LIST_HEAD ,
+.Fn SIMPLEQ_HEAD ,
+.Fn STAILQ_HEAD ,
+or
+.Fn TAILQ_HEAD .
+See the examples below for further explanation of how these macros are used.
+.Sh SINGLY-LINKED LISTS
+A singly-linked list is headed by a structure defined by the
+.Fn SLIST_HEAD
+macro.
+This structure contains a single pointer to the first element on the list.
+The elements are singly linked for minimum space and pointer manipulation
+overhead at the expense of O(n) removal for arbitrary elements.
+New elements can be added to the list after an existing element or
+at the head of the list.
+A
+.Fa SLIST_HEAD
+structure is declared as follows:
+.Bd -literal -offset indent
+SLIST_HEAD(HEADNAME, TYPE) head;
+.Ed
+.Pp
+where
+.Fa HEADNAME
+is the name of the structure to be defined, and struct
+.Fa TYPE
+is the type of the elements to be linked into the list.
+A pointer to the head of the list can later be declared as:
+.Bd -literal -offset indent
+struct HEADNAME *headp;
+.Ed
+.Pp
+(The names
+.Li head
+and
+.Li headp
+are user selectable.)
+.Pp
+The
+.Fa HEADNAME
+facility is often not used, leading to the following bizarre code:
+.Bd -literal -offset indent
+SLIST_HEAD(, TYPE) head, *headp;
+.Ed
+.Pp
+The
+.Fn SLIST_ENTRY
+macro declares a structure that connects the elements in the list.
+.Pp
+The
+.Fn SLIST_INIT
+macro initializes the list referenced by
+.Fa head .
+.Pp
+The list can also be initialized statically by using the
+.Fn SLIST_HEAD_INITIALIZER
+macro like this:
+.Bd -literal -offset indent
+SLIST_HEAD(HEADNAME, TYPE) head = SLIST_HEAD_INITIALIZER(head);
+.Ed
+.Pp
+The
+.Fn SLIST_INSERT_HEAD
+macro inserts the new element
+.Fa elm
+at the head of the list.
+.Pp
+The
+.Fn SLIST_INSERT_AFTER
+macro inserts the new element
+.Fa elm
+after the element
+.Fa listelm .
+.Pp
+The
+.Fn SLIST_REMOVE_HEAD
+macro removes the first element of the list pointed by
+.Fa head .
+.Pp
+The
+.Fn SLIST_REMOVE_AFTER
+macro removes the list element immediately following
+.Fa elm .
+.Pp
+The
+.Fn SLIST_REMOVE
+macro removes the element
+.Fa elm
+of the list pointed by
+.Fa head .
+.Pp
+The
+.Fn SLIST_FIRST
+and
+.Fn SLIST_NEXT
+macros can be used to traverse the list:
+.Bd -literal -offset indent
+for (np = SLIST_FIRST(&head); np != NULL; np = SLIST_NEXT(np, FIELDNAME))
+.Ed
+.Pp
+Or, for simplicity, one can use the
+.Fn SLIST_FOREACH
+macro:
+.Bd -literal -offset indent
+SLIST_FOREACH(np, head, FIELDNAME)
+.Ed
+.Pp
+The macro
+.Fn SLIST_FOREACH_SAFE
+traverses the list referenced by head in a
+forward direction, assigning each element in turn to var.
+However, unlike
+.Fn SLIST_FOREACH
+it is permitted to remove var as well
+as free it from within the loop safely without interfering with the traversal.
+.Pp
+The
+.Fn SLIST_EMPTY
+macro should be used to check whether a simple list is empty.
+.Sh SINGLY-LINKED LIST EXAMPLE
+.Bd -literal
+SLIST_HEAD(listhead, entry) head;
+struct entry {
+       ...
+       SLIST_ENTRY(entry) entries;     /* Simple list. */
+       ...
+} *n1, *n2, *np;
+
+SLIST_INIT(&head);                     /* Initialize simple list. */
+
+n1 = malloc(sizeof(struct entry));     /* Insert at the head. */
+SLIST_INSERT_HEAD(&head, n1, entries);
+
+n2 = malloc(sizeof(struct entry));     /* Insert after. */
+SLIST_INSERT_AFTER(n1, n2, entries);
+
+SLIST_FOREACH(np, &head, entries)      /* Forward traversal. */
+       np-> ...
+
+while (!SLIST_EMPTY(&head)) {          /* Delete. */
+       n1 = SLIST_FIRST(&head);
+       SLIST_REMOVE_HEAD(&head, entries);
+       free(n1);
+}
+
+.Ed
+.Sh LISTS
+A list is headed by a structure defined by the
+.Fn LIST_HEAD
+macro.
+This structure contains a single pointer to the first element on the list.
+The elements are doubly linked so that an arbitrary element can be
+removed without traversing the list.
+New elements can be added to the list after an existing element,
+before an existing element, or at the head of the list.
+A
+.Fa LIST_HEAD
+structure is declared as follows:
+.Bd -literal -offset indent
+LIST_HEAD(HEADNAME, TYPE) head;
+.Ed
+.Pp
+where
+.Fa HEADNAME
+is the name of the structure to be defined, and struct
+.Fa TYPE
+is the type of the elements to be linked into the list.
+A pointer to the head of the list can later be declared as:
+.Bd -literal -offset indent
+struct HEADNAME *headp;
+.Ed
+.Pp
+(The names
+.Li head
+and
+.Li headp
+are user selectable.)
+.Pp
+The
+.Fa HEADNAME
+facility is often not used, leading to the following bizarre code:
+.Bd -literal -offset indent
+LIST_HEAD(, TYPE) head, *headp;
+.Ed
+.Pp
+The
+.Fn LIST_ENTRY
+macro declares a structure that connects the elements in the list.
+.Pp
+The
+.Fn LIST_INIT
+macro initializes the list referenced by
+.Fa head .
+.Pp
+The list can also be initialized statically by using the
+.Fn LIST_HEAD_INITIALIZER
+macro like this:
+.Bd -literal -offset indent
+LIST_HEAD(HEADNAME, TYPE) head = LIST_HEAD_INITIALIZER(head);
+.Ed
+.Pp
+The
+.Fn LIST_INSERT_HEAD
+macro inserts the new element
+.Fa elm
+at the head of the list.
+.Pp
+The
+.Fn LIST_INSERT_AFTER
+macro inserts the new element
+.Fa elm
+after the element
+.Fa listelm .
+.Pp
+The
+.Fn LIST_INSERT_BEFORE
+macro inserts the new element
+.Fa elm
+before the element
+.Fa listelm .
+.Pp
+The
+.Fn LIST_REMOVE
+macro removes the element
+.Fa elm
+from the list.
+.Pp
+The
+.Fn LIST_REPLACE
+macro replaces the list element
+.Fa elm
+with the new element
+.Fa elm2 .
+.Pp
+The
+.Fn LIST_FIRST
+and
+.Fn LIST_NEXT
+macros can be used to traverse the list:
+.Bd -literal -offset indent
+for (np = LIST_FIRST(&head); np != NULL; np = LIST_NEXT(np, FIELDNAME))
+.Ed
+.Pp
+Or, for simplicity, one can use the
+.Fn LIST_FOREACH
+macro:
+.Bd -literal -offset indent
+LIST_FOREACH(np, head, FIELDNAME)
+.Ed
+.Pp
+The macro
+.Fn LIST_FOREACH_SAFE
+traverses the list referenced by head in a
+forward direction, assigning each element in turn to var.
+However, unlike
+.Fn LIST_FOREACH
+it is permitted to remove var as well
+as free it from within the loop safely without interfering with the traversal.
+.Pp
+The
+.Fn LIST_EMPTY
+macro should be used to check whether a list is empty.
+.Sh LIST EXAMPLE
+.Bd -literal
+LIST_HEAD(listhead, entry) head;
+struct entry {
+       ...
+       LIST_ENTRY(entry) entries;      /* List. */
+       ...
+} *n1, *n2, *np;
+
+LIST_INIT(&head);                      /* Initialize list. */
+
+n1 = malloc(sizeof(struct entry));     /* Insert at the head. */
+LIST_INSERT_HEAD(&head, n1, entries);
+
+n2 = malloc(sizeof(struct entry));     /* Insert after. */
+LIST_INSERT_AFTER(n1, n2, entries);
+
+n2 = malloc(sizeof(struct entry));     /* Insert before. */
+LIST_INSERT_BEFORE(n1, n2, entries);
+                                       /* Forward traversal. */
+LIST_FOREACH(np, &head, entries)
+       np-> ...
+
+while (!LIST_EMPTY(&head)) {           /* Delete. */
+       n1 = LIST_FIRST(&head);
+       LIST_REMOVE(n1, entries);
+       free(n1);
+}
+.Ed
+.Sh SIMPLE QUEUES
+A simple queue is headed by a structure defined by the
+.Fn SIMPLEQ_HEAD
+macro.
+This structure contains a pair of pointers, one to the first element in the
+simple queue and the other to the last element in the simple queue.
+The elements are singly linked.
+New elements can be added to the queue after an existing element,
+at the head of the queue or at the tail of the queue.
+A
+.Fa SIMPLEQ_HEAD
+structure is declared as follows:
+.Bd -literal -offset indent
+SIMPLEQ_HEAD(HEADNAME, TYPE) head;
+.Ed
+.Pp
+where
+.Fa HEADNAME
+is the name of the structure to be defined, and struct
+.Fa TYPE
+is the type of the elements to be linked into the queue.
+A pointer to the head of the queue can later be declared as:
+.Bd -literal -offset indent
+struct HEADNAME *headp;
+.Ed
+.Pp
+(The names
+.Li head
+and
+.Li headp
+are user selectable.)
+.Pp
+The
+.Fn SIMPLEQ_ENTRY
+macro declares a structure that connects the elements in
+the queue.
+.Pp
+The
+.Fn SIMPLEQ_INIT
+macro initializes the queue referenced by
+.Fa head .
+.Pp
+The queue can also be initialized statically by using the
+.Fn SIMPLEQ_HEAD_INITIALIZER
+macro like this:
+.Bd -literal -offset indent
+SIMPLEQ_HEAD(HEADNAME, TYPE) head = SIMPLEQ_HEAD_INITIALIZER(head);
+.Ed
+.Pp
+The
+.Fn SIMPLEQ_INSERT_AFTER
+macro inserts the new element
+.Fa elm
+after the element
+.Fa listelm .
+.Pp
+The
+.Fn SIMPLEQ_INSERT_HEAD
+macro inserts the new element
+.Fa elm
+at the head of the queue.
+.Pp
+The
+.Fn SIMPLEQ_INSERT_TAIL
+macro inserts the new element
+.Fa elm
+at the end of the queue.
+.Pp
+The
+.Fn SIMPLEQ_REMOVE_AFTER
+macro removes the queue element immediately following
+.Fa elm .
+.Pp
+The
+.Fn SIMPLEQ_REMOVE_HEAD
+macro removes the first element
+from the queue.
+.Pp
+The
+.Fn SIMPLEQ_CONCAT
+macro concatenates all the elements of the queue referenced by
+.Fa head2
+to the end of the queue referenced by
+.Fa head1 ,
+emptying
+.Fa head2
+in the process.
+This is more efficient than removing and inserting the individual elements
+as it does not actually traverse
+.Fa head2 .
+.Pp
+The
+.Fn SIMPLEQ_FIRST
+and
+.Fn SIMPLEQ_NEXT
+macros can be used to traverse the queue.
+The
+.Fn SIMPLEQ_FOREACH
+is used for queue traversal:
+.Bd -literal -offset indent
+SIMPLEQ_FOREACH(np, head, FIELDNAME)
+.Ed
+.Pp
+The macro
+.Fn SIMPLEQ_FOREACH_SAFE
+traverses the queue referenced by head in a
+forward direction, assigning each element in turn to var.
+However, unlike
+.Fn SIMPLEQ_FOREACH
+it is permitted to remove var as well
+as free it from within the loop safely without interfering with the traversal.
+.Pp
+The
+.Fn SIMPLEQ_EMPTY
+macro should be used to check whether a list is empty.
+.Sh SIMPLE QUEUE EXAMPLE
+.Bd -literal
+SIMPLEQ_HEAD(listhead, entry) head = SIMPLEQ_HEAD_INITIALIZER(head);
+struct entry {
+       ...
+       SIMPLEQ_ENTRY(entry) entries;   /* Simple queue. */
+       ...
+} *n1, *n2, *np;
+
+n1 = malloc(sizeof(struct entry));     /* Insert at the head. */
+SIMPLEQ_INSERT_HEAD(&head, n1, entries);
+
+n2 = malloc(sizeof(struct entry));     /* Insert after. */
+SIMPLEQ_INSERT_AFTER(&head, n1, n2, entries);
+
+n2 = malloc(sizeof(struct entry));     /* Insert at the tail. */
+SIMPLEQ_INSERT_TAIL(&head, n2, entries);
+                                       /* Forward traversal. */
+SIMPLEQ_FOREACH(np, &head, entries)
+       np-> ...
+                                       /* Delete. */
+while (!SIMPLEQ_EMPTY(&head)) {
+       n1 = SIMPLEQ_FIRST(&head);
+       SIMPLEQ_REMOVE_HEAD(&head, entries);
+       free(n1);
+}
+.Ed
+.Sh SINGLY-LINKED TAIL QUEUES
+A singly-linked tail queue is headed by a structure defined by the
+.Fn STAILQ_HEAD
+macro.
+This structure contains a pair of pointers, one to the first element in
+the tail queue and the other to the last element in the tail queue.
+The elements are singly linked for minimum space and pointer manipulation
+overhead at the expense of O(n) removal for arbitrary elements.
+New elements can be added to the tail queue after an existing element,
+at the head of the tail queue or at the end of the tail queue.
+A
+.Fa STAILQ_HEAD
+structure is declared as follows:
+.Bd -literal -offset indent
+STAILQ_HEAD(HEADNAME, TYPE) head;
+.Ed
+.Pp
+where
+.Fa HEADNAME
+is the name of the structure to be defined, and struct
+.Fa TYPE
+is the type of the elements to be linked into the tail queue.
+A pointer to the head of the tail queue can later be declared as:
+.Bd -literal -offset indent
+struct HEADNAME *headp;
+.Ed
+.Pp
+(The names
+.Li head
+and
+.Li headp
+are user selectable.)
+.Pp
+The
+.Fn STAILQ_ENTRY
+macro declares a structure that connects the elements in
+the tail queue.
+.Pp
+The
+.Fn STAILQ_INIT
+macro initializes the tail queue referenced by
+.Fa head .
+.Pp
+The tail queue can also be initialized statically by using the
+.Fn STAILQ_HEAD_INITIALIZER
+macro like this:
+.Bd -literal -offset indent
+STAILQ_HEAD(HEADNAME, TYPE) head = STAILQ_HEAD_INITIALIZER(head);
+.Ed
+.Pp
+The
+.Fn STAILQ_INSERT_AFTER
+macro inserts the new element
+.Fa elm
+after the element
+.Fa listelm .
+.Pp
+The
+.Fn STAILQ_INSERT_HEAD
+macro inserts the new element
+.Fa elm
+at the head of the tail queue.
+.Pp
+The
+.Fn STAILQ_INSERT_TAIL
+macro inserts the new element
+.Fa elm
+at the end of the tail queue.
+.Pp
+The
+.Fn STAILQ_REMOVE_AFTER
+macro removes the queue element immediately following
+.Fa elm .
+Unlike
+.Fa STAILQ_REMOVE ,
+this macro does not traverse the entire tail queue.
+.Pp
+The
+.Fn STAILQ_REMOVE_HEAD
+macro removes the first element
+from the tail queue.
+For optimum efficiency,
+elements being removed from the head of the tail queue should
+use this macro explicitly rather than the generic
+.Fa STAILQ_REMOVE
+macro.
+.Pp
+The
+.Fn STAILQ_REMOVE
+macro removes the element
+.Fa elm
+from the tail queue.
+Use of this macro should be avoided as it traverses the entire list.
+A doubly-linked tail queue should be used if this macro is needed in
+high-usage code paths or to operate on long tail queues.
+.Pp
+The
+.Fn STAILQ_CONCAT
+macro concatenates all the elements of the tail queue referenced by
+.Fa head2
+to the end of the tail queue referenced by
+.Fa head1 ,
+emptying
+.Fa head2
+in the process.
+This is more efficient than removing and inserting the individual elements
+as it does not actually traverse
+.Fa head2 .
+.Pp
+The
+.Fn STAILQ_FOREACH
+is used for queue traversal:
+.Bd -literal -offset indent
+STAILQ_FOREACH(np, head, FIELDNAME)
+.Ed
+.Pp
+The macro
+.Fn STAILQ_FOREACH_SAFE
+traverses the queue referenced by head in a
+forward direction, assigning each element in turn to var.
+However, unlike
+.Fn STAILQ_FOREACH
+it is permitted to remove var as well
+as free it from within the loop safely without interfering with the traversal.
+.Pp
+The
+.Fn STAILQ_FIRST
+.Fn STAILQ_NEXT ,
+and
+.Fn STAILQ_LAST
+macros can be used to manually traverse a tail queue or an arbitrary part of
+one.
+The
+.Fn STAILQ_EMPTY
+macro should be used to check whether a tail queue is empty.
+.Sh SINGLY-LINKED TAIL QUEUE EXAMPLE
+.Bd -literal
+STAILQ_HEAD(listhead, entry) head = STAILQ_HEAD_INITIALIZER(head);
+struct entry {
+       ...
+       STAILQ_ENTRY(entry) entries;    /* Singly-linked tail queue. */
+       ...
+} *n1, *n2, *np;
+
+n1 = malloc(sizeof(struct entry));     /* Insert at the head. */
+STAILQ_INSERT_HEAD(&head, n1, entries);
+
+n2 = malloc(sizeof(struct entry));     /* Insert at the tail. */
+STAILQ_INSERT_TAIL(&head, n2, entries);
+
+n2 = malloc(sizeof(struct entry));     /* Insert after. */
+STAILQ_INSERT_AFTER(&head, n1, n2, entries);
+
+                                       /* Deletion. */
+STAILQ_REMOVE(&head, n2, entry, entries);
+free(n2);
+                                       /* Deletion from the head. */
+n3 = STAILQ_FIRST(&head);
+STAILQ_REMOVE_HEAD(&head, entries);
+free(n3);
+                                       /* Forward traversal. */
+STAILQ_FOREACH(np, &head, entries)
+       np-> ...
+                                       /* Safe forward traversal. */
+STAILQ_FOREACH_SAFE(np, &head, entries, np_temp) {
+       np-> ...
+       STAILQ_REMOVE(&head, np, entry, entries);
+       free(np);
+}
+                                       /* Delete. */
+while (!STAILQ_EMPTY(&head)) {
+       n1 = STAILQ_FIRST(&head);
+       STAILQ_REMOVE_HEAD(&head, entries);
+       free(n1);
+}
+.Ed
+.Sh TAIL QUEUES
+A tail queue is headed by a structure defined by the
+.Fn TAILQ_HEAD
+macro.
+This structure contains a pair of pointers,
+one to the first element in the tail queue and the other to
+the last element in the tail queue.
+The elements are doubly linked so that an arbitrary element can be
+removed without traversing the tail queue.
+New elements can be added to the queue after an existing element,
+before an existing element, at the head of the queue, or at the end
+of the queue.
+A
+.Fa TAILQ_HEAD
+structure is declared as follows:
+.Bd -literal -offset indent
+TAILQ_HEAD(HEADNAME, TYPE) head;
+.Ed
+.Pp
+where
+.Fa HEADNAME
+is the name of the structure to be defined, and struct
+.Fa TYPE
+is the type of the elements to be linked into the tail queue.
+A pointer to the head of the tail queue can later be declared as:
+.Bd -literal -offset indent
+struct HEADNAME *headp;
+.Ed
+.Pp
+(The names
+.Li head
+and
+.Li headp
+are user selectable.)
+.Pp
+The
+.Fn TAILQ_ENTRY
+macro declares a structure that connects the elements in
+the tail queue.
+.Pp
+The
+.Fn TAILQ_INIT
+macro initializes the tail queue referenced by
+.Fa head .
+.Pp
+The tail queue can also be initialized statically by using the
+.Fn TAILQ_HEAD_INITIALIZER
+macro.
+.Pp
+The
+.Fn TAILQ_INSERT_HEAD
+macro inserts the new element
+.Fa elm
+at the head of the tail queue.
+.Pp
+The
+.Fn TAILQ_INSERT_TAIL
+macro inserts the new element
+.Fa elm
+at the end of the tail queue.
+.Pp
+The
+.Fn TAILQ_INSERT_AFTER
+macro inserts the new element
+.Fa elm
+after the element
+.Fa listelm .
+.Pp
+The
+.Fn TAILQ_INSERT_BEFORE
+macro inserts the new element
+.Fa elm
+before the element
+.Fa listelm .
+.Pp
+The
+.Fn TAILQ_REMOVE
+macro removes the element
+.Fa elm
+from the tail queue.
+.Pp
+The
+.Fn TAILQ_REPLACE
+macro replaces the list element
+.Fa elm
+with the new element
+.Fa elm2 .
+.Pp
+The
+.Fn TAILQ_CONCAT
+macro concatenates all the elements of the tail queue referenced by
+.Fa head2
+to the end of the tail queue referenced by
+.Fa head1 ,
+emptying
+.Fa head2
+in the process.
+This is more efficient than removing and inserting the individual elements
+as it does not actually traverse
+.Fa head2 .
+.Pp
+.Fn TAILQ_FOREACH
+and
+.Fn TAILQ_FOREACH_REVERSE
+are used for traversing a tail queue.
+.Fn TAILQ_FOREACH
+starts at the first element and proceeds towards the last.
+.Fn TAILQ_FOREACH_REVERSE
+starts at the last element and proceeds towards the first.
+.Bd -literal -offset indent
+TAILQ_FOREACH(np, &head, FIELDNAME)
+TAILQ_FOREACH_REVERSE(np, &head, HEADNAME, FIELDNAME)
+.Ed
+.Pp
+The macros
+.Fn TAILQ_FOREACH_SAFE
+and
+.Fn TAILQ_FOREACH_REVERSE_SAFE
+traverse the list referenced by head
+in a forward or reverse direction respectively,
+assigning each element in turn to var.
+However, unlike their unsafe counterparts,
+they permit both the removal of var
+as well as freeing it from within the loop safely
+without interfering with the traversal.
+.Pp
+The
+.Fn TAILQ_FIRST ,
+.Fn TAILQ_NEXT ,
+.Fn TAILQ_LAST
+and
+.Fn TAILQ_PREV
+macros can be used to manually traverse a tail queue or an arbitrary part of
+one.
+.Pp
+The
+.Fn TAILQ_EMPTY
+macro should be used to check whether a tail queue is empty.
+.Sh TAIL QUEUE EXAMPLE
+.Bd -literal
+TAILQ_HEAD(tailhead, entry) head;
+struct entry {
+       ...
+       TAILQ_ENTRY(entry) entries;     /* Tail queue. */
+       ...
+} *n1, *n2, *np;
+
+TAILQ_INIT(&head);                     /* Initialize queue. */
+
+n1 = malloc(sizeof(struct entry));     /* Insert at the head. */
+TAILQ_INSERT_HEAD(&head, n1, entries);
+
+n1 = malloc(sizeof(struct entry));     /* Insert at the tail. */
+TAILQ_INSERT_TAIL(&head, n1, entries);
+
+n2 = malloc(sizeof(struct entry));     /* Insert after. */
+TAILQ_INSERT_AFTER(&head, n1, n2, entries);
+
+n2 = malloc(sizeof(struct entry));     /* Insert before. */
+TAILQ_INSERT_BEFORE(n1, n2, entries);
+                                       /* Forward traversal. */
+TAILQ_FOREACH(np, &head, entries)
+       np-> ...
+                                       /* Manual forward traversal. */
+for (np = n2; np != NULL; np = TAILQ_NEXT(np, entries))
+       np-> ...
+                                       /* Delete. */
+while ((np = TAILQ_FIRST(&head))) {
+       TAILQ_REMOVE(&head, np, entries);
+       free(np);
+}
+
+.Ed
+.Sh SEE ALSO
+.Xr tree 3
+.Sh NOTES
+It is an error to assume the next and previous fields are preserved
+after an element has been removed from a list or queue.
+Using any macro (except the various forms of insertion) on an element
+removed from a list or queue is incorrect.
+An example of erroneous usage is removing the same element twice.
+.Pp
+The
+.Fn SLIST_END ,
+.Fn LIST_END ,
+.Fn SIMPLEQ_END ,
+.Fn STAILQ_END
+and
+.Fn TAILQ_END
+macros are deprecated; they provided symmetry with the historical
+.Fn CIRCLEQ_END
+and just expand to
+.Dv NULL .
+.Pp
+Trying to free a list in the following way is a common error:
+.Bd -literal -offset indent
+LIST_FOREACH(var, head, entry)
+       free(var);
+free(head);
+.Ed
+.Pp
+Since
+.Va var
+is free'd, the FOREACH macros refer to a pointer that may have been
+reallocated already.
+A similar situation occurs when the current element is deleted
+from the list.
+In cases like these the data structure's FOREACH_SAFE macros should be used
+instead.
+.Sh HISTORY
+The
+.Nm queue
+functions first appeared in
+.Bx 4.4 .
+The historical circle queue macros were deprecated in
+.Ox 5.5 .
diff --git a/bsd_queue.h b/bsd_queue.h
new file mode 100644 (file)
index 0000000..18f04ea
--- /dev/null
@@ -0,0 +1,633 @@
+/*     $OpenBSD: queue.h,v 1.46 2020/12/30 13:33:12 millert Exp $      */
+/*     $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $       */
+
+/*
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * 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. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
+ *
+ *     @(#)queue.h     8.5 (Berkeley) 8/20/94
+ */
+
+#ifndef        BSD_QUEUE_H
+#define        BSD_QUEUE_H
+
+// #include <sys/_null.h>
+
+/*
+ * This file defines five types of data structures: singly-linked lists,
+ * lists, simple queues, tail queues and XOR simple queues.
+ *
+ *
+ * A singly-linked list is headed by a single forward pointer. The elements
+ * are singly linked for minimum space and pointer manipulation overhead at
+ * the expense of O(n) removal for arbitrary elements. New elements can be
+ * added to the list after an existing element or at the head of the list.
+ * Elements being removed from the head of the list should use the explicit
+ * macro for this purpose for optimum efficiency. A singly-linked list may
+ * only be traversed in the forward direction.  Singly-linked lists are ideal
+ * for applications with large datasets and few or no removals or for
+ * implementing a LIFO queue.
+ *
+ * A list is headed by a single forward pointer (or an array of forward
+ * pointers for a hash table header). The elements are doubly linked
+ * so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before
+ * or after an existing element or at the head of the list. A list
+ * may only be traversed in the forward direction.
+ *
+ * A simple queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are singly
+ * linked to save space, so elements can only be removed from the
+ * head of the list. New elements can be added to the list before or after
+ * an existing element, at the head of the list, or at the end of the
+ * list. A simple queue may only be traversed in the forward direction.
+ *
+ * A tail queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or
+ * after an existing element, at the head of the list, or at the end of
+ * the list. A tail queue may be traversed in either direction.
+ *
+ * An XOR simple queue is used in the same way as a regular simple queue.
+ * The difference is that the head structure also includes a "cookie" that
+ * is XOR'd with the queue pointer (first, last or next) to generate the
+ * real pointer value.
+ *
+ * For details on the use of these macros, see the queue(3) manual page.
+ */
+
+#if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC))
+#define _Q_INVALID ((void *)-1)
+#define _Q_INVALIDATE(a) (a) = _Q_INVALID
+#else
+#define _Q_INVALIDATE(a)
+#endif
+
+/*
+ * Singly-linked List definitions.
+ */
+#define SLIST_HEAD(name, type)                                         \
+struct name {                                                          \
+       struct type *slh_first; /* first element */                     \
+}
+
+#define        SLIST_HEAD_INITIALIZER(head)                                    \
+       { NULL }
+
+#define SLIST_ENTRY(type)                                              \
+struct {                                                               \
+       struct type *sle_next;  /* next element */                      \
+}
+
+/*
+ * Singly-linked List access methods.
+ */
+#define        SLIST_FIRST(head)       ((head)->slh_first)
+#define        SLIST_END(head)         NULL
+#define        SLIST_EMPTY(head)       (SLIST_FIRST(head) == SLIST_END(head))
+#define        SLIST_NEXT(elm, field)  ((elm)->field.sle_next)
+
+#define        SLIST_FOREACH(var, head, field)                                 \
+       for((var) = SLIST_FIRST(head);                                  \
+           (var) != SLIST_END(head);                                   \
+           (var) = SLIST_NEXT(var, field))
+
+#define        SLIST_FOREACH_SAFE(var, head, field, tvar)                      \
+       for ((var) = SLIST_FIRST(head);                         \
+           (var) && ((tvar) = SLIST_NEXT(var, field), 1);              \
+           (var) = (tvar))
+
+/*
+ * Singly-linked List functions.
+ */
+#define        SLIST_INIT(head) {                                              \
+       SLIST_FIRST(head) = SLIST_END(head);                            \
+}
+
+#define        SLIST_INSERT_AFTER(slistelm, elm, field) do {                   \
+       (elm)->field.sle_next = (slistelm)->field.sle_next;             \
+       (slistelm)->field.sle_next = (elm);                             \
+} while (0)
+
+#define        SLIST_INSERT_HEAD(head, elm, field) do {                        \
+       (elm)->field.sle_next = (head)->slh_first;                      \
+       (head)->slh_first = (elm);                                      \
+} while (0)
+
+#define        SLIST_REMOVE_AFTER(elm, field) do {                             \
+       (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next;  \
+} while (0)
+
+#define        SLIST_REMOVE_HEAD(head, field) do {                             \
+       (head)->slh_first = (head)->slh_first->field.sle_next;          \
+} while (0)
+
+#define SLIST_REMOVE(head, elm, type, field) do {                      \
+       if ((head)->slh_first == (elm)) {                               \
+               SLIST_REMOVE_HEAD((head), field);                       \
+       } else {                                                        \
+               struct type *curelm = (head)->slh_first;                \
+                                                                       \
+               while (curelm->field.sle_next != (elm))                 \
+                       curelm = curelm->field.sle_next;                \
+               curelm->field.sle_next =                                \
+                   curelm->field.sle_next->field.sle_next;             \
+       }                                                               \
+       _Q_INVALIDATE((elm)->field.sle_next);                           \
+} while (0)
+
+/*
+ * List definitions.
+ */
+#define LIST_HEAD(name, type)                                          \
+struct name {                                                          \
+       struct type *lh_first;  /* first element */                     \
+}
+
+#define LIST_HEAD_INITIALIZER(head)                                    \
+       { NULL }
+
+#define LIST_ENTRY(type)                                               \
+struct {                                                               \
+       struct type *le_next;   /* next element */                      \
+       struct type **le_prev;  /* address of previous next element */  \
+}
+
+/*
+ * List access methods.
+ */
+#define        LIST_FIRST(head)                ((head)->lh_first)
+#define        LIST_END(head)                  NULL
+#define        LIST_EMPTY(head)                (LIST_FIRST(head) == LIST_END(head))
+#define        LIST_NEXT(elm, field)           ((elm)->field.le_next)
+
+#define LIST_FOREACH(var, head, field)                                 \
+       for((var) = LIST_FIRST(head);                                   \
+           (var)!= LIST_END(head);                                     \
+           (var) = LIST_NEXT(var, field))
+
+#define        LIST_FOREACH_SAFE(var, head, field, tvar)                       \
+       for ((var) = LIST_FIRST(head);                          \
+           (var) && ((tvar) = LIST_NEXT(var, field), 1);               \
+           (var) = (tvar))
+
+/*
+ * List functions.
+ */
+#define        LIST_INIT(head) do {                                            \
+       LIST_FIRST(head) = LIST_END(head);                              \
+} while (0)
+
+#define LIST_INSERT_AFTER(listelm, elm, field) do {                    \
+       if (((elm)->field.le_next = (listelm)->field.le_next) != NULL)  \
+               (listelm)->field.le_next->field.le_prev =               \
+                   &(elm)->field.le_next;                              \
+       (listelm)->field.le_next = (elm);                               \
+       (elm)->field.le_prev = &(listelm)->field.le_next;               \
+} while (0)
+
+#define        LIST_INSERT_BEFORE(listelm, elm, field) do {                    \
+       (elm)->field.le_prev = (listelm)->field.le_prev;                \
+       (elm)->field.le_next = (listelm);                               \
+       *(listelm)->field.le_prev = (elm);                              \
+       (listelm)->field.le_prev = &(elm)->field.le_next;               \
+} while (0)
+
+#define LIST_INSERT_HEAD(head, elm, field) do {                                \
+       if (((elm)->field.le_next = (head)->lh_first) != NULL)          \
+               (head)->lh_first->field.le_prev = &(elm)->field.le_next;\
+       (head)->lh_first = (elm);                                       \
+       (elm)->field.le_prev = &(head)->lh_first;                       \
+} while (0)
+
+#define LIST_REMOVE(elm, field) do {                                   \
+       if ((elm)->field.le_next != NULL)                               \
+               (elm)->field.le_next->field.le_prev =                   \
+                   (elm)->field.le_prev;                               \
+       *(elm)->field.le_prev = (elm)->field.le_next;                   \
+       _Q_INVALIDATE((elm)->field.le_prev);                            \
+       _Q_INVALIDATE((elm)->field.le_next);                            \
+} while (0)
+
+#define LIST_REPLACE(elm, elm2, field) do {                            \
+       if (((elm2)->field.le_next = (elm)->field.le_next) != NULL)     \
+               (elm2)->field.le_next->field.le_prev =                  \
+                   &(elm2)->field.le_next;                             \
+       (elm2)->field.le_prev = (elm)->field.le_prev;                   \
+       *(elm2)->field.le_prev = (elm2);                                \
+       _Q_INVALIDATE((elm)->field.le_prev);                            \
+       _Q_INVALIDATE((elm)->field.le_next);                            \
+} while (0)
+
+/*
+ * Simple queue definitions.
+ */
+#define SIMPLEQ_HEAD(name, type)                                       \
+struct name {                                                          \
+       struct type *sqh_first; /* first element */                     \
+       struct type **sqh_last; /* addr of last next element */         \
+}
+
+#define SIMPLEQ_HEAD_INITIALIZER(head)                                 \
+       { NULL, &(head).sqh_first }
+
+#define SIMPLEQ_ENTRY(type)                                            \
+struct {                                                               \
+       struct type *sqe_next;  /* next element */                      \
+}
+
+/*
+ * Simple queue access methods.
+ */
+#define        SIMPLEQ_FIRST(head)         ((head)->sqh_first)
+#define        SIMPLEQ_END(head)           NULL
+#define        SIMPLEQ_EMPTY(head)         (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head))
+#define        SIMPLEQ_NEXT(elm, field)    ((elm)->field.sqe_next)
+
+#define SIMPLEQ_FOREACH(var, head, field)                              \
+       for((var) = SIMPLEQ_FIRST(head);                                \
+           (var) != SIMPLEQ_END(head);                                 \
+           (var) = SIMPLEQ_NEXT(var, field))
+
+#define        SIMPLEQ_FOREACH_SAFE(var, head, field, tvar)                    \
+       for ((var) = SIMPLEQ_FIRST(head);                               \
+           (var) && ((tvar) = SIMPLEQ_NEXT(var, field), 1);            \
+           (var) = (tvar))
+
+/*
+ * Simple queue functions.
+ */
+#define        SIMPLEQ_INIT(head) do {                                         \
+       (head)->sqh_first = NULL;                                       \
+       (head)->sqh_last = &(head)->sqh_first;                          \
+} while (0)
+
+#define SIMPLEQ_INSERT_HEAD(head, elm, field) do {                     \
+       if (((elm)->field.sqe_next = (head)->sqh_first) == NULL)        \
+               (head)->sqh_last = &(elm)->field.sqe_next;              \
+       (head)->sqh_first = (elm);                                      \
+} while (0)
+
+#define SIMPLEQ_INSERT_TAIL(head, elm, field) do {                     \
+       (elm)->field.sqe_next = NULL;                                   \
+       *(head)->sqh_last = (elm);                                      \
+       (head)->sqh_last = &(elm)->field.sqe_next;                      \
+} while (0)
+
+#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do {           \
+       if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\
+               (head)->sqh_last = &(elm)->field.sqe_next;              \
+       (listelm)->field.sqe_next = (elm);                              \
+} while (0)
+
+#define SIMPLEQ_REMOVE_HEAD(head, field) do {                  \
+       if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \
+               (head)->sqh_last = &(head)->sqh_first;                  \
+} while (0)
+
+#define SIMPLEQ_REMOVE_AFTER(head, elm, field) do {                    \
+       if (((elm)->field.sqe_next = (elm)->field.sqe_next->field.sqe_next) \
+           == NULL)                                                    \
+               (head)->sqh_last = &(elm)->field.sqe_next;              \
+} while (0)
+
+#define SIMPLEQ_CONCAT(head1, head2) do {                              \
+       if (!SIMPLEQ_EMPTY((head2))) {                                  \
+               *(head1)->sqh_last = (head2)->sqh_first;                \
+               (head1)->sqh_last = (head2)->sqh_last;                  \
+               SIMPLEQ_INIT((head2));                                  \
+       }                                                               \
+} while (0)
+
+/*
+ * XOR Simple queue definitions.
+ */
+#define XSIMPLEQ_HEAD(name, type)                                      \
+struct name {                                                          \
+       struct type *sqx_first; /* first element */                     \
+       struct type **sqx_last; /* addr of last next element */         \
+       unsigned long sqx_cookie;                                       \
+}
+
+#define XSIMPLEQ_ENTRY(type)                                           \
+struct {                                                               \
+       struct type *sqx_next;  /* next element */                      \
+}
+
+/*
+ * XOR Simple queue access methods.
+ */
+#define XSIMPLEQ_XOR(head, ptr)            ((__typeof(ptr))((head)->sqx_cookie ^ \
+                                       (unsigned long)(ptr)))
+#define        XSIMPLEQ_FIRST(head)        XSIMPLEQ_XOR(head, ((head)->sqx_first))
+#define        XSIMPLEQ_END(head)          NULL
+#define        XSIMPLEQ_EMPTY(head)        (XSIMPLEQ_FIRST(head) == XSIMPLEQ_END(head))
+#define        XSIMPLEQ_NEXT(head, elm, field)    XSIMPLEQ_XOR(head, ((elm)->field.sqx_next))
+
+
+#define XSIMPLEQ_FOREACH(var, head, field)                             \
+       for ((var) = XSIMPLEQ_FIRST(head);                              \
+           (var) != XSIMPLEQ_END(head);                                \
+           (var) = XSIMPLEQ_NEXT(head, var, field))
+
+#define        XSIMPLEQ_FOREACH_SAFE(var, head, field, tvar)                   \
+       for ((var) = XSIMPLEQ_FIRST(head);                              \
+           (var) && ((tvar) = XSIMPLEQ_NEXT(head, var, field), 1);     \
+           (var) = (tvar))
+
+/*
+ * XOR Simple queue functions.
+ */
+#define        XSIMPLEQ_INIT(head) do {                                        \
+       arc4random_buf(&(head)->sqx_cookie, sizeof((head)->sqx_cookie)); \
+       (head)->sqx_first = XSIMPLEQ_XOR(head, NULL);                   \
+       (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first);      \
+} while (0)
+
+#define XSIMPLEQ_INSERT_HEAD(head, elm, field) do {                    \
+       if (((elm)->field.sqx_next = (head)->sqx_first) ==              \
+           XSIMPLEQ_XOR(head, NULL))                                   \
+               (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \
+       (head)->sqx_first = XSIMPLEQ_XOR(head, (elm));                  \
+} while (0)
+
+#define XSIMPLEQ_INSERT_TAIL(head, elm, field) do {                    \
+       (elm)->field.sqx_next = XSIMPLEQ_XOR(head, NULL);               \
+       *(XSIMPLEQ_XOR(head, (head)->sqx_last)) = XSIMPLEQ_XOR(head, (elm)); \
+       (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next);  \
+} while (0)
+
+#define XSIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do {          \
+       if (((elm)->field.sqx_next = (listelm)->field.sqx_next) ==      \
+           XSIMPLEQ_XOR(head, NULL))                                   \
+               (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \
+       (listelm)->field.sqx_next = XSIMPLEQ_XOR(head, (elm));          \
+} while (0)
+
+#define XSIMPLEQ_REMOVE_HEAD(head, field) do {                         \
+       if (((head)->sqx_first = XSIMPLEQ_XOR(head,                     \
+           (head)->sqx_first)->field.sqx_next) == XSIMPLEQ_XOR(head, NULL)) \
+               (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \
+} while (0)
+
+#define XSIMPLEQ_REMOVE_AFTER(head, elm, field) do {                   \
+       if (((elm)->field.sqx_next = XSIMPLEQ_XOR(head,                 \
+           (elm)->field.sqx_next)->field.sqx_next)                     \
+           == XSIMPLEQ_XOR(head, NULL))                                \
+               (head)->sqx_last =                                      \
+                   XSIMPLEQ_XOR(head, &(elm)->field.sqx_next);         \
+} while (0)
+
+
+/*
+ * Tail queue definitions.
+ */
+#define TAILQ_HEAD(name, type)                                         \
+struct name {                                                          \
+       struct type *tqh_first; /* first element */                     \
+       struct type **tqh_last; /* addr of last next element */         \
+}
+
+#define TAILQ_HEAD_INITIALIZER(head)                                   \
+       { NULL, &(head).tqh_first }
+
+#define TAILQ_ENTRY(type)                                              \
+struct {                                                               \
+       struct type *tqe_next;  /* next element */                      \
+       struct type **tqe_prev; /* address of previous next element */  \
+}
+
+/*
+ * Tail queue access methods.
+ */
+#define        TAILQ_FIRST(head)               ((head)->tqh_first)
+#define        TAILQ_END(head)                 NULL
+#define        TAILQ_NEXT(elm, field)          ((elm)->field.tqe_next)
+#define TAILQ_LAST(head, headname)                                     \
+       (*(((struct headname *)((head)->tqh_last))->tqh_last))
+/* XXX */
+#define TAILQ_PREV(elm, headname, field)                               \
+       (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
+#define        TAILQ_EMPTY(head)                                               \
+       (TAILQ_FIRST(head) == TAILQ_END(head))
+
+#define TAILQ_FOREACH(var, head, field)                                        \
+       for((var) = TAILQ_FIRST(head);                                  \
+           (var) != TAILQ_END(head);                                   \
+           (var) = TAILQ_NEXT(var, field))
+
+#define        TAILQ_FOREACH_SAFE(var, head, field, tvar)                      \
+       for ((var) = TAILQ_FIRST(head);                                 \
+           (var) != TAILQ_END(head) &&                                 \
+           ((tvar) = TAILQ_NEXT(var, field), 1);                       \
+           (var) = (tvar))
+
+
+#define TAILQ_FOREACH_REVERSE(var, head, headname, field)              \
+       for((var) = TAILQ_LAST(head, headname);                         \
+           (var) != TAILQ_END(head);                                   \
+           (var) = TAILQ_PREV(var, headname, field))
+
+#define        TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar)    \
+       for ((var) = TAILQ_LAST(head, headname);                        \
+           (var) != TAILQ_END(head) &&                                 \
+           ((tvar) = TAILQ_PREV(var, headname, field), 1);             \
+           (var) = (tvar))
+
+/*
+ * Tail queue functions.
+ */
+#define        TAILQ_INIT(head) do {                                           \
+       (head)->tqh_first = NULL;                                       \
+       (head)->tqh_last = &(head)->tqh_first;                          \
+} while (0)
+
+#define TAILQ_INSERT_HEAD(head, elm, field) do {                       \
+       if (((elm)->field.tqe_next = (head)->tqh_first) != NULL)        \
+               (head)->tqh_first->field.tqe_prev =                     \
+                   &(elm)->field.tqe_next;                             \
+       else                                                            \
+               (head)->tqh_last = &(elm)->field.tqe_next;              \
+       (head)->tqh_first = (elm);                                      \
+       (elm)->field.tqe_prev = &(head)->tqh_first;                     \
+} while (0)
+
+#define TAILQ_INSERT_TAIL(head, elm, field) do {                       \
+       (elm)->field.tqe_next = NULL;                                   \
+       (elm)->field.tqe_prev = (head)->tqh_last;                       \
+       *(head)->tqh_last = (elm);                                      \
+       (head)->tqh_last = &(elm)->field.tqe_next;                      \
+} while (0)
+
+#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do {             \
+       if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\
+               (elm)->field.tqe_next->field.tqe_prev =                 \
+                   &(elm)->field.tqe_next;                             \
+       else                                                            \
+               (head)->tqh_last = &(elm)->field.tqe_next;              \
+       (listelm)->field.tqe_next = (elm);                              \
+       (elm)->field.tqe_prev = &(listelm)->field.tqe_next;             \
+} while (0)
+
+#define        TAILQ_INSERT_BEFORE(listelm, elm, field) do {                   \
+       (elm)->field.tqe_prev = (listelm)->field.tqe_prev;              \
+       (elm)->field.tqe_next = (listelm);                              \
+       *(listelm)->field.tqe_prev = (elm);                             \
+       (listelm)->field.tqe_prev = &(elm)->field.tqe_next;             \
+} while (0)
+
+#define TAILQ_REMOVE(head, elm, field) do {                            \
+       if (((elm)->field.tqe_next) != NULL)                            \
+               (elm)->field.tqe_next->field.tqe_prev =                 \
+                   (elm)->field.tqe_prev;                              \
+       else                                                            \
+               (head)->tqh_last = (elm)->field.tqe_prev;               \
+       *(elm)->field.tqe_prev = (elm)->field.tqe_next;                 \
+       _Q_INVALIDATE((elm)->field.tqe_prev);                           \
+       _Q_INVALIDATE((elm)->field.tqe_next);                           \
+} while (0)
+
+#define TAILQ_REPLACE(head, elm, elm2, field) do {                     \
+       if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL)   \
+               (elm2)->field.tqe_next->field.tqe_prev =                \
+                   &(elm2)->field.tqe_next;                            \
+       else                                                            \
+               (head)->tqh_last = &(elm2)->field.tqe_next;             \
+       (elm2)->field.tqe_prev = (elm)->field.tqe_prev;                 \
+       *(elm2)->field.tqe_prev = (elm2);                               \
+       _Q_INVALIDATE((elm)->field.tqe_prev);                           \
+       _Q_INVALIDATE((elm)->field.tqe_next);                           \
+} while (0)
+
+#define TAILQ_CONCAT(head1, head2, field) do {                         \
+       if (!TAILQ_EMPTY(head2)) {                                      \
+               *(head1)->tqh_last = (head2)->tqh_first;                \
+               (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
+               (head1)->tqh_last = (head2)->tqh_last;                  \
+               TAILQ_INIT((head2));                                    \
+       }                                                               \
+} while (0)
+
+/*
+ * Singly-linked Tail queue declarations.
+ */
+#define        STAILQ_HEAD(name, type)                                         \
+struct name {                                                          \
+       struct type *stqh_first;        /* first element */             \
+       struct type **stqh_last;        /* addr of last next element */ \
+}
+
+#define        STAILQ_HEAD_INITIALIZER(head)                                   \
+       { NULL, &(head).stqh_first }
+
+#define        STAILQ_ENTRY(type)                                              \
+struct {                                                               \
+       struct type *stqe_next; /* next element */                      \
+}
+
+/*
+ * Singly-linked Tail queue access methods.
+ */
+#define        STAILQ_FIRST(head)      ((head)->stqh_first)
+#define        STAILQ_END(head)        NULL
+#define        STAILQ_EMPTY(head)      (STAILQ_FIRST(head) == STAILQ_END(head))
+#define        STAILQ_NEXT(elm, field) ((elm)->field.stqe_next)
+
+#define STAILQ_FOREACH(var, head, field)                               \
+       for ((var) = STAILQ_FIRST(head);                                \
+           (var) != STAILQ_END(head);                                  \
+           (var) = STAILQ_NEXT(var, field))
+
+#define        STAILQ_FOREACH_SAFE(var, head, field, tvar)                     \
+       for ((var) = STAILQ_FIRST(head);                                \
+           (var) && ((tvar) = STAILQ_NEXT(var, field), 1);             \
+           (var) = (tvar))
+
+/*
+ * Singly-linked Tail queue functions.
+ */
+#define        STAILQ_INIT(head) do {                                          \
+       STAILQ_FIRST((head)) = NULL;                                    \
+       (head)->stqh_last = &STAILQ_FIRST((head));                      \
+} while (0)
+
+#define        STAILQ_INSERT_HEAD(head, elm, field) do {                       \
+       if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \
+               (head)->stqh_last = &STAILQ_NEXT((elm), field);         \
+       STAILQ_FIRST((head)) = (elm);                                   \
+} while (0)
+
+#define        STAILQ_INSERT_TAIL(head, elm, field) do {                       \
+       STAILQ_NEXT((elm), field) = NULL;                               \
+       *(head)->stqh_last = (elm);                                     \
+       (head)->stqh_last = &STAILQ_NEXT((elm), field);                 \
+} while (0)
+
+#define        STAILQ_INSERT_AFTER(head, listelm, elm, field) do {             \
+       if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((elm), field)) == NULL)\
+               (head)->stqh_last = &STAILQ_NEXT((elm), field);         \
+       STAILQ_NEXT((elm), field) = (elm);                              \
+} while (0)
+
+#define STAILQ_REMOVE_HEAD(head, field) do {                            \
+       if ((STAILQ_FIRST((head)) =                                     \
+           STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL)          \
+               (head)->stqh_last = &STAILQ_FIRST((head));              \
+} while (0)
+
+#define STAILQ_REMOVE_AFTER(head, elm, field) do {                      \
+       if ((STAILQ_NEXT(elm, field) =                                  \
+           STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL)       \
+               (head)->stqh_last = &STAILQ_NEXT((elm), field);         \
+} while (0)
+
+#define        STAILQ_REMOVE(head, elm, type, field) do {                      \
+       if (STAILQ_FIRST((head)) == (elm)) {                            \
+               STAILQ_REMOVE_HEAD((head), field);                      \
+       } else {                                                        \
+               struct type *curelm = (head)->stqh_first;               \
+               while (STAILQ_NEXT(curelm, field) != (elm))             \
+                       curelm = STAILQ_NEXT(curelm, field);            \
+               STAILQ_REMOVE_AFTER(head, curelm, field);               \
+       }                                                               \
+} while (0)
+
+#define        STAILQ_CONCAT(head1, head2) do {                                \
+       if (!STAILQ_EMPTY((head2))) {                                   \
+               *(head1)->stqh_last = (head2)->stqh_first;              \
+               (head1)->stqh_last = (head2)->stqh_last;                \
+               STAILQ_INIT((head2));                                   \
+       }                                                               \
+} while (0)
+
+#define        STAILQ_LAST(head, type, field)                                  \
+       (STAILQ_EMPTY((head)) ? NULL :                                  \
+               ((struct type *)(void *)                                \
+               ((char *)((head)->stqh_last) - offsetof(struct type, field))))
+
+#endif /* !BSD_QUEUE_H */
diff --git a/command.c b/command.c
new file mode 100644 (file)
index 0000000..20ca0fc
--- /dev/null
+++ b/command.c
@@ -0,0 +1,248 @@
+#include <strings.h>
+
+#include <event2/event.h>
+#include <event2/bufferevent.h>
+#include <event2/bufferevent_struct.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <pthread.h>
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include "command.h"
+#include "common.h"
+#include "connections.h"
+#include "db.h"
+#include "lua_interface.h"
+#include "notify.h"
+#include "server.h"
+
+/**
+ * Return a new populated command.
+ */
+struct command *
+command_new(unsigned char *command_line, size_t command_len, command_flags_t flags)
+{
+       struct command *command;
+
+       command = calloc(1, sizeof *command);
+       if (command == NULL) {
+               NOTIFY_ERROR("%s:%s", "calloc", strerror(errno));
+               return NULL;
+       }
+
+       command->command = command_line;
+       command->command_len = command_len;
+       command->flags = flags;
+
+       return command;
+}
+
+
+/**
+ * Release a command and its data.
+ */
+void
+command_free(struct command *command)
+{
+       command->command_len = 0;
+       command->flags = 0;
+       if (command->command) {
+               free(command->command);
+               command->command = NULL;
+       }
+       free(command);
+}
+
+
+/**
+ * This is invoked when a connection has successfully joined the server.
+ */
+void
+command_connected(struct connection *c)
+{
+       connection_printf(c, "Welcome!\n");
+       connection_printf_broadcast(c, true, "[%s has connected]\n", c->name);
+}
+
+
+/**
+ * This is invoked when a connection has been disconnected from the server,
+ * before the connection is destroyed.
+ */
+void
+command_disconnected(struct connection *c)
+{
+       connection_printf(c, "Goodbye!\n");
+       connection_printf_broadcast(c, true, "[%s has disconnected]\n", c->name);
+       NOTIFY_DEBUG("%s disconnected", c->name);
+}
+
+
+/**
+ * process some simple commands, until we get fancier
+ */
+void
+command_parse(struct connection *c, struct server_worker_context *ctx, size_t thread_id, struct command *command)
+{
+       NOTIFY_DEBUG("parsing '%s' from '%s' (%p) [%zu]", command->command, c->name, ctx, thread_id);
+
+       if (command->flags & COMMAND_FLAG_EVENT_CONNECT) {
+               command_connected(c);
+       }
+       if (command->flags & COMMAND_FLAG_EVENT_DISCONNECT) {
+               command_disconnected(c);
+       }
+
+       if (!command->command) {
+               return;
+       }
+
+       if (strncasecmp((char *)command->command, "auth", 4) == 0) {
+               char *cmd, *user, *secret, *state;
+               int uid = 0;
+               int r = 0;
+
+               cmd = strtok_r((char *)command->command, " \t\n\r", &state);
+               user = strtok_r(NULL, " \t\n\r", &state);
+               secret = strtok_r(NULL, " \t\n\r", &state);
+
+               if (!cmd || !user || !secret) {
+                       connection_printf(c, "Failed.\n");
+                       return;
+               }
+
+               NOTIFY_DEBUG("authentication request for '%s'", user);
+
+               r = db_uid_from_name(&c->server->db, (unsigned char *)user, &uid);
+               NOTIFY_DEBUG("r:%d uid: %d", r, uid);
+
+               connection_printf(c, ">> r:%d uid: %d\n", r, uid);
+
+               return;
+       }
+
+       if (memcmp(command->command, "QUIT", command->command_len) == 0) {
+               bufferevent_disable(c->bev, EV_READ);
+               connection_printf(c, "Goodbye! You sent me a total of %zu bytes! (user_time:%ld.%.6ld sys_time:%ld.%.6ld)\n",
+                                 c->total_read,
+                                 c->utime.tv_sec, (long int) c->utime.tv_usec,
+                                 c->stime.tv_sec, (long int) c->utime.tv_usec);
+               c->state = CONNECTION_STATE_WANT_CLOSE;
+               return;
+       }
+
+       if (memcmp(command->command, "@SHUTDOWN", command->command_len) == 0) {
+               struct timeval _now = { 0, 0 };
+               struct event_base *base = c->bev->ev_base; /* no accessor like bufferevent_get_base() so need to directly grab this with <event2/bufferevent_struct.h> */
+
+               connection_printf(c, "Killing server.\n");
+
+               event_base_loopexit(base, &_now);
+               return;
+       }
+
+       if (memcmp(command->command, "INFO", command->command_len) == 0) {
+               char *uuid_buf = NULL;
+               size_t uuid_len;
+               uuid_rc_t rc;
+
+               rc = uuid_export(c->uuid, UUID_FMT_STR, &uuid_buf, &uuid_len);
+               if (rc != UUID_RC_OK) {
+                       NOTIFY_ERROR("%s:%s", "uuid_export", uuid_error(rc));
+               }
+
+               connection_lock_output(c);
+               connection_printf(c, "You are connected from: %s\n", c->client_address);
+               connection_printf(c, "Your connection is %sencrypted.\n", c->flags & CONN_TYPE_SSL ? "" : "not ");
+               connection_printf(c, "Your UUID is: %s\n", uuid_buf);
+               connection_unlock_output(c);
+
+               free(uuid_buf);
+
+               return;
+       }
+
+       if (memcmp(command->command, "WHO", command->command_len) == 0) {
+               struct connection **clist;
+               struct connection **clist_iter;
+
+               clist_iter = clist = connections_all_as_array(&c->server->connections, NULL);
+
+               connection_lock_output(c);
+               while (*clist_iter) {
+                       connection_printf(c, "%s from %s%s%s\n",
+                                         (*clist_iter)->name,
+                                         (*clist_iter)->client_address,
+                                         (*clist_iter)->flags & CONN_TYPE_SSL ? " (via ssl)" : "",
+                                         (*clist_iter == c) ? " <- you" : "");
+                       connection_free(*clist_iter);
+                       clist_iter++;
+               }
+               connection_unlock_output(c);
+
+               free(clist);
+
+               return;
+       }
+
+#define TOSD(__off__) NOTIFY_DEBUG("TOS[%d]: %s '%s'", lua_gettop(L) - (__off__), lua_typename(L, lua_type(L, -1 - (__off__))), lua_tostring(L, -1 - (__off__)))
+       if (*command->command == '!') {
+               lua_State *L = ctx->L;
+               const char *echo = (const char *) command->command + 1;
+
+
+               // lemu_cosnnection_push(L, c); // c
+
+               // int t = lua_gettop(L);
+               // lua_pushnil(L);  /* first key */
+               // while (lua_next(L, t) != 0) {
+               //      /* uses 'key' (at index -2) and 'value' (at index -1) */
+               //      printf("k:%s v:%s\n",
+               //             lua_typename(L, lua_type(L, -2)),
+               //             lua_typename(L, lua_type(L, -1)));
+               //      /* removes 'value'; keeps 'key' for next iteration */
+               //      switch (lua_type(L, -2)) {
+               //              case LUA_TSTRING:
+               //                      printf("\tk:%s\n", lua_tostring(L, -2));
+               //                      break;
+               //              case LUA_TNUMBER:
+               //                      printf("\tk:%f\n", lua_tonumber(L, -2));
+               //                      break;
+               //              default:
+               //                      printf("\tk:?\n");
+               //      }
+               //      switch (lua_type(L, -1)) {
+               //              case LUA_TSTRING:
+               //                      printf("\tv:%s\n", lua_tostring(L, -1));
+               //                      break;
+               //              case LUA_TNUMBER:
+               //                      printf("\tv:%f\n", lua_tonumber(L, -1));
+               //                      break;
+               //              default:
+               //                      printf("\tv:?\n");
+               //      }
+               //      lua_pop(L, 1);
+               // }
+
+               NOTIFY_DEBUG("lua echo on '%s'", echo);
+               TOSD(0);
+               lemu_connection_push(L, c); // c
+               TOSD(0);
+               lua_getfield(L, 1, "send"); // c sendfn
+               TOSD(0);
+               lua_rotate(L, 1, 1); // sendfn c
+               TOSD(0);
+               lua_pushfstring(L, "(lua echo) %s", echo); // sendfn c str
+               TOSD(0);
+               lua_call(L, 2, 0);
+       }
+
+       /* default to broadcasting message */
+       connection_printf_broadcast(c, true, "%s said: %s\n", c->name, command->command);
+       connection_printf(c, "%s said: %s\n", "You", command->command);
+}
diff --git a/command.h b/command.h
new file mode 100644 (file)
index 0000000..b789927
--- /dev/null
+++ b/command.h
@@ -0,0 +1,24 @@
+#ifndef COMMAND_H
+#define COMMAND_H
+
+#include "server.h"
+
+typedef uint_fast8_t command_flags_t;
+enum command_flags {
+       COMMAND_FLAG_EVENT_CONNECT = (1<<0),
+       COMMAND_FLAG_EVENT_DISCONNECT = (1<<1),
+};
+
+struct command {
+       unsigned char *command;
+       size_t command_len;
+       command_flags_t flags;
+       STAILQ_ENTRY(command) stailq_entry;
+};
+
+struct command * command_new(unsigned char *command, size_t command_len, command_flags_t flags);
+void command_free(struct command *command);
+
+void command_parse(struct connection *c, struct server_worker_context *ctx, size_t thread_id, struct command *command);
+
+#endif /* COMMAND_H */
\ No newline at end of file
diff --git a/common.c b/common.c
new file mode 100644 (file)
index 0000000..fb45589
--- /dev/null
+++ b/common.c
@@ -0,0 +1,150 @@
+/*
+ *  General utility functions which don't have better places to live.
+ *
+ */
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netdb.h>
+#include <string.h>
+#include <errno.h>
+
+#include "common.h"
+#include "notify.h"
+
+/** timeval_diff
+ *  Calculate the difference between two timevals.
+ *  Lifted wholesale from the GNU libc manual.
+**/
+int
+timeval_diff(struct timeval *result, struct timeval *x, struct timeval *y)
+{
+       /* Perform the carry for the later subtraction by updating y. */
+       if (x->tv_usec < y->tv_usec) {
+               int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
+               y->tv_usec -= 1000000 * nsec;
+               y->tv_sec += nsec;
+       }
+       if (x->tv_usec - y->tv_usec > 1000000) {
+               int nsec = (x->tv_usec - y->tv_usec) / 1000000;
+               y->tv_usec += 1000000 * nsec;
+               y->tv_sec -= nsec;
+       }
+
+       /* Compute the time remaining to wait.
+          tv_usec is certainly positive. */
+       result->tv_sec = x->tv_sec - y->tv_sec;
+       result->tv_usec = x->tv_usec - y->tv_usec;
+
+       /* Return 1 if result is negative. */
+       return x->tv_sec < y->tv_sec;
+}
+
+/** timeval_increment
+ *  Increment one timeval by another.
+**/
+void
+timeval_increment(struct timeval *result, struct timeval *diff)
+{
+       const long u_factor = 1000000;
+
+       result->tv_sec += diff->tv_sec;
+       result->tv_usec += diff->tv_usec;
+       if (result->tv_usec < 0) {
+               result->tv_usec += u_factor;
+               result->tv_sec--;
+       } else if (result->tv_usec > u_factor) {
+               result->tv_usec -= u_factor;
+               result->tv_sec++;
+       }
+}
+
+/** string_to_addrinfo_call
+ *  Attempt to parse a sockaddr ip and port out of a string,
+ *  calls ai_cb function with result (until one success, or
+ *  for all results depending on flag values).
+ *
+ *  IPv6 addresses must be [bracketed]. (Because of colons.)
+ *  Accepts:
+ *      [ipv6]:port
+ *      ipv4:port
+ *  Returns:
+ *      0 on valid parse
+ *      -1 on error
+**/
+#define _CB_ITER_ALL    (1<<0)
+int
+string_to_addrinfo_call(char *in, unsigned int flags, int (*ai_cb)(struct addrinfo *, void *), void *cb_data)
+{
+       char *full_string, *port_start, *addr_start, *tmp;
+       struct addrinfo hints, *res=NULL, *res_iter;
+       int r;
+
+       full_string = strdup(in);
+       if (!full_string) {
+               NOTIFY_ERROR("%s:%s", "strdup", strerror(errno) );
+               return -1;
+       }
+
+       addr_start = strchr(full_string, '[');
+       if (addr_start) {
+               *addr_start++ = '\0';
+               tmp = strchr(addr_start, ']');
+               if (!tmp) {
+                       NOTIFY_ERROR("invalid %saddress '%s': %s", "IPv6 ", in, "unmatched brackets");
+                       free(full_string);
+                       return -1;
+               }
+               *tmp++ = '\0';
+               port_start = tmp;
+       } else {
+               addr_start = full_string;
+               port_start = addr_start;
+       }
+
+       tmp = strrchr(port_start, ':');
+       if (!tmp) {
+               NOTIFY_ERROR("invalid %saddress '%s': %s", "", in, "no port specified");
+               free(full_string);
+               return -1;
+       }
+       *tmp++ = '\0';
+       port_start = tmp;
+
+       /* We now have address and port as separate strings */
+
+       memset(&hints, 0, sizeof hints);
+
+       hints.ai_family = AF_UNSPEC;
+       hints.ai_socktype = SOCK_STREAM;
+
+       /* bind to equiv of INADDR_ANY for any AF, if address is specified as '*' */
+       if (strcmp(addr_start, "*") == 0) {
+               hints.ai_flags |= AI_PASSIVE;
+               addr_start = NULL;
+       }
+
+       r = getaddrinfo(addr_start, port_start, &hints, &res);
+       if (r) {
+               NOTIFY_ERROR("%s:%s", "getaddrinfo", gai_strerror(r));
+               free(full_string);
+               return -1;
+       }
+
+       for (res_iter = res; res_iter; res_iter = res_iter->ai_next) {
+               r = ai_cb(res_iter, cb_data);
+               if (r) {
+                       NOTIFY_DEBUG("%s:%d", "(ai_cb)", r);
+                       continue;
+               }
+
+               if (! (flags & _CB_ITER_ALL)) {
+                       break;
+               }
+       }
+       freeaddrinfo(res);
+       free(full_string);
+
+       return 0;
+}
diff --git a/common.h b/common.h
new file mode 100644 (file)
index 0000000..f7c0987
--- /dev/null
+++ b/common.h
@@ -0,0 +1,20 @@
+#ifndef COMMON_H
+#define COMMON_H
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#define UNUSED __attribute__((__unused__))
+
+int
+timeval_diff(struct timeval *result, struct timeval *x, struct timeval *y);
+
+void
+timeval_increment(struct timeval *result, struct timeval *diff);
+
+int
+string_to_addrinfo_call(char *in, unsigned int flags, int (*ai_cb)(struct addrinfo *, void *), void *cb_data);
+
+#endif /* COMMON_H */
diff --git a/connections.c b/connections.c
new file mode 100644 (file)
index 0000000..fa35f79
--- /dev/null
@@ -0,0 +1,927 @@
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <pthread.h>
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#ifndef NI_MAXHOST
+#      define NI_MAXHOST 1025
+#endif
+#ifndef NI_MAXSERV
+#      define NI_MAXSERV 32
+#endif
+
+#include <sys/resource.h> // needs _GNU_SOURCE for RUSAGE_THREAD
+
+#include <event2/bufferevent.h>
+#include <event2/buffer.h>
+#include <event2/event.h>
+#include <event2/util.h>
+
+#include <ossp/uuid.h>
+
+#include "common.h"
+#include "notify.h"
+#include "connections.h"
+#include "command.h"
+#include "server.h"
+
+/**
+ *  A reference-counted data buffer.
+ *  Used internally here for sending out one message to multiple connections, without
+ *  creating a copy for each connection.
+ *  Frees data pointer when the last reference is removed.
+ *  A connection thread increases the reference count as it sends the message to each
+ *  destination connection, while the libevent thread decrements as it completes each
+ *  transmission.
+ */
+struct rc_data_ {
+       pthread_mutex_t mutex;
+       size_t reference_count;
+       size_t data_len;
+       char *data;
+};
+
+/**
+ *  Allocates, initializes, and returns a new struct rc_data_ entity.
+ */
+static struct rc_data_ *
+rc_data_new_(char *data, size_t len)
+{
+       struct rc_data_ *rc;
+       int r;
+
+       if (!data) {
+               NOTIFY_DEBUG("data:%p len:%zu", data, len);
+               return NULL;
+       }
+
+       rc = malloc(sizeof *rc);
+       if (!rc) {
+               NOTIFY_ERROR("malloc(%zu): %s", sizeof *rc, strerror(errno));
+               return NULL;
+       }
+
+       if ( (r = pthread_mutex_init(&rc->mutex, NULL)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_init", strerror(r));
+               free(rc);
+               return NULL;
+       }
+
+       rc->reference_count = 1;
+       rc->data_len = len;
+       rc->data = data;
+
+       return rc;
+}
+
+/**
+ *  Increments the reference count of a struct rc_data_ entity.
+ */
+static void
+rc_data_ref_inc_(struct rc_data_ *rc)
+{
+       int r;
+
+       if ( (r = pthread_mutex_lock(&rc->mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_lock", strerror(r));
+               return;
+       }
+
+       rc->reference_count += 1;
+
+       if ( (r = pthread_mutex_unlock(&rc->mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r));
+       }
+}
+
+/**
+ *  Decrements the reference count of a struct rc_data_ entity
+ *  and frees everything if there are no more references.
+ */
+static void
+rc_data_free_(struct rc_data_ *rc)
+{
+       int r;
+
+       if ( (r = pthread_mutex_lock(&rc->mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_lock", strerror(r));
+               return;
+       }
+       rc->reference_count -= 1;
+       if (rc->reference_count > 0) {
+               if ( (r = pthread_mutex_unlock(&rc->mutex)) ) {
+                       NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r));
+               }
+               return;
+       }
+
+       free(rc->data);
+       rc->data = NULL;
+       if ( (r = pthread_mutex_unlock(&rc->mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r));
+       }
+       if ( (r = pthread_mutex_destroy(&rc->mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_destroy", strerror(r));
+       }
+
+       memset(rc, 0, sizeof *rc);
+
+       free(rc);
+}
+
+/**
+ *  Wrapper for rc_data_free_() of the proper callback type to be used by
+ *  evbuffer_add_reference() in connection_multi_send().
+ */
+static void
+rc_cleanup_cb_(const void *data UNUSED, size_t len UNUSED, void *arg)
+{
+       struct rc_data_ *r = arg;
+
+       rc_data_free_(r);
+}
+
+/**
+ *  Initializes a struct connections entity.
+ */
+int
+connections_init(struct connections *cs)
+{
+       int r;
+
+       cs->count = 0;
+       TAILQ_INIT(&cs->head);
+
+       if ( (r = pthread_mutex_init(&cs->mutex, NULL)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_init", strerror(r));
+               return -1;
+       }
+
+       return 0;
+}
+
+/**
+ * Cleanup a struct connections.
+ */
+void
+connections_fini(struct connections *cs)
+{
+       struct connection *c1, *c2;
+       int r;
+
+#if DEBUG_SILLY
+       NOTIFY_DEBUG("cs:%p", cs);
+#endif
+
+       /* free any connection entities */
+       c1 = TAILQ_FIRST(&cs->head);
+       while (c1 != NULL) {
+               c2 = TAILQ_NEXT(c1, tailq_entry);
+               connection_free(c1);
+               c1 = c2;
+       }
+       TAILQ_INIT(&cs->head);
+       cs->count = 0;
+
+       if ( (r = pthread_mutex_destroy(&cs->mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_destroy", strerror(r));
+       }
+}
+
+
+/**
+ *  Inserts a connection at the end of connections list.
+ */
+void
+connections_append(struct connection *c)
+{
+       struct connections *cs = &c->server->connections;
+       int r;
+
+#if DEBUG_SILLY
+       NOTIFY_DEBUG("c:%p", c);
+#endif
+
+       if ( (r = pthread_mutex_lock(&cs->mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_lock", strerror(r));
+               return;
+       }
+
+       TAILQ_INSERT_TAIL(&cs->head, c, tailq_entry);
+       cs->count += 1;
+
+       if ( (r = pthread_mutex_unlock(&cs->mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r));
+               return;
+       }
+}
+
+/**
+ *  Removes a connection from its struct connections list.
+ */
+void
+connections_remove(struct connection *c)
+{
+       struct connections *cs = &c->server->connections;
+       int r;
+
+#if DEBUG_SILLY
+       NOTIFY_DEBUG("c:%p", c);
+#endif
+
+       if ( !c || !cs) {
+               NOTIFY_DEBUG("c:%p connections:%p", c, cs);
+               return;
+       }
+
+       if ( (r = pthread_mutex_lock(&cs->mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_lock", strerror(r));
+               return;
+       }
+
+       TAILQ_REMOVE(&cs->head, c, tailq_entry);
+       cs->count -= 1;
+
+       if ( (r = pthread_mutex_unlock(&cs->mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r));
+               return;
+       }
+}
+
+/**
+ *  Allocates and returns a null-terminated array of pointers to all connection entities in
+ *  connections list, minus the 'c_exclude' connection, if set.
+ *  Used as a simple implementation of broadcasting a message.
+ *  This increments the reference count of each connection added to the array
+ *  so they need to individually be freed before freeing the array.
+ */
+struct connection **
+connections_all_as_array(struct connections *cs, struct connection *c_exclude)
+{
+       struct connection **all;
+       struct connection *c;
+       size_t i = 0;
+       int r;
+
+       if ( (r = pthread_mutex_lock(&cs->mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_lock", strerror(r));
+               return NULL;
+       }
+
+       all = calloc(cs->count + 1, sizeof *all);
+       if (!all) {
+               NOTIFY_ERROR("calloc(%zu, %zu): %s", cs->count + 1, sizeof *all, strerror(errno));
+               goto err_unlock;
+       }
+
+       TAILQ_FOREACH(c, &cs->head, tailq_entry) {
+               if (c != c_exclude
+               &&  c->state < CONNECTION_STATE_WANT_CLOSE
+               &&  c->state > CONNECTION_STATE_INIT) {
+                       connection_inc_ref(c);
+                       all[i++] = c;
+               }
+       }
+
+       if ( (r = pthread_mutex_unlock(&cs->mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r));
+               while (i) {
+                       i--;
+                       connection_free(c);
+               }
+               free(all);
+               return NULL;
+       }
+
+       return all;
+
+err_unlock:
+       if ( (r = pthread_mutex_unlock(&cs->mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r));
+       }
+       return NULL;
+}
+
+
+/**
+ * Wrap ev timeout event to re-resolve hostname.
+ */
+static void
+connection_resolve_retry_event_(evutil_socket_t fd UNUSED, short what UNUSED, void *arg)
+{
+       struct connection *c = arg;
+
+       NOTIFY_DEBUG("retrying hostname resolution");
+
+       c->dns_retry_ev = NULL;
+       c->dns_retries += 1;
+       connection_resolve_hostname(c);
+}
+
+/**
+ *  A callback used by connection_init to populate the reverse-lookup of a connection's
+ *  client_address.
+**/
+static void
+connection_reverse_dns_cb_(int result, char type, int count, int ttl UNUSED, void *addresses, void *ctx)
+{
+       struct connection *c = ctx;
+       char *old_client_address = c->client_address;
+
+       if (result != DNS_ERR_NONE) {
+               NOTIFY_ERROR("Error resolving: %s",
+                            evdns_err_to_string(result) );
+               c->evdns_request = NULL;
+
+               // TODO: configurable
+               // if (server_get_config_integer(c->server, "dns_retries_max")) {
+               // if (server_get_config_integer(c->server, "dns_retry_seconds")) {
+
+               if (c->dns_retries < 4) {
+                       struct timeval _seconds = {30, 0};
+                       c->dns_retry_ev = event_new(c->server->base, -1, EV_TIMEOUT, connection_resolve_retry_event_, c);
+                       event_add(c->dns_retry_ev, &_seconds);
+               };
+               return;
+       }
+
+       switch (type) {
+               case DNS_PTR:
+                       if (count < 1) {
+                               return;
+                       }
+                       if (count > 1) {
+                               NOTIFY_DEBUG("multiple records returned, using first");
+                       }
+                       c->client_address = strdup(((char **)addresses)[0]);
+                       NOTIFY_DEBUG("resolved [%s] as %s for '%s' (%p)", old_client_address, c->client_address, c->name, c);
+                       break;
+
+               default:
+                       NOTIFY_DEBUG("reverse lookup of [%s] returned dns type %d", c->client_address, type);
+                       break;
+       }
+       c->evdns_request = NULL;
+
+       if (old_client_address) {
+               free(old_client_address);
+       }
+}
+
+
+/**
+ * Perform reverse lookup of connection address.
+ */
+void
+connection_resolve_hostname(struct connection *c)
+{
+       if (c->evdns_request) {
+               NOTIFY_DEBUG("resolution already in progress");
+               return;
+       }
+
+       /* does libevent's evdns have a getnameinfo style call? */
+
+       switch (c->sa->sa_family) {
+       case AF_INET:
+               c->evdns_request = evdns_base_resolve_reverse(c->server->evdns_base, &((struct sockaddr_in *)c->sa)->sin_addr, 0, connection_reverse_dns_cb_, c);
+               break;
+
+       case AF_INET6:
+               c->evdns_request = evdns_base_resolve_reverse_ipv6(c->server->evdns_base, &((struct sockaddr_in6 *)c->sa)->sin6_addr, 0, connection_reverse_dns_cb_, c);
+               break;
+
+       default:
+               NOTIFY_DEBUG("unhandled address family %u", c->sa->sa_family);
+       }
+       if (!c->evdns_request) {
+               NOTIFY_DEBUG("could not submit PTR lookup request");
+       }
+}
+
+
+/**
+ *  Populates a connection with initial information and sets reference count.
+ */
+int
+connection_init(struct server *s, struct connection *c, struct bufferevent *bev, struct sockaddr *sock, int socklen, connection_flags_t flags)
+{
+       char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
+       int r;
+       uuid_rc_t rc;
+
+       if ( (r = pthread_mutex_init(&c->rc_mutex, NULL)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_init", strerror(r));
+               goto err;
+       }
+
+       if ( (r = pthread_mutex_init(&c->commands_mutex, NULL)) ) {
+               NOTIFY_ERROR("%s:%s", "pthead_mutex_init", strerror(r));
+               goto err_rc_mutex_destroy;
+       }
+
+       rc = uuid_create(&c->uuid);
+       if (rc != UUID_RC_OK) {
+               NOTIFY_ERROR("%s:%s", "uuid_create", uuid_error(rc));
+               goto err_commands_mutex_destroy;
+       }
+
+       rc = uuid_make(c->uuid, UUID_MAKE_V1|UUID_MAKE_MC);
+       if (rc != UUID_RC_OK) {
+               NOTIFY_ERROR("%s:%s", "uuid_make", uuid_error(rc));
+               goto err_uuid_destroy;
+       }
+
+       c->state = CONNECTION_STATE_INIT;
+       c->bev = bev;
+       c->flags = flags;
+       c->server = s;
+
+       /* render an IP from a sockaddr */
+       r = getnameinfo(sock, socklen, hbuf, sizeof hbuf, sbuf, sizeof sbuf, NI_NUMERICHOST | NI_NUMERICSERV);
+       if (r) {
+               NOTIFY_ERROR("%s:%s", "getnameinfo", gai_strerror(r));
+               strncpy(hbuf, "(unknown)", sizeof hbuf);
+               strncpy(sbuf, "(?)", sizeof sbuf);
+       }
+       c->client_address = strdup(hbuf);
+       if (!c->client_address) {
+               NOTIFY_ERROR("%s:%s", "strdup", strerror(errno));
+               goto err_uuid_destroy;
+       }
+
+       /* Past this point, errors are non-fatal. */
+       c->reference_count = 1;
+
+       /* Now try to resolve a name from client ip, in background. */
+       c->sa = sock;
+       c->sa_len = socklen;
+       c->dns_retries = 0;
+       c->dns_retry_ev = NULL;
+       connection_resolve_hostname(c);
+
+       /* FIXME: temporary name of connection for POC */
+       static int current_connection_number;
+       r = snprintf((char *)c->name, sizeof(c->name), "conn %d", current_connection_number++);
+       if ((size_t)r >= sizeof c->name) {
+               NOTIFY_ERROR("buffer truncated [%s:%d]", __FILE__, __LINE__);
+       }
+
+#if DARWIN
+       /* Darwin systems <10.12 lack clock_gettime (!) */
+       if (gettimeofday(&c->connect_time, NULL)) {
+               NOTIFY_ERROR("%s:%s", "gettimeofday", strerror(errno));
+       }
+       NOTIFY_DEBUG("c:%p [%s] from %s at %.24s", c, c->name, c->client_address, ctime(&c->connect_time.tv_sec));
+#else
+       if (clock_gettime(CLOCK_REALTIME, &c->connect_timespec)) {
+               NOTIFY_ERROR("%s:%s", "clock_gettime", strerror(errno));
+       }
+       NOTIFY_DEBUG("c:%p [%s] from %s at %.24s", c, c->name, c->client_address, ctime(&c->connect_timespec.tv_sec));
+#endif /* DARWIN */
+
+       return 0;
+
+err_uuid_destroy:
+       rc = uuid_destroy(c->uuid);
+       if (rc != UUID_RC_OK) {
+               NOTIFY_ERROR("%s:%s", "uuid_destroy", uuid_error(rc));
+       }
+err_commands_mutex_destroy:
+       if ( (r = pthread_mutex_destroy(&c->commands_mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_destroy", strerror(r));
+       }
+err_rc_mutex_destroy:
+       if ( (r = pthread_mutex_destroy(&c->rc_mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_destroy", strerror(r));
+       }
+err:
+       return -1;
+}
+
+
+/**
+ * Process a connection's commands as long as there are commands to process.
+ * FIXME: track how much time has been spend consuming commands, re-queue
+ *        processor job when over some threshold considering pending workqueue
+ */
+static void
+connection_process_queue_(void *data, void *context, size_t id)
+{
+       struct connection *c = data;
+       struct server_worker_context *ctx = context;
+       struct command *command;
+       struct rusage ru_start, ru_end;
+       struct timespec ts_start, ts_current;
+       int r;
+
+       if (clock_gettime(CLOCK_MONOTONIC, &ts_start) < 0) {
+               NOTIFY_DEBUG("%s:%s", "clock_gettime", strerror(errno));
+       }
+       while (1) {
+               if ( (r = pthread_mutex_lock(&c->commands_mutex)) ) {
+                       NOTIFY_ERROR("%s:%s", "pthread_mutex_lock", strerror(r));
+                       return;
+               }
+
+               command = STAILQ_FIRST(&c->commands_head);
+               if (command) {
+                       STAILQ_REMOVE_HEAD(&c->commands_head, stailq_entry);
+               }
+
+               if ( (r = pthread_mutex_unlock(&c->commands_mutex)) ) {
+                       NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r));
+                       return;
+               }
+
+               if (command == NULL) {
+                       break;
+               }
+
+               if ( (r = getrusage(RUSAGE_THREAD, &ru_start)) ) {
+                       NOTIFY_ERROR("%s:%s", "getrusage", strerror(r));
+               }
+
+               command_parse(c, context, id, command);
+
+               if ( (r = getrusage(RUSAGE_THREAD, &ru_end)) ) {
+                       NOTIFY_ERROR("%s:%s", "getrusage", strerror(r));
+               }
+
+               /* track how much time was spent procesing */
+               connection_accounting_increment(c, &ru_start, &ru_end);
+
+               command_free(command);
+
+               if (clock_gettime(CLOCK_MONOTONIC, &ts_current) < 0) {
+                       NOTIFY_ERROR("%s:%s", "clock_gettime", strerror(errno));
+               }
+
+       }
+       connection_free(c);
+
+       // Done processing for a connection, force GC cycle to release additional c references.
+       lua_gc(ctx->L, LUA_GCCOLLECT);
+}
+
+
+/**
+ *  Adds a command to a connection's list of things to process.
+ */
+int
+connection_command_enqueue(struct connection *c, struct command *command)
+{
+       bool was_empty;
+       int r;
+
+       if ( (r = pthread_mutex_lock(&c->commands_mutex)) ) {
+               command_free(command);
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_lock", strerror(r));
+               return -1;
+       }
+
+       was_empty = STAILQ_EMPTY(&c->commands_head) ? true : false;
+       if (was_empty == true) {
+               STAILQ_INSERT_HEAD(&c->commands_head, command, stailq_entry);
+       } else {
+               STAILQ_INSERT_TAIL(&c->commands_head, command, stailq_entry);
+       }
+
+#if DARWIN
+       /* Darwin systems <10.12 lack clock_gettime (!) */
+       if (gettimeofday(&c->last_received_time, NULL)) {
+               NOTIFY_ERROR("%s:%s", "gettimeofday", strerror(errno));
+       }
+#else
+       if (clock_gettime(CLOCK_REALTIME, &c->last_received_timespec)) {
+               NOTIFY_ERROR("%s:%s", "clock_gettime", strerror(errno));
+       }
+#endif /* DARWIN */
+
+       if ( (r = pthread_mutex_unlock(&c->commands_mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r));
+               return -1;
+       }
+
+       /* if there were already queued commands, a processor should already be scheduled */
+       if (was_empty == true) {
+               connection_inc_ref(c);
+               if (workqueue_add(&c->server->workqueue, connection_process_queue_, c)) {
+                       NOTIFY_ERROR("%s:%s", "workqueue_add", "failed");
+                       return -1;
+               }
+       }
+
+       return 0;
+}
+
+
+/**
+ *  Add the utime and stime values to a connection's totals.
+ */
+void
+connection_accounting_increment(struct connection *c, struct rusage *ru_start, struct rusage *ru_end)
+{
+       struct timeval utime_diff, stime_diff;
+
+       timeval_diff(&utime_diff, &ru_end->ru_utime, &ru_start->ru_utime);
+       timeval_diff(&stime_diff, &ru_end->ru_stime, &ru_start->ru_stime);
+
+       timeval_increment(&c->utime, &utime_diff);
+       timeval_increment(&c->stime, &stime_diff);
+}
+
+/**
+ *  Locks the output buffer, for sending multiple
+ *  contiguous messages without possible interleaving.
+ */
+void
+connection_lock_output(struct connection *c)
+{
+       struct evbuffer *output = bufferevent_get_output(c->bev);
+
+       evbuffer_lock(output);
+}
+
+/**
+ *  Unlocks the output buffer.
+ */
+void
+connection_unlock_output(struct connection *c)
+{
+       struct evbuffer *output = bufferevent_get_output(c->bev);
+
+       evbuffer_unlock(output);
+}
+
+/**
+ *  Sends a text string to a connection.
+ */
+int
+connection_printf(struct connection *c, const char *fmt, ...)
+{
+       struct evbuffer *output = bufferevent_get_output(c->bev);
+       va_list ap;
+       int r;
+
+       va_start(ap, fmt);
+       r = evbuffer_add_vprintf(output, fmt, ap);
+       va_end(ap);
+
+       return r;
+}
+
+
+/**
+ *  Sends a text string to a connection.
+ */
+int
+connection_vprintf(struct connection *c, const char *fmt, va_list ap)
+{
+       struct evbuffer *output = bufferevent_get_output(c->bev);
+       int r;
+
+       r = evbuffer_add_vprintf(output, fmt, ap);
+
+       return r;
+}
+
+
+/**
+ *  Send one data buffer to multiple connections.
+ *  Data buffer must be malloced, and will be freed when last connection
+ *  has finished with it.
+ *  Returns number of items sent, or negative on error.
+ */
+int
+connection_multi_send(struct connection **clist, char *data, size_t len)
+{
+       struct connection **clist_iter;
+       struct rc_data_ *rc;
+       int r = 0;
+
+       if (!clist) {
+               NOTIFY_DEBUG("null clist [%s:%d]", __FILE__, __LINE__);
+               return -1;
+       }
+
+       /* Track our data so it can be freed once all connections are done with it. */
+       rc = rc_data_new_(data, len);
+       if (rc == NULL) {
+               NOTIFY_ERROR("rc_data_new_('%s', %zu) failed", data, len);
+               return -1;
+       }
+
+       clist_iter = clist;
+       while (*clist_iter) {
+               struct evbuffer *output = bufferevent_get_output((*clist_iter)->bev);
+
+               /* FIXME: if connection is still valid... */
+
+               /* FIXME: clang claims this is a use-after-free if evbuffer_add_reference fails, but I don't yet believe it. */
+               rc_data_ref_inc_(rc);
+               if (evbuffer_add_reference(output, rc->data, rc->data_len, rc_cleanup_cb_, rc)) {
+                       NOTIFY_ERROR("%s:%s", "evbuffer_add_reference", "failed");
+                       rc_data_free_(rc);
+               } else {
+                       r++;
+               }
+               clist_iter++;
+       }
+
+       /* FIXME: clang also claims this is use-after-free, same as above. */
+       rc_data_free_(rc);
+
+       return r;
+}
+
+
+int
+connection_printf_broadcast(struct connection *sender, bool exclude_sender, const char *fmt, ...)
+{
+       struct connection **clist;
+       char *message = NULL;
+       ssize_t message_len = 0;
+       va_list ap;
+       int r;
+
+       va_start(ap, fmt);
+       message_len = vsnprintf(message, message_len, fmt, ap);
+       va_end(ap);
+       if (message_len < 0) {
+               NOTIFY_ERROR("%s:%s (%zd)", "vsnprintf", strerror(errno), message_len);
+               return -1;
+       }
+       message_len += 1;
+       message = malloc(message_len);
+       if (!message) {
+               NOTIFY_ERROR("%s(%zd):%s", "malloc", message_len, strerror(errno));
+               return -1;
+       }
+       va_start(ap, fmt);
+       message_len = vsnprintf(message, message_len, fmt, ap);
+       va_end(ap);
+       if (message_len < 0) {
+               NOTIFY_ERROR("%s:%s (%zd)", "vsnprintf", strerror(errno), message_len);
+               free(message);
+               return -1;
+       }
+
+#ifdef DEBUG_SILLY
+               NOTIFY_DEBUG("message_len:%zu message:%s", message_len, message);
+#endif
+
+       clist = connections_all_as_array(&sender->server->connections, exclude_sender == true ? sender : NULL);
+       r = connection_multi_send(clist, message, message_len);
+       if (r < 0) {
+               NOTIFY_ERROR("%s:%s", "connection_multi_send", "failed");
+       }
+       for (struct connection **clist_iter = clist; *clist_iter; clist_iter++) {
+               connection_free(*clist_iter);
+       }
+       free(clist);
+
+       return r;
+}
+
+/**
+ *
+ */
+void
+connection_inc_ref(struct connection *c)
+{
+       int r;
+
+       if ( (r = pthread_mutex_lock(&c->rc_mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_lock", strerror(r));
+               return;
+       }
+
+       c->reference_count += 1;
+
+#if DEBUG_SILLY
+       NOTIFY_DEBUG("c:%p new reference_count:%zu", c, c->reference_count);
+#endif
+
+       if ( (r = pthread_mutex_unlock(&c->rc_mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r));
+               return;
+       }
+}
+
+/**
+ *  Frees a connection (and all data owned by a connection) if there are no more references to it.
+ */
+void
+connection_free(struct connection *c)
+{
+       struct evbuffer *tmp;
+       evutil_socket_t fd;
+       int r;
+
+       if ( (r = pthread_mutex_lock(&c->rc_mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_lock", strerror(r));
+               return;
+       }
+
+       c->reference_count -= 1;
+
+#if DEBUG_SILLY
+       NOTIFY_DEBUG("c:%p new reference_count:%zu", c, c->reference_count);
+#endif
+
+       if (c->reference_count > 0) {
+               if ( (r = pthread_mutex_unlock(&c->rc_mutex)) ) {
+                       NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r));
+               }
+
+               return;
+       }
+
+       NOTIFY_DEBUG("c:%p freeing", c);
+
+       connections_remove(c);
+
+       bufferevent_disable(c->bev, EV_READ|EV_WRITE);
+       tmp = bufferevent_get_output(c->bev);
+       evbuffer_drain(tmp, evbuffer_get_length(tmp));
+       tmp = bufferevent_get_input(c->bev);
+       evbuffer_drain(tmp, evbuffer_get_length(tmp));
+
+       fd = bufferevent_getfd(c->bev);
+       if (fd != -1) {
+               EVUTIL_CLOSESOCKET(fd);
+               bufferevent_setfd(c->bev, -1);
+       }
+
+       if (c->dns_retry_ev) {
+               if (event_del(c->dns_retry_ev)) {
+                       NOTIFY_ERROR("%s:%s", "event_del", strerror(errno));
+               }
+               c->dns_retry_ev = NULL;
+       }
+
+       if (c->evdns_request) {
+               evdns_cancel_request(c->server->evdns_base, c->evdns_request);
+               c->evdns_request = NULL;
+       }
+
+       if (c->client_address) {
+               free(c->client_address);
+               c->client_address = NULL;
+       }
+
+       bufferevent_free(c->bev);
+
+       if (c->uuid) {
+               uuid_rc_t rc;
+
+               rc = uuid_destroy(c->uuid);
+               if (rc != UUID_RC_OK) {
+                       NOTIFY_ERROR("%s:%s", "uuid_destroy", uuid_error(rc));
+               }
+               c->uuid = NULL;
+       }
+
+       /* empty out the command queue */
+       if ( (r = pthread_mutex_lock(&c->commands_mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_lock", strerror(r));
+       }
+
+       struct command *c1, *c2;
+       c1 = STAILQ_FIRST(&c->commands_head);
+       while (c1 != NULL) {
+               c2 = STAILQ_NEXT(c1, stailq_entry);
+               free(c1);
+               c1 = c2;
+       }
+       STAILQ_INIT(&c->commands_head);
+
+       if ( (r = pthread_mutex_unlock(&c->commands_mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r));
+       }
+       if ( (r = pthread_mutex_destroy(&c->commands_mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_destroy", strerror(r));
+       }
+
+       if ( (r = pthread_mutex_unlock(&c->rc_mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r));
+       }
+       if ( (r = pthread_mutex_destroy(&c->rc_mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_destroy", strerror(r));
+       }
+
+       memset(c, 0, sizeof *c);
+
+       free(c);
+}
diff --git a/connections.h b/connections.h
new file mode 100644 (file)
index 0000000..5c0a3f8
--- /dev/null
@@ -0,0 +1,109 @@
+#ifndef CONNECTIONS_H
+#define CONNECTIONS_H
+
+#include <event2/bufferevent.h>
+#include <event2/buffer.h>
+#include <event2/dns.h>
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <arpa/inet.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+
+#include <ossp/uuid.h>
+
+#include "bsd_queue.h"
+#include "workqueue.h"
+
+enum connection_state_enum {
+       CONNECTION_STATE_INIT = 0,
+       CONNECTION_STATE_CONNECTED,
+       CONNECTION_STATE_AUTHENTICATED,
+       CONNECTION_STATE_WANT_CLOSE,
+       CONNECTION_STATE_CLOSED
+};
+
+typedef uint_fast8_t connection_flags_t;
+enum connection_flags {
+       CONN_TYPE_SSL = (1<<0),
+};
+
+struct connection {
+       pthread_mutex_t rc_mutex;               /* mutex for ref count */
+       size_t reference_count;                 /* */
+
+       struct server *server;
+
+       enum connection_state_enum state;
+       connection_flags_t flags;
+
+       struct bufferevent *bev;
+       uuid_t *uuid;                           /* uuid */
+       
+       char name[256];                         /* temporary auto-generated unique name */
+
+#ifdef DARWIN
+       struct timeval connect_time;            /* time of instantiation of this connection entity */
+       struct timeval last_received_time;      /* time of last data received from connection */
+#else
+       struct timespec connect_timespec;       /* time this connection entity was initialized */
+       struct timespec last_received_timespec; /* time of last data received from connection */
+#endif /* DARWIN */
+
+       size_t total_read;                      /* a tally of number of bytes read from connection */
+       struct timeval utime;                   /* accounting: rusage usertime accumulation */
+       struct timeval stime;                   /* accounting: rusage systime accumulation */
+
+       char *client_address;
+       struct sockaddr *sa;
+       int sa_len;
+       struct evdns_request *evdns_request;    /* the request for looking up the reverse of sa */
+       struct event *dns_retry_ev;             /* timeout event for retrying failed dns */
+       size_t dns_retries;
+
+       pthread_mutex_t commands_mutex;
+       STAILQ_HEAD(commands_stailq_head, command) commands_head;
+       volatile struct thread_struct *owner;   /* set if command queue is currently being consumed */
+
+       TAILQ_ENTRY(connection) tailq_entry;
+};
+
+struct connections {
+       pthread_mutex_t mutex;
+
+       TAILQ_HEAD(connection_tailq_head, connection) head;
+       size_t count;
+};
+
+
+
+int connections_init(struct connections *cs);
+void connections_fini(struct connections *cs);
+
+void connections_append(struct connection *c);
+void connections_remove(struct connection *c);
+
+struct connection ** connections_all_as_array(struct connections *cs, struct connection *c_exclude);
+
+int connection_init(struct server *s, struct connection *c, struct bufferevent *bev, struct sockaddr *sock, int socklen, connection_flags_t flags);
+void connection_inc_ref(struct connection *c);
+void connection_free(struct connection *c);
+
+int connection_command_enqueue(struct connection *c, struct command *command);
+
+void connection_accounting_increment(struct connection *c, struct rusage *ru_start, struct rusage *ru_end);
+
+void connection_lock_output(struct connection *c);
+void connection_unlock_output(struct connection *c);
+
+int connection_printf(struct connection *c, const char *fmt, ...);
+int connection_vprintf(struct connection *c, const char *fmt, va_list ap);
+int connection_printf_broadcast(struct connection *sender, bool exclude_sender, const char *fmt, ...);
+
+int connection_multi_send(struct connection **clist, char *data, size_t len);
+
+void connection_resolve_hostname(struct connection *c);
+
+
+#endif /* CONNECTIONS_H */
diff --git a/db.c b/db.c
new file mode 100644 (file)
index 0000000..d089231
--- /dev/null
+++ b/db.c
@@ -0,0 +1,179 @@
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include <sqlite3.h>
+
+#include "common.h"
+#include "notify.h"
+#include "db.h"
+
+static const char db_engine_[] = "sqlite3";
+
+struct prepared_statement {
+       const char *statement_name;
+       const char *arguments;
+       const char *query;
+       sqlite3_stmt *statement;
+} prepared_statements[] = {
+       { "begin_transaction", "", "BEGIN TRANSACTION", NULL },
+       { "end_transaction", "", "END TRANSACTION", NULL },
+       { "vacuum", "", "VACUUM", NULL },
+       { "uid_from_name", "s", "SELECT uid FROM uid_name WHERE name = ?", NULL },
+       { NULL, NULL, NULL, NULL }
+};
+
+enum prepared_statement_enums {
+       PS_BEGIN,
+       PS_END,
+       PS_VACUUM,
+       PS_UID_FROM_NAME,
+       _NUM_PS_ENUMS
+};
+
+/**
+ *  pre-prepare all queries used
+ */
+static int
+db_init_all_statements_(sqlite3 *db)
+{
+       struct prepared_statement *s;
+       int r;
+
+       for (s = prepared_statements; s->statement_name; s++) {
+               r = sqlite3_prepare_v2(db, s->query, -1, &s->statement, NULL);
+               if (r != SQLITE_OK) {
+                       NOTIFY_ERROR("%s('%s'):%d:%s", "sqlite3_prepare_v2", s->statement_name, r, sqlite3_errmsg(db));
+                       return -1;
+               }
+       }
+
+       return 0;
+}
+
+/**
+ *  finalize all queries used
+ */
+static void
+db_finalize_all_statements_(sqlite3 *db)
+{
+       struct prepared_statement *s;
+       int r;
+
+       for (s = prepared_statements; s->statement_name; s++) {
+               if (s->statement) {
+                       r = sqlite3_finalize(s->statement);
+                       if (r != SQLITE_OK) {
+                               NOTIFY_ERROR("%s('%s'):%d:%s", "sqlite3_finalize", s->statement_name, r, sqlite3_errmsg(db));
+                       }
+                       s->statement = NULL;
+               }
+       }
+}
+
+const char *
+db_engine()
+{
+       return db_engine_;
+}
+
+const char *
+db_version()
+{
+       return sqlite3_libversion();
+}
+
+
+/**
+ * 
+ */
+int
+db_init(struct database *db, struct database_options *db_opts)
+{
+       int r;
+
+       /* Ensure sqlite header matches library. */
+       assert(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER);
+       assert(strncmp(sqlite3_sourceid(), SQLITE_SOURCE_ID, 80) == 0);
+       assert(strcmp(sqlite3_libversion(), SQLITE_VERSION) == 0);
+
+       r = sqlite3_open_v2(db_opts->db_file, &db->db, SQLITE_OPEN_READWRITE, NULL);
+       if (r != SQLITE_OK) {
+               NOTIFY_ERROR("error opening %s '%s': %s", "db_file", db_opts->db_file, sqlite3_errmsg(db->db));
+               return -1;
+       }
+
+       if (db_init_all_statements_(db->db)) {
+               NOTIFY_ERROR("%s:%s", "db_init_all_statements", "failed");
+               return -1;
+       }
+
+       return 0;
+}
+
+/**
+ * 
+ */
+void
+db_fini(struct database *db)
+{
+       int r;
+
+       db_finalize_all_statements_(db->db);
+       r = sqlite3_close(db->db);
+       if (r != SQLITE_OK) {
+               NOTIFY_ERROR("%s:%s", "sqlite3_close", sqlite3_errmsg(db->db));
+       }
+       db->db = NULL;
+}
+
+/** 
+ *  given a string, returns the uid which matches
+ */
+int
+db_uid_from_name(struct database *db, unsigned char *name, int *uid)
+{
+       struct prepared_statement *ps = &prepared_statements[PS_UID_FROM_NAME];
+       int retval = -1;
+       int r;
+
+       r = sqlite3_bind_text(ps->statement, 1, (char *)name, -1, SQLITE_TRANSIENT);
+       if (r != SQLITE_OK) {
+               NOTIFY_ERROR("%s('%s'):%d:%s", "sqlite3_bind_text", name, r, sqlite3_errmsg(db->db));
+               return -2;
+       }
+
+       do {
+               r = sqlite3_step(ps->statement);
+               if (r == SQLITE_ROW) {
+                       if (retval == 0) {
+                               NOTIFY_ERROR("%s:%s", "internal db error", "multiple uids for name");
+                       }
+                       *uid = sqlite3_column_int(ps->statement, 0);
+                       retval = 0;
+
+                       if (*uid == 0) {
+                               if (SQLITE_INTEGER != sqlite3_column_type(ps->statement, 0)) {
+                                       NOTIFY_ERROR("%s:%s", "internal db error", "uid is not an integer");
+                                       retval = -3;
+                               }
+                       }
+               }
+       } while (r == SQLITE_ROW || r == SQLITE_BUSY);
+
+       if (r != SQLITE_DONE) {
+               NOTIFY_ERROR("%s('%s'):%d:%s", "sqlite3_step", name, r, sqlite3_errmsg(db->db));
+               retval = -3;
+       }
+
+       r = sqlite3_reset(ps->statement);
+       if (r != SQLITE_OK) {
+               NOTIFY_ERROR("%s('%s'):%d:%s", "sqlite3_reset", name, r, sqlite3_errmsg(db->db));
+       }
+       r = sqlite3_clear_bindings(ps->statement);
+       if (r != SQLITE_OK) {
+               NOTIFY_ERROR("%s('%s'):%d:%s", "sqlite3_clear_bindings", name, r, sqlite3_errmsg(db->db));
+       }
+
+       return retval;
+}
diff --git a/db.h b/db.h
new file mode 100644 (file)
index 0000000..a1f929f
--- /dev/null
+++ b/db.h
@@ -0,0 +1,22 @@
+#ifndef DB_H
+#define DB_H
+
+#include <sqlite3.h>
+
+struct database {
+       sqlite3 *db;
+};
+
+struct database_options {
+       char *db_file;
+};
+
+int db_init(struct database *db, struct database_options *db_opts);
+void db_fini(struct database *db);
+
+const char *db_engine();
+const char *db_version();
+
+int db_uid_from_name(struct database *db, unsigned char *name, int *uid);
+
+#endif /* DB_H */
\ No newline at end of file
diff --git a/lua_interface.c b/lua_interface.c
new file mode 100644 (file)
index 0000000..03496e8
--- /dev/null
@@ -0,0 +1,168 @@
+#include <stdlib.h>
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include "connections.h"
+#include "lua_interface.h"
+#include "notify.h"
+
+#define CONNECTION_META_TABLE "MetaConnection"
+#define CONNECTION_TABLE "Connection"
+#define CONNECTION_TABLE_UD_FIELD "core_"
+
+/**
+ * HARD: shared common server state, updatable commands table
+ * Interim: independent states, any global changes queued to all workers
+ * Idea: can a proxy table be crafted to access a shared table-like c entity
+ * Idea: eventual-consistency between lua states (somehow)
+ * 
+ */
+
+
+/**
+ * Create a new referenced coroutine on lua state.
+ * release with:
+ *   luaL_unref(L, LUA_REGISTRYINDEX, refkey);
+ */
+int
+lua_new_coroutine_ref(lua_State *L, lua_State **coL, int *refkey)
+{
+       *refkey = LUA_NOREF;
+
+       /* Create new coroutine on top of stack. */
+       *coL = lua_newthread(L);
+       if (!*coL) {
+               NOTIFY_ERROR("%s:%s", "lua_newthread", "failed");
+               return -1;
+       }
+
+       /* Store coroutine reference in state registry. */
+       *refkey = luaL_ref(L, LUA_REGISTRYINDEX);
+
+       return 0;
+}
+
+
+/**
+ * Connection Object
+ * is a table with metatable for methods and userdata field
+ * 
+ */
+
+
+/**
+ * Add a connection reference to lua stack
+ * 
+ */
+int
+lemu_connection_push(lua_State *L, struct connection *c)
+{
+       connection_inc_ref(c);
+
+       lua_newtable(L); // new connection table
+       luaL_getmetatable(L, CONNECTION_META_TABLE); // locate meta
+       lua_setmetatable(L, -2); // assign meta to connection table
+
+       /* keeping a pointer in userdata, rather than light userdata, to get metatable support */
+       struct connection **cud = lua_newuserdatauv(L, sizeof c, 0);
+       *cud = c;
+       luaL_getmetatable(L, CONNECTION_META_TABLE);
+       lua_setmetatable(L, -2);
+
+       lua_setfield(L, -2, CONNECTION_TABLE_UD_FIELD);
+       return 1;
+}
+
+
+/**
+ * I guess this one would take a stack value to find a connection
+ */
+static int
+lemu_connection_create_(lua_State *L)
+{
+       struct connection *c;
+
+       luaL_checktype(L, 1, LUA_TSTRING);
+       // c = connection_lookup(lua_tostring(L, 1));
+       c = NULL;
+
+       return lemu_connection_push(L, c);
+}
+
+
+/**
+ * this will only get called on userdata (how does that happen??)
+ */
+static int
+lemu_connection_destroy_(lua_State *L)
+{
+       struct connection **cud = luaL_checkudata(L, 1, CONNECTION_META_TABLE);
+       struct connection *c = *cud;
+
+       connection_free(c);
+
+       return 0;
+}
+
+
+/**
+ * Send a fixed string to a connection.
+ */
+static int
+lemu_connection_send_(lua_State *L)
+{
+       if (lua_gettop(L) < 2) {
+               lua_pushliteral(L, "too few arguments");
+               lua_error(L);
+       }
+       luaL_checktype(L, 1, LUA_TTABLE);
+       lua_getfield(L, 1, CONNECTION_TABLE_UD_FIELD);
+       luaL_checktype(L, -1, LUA_TUSERDATA);
+       struct connection **cud = lua_touserdata(L, -1);
+       struct connection *c = *cud;
+
+       luaL_checktype(L, 2, LUA_TSTRING);
+       const char *message = lua_tostring(L, 2);
+
+       connection_printf(c, "%s\n", message);
+
+       return 0;
+}
+
+
+static const luaL_Reg Connection_funcs[] = {
+       { "create", lemu_connection_create_ },
+       { NULL, NULL }
+};
+
+
+static const luaL_Reg Connection_methods[] = {
+       { "__gc", lemu_connection_destroy_ },
+       { "send", lemu_connection_send_ },
+       { NULL, NULL }
+};
+
+
+/**
+ * Initialize the Connection object prototypes
+ */
+int
+lemu_connection_luainit(lua_State *L)
+{
+       luaL_newmetatable(L, CONNECTION_META_TABLE); // new userdata metatable, __name = CONNECTION_META_TABLE
+       lua_pushstring(L, "__index");
+       lua_pushvalue(L, -2); // meta table
+       lua_settable(L, -3); // becomes its own index table
+       luaL_setfuncs(L, Connection_methods, 0); // add methods to metatable
+       lua_pop(L, 1);
+
+       luaL_newlibtable(L, Connection_funcs); // new table with functions
+       luaL_setfuncs(L, Connection_funcs, 0);
+       lua_setglobal(L, CONNECTION_TABLE); // make it available
+
+       return 0;
+}
+
+// int luaopen_lemu(lua_State *L);
diff --git a/lua_interface.h b/lua_interface.h
new file mode 100644 (file)
index 0000000..7f897b0
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef LUA_INTERFACE_H
+#define LUA_INTERFACE_H
+
+int lemu_connection_luainit(lua_State *);
+
+int lemu_connection_push(lua_State *, struct connection *);
+
+#endif /* LUA_INTERFACE_H */
diff --git a/main.c b/main.c
new file mode 100644 (file)
index 0000000..9b0d98d
--- /dev/null
+++ b/main.c
@@ -0,0 +1,93 @@
+/*
+ * toy server
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <pthread.h>
+#include <arpa/inet.h>
+
+#include <event2/event.h>
+#include <event2/thread.h>
+
+#include <openssl/ssl.h>
+#include <openssl/crypto.h>
+#include <openssl/err.h>
+
+#include "version.h"
+#include "notify.h"
+#include "server.h"
+
+#define DEFAULT_CONFIG_FILE "conf/lemu.conf"
+
+#define USAGE_FLAGS_LONG (1<<0)
+static void
+usage_(char *prog, unsigned int usage_flags)
+{
+       char *x;
+
+       x = strrchr(prog, '/');
+       if (x && *(x + 1)) {
+               prog = x + 1;
+       }
+
+       printf("Usage: %s [-c config_file]\n", prog);
+       if (! (usage_flags & USAGE_FLAGS_LONG)) {
+               return;
+       }
+       printf(
+              "\t-h \t : this help\n"
+              "\t-c config_file\t : loads the specified config file [default: '" DEFAULT_CONFIG_FILE "']\n"
+       );
+}
+
+int
+main(int argc, char **argv)
+{
+       struct server *server;
+       char *conf_file = DEFAULT_CONFIG_FILE;
+       int opt;
+       int r;
+
+       /* enable threading in libevent before anything tries to use it */
+       if ( (r = evthread_use_pthreads()) ) {
+               NOTIFY_ERROR("%s:%d", "evthread_use_pthreads", r);
+       }
+
+       SSL_load_error_strings();
+       SSL_library_init();
+       OpenSSL_add_all_algorithms();
+
+       while ( (opt = getopt(argc, argv, "c:h")) != -1 ) {
+               switch (opt) {
+               case 'c':
+                       conf_file = optarg;
+                       break;
+               case 'h':
+                       usage_(argv[0], USAGE_FLAGS_LONG);
+                       exit(EXIT_SUCCESS);
+                       break;
+               default:
+                       usage_(argv[0], 0);
+                       exit(EXIT_FAILURE);
+               }
+       }
+
+       server = server_new();
+       if (!server) {
+               NOTIFY_FATAL("server_new");
+               exit(EXIT_FAILURE);
+       }
+
+       if (server_init(server, conf_file)) {
+               NOTIFY_FATAL("error parsing config file '%s'", conf_file);
+               exit(EXIT_FAILURE);
+       }
+
+       server_free(server);
+
+       exit(EXIT_SUCCESS);
+}
diff --git a/notify.c b/notify.c
new file mode 100644 (file)
index 0000000..8662f05
--- /dev/null
+++ b/notify.c
@@ -0,0 +1,52 @@
+#define _GNU_SOURCE 1
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+
+#include "notify.h"
+
+/*
+ * Generic console notifier.
+ */
+
+static void
+notify_fn_default_(int level, const char * func_name, const char * fmt, ...)
+{
+       FILE  *f = (level <= 1) ? stderr : stdout;
+       static const char * const levels[] = { "FATAL", "ERROR", "INFO", "DEBUG" };
+       const int levels_num = sizeof levels / sizeof *levels;
+       va_list ap;
+
+       flockfile(f);
+
+       fputs_unlocked((func_name && *func_name) ? func_name : "[null]", f);
+       fputs_unlocked(": --", f);
+       if (level >= 0
+       &&  level <= levels_num) {
+               fputs_unlocked(levels[level], f);
+       } else {
+               fprintf(f, "%d", level);
+       }
+       fputs_unlocked("-- ", f);
+
+       va_start(ap, fmt);
+       vfprintf(f, fmt, ap);
+       va_end(ap);
+
+       fputc_unlocked('\n', f);
+       fflush_unlocked(f);
+
+       funlockfile(f);
+
+}
+
+void (* notify_fn)(int level, const char *func_name, const char *msg, ...) __attribute__((format(printf, 3, 4))) = notify_fn_default_;
+
+void
+notify_fn_set(void (* fn)(int level, const char *func_name, const char *fmt, ...))
+{
+       notify_fn = fn;
+}
diff --git a/notify.h b/notify.h
new file mode 100644 (file)
index 0000000..7a31faa
--- /dev/null
+++ b/notify.h
@@ -0,0 +1,14 @@
+#ifndef NOTIFY_H
+#define NOTIFY_H
+
+#define NOTIFY_FATAL(msg, args...) do { if (notify_fn) notify_fn(0, __func__, msg , ##args); } while (0)
+#define NOTIFY_ERROR(msg, args...) do { if (notify_fn) notify_fn(1, __func__, msg , ##args); } while (0)
+#define NOTIFY_INFO(msg, args...)  do { if (notify_fn) notify_fn(2, __func__, msg , ##args); } while (0)
+#define NOTIFY_DEBUG(msg, args...) do { if (notify_fn) notify_fn(3, __func__, msg , ##args); } while (0)
+
+extern
+void (* notify_fn)(int level, const char *func_name, const char *msg, ...) __attribute__((format(printf, 3, 4)));
+
+void notify_fn_set(void (* notify_fn)(int level, const char *func_name, const char *fmt, ...));
+
+#endif /* NOTIFY_H */
diff --git a/server.c b/server.c
new file mode 100644 (file)
index 0000000..f421383
--- /dev/null
+++ b/server.c
@@ -0,0 +1,839 @@
+#include <pthread.h>
+#include <string.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <event2/bufferevent_ssl.h>
+#include <event2/bufferevent.h>
+#include <event2/buffer.h>
+#include <event2/listener.h>
+#include <event2/dns.h>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include "common.h"
+#include "command.h"
+#include "db.h"
+#include "notify.h"
+#include "lua_interface.h"
+#include "server.h"
+#include "workqueue.h"
+#include "version.h"
+
+
+/*  Wrapper struct for passing multiple values through a voidstar.
+ *  Used when calling server_add_listener_addrinfo_().
+ */
+struct server_listener_convenience_ {
+       struct server *s;
+       evconnlistener_cb cb;
+       char *type;
+};
+
+
+/**
+ * Load all lua libs except for io and os modules,
+ * TODO: this should be more fine-grained and properly sandboxed.  Also, add server lib (needs to be created).
+ */
+static void
+server_lua_openlibs_(lua_State *L)
+{
+       const luaL_Reg lualibs[] = {
+               { "", luaopen_base },
+               { LUA_LOADLIBNAME, luaopen_package },
+               { LUA_TABLIBNAME, luaopen_table },
+               { LUA_STRLIBNAME, luaopen_string },
+               { LUA_MATHLIBNAME, luaopen_math },
+               // { LUA_DBLIBNAME, luaopen_debug },
+               { NULL, NULL }
+       };
+       const luaL_Reg *lib;
+       for (lib = lualibs ; lib->func; lib++) {
+               lua_pushcfunction(L, lib->func);
+               lua_pushstring(L, lib->name);
+               lua_call(L, 1, 0);
+       }
+
+       lemu_connection_luainit(L);
+}
+
+
+/**
+ * Allocate and initialize a new per-thread context.
+ */
+static struct server_worker_context *
+server_worker_context_create_(struct server *server)
+{
+       struct server_worker_context *ctx;
+
+       ctx = calloc(1, sizeof *ctx);
+       if (ctx == NULL) {
+               NOTIFY_ERROR("%s:%s", "calloc", strerror(errno));
+               return NULL;
+       }
+       ctx->server = server;
+       ctx->L = luaL_newstate();
+       if (!ctx->L) {
+               NOTIFY_ERROR("%s:%s", "luaL_newstate", "failed");
+               free(ctx);
+               return NULL;
+       }
+       server_lua_openlibs_(ctx->L);
+
+       return ctx;     
+}
+
+/**
+ * Free a thread context.
+ * workqueue:worker_ctx_free_fn
+ */
+static void
+server_worker_context_destroy_(void *data)
+{
+       struct server_worker_context *ctx = data;
+       lua_close(ctx->L);
+       free(ctx);
+}
+
+/**
+ * Adds a worker thread to the pool.
+ */
+static int
+server_init_add_thread_(struct server *s)
+{
+       struct server_worker_context *ctx;
+       ssize_t worker_id;
+
+       ctx = server_worker_context_create_(s);
+       if (ctx == NULL) {
+               NOTIFY_ERROR("%s:%s", "server_worker_context_create_", "failed");
+               return -1;
+       }
+
+       worker_id = workqueue_worker_add(&s->workqueue, ctx, 0);
+       if (worker_id < 0) {
+               NOTIFY_ERROR("%s:%s", "workqueue_worker_add", "failed");
+               server_worker_context_destroy_(ctx);
+               return -1;
+       }
+
+       NOTIFY_DEBUG("added worker thread %zd", worker_id);
+
+       return 0;
+}
+
+/**
+ * Handle accept errors.
+ */
+static void
+server_accept_error_cb_(struct evconnlistener *listener UNUSED, void *ctx UNUSED)
+{
+       int err = EVUTIL_SOCKET_ERROR();
+       NOTIFY_ERROR("%s (%d)", evutil_socket_error_to_string(err), err);
+}
+
+
+/**
+ * Move incoming data to connection queue.
+ * TBD: is it worth work-queueing this data shuffling, or just let main thread handle it?
+ */
+static void
+server_read_event_(struct bufferevent *bev, void *ctx)
+{
+       struct connection *c = ctx;
+       struct evbuffer *input = bufferevent_get_input(bev);
+       size_t line_len;
+       unsigned char *line;
+
+       while ( (line = (unsigned char *)evbuffer_readln(input, &line_len, EVBUFFER_EOL_CRLF)) ) {
+               c->total_read += line_len; /* this drops the newlines from the total_read tally */
+
+               struct command *command = command_new(line, line_len, 0);
+               if (command == NULL) {
+                       NOTIFY_ERROR("%s:%s", "command_new", "failed");
+                       free(line);
+                       return;
+               }
+
+               if (connection_command_enqueue(c, command)) {
+                       NOTIFY_ERROR("%s:%s", "connection_command_enqueue", "failed");
+                       command_free(command);
+                       return;
+               }
+       }
+}
+
+
+/**
+ * Handle write events.
+ */
+static void
+server_write_event_(struct bufferevent *bev UNUSED, void *ctx)
+{
+       struct connection *c = ctx;
+
+       if (c->state == CONNECTION_STATE_WANT_CLOSE) {
+               c->state = CONNECTION_STATE_CLOSED;
+
+               struct command *command = command_new(NULL, 0, COMMAND_FLAG_EVENT_DISCONNECT);
+               if (command == NULL) {
+                       NOTIFY_ERROR("%s:%s", "command_new", "failed");
+                       return;
+               }
+               if (connection_command_enqueue(c, command)) {
+                       NOTIFY_ERROR("%s:%s", "connection_command_enqueue", "failed");
+                       command_free(command);
+               }
+               connection_free(c);
+       }
+}
+
+
+/**
+ * Handle some events.
+ */
+static void
+server_event_(struct bufferevent *bev UNUSED, short events, void *ctx)
+{
+       struct connection *c = ctx;
+       int finished = 0;
+
+       if (events & BEV_EVENT_READING) {
+               NOTIFY_ERROR("reading error from '%s'", c->name);
+               finished = 1;
+       }
+
+       if (events & BEV_EVENT_WRITING) {
+               NOTIFY_ERROR("writing error from '%s'", c->name);
+               finished = 1;
+       }
+
+       if (events & BEV_EVENT_EOF) {
+               NOTIFY_DEBUG("eof from '%s'", c->name);
+               finished = 1;
+       }
+
+       if (events & BEV_EVENT_ERROR) {
+               NOTIFY_ERROR("unrecoverable error from '%s': %s",
+                            c->name,
+                            evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()) );
+               finished = 1;
+       }
+
+       if (events & BEV_EVENT_TIMEOUT) {
+               NOTIFY_ERROR("timeout from '%s'", c->name);
+               finished = 1;
+       }
+
+       if (events & BEV_EVENT_CONNECTED) {
+               NOTIFY_DEBUG("connected from '%s'", c->name);
+       }
+
+       if (events & ~(BEV_EVENT_READING|BEV_EVENT_WRITING|BEV_EVENT_EOF|BEV_EVENT_ERROR|BEV_EVENT_TIMEOUT|BEV_EVENT_CONNECTED)) {
+               NOTIFY_ERROR("unrecognized event from '%s': %hd", c->name, events);
+       }
+
+       if (finished) {
+               struct command *command = command_new(NULL, 0, COMMAND_FLAG_EVENT_DISCONNECT);
+               if (command == NULL) {
+                       NOTIFY_ERROR("%s:%s", "command_new", "failed");
+               } else {
+                       if (connection_command_enqueue(c, command)) {
+                               NOTIFY_ERROR("%s:%s", "connection_comand_enqueue", "failed");
+                               command_free(command);
+                       }
+               }
+               connection_free(c);
+       }
+}
+
+
+/**
+ * Everything needed to accept a new connection, regardless of transport type.
+ */
+static void
+server_accept_conn_common_(struct server *server, struct bufferevent *bev, struct sockaddr *sock, int socklen, connection_flags_t flags)
+{
+       struct connection *c;
+
+       c = calloc(1, sizeof *c);
+       if (!c) {
+               NOTIFY_ERROR("%s:%s", "calloc", strerror(errno));
+               bufferevent_free(bev);
+               return;
+       }
+
+       if (connection_init(server, c, bev, sock, socklen, flags)) {
+               NOTIFY_ERROR("%s:%s", "connection_init", "failed");
+               bufferevent_free(bev);
+               free(c);
+               return;
+       }
+       c->state = CONNECTION_STATE_CONNECTED;
+
+       /* a bufferevent_add_cb interface would be better, but there isn't one yet */
+       bufferevent_setcb(c->bev, server_read_event_, server_write_event_, server_event_, c);
+       bufferevent_enable(c->bev, EV_READ|EV_WRITE);
+
+       connections_append(c);
+
+       struct command *command = command_new(NULL, 0, COMMAND_FLAG_EVENT_CONNECT);
+       if (command == NULL) {
+               NOTIFY_ERROR("%s:%s", "command_new", "failed");
+               return;
+       }
+       if (connection_command_enqueue(c, command)) {
+               NOTIFY_ERROR("%s:%s", "connection_command_enqueue", "failed");
+               command_free(command);
+       }
+}
+
+
+/**
+ * Accept a new plain-text connection.
+ */
+static void
+server_accept_conn_event_(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *sock, int socklen, void *ctx)
+{
+       struct server *server = ctx;
+       struct event_base *base = evconnlistener_get_base(listener);
+       struct bufferevent *bev;
+
+       bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE | BEV_OPT_DEFER_CALLBACKS);
+       if (!bev) {
+               NOTIFY_ERROR("%s:%s", "bufferevent_socket_new", "failed");
+               return;
+       }
+
+       server_accept_conn_common_(server, bev, sock, socklen, 0);
+}
+
+/**
+ * Accept a new ssl connection.
+ */
+static void
+server_accept_ssl_conn_event_(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *sock, int socklen, void *ctx)
+{
+       struct server *server = ctx;
+       struct event_base *base = evconnlistener_get_base(listener);
+       struct bufferevent *bev;
+       SSL *ssl;
+
+       ssl = SSL_new(server->ssl_ctx);
+
+       bev = bufferevent_openssl_socket_new(base, fd, ssl, BUFFEREVENT_SSL_ACCEPTING, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE | BEV_OPT_DEFER_CALLBACKS);
+       if (!bev) {
+               NOTIFY_ERROR("%s:%s", "bufferevent_openssl_socket_new", "failed");
+               SSL_free(ssl);
+               return;
+       }
+
+       server_accept_conn_common_(server, bev, sock, socklen, CONN_TYPE_SSL);
+}
+
+
+/**
+ * Add a new listener binding to server for the provided address.
+ */
+static int
+server_add_listener_addrinfo_(struct addrinfo *ai, void *data)
+{
+       struct server *s = ((struct server_listener_convenience_ *)data)->s;
+       evconnlistener_cb cb = ((struct server_listener_convenience_ *)data)->cb;
+       struct evconnlistener **l;
+       int retval = 0;
+       int r;
+
+       if ( (r = pthread_mutex_lock(&s->listeners_mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_lock", strerror(r));
+               return -1;
+       }
+
+       if (s->listeners_used == s->listeners_allocated) {
+               void *new_ptr;
+               size_t new_allocated = s->listeners_allocated + 8;
+
+               new_ptr = realloc(s->listeners, new_allocated * sizeof *(s->listeners));
+               if (!new_ptr) {
+                       NOTIFY_ERROR("realloc(%zu): %s", new_allocated * sizeof *(s->listeners), strerror(errno));
+                       retval = -1;
+                       goto done;
+               }
+               s->listeners = new_ptr;
+               s->listeners_allocated = new_allocated;
+
+               /* recalloc */
+               memset(&s->listeners[s->listeners_used], 0, (s->listeners_allocated - s->listeners_used - 1) * sizeof *s->listeners);
+       }
+
+#if DEBUG_SILLY
+       NOTIFY_DEBUG("new listener in slot %zu", s->listeners_used);
+#endif
+
+       s->listeners_used++;
+       l = &(s->listeners[s->listeners_used - 1]);
+       *l = evconnlistener_new_bind(s->base, cb, s, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, -1, ai->ai_addr, ai->ai_addrlen);
+       if (!*l) {
+               NOTIFY_ERROR("%s:%s", "evconnlistener_new_bind", strerror(errno));
+               *l = NULL;
+               s->listeners_used--;
+               retval = -1;
+               goto done;
+       }
+       evconnlistener_set_error_cb(*l, server_accept_error_cb_);
+
+done:
+       if ( (r = pthread_mutex_unlock(&s->listeners_mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r));
+               retval = -1;
+       }
+
+       return retval;
+}
+
+
+/**
+ * Allocates a new server structure, initializes all the structures which don't need
+ * any specific configuration.  Such further setup is handled by server_init().
+ */
+struct server *
+server_new(void)
+{
+       pthread_mutexattr_t attr;
+       struct server *s;
+       int r;
+
+       s = calloc(1, sizeof *s);
+       if (!s) {
+               NOTIFY_ERROR("calloc(%zu, %zu): %s", (size_t)1, sizeof *s, strerror(errno));
+               return NULL;
+       }
+
+       s->base = event_base_new();
+       if (!s->base) {
+               NOTIFY_ERROR("%s:%s", "event_base_new", "failed");
+               goto err_free_server;
+       }
+
+       s->evdns_base = evdns_base_new(s->base, 1);
+       if (!s->evdns_base) {
+               NOTIFY_ERROR("%s:%s", "evdns_base", "failed");
+               goto err_free_event_base;
+       }
+
+       if (workqueue_init(&s->workqueue, server_worker_context_destroy_, 0)) {
+               NOTIFY_ERROR("%s:%s", "workqueue_init", "failed");
+               goto err_free_evdns_base;
+       }
+
+       if ( (r = pthread_mutexattr_init(&attr)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutexattr_init", strerror(r));
+               goto err_fini_workqueue;
+       }
+
+       if ( (r = pthread_mutex_init(&s->listeners_mutex, &attr)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_init", strerror(r));
+               goto err_destroy_attr;
+       }
+
+       if (connections_init(&s->connections) < 0) {
+               NOTIFY_ERROR("%s:%s", "connections_init", "&s->connections failed");
+               goto err_destroy_mutex;
+       }
+
+       s->L = luaL_newstate();
+       if (!s->L) {
+               NOTIFY_ERROR("%s:%s", "luaL_newstate", "failed");
+               goto err_free_connections;
+       }
+
+       if ( (r = pthread_mutexattr_destroy(&attr)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutexattr_destroy", strerror(r));
+               goto err_close_lua;
+       }
+
+       return s;
+
+err_close_lua:
+       lua_close(s->L);
+err_free_connections:
+       connections_fini(&s->connections);
+err_destroy_mutex:
+       pthread_mutex_destroy(&s->listeners_mutex);
+err_destroy_attr:
+       pthread_mutexattr_destroy(&attr);
+err_fini_workqueue:
+       workqueue_fini(&s->workqueue, true);
+err_free_evdns_base:
+       evdns_base_free(s->evdns_base, 0);
+err_free_event_base:
+       event_base_free(s->base);
+err_free_server:
+       free(s);
+       return NULL;
+}
+
+/**
+ * Looking at top of lua stack as string, parse as address and port,
+ * then add as listener to server.
+ */
+static void
+server_init_listener_directive_a_string_(lua_State *L, struct server_listener_convenience_ *c_data)
+{
+       char *l_str;
+
+       l_str = (char *)lua_tostring(L, -1);
+       if (string_to_addrinfo_call(l_str, 0, server_add_listener_addrinfo_, c_data)) {
+               NOTIFY_ERROR("failed to add '%s' value '%s'", c_data->type, l_str ? l_str : "[unknown]");
+       }
+       NOTIFY_INFO("listening on %s (%s)", l_str, c_data->type);
+}
+
+/**
+ * Using configuration key provided in c_data->type, create all listeners.
+ */
+static void
+server_init_listener_directive_(lua_State *L, struct server_listener_convenience_ *c_data)
+{
+       lua_getglobal(L, "server_config");
+       lua_getfield(L, -1, c_data->type);
+       if (lua_istable(L, -1)) {
+               lua_pushnil(L);
+               while (lua_next(L, -2) != 0) {
+                       if (lua_isstring(L, -1)) {
+                               server_init_listener_directive_a_string_(L, c_data);
+                       } else {
+                               char *l_str = (char *)lua_tostring(L, -2); /* table key name */
+                               NOTIFY_ERROR("could not fathom value for '%s' at index '%s'", c_data->type, l_str ? l_str : "[unknown]");
+                       }
+                       lua_pop(L, 1);
+               }
+       } else if (lua_isstring(L, -1)) {
+               server_init_listener_directive_a_string_(L, c_data);
+       } else {
+               NOTIFY_ERROR("could not fathom '%s' value", c_data->type);
+       }
+       lua_pop(L, 2);
+}
+
+/**
+ * Return an allocated copy of value of global lua variable.
+ */
+static int
+get_lua_config_string_(lua_State *L, const char *key, char **value, size_t *value_len)
+{
+       int retval = 0;
+       const char *l_str;
+       size_t l_str_len;
+       assert(key);
+       assert(value);
+       *value = NULL;
+
+       lua_getglobal(L, "server_config");
+       lua_getfield(L, -1, key);
+
+       if (!lua_isstring(L, -1)) {
+               NOTIFY_ERROR("'%s' needs to be %s, but is %s", key, "string", lua_typename(L, lua_type(L, -1)));
+               retval = -1;
+               goto done;
+       }
+       l_str = lua_tolstring(L, -1, &l_str_len);
+       if (!l_str) {
+               NOTIFY_ERROR("'%s' value invalid", key);
+               retval = -1;
+               goto done;
+       }
+       if (value_len) {
+               *value_len = l_str_len;
+       }
+       l_str_len += 1; // \0
+       *value = malloc(l_str_len);
+       if (!*value) {
+               NOTIFY_ERROR("%s:%s", "malloc", strerror(errno));
+               retval = -1;
+               goto done;
+       }
+       memcpy(*value, l_str, l_str_len);
+done:
+       lua_pop(L, 1);
+       return retval;
+}
+
+static int
+get_lua_config_integer_(lua_State *L, const char *key, int *value)
+{
+       int retval = 0;
+       assert(key);
+       assert(value);
+
+       lua_getglobal(L, "server_config");
+       lua_getfield(L, -1, key);
+
+       if (!lua_isinteger(L, -1)) {
+               NOTIFY_ERROR("'%s' needs to be %s, but is %s", key, "integer", lua_typename(L, lua_type(L, -1)));
+               retval = -1;
+               goto done;
+       }
+       *value = (int)lua_tointeger(L, -1);
+done:
+       lua_pop(L, 1);
+       return retval;
+}
+
+/**
+ * Load or reload the ssl context, with cert and settings.
+ */
+int
+server_refresh_ssl_context(lua_State *L, struct server *s)
+{
+       SSL_CTX *new_ssl_ctx, *old_ssl_ctx;
+       char *l_str;
+
+       new_ssl_ctx = SSL_CTX_new(SSLv23_server_method());
+       if (!new_ssl_ctx) {
+               NOTIFY_ERROR("%s:%s", "SSL_CTX_new", ERR_error_string(ERR_get_error(), NULL));
+               return -1;
+       }
+
+       // load cert
+       if (get_lua_config_string_(L, "ssl_keyfile", &l_str, NULL)) {
+               return -1;
+       }
+       if (SSL_CTX_use_PrivateKey_file(new_ssl_ctx, l_str, SSL_FILETYPE_PEM) < 1) {
+               NOTIFY_ERROR("%s file '%s': %s", "key", l_str, ERR_error_string(ERR_get_error(), NULL));
+               free(l_str);
+               return -1;
+       }
+       free(l_str);
+
+       if (get_lua_config_string_(L, "ssl_certfile", &l_str, NULL)) {
+               return -1;
+       }
+       if (SSL_CTX_use_certificate_file(new_ssl_ctx, l_str, SSL_FILETYPE_PEM) < 1) {
+               NOTIFY_ERROR("%s file '%s': %s", "certificate", l_str, ERR_error_string(ERR_get_error(), NULL));
+               free(l_str);
+               return -1;
+       }
+       free(l_str);
+
+       old_ssl_ctx = s->ssl_ctx;
+       s->ssl_ctx = new_ssl_ctx;
+
+       if (old_ssl_ctx) {
+               SSL_CTX_free(old_ssl_ctx);
+       }
+
+       return 0;
+}
+
+
+/**
+ * Example heartbeat worker job.
+ * N.B. server as data instead of connection.
+ */
+static void
+server_heartbeat_worker_(void *data, void *context UNUSED, size_t id UNUSED)
+{
+       struct server *s = data;
+       struct connection **clist, **clist_iter;
+       char *message;
+       int r;
+
+       clist = connections_all_as_array(&(s->connections), NULL);
+       if (!clist) {
+               return;
+       }
+
+       message = strdup("[heartbeat]\n");
+       if (!message) {
+               NOTIFY_ERROR("%s:%s", "strdup", strerror(errno));
+               return;
+       }
+
+       r = connection_multi_send(clist, message, strlen(message));
+       if (r < 0) {
+               NOTIFY_ERROR("could not broadcast heartbeat");
+       } else {
+               NOTIFY_DEBUG("broadcast heartbeat to %d connection%s", r, (r > 1) ? "s" : "");
+       }
+
+       clist_iter = clist;
+       while (*clist_iter) {
+               connection_free(*clist_iter);
+               clist_iter++;
+       }
+       free(clist);
+}
+
+
+/**
+ * Periodic heartbeat handler.
+ */
+static void
+server_heartbeat_event_(evutil_socket_t fd UNUSED, short what UNUSED, void *arg)
+{
+       struct server *server = arg;
+
+       if (workqueue_add(&server->workqueue, server_heartbeat_worker_, server)) {
+               NOTIFY_ERROR("%s:%s", "workqueue_add", "failed");
+       }
+}
+
+
+/**
+ * Configure the server based on the provided lua file.
+ */
+int
+server_init(struct server *s, char *conf_file)
+{
+       struct server_listener_convenience_ c_data;
+       int l_int;
+
+       /* Set up initial server state, in order to process configuration file. */
+       server_lua_openlibs_(s->L);
+
+       /* Create config table, which will be used as global env for loading config file. */
+       lua_newtable(s->L);
+       lua_setglobal(s->L, "server_config");
+
+
+       if (luaL_loadfile(s->L, conf_file)) {
+               NOTIFY_ERROR("could not %s configuration file '%s': %s",
+                            "load",
+                            conf_file,
+                            lua_tostring(s->L, -1));
+               return -1;
+       }
+       /* server_config table becomes global env for executing config file */
+       lua_getglobal(s->L, "server_config");
+       lua_setupvalue(s->L, -2, 1);
+
+       if (lua_pcall(s->L, 0, 0, 0) != LUA_OK) {
+               NOTIFY_ERROR("could not %s configuration file '%s': %s",
+                            "parse",
+                            conf_file,
+                            lua_tostring(s->L, -1));
+               return -1;
+       }
+
+       // FIXME: let db fetch its own options
+       struct database_options db_options =  { 0 };
+       if (get_lua_config_string_(s->L, "db_file", &db_options.db_file, NULL)) {
+               return -1;
+       }
+
+       if (db_init(&s->db, &db_options)) {
+               NOTIFY_ERROR("%s:%s", "db_init", "failed");
+               free(db_options.db_file);
+               return -1;
+       }
+       free(db_options.db_file);
+
+       if (server_refresh_ssl_context(s->L, s)) {
+               return -1;
+       }
+
+       if (get_lua_config_integer_(s->L, "processing_threads", &l_int)) {
+               return -1;
+       }
+       /* FIXME: define reasonable limits */
+       if (l_int < 1 || l_int > 64) {
+               NOTIFY_ERROR("'%s' is not a reasonable value", "processing_threads");
+               return -1;
+       }
+       // FIXME: while l_int != workqueue_workers()
+       while (l_int) {
+               if (server_init_add_thread_(s)) {
+                       NOTIFY_ERROR("could not start processing thread");
+                       return -1;
+               }
+               l_int--;
+       }
+
+       c_data.s = s;
+       c_data.cb = server_accept_conn_event_;
+       c_data.type = "listen";
+       server_init_listener_directive_(s->L, &c_data);
+
+       c_data.cb = server_accept_ssl_conn_event_;
+       c_data.type = "listen_ssl";
+       server_init_listener_directive_(s->L, &c_data);
+
+       do {
+               struct timeval _seconds = {30, 0};
+               struct event *ev;
+               /* FIXME: instead of persist, reset from event */
+               ev = event_new(s->base, -1, EV_TIMEOUT | EV_PERSIST, server_heartbeat_event_, s);
+               event_add(ev, &_seconds);
+       } while (0);
+
+       NOTIFY_INFO("server starting...\n"
+                    "\t%s %s (%s)\n"
+                    "\tlibevent %s [%s]\n"
+                    "\t%s %s\n"
+                    "\t%s\n"
+                    "\t%s",
+                    "lemu", VERSION_STR, VERSION_DATE,
+                    event_get_version(), event_base_get_method(s->base),
+                    db_engine(), db_version(),
+                    OpenSSL_version(OPENSSL_VERSION),
+                    LUA_RELEASE);
+
+       event_base_dispatch(s->base);
+
+       return 0;
+}
+
+void
+server_free(struct server *s)
+{
+       int r;
+
+       while (s->listeners_used > 0) {
+               s->listeners_used--;
+               if (s->listeners[s->listeners_used]) {
+                       evconnlistener_free(s->listeners[s->listeners_used]);
+                       s->listeners[s->listeners_used] = NULL;
+               }
+               NOTIFY_DEBUG("freed listener %zu", s->listeners_used);
+       }
+       free(s->listeners);
+       s->listeners = NULL;
+
+       workqueue_fini(&s->workqueue, false);
+
+       if (s->ssl_ctx) {
+               SSL_CTX_free(s->ssl_ctx);
+               s->ssl_ctx = NULL;
+       }
+
+       if (s->evdns_base) {
+               evdns_base_free(s->evdns_base, 0);
+               s->evdns_base = NULL;
+       }
+
+       if (s->base) {
+               event_base_free(s->base);
+               s->base = NULL;
+       }
+
+       if ( (r = pthread_mutex_destroy(&s->connections.mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_destroy", strerror(r));
+       }
+
+       if ( (r = pthread_mutex_destroy(&s->listeners_mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_destroy", strerror(r));
+       }
+
+       lua_close(s->L);
+
+       db_fini(&s->db);
+
+       free(s);
+}
diff --git a/server.h b/server.h
new file mode 100644 (file)
index 0000000..e4b0705
--- /dev/null
+++ b/server.h
@@ -0,0 +1,55 @@
+#ifndef SERVER_H
+#define SERVER_H
+
+#include <event2/listener.h>
+#include <event2/util.h>
+
+#include <openssl/ssl.h>
+
+#include <lua.h>
+
+#include "common.h"
+#include "connections.h"
+#include "db.h"
+#include "workqueue.h"
+
+struct server {
+       struct event_base *base;
+       struct evdns_base *evdns_base;
+
+       pthread_mutex_t listeners_mutex;
+       struct evconnlistener **listeners;
+       size_t listeners_allocated;
+       size_t listeners_used;
+
+       SSL_CTX *ssl_ctx;
+
+       struct database db;
+
+       /* a collection of all connections */
+       struct connections connections;
+
+       /* worker thread wrangler */
+       struct workqueue workqueue;
+
+       /* server lua context, currently to load and parse config file */
+       lua_State *L;
+};
+
+/* each worker thread gets a reference to the server and its own lua state */
+struct server_worker_context {
+       struct server *server;
+       lua_State *L;
+};
+
+struct server * server_new(void);
+
+int server_init(struct server *s, char *conf_file);
+
+void server_free(struct server *s);
+
+void server_heartbeat_event(evutil_socket_t, short, void *);
+
+int server_refresh_ssl_context(lua_State *, struct server *);
+
+#endif /* SERVER_H */
\ No newline at end of file
diff --git a/version.h b/version.h
new file mode 100644 (file)
index 0000000..92d07ef
--- /dev/null
+++ b/version.h
@@ -0,0 +1,12 @@
+#ifndef VERSION_H
+#define VERSION_H
+
+#define VERSION_MAJOR 0
+#define VERSION_MINOR 0
+#define VERSION_EXTRA_STR "prototype"
+#define VERSION_STR "0.0-prototype"
+#define VERSION_DATE  "2021-02-23"
+#define VERSION_COMMIT "863cf651c2fcb4cc1735f8f7df1620025352f2b1"
+#define VERSION_BRANCH "master"
+
+#endif
diff --git a/version.sh b/version.sh
new file mode 100755 (executable)
index 0000000..c443233
--- /dev/null
@@ -0,0 +1,41 @@
+#!/bin/sh
+# generate new version.h
+
+set -e
+
+if [ $# -lt 2 ]
+       then
+       echo "usage: `basename $0` <major> <minor> [<extra>]"
+       exit 1
+fi
+
+maj=`printf "%u" $1`
+min=`printf "%u" $2`
+shift 2
+ext="$@"
+
+str="${maj}.${min}"
+if [ -n "${ext}" ]
+       then
+       str="${str}-${ext}"
+fi
+now=`TZ=UTC date "+%Y-%m-%d"`
+
+commit=`git rev-parse HEAD`
+branch=`git rev-parse --abbrev-ref HEAD`
+
+cat>"version.h"<<EOF
+#ifndef VERSION_H
+#define VERSION_H
+
+#define VERSION_MAJOR ${maj}
+#define VERSION_MINOR ${min}
+#define VERSION_EXTRA_STR "${ext}"
+#define VERSION_STR "${str}"
+#define VERSION_DATE  "${now}"
+#define VERSION_COMMIT "${commit}"
+#define VERSION_BRANCH "${branch}"
+
+#endif
+EOF
+
diff --git a/workqueue.c b/workqueue.c
new file mode 100644 (file)
index 0000000..ee24087
--- /dev/null
@@ -0,0 +1,633 @@
+/**
+ * A simple little concurrent work queue module.
+ */
+
+// #define _POSIX_C_SOURCE 200809L
+// #define _ISOC11_SOURCE
+
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <errno.h>
+#include <assert.h>
+
+#include "notify.h"
+#include "workqueue.h"
+
+#ifdef DEBUG_SILLY
+#      define D(fmt, ...) NOTIFY_DEBUG(fmt, ##__VA_ARGS__)
+#else
+#      define D(fmt, ...)
+#endif /* DEBUG_SILLY */
+
+#define FLAG_IS_TERMINATE(__flags__) ((__flags__) & WQF_TERMINATE)
+#define FLAG_IS_NOCOMPLETE(__flags__) ((__flags__) & WQF_NOCOMPLETE)
+#define FLAG_IS_TERMINATE_NOCOMPLETE(__flags__) ((__flags__) && (WQF_TERMINATE|WQF_NOCOMPLETE) == (WQF_TERMINATE|WQF_NOCOMPLETE))
+#define FLAG_IS_NORECYCLE(__flags__) ((__flags__) & WQF_NORECYCLE)
+
+#if defined(__GNUC__) || defined(__clang__)
+#      define ALWAYS_INLINE __attribute__((always_inline))
+#else
+#      define ALWAYS_INLINE
+#endif
+
+/**
+ * Shorthand for locking with error report.
+ */
+inline static int
+lock_(pthread_mutex_t *mutex)
+{
+       assert(mutex != NULL);
+       int r;
+       if ( (r = pthread_mutex_lock(mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_lock", strerror(r));
+               if (r == EOWNERDEAD) {
+                       /* sanity check call here */
+                       if ( (r = pthread_mutex_consistent(mutex)) ) {
+                               NOTIFY_ERROR("%s:%s", "pthread_mutex_consistent", strerror(r));
+                       }
+               }
+       }
+       return r;
+} ALWAYS_INLINE
+
+/**
+ * Shorthand for unlocking with error report.
+ */
+inline static int
+unlock_(pthread_mutex_t *mutex)
+{
+       assert(mutex != NULL);
+       int r;
+       if ( (r = pthread_mutex_unlock(mutex)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r));
+       }
+       return r;
+} ALWAYS_INLINE
+
+/**
+ * Release a work struct, either saving it to the free list or freeing the allocation.
+ */
+inline static void
+work_free_(struct workqueue *wq, struct work *work)
+{
+       assert(wq != NULL);
+       if (work) {
+               if (FLAG_IS_NORECYCLE(wq->flags)) {
+                       free(work);
+               } else {
+                       if (lock_(&(wq->free_mutex))) {
+                               D("freeing work %p", work);
+                               free(work);
+                               return;
+                       }
+
+                       STAILQ_INSERT_TAIL(&(wq->free_head), work, stailq);
+                       wq->n_free += 1;
+
+                       (void)unlock_(&(wq->free_mutex));
+                       D("recycling work %p, %zu available", work, wq->n_free);
+               }
+       }
+}
+
+/**
+ * Allocate a work struct, either new or from free list.
+ */
+inline static struct work *
+work_alloc_(struct workqueue *wq)
+{
+       assert(wq != NULL);
+       struct work *work = NULL;
+
+       if (!FLAG_IS_NORECYCLE(wq->flags)) {
+               if (lock_(&(wq->free_mutex))) {
+                       return NULL;
+               }
+
+               work = STAILQ_FIRST(&(wq->free_head));
+               if (work) {
+                       STAILQ_REMOVE_HEAD(&(wq->free_head), stailq);
+                       wq->n_free -= 1;
+               }
+
+               (void)unlock_(&(wq->free_mutex));
+       }
+
+       if (!work) {
+               work = calloc(1, sizeof *work);
+               if (!work) {
+                       NOTIFY_ERROR("%s:%s", "calloc", strerror(errno));
+               }
+               D("new work %p, %zu", work, wq->n_free);
+       } else {
+               D("recycled work %p, %zu available", work, wq->n_free);
+       }
+
+       return work;
+}
+
+/**
+ * Trim free list down to #retain items.
+ */
+ssize_t
+workqueue_release_work(struct workqueue *wq, size_t retain) {
+       assert(wq != NULL);
+       struct work *work;
+       ssize_t released = 0;
+
+       if (lock_(&(wq->free_mutex))) {
+               return -1;
+       }
+
+       while (wq->n_free > retain) {
+               work = STAILQ_FIRST(&(wq->free_head));
+               STAILQ_REMOVE_HEAD(&(wq->free_head), stailq);
+               wq->n_free -= 1;
+               released += 1;
+               free(work);
+               D("freeing work %p", work);
+       }
+
+       if (unlock_(&(wq->free_mutex))) {
+               return -1;
+       }
+
+       return released;
+}
+
+static void *
+worker_(void *data)
+{
+       assert(data != NULL);
+       struct worker * const worker = data;
+       struct workqueue * const wq = worker->workqueue;
+       void *retval = NULL;
+       int r;
+       struct work *work = NULL, *priority_work = NULL;
+
+       D("[%zu] started", worker->id);
+
+       while (!FLAG_IS_TERMINATE_NOCOMPLETE(wq->flags | worker->flags)) {
+               if (lock_(&(wq->work_mutex))) {
+                       retval = (void *)-1;
+                       goto done;
+               }
+
+               D("[%zu] looking for work", worker->id);
+               while ((priority_work = STAILQ_FIRST(&(worker->priority_work_head))) == NULL
+               &&     (work = STAILQ_FIRST(&(wq->work_head))) == NULL) {
+                       if (FLAG_IS_TERMINATE(wq->flags | worker->flags)) {
+                               D("[%zu] no more work", worker->id);
+                               if (unlock_(&(wq->work_mutex))) {
+                                       retval = (void *)-1;
+                               }
+                               goto done;
+                       }
+
+                       if ( (r = pthread_cond_wait(&(wq->work_cond), &(wq->work_mutex))) ) {
+                               NOTIFY_ERROR("%s:%s", "pthread_cond_wait", strerror(r));
+                               retval = (void *)-1;
+                       }
+                       D("[%zu] woke (flags %d|%d)", worker->id, worker->flags, wq->flags);
+                       if (r
+                       ||  FLAG_IS_TERMINATE_NOCOMPLETE(wq->flags | worker->flags)) {
+                               if (unlock_(&(wq->work_mutex))) {
+                                       retval = (void *)-1;
+                               }
+                               goto done;
+                       }
+               }
+
+               if (priority_work) {
+                       D("[%zu] got priority work %p", worker->id, work);
+                       STAILQ_REMOVE_HEAD(&(worker->priority_work_head), stailq);
+                       worker->n_priority_work -= 1;
+                       work = priority_work;
+               } else {
+                       D("[%zu] got work %p", worker->id, work);
+                       STAILQ_REMOVE_HEAD(&(wq->work_head), stailq);
+                       wq->n_work -= 1;
+               }
+
+               r = 0;
+               if (STAILQ_FIRST(&(wq->work_head))) {
+                       if ( (r = pthread_cond_signal(&(wq->work_cond))) ) {
+                               NOTIFY_ERROR("%s:%s", "pthread_cond_signal", strerror(r));
+                               retval = (void *)-1;
+                       }
+               }
+
+               if (unlock_(&(wq->work_mutex)) || r) {
+                       retval = (void *)-1;
+                       goto done;
+               }
+
+               work->fn(work->data, worker->ctx, worker->id);
+               work_free_(wq, work);
+               work = NULL;
+       }
+done:
+       D("[%zu] done (%p)", worker->id, retval);
+       pthread_exit(retval);
+}
+
+int
+workqueue_init(struct workqueue *wq, worker_ctx_free_fn_t *worker_ctx_free_fn, workqueue_flags_t flags)
+{
+       assert(wq != NULL);
+       int r;
+       pthread_mutexattr_t mutexattr;
+
+       D("initializing");
+
+       wq->flags = flags;
+       wq->worker_ctx_free_fn = worker_ctx_free_fn;
+       wq->n_workers = 0;
+       wq->workers_next_id = 0;
+       wq->n_free = 0;
+       wq->n_work = 0;
+       wq->n_work_highwater = 0;
+
+       STAILQ_INIT(&(wq->work_head));
+       STAILQ_INIT(&(wq->free_head));
+       LIST_INIT(&(wq->workers_head));
+
+       if ( (r = pthread_mutexattr_init(&mutexattr)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutexattr_init", strerror(r));
+               goto err_init;
+       }
+       if ( (r = pthread_mutexattr_setrobust(&mutexattr, 1)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutexattr_setrobust", strerror(r));
+               goto err_destroy_mutexattr;
+       }
+
+       if ( (r = pthread_mutex_init(&(wq->free_mutex), &mutexattr)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_init", strerror(r));
+               goto err_destroy_mutexattr;
+       }
+
+       if ( (r = pthread_mutex_init(&(wq->workers_mutex), &mutexattr)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_init", strerror(r));
+               goto err_destroy_freemutex;
+       }
+
+       if ( (r = pthread_mutex_init(&(wq->work_mutex), &mutexattr)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_init", strerror(r));
+               goto err_destroy_workersmutex;
+       }
+       if ( (r = pthread_cond_init(&(wq->work_cond), NULL)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_cond_init", strerror(r));
+               goto err_destroy_workmutex;
+       }
+
+       if ( (r = pthread_mutexattr_destroy(&mutexattr)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutexattr_destroy", strerror(r));
+               goto err_destroy_workcond;
+       }
+
+       return 0;
+
+err_destroy_workcond:
+       if ( (r = pthread_cond_destroy(&(wq->work_cond))) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_cond_destroy", strerror(r));
+       }
+err_destroy_workmutex:
+       if ( (r = pthread_mutex_destroy(&(wq->work_mutex))) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_destroy", strerror(r));
+       }
+err_destroy_workersmutex:
+       if ( (r = pthread_mutex_destroy(&(wq->workers_mutex))) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_destroy", strerror(r));
+       }
+err_destroy_freemutex:
+       if ( (r = pthread_mutex_destroy(&(wq->free_mutex))) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_destroy", strerror(r));
+       }
+err_destroy_mutexattr:
+       if ( (r = pthread_mutexattr_destroy(&mutexattr)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutexattr_destroy", strerror(r));
+       }
+err_init:
+       return -1;
+}
+
+void
+workqueue_fini(struct workqueue *wq, bool flush_unfinished)
+{
+       assert(wq != NULL);
+       struct worker *worker, *worker_tmp;
+       struct work *w1, *w2;
+       void *res;
+       int r;
+
+       D("destroying");
+
+       workqueue_flags_t new_flags = WQF_TERMINATE;
+       if (flush_unfinished == true) {
+               new_flags |= WQF_NOCOMPLETE;
+       }
+
+       wq->flags |= new_flags;
+
+       if (flush_unfinished == true) {
+               (void)lock_(&(wq->work_mutex));
+               w1 = STAILQ_FIRST(&(wq->work_head));
+               while (w1 != NULL) {
+                       D("flushing unfinished work %p", w1);
+                       w2 = STAILQ_NEXT(w1, stailq);
+                       free(w1);
+                       w1 = w2;
+               }
+               STAILQ_INIT(&(wq->work_head));
+               (void)unlock_(&(wq->work_mutex));
+       }
+
+       (void)lock_(&(wq->workers_mutex));
+
+       LIST_FOREACH(worker, &(wq->workers_head), list) {
+               worker->flags |= new_flags;
+       }
+
+       if ( (r = pthread_cond_broadcast(&(wq->work_cond))) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_cond_broadcast", strerror(r));
+       }
+
+       LIST_FOREACH_SAFE(worker, &(wq->workers_head), list, worker_tmp) {
+               LIST_REMOVE(worker, list);
+               wq->n_workers -= 1;
+
+               D("joining worker %zu", worker->id);
+               if ( (r = pthread_join(worker->thread, &res)) ) {
+                       NOTIFY_ERROR("%s:%s", "pthread_join", strerror(r));
+               }
+
+               w1 = STAILQ_FIRST(&(worker->priority_work_head));
+               while (w1 != NULL) {
+                       D("flushing unfinished priority work %p", w1);
+                       w2 = STAILQ_NEXT(w1, stailq);
+                       free(w1);
+                       w1 = w2;
+               }
+               STAILQ_INIT(&(worker->priority_work_head));
+
+               if (wq->worker_ctx_free_fn) {
+                       wq->worker_ctx_free_fn(worker->ctx);
+               }
+
+               free(worker);
+       }
+
+       (void)unlock_(&(wq->workers_mutex));
+
+       if ( (r = pthread_cond_destroy(&(wq->work_cond))) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_cond_destroy", strerror(r));
+       }
+       if ( (r = pthread_mutex_destroy(&(wq->work_mutex))) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_destroy", strerror(r));
+       }
+       if ( (r = pthread_mutex_destroy(&(wq->free_mutex))) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_mutex_destroy", strerror(r));
+       }
+
+       w1 = STAILQ_FIRST(&(wq->free_head));
+       while (w1 != NULL) {
+               D("freeing work %p", w1);
+               w2 = STAILQ_NEXT(w1, stailq);
+               free(w1);
+               w1 = w2;
+       }
+       STAILQ_INIT(&(wq->free_head));
+}
+
+ssize_t
+workqueue_worker_add(struct workqueue *wq, void *ctx, workqueue_flags_t flags)
+{
+       assert(wq != NULL);
+       struct worker *worker;
+       sigset_t set, oldset;
+       int r;
+
+       if (sigfillset(&set) < 0) {
+               NOTIFY_ERROR("%s:%s", "sigfillset", strerror(errno));
+               return -1;
+       }
+
+       worker = calloc(1, sizeof *worker);
+       if (worker == NULL) {
+               NOTIFY_ERROR("%s:%s", "calloc", strerror(errno));
+               return -1;
+       }
+
+       worker->workqueue = wq;
+       worker->ctx = ctx;
+       worker->flags = flags;
+
+       STAILQ_INIT(&(worker->priority_work_head));
+       worker->n_priority_work = 0;
+
+       if ( (r = pthread_sigmask(SIG_BLOCK, &set, &oldset)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_sigmask", strerror(r));
+               goto err_free_worker;
+       }
+
+       if ( (r = pthread_create(&(worker->thread), NULL, worker_, worker)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_create", strerror(r));
+               if ((r = pthread_sigmask(SIG_SETMASK, &oldset, NULL))) {
+                       NOTIFY_ERROR("%s:%s", "pthread_sigmask", strerror(r));
+               }
+               goto err_cancel;
+       }
+
+       if ( (r = pthread_sigmask(SIG_SETMASK, &oldset, NULL)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_sigmask", strerror(r));
+               goto err_cancel;
+       }
+
+       if (lock_(&(wq->workers_mutex))) {
+               goto err_cancel;
+       }
+
+       worker->id = wq->workers_next_id;
+       wq->workers_next_id += 1;
+
+       LIST_INSERT_HEAD(&(wq->workers_head), worker, list);
+
+       if (unlock_(&(wq->workers_mutex))) {
+               goto err_cancel;
+       }
+
+       return worker->id;
+
+err_cancel:
+       worker->flags |= WQF_TERMINATE|WQF_NOCOMPLETE;
+       if ( (r = pthread_cond_broadcast(&(wq->work_cond))) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_cond_broadcast", strerror(r));
+       }
+       void *res;
+       if ( (r = pthread_join(worker->thread, &res)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_join", strerror(r));
+       }
+
+err_free_worker:
+       free(worker);
+       return -1;
+}
+
+ssize_t
+workqueue_worker_remove(struct workqueue *wq)
+{
+       assert(wq != NULL);
+       struct worker *worker;
+       struct work *w1, *w2;
+       ssize_t retval = -1;
+       int r;
+
+       if (lock_(&(wq->workers_mutex))) {
+               return -1;
+       }
+
+       worker = LIST_FIRST(&(wq->workers_head));
+       if (worker) {
+               LIST_REMOVE(worker, list);
+               wq->n_workers -= 1;
+               retval = worker->id;
+       }
+
+       if (unlock_(&(wq->workers_mutex))) {
+               retval = -1;
+       }
+
+       if (worker == NULL) {
+               NOTIFY_ERROR("no workers to remove");
+               return -1;
+       }
+
+       worker->flags |= WQF_TERMINATE | WQF_NOCOMPLETE;
+
+       if ( (r = pthread_cond_broadcast(&(wq->work_cond))) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_cond_broadcast", strerror(r));
+               retval = -1;
+       }
+
+       void *res;
+       if ( (r = pthread_join(worker->thread, &res)) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_join", strerror(r));
+       }
+
+       w1 = STAILQ_FIRST(&(worker->priority_work_head));
+       while (w1 != NULL) {
+               D("freeing work %p", w1);
+               w2 = STAILQ_NEXT(w1, stailq);
+               free(w1);
+               w1 = w2;
+       }
+       STAILQ_INIT(&(wq->free_head));
+
+       if (wq->worker_ctx_free_fn) {
+               wq->worker_ctx_free_fn(worker->ctx);
+       }
+
+       free(worker);
+
+       return retval;
+}
+
+int
+workqueue_add(struct workqueue *wq, work_fn_t *fn, void *data)
+{
+       assert(wq != NULL);
+       assert(fn != NULL);
+       struct work *work;
+       int r;
+
+       if (FLAG_IS_TERMINATE(wq->flags)) {
+               NOTIFY_ERROR("not adding work to terminating queue");
+               return -1;
+       }
+
+       work = work_alloc_(wq);
+       if (work == NULL) {
+               return -1;
+       }
+       work->fn = fn;
+       work->data = data;
+
+       if (lock_(&(wq->work_mutex))) {
+               free(work);
+               return -1;
+       }
+
+       STAILQ_INSERT_TAIL(&(wq->work_head), work, stailq);
+       wq->n_work += 1;
+       if (wq->n_work > wq->n_work_highwater) {
+               wq->n_work_highwater = wq->n_work;
+       }
+
+       if ( (r = pthread_cond_signal(&(wq->work_cond))) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_cond_signal", strerror(r));
+       }
+
+       if (unlock_(&(wq->work_mutex))) {
+               return -1;
+       }
+
+       return 0;
+}
+
+int
+workqueue_add_all_workers(struct workqueue *wq, work_fn_t *fn, void *data)
+{
+       assert(wq != NULL);
+       assert(fn != NULL);
+       struct worker *worker;
+       struct work *work;
+       int retval = 0;
+       int r;
+
+       if (FLAG_IS_TERMINATE(wq->flags)) {
+               NOTIFY_ERROR("not adding work to terminating queue");
+               retval = -1;
+               goto done;
+       }
+
+       if (lock_(&(wq->workers_mutex))) {
+               retval = -1;
+               goto done;
+       }
+
+       if (lock_(&(wq->work_mutex))) {
+               retval = -1;
+               goto err_unlock_workers;
+       }
+
+       LIST_FOREACH(worker, &(wq->workers_head), list) {
+               work = work_alloc_(wq);
+               if (work == NULL) {
+                       retval = -1;
+                       goto err_unlock_work;
+               }
+               work->fn = fn;
+               work->data = data;
+
+               STAILQ_INSERT_TAIL(&(worker->priority_work_head), work, stailq);
+               worker->n_priority_work += 1;
+       }
+
+       if ( (r = pthread_cond_broadcast(&(wq->work_cond))) ) {
+               NOTIFY_ERROR("%s:%s", "pthread_cond_broadcast", strerror(r));
+       }
+
+err_unlock_work:
+       if (unlock_(&(wq->work_mutex))) {
+               retval = -1;
+               goto err_unlock_workers;
+       }
+
+err_unlock_workers:
+       if (unlock_(&(wq->workers_mutex))) {
+               return -1;
+       }
+done:
+       return retval;
+}
diff --git a/workqueue.h b/workqueue.h
new file mode 100644 (file)
index 0000000..0c4d40e
--- /dev/null
@@ -0,0 +1,108 @@
+#ifndef _WORKQUEUE_H_
+#define _WORKQUEUE_H_
+
+#include <pthread.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
+
+#include "bsd_queue.h"
+
+/**
+ * Function signature to execute with user data, thread context, and thread id.
+ */
+typedef void (work_fn_t)(void *, void *, size_t);
+
+/**
+ * Function signature to free a worker thread's ctx.
+ */
+typedef void (worker_ctx_free_fn_t)(void *);
+
+/**
+ * Function signature for receiving error strings.
+ */
+typedef int (printf_fn_t) (const char *format, ...);
+
+/**
+ * Flag type for controlling worker behavior.
+ * We only need three.
+ */
+typedef uint_fast8_t workqueue_flags_t;
+
+/**
+ * Internal unit of work.
+ */
+struct work {
+       STAILQ_ENTRY(work) stailq;
+       work_fn_t *fn;
+       void *data;
+};
+
+enum workqueue_flags {
+       WQF_TERMINATE = (1<<0), /* worker thread will exit when out of work */
+       WQF_NOCOMPLETE = (1<<1), /* worker thread will not finish all work before exiting */
+       WQF_NORECYCLE = (1<<2), /* do not reuse work allocations */
+};
+
+/**
+ * A worker thread, with related data.
+ */
+struct worker {
+       pthread_t thread;
+       struct workqueue *workqueue;
+       void *ctx;
+       LIST_ENTRY(worker) list;
+       size_t id;
+       volatile workqueue_flags_t flags;
+
+       /* Queue of worker-specific work to perform, before consuming general work queue. */
+       /* Guarded by workqueue->work_mutex */
+       STAILQ_HEAD(priority_work_head, work) priority_work_head;
+       size_t n_priority_work;
+       /* End of workqueue->work_mutex guard */
+};
+
+/**
+ * 
+ */
+struct workqueue {
+       /* List of active worker threads. */
+       pthread_mutex_t workers_mutex;
+       LIST_HEAD(workers_head, worker) workers_head;
+       size_t n_workers;
+       size_t workers_next_id;
+       /* End of workers_mutex guard */
+
+       worker_ctx_free_fn_t *worker_ctx_free_fn;
+
+       /* Queue of work units awaiting processing. */
+       pthread_mutex_t work_mutex;
+       pthread_cond_t work_cond;
+       STAILQ_HEAD(work_head, work) work_head;
+       size_t n_work;
+       size_t n_work_highwater;
+       /* End of work_mutex guard */
+
+       /* Queue of allocated unutilized work units. */
+       pthread_mutex_t free_mutex;
+       STAILQ_HEAD(free_head, work) free_head;
+       size_t n_free;
+       /* End of free_mutex guard */
+
+       volatile workqueue_flags_t flags;
+};
+
+/**
+ * Initialize a workqueue.
+ */
+int workqueue_init(struct workqueue *, worker_ctx_free_fn_t *, workqueue_flags_t);
+void workqueue_fini(struct workqueue *, bool);
+
+ssize_t workqueue_worker_add(struct workqueue *, void *, workqueue_flags_t);
+ssize_t workqueue_worker_remove(struct workqueue *);
+
+ssize_t workqueue_release_work(struct workqueue *, size_t);
+
+int workqueue_add(struct workqueue *, work_fn_t *, void *);
+
+#endif /* _WORKQUEUE_H_ */