Commit Diff


commit - /dev/null
commit + 014c66b699bcbd16589b535fc06203c4a9e5110c
blob - /dev/null
blob + 18c2b7b613f81c234cadfc659cc40c7e5804d9bf (mode 644)
--- /dev/null
+++ Makefile
@@ -0,0 +1,115 @@
+include config.mk
+
+# -- options --
+
+PREFIX =	/usr/local
+SBINDIR =	${PREFIX}/sbin
+MANDIR =	${PREFIX}/man
+WWWDIR =	/var/www/htdocs
+
+# -- build-related variables --
+
+PROG =		pkg_fcgi
+VERSION =	0.1
+DISTNAME =	${PROG}-${VERSION}
+
+SRCS =		pkg_fcgi.c fcgi.c log.c server.c xmalloc.c
+
+COBJS =		${COMPATS:.c=.o}
+OBJS =		${SRCS:.c=.o} ${COBJS}
+
+MAN =		${PROG}.conf.5 ${PROG}.8
+
+# -- public targets --
+
+all: ${PROG}
+.PHONY: all clean distclean install uninstall
+
+clean:
+	rm -f *.[do] compat/*.[do] tests/*.[do] ui.c ${PROG}
+	${MAKE} -C template clean
+
+distclean: clean
+	rm -f config.h config.h.old config.mk config.log config.log.old
+	# ${MAKE} -C template distclean
+
+install:
+	mkdir -p ${DESTDIR}${MANDIR}/man5
+	mkdir -p ${DESTDIR}${MANDIR}/man8
+	mkdir -p ${DESTDIR}${SBINDIR}
+	mkdir -p ${DESTDIR}${WWWDIR}
+	${INSTALL_MAN} galileo.conf.5 ${DESTDIR}${MANDIR}/man5/${PROG}.conf.5
+	${INSTALL_MAN} galileo.8 ${DESTDIR}${MANDIR}/man8/${PROG}.8
+	${INSTALL_PROGRAM} ${PROG} ${DESTDIR}${SBINDIR}
+	${INSTALL_DATA} galileo.css ${DESTDIR}${WWWDIR}
+
+uninstall:
+	rm ${DESTDIR}${MANDIR}/man5/${PROG}.conf.5
+	rm ${DESTDIR}${MANDIR}/man8/${PROG}.8
+	rm ${DESTDIR}${SBINDIR}/${PROG}
+	rm ${DESTDIR}${WWWDIR}/galileo.css
+
+# -- internal build targets --
+
+${PROG}: ${OBJS}
+	${CC} -o $@ ${OBJS} ${LIBS} ${LDFLAGS}
+
+#ui.c: ui.tmpl
+#	${MAKE} -C template
+#	./template/template -o $@ ui.tmpl
+
+.c.o:
+	${CC} ${CFLAGS} -c $< -o $@
+
+# -- maintainer targets --
+
+PRIVKEY =	set-PRIVKEY
+DISTFILES =	CHANGES \
+		Makefile \
+		README \
+		configure \
+		fcgi.c \
+		pkg_fcgi.8 \
+		pkg_fcgi.c \
+		pkg_fcgi.css \
+		pkg_fcgi.h \
+		log.c \
+		log.h \
+		server.c \
+		ui.c \
+		ui.tmpl \
+		xmalloc.c \
+		xmalloc.h \
+
+.PHONY: release dist
+
+release: ${DISTNAME}.sha256.sig
+dist: ${DISTNAME}.sha256
+
+${DISTNAME}.sha256.sig: ${DISTNAME}.sha256
+	signify -S -e -m ${DISTNAME}.sha256 -s ${PRIVKEY}
+
+${DISTNAME}.sha256: ${DISTNAME}.tar.gz
+	sha256 ${DISTNAME}.tar.gz > $@
+
+${DISTNAME}.tar.gz: ${DISTFILES}
+	mkdir -p .dist/${DISTNAME}/
+	${INSTALL} -m 0644 ${DISTFILES} .dist/${DISTNAME}
+	${MAKE} -C compat	DESTDIR=${PWD}/.dist/${DISTNAME}/compat dist
+	${MAKE} -C keys		DESTDIR=${PWD}/.dist/${DISTNAME}/keys dist
+	${MAKE} -C template	DESTDIR=${PWD}/.dist/${DISTNAME}/template dist
+	${MAKE} -C tests	DESTDIR=${PWD}/.dist/${DISTNAME}/tests dist
+	cd .dist/${DISTNAME} && chmod 755 configure template/configure
+	cd .dist && tar czf ../$@ ${DISTNAME}
+	rm -rf .dist/
+
+.PHONY: ${DISTNAME}.tar.gz
+
+# -- dependencies --
+
+-include fcgi.d
+-include log.d
+-include pgk_fcgi.d
+-include server.d
+-include ui.d
+-include xmalloc.d
blob - /dev/null
blob + 16f8ad634de95e716e525b0c0bb4fc6afd255f4a (mode 644)
--- /dev/null
+++ acme.guide
@@ -0,0 +1,4 @@
+# win
+
+fn m { make $* && doas ./pkg_fcgi -dv -j1 }
+fn g { gg -d all gemini://localhost/$"* }
blob - /dev/null
blob + 65aa7d9e4b3189c0b560fe906708dc0c142dac51 (mode 644)
--- /dev/null
+++ compat/Makefile
@@ -0,0 +1,35 @@
+DISTFILES =	Makefile \
+		bufferevent_read_pressure_cb.c \
+		err.c \
+		event.h \
+		event_asr_run.c \
+		freezero.c \
+		getdtablecount.c \
+		getdtablesize.c \
+		getprogname.c \
+		pledge.c \
+		reallocarray.c \
+		recallocarray.c \
+		setproctitle.c \
+		setresgid.c \
+		setresuid.c \
+		stdlib.h \
+		string.h \
+		strlcat.c \
+		strlcpy.c \
+		strtonum.c \
+		unistd.h \
+		unveil.c \
+		vasprintf.c
+
+all:
+	false
+
+dist: ${DISTFILES}
+	mkdir -p ${DESTDIR}/
+	${INSTALL} -m 0644 ${DISTFILES} ${DESTDIR}/
+	${MAKE} -C imsg DESTDIR=${DESTDIR}/imsg dist
+	${MAKE} -C sys DESTDIR=${DESTDIR}/sys dist
+
+.PHONY: all dist
+include ../config.mk
blob - /dev/null
blob + cb22b6f52cad3fa05f5724b548c6977192539497 (mode 644)
--- /dev/null
+++ compat/err.c
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void vwarn_impl(const char*, va_list);
+static void vwarnx_impl(const char*, va_list);
+
+static void
+vwarn_impl(const char *fmt, va_list ap)
+{
+	fprintf(stderr, "%s: ", getprogname());
+	vfprintf(stderr, fmt, ap);
+	fprintf(stderr, ": %s\n", strerror(errno));
+}
+
+static void
+vwarnx_impl(const char *fmt, va_list ap)
+{
+	fprintf(stderr, "%s: ", getprogname());
+	vfprintf(stderr, fmt, ap);
+	fprintf(stderr, "\n");
+}
+
+void
+err(int ret, const char *fmt, ...)
+{
+	va_list	ap;
+
+	va_start(ap, fmt);
+	vwarn_impl(fmt, ap);
+	va_end(ap);
+	exit(ret);
+}
+
+void
+errx(int ret, const char *fmt, ...)
+{
+	va_list	ap;
+
+	va_start(ap, fmt);
+	vwarnx_impl(fmt, ap);
+	va_end(ap);
+	exit(ret);
+}
+
+void
+warn(const char *fmt, ...)
+{
+	va_list	ap;
+
+	va_start(ap, fmt);
+	vwarn_impl(fmt, ap);
+	va_end(ap);
+}
+
+void
+warnx(const char *fmt, ...)
+{
+	va_list	ap;
+
+	va_start(ap, fmt);
+	vwarnx_impl(fmt, ap);
+	va_end(ap);
+}
blob - /dev/null
blob + c89fc77a8db4036318b9dde961b9a30e9212af89 (mode 644)
--- /dev/null
+++ compat/event.h
@@ -0,0 +1,13 @@
+#include_next "event.h"
+
+#include "../config.h"
+
+#if !HAVE_EVENT_ASR_RUN
+struct asr_query;
+struct asr_result;
+struct event_asr;
+
+struct event_asr	*event_asr_run(struct asr_query *,
+    void (*cb)(struct asr_result *, void *), void *);
+void			 event_asr_abort(struct event_asr *);
+#endif
blob - /dev/null
blob + 6366503ab43a6917d9958656c6c7f5c381f3ef80 (mode 644)
--- /dev/null
+++ compat/event_asr_run.c
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2000-2004 Niels Provos <provos@citi.umich.edu>
+ * 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. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR 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.
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+
+#include <netdb.h>
+#include <asr.h>
+#include <event.h>
+#include <stdlib.h>
+
+/*
+ * Libevent glue for ASR.
+ */
+struct event_asr {
+	struct event	 ev;
+	struct asr_query *async;
+	void		(*cb)(struct asr_result *, void *);
+	void		*arg;
+};
+
+static void
+event_asr_dispatch(int fd __attribute__((__unused__)),
+    short ev __attribute__((__unused__)), void *arg)
+{
+	struct event_asr	*eva = arg;
+	struct asr_result	 ar;
+	struct timeval		 tv;
+
+	event_del(&eva->ev);
+
+	if (asr_run(eva->async, &ar)) {
+		eva->cb(&ar, eva->arg);
+		free(eva);
+	} else {
+		event_set(&eva->ev, ar.ar_fd,
+		    ar.ar_cond == ASR_WANT_READ ? EV_READ : EV_WRITE,
+		    event_asr_dispatch, eva);
+		tv.tv_sec = ar.ar_timeout / 1000;
+		tv.tv_usec = (ar.ar_timeout % 1000) * 1000;
+		event_add(&eva->ev, &tv);
+	}
+}
+
+struct event_asr *
+event_asr_run(struct asr_query *async, void (*cb)(struct asr_result *, void *),
+    void *arg)
+{
+	struct event_asr *eva;
+	struct timeval tv;
+
+	eva = calloc(1, sizeof *eva);
+	if (eva == NULL)
+		return (NULL);
+	eva->async = async;
+	eva->cb = cb;
+	eva->arg = arg;
+	tv.tv_sec = 0;
+	tv.tv_usec = 0;
+	evtimer_set(&eva->ev, event_asr_dispatch, eva);
+	evtimer_add(&eva->ev, &tv);
+	return (eva);
+}
+
+void
+event_asr_abort(struct event_asr *eva)
+{
+	asr_abort(eva->async);
+	event_del(&eva->ev);
+	free(eva);
+}
blob - /dev/null
blob + 0f83c59fffbfacccfe8af6db827e93f0bb9997ed (mode 644)
--- /dev/null
+++ compat/freezero.c
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+void
+freezero(void *ptr, size_t len)
+{
+	if (ptr == NULL)
+		return;
+
+	memset(ptr, 0, len);
+	free(ptr);
+}
blob - /dev/null
blob + f6fe14a6f40035e080311b006af986bf1b9a3131 (mode 644)
--- /dev/null
+++ compat/getdtablecount.c
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * XXX: on linux it's possible to glob("/proc/$pid/fd/ *") to know the
+ * dtablecount.
+ */
+
+#include "../config.h"
+
+int	getdtablecount(void);
+
+int
+getdtablecount(void)
+{
+	return 0;
+}
blob - /dev/null
blob + c0a18609fdaa36944e1a72debf4fd70cf2a9e450 (mode 644)
--- /dev/null
+++ compat/getdtablesize.c
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <unistd.h>
+
+int	getdtablesize(void);
+
+int
+getdtablesize(void)
+{
+	return sysconf(_SC_OPEN_MAX);
+}
blob - /dev/null
blob + 4aff31e40afec0da1d5879697964a3032efab9c5 (mode 644)
--- /dev/null
+++ compat/getprogname.c
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "../config.h"
+
+const char	*getprogname(void);
+
+#if HAVE___PROGNAME
+
+const char *
+getprogname(void)
+{
+	extern const char *__progname;
+
+	return __progname;
+}
+
+#elif HAVE_GETEXECNAME
+
+const char *
+getprogname(void)
+{
+	return getexecname();
+}
+
+#else
+
+const char *
+getprogname(void)
+{
+	return "galileo";
+}
+
+#endif
blob - /dev/null
blob + e564f5e6f17add39aa7f62c26706c7f33020f747 (mode 644)
--- /dev/null
+++ compat/pledge.c
@@ -0,0 +1,7 @@
+int	pledge(const char *, const char *);
+
+int
+pledge(const char *promises, const char *execpromises)
+{
+	return 0;
+}
blob - /dev/null
blob + 43f0b69158ac2a290dec1f45075d7b6f6a18678b (mode 644)
--- /dev/null
+++ compat/reallocarray.c
@@ -0,0 +1,38 @@
+/*	$OpenBSD: reallocarray.c,v 1.3 2015/09/13 08:31:47 guenther Exp $	*/
+/*
+ * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+/*
+ * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
+ * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
+ */
+#define MUL_NO_OVERFLOW	((size_t)1 << (sizeof(size_t) * 4))
+
+void *
+reallocarray(void *optr, size_t nmemb, size_t size)
+{
+	if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+	    nmemb > 0 && SIZE_MAX / nmemb < size) {
+		errno = ENOMEM;
+		return NULL;
+	}
+	return realloc(optr, size * nmemb);
+}
blob - /dev/null
blob + 763552dc901ea6b8a38268ec0c436a8319de0182 (mode 644)
--- /dev/null
+++ compat/recallocarray.c
@@ -0,0 +1,88 @@
+/*	$OpenBSD: recallocarray.c,v 1.2 2021/03/18 11:16:58 claudio Exp $	*/
+/*
+ * Copyright (c) 2008, 2017 Otto Moerbeek <otto@drijf.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
+ * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
+ */
+#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4))
+
+/*
+ * Even though specified in POSIX, the PAGESIZE and PAGE_SIZE
+ * macros have very poor portability.  Since we only use this
+ * to avoid free() overhead for small shrinking, simply pick
+ * an arbitrary number.
+ */
+#define getpagesize()	(1UL << 12)
+
+void *
+recallocarray(void *ptr, size_t oldnmemb, size_t newnmemb, size_t size)
+{
+	size_t oldsize, newsize;
+	void *newptr;
+
+	if (ptr == NULL)
+		return calloc(newnmemb, size);
+
+	if ((newnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+	    newnmemb > 0 && SIZE_MAX / newnmemb < size) {
+		errno = ENOMEM;
+		return NULL;
+	}
+	newsize = newnmemb * size;
+
+	if ((oldnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+	    oldnmemb > 0 && SIZE_MAX / oldnmemb < size) {
+		errno = EINVAL;
+		return NULL;
+	}
+	oldsize = oldnmemb * size;
+
+	/*
+	 * Don't bother too much if we're shrinking just a bit,
+	 * we do not shrink for series of small steps, oh well.
+	 */
+	if (newsize <= oldsize) {
+		size_t d = oldsize - newsize;
+
+		if (d < oldsize / 2 && d < (size_t)getpagesize()) {
+			memset((char *)ptr + newsize, 0, d);
+			return ptr;
+		}
+	}
+
+	newptr = malloc(newsize);
+	if (newptr == NULL)
+		return NULL;
+
+	if (newsize > oldsize) {
+		memcpy(newptr, ptr, oldsize);
+		memset((char *)newptr + oldsize, 0, newsize - oldsize);
+	} else
+		memcpy(newptr, ptr, newsize);
+
+	explicit_bzero(ptr, oldsize);
+	free(ptr);
+
+	return newptr;
+}
blob - /dev/null
blob + 4504b69fb16b99333a1028491adda8c8bb0de5a5 (mode 644)
--- /dev/null
+++ compat/setproctitle.c
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2016 Nicholas Marriott <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "../config.h"
+
+void	setproctitle(const char *, ...);
+
+#if HAVE_PR_SET_NAME
+
+#include <sys/prctl.h>
+
+void
+setproctitle(const char *fmt, ...)
+{
+	char	title[16], name[16], *cp;
+	va_list	ap;
+	int	used;
+
+	va_start(ap, fmt);
+	vsnprintf(title, sizeof title, fmt, ap);
+	va_end(ap);
+
+	used = snprintf(name, sizeof name, "%s: %s", getprogname(), title);
+	if (used >= (int)sizeof name) {
+		cp = strrchr(name, ' ');
+		if (cp != NULL)
+			*cp = '\0';
+	}
+	prctl(PR_SET_NAME, name);
+}
+#else
+void
+setproctitle(const char *fmt, ...)
+{
+	(void)fmt;
+}
+#endif
blob - /dev/null
blob + ec625cd0abbff37a6be1700a1cd73e0f67bd2079 (mode 644)
--- /dev/null
+++ compat/setresgid.c
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2004, 2005 Darren Tucker (dtucker at zip com au).
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <unistd.h>
+
+int
+setresgid(gid_t rgid, gid_t egid, gid_t sgid)
+{
+	/* this is the only configuration tested */
+
+	if (rgid != egid || egid != sgid)
+		return -1;
+
+	if (setregid(rgid, egid) == -1)
+		return -1;
+
+	return 0;
+}
blob - /dev/null
blob + a033d99ab249e10501c2f99de973d2d3242757f8 (mode 644)
--- /dev/null
+++ compat/setresuid.c
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2004, 2005 Darren Tucker (dtucker at zip com au).
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+#include <errno.h>
+#include <unistd.h>
+
+int
+setresuid(uid_t ruid, uid_t euid, uid_t suid)
+{
+	uid_t ouid;
+	int ret = -1;
+
+	/* Allow only the tested configuration. */
+
+	if (ruid != euid || euid != suid) {
+		errno = ENOSYS;
+		return -1;
+	}
+	ouid = getuid();
+
+	if ((ret = setreuid(euid, euid)) == -1)
+		return -1;
+
+	/*
+	 * When real, effective and saved uids are the same and we have
+	 * changed uids, sanity check that we cannot restore the old uid.
+	 */
+
+	if (ruid == euid && euid == suid && ouid != ruid &&
+	    setuid(ouid) != -1 && seteuid(ouid) != -1) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	/*
+	 * Finally, check that the real and effective uids are what we
+	 * expect.
+	 */
+	if (getuid() != ruid || geteuid() != euid) {
+		errno = EACCES;
+		return -1;
+	}
+
+	return ret;
+}
blob - /dev/null
blob + a9bac69f267bb7ef4314d165ddbf70dedcdc5393 (mode 644)
--- /dev/null
+++ compat/stdlib.h
@@ -0,0 +1,31 @@
+#include_next "stdlib.h"
+
+#include "../config.h"
+
+#ifndef __dead
+# define __dead __attribute__((noreturn))
+#endif
+
+#if !HAVE_GETPROGNAME
+const char	*getprogname(void);
+#endif
+
+#if !HAVE_SETPROCTITLE
+const char	*setproctitle(const char *, ...);
+#endif
+
+#if !HAVE_FREEZERO
+void		 freezero(void *, size_t);
+#endif
+
+#if !HAVE_REALLOCARRAY
+void		*reallocarray(void *, size_t, size_t);
+#endif
+
+#if !HAVE_RECALLOCARRAY
+void		*recallocarray(void *, size_t, size_t, size_t);
+#endif
+
+#if !HAVE_STRTONUM
+long long	 strtonum(const char *, long long, long long, const char **);
+#endif
blob - /dev/null
blob + 94e0bbb402c943b022f5b444df9d90f0081a8d66 (mode 644)
--- /dev/null
+++ compat/string.h
@@ -0,0 +1,11 @@
+#include_next "string.h"
+
+#include "../config.h"
+
+#if !HAVE_STRLCPY
+size_t		strlcpy(char *, const char *, size_t);
+#endif
+
+#if !HAVE_STRLCAT
+size_t		strlcat(char *, const char *, size_t);
+#endif
blob - /dev/null
blob + 1c19062bf839da6e099ffca1dddcdb4070a44edf (mode 644)
--- /dev/null
+++ compat/strlcat.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 1998, 2015 Todd C. Miller <millert@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <string.h>
+
+/*
+ * Appends src to string dst of size dsize (unlike strncat, dsize is the
+ * full size of dst, not space left).  At most dsize-1 characters
+ * will be copied.  Always NUL terminates (unless dsize <= strlen(dst)).
+ * Returns strlen(src) + MIN(dsize, strlen(initial dst)).
+ * If retval >= dsize, truncation occurred.
+ */
+size_t
+strlcat(char *dst, const char *src, size_t dsize)
+{
+	const char *odst = dst;
+	const char *osrc = src;
+	size_t n = dsize;
+	size_t dlen;
+
+	/* Find the end of dst and adjust bytes left but don't go past end. */
+	while (n-- != 0 && *dst != '\0')
+		dst++;
+	dlen = dst - odst;
+	n = dsize - dlen;
+
+	if (n-- == 0)
+		return(dlen + strlen(src));
+	while (*src != '\0') {
+		if (n != 0) {
+			*dst++ = *src;
+			n--;
+		}
+		src++;
+	}
+	*dst = '\0';
+
+	return(dlen + (src - osrc));	/* count does not include NUL */
+}
blob - /dev/null
blob + 177ff7269492ec85c2196e31edc425107a958e4b (mode 644)
--- /dev/null
+++ compat/strlcpy.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 1998, 2015 Todd C. Miller <millert@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <string.h>
+
+/*
+ * Copy string src to buffer dst of size dsize.  At most dsize-1
+ * chars will be copied.  Always NUL terminates (unless dsize == 0).
+ * Returns strlen(src); if retval >= dsize, truncation occurred.
+ */
+size_t
+strlcpy(char *dst, const char *src, size_t dsize)
+{
+	const char *osrc = src;
+	size_t nleft = dsize;
+
+	/* Copy as many bytes as will fit. */
+	if (nleft != 0) {
+		while (--nleft != 0) {
+			if ((*dst++ = *src++) == '\0')
+				break;
+		}
+	}
+
+	/* Not enough room in dst, add NUL and traverse rest of src. */
+	if (nleft == 0) {
+		if (dsize != 0)
+			*dst = '\0';		/* NUL-terminate dst */
+		while (*src++)
+			;
+	}
+
+	return(src - osrc - 1);	/* count does not include NUL */
+}
blob - /dev/null
blob + e1807693138c2fc6b662ac9d8859fa0f2431c360 (mode 644)
--- /dev/null
+++ compat/strtonum.c
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2004 Ted Unangst and Todd Miller
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#define	INVALID		1
+#define	TOOSMALL	2
+#define	TOOLARGE	3
+
+long long
+strtonum(const char *numstr, long long minval, long long maxval,
+    const char **errstrp)
+{
+	long long ll = 0;
+	int error = 0;
+	char *ep;
+	struct errval {
+		const char *errstr;
+		int err;
+	} ev[4] = {
+		{ NULL,		0 },
+		{ "invalid",	EINVAL },
+		{ "too small",	ERANGE },
+		{ "too large",	ERANGE },
+	};
+
+	ev[0].err = errno;
+	errno = 0;
+	if (minval > maxval) {
+		error = INVALID;
+	} else {
+		ll = strtoll(numstr, &ep, 10);
+		if (numstr == ep || *ep != '\0')
+			error = INVALID;
+		else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval)
+			error = TOOSMALL;
+		else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval)
+			error = TOOLARGE;
+	}
+	if (errstrp != NULL)
+		*errstrp = ev[error].errstr;
+	errno = ev[error].err;
+	if (error)
+		ll = 0;
+
+	return (ll);
+}
blob - /dev/null
blob + 360ee3a33402c4945921f43c2fecb15dc54c305a (mode 644)
--- /dev/null
+++ compat/sys/Makefile
@@ -0,0 +1,13 @@
+DISTFILES =	Makefile \
+		queue.h \
+		tree.h
+
+all:
+	false
+
+dist: ${DISTFILES}
+	mkdir -p ${DESTDIR}/
+	${INSTALL} -m 0644 ${DISTFILES} ${DESTDIR}/
+
+.PHONY: all dist
+include ../../config.mk
blob - /dev/null
blob + bc1568be67482bf033c0da484f8bd9fd41426d03 (mode 644)
--- /dev/null
+++ compat/sys/queue.h
@@ -0,0 +1,631 @@
+/*	$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	_SYS_QUEUE_H_
+#define	_SYS_QUEUE_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	/* !_SYS_QUEUE_H_ */
blob - /dev/null
blob + fb56e32567bed50a2455b9b8fb87e809bba9acd0 (mode 644)
--- /dev/null
+++ compat/sys/tree.h
@@ -0,0 +1,1012 @@
+/*	$OpenBSD: tree.h,v 1.30 2020/10/10 18:03:41 otto Exp $	*/
+/*
+ * Copyright 2002 Niels Provos <provos@citi.umich.edu>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR 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.
+ */
+
+#ifndef	_SYS_TREE_H_
+#define	_SYS_TREE_H_
+
+#include <stddef.h>
+
+/*
+ * Local modifications:
+ *  - dropped __unused
+ *  - __inline -> inline
+ */
+
+/*
+ * This file defines data structures for different types of trees:
+ * splay trees and red-black trees.
+ *
+ * A splay tree is a self-organizing data structure.  Every operation
+ * on the tree causes a splay to happen.  The splay moves the requested
+ * node to the root of the tree and partly rebalances it.
+ *
+ * This has the benefit that request locality causes faster lookups as
+ * the requested nodes move to the top of the tree.  On the other hand,
+ * every lookup causes memory writes.
+ *
+ * The Balance Theorem bounds the total access time for m operations
+ * and n inserts on an initially empty tree as O((m + n)lg n).  The
+ * amortized cost for a sequence of m accesses to a splay tree is O(lg n);
+ *
+ * A red-black tree is a binary search tree with the node color as an
+ * extra attribute.  It fulfills a set of conditions:
+ *	- every search path from the root to a leaf consists of the
+ *	  same number of black nodes,
+ *	- each red node (except for the root) has a black parent,
+ *	- each leaf node is black.
+ *
+ * Every operation on a red-black tree is bounded as O(lg n).
+ * The maximum height of a red-black tree is 2lg (n+1).
+ */
+
+#define SPLAY_HEAD(name, type)						\
+struct name {								\
+	struct type *sph_root; /* root of the tree */			\
+}
+
+#define SPLAY_INITIALIZER(root)						\
+	{ NULL }
+
+#define SPLAY_INIT(root) do {						\
+	(root)->sph_root = NULL;					\
+} while (0)
+
+#define SPLAY_ENTRY(type)						\
+struct {								\
+	struct type *spe_left; /* left element */			\
+	struct type *spe_right; /* right element */			\
+}
+
+#define SPLAY_LEFT(elm, field)		(elm)->field.spe_left
+#define SPLAY_RIGHT(elm, field)		(elm)->field.spe_right
+#define SPLAY_ROOT(head)		(head)->sph_root
+#define SPLAY_EMPTY(head)		(SPLAY_ROOT(head) == NULL)
+
+/* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */
+#define SPLAY_ROTATE_RIGHT(head, tmp, field) do {			\
+	SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field);	\
+	SPLAY_RIGHT(tmp, field) = (head)->sph_root;			\
+	(head)->sph_root = tmp;						\
+} while (0)
+
+#define SPLAY_ROTATE_LEFT(head, tmp, field) do {			\
+	SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field);	\
+	SPLAY_LEFT(tmp, field) = (head)->sph_root;			\
+	(head)->sph_root = tmp;						\
+} while (0)
+
+#define SPLAY_LINKLEFT(head, tmp, field) do {				\
+	SPLAY_LEFT(tmp, field) = (head)->sph_root;			\
+	tmp = (head)->sph_root;						\
+	(head)->sph_root = SPLAY_LEFT((head)->sph_root, field);		\
+} while (0)
+
+#define SPLAY_LINKRIGHT(head, tmp, field) do {				\
+	SPLAY_RIGHT(tmp, field) = (head)->sph_root;			\
+	tmp = (head)->sph_root;						\
+	(head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);	\
+} while (0)
+
+#define SPLAY_ASSEMBLE(head, node, left, right, field) do {		\
+	SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field);	\
+	SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\
+	SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field);	\
+	SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field);	\
+} while (0)
+
+/* Generates prototypes and inline functions */
+
+#define SPLAY_PROTOTYPE(name, type, field, cmp)				\
+void name##_SPLAY(struct name *, struct type *);			\
+void name##_SPLAY_MINMAX(struct name *, int);				\
+struct type *name##_SPLAY_INSERT(struct name *, struct type *);		\
+struct type *name##_SPLAY_REMOVE(struct name *, struct type *);		\
+									\
+/* Finds the node with the same key as elm */				\
+static inline struct type *						\
+name##_SPLAY_FIND(struct name *head, struct type *elm)			\
+{									\
+	if (SPLAY_EMPTY(head))						\
+		return(NULL);						\
+	name##_SPLAY(head, elm);					\
+	if ((cmp)(elm, (head)->sph_root) == 0)				\
+		return (head->sph_root);				\
+	return (NULL);							\
+}									\
+									\
+static inline struct type *						\
+name##_SPLAY_NEXT(struct name *head, struct type *elm)			\
+{									\
+	name##_SPLAY(head, elm);					\
+	if (SPLAY_RIGHT(elm, field) != NULL) {				\
+		elm = SPLAY_RIGHT(elm, field);				\
+		while (SPLAY_LEFT(elm, field) != NULL) {		\
+			elm = SPLAY_LEFT(elm, field);			\
+		}							\
+	} else								\
+		elm = NULL;						\
+	return (elm);							\
+}									\
+									\
+static inline struct type *						\
+name##_SPLAY_MIN_MAX(struct name *head, int val)			\
+{									\
+	name##_SPLAY_MINMAX(head, val);					\
+        return (SPLAY_ROOT(head));					\
+}
+
+/* Main splay operation.
+ * Moves node close to the key of elm to top
+ */
+#define SPLAY_GENERATE(name, type, field, cmp)				\
+struct type *								\
+name##_SPLAY_INSERT(struct name *head, struct type *elm)		\
+{									\
+    if (SPLAY_EMPTY(head)) {						\
+	    SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL;	\
+    } else {								\
+	    int __comp;							\
+	    name##_SPLAY(head, elm);					\
+	    __comp = (cmp)(elm, (head)->sph_root);			\
+	    if(__comp < 0) {						\
+		    SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\
+		    SPLAY_RIGHT(elm, field) = (head)->sph_root;		\
+		    SPLAY_LEFT((head)->sph_root, field) = NULL;		\
+	    } else if (__comp > 0) {					\
+		    SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\
+		    SPLAY_LEFT(elm, field) = (head)->sph_root;		\
+		    SPLAY_RIGHT((head)->sph_root, field) = NULL;	\
+	    } else							\
+		    return ((head)->sph_root);				\
+    }									\
+    (head)->sph_root = (elm);						\
+    return (NULL);							\
+}									\
+									\
+struct type *								\
+name##_SPLAY_REMOVE(struct name *head, struct type *elm)		\
+{									\
+	struct type *__tmp;						\
+	if (SPLAY_EMPTY(head))						\
+		return (NULL);						\
+	name##_SPLAY(head, elm);					\
+	if ((cmp)(elm, (head)->sph_root) == 0) {			\
+		if (SPLAY_LEFT((head)->sph_root, field) == NULL) {	\
+			(head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\
+		} else {						\
+			__tmp = SPLAY_RIGHT((head)->sph_root, field);	\
+			(head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\
+			name##_SPLAY(head, elm);			\
+			SPLAY_RIGHT((head)->sph_root, field) = __tmp;	\
+		}							\
+		return (elm);						\
+	}								\
+	return (NULL);							\
+}									\
+									\
+void									\
+name##_SPLAY(struct name *head, struct type *elm)			\
+{									\
+	struct type __node, *__left, *__right, *__tmp;			\
+	int __comp;							\
+\
+	SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\
+	__left = __right = &__node;					\
+\
+	while ((__comp = (cmp)(elm, (head)->sph_root))) {		\
+		if (__comp < 0) {					\
+			__tmp = SPLAY_LEFT((head)->sph_root, field);	\
+			if (__tmp == NULL)				\
+				break;					\
+			if ((cmp)(elm, __tmp) < 0){			\
+				SPLAY_ROTATE_RIGHT(head, __tmp, field);	\
+				if (SPLAY_LEFT((head)->sph_root, field) == NULL)\
+					break;				\
+			}						\
+			SPLAY_LINKLEFT(head, __right, field);		\
+		} else if (__comp > 0) {				\
+			__tmp = SPLAY_RIGHT((head)->sph_root, field);	\
+			if (__tmp == NULL)				\
+				break;					\
+			if ((cmp)(elm, __tmp) > 0){			\
+				SPLAY_ROTATE_LEFT(head, __tmp, field);	\
+				if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\
+					break;				\
+			}						\
+			SPLAY_LINKRIGHT(head, __left, field);		\
+		}							\
+	}								\
+	SPLAY_ASSEMBLE(head, &__node, __left, __right, field);		\
+}									\
+									\
+/* Splay with either the minimum or the maximum element			\
+ * Used to find minimum or maximum element in tree.			\
+ */									\
+void name##_SPLAY_MINMAX(struct name *head, int __comp) \
+{									\
+	struct type __node, *__left, *__right, *__tmp;			\
+\
+	SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\
+	__left = __right = &__node;					\
+\
+	while (1) {							\
+		if (__comp < 0) {					\
+			__tmp = SPLAY_LEFT((head)->sph_root, field);	\
+			if (__tmp == NULL)				\
+				break;					\
+			if (__comp < 0){				\
+				SPLAY_ROTATE_RIGHT(head, __tmp, field);	\
+				if (SPLAY_LEFT((head)->sph_root, field) == NULL)\
+					break;				\
+			}						\
+			SPLAY_LINKLEFT(head, __right, field);		\
+		} else if (__comp > 0) {				\
+			__tmp = SPLAY_RIGHT((head)->sph_root, field);	\
+			if (__tmp == NULL)				\
+				break;					\
+			if (__comp > 0) {				\
+				SPLAY_ROTATE_LEFT(head, __tmp, field);	\
+				if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\
+					break;				\
+			}						\
+			SPLAY_LINKRIGHT(head, __left, field);		\
+		}							\
+	}								\
+	SPLAY_ASSEMBLE(head, &__node, __left, __right, field);		\
+}
+
+#define SPLAY_NEGINF	-1
+#define SPLAY_INF	1
+
+#define SPLAY_INSERT(name, x, y)	name##_SPLAY_INSERT(x, y)
+#define SPLAY_REMOVE(name, x, y)	name##_SPLAY_REMOVE(x, y)
+#define SPLAY_FIND(name, x, y)		name##_SPLAY_FIND(x, y)
+#define SPLAY_NEXT(name, x, y)		name##_SPLAY_NEXT(x, y)
+#define SPLAY_MIN(name, x)		(SPLAY_EMPTY(x) ? NULL	\
+					: name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF))
+#define SPLAY_MAX(name, x)		(SPLAY_EMPTY(x) ? NULL	\
+					: name##_SPLAY_MIN_MAX(x, SPLAY_INF))
+
+#define SPLAY_FOREACH(x, name, head)					\
+	for ((x) = SPLAY_MIN(name, head);				\
+	     (x) != NULL;						\
+	     (x) = SPLAY_NEXT(name, head, x))
+
+/* Macros that define a red-black tree */
+#define RB_HEAD(name, type)						\
+struct name {								\
+	struct type *rbh_root; /* root of the tree */			\
+}
+
+#define RB_INITIALIZER(root)						\
+	{ NULL }
+
+#define RB_INIT(root) do {						\
+	(root)->rbh_root = NULL;					\
+} while (0)
+
+#define RB_BLACK	0
+#define RB_RED		1
+#define RB_ENTRY(type)							\
+struct {								\
+	struct type *rbe_left;		/* left element */		\
+	struct type *rbe_right;		/* right element */		\
+	struct type *rbe_parent;	/* parent element */		\
+	int rbe_color;			/* node color */		\
+}
+
+#define RB_LEFT(elm, field)		(elm)->field.rbe_left
+#define RB_RIGHT(elm, field)		(elm)->field.rbe_right
+#define RB_PARENT(elm, field)		(elm)->field.rbe_parent
+#define RB_COLOR(elm, field)		(elm)->field.rbe_color
+#define RB_ROOT(head)			(head)->rbh_root
+#define RB_EMPTY(head)			(RB_ROOT(head) == NULL)
+
+#define RB_SET(elm, parent, field) do {					\
+	RB_PARENT(elm, field) = parent;					\
+	RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL;		\
+	RB_COLOR(elm, field) = RB_RED;					\
+} while (0)
+
+#define RB_SET_BLACKRED(black, red, field) do {				\
+	RB_COLOR(black, field) = RB_BLACK;				\
+	RB_COLOR(red, field) = RB_RED;					\
+} while (0)
+
+#ifndef RB_AUGMENT
+#define RB_AUGMENT(x)	do {} while (0)
+#endif
+
+#define RB_ROTATE_LEFT(head, elm, tmp, field) do {			\
+	(tmp) = RB_RIGHT(elm, field);					\
+	if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field))) {		\
+		RB_PARENT(RB_LEFT(tmp, field), field) = (elm);		\
+	}								\
+	RB_AUGMENT(elm);						\
+	if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) {		\
+		if ((elm) == RB_LEFT(RB_PARENT(elm, field), field))	\
+			RB_LEFT(RB_PARENT(elm, field), field) = (tmp);	\
+		else							\
+			RB_RIGHT(RB_PARENT(elm, field), field) = (tmp);	\
+	} else								\
+		(head)->rbh_root = (tmp);				\
+	RB_LEFT(tmp, field) = (elm);					\
+	RB_PARENT(elm, field) = (tmp);					\
+	RB_AUGMENT(tmp);						\
+	if ((RB_PARENT(tmp, field)))					\
+		RB_AUGMENT(RB_PARENT(tmp, field));			\
+} while (0)
+
+#define RB_ROTATE_RIGHT(head, elm, tmp, field) do {			\
+	(tmp) = RB_LEFT(elm, field);					\
+	if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field))) {		\
+		RB_PARENT(RB_RIGHT(tmp, field), field) = (elm);		\
+	}								\
+	RB_AUGMENT(elm);						\
+	if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) {		\
+		if ((elm) == RB_LEFT(RB_PARENT(elm, field), field))	\
+			RB_LEFT(RB_PARENT(elm, field), field) = (tmp);	\
+		else							\
+			RB_RIGHT(RB_PARENT(elm, field), field) = (tmp);	\
+	} else								\
+		(head)->rbh_root = (tmp);				\
+	RB_RIGHT(tmp, field) = (elm);					\
+	RB_PARENT(elm, field) = (tmp);					\
+	RB_AUGMENT(tmp);						\
+	if ((RB_PARENT(tmp, field)))					\
+		RB_AUGMENT(RB_PARENT(tmp, field));			\
+} while (0)
+
+/* Generates prototypes and inline functions */
+#define	RB_PROTOTYPE(name, type, field, cmp)				\
+	RB_PROTOTYPE_INTERNAL(name, type, field, cmp,)
+#define	RB_PROTOTYPE_STATIC(name, type, field, cmp)			\
+	RB_PROTOTYPE_INTERNAL(name, type, field, cmp, __attribute__((__unused__)) static)
+#define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr)		\
+attr void name##_RB_INSERT_COLOR(struct name *, struct type *);		\
+attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *);\
+attr struct type *name##_RB_REMOVE(struct name *, struct type *);	\
+attr struct type *name##_RB_INSERT(struct name *, struct type *);	\
+attr struct type *name##_RB_FIND(struct name *, struct type *);		\
+attr struct type *name##_RB_NFIND(struct name *, struct type *);	\
+attr struct type *name##_RB_NEXT(struct type *);			\
+attr struct type *name##_RB_PREV(struct type *);			\
+attr struct type *name##_RB_MINMAX(struct name *, int);			\
+									\
+
+/* Main rb operation.
+ * Moves node close to the key of elm to top
+ */
+#define	RB_GENERATE(name, type, field, cmp)				\
+	RB_GENERATE_INTERNAL(name, type, field, cmp,)
+#define	RB_GENERATE_STATIC(name, type, field, cmp)			\
+	RB_GENERATE_INTERNAL(name, type, field, cmp, __attribute__((__unused__)) static)
+#define RB_GENERATE_INTERNAL(name, type, field, cmp, attr)		\
+attr void								\
+name##_RB_INSERT_COLOR(struct name *head, struct type *elm)		\
+{									\
+	struct type *parent, *gparent, *tmp;				\
+	while ((parent = RB_PARENT(elm, field)) &&			\
+	    RB_COLOR(parent, field) == RB_RED) {			\
+		gparent = RB_PARENT(parent, field);			\
+		if (parent == RB_LEFT(gparent, field)) {		\
+			tmp = RB_RIGHT(gparent, field);			\
+			if (tmp && RB_COLOR(tmp, field) == RB_RED) {	\
+				RB_COLOR(tmp, field) = RB_BLACK;	\
+				RB_SET_BLACKRED(parent, gparent, field);\
+				elm = gparent;				\
+				continue;				\
+			}						\
+			if (RB_RIGHT(parent, field) == elm) {		\
+				RB_ROTATE_LEFT(head, parent, tmp, field);\
+				tmp = parent;				\
+				parent = elm;				\
+				elm = tmp;				\
+			}						\
+			RB_SET_BLACKRED(parent, gparent, field);	\
+			RB_ROTATE_RIGHT(head, gparent, tmp, field);	\
+		} else {						\
+			tmp = RB_LEFT(gparent, field);			\
+			if (tmp && RB_COLOR(tmp, field) == RB_RED) {	\
+				RB_COLOR(tmp, field) = RB_BLACK;	\
+				RB_SET_BLACKRED(parent, gparent, field);\
+				elm = gparent;				\
+				continue;				\
+			}						\
+			if (RB_LEFT(parent, field) == elm) {		\
+				RB_ROTATE_RIGHT(head, parent, tmp, field);\
+				tmp = parent;				\
+				parent = elm;				\
+				elm = tmp;				\
+			}						\
+			RB_SET_BLACKRED(parent, gparent, field);	\
+			RB_ROTATE_LEFT(head, gparent, tmp, field);	\
+		}							\
+	}								\
+	RB_COLOR(head->rbh_root, field) = RB_BLACK;			\
+}									\
+									\
+attr void								\
+name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \
+{									\
+	struct type *tmp;						\
+	while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) &&	\
+	    elm != RB_ROOT(head)) {					\
+		if (RB_LEFT(parent, field) == elm) {			\
+			tmp = RB_RIGHT(parent, field);			\
+			if (RB_COLOR(tmp, field) == RB_RED) {		\
+				RB_SET_BLACKRED(tmp, parent, field);	\
+				RB_ROTATE_LEFT(head, parent, tmp, field);\
+				tmp = RB_RIGHT(parent, field);		\
+			}						\
+			if ((RB_LEFT(tmp, field) == NULL ||		\
+			    RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\
+			    (RB_RIGHT(tmp, field) == NULL ||		\
+			    RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\
+				RB_COLOR(tmp, field) = RB_RED;		\
+				elm = parent;				\
+				parent = RB_PARENT(elm, field);		\
+			} else {					\
+				if (RB_RIGHT(tmp, field) == NULL ||	\
+				    RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\
+					struct type *oleft;		\
+					if ((oleft = RB_LEFT(tmp, field)))\
+						RB_COLOR(oleft, field) = RB_BLACK;\
+					RB_COLOR(tmp, field) = RB_RED;	\
+					RB_ROTATE_RIGHT(head, tmp, oleft, field);\
+					tmp = RB_RIGHT(parent, field);	\
+				}					\
+				RB_COLOR(tmp, field) = RB_COLOR(parent, field);\
+				RB_COLOR(parent, field) = RB_BLACK;	\
+				if (RB_RIGHT(tmp, field))		\
+					RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\
+				RB_ROTATE_LEFT(head, parent, tmp, field);\
+				elm = RB_ROOT(head);			\
+				break;					\
+			}						\
+		} else {						\
+			tmp = RB_LEFT(parent, field);			\
+			if (RB_COLOR(tmp, field) == RB_RED) {		\
+				RB_SET_BLACKRED(tmp, parent, field);	\
+				RB_ROTATE_RIGHT(head, parent, tmp, field);\
+				tmp = RB_LEFT(parent, field);		\
+			}						\
+			if ((RB_LEFT(tmp, field) == NULL ||		\
+			    RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\
+			    (RB_RIGHT(tmp, field) == NULL ||		\
+			    RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\
+				RB_COLOR(tmp, field) = RB_RED;		\
+				elm = parent;				\
+				parent = RB_PARENT(elm, field);		\
+			} else {					\
+				if (RB_LEFT(tmp, field) == NULL ||	\
+				    RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\
+					struct type *oright;		\
+					if ((oright = RB_RIGHT(tmp, field)))\
+						RB_COLOR(oright, field) = RB_BLACK;\
+					RB_COLOR(tmp, field) = RB_RED;	\
+					RB_ROTATE_LEFT(head, tmp, oright, field);\
+					tmp = RB_LEFT(parent, field);	\
+				}					\
+				RB_COLOR(tmp, field) = RB_COLOR(parent, field);\
+				RB_COLOR(parent, field) = RB_BLACK;	\
+				if (RB_LEFT(tmp, field))		\
+					RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\
+				RB_ROTATE_RIGHT(head, parent, tmp, field);\
+				elm = RB_ROOT(head);			\
+				break;					\
+			}						\
+		}							\
+	}								\
+	if (elm)							\
+		RB_COLOR(elm, field) = RB_BLACK;			\
+}									\
+									\
+attr struct type *							\
+name##_RB_REMOVE(struct name *head, struct type *elm)			\
+{									\
+	struct type *child, *parent, *old = elm;			\
+	int color;							\
+	if (RB_LEFT(elm, field) == NULL)				\
+		child = RB_RIGHT(elm, field);				\
+	else if (RB_RIGHT(elm, field) == NULL)				\
+		child = RB_LEFT(elm, field);				\
+	else {								\
+		struct type *left;					\
+		elm = RB_RIGHT(elm, field);				\
+		while ((left = RB_LEFT(elm, field)))			\
+			elm = left;					\
+		child = RB_RIGHT(elm, field);				\
+		parent = RB_PARENT(elm, field);				\
+		color = RB_COLOR(elm, field);				\
+		if (child)						\
+			RB_PARENT(child, field) = parent;		\
+		if (parent) {						\
+			if (RB_LEFT(parent, field) == elm)		\
+				RB_LEFT(parent, field) = child;		\
+			else						\
+				RB_RIGHT(parent, field) = child;	\
+			RB_AUGMENT(parent);				\
+		} else							\
+			RB_ROOT(head) = child;				\
+		if (RB_PARENT(elm, field) == old)			\
+			parent = elm;					\
+		(elm)->field = (old)->field;				\
+		if (RB_PARENT(old, field)) {				\
+			if (RB_LEFT(RB_PARENT(old, field), field) == old)\
+				RB_LEFT(RB_PARENT(old, field), field) = elm;\
+			else						\
+				RB_RIGHT(RB_PARENT(old, field), field) = elm;\
+			RB_AUGMENT(RB_PARENT(old, field));		\
+		} else							\
+			RB_ROOT(head) = elm;				\
+		RB_PARENT(RB_LEFT(old, field), field) = elm;		\
+		if (RB_RIGHT(old, field))				\
+			RB_PARENT(RB_RIGHT(old, field), field) = elm;	\
+		if (parent) {						\
+			left = parent;					\
+			do {						\
+				RB_AUGMENT(left);			\
+			} while ((left = RB_PARENT(left, field)));	\
+		}							\
+		goto color;						\
+	}								\
+	parent = RB_PARENT(elm, field);					\
+	color = RB_COLOR(elm, field);					\
+	if (child)							\
+		RB_PARENT(child, field) = parent;			\
+	if (parent) {							\
+		if (RB_LEFT(parent, field) == elm)			\
+			RB_LEFT(parent, field) = child;			\
+		else							\
+			RB_RIGHT(parent, field) = child;		\
+		RB_AUGMENT(parent);					\
+	} else								\
+		RB_ROOT(head) = child;					\
+color:									\
+	if (color == RB_BLACK)						\
+		name##_RB_REMOVE_COLOR(head, parent, child);		\
+	return (old);							\
+}									\
+									\
+/* Inserts a node into the RB tree */					\
+attr struct type *							\
+name##_RB_INSERT(struct name *head, struct type *elm)			\
+{									\
+	struct type *tmp;						\
+	struct type *parent = NULL;					\
+	int comp = 0;							\
+	tmp = RB_ROOT(head);						\
+	while (tmp) {							\
+		parent = tmp;						\
+		comp = (cmp)(elm, parent);				\
+		if (comp < 0)						\
+			tmp = RB_LEFT(tmp, field);			\
+		else if (comp > 0)					\
+			tmp = RB_RIGHT(tmp, field);			\
+		else							\
+			return (tmp);					\
+	}								\
+	RB_SET(elm, parent, field);					\
+	if (parent != NULL) {						\
+		if (comp < 0)						\
+			RB_LEFT(parent, field) = elm;			\
+		else							\
+			RB_RIGHT(parent, field) = elm;			\
+		RB_AUGMENT(parent);					\
+	} else								\
+		RB_ROOT(head) = elm;					\
+	name##_RB_INSERT_COLOR(head, elm);				\
+	return (NULL);							\
+}									\
+									\
+/* Finds the node with the same key as elm */				\
+attr struct type *							\
+name##_RB_FIND(struct name *head, struct type *elm)			\
+{									\
+	struct type *tmp = RB_ROOT(head);				\
+	int comp;							\
+	while (tmp) {							\
+		comp = cmp(elm, tmp);					\
+		if (comp < 0)						\
+			tmp = RB_LEFT(tmp, field);			\
+		else if (comp > 0)					\
+			tmp = RB_RIGHT(tmp, field);			\
+		else							\
+			return (tmp);					\
+	}								\
+	return (NULL);							\
+}									\
+									\
+/* Finds the first node greater than or equal to the search key */	\
+attr struct type *							\
+name##_RB_NFIND(struct name *head, struct type *elm)			\
+{									\
+	struct type *tmp = RB_ROOT(head);				\
+	struct type *res = NULL;					\
+	int comp;							\
+	while (tmp) {							\
+		comp = cmp(elm, tmp);					\
+		if (comp < 0) {						\
+			res = tmp;					\
+			tmp = RB_LEFT(tmp, field);			\
+		}							\
+		else if (comp > 0)					\
+			tmp = RB_RIGHT(tmp, field);			\
+		else							\
+			return (tmp);					\
+	}								\
+	return (res);							\
+}									\
+									\
+/* ARGSUSED */								\
+attr struct type *							\
+name##_RB_NEXT(struct type *elm)					\
+{									\
+	if (RB_RIGHT(elm, field)) {					\
+		elm = RB_RIGHT(elm, field);				\
+		while (RB_LEFT(elm, field))				\
+			elm = RB_LEFT(elm, field);			\
+	} else {							\
+		if (RB_PARENT(elm, field) &&				\
+		    (elm == RB_LEFT(RB_PARENT(elm, field), field)))	\
+			elm = RB_PARENT(elm, field);			\
+		else {							\
+			while (RB_PARENT(elm, field) &&			\
+			    (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\
+				elm = RB_PARENT(elm, field);		\
+			elm = RB_PARENT(elm, field);			\
+		}							\
+	}								\
+	return (elm);							\
+}									\
+									\
+/* ARGSUSED */								\
+attr struct type *							\
+name##_RB_PREV(struct type *elm)					\
+{									\
+	if (RB_LEFT(elm, field)) {					\
+		elm = RB_LEFT(elm, field);				\
+		while (RB_RIGHT(elm, field))				\
+			elm = RB_RIGHT(elm, field);			\
+	} else {							\
+		if (RB_PARENT(elm, field) &&				\
+		    (elm == RB_RIGHT(RB_PARENT(elm, field), field)))	\
+			elm = RB_PARENT(elm, field);			\
+		else {							\
+			while (RB_PARENT(elm, field) &&			\
+			    (elm == RB_LEFT(RB_PARENT(elm, field), field)))\
+				elm = RB_PARENT(elm, field);		\
+			elm = RB_PARENT(elm, field);			\
+		}							\
+	}								\
+	return (elm);							\
+}									\
+									\
+attr struct type *							\
+name##_RB_MINMAX(struct name *head, int val)				\
+{									\
+	struct type *tmp = RB_ROOT(head);				\
+	struct type *parent = NULL;					\
+	while (tmp) {							\
+		parent = tmp;						\
+		if (val < 0)						\
+			tmp = RB_LEFT(tmp, field);			\
+		else							\
+			tmp = RB_RIGHT(tmp, field);			\
+	}								\
+	return (parent);						\
+}
+
+#define RB_NEGINF	-1
+#define RB_INF	1
+
+#define RB_INSERT(name, x, y)	name##_RB_INSERT(x, y)
+#define RB_REMOVE(name, x, y)	name##_RB_REMOVE(x, y)
+#define RB_FIND(name, x, y)	name##_RB_FIND(x, y)
+#define RB_NFIND(name, x, y)	name##_RB_NFIND(x, y)
+#define RB_NEXT(name, x, y)	name##_RB_NEXT(y)
+#define RB_PREV(name, x, y)	name##_RB_PREV(y)
+#define RB_MIN(name, x)		name##_RB_MINMAX(x, RB_NEGINF)
+#define RB_MAX(name, x)		name##_RB_MINMAX(x, RB_INF)
+
+#define RB_FOREACH(x, name, head)					\
+	for ((x) = RB_MIN(name, head);					\
+	     (x) != NULL;						\
+	     (x) = name##_RB_NEXT(x))
+
+#define RB_FOREACH_SAFE(x, name, head, y)				\
+	for ((x) = RB_MIN(name, head);					\
+	    ((x) != NULL) && ((y) = name##_RB_NEXT(x), 1);		\
+	     (x) = (y))
+
+#define RB_FOREACH_REVERSE(x, name, head)				\
+	for ((x) = RB_MAX(name, head);					\
+	     (x) != NULL;						\
+	     (x) = name##_RB_PREV(x))
+
+#define RB_FOREACH_REVERSE_SAFE(x, name, head, y)			\
+	for ((x) = RB_MAX(name, head);					\
+	    ((x) != NULL) && ((y) = name##_RB_PREV(x), 1);		\
+	     (x) = (y))
+
+
+/*
+ * Copyright (c) 2016 David Gwynne <dlg@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+struct rb_type {
+	int		(*t_compare)(const void *, const void *);
+	void		(*t_augment)(void *);
+	unsigned int	  t_offset;	/* offset of rb_entry in type */
+};
+
+struct rb_tree {
+	struct rb_entry	*rbt_root;
+};
+
+struct rb_entry {
+	struct rb_entry	 *rbt_parent;
+	struct rb_entry	 *rbt_left;
+	struct rb_entry	 *rbt_right;
+	unsigned int	  rbt_color;
+};
+
+#define RBT_HEAD(_name, _type)						\
+struct _name {								\
+	struct rb_tree rbh_root;					\
+}
+
+#define RBT_ENTRY(_type)	struct rb_entry
+
+static inline void
+_rb_init(struct rb_tree *rbt)
+{
+	rbt->rbt_root = NULL;
+}
+
+static inline int
+_rb_empty(struct rb_tree *rbt)
+{
+	return (rbt->rbt_root == NULL);
+}
+
+void	*_rb_insert(const struct rb_type *, struct rb_tree *, void *);
+void	*_rb_remove(const struct rb_type *, struct rb_tree *, void *);
+void	*_rb_find(const struct rb_type *, struct rb_tree *, const void *);
+void	*_rb_nfind(const struct rb_type *, struct rb_tree *, const void *);
+void	*_rb_root(const struct rb_type *, struct rb_tree *);
+void	*_rb_min(const struct rb_type *, struct rb_tree *);
+void	*_rb_max(const struct rb_type *, struct rb_tree *);
+void	*_rb_next(const struct rb_type *, void *);
+void	*_rb_prev(const struct rb_type *, void *);
+void	*_rb_left(const struct rb_type *, void *);
+void	*_rb_right(const struct rb_type *, void *);
+void	*_rb_parent(const struct rb_type *, void *);
+void	 _rb_set_left(const struct rb_type *, void *, void *);
+void	 _rb_set_right(const struct rb_type *, void *, void *);
+void	 _rb_set_parent(const struct rb_type *, void *, void *);
+void	 _rb_poison(const struct rb_type *, void *, unsigned long);
+int	 _rb_check(const struct rb_type *, void *, unsigned long);
+
+#define RBT_INITIALIZER(_head)	{ { NULL } }
+
+#define RBT_PROTOTYPE(_name, _type, _field, _cmp)			\
+extern const struct rb_type *const _name##_RBT_TYPE;			\
+									\
+static inline void							\
+_name##_RBT_INIT(struct _name *head)					\
+{									\
+	_rb_init(&head->rbh_root);					\
+}									\
+									\
+static inline struct _type *						\
+_name##_RBT_INSERT(struct _name *head, struct _type *elm)		\
+{									\
+	return _rb_insert(_name##_RBT_TYPE, &head->rbh_root, elm);	\
+}									\
+									\
+static inline struct _type *						\
+_name##_RBT_REMOVE(struct _name *head, struct _type *elm)		\
+{									\
+	return _rb_remove(_name##_RBT_TYPE, &head->rbh_root, elm);	\
+}									\
+									\
+static inline struct _type *						\
+_name##_RBT_FIND(struct _name *head, const struct _type *key)		\
+{									\
+	return _rb_find(_name##_RBT_TYPE, &head->rbh_root, key);	\
+}									\
+									\
+static inline struct _type *						\
+_name##_RBT_NFIND(struct _name *head, const struct _type *key)		\
+{									\
+	return _rb_nfind(_name##_RBT_TYPE, &head->rbh_root, key);	\
+}									\
+									\
+static inline struct _type *						\
+_name##_RBT_ROOT(struct _name *head)					\
+{									\
+	return _rb_root(_name##_RBT_TYPE, &head->rbh_root);		\
+}									\
+									\
+static inline int							\
+_name##_RBT_EMPTY(struct _name *head)					\
+{									\
+	return _rb_empty(&head->rbh_root);				\
+}									\
+									\
+static inline struct _type *						\
+_name##_RBT_MIN(struct _name *head)					\
+{									\
+	return _rb_min(_name##_RBT_TYPE, &head->rbh_root);		\
+}									\
+									\
+static inline struct _type *						\
+_name##_RBT_MAX(struct _name *head)					\
+{									\
+	return _rb_max(_name##_RBT_TYPE, &head->rbh_root);		\
+}									\
+									\
+static inline struct _type *						\
+_name##_RBT_NEXT(struct _type *elm)					\
+{									\
+	return _rb_next(_name##_RBT_TYPE, elm);				\
+}									\
+									\
+static inline struct _type *						\
+_name##_RBT_PREV(struct _type *elm)					\
+{									\
+	return _rb_prev(_name##_RBT_TYPE, elm);				\
+}									\
+									\
+static inline struct _type *						\
+_name##_RBT_LEFT(struct _type *elm)					\
+{									\
+	return _rb_left(_name##_RBT_TYPE, elm);				\
+}									\
+									\
+static inline struct _type *						\
+_name##_RBT_RIGHT(struct _type *elm)					\
+{									\
+	return _rb_right(_name##_RBT_TYPE, elm);			\
+}									\
+									\
+static inline struct _type *						\
+_name##_RBT_PARENT(struct _type *elm)					\
+{									\
+	return _rb_parent(_name##_RBT_TYPE, elm);			\
+}									\
+									\
+static inline void							\
+_name##_RBT_SET_LEFT(struct _type *elm, struct _type *left)		\
+{									\
+	_rb_set_left(_name##_RBT_TYPE, elm, left);			\
+}									\
+									\
+static inline void							\
+_name##_RBT_SET_RIGHT(struct _type *elm, struct _type *right)		\
+{									\
+	_rb_set_right(_name##_RBT_TYPE, elm, right);			\
+}									\
+									\
+static inline void							\
+_name##_RBT_SET_PARENT(struct _type *elm, struct _type *parent)		\
+{									\
+	_rb_set_parent(_name##_RBT_TYPE, elm, parent);			\
+}									\
+									\
+static inline void							\
+_name##_RBT_POISON(struct _type *elm, unsigned long poison)		\
+{									\
+	_rb_poison(_name##_RBT_TYPE, elm, poison);			\
+}									\
+									\
+static inline int							\
+_name##_RBT_CHECK(struct _type *elm, unsigned long poison)		\
+{									\
+	return _rb_check(_name##_RBT_TYPE, elm, poison);		\
+}
+
+#define RBT_GENERATE_INTERNAL(_name, _type, _field, _cmp, _aug)		\
+static int								\
+_name##_RBT_COMPARE(const void *lptr, const void *rptr)			\
+{									\
+	const struct _type *l = lptr, *r = rptr;			\
+	return _cmp(l, r);						\
+}									\
+static const struct rb_type _name##_RBT_INFO = {			\
+	_name##_RBT_COMPARE,						\
+	_aug,								\
+	offsetof(struct _type, _field),					\
+};									\
+const struct rb_type *const _name##_RBT_TYPE = &_name##_RBT_INFO
+
+#define RBT_GENERATE_AUGMENT(_name, _type, _field, _cmp, _aug)		\
+static void								\
+_name##_RBT_AUGMENT(void *ptr)						\
+{									\
+	struct _type *p = ptr;						\
+	return _aug(p);							\
+}									\
+RBT_GENERATE_INTERNAL(_name, _type, _field, _cmp, _name##_RBT_AUGMENT)
+
+#define RBT_GENERATE(_name, _type, _field, _cmp)			\
+    RBT_GENERATE_INTERNAL(_name, _type, _field, _cmp, NULL)
+
+#define RBT_INIT(_name, _head)		_name##_RBT_INIT(_head)
+#define RBT_INSERT(_name, _head, _elm)	_name##_RBT_INSERT(_head, _elm)
+#define RBT_REMOVE(_name, _head, _elm)	_name##_RBT_REMOVE(_head, _elm)
+#define RBT_FIND(_name, _head, _key)	_name##_RBT_FIND(_head, _key)
+#define RBT_NFIND(_name, _head, _key)	_name##_RBT_NFIND(_head, _key)
+#define RBT_ROOT(_name, _head)		_name##_RBT_ROOT(_head)
+#define RBT_EMPTY(_name, _head)		_name##_RBT_EMPTY(_head)
+#define RBT_MIN(_name, _head)		_name##_RBT_MIN(_head)
+#define RBT_MAX(_name, _head)		_name##_RBT_MAX(_head)
+#define RBT_NEXT(_name, _elm)		_name##_RBT_NEXT(_elm)
+#define RBT_PREV(_name, _elm)		_name##_RBT_PREV(_elm)
+#define RBT_LEFT(_name, _elm)		_name##_RBT_LEFT(_elm)
+#define RBT_RIGHT(_name, _elm)		_name##_RBT_RIGHT(_elm)
+#define RBT_PARENT(_name, _elm)		_name##_RBT_PARENT(_elm)
+#define RBT_SET_LEFT(_name, _elm, _l)	_name##_RBT_SET_LEFT(_elm, _l)
+#define RBT_SET_RIGHT(_name, _elm, _r)	_name##_RBT_SET_RIGHT(_elm, _r)
+#define RBT_SET_PARENT(_name, _elm, _p)	_name##_RBT_SET_PARENT(_elm, _p)
+#define RBT_POISON(_name, _elm, _p)	_name##_RBT_POISON(_elm, _p)
+#define RBT_CHECK(_name, _elm, _p)	_name##_RBT_CHECK(_elm, _p)
+
+#define RBT_FOREACH(_e, _name, _head)					\
+	for ((_e) = RBT_MIN(_name, (_head));				\
+	     (_e) != NULL;						\
+	     (_e) = RBT_NEXT(_name, (_e)))
+
+#define RBT_FOREACH_SAFE(_e, _name, _head, _n)				\
+	for ((_e) = RBT_MIN(_name, (_head));				\
+	     (_e) != NULL && ((_n) = RBT_NEXT(_name, (_e)), 1);	\
+	     (_e) = (_n))
+
+#define RBT_FOREACH_REVERSE(_e, _name, _head)				\
+	for ((_e) = RBT_MAX(_name, (_head));				\
+	     (_e) != NULL;						\
+	     (_e) = RBT_PREV(_name, (_e)))
+
+#define RBT_FOREACH_REVERSE_SAFE(_e, _name, _head, _n)			\
+	for ((_e) = RBT_MAX(_name, (_head));				\
+	     (_e) != NULL && ((_n) = RBT_PREV(_name, (_e)), 1);	\
+	     (_e) = (_n))
+
+#endif	/* _SYS_TREE_H_ */
blob - /dev/null
blob + 6452b2bf184aa60b92f3c0f69f6f9e3e50b52dcc (mode 644)
--- /dev/null
+++ compat/unistd.h
@@ -0,0 +1,27 @@
+#include_next "unistd.h"
+
+#include "../config.h"
+
+#if !HAVE_PLEGDE
+int	pledge(const char *, const char *);
+#endif
+
+#if !HAVE_UNVEIL
+int	unveil(const char *, const char *);
+#endif
+
+#if !HAVE_GETDTABLECOUNT
+int	getdtablecount(void);
+#endif
+
+#if !HAVE_GETDTABLESIZE
+int	getdtablesize(void);
+#endif
+
+#if !HAVE_SETRESGID
+int	setresgid(gid_t, gid_t, gid_t);
+#endif
+
+#if !HAVE_SETRESUID
+int	setresuid(uid_t, uid_t, uid_t);
+#endif
blob - /dev/null
blob + a6fd562ca3f2e3974dba47e0b5029eabffd51e9e (mode 644)
--- /dev/null
+++ compat/unveil.c
@@ -0,0 +1,7 @@
+int	unveil(const char *, const char *);
+
+int
+unveil(const char *path, const char *permissions)
+{
+	return 0;
+}
blob - /dev/null
blob + 8d012e94124300b10529531bbfb0ea93cf36eca3 (mode 644)
--- /dev/null
+++ compat/vasprintf.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2015 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * This fallback implementation is not efficient:
+ * It does the formatting twice.
+ * Short of fiddling with the unknown internals of the system's
+ * printf(3) or completely reimplementing printf(3), i can't think
+ * of another portable solution.
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int
+vasprintf(char **ret, const char *format, va_list ap)
+{
+	char	 buf[2];
+	va_list	 ap2;
+	int	 sz;
+
+	va_copy(ap2, ap);
+	sz = vsnprintf(buf, sizeof(buf), format, ap2);
+	va_end(ap2);
+
+	if (sz != -1 && (*ret = malloc(sz + 1)) != NULL) {
+		if (vsnprintf(*ret, sz + 1, format, ap) == sz)
+			return sz;
+		free(*ret);
+	}
+	*ret = NULL;
+	return -1;
+}
blob - /dev/null
blob + 2cef3cda79960358a8bf5b2f9be655931973aa2b (mode 755)
--- /dev/null
+++ configure
@@ -0,0 +1,402 @@
+#!/bin/sh
+#
+# Copyright (c) 2014, 2015, 2016 Ingo Schwarze <schwarze@openbsd.org>
+# Copyright (c) 2017, 2018 Kristaps Dzonsons <kristaps@bsd.lv>
+# Copyright (c) 2022 Omar Polo <op@omarpolo.com>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+set -e
+
+RELEASE="${RELEASE:-no}"
+
+usage()
+{
+	echo "usage: $0 [--help] [--prefix=prefix] [OPTION=VALUE...]" >&2
+	exit 1
+}
+
+if command -v yacc 2>/dev/null >&2; then
+	YACC=yacc
+elif command -v bison 2>/dev/null >&2; then
+	YACC=bison
+else
+	# assume yacc by default.  Make will fail building parse.y if
+	# not from a release tarball, but at least it'll have a decent
+	# error message.
+	YACC=yacc
+fi
+
+if command -v pkg-config 2>/dev/null >&2; then
+	pkgconfig=pkg-config
+else
+	pkgconfig=
+fi
+
+db=
+user=
+sock=
+while [ $# -gt 0 ]; do
+	key="${1%%=*}"
+	val="${1#*=}"
+
+	if [ "$key" = --help ]; then
+		usage
+	fi
+
+	if [ "$key" = --prefix ]; then
+		key=PREFIX
+		if [ "$1" = --prefix ]; then # no =, look at next arg
+			if !shift 2>&1 >/dev/null; then
+				echo "$0: missing value for --prefix" >&2
+				exit 1
+			fi
+			val="$1"
+		fi
+	fi
+
+	if [ "$1" = "$key" ]; then
+		echo "$0: invalid key-value: $1" >&2
+		exit 1
+	fi
+
+	case "$key" in
+	CC)
+		CC="$val" ;;
+	CFLAGS)
+		CFLAGS="$val" ;;
+	DB)
+		db="$val" ;;
+	LDADD)
+		LDADD="$val" ;;
+	LDADD_LIBEVENT)
+		LDADD_LIBEVENT="$val" ;;
+	LDADD_LIBSQLITE3)
+		LDADD_LIBSQLITE3="$val" ;;
+	LDADD_LIBSOCKET)
+		LDADD_LIBSOCKET="$val" ;;
+	PKG_CONFIG)
+		pkgconfig="$val" ;;
+	SOCK)
+		sock="$sock" ;;
+	USER)
+		user="$val" ;;
+	YACC)
+		YACC="$val" ;;
+	esac
+
+	shift
+done
+
+CDIAGFLAGS=
+CDIAGFLAGS="${CDIAGFLAGS} -Wall -Wextra -Wpointer-arith -Wuninitialized"
+CDIAGFLAGS="${CDIAGFLAGS} -Wstrict-prototypes -Wmissing-prototypes -Wunused"
+CDIAGFLAGS="${CDIAGFLAGS} -Wsign-compare -Wshadow -Wno-unused-parameter"
+CDIAGFLAGS="${CDIAGFLAGS} -Wno-missing-field-initializers"
+CDIAGFLAGS="${CDIAGFLAGS} -Wno-pointer-sign"
+
+# don't ship releases with -Werror
+test "$RELEASE" = no && CDIAGFLAGS="${CDIAGFLAGS} -Werror"
+
+CFLAGS="${CFLAGS:--O2 -pipe} ${CDIAGFLAGS}"
+CC="${CC:-cc}"
+LIBS="${LIBS:-}"
+LDFLAGS="${LDFLAGS:-}"
+
+HOSTCC="${HOSTCC:-${CC}}"
+HOSTCFLAGS="${HOSTCFLAGS:-${CFLAGS}}"
+
+# echo "running configure for \`template':" >&2
+# (cd ./template && ./configure CC="$HOSTCC" CFLAGS="$HOSTCFLAGS" YACC="$YACC")
+# echo "returning to the configure for \`pkg_fcgi':" >&2
+
+CFLAGS="${CFLAGS} -I. -Itemplate"
+test -n "$db"   && CFLAGS="${CFLAGS} -DPKG_FCGI_DB=\"\\\"$conf\\\"\""
+test -n "$user" && CFLAGS="${CFLAGS} -DPKG_FCGI_USER=\"\\\"$user\\\"\""
+test -n "$sock" && CFLAGS="${CFLAGS} -DPKG_FCGI_SOCK=\"\\\"$conf\\\"\""
+
+[ -w config.log ] && mv config.log config.log.old
+
+exec 3> config.log
+echo "config.log: writing..."
+
+COMPATS=
+
+HAVE_ACCEPT4=
+HAVE_ASR_RUN=
+HAVE_ERR=
+HAVE_EVENT_ASR_RUN=
+HAVE_FREEZERO=
+HAVE_GETDTABLECOUNT=
+HAVE_GETDTABLESIZE=
+HAVE_GETEXECNAME=
+HAVE_GETPROGNAME=
+HAVE_LIBEVENT=
+HAVE_LIBSQLITE3=
+HAVE_PLEDGE=
+HAVE_REALLOCARRAY=
+HAVE_RECALLOCARRAY=
+HAVE_SETGROUPS=
+HAVE_SETPROCTITLE=
+HAVE_SETRESGID=
+HAVE_SETRESUID=
+HAVE_STRLCAT=
+HAVE_STRLCPY=
+HAVE_STRTONUM=
+HAVE_SYS_QUEUE=
+HAVE_SYS_TREE=
+HAVE_UNVEIL=
+HAVE_VASPRINTF=
+HAVE_WAIT_ANY=
+HAVE___PROGNAME=
+
+# singletest name var extra-cflags extra-libs msg
+singletest() {
+	msg="$5"
+	if [ -z "$msg" ]; then
+		if [ -n "$3" -a "$4" ]; then
+			msg=" ($3 $4)"
+		elif [ -n "$3" ]; then
+			msg=" ($3)"
+		elif [ -n "$4" ]; then
+			msg=" ($4)"
+		fi
+	elif [ "$msg" = no ]; then
+	     msg=""
+	fi
+
+	cat >&3 <<EOF
+${1}: testing...
+$CC tests/${1}.c -Werror $3 -o test-$1 $LDFLAGS $4
+EOF
+	if $CC tests/${1}.c -Werror $3 -o test-$1 $LDFLAGS $4 >&3 2>&3; then
+		rm -f test-${1} test-${1}.d
+
+		echo "${1}: $CC$msg succeeded" >&3
+		echo "${1}$msg: yes"
+		echo >&3
+
+		return 0
+	fi
+
+	echo "${1}: $CC$msg failed with $?" >&3
+	echo "${1}$msg: no"
+	echo >&3
+
+	return 1
+}
+
+# deptest name var
+deptest() {
+	if singletest "$1" "$2" "${CFLAGS}" "${LIBS}" no; then
+		eval HAVE_${2}=1
+		return 0
+	fi
+
+	if [ -f compat/${1}.c ]; then
+		COMPATS="compat/${1}.c $COMPATS"
+	fi
+
+	eval HAVE_${2}=0
+	return 1
+}
+
+# runtest name var extra-cflags extra-libs pkgconfig-name
+runtest() {
+	if singletest "$1" "$2" "" ""; then
+		eval HAVE_${2}=1
+		return 0
+	fi
+
+	if [ -n "$3" -o -n "$4" ]; then
+		echo "retrying with ${3+$3 }$4" >&3
+		if singletest "$1" "$2" "$3" "$4"; then
+			if [ -n "$3" ]; then
+				CFLAGS="$CFLAGS $3"
+			fi
+			if [ -n "${4}" ]; then
+				LIBS="$LIBS $4"
+			fi
+			eval HAVE_${2}=1
+			return 0
+		fi
+	fi
+
+	if [ -n "$5" -a -n "$pkgconfig" ]; then
+		if $pkgconfig "$5"; then
+			cflags="$($pkgconfig --cflags "$5")"
+			ldflags="$($pkgconfig --libs "$5")"
+			echo "retrying with pkg-config" >&3
+			if singletest "$1" "$2" "$3" "$cflags" "$ldflags"; then
+				CFLAGS="$CFLAGS $cflags"
+				LIBS="$LIBS $ldflags"
+				eval HAVE_${2}=1
+				return 0
+			fi
+		fi
+	fi
+
+	if [ -f compat/${1}.c ]; then
+		COMPATS="compat/${1}.c $COMPATS"
+	fi
+
+	eval HAVE_${2}=0
+	return 1
+}
+
+if singletest MMD _MMD -MMD >/dev/null; then
+	CFLAGS="${CFLAGS} -MMD"
+	echo "adding -MMD to CFLAGS" >&2
+	echo "adding -MMD to CFLAGS" >&3
+fi
+
+if ! singletest WAIT_ANY WAIT_ANY; then
+	CFLAGS="${CFLAGS} -DWAIT_ANY=-1"
+fi
+
+runtest accept4		ACCEPT4 -D_GNU_SOURCE			|| true
+runtest asr_run		ASR_RUN "" "-lasr"			|| true
+runtest err		ERR					|| true
+runtest freezero	FREEZERO				|| true
+runtest getdtablecount	GETDTABLECOUNT				|| true
+runtest getdtablesize	GETDTABLESIZE				|| true
+runtest getexecname	GETEXECNAME				|| true
+runtest getprogname	GETPROGNAME				|| true
+runtest libevent	LIBEVENT "" "-levent"	libevent_core	|| true
+runtest libsqlite3	LIBSQLITE3 "-I/usr/local/include" "-L/usr/local/lib -lsqlite3" sqlite3	|| true
+runtest pledge		PLEDGE					|| true
+runtest reallocarray	REALLOCARRAY -D_OPENBSD_SOURCE		|| true
+runtest recallocarray	RECALLOCARRAY -D_OPENBSD_SOURCE		|| true
+runtest setgroups	SETGROUPS -D_BSD_SOURCE			|| true
+runtest setproctitle	SETPROCTITLE				|| true
+runtest setresgid	SETRESGID -D_GNU_SOURCE			|| true
+runtest setresuid	SETRESUID -D_GNU_SOURCE			|| true
+runtest strlcat		STRLCAT					|| true
+runtest strlcpy		STRLCPY					|| true
+runtest strtonum	STRTONUM				|| true
+runtest sys_queue	SYS_QUEUE				|| true
+runtest sys_tree	SYS_TREE				|| true
+runtest unveil		UNVEIL					|| true
+runtest vasprintf	VASPRINTF -D_GNU_SOURCE			|| true
+runtest __progname	__PROGNAME				|| true
+
+deptest event_asr_run	EVENT_ASR_RUN				|| true
+
+# mandatory things:
+
+if [ "${HAVE_ACCEPT4}" -eq 0 ]; then
+	echo "Fatal: missing accept4(2)" >&2
+	echo "Fatal: missing accept4(2)" >&3
+	exit 1
+fi
+
+if [ "${HAVE_ASR_RUN}" -eq 0 ]; then
+	echo "Fatal: missing libasr" >&2
+	echo "Fatal: missing libasr" >&3
+	exit 1
+fi
+
+if [ "${HAVE_LIBEVENT}" -eq 0 ]; then
+	echo "Fatal: missing libevent" >&2
+	echo "Fatal: missing libevent" >&3
+	exit 1
+fi
+
+if [ "${HAVE_LIBSQLITE3}" -eq 0 ]; then
+	echo "Fatal: missing libsqlite3" >&2
+	echo "Fatal: missing libsqlite3" >&3
+	exit 1
+fi
+
+if [ "${HAVE_SETGROUPS}" -eq 0 ]; then
+	echo "Fatal: missing setgroups(2)" >&2
+	echo "Fatal: missing setgroups(2)" >&3
+	exit 1
+fi
+
+# things we can provide by ourselves:
+
+if [ "${HAVE_SYS_QUEUE}" -eq 0 -o "${HAVE_SYS_TREE}" -eq 0 ]; then
+	CFLAGS="-I compat/sys ${CFLAGS}"
+fi
+
+if [ -n "${COMPATS}" ]; then
+	CFLAGS="-I compat/ ${CFLAGS}"
+fi
+
+exec > config.h
+echo "config.h: writing..." >&2
+
+cat <<EOF
+#ifndef CONFIG_H
+#define CONFIG_H
+
+#ifdef __cplusplus
+# error "Do not use C++: this is a C application."
+#endif
+
+#define HAVE_ACCEPT4		${HAVE_ACCEPT4}
+#define HAVE_ASR_RUN		${HAVE_ASR_RUN}
+#define HAVE_ERR		${HAVE_ERR}
+#define HAVE_EVENT_ASR_RUN	${HAVE_EVENT_ASR_RUN}
+#define HAVE_FREEZERO		${HAVE_FREEZERO}
+#define HAVE_GETDTABLECOUNT	${HAVE_GETDTABLECOUNT}
+#define HAVE_GETDTABLESIZE	${HAVE_GETDTABLESIZE}
+#define HAVE_GETEXECNAME	${HAVE_GETEXECNAME}
+#define HAVE_GETPROGNAME	${HAVE_GETPROGNAME}
+#define HAVE_LIBEVENT		${HAVE_LIBEVENT}
+#define HAVE_LIBSQLITE3		${HAVE_LIBSQLITE3}
+#define HAVE_PLEDGE		${HAVE_PLEDGE}
+#define HAVE_REALLOCARRAY	${HAVE_REALLOCARRAY}
+#define HAVE_RECALLOCARRAY	${HAVE_RECALLOCARRAY}
+#define HAVE_SETGROUPS		${HAVE_SETGROUPS}
+#define HAVE_SETPROCTITLE	${HAVE_SETPROCTITLE}
+#define HAVE_SETRESGID		${HAVE_SETRESGID}
+#define HAVE_SETRESUID		${HAVE_SETRESUID}
+#define HAVE_STRLCAT		${HAVE_STRLCAT}
+#define HAVE_STRLCPY		${HAVE_STRLCPY}
+#define HAVE_STRTONUM		${HAVE_STRTONUM}
+#define HAVE_SYS_QUEUE		${HAVE_SYS_QUEUE}
+#define HAVE_SYS_TREE		${HAVE_SYS_TREE}
+#define HAVE_UNVEIL		${HAVE_UNVEIL}
+#define HAVE_VASPRINTF		${HAVE_VASPRINTF}
+#define HAVE___PROGNAME		${HAVE___PROGNAME}
+
+#endif
+EOF
+
+exec > config.mk
+echo "config.mk: writing..." >&2
+
+cat <<EOF
+CC=		${CC}
+CFLAGS=		${CFLAGS}
+LIBS=		${LIBS}
+LDFLAGS=	${LDFLAGS}
+YACC=		${YACC}
+
+COMPATS=	${COMPATS}
+
+INSTALL=	install
+INSTALL_PROGRAM=\${INSTALL} -m 0555
+INSTALL_LIB=	\${INSTALL} -m 0444
+INSTALL_MAN=	\${INSTALL} -m 0444
+INSTALL_DATA=	\${INSTALL} -m 0444
+
+EOF
+
+echo "done!" >&2
+
+echo >&2
+echo "Now run \`make' to compile." >&2
+echo >&2
blob - /dev/null
blob + 166777374961a77ac558fb8d7c3be442a6e1333c (mode 644)
--- /dev/null
+++ fcgi.c
@@ -0,0 +1,788 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/queue.h>
+#include <sys/tree.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <event.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "pkg.h"
+#include "tmpl.h"
+
+#define MIN(a, b)	((a) < (b) ? (a) : (b))
+
+struct fcgi_header {
+	unsigned char version;
+	unsigned char type;
+	unsigned char req_id1;
+	unsigned char req_id0;
+	unsigned char content_len1;
+	unsigned char content_len0;
+	unsigned char padding;
+	unsigned char reserved;
+} __attribute__((packed));
+
+/*
+ * number of bytes in a FCGI_HEADER.  Future version of the protocol
+ * will not reduce this number.
+ */
+#define FCGI_HEADER_LEN	8
+
+/*
+ * values for the version component
+ */
+#define FCGI_VERSION_1	1
+
+/*
+ * values for the type component
+ */
+#define FCGI_BEGIN_REQUEST	 1
+#define FCGI_ABORT_REQUEST	 2
+#define FCGI_END_REQUEST	 3
+#define FCGI_PARAMS		 4
+#define FCGI_STDIN		 5
+#define FCGI_STDOUT		 6
+#define FCGI_STDERR		 7
+#define FCGI_DATA		 8
+#define FCGI_GET_VALUES		 9
+#define FCGI_GET_VALUES_RESULT	10
+#define FCGI_UNKNOWN_TYPE	11
+#define FCGI_MAXTYPE		(FCGI_UNKNOWN_TYPE)
+
+struct fcgi_begin_req {
+	unsigned char role1;
+	unsigned char role0;
+	unsigned char flags;
+	unsigned char reserved[5];
+};
+
+struct fcgi_begin_req_record {
+	struct fcgi_header	header;
+	struct fcgi_begin_req	body;
+};
+
+/*
+ * mask for flags;
+ */
+#define FCGI_KEEP_CONN		1
+
+/*
+ * values for the role
+ */
+#define FCGI_RESPONDER	1
+#define FCGI_AUTHORIZER	2
+#define FCGI_FILTER	3
+
+struct fcgi_end_req_body {
+	unsigned char app_status3;
+	unsigned char app_status2;
+	unsigned char app_status1;
+	unsigned char app_status0;
+	unsigned char proto_status;
+	unsigned char reserved[3];
+};
+
+/*
+ * values for proto_status
+ */
+#define FCGI_REQUEST_COMPLETE	0
+#define FCGI_CANT_MPX_CONN	1
+#define FCGI_OVERLOADED		2
+#define FCGI_UNKNOWN_ROLE	3
+
+/*
+ * Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT
+ * records.
+ */
+#define FCGI_MAX_CONNS	"FCGI_MAX_CONNS"
+#define FCGI_MAX_REQS	"FCGI_MAX_REQS"
+#define FCGI_MPXS_CONNS	"FCGI_MPXS_CONNS"
+
+#define CAT(f0, f1)	((f0) + ((f1) << 8))
+
+enum {
+	FCGI_RECORD_HEADER,
+	FCGI_RECORD_BODY,
+};
+
+volatile int	fcgi_inflight;
+int32_t		fcgi_id;
+
+int	accept_reserve(int, struct sockaddr *, socklen_t *, int,
+    volatile int *);
+
+static int
+fcgi_send_end_req(struct fcgi *fcgi, int id, int as, int ps)
+{
+	struct bufferevent	*bev = fcgi->fcg_bev;
+	struct fcgi_header	 hdr;
+	struct fcgi_end_req_body end;
+
+	memset(&hdr, 0, sizeof(hdr));
+	memset(&end, 0, sizeof(end));
+
+	hdr.version = FCGI_VERSION_1;
+	hdr.type = FCGI_END_REQUEST;
+	hdr.req_id0 = (id & 0xFF);
+	hdr.req_id1 = (id >> 8);
+	hdr.content_len0 = sizeof(end);
+
+	end.app_status0 = (unsigned char)as;
+	end.proto_status = (unsigned char)ps;
+
+	if (bufferevent_write(bev, &hdr, sizeof(hdr)) == -1)
+		return (-1);
+	if (bufferevent_write(bev, &end, sizeof(end)) == -1)
+		return (-1);
+	return (0);
+}
+
+static int
+end_request(struct client *clt, int status, int proto_status)
+{
+	struct fcgi		*fcgi = clt->clt_fcgi;
+	int			 r;
+
+	if (clt_flush(clt) == -1)
+		return (-1);
+
+	r = fcgi_send_end_req(fcgi, clt->clt_id, status,
+	    proto_status);
+	if (r == -1) {
+		fcgi_error(fcgi->fcg_bev, EV_WRITE, fcgi);
+		return (-1);
+	}
+
+	SPLAY_REMOVE(client_tree, &fcgi->fcg_clients, clt);
+	server_client_free(clt);
+
+	if (!fcgi->fcg_keep_conn)
+		fcgi->fcg_done = 1;
+
+	return (0);
+}
+
+int
+fcgi_end_request(struct client *clt, int status)
+{
+	return (end_request(clt, status, FCGI_REQUEST_COMPLETE));
+}
+
+int
+fcgi_abort_request(struct client *clt)
+{
+	return (end_request(clt, 1, FCGI_OVERLOADED));
+}
+
+static void
+fcgi_inflight_dec(const char *why)
+{
+	fcgi_inflight--;
+	log_debug("%s: fcgi inflight decremented, now %d, %s",
+	    __func__, fcgi_inflight, why);
+}
+
+void
+fcgi_accept(int fd, short event, void *arg)
+{
+	struct env		*env = arg;
+	struct fcgi		*fcgi = NULL;
+	socklen_t		 slen;
+	struct sockaddr_storage	 ss;
+	int			 s = -1;
+
+	event_add(&env->env_pausev, NULL);
+	if ((event & EV_TIMEOUT))
+		return;
+
+	slen = sizeof(ss);
+	if ((s = accept_reserve(env->env_sockfd, (struct sockaddr *)&ss,
+	    &slen, FD_RESERVE, &fcgi_inflight)) == -1) {
+		/*
+		 * Pause accept if we are out of file descriptors, or
+		 * libevent will haunt us here too.
+		 */
+		if (errno == ENFILE || errno == EMFILE) {
+			struct timeval evtpause = { 1, 0 };
+
+			event_del(&env->env_sockev);
+			evtimer_add(&env->env_pausev, &evtpause);
+			log_debug("%s: deferring connections", __func__);
+		}
+		return;
+	}
+
+	if ((fcgi = calloc(1, sizeof(*fcgi))) == NULL)
+		goto err;
+
+	fcgi->fcg_id = ++fcgi_id;
+	fcgi->fcg_s = s;
+	fcgi->fcg_env = env;
+	fcgi->fcg_want = FCGI_RECORD_HEADER;
+	fcgi->fcg_toread = sizeof(struct fcgi_header);
+	SPLAY_INIT(&fcgi->fcg_clients);
+
+	/* assume it's enabled until we get a FCGI_BEGIN_REQUEST */
+	fcgi->fcg_keep_conn = 1;
+
+	fcgi->fcg_bev = bufferevent_new(fcgi->fcg_s, fcgi_read, fcgi_write,
+	    fcgi_error, fcgi);
+	if (fcgi->fcg_bev == NULL)
+		goto err;
+
+	bufferevent_enable(fcgi->fcg_bev, EV_READ | EV_WRITE);
+	return;
+
+err:
+	if (s != -1) {
+		close(s);
+		free(fcgi);
+		fcgi_inflight_dec(__func__);
+	}
+}
+
+static int
+parse_len(struct fcgi *fcgi, struct evbuffer *src)
+{
+	unsigned char		 c, x[3];
+
+	fcgi->fcg_toread--;
+	evbuffer_remove(src, &c, 1);
+	if (c >> 7 == 0)
+		return (c);
+
+	if (fcgi->fcg_toread < 3)
+		return (-1);
+
+	fcgi->fcg_toread -= 3;
+	evbuffer_remove(src, x, sizeof(x));
+	return (((c & 0x7F) << 24) | (x[0] << 16) | (x[1] << 8) | x[2]);
+}
+
+static int
+fcgi_parse_params(struct fcgi *fcgi, struct evbuffer *src, struct client *clt)
+{
+	char			 pname[32];
+	char			 server[HOST_NAME_MAX + 1];
+	char			 path[PATH_MAX];
+	char			 query[GEMINI_MAXLEN];
+	char			 method[8];
+	int			 nlen, vlen;
+
+	while (fcgi->fcg_toread > 0) {
+		if ((nlen = parse_len(fcgi, src)) < 0 ||
+		    (vlen = parse_len(fcgi, src)) < 0)
+			return (-1);
+
+		if (fcgi->fcg_toread < nlen + vlen)
+			return (-1);
+
+		if ((size_t)nlen > sizeof(pname) - 1) {
+			/* ignore this parameter */
+			fcgi->fcg_toread -= nlen - vlen;
+			evbuffer_drain(src, nlen + vlen);
+			continue;
+		}
+
+		fcgi->fcg_toread -= nlen;
+		evbuffer_remove(src, &pname, nlen);
+		pname[nlen] = '\0';
+
+		if (!strcmp(pname, "SERVER_NAME") &&
+		    (size_t)vlen < sizeof(server)) {
+			fcgi->fcg_toread -= vlen;
+			evbuffer_remove(src, &server, vlen);
+			server[vlen] = '\0';
+
+			free(clt->clt_server_name);
+			if ((clt->clt_server_name = strdup(server)) == NULL)
+				return (-1);
+			DPRINTF("clt %d: server_name: %s", clt->clt_id,
+			    clt->clt_server_name);
+			continue;
+		}
+
+		if (!strcmp(pname, "SCRIPT_NAME") &&
+		    (size_t)vlen < sizeof(path)) {
+			fcgi->fcg_toread -= vlen;
+			evbuffer_remove(src, &path, vlen);
+			path[vlen] = '\0';
+
+			free(clt->clt_script_name);
+			clt->clt_script_name = NULL;
+
+			if (vlen == 0 || path[vlen - 1] != '/')
+				asprintf(&clt->clt_script_name, "%s/", path);
+			else
+				clt->clt_script_name = strdup(path);
+
+			if (clt->clt_script_name == NULL)
+				return (-1);
+
+			DPRINTF("clt %d: script_name: %s", clt->clt_id,
+			    clt->clt_script_name);
+			continue;
+		}
+
+		/* XXX: fix gmid */
+		if (/*!strcmp(pname, "PATH_INFO") && */
+		    !strcmp(pname, "GEMINI_URL_PATH") &&
+		    (size_t)vlen < sizeof(path)) {
+			fcgi->fcg_toread -= vlen;
+			evbuffer_remove(src, &path, vlen);
+			path[vlen] = '\0';
+
+			free(clt->clt_path_info);
+			clt->clt_path_info = NULL;
+
+			if (*path != '/')
+				asprintf(&clt->clt_path_info, "/%s", path);
+			else
+				clt->clt_path_info = strdup(path);
+
+			if (clt->clt_path_info == NULL)
+				return (-1);
+
+			DPRINTF("clt %d: path_info: %s", clt->clt_id,
+			    clt->clt_path_info);
+			continue;
+		}
+
+		if (!strcmp(pname, "QUERY_STRING") &&
+		    (size_t)vlen < sizeof(query) &&
+		    vlen > 0) {
+			fcgi->fcg_toread -= vlen;
+			evbuffer_remove(src, &query, vlen);
+			query[vlen] = '\0';
+
+			free(clt->clt_query);
+			if ((clt->clt_query = strdup(query)) == NULL)
+				return (-1);
+
+			DPRINTF("clt %d: query: %s", clt->clt_id,
+			    clt->clt_query);
+			continue;
+		}
+
+		if (!strcmp(pname, "REQUEST_METHOD") &&
+		    (size_t)vlen < sizeof(method)) {
+			fcgi->fcg_toread -= vlen;
+			evbuffer_remove(src, &method, vlen);
+			method[vlen] = '\0';
+
+			if (!strcasecmp(method, "GET"))
+				clt->clt_method = METHOD_GET;
+			if (!strcasecmp(method, "POST"))
+				clt->clt_method = METHOD_POST;
+
+			continue;
+		}
+
+		fcgi->fcg_toread -= vlen;
+		evbuffer_drain(src, vlen);
+	}
+
+	return (0);
+}
+
+void
+fcgi_read(struct bufferevent *bev, void *d)
+{
+	struct fcgi		*fcgi = d;
+	struct env		*env = fcgi->fcg_env;
+	struct evbuffer		*src = EVBUFFER_INPUT(bev);
+	struct fcgi_header	 hdr;
+	struct fcgi_begin_req	 breq;
+	struct client		*clt, q;
+	int			 role;
+
+	memset(&q, 0, sizeof(q));
+
+	for (;;) {
+		if (EVBUFFER_LENGTH(src) < (size_t)fcgi->fcg_toread)
+			return;
+
+		if (fcgi->fcg_want == FCGI_RECORD_HEADER) {
+			fcgi->fcg_want = FCGI_RECORD_BODY;
+			bufferevent_read(bev, &hdr, sizeof(hdr));
+
+			DPRINTF("header: v=%d t=%d id=%d len=%d p=%d",
+			    hdr.version, hdr.type,
+			    CAT(hdr.req_id0, hdr.req_id1),
+			    CAT(hdr.content_len0, hdr.content_len1),
+			    hdr.padding);
+
+			if (hdr.version != FCGI_VERSION_1) {
+				log_warnx("unknown fastcgi version: %d",
+				    hdr.version);
+				fcgi_error(bev, EV_READ, d);
+				return;
+			}
+
+			fcgi->fcg_toread = CAT(hdr.content_len0,
+			    hdr.content_len1);
+			if (fcgi->fcg_toread < 0) {
+				log_warnx("invalid record length: %d",
+				    fcgi->fcg_toread);
+				fcgi_error(bev, EV_READ, d);
+				return;
+			}
+
+			fcgi->fcg_padding = hdr.padding;
+			if (fcgi->fcg_padding < 0) {
+				log_warnx("invalid padding: %d",
+				    fcgi->fcg_padding);
+				fcgi_error(bev, EV_READ, d);
+				return;
+			}
+
+			fcgi->fcg_type = hdr.type;
+			fcgi->fcg_rec_id = CAT(hdr.req_id0, hdr.req_id1);
+			continue;
+		}
+
+		q.clt_id = fcgi->fcg_rec_id;
+		clt = SPLAY_FIND(client_tree, &fcgi->fcg_clients, &q);
+
+		switch (fcgi->fcg_type) {
+		case FCGI_BEGIN_REQUEST:
+			if (sizeof(breq) != fcgi->fcg_toread) {
+				log_warnx("unexpected size for "
+				    "FCGI_BEGIN_REQUEST");
+				fcgi_error(bev, EV_READ, d);
+				return;
+			}
+
+			evbuffer_remove(src, &breq, sizeof(breq));
+
+			role = CAT(breq.role0, breq.role1);
+			if (role != FCGI_RESPONDER) {
+				log_warnx("unknown fastcgi role: %d",
+				    role);
+				if (fcgi_send_end_req(fcgi, fcgi->fcg_rec_id,
+				    1, FCGI_UNKNOWN_ROLE) == -1) {
+					fcgi_error(bev, EV_READ, d);
+					return;
+				}
+				break;
+			}
+
+			if (!fcgi->fcg_keep_conn) {
+				log_warnx("trying to reuse the fastcgi "
+				    "socket without marking it as so.");
+				fcgi_error(bev, EV_READ, d);
+				return;
+			}
+			fcgi->fcg_keep_conn = breq.flags & FCGI_KEEP_CONN;
+
+			if (clt != NULL) {
+				log_warnx("ignoring attemp to re-use an "
+				    "active request id (%d)",
+				    fcgi->fcg_rec_id);
+				break;
+			}
+
+			if ((clt = calloc(1, sizeof(*clt))) == NULL) {
+				log_warnx("calloc");
+				break;
+			}
+
+#if template
+			clt->clt_tp = template(clt, clt_tp_puts, clt_tp_putc);
+			if (clt->clt_tp == NULL) {
+				free(clt);
+				log_warn("template");
+				break;
+			}
+#endif
+
+			clt->clt_id = fcgi->fcg_rec_id;
+			clt->clt_fd = -1;
+			clt->clt_fcgi = fcgi;
+			SPLAY_INSERT(client_tree, &fcgi->fcg_clients, clt);
+			break;
+		case FCGI_PARAMS:
+			if (clt == NULL) {
+				log_warnx("got FCGI_PARAMS for inactive id "
+				    "(%d)", fcgi->fcg_rec_id);
+				evbuffer_drain(src, fcgi->fcg_toread);
+				break;
+			}
+			if (fcgi->fcg_toread == 0) {
+				evbuffer_drain(src, fcgi->fcg_toread);
+				if (server_handle(env, clt) == -1)
+					return;
+				break;
+			}
+			if (fcgi_parse_params(fcgi, src, clt) == -1) {
+				log_warnx("fcgi_parse_params failed");
+				fcgi_error(bev, EV_READ, d);
+				return;
+			}
+			break;
+		case FCGI_STDIN:
+			/* not interested in reading stdin */
+			evbuffer_drain(src, fcgi->fcg_toread);
+			break;
+		case FCGI_ABORT_REQUEST:
+			if (clt == NULL) {
+				log_warnx("got FCGI_ABORT_REQUEST for inactive"
+				    " id (%d)", fcgi->fcg_rec_id);
+				evbuffer_drain(src, fcgi->fcg_toread);
+				break;
+			}
+			if (fcgi_end_request(clt, 1) == -1) {
+				/* calls fcgi_error on failure */
+				return;
+			}
+			break;
+		default:
+			log_warnx("unknown fastcgi record type %d",
+			    fcgi->fcg_type);
+			evbuffer_drain(src, fcgi->fcg_toread);
+			break;
+		}
+
+		/* Prepare for the next record. */
+		evbuffer_drain(src, fcgi->fcg_padding);
+		fcgi->fcg_want = FCGI_RECORD_HEADER;
+		fcgi->fcg_toread = sizeof(struct fcgi_header);
+	}
+}
+
+void
+fcgi_write(struct bufferevent *bev, void *d)
+{
+	struct fcgi		*fcgi = d;
+	struct evbuffer		*out = EVBUFFER_OUTPUT(bev);
+
+	if (fcgi->fcg_done && EVBUFFER_LENGTH(out) == 0)
+		fcgi_error(bev, EVBUFFER_EOF, fcgi);
+}
+
+void
+fcgi_error(struct bufferevent *bev, short event, void *d)
+{
+	struct fcgi		*fcgi = d;
+	struct env		*env = fcgi->fcg_env;
+	struct client		*clt;
+
+	log_debug("fcgi failure, shutting down connection (ev: %x)",
+	    event);
+	fcgi_inflight_dec(__func__);
+
+	while ((clt = SPLAY_MIN(client_tree, &fcgi->fcg_clients)) != NULL) {
+		SPLAY_REMOVE(client_tree, &fcgi->fcg_clients, clt);
+		server_client_free(clt);
+	}
+
+	SPLAY_REMOVE(fcgi_tree, &env->env_fcgi_socks, fcgi);
+	fcgi_free(fcgi);
+
+	return;
+}
+
+void
+fcgi_free(struct fcgi *fcgi)
+{
+	close(fcgi->fcg_s);
+	bufferevent_free(fcgi->fcg_bev);
+	free(fcgi);
+}
+
+int
+clt_flush(struct client *clt)
+{
+	struct fcgi		*fcgi = clt->clt_fcgi;
+	struct bufferevent	*bev = fcgi->fcg_bev;
+	struct fcgi_header	 hdr;
+
+	if (clt->clt_buflen == 0)
+		return (0);
+
+	memset(&hdr, 0, sizeof(hdr));
+	hdr.version = FCGI_VERSION_1;
+	hdr.type = FCGI_STDOUT;
+	hdr.req_id0 = (clt->clt_id & 0xFF);
+	hdr.req_id1 = (clt->clt_id >> 8);
+	hdr.content_len0 = (clt->clt_buflen & 0xFF);
+	hdr.content_len1 = (clt->clt_buflen >> 8);
+
+	if (bufferevent_write(bev, &hdr, sizeof(hdr)) == -1 ||
+	    bufferevent_write(bev, clt->clt_buf, clt->clt_buflen) == -1) {
+		fcgi_error(bev, EV_WRITE, fcgi);
+		return (-1);
+	}
+
+	clt->clt_buflen = 0;
+
+	return (0);
+}
+
+int
+clt_write(struct client *clt, const uint8_t *buf, size_t len)
+{
+	size_t			 left, copy;
+
+	while (len > 0) {
+		left = sizeof(clt->clt_buf) - clt->clt_buflen;
+		if (left == 0) {
+			if (clt_flush(clt) == -1)
+				return (-1);
+			left = sizeof(clt->clt_buf);
+		}
+
+		copy = MIN(left, len);
+
+		memcpy(&clt->clt_buf[clt->clt_buflen], buf, copy);
+		clt->clt_buflen += copy;
+		buf += copy;
+		len -= copy;
+	}
+
+	return (0);
+}
+
+int
+clt_putc(struct client *clt, char ch)
+{
+	return (clt_write(clt, &ch, 1));
+}
+
+int
+clt_puts(struct client *clt, const char *str)
+{
+	return (clt_write(clt, str, strlen(str)));
+}
+
+int
+clt_write_bufferevent(struct client *clt, struct bufferevent *bev)
+{
+	struct evbuffer		*src = EVBUFFER_INPUT(bev);
+	size_t			 len, left, copy;
+
+	len = EVBUFFER_LENGTH(src);
+	while (len > 0) {
+		left = sizeof(clt->clt_buf) - clt->clt_buflen;
+		if (left == 0) {
+			if (clt_flush(clt) == -1)
+				return (-1);
+			left = sizeof(clt->clt_buf);
+		}
+
+		copy = bufferevent_read(bev, &clt->clt_buf[clt->clt_buflen],
+		    MIN(left, len));
+		clt->clt_buflen += copy;
+
+		len = EVBUFFER_LENGTH(src);
+	}
+
+	return (0);
+}
+
+int
+clt_printf(struct client *clt, const char *fmt, ...)
+{
+	struct fcgi		*fcgi = clt->clt_fcgi;
+	struct bufferevent	*bev = fcgi->fcg_bev;
+	char			*str;
+	va_list			 ap;
+	int			 r;
+
+	va_start(ap, fmt);
+	r = vasprintf(&str, fmt, ap);
+	va_end(ap);
+	if (r == -1) {
+		fcgi_error(bev, EV_WRITE, fcgi);
+		return (-1);
+	}
+
+	r = clt_write(clt, str, r);
+	free(str);
+	return (r);
+}
+
+#if template
+int
+clt_tp_puts(struct template *tp, const char *str)
+{
+	struct client		*clt = tp->tp_arg;
+
+	if (clt_puts(clt, str) == -1)
+		return (-1);
+
+	return (0);
+}
+
+int
+clt_tp_putc(struct template *tp, int c)
+{
+	struct client		*clt = tp->tp_arg;
+
+	if (clt_putc(clt, c) == -1)
+		return (-1);
+
+	return (0);
+}
+#endif
+
+int
+fcgi_cmp(struct fcgi *a, struct fcgi *b)
+{
+	return ((int)a->fcg_id - b->fcg_id);
+}
+
+int
+fcgi_client_cmp(struct client *a, struct client *b)
+{
+	return ((int)a->clt_id - b->clt_id);
+}
+
+int
+accept_reserve(int sockfd, struct sockaddr *addr, socklen_t *addrlen,
+    int reserve, volatile int *counter)
+{
+	int ret;
+
+	if (getdtablecount() + reserve + *counter >= getdtablesize()) {
+		errno = EMFILE;
+		return (-1);
+	}
+
+	if ((ret = accept4(sockfd, addr, addrlen, SOCK_NONBLOCK)) > -1) {
+		(*counter)++;
+		log_debug("%s: inflight incremented, now %d", __func__,
+		    *counter);
+	}
+
+	return (ret);
+}
+
+SPLAY_GENERATE(fcgi_tree, fcgi, fcg_nodes, fcgi_cmp);
+SPLAY_GENERATE(client_tree, client, clt_nodes, fcgi_client_cmp);
blob - /dev/null
blob + c0baa8e37797771914fd6765192f98b8e55961fb (mode 644)
--- /dev/null
+++ keys/Makefile
@@ -0,0 +1,18 @@
+NEXTV =		03
+
+DISTFILES =	Makefile \
+		pkg_fcgi-01.pub \
+		pkg_fcgi-02.pub
+
+all:
+	false
+
+newkey:
+	signify -G -p pkg_fcgi-${NEXTV}.pub -s pkg_fcgi-${NEXTV}.sec
+
+dist: ${DISTFILES}
+	mkdir -p ${DESTDIR}/
+	${INSTALL} -m 0644 ${DISTFILES} ${DESTDIR}/
+
+.PHONY: all dist
+include ../config.mk
blob - /dev/null
blob + 179235943ad862e0426e472d7b0922bf3fef0371 (mode 644)
--- /dev/null
+++ log.c
@@ -0,0 +1,197 @@
+/*	$OpenBSD: log.c,v 1.1 2018/07/10 16:39:54 florian Exp $	*/
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <syslog.h>
+#include <errno.h>
+#include <time.h>
+
+#include "log.h"
+
+static int		 debug;
+static int		 verbose;
+static const char	*log_procname;
+
+void
+log_init(int n_debug, int facility)
+{
+	debug = n_debug;
+	verbose = n_debug;
+	log_procinit(getprogname());
+
+	if (!debug)
+		openlog(getprogname(), LOG_PID | LOG_NDELAY, facility);
+
+	tzset();
+}
+
+void
+log_procinit(const char *procname)
+{
+	if (procname != NULL)
+		log_procname = procname;
+}
+
+void
+log_setverbose(int v)
+{
+	verbose = v;
+}
+
+int
+log_getverbose(void)
+{
+	return (verbose);
+}
+
+void
+logit(int pri, const char *fmt, ...)
+{
+	va_list	ap;
+
+	va_start(ap, fmt);
+	vlog(pri, fmt, ap);
+	va_end(ap);
+}
+
+void
+vlog(int pri, const char *fmt, va_list ap)
+{
+	char	*nfmt;
+	int	 saved_errno = errno;
+
+	if (debug) {
+		/* best effort in out of mem situations */
+		if (asprintf(&nfmt, "%s\n", fmt) == -1) {
+			vfprintf(stderr, fmt, ap);
+			fprintf(stderr, "\n");
+		} else {
+			vfprintf(stderr, nfmt, ap);
+			free(nfmt);
+		}
+		fflush(stderr);
+	} else
+		vsyslog(pri, fmt, ap);
+
+	errno = saved_errno;
+}
+
+void
+log_warn(const char *emsg, ...)
+{
+	char		*nfmt;
+	va_list		 ap;
+	int		 saved_errno = errno;
+
+	/* best effort to even work in out of memory situations */
+	if (emsg == NULL)
+		logit(LOG_ERR, "%s", strerror(saved_errno));
+	else {
+		va_start(ap, emsg);
+
+		if (asprintf(&nfmt, "%s: %s", emsg,
+		    strerror(saved_errno)) == -1) {
+			/* we tried it... */
+			vlog(LOG_ERR, emsg, ap);
+			logit(LOG_ERR, "%s", strerror(saved_errno));
+		} else {
+			vlog(LOG_ERR, nfmt, ap);
+			free(nfmt);
+		}
+		va_end(ap);
+	}
+
+	errno = saved_errno;
+}
+
+void
+log_warnx(const char *emsg, ...)
+{
+	va_list	 ap;
+
+	va_start(ap, emsg);
+	vlog(LOG_ERR, emsg, ap);
+	va_end(ap);
+}
+
+void
+log_info(const char *emsg, ...)
+{
+	va_list	 ap;
+
+	va_start(ap, emsg);
+	vlog(LOG_INFO, emsg, ap);
+	va_end(ap);
+}
+
+void
+log_debug(const char *emsg, ...)
+{
+	va_list	 ap;
+
+	if (verbose) {
+		va_start(ap, emsg);
+		vlog(LOG_DEBUG, emsg, ap);
+		va_end(ap);
+	}
+}
+
+static void
+vfatalc(int code, const char *emsg, va_list ap)
+{
+	static char	s[BUFSIZ];
+	const char	*sep;
+
+	if (emsg != NULL) {
+		(void)vsnprintf(s, sizeof(s), emsg, ap);
+		sep = ": ";
+	} else {
+		s[0] = '\0';
+		sep = "";
+	}
+	if (code)
+		logit(LOG_CRIT, "fatal in %s: %s%s%s",
+		    log_procname, s, sep, strerror(code));
+	else
+		logit(LOG_CRIT, "fatal in %s%s%s", log_procname, sep, s);
+}
+
+void
+fatal(const char *emsg, ...)
+{
+	va_list	ap;
+
+	va_start(ap, emsg);
+	vfatalc(errno, emsg, ap);
+	va_end(ap);
+	exit(1);
+}
+
+void
+fatalx(const char *emsg, ...)
+{
+	va_list	ap;
+
+	va_start(ap, emsg);
+	vfatalc(0, emsg, ap);
+	va_end(ap);
+	exit(1);
+}
blob - /dev/null
blob + 0fa046fc3afbe8c893d40f492481a4d3055dd2bc (mode 644)
--- /dev/null
+++ log.h
@@ -0,0 +1,45 @@
+/*	$OpenBSD: log.h,v 1.2 2021/12/13 18:28:40 deraadt Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef LOG_H
+#define LOG_H
+
+#include <stdarg.h>
+
+void	log_init(int, int);
+void	log_procinit(const char *);
+void	log_setverbose(int);
+int	log_getverbose(void);
+void	log_warn(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+void	log_warnx(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+void	log_info(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+void	log_debug(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+void	logit(int, const char *, ...)
+	    __attribute__((__format__ (printf, 2, 3)));
+void	vlog(int, const char *, va_list)
+	    __attribute__((__format__ (printf, 2, 0)));
+__dead void fatal(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+__dead void fatalx(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+
+#endif /* LOG_H */
blob - /dev/null
blob + 1a72b2d68dfc7f6b91ebb75ab847109230bb5ad2 (mode 644)
--- /dev/null
+++ pkg.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define FD_RESERVE	5
+#define GEMINI_MAXLEN	1025	/* including NUL */
+
+#ifdef DEBUG
+#define DPRINTF		log_debug
+#else
+#define DPRINTF(x...)	do {} while (0)
+#endif
+
+struct bufferevent;
+struct event;
+struct fcgi;
+struct sqlite3;
+struct sqlite3_stmt;
+
+enum {
+	METHOD_UNKNOWN,
+	METHOD_GET,
+	METHOD_POST,
+};
+
+struct client {
+	uint32_t		 clt_id;
+	int			 clt_fd;
+	struct fcgi		*clt_fcgi;
+	char			*clt_server_name;
+	char			*clt_script_name;
+	char			*clt_path_info;
+	char			*clt_query;
+	int			 clt_method;
+#if template
+	struct template		*clt_tp;
+#endif
+	char			 clt_buf[1024];
+	size_t			 clt_buflen;
+
+	SPLAY_ENTRY(client)	 clt_nodes;
+};
+SPLAY_HEAD(client_tree, client);
+
+struct fcgi {
+	uint32_t		 fcg_id;
+	int			 fcg_s;
+	struct client_tree	 fcg_clients;
+	struct bufferevent	*fcg_bev;
+	int			 fcg_toread;
+	int			 fcg_want;
+	int			 fcg_padding;
+	int			 fcg_type;
+	int			 fcg_rec_id;
+	int			 fcg_keep_conn;
+	int			 fcg_done;
+
+	struct env		*fcg_env;
+
+	SPLAY_ENTRY(fcgi)	 fcg_nodes;
+};
+SPLAY_HEAD(fcgi_tree, fcgi);
+
+struct env {
+	int			 env_sockfd;
+	struct event		 env_sockev;
+	struct event		 env_pausev;
+	struct fcgi_tree	 env_fcgi_socks;
+
+	struct sqlite3		*env_db;
+	struct sqlite3_stmt	*env_qsearch;
+	struct sqlite3_stmt	*env_qfullpkgpath;
+	struct sqlite3_stmt	*env_qcats;
+	struct sqlite3_stmt	*env_qbycat;
+};
+
+/* fcgi.c */
+int	fcgi_end_request(struct client *, int);
+int	fcgi_abort_request(struct client *);
+void	fcgi_accept(int, short, void *);
+void	fcgi_read(struct bufferevent *, void *);
+void	fcgi_write(struct bufferevent *, void *);
+void	fcgi_error(struct bufferevent *, short, void *);
+void	fcgi_free(struct fcgi *);
+int	clt_putc(struct client *, char);
+int	clt_puts(struct client *, const char *);
+int	clt_write_bufferevent(struct client *, struct bufferevent *);
+int	clt_flush(struct client *);
+int	clt_write(struct client *, const uint8_t *, size_t);
+int	clt_printf(struct client *, const char *, ...)
+	    __attribute__((__format__(printf, 2, 3)))
+	    __attribute__((__nonnull__(2)));
+#if template
+int	clt_tp_puts(struct template *, const char *);
+int	clt_tp_putc(struct template *, int);
+#endif
+int	fcgi_cmp(struct fcgi *, struct fcgi *);
+int	fcgi_client_cmp(struct client *, struct client *);
+
+/* server.c */
+int	server_main(const char *);
+int	server_handle(struct env *, struct client *);
+void	server_client_free(struct client *);
+
+#if template
+/* ui.tmpl */
+int	tp_home(struct template *);
+#endif
+
+SPLAY_PROTOTYPE(client_tree, client, clt_nodes, fcgi_client_cmp);
+SPLAY_PROTOTYPE(fcgi_tree, fcgi, fcg_nodes, fcgi_cmp);
blob - /dev/null
blob + d70e224c5dc760f294495d3f54a40a8693f56fbc (mode 644)
--- /dev/null
+++ pkg_fcgi.c
@@ -0,0 +1,344 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/tree.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <limits.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "pkg.h"
+
+#ifndef PKG_FCGI_DB
+#define PKG_FCGI_DB "/pkg_fcgi/pkgs.sqlite3"
+#endif
+
+#ifndef PKG_FCGI_SOCK
+#define PKG_FCGI_SOCK "/run/pkg_fcgi.sock"
+#endif
+
+#ifndef PKG_FCGI_USER
+#define PKG_FCGI_USER "www"
+#endif
+
+#define MAX_CHILDREN	32
+
+static const char		*argv0;
+static pid_t			 pids[MAX_CHILDREN];
+static int			 children = 3;
+
+static volatile sig_atomic_t	 got_sigchld;
+
+static void
+handle_sigchld(int sig)
+{
+	int	i, saved_errno;
+
+	if (got_sigchld)
+		return;
+
+	got_sigchld = 1;
+	saved_errno = errno;
+
+	for (i = 0; i < children; ++i)
+		(void) kill(pids[i], SIGTERM);
+
+	errno = saved_errno;
+}
+
+static int
+bind_socket(const char *path, struct passwd *pw)
+{
+	struct sockaddr_un	 sun;
+	int			 fd, old_umask;
+
+	if ((fd = socket(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK, 0)) == -1) {
+		log_warn("%s: socket", __func__);
+		return (-1);
+	}
+
+	memset(&sun, 0, sizeof(sun));
+	sun.sun_family = AF_UNIX;
+
+	if (strlcpy(sun.sun_path, path, sizeof(sun.sun_path)) >=
+	    sizeof(sun.sun_path)) {
+		log_warnx("%s: path too long: %s", __func__, path);
+		close(fd);
+		return (-1);
+	}
+
+	if (unlink(path) == -1 && errno != ENOENT) {
+		log_warn("%s: unlink %s", __func__, path);
+		close(fd);
+		return (-1);
+	}
+
+	old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
+	if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
+		log_warn("%s: bind: %s (%d)", __func__, path, geteuid());
+		close(fd);
+		umask(old_umask);
+		return (-1);
+	}
+	umask(old_umask);
+
+	if (chmod(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) {
+		log_warn("%s: chmod %s", __func__, path);
+		close(fd);
+		(void) unlink(path);
+		return (-1);
+	}
+
+	if (chown(path, pw->pw_uid, pw->pw_gid) == -1) {
+		log_warn("%s: chown %s %s", __func__, pw->pw_name, path);
+		close(fd);
+		(void) unlink(path);
+		return (-1);
+	}
+
+	if (listen(fd, 5) == -1) {
+		log_warn("%s: listen", __func__);
+		close(fd);
+		(void) unlink(path);
+		return (-1);
+	}
+
+	return (fd);
+}
+
+static pid_t
+start_child(const char *root, const char *user, const char *db,
+    int daemonize, int verbose, int fd)
+{
+	char	*argv[10];
+	int	 argc = 0;
+	pid_t	 pid;
+
+	switch (pid = fork()) {
+	case -1:
+		fatal("cannot fork");
+	case 0:
+		break;
+	default:
+		close(fd);
+		return (pid);
+	}
+
+	if (fd != 3) {
+		if (dup2(fd, 3) == -1)
+			fatal("cannot setup imsg fd");
+	} else if (fcntl(fd, F_SETFD, 0) == -1)
+		fatal("cannot setup imsg fd");
+
+	argv[argc++] = (char *)argv0;
+	argv[argc++] = (char *)"-S";
+	argv[argc++] = (char *)"-p"; argv[argc++] = (char *)root;
+	argv[argc++] = (char *)"-u"; argv[argc++] = (char *)user;
+	if (!daemonize)
+		argv[argc++] = (char *)"-d";
+	if (verbose)
+		argv[argc++] = (char *)"-v";
+	argv[argc++] = (char *)db;
+	argv[argc++] = NULL;
+
+	execvp(argv0, argv);
+	fatal("execvp");
+}
+
+static void __dead
+usage(void)
+{
+	fprintf(stderr,
+	    "usage: %s [-dv] [-j n] [-p path] [-s socket] [-u user] [db]\n",
+	    getprogname());
+	exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+	struct stat	 sb;
+	struct passwd	*pw;
+	pid_t		 pid;
+	char		 path[PATH_MAX];
+	const char	*cause;
+	const char	*root = NULL;
+	const char	*sock = PKG_FCGI_SOCK;
+	const char	*user = PKG_FCGI_USER;
+	const char	*db = PKG_FCGI_DB;
+	const char	*errstr;
+	int		 ch, i, daemonize = 1, verbosity = 0;
+	int		 server = 0, fd = -1;
+	int		 status;
+
+	/*
+	 * Ensure we have fds 0-2 open so that we have no issue with
+	 * calling bind_socket before daemon(3).
+	 */
+	for (i = 0; i < 3; ++i) {
+		if (fstat(i, &sb) == -1) {
+			if ((fd = open("/dev/null", O_RDWR)) != -1) {
+				if (dup2(fd, i) == -1)
+					exit(1);
+				if (fd > i)
+					close(fd);
+			} else
+				exit(1);
+		}
+	}
+
+	log_init(1, LOG_DAEMON); /* Log to stderr until daemonized. */
+
+	if ((argv0 = argv[0]) == NULL)
+		fatalx("argv[0] is NULL");
+
+	while ((ch = getopt(argc, argv, "dj:p:Ss:u:v")) != -1) {
+		switch (ch) {
+		case 'd':
+			daemonize = 0;
+			break;
+		case 'j':
+			children = strtonum(optarg, 1, MAX_CHILDREN, &errstr);
+			if (errstr)
+				fatalx("number of children is %s: %s",
+				    errstr, optarg);
+			break;
+		case 'p':
+			root = optarg;
+			break;
+		case 'S':
+			server = 1;
+			break;
+		case 's':
+			sock = optarg;
+			break;
+		case 'u':
+			user = optarg;
+			break;
+		case 'v':
+			verbosity++;
+			break;
+		default:
+			usage();
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc > 1)
+		usage();
+	if (argc == 1)
+		db = argv[0];
+
+	if (geteuid())
+		fatalx("need root privileges");
+
+	pw = getpwnam(user);
+	if (pw == NULL)
+		fatalx("user %s not found", user);
+	if (pw->pw_uid == 0)
+		fatalx("cannot run as %s: must not be the superuser", user);
+
+	if (root == NULL)
+		root = pw->pw_dir;
+
+	if (!server) {
+		int ret;
+
+		ret = snprintf(path, sizeof(path), "%s/%s", root, sock);
+		if (ret < 0 || (size_t)ret >= sizeof(path))
+			fatalx("socket path too long");
+
+		if ((fd = bind_socket(path, pw)) == -1)
+			fatalx("failed to open socket %s", sock);
+
+		for (i = 0; i < children; ++i) {
+			int d;
+
+			if ((d = dup(fd)) == -1)
+				fatalx("dup");
+			pids[i] = start_child(root, user, db,
+			    daemonize, verbosity, d);
+			log_debug("forking child %d (pid %lld)", i,
+			    (long long)pids[i]);
+		}
+
+		signal(SIGCHLD, handle_sigchld);
+	}
+
+	if (chroot(root) == -1)
+		fatal("chroot %s", root);
+	if (chdir("/") == -1)
+		fatal("chdir /");
+
+	if (setgroups(1, &pw->pw_gid) == -1 ||
+	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1 ||
+	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
+		fatal("failed to drop privileges");
+
+	log_init(daemonize ? 0 : 1, LOG_DAEMON);
+	log_setverbose(verbosity);
+
+	if (server)
+		exit(server_main(db));
+
+	if (daemonize && daemon(1, 0) == -1)
+		fatal("daemon");
+
+	if (pledge("stdio proc", NULL) == -1)
+		fatal("pledge");
+
+	for (;;) {
+		do {
+			pid = waitpid(WAIT_ANY, &status, 0);
+		} while (pid != -1 || errno == EINTR);
+
+		if (pid == -1) {
+			if (errno == ECHILD)
+				break;
+			fatal("waitpid failed");
+		}
+
+		if (WIFSIGNALED(status))
+			cause = "was terminated";
+		else if (WIFEXITED(status)) {
+			if (WEXITSTATUS(status) != 0)
+				cause = "exited abnormally";
+			else
+				cause = "exited successfully";
+		} else
+			cause = "died";
+
+		log_warnx("child process %lld %s", (long long)pid, cause);
+	}
+
+	return (1);
+}
blob - /dev/null
blob + 77f8c6984b87b2064b3a8c6431b66eea2ad1c5ad (mode 644)
--- /dev/null
+++ server.c
@@ -0,0 +1,624 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/tree.h>
+
+#include <ctype.h>
+#include <event.h>
+#include <fnmatch.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sqlite3.h>
+
+#include "log.h"
+#include "pkg.h"
+#include "tmpl.h"
+
+#ifndef nitems
+#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
+#endif
+
+char		dbpath[PATH_MAX];
+
+void		server_sig_handler(int, short, void *);
+void		server_open_db(struct env *);
+void		server_close_db(struct env *);
+__dead void	server_shutdown(struct env *);
+int		server_reply(struct client *, int, const char *);
+
+int		route_dispatch(struct env *, struct client *);
+int		route_home(struct env *, struct client *);
+int		route_search(struct env *, struct client *);
+int		route_categories(struct env *, struct client *);
+int		route_listing(struct env *, struct client *);
+int		route_port(struct env *, struct client *);
+
+typedef int (*route_t)(struct env *, struct client *);
+
+static const struct route {
+	const char	*r_path;
+	route_t		 r_fn;
+} routes[] = {
+	{ "/",		route_home },
+	{ "/search",	route_search },
+	{ "/all",	route_categories },
+	{ "/*",		route_port },
+};
+
+void
+server_sig_handler(int sig, short ev, void *arg)
+{
+	struct env	*env = arg;
+
+	/*
+	 * Normal signal handler rules don't apply because libevent
+	 * decouples for us.
+	 */
+
+	switch (sig) {
+	case SIGHUP:
+		log_info("re-opening the db");
+		server_close_db(env);
+		server_open_db(env);
+		break;
+	case SIGTERM:
+	case SIGINT:
+		server_shutdown(env);
+		break;
+	default:
+		fatalx("unexpected signal %d", sig);
+	}
+}
+
+static inline void
+loadstmt(sqlite3 *db, sqlite3_stmt **stmt, const char *sql)
+{
+	int		 err;
+
+	err = sqlite3_prepare_v2(db, sql, -1, stmt, NULL);
+	if (err != SQLITE_OK)
+		fatalx("failed prepare statement \"%s\": %s",
+		    sql, sqlite3_errstr(err));
+}
+
+void
+server_open_db(struct env *env)
+{
+	int		 err;
+
+	err = sqlite3_open_v2(dbpath, &env->env_db,
+	    SQLITE_OPEN_READONLY, NULL);
+	if (err != SQLITE_OK)
+		fatalx("can't open database %s: %s", dbpath,
+		    sqlite3_errmsg(env->env_db));
+
+	/* load prepared statements */
+	loadstmt(env->env_db, &env->env_qsearch,
+	    "select webpkg_fts.pkgstem, webpkg_fts.comment, paths.fullpkgpath"
+	    " from webpkg_fts"
+	    " join _ports p on p.fullpkgpath = webpkg_fts.id"
+	    " join _paths paths on paths.id = webpkg_fts.id"
+	    " where webpkg_fts match ?"
+	    " order by bm25(webpkg_fts)");
+
+	loadstmt(env->env_db, &env->env_qfullpkgpath,
+	    "select p.fullpkgpath, pp.pkgstem, pp.comment, pp.pkgname,"
+	    "       d.value, e.value, r.value, pp.homepage"
+	    " from _paths p"
+	    " join _descr d on d.fullpkgpath = p.id"
+	    " join _ports pp on pp.fullpkgpath = p.id"
+	    " join _email e on e.keyref = pp.maintainer"
+	    " left join _readme r on r.fullpkgpath = p.id"
+	    " where p.fullpkgpath = ?");
+
+	loadstmt(env->env_db, &env->env_qcats,
+	    "select distinct value from categories order by value");
+
+	loadstmt(env->env_db, &env->env_qbycat,
+	    "select fullpkgpath from categories where value = ?"
+	    " order by fullpkgpath");
+}
+
+void
+server_close_db(struct env *env)
+{
+	int		 err;
+
+	sqlite3_finalize(env->env_qsearch);
+	sqlite3_finalize(env->env_qfullpkgpath);
+	sqlite3_finalize(env->env_qcats);
+	sqlite3_finalize(env->env_qbycat);
+
+	if ((err = sqlite3_close(env->env_db)) != SQLITE_OK)
+		log_warnx("sqlite3_close %s", sqlite3_errstr(err));
+}
+
+int
+server_main(const char *db)
+{
+	struct env	 env;
+	struct event	 sighup;
+	struct event	 sigint;
+	struct event	 sigterm;
+
+	signal(SIGPIPE, SIG_IGN);
+
+	memset(&env, 0, sizeof(env));
+
+	if (pledge("stdio rpath flock unix", NULL) == -1)
+		fatal("pledge");
+
+	if (realpath(db, dbpath) == NULL)
+		fatal("realpath %s", db);
+
+	server_open_db(&env);
+
+	event_init();
+
+	env.env_sockfd = 3;
+
+	event_set(&env.env_sockev, env.env_sockfd, EV_READ | EV_PERSIST,
+	    fcgi_accept, &env);
+	event_add(&env.env_sockev, NULL);
+
+	evtimer_set(&env.env_pausev, fcgi_accept, &env);
+
+	signal_set(&sighup, SIGHUP, server_sig_handler, &env);
+	signal_set(&sigint, SIGINT, server_sig_handler, &env);
+	signal_set(&sigterm, SIGTERM, server_sig_handler, &env);
+
+	signal_add(&sighup, NULL);
+	signal_add(&sigint, NULL);
+	signal_add(&sigterm, NULL);
+
+	log_info("ready");
+	event_dispatch();
+
+	server_shutdown(&env);
+}
+
+void __dead
+server_shutdown(struct env *env)
+{
+	log_info("shutting down");
+	server_close_db(env);
+	exit(0);
+}
+
+int
+server_reply(struct client *clt, int status, const char *ctype)
+{
+	if (clt_printf(clt, "%02d %s\r\n", status, ctype) == -1)
+		return (-1);
+	return (0);
+}
+
+int
+server_handle(struct env *env, struct client *clt)
+{
+	return (route_dispatch(env, clt));
+}
+
+void
+server_client_free(struct client *clt)
+{
+#if template
+	template_free(clt->clt_tp);
+#endif
+	free(clt->clt_server_name);
+	free(clt->clt_script_name);
+	free(clt->clt_path_info);
+	free(clt->clt_query);
+	free(clt);
+}
+
+static inline int
+unquote(char *str)
+{
+	char		*p, *q;
+	char		 hex[3];
+	unsigned long	 x;
+
+	hex[2] = '\0';
+	p = q = str;
+	while (*p) {
+		switch (*p) {
+		case '%':
+			if (!isxdigit((unsigned char)p[1]) ||
+			    !isxdigit((unsigned char)p[2]) ||
+			    (p[1] == '0' && p[2] == '0'))
+				return (-1);
+
+			hex[0] = p[1];
+			hex[1] = p[2];
+
+			x = strtoul(hex, NULL, 16);
+			*q++ = (char)x;
+			p += 3;
+			break;
+		default:
+			*q++ = *p++;
+			break;
+		}
+	}
+	*q = '\0';
+	return (0);
+}
+
+static inline int
+fts_escape(const char *p, char *buf, size_t bufsize)
+{
+	char			*q;
+
+	/*
+	 * split p into words and quote them into buf.
+	 * quoting means wrapping each word into "..." and
+	 * replace every " with "".
+	 * i.e. 'C++ "framework"' -> '"C++" """framework"""'
+	 * flatting all the whitespaces seems fine too.
+	 */
+
+	q = buf;
+	while (bufsize != 0) {
+		p += strspn(p, " \f\n\r\t\v");
+		if (*p == '\0')
+			break;
+
+		*q++ = '"';
+		bufsize--;
+		while (*p && !isspace((unsigned char)*p) && bufsize != 0) {
+			if (*p == '"') { /* double the quote character */
+				*q++ = '"';
+				bufsize--;
+				if (bufsize == 0)
+					break;
+			}
+			*q++ = *p++;
+			bufsize--;
+		}
+
+		if (bufsize < 2)
+			break;
+		*q++ = '"';
+		*q++ = ' ';
+		bufsize -= 2;
+	}
+	if ((*p == '\0') && bufsize != 0) {
+		*q = '\0';
+		return (0);
+	}
+
+	return (-1);
+}
+
+int
+route_dispatch(struct env *env, struct client *clt)
+{
+	const struct route	*r;
+	size_t			 i;
+
+	log_debug("path info is %s", clt->clt_path_info);
+
+	for (i = 0; i < nitems(routes); ++i) {
+		r = &routes[i];
+
+		if (fnmatch(r->r_path, clt->clt_path_info, 0) != 0)
+			continue;
+		return (r->r_fn(env, clt));
+	}
+
+	if (server_reply(clt, 51, "not found") == -1)
+		return (-1);
+	return (fcgi_end_request(clt, 0));
+}
+
+int
+route_home(struct env *env, struct client *clt)
+{
+	if (server_reply(clt, 20, "text/gemini") == -1)
+		return (-1);
+
+#if 1
+	if (clt_printf(clt, "# pkg_fcgi\n\n") == -1)
+		return (-1);
+	if (clt_printf(clt, "Welcome to pkg_fcgi, the Gemini interface "
+	    "for the OpenBSD ports collection.\n\n") == -1)
+		return (-1);
+	if (clt_printf(clt, "=> /search Search for a package\n") == -1)
+		return (-1);
+	if (clt_printf(clt, "=> /all/ All categories\n") == -1)
+		return (-1);
+	if (clt_printf(clt, "\n") == -1)
+		return (-1);
+	if (clt_printf(clt, "What you search will be matched against the "
+	    "package name (pkgstem), comment, DESCR and maintainer.\n") == -1)
+		return (-1);
+#else
+	if (tp_home(clt->clt_tp) == -1)
+		return (-1);
+#endif
+
+	return (fcgi_end_request(clt, 0));
+}
+
+int
+route_search(struct env *env, struct client *clt)
+{
+	const char	*stem, *comment, *fullpkgpath;
+	char		*query = clt->clt_query;
+	char		 equery[1024];
+	int		 err;
+	int		 found = 0;
+
+	if (query == NULL || *query == '\0') {
+		if (server_reply(clt, 10, "search for a package") == -1)
+			return (-1);
+		return (fcgi_end_request(clt, 0));
+	}
+
+	if (unquote(query) == -1 ||
+	    fts_escape(query, equery, sizeof(equery)) == -1) {
+		if (server_reply(clt, 59, "bad request") == -1)
+			return (-1);
+		return (fcgi_end_request(clt, 1));
+	}
+
+	log_debug("searching for %s", equery);
+
+	err = sqlite3_bind_text(env->env_qsearch, 1, equery, -1, NULL);
+	if (err != SQLITE_OK) {
+		log_warnx("%s: sqlite3_bind_text \"%s\": %s", __func__,
+		    query, sqlite3_errstr(err));
+		sqlite3_reset(env->env_qsearch);
+
+		if (server_reply(clt, 42, "internal error") == -1)
+			return (-1);
+		return (fcgi_end_request(clt, 1));
+	}
+
+	if (server_reply(clt, 20, "text/gemini") == -1)
+		goto err;
+
+	if (clt_printf(clt, "# search results for %s\n\n", query) == -1)
+		goto err;
+
+	for (;;) {
+		err = sqlite3_step(env->env_qsearch);
+		if (err == SQLITE_DONE)
+			break;
+		if (err != SQLITE_ROW) {
+			log_warnx("%s: sqlite3_step %s", __func__,
+			    sqlite3_errstr(err));
+			break;
+		}
+		found = 1;
+
+		stem = sqlite3_column_text(env->env_qsearch, 0);
+		comment = sqlite3_column_text(env->env_qsearch, 1);
+		fullpkgpath = sqlite3_column_text(env->env_qsearch, 2);
+
+		/* XXX fix URL */
+		if (clt_printf(clt, "=> /%s %s: %s\n", fullpkgpath,
+		    stem, comment) == -1)
+			goto err;
+	}
+
+	sqlite3_reset(env->env_qsearch);
+
+	if (!found && clt_printf(clt, "No ports found\n") == -1)
+		return (-1);
+
+	return (fcgi_end_request(clt, 0));
+
+ err:
+	sqlite3_reset(env->env_qsearch);
+	return (-1);
+}
+
+int
+route_categories(struct env *env, struct client *clt)
+{
+	const char	*fullpkgpath;
+	int		 err;
+
+	if (server_reply(clt, 20, "text/gemini") == -1)
+		return (-1);
+	if (clt_printf(clt, "# list of all categories\n") == -1)
+		return (-1);
+
+	if (clt_puts(clt, "\n") == -1)
+		return (-1);
+
+	for (;;) {
+		err = sqlite3_step(env->env_qcats);
+		if (err == SQLITE_DONE)
+			break;
+		if (err != SQLITE_ROW) {
+			log_warnx("%s: sqlite3_step %s", __func__,
+			    sqlite3_errstr(err));
+			break;
+		}
+
+		fullpkgpath = sqlite3_column_text(env->env_qcats, 0);
+
+		/* XXX fix URL! */
+		if (clt_printf(clt, "=> /%s %s\n", fullpkgpath, fullpkgpath)
+		    == -1) {
+			sqlite3_reset(env->env_qcats);
+			return (-1);
+		}
+	}
+
+	sqlite3_reset(env->env_qcats);
+	return (fcgi_end_request(clt, 0));
+}
+
+int
+route_listing(struct env *env, struct client *clt)
+{
+	char		 buf[128], *s;
+	const char	*path = clt->clt_path_info + 1;
+	const char	*fullpkgpath;
+	int		 err;
+
+	strlcpy(buf, path, sizeof(buf));
+	while ((s = strrchr(buf, '/')) != NULL)
+		*s = '\0';
+
+	err = sqlite3_bind_text(env->env_qbycat, 1, buf, -1, NULL);
+	if (err != SQLITE_OK) {
+		log_warnx("%s: sqlite3_bind_text \"%s\": %s", __func__,
+		    path, sqlite3_errstr(err));
+		sqlite3_reset(env->env_qbycat);
+
+		if (server_reply(clt, 42, "internal error") == -1)
+			return (-1);
+		return (fcgi_end_request(clt, 1));
+	}
+
+	if (server_reply(clt, 20, "text/gemini") == -1)
+		goto err;
+
+	if (clt_printf(clt, "# port(s) under %s\n\n", path) == -1)
+		goto err;
+
+	for (;;) {
+		err = sqlite3_step(env->env_qbycat);
+		if (err == SQLITE_DONE)
+			break;
+		if (err != SQLITE_ROW) {
+			log_warnx("%s: sqlite3_step %s", __func__,
+			    sqlite3_errstr(err));
+			break;
+		}
+
+		fullpkgpath = sqlite3_column_text(env->env_qbycat, 0);
+
+		/* XXX fix URL */
+		if (clt_printf(clt, "=> /%s %s\n", fullpkgpath, fullpkgpath)
+		    == -1) {
+			sqlite3_reset(env->env_qbycat);
+			return (-1);
+		}
+	}
+
+	sqlite3_reset(env->env_qbycat);
+	return (fcgi_end_request(clt, 0));
+
+ err:
+	sqlite3_reset(env->env_qbycat);
+	return (-1);
+}
+
+int
+route_port(struct env *env, struct client *clt)
+{
+	const char	*path = clt->clt_path_info + 1;
+	const char	*fullpkgpath, *stem, *pkgname, *descr;
+	const char	*comment, *maintainer, *readme, *www;
+	const char	*version;
+	int		 err;
+
+	err = sqlite3_bind_text(env->env_qfullpkgpath, 1, path, -1, NULL);
+	if (err != SQLITE_OK) {
+		log_warnx("%s: sqlite3_bind_text \"%s\": %s", __func__,
+		    path, sqlite3_errstr(err));
+		sqlite3_reset(env->env_qfullpkgpath);
+
+		if (server_reply(clt, 42, "internal error") == -1)
+			return (-1);
+		return (fcgi_end_request(clt, 1));
+	}
+
+	err = sqlite3_step(env->env_qfullpkgpath);
+	if (err == SQLITE_DONE) {
+		/* No rows, retry as a category */
+		sqlite3_reset(env->env_qfullpkgpath);
+		return (route_listing(env, clt));
+	}
+
+	if (err != SQLITE_ROW) {
+		log_warnx("%s: sqlite3_step %s", __func__,
+		    sqlite3_errstr(err));
+		if (server_reply(clt, 42, "internal error") == -1)
+			goto err;
+		goto done;
+	}
+
+	fullpkgpath = sqlite3_column_text(env->env_qfullpkgpath, 0);
+	stem = sqlite3_column_text(env->env_qfullpkgpath, 1);
+	comment = sqlite3_column_text(env->env_qfullpkgpath, 2);
+	pkgname = sqlite3_column_text(env->env_qfullpkgpath, 3);
+	descr = sqlite3_column_text(env->env_qfullpkgpath, 4);
+	maintainer = sqlite3_column_text(env->env_qfullpkgpath, 5);
+	readme = sqlite3_column_text(env->env_qfullpkgpath, 6);
+	www = sqlite3_column_text(env->env_qfullpkgpath, 7);
+
+	if ((version = strrchr(pkgname, '-')) != NULL)
+		version++;
+	else
+		version = " unknown";
+
+	if (server_reply(clt, 20, "text/gemini") == -1)
+		goto err;
+
+	if (clt_printf(clt, "# %s v%s\n", path, version) == -1 ||
+	    clt_puts(clt, "\n") == -1 ||
+	    clt_printf(clt, "``` Command to install the package %s\n",
+	    stem) == -1 ||
+	    clt_printf(clt, "# pkg_add %s\n", stem) == -1 ||
+	    clt_printf(clt, "```\n") == -1 ||
+	    clt_printf(clt, "\n") == -1 ||
+	    clt_printf(clt, "> %s\n", comment) == -1 ||
+	    clt_printf(clt, "\n") == -1 ||
+	    clt_printf(clt, "=> https://cvsweb.openbsd.org/ports/%s "
+	    "CVS Web\n", fullpkgpath) == -1)
+		goto err;
+
+	if (www && *www != '\0' &&
+	    clt_printf(clt, "=> %s Port Homepage (WWW)\n", www) == -1)
+		goto err;
+
+	if (clt_printf(clt, "\n") == -1 ||
+	    clt_printf(clt, "Maintainer: %s\n", maintainer) == -1 ||
+	    clt_printf(clt, "## Description\n") == -1 ||
+	    clt_printf(clt, "``` %s description\n", stem) == -1 ||
+	    clt_puts(clt, descr) == -1 ||
+	    clt_puts(clt, "```\n") == -1 ||
+	    clt_puts(clt, "\n") == -1)
+		goto err;
+
+	if (readme && *readme != '\0') {
+		if (clt_puts(clt, "## Readme\n") == -1 ||
+		    clt_puts(clt, "\n") == -1 ||
+		    clt_printf(clt, "``` README for %s\n", stem) == -1 ||
+		    clt_puts(clt, readme) == -1 ||
+		    clt_puts(clt, "\n") == -1)
+			goto err;
+	}
+
+ done:
+	sqlite3_reset(env->env_qfullpkgpath);
+	return (fcgi_end_request(clt, 0));
+
+ err:
+	sqlite3_reset(env->env_qfullpkgpath);
+	return (-1);
+}
blob - /dev/null
blob + e93001fad9cec3417a04f6241f9f121d3b3ae9b0 (mode 644)
--- /dev/null
+++ template/Makefile
@@ -0,0 +1,33 @@
+PROG =		template
+SRCS =		template.c y.tab.c
+OBJS =		${SRCS:.c=.o} ${COBJS}
+
+DISTFILES =	Makefile \
+		configure \
+		parse.y \
+		template.c \
+		tmpl.c \
+		tmpl.h \
+		y.tab.c
+
+all: ${PROG}
+
+include config.mk
+
+${PROG}: ${OBJS}
+	${CC} -o $@ ${OBJS}
+
+y.tab.c: parse.y
+	${YACC} -b y parse.y
+
+clean:
+	rm -rf *.o y.tab.* ${PROG}
+
+distclean: clean
+	rm -f config.h config.h.old config.mk config.log config.log.old
+
+dist: ${DISTFILES}
+	mkdir -p ${DESTDIR}/
+	${INSTALL} -m 0644 ${DISTFILES} ${DESTDIR}/
+
+.PHONY: all clean distclean dist
blob - /dev/null
blob + db1e39d22231e7c7f03b56af10ee2aac607e7a15 (mode 755)
--- /dev/null
+++ template/configure
@@ -0,0 +1,233 @@
+#!/bin/sh
+#
+# Copyright (c) 2014, 2015, 2016 Ingo Schwarze <schwarze@openbsd.org>
+# Copyright (c) 2017, 2018 Kristaps Dzonsons <kristaps@bsd.lv>
+# Copyright (c) 2022 Omar Polo <op@omarpolo.com>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+set -e
+
+if command -v yacc 2>/dev/null >&2; then
+	YACC=yacc
+elif command -v bison 2>/dev/null >&2; then
+	YACC=bison
+else
+	YACC=
+fi
+
+while [ $# -gt 0 ]; do
+	key="${1%%=*}"
+	val="${1#*=}"
+
+	if [ "$1" = "$key" ]; then
+		echo "$0: invalid key-value: $1" >&2
+		exit 1
+	fi
+
+	case "$key" in
+	CC)
+		CC="$val" ;;
+	CFLAGS)
+		CFLAGS="$val" ;;
+	YACC)
+		YACC="$val" ;;
+	esac
+
+	shift
+done
+
+if [ -z "$YACC" ]; then
+	echo "fatal: can't find yacc or bison." >&2
+	exit 1
+fi
+
+[ -w config.log ] && mv config.log config.log.old
+
+exec 3> config.log
+echo "config.log: writing..."
+
+CC="${CC:-cc}"
+CFLAGS="${CFLAGS:--O2 -pipe} -I."
+LIBS="${LIBS:-}"
+LDFLAGS="${LDFLAGS:-}"
+
+COMPATS=
+
+HAVE_ERR=
+HAVE_FREEZERO=
+HAVE_GETEXECNAME=
+HAVE_GETPROGNAME=
+HAVE_PLEDGE=
+HAVE_REALLOCARRAY=
+HAVE_STRLCAT=
+HAVE_STRLCPY=
+HAVE_STRTONUM=
+HAVE_SYS_QUEUE=
+HAVE_UNVEIL=
+HAVE_VASPRINTF=
+HAVE___PROGNAME=
+
+# singletest message var extra-cflags extra-libs
+singletest() {
+	cat >&3 <<EOF
+${1}: testing...
+$CC ./../tests/${1}.c -Werror $3 -o test-$1 $LDFLAGS $4
+EOF
+	if $CC ./../tests/${1}.c -Werror $3 -o test-$1 $LDFLAGS $4 >&3 2>&3; then
+		rm -f test-${1} test-${1}.d
+
+		if [ -n "$3" ]; then
+			echo "${1}: $CC $3 succeeded" >&3
+			echo "${1} ($3): yes"
+		elif [ -n "${4}" ]; then
+			echo "${1}: $CC $4 succeeded" >&3
+			echo "${1} ($4): yes"
+		else
+			echo "${1}: $CC succeeded" >&3
+			echo "${1}: yes"
+		fi
+		echo >&3
+
+		return 0
+	fi
+
+	if [ -n "$3" ]; then
+		echo "${1}: $CC $3 failed with $?" >&3
+		echo "${1} ($3): no"
+	elif [ -n "${4}" ]; then
+		echo "${1}: $CC $4 failed with $?" >&3
+		echo "${1} ($4): no"
+	else
+		echo "${1}: $CC failed with $?" >&3
+		echo "${1}: no"
+	fi
+	echo >&3
+
+	return 1
+}
+
+# runtest message var extra-cflags extra-libs pkgconfig-name
+runtest() {
+	if singletest "$1" "$2" "" ""; then
+		eval HAVE_${2}=1
+		return 0
+	fi
+
+	if [ -n "$3" -o -n "$4" ]; then
+		echo "retrying with ${3+$3 }$4" >&3
+		if singletest "$1" "$2" "$3" "$4"; then
+			if [ -n "$3" ]; then
+				CFLAGS="$CFLAGS $3"
+			fi
+			if [ -n "${4}" ]; then
+				LIBS="$LIBS $4"
+			fi
+			eval HAVE_${2}=1
+			return 0
+		fi
+	fi
+
+	if [ -f ./../compat/${1}.c ]; then
+		COMPATS="${1}.o $COMPATS"
+	fi
+
+	eval HAVE_${2}=0
+	return 1
+}
+
+if runtest MMD _MMD -MMD >/dev/null; then
+	echo "adding -MMD to CFLAGS" >&2
+	echo "adding -MMD to CFLAGS" >&3
+fi
+
+runtest err		ERR					|| true
+runtest freezero	FREEZERO				|| true
+runtest getexecname	GETEXECNAME				|| true
+runtest getprogname	GETPROGNAME				|| true
+runtest pledge		PLEDGE					|| true
+runtest reallocarray	REALLOCARRAY -D_OPENBSD_SOURCE		|| true
+runtest strlcat		STRLCAT					|| true
+runtest strlcpy		STRLCPY					|| true
+runtest strtonum	STRTONUM				|| true
+runtest sys_queue	SYS_QUEUE				|| true
+runtest unveil		UNVEIL					|| true
+runtest vasprintf	VASPRINTF -D_GNU_SOURCE			|| true
+runtest __progname	__PROGNAME				|| true
+
+if [ "${HAVE_SYS_QUEUE}" -eq 0 ]; then
+	CFLAGS="-I ./../compat/sys ${CFLAGS}"
+fi
+
+if [ -n "${COMPATS}" ]; then
+	CFLAGS="-I ./../compat/ ${CFLAGS}"
+fi
+
+exec > config.h
+echo "config.h: writing..." >&2
+
+cat <<EOF
+#ifndef CONFIG_H
+#define CONFIG_H
+
+#ifdef __cplusplus
+# error "Do not use C++: this is a C application."
+#endif
+
+#define HAVE_ERR		${HAVE_ERR}
+#define HAVE_FREEZERO		${HAVE_FREEZERO}
+#define HAVE_GETEXECNAME	${HAVE_GETEXECNAME}
+#define HAVE_GETPROGNAME	${HAVE_GETPROGNAME}
+#define HAVE_PLEDGE		${HAVE_PLEDGE}
+#define HAVE_REALLOCARRAY	${HAVE_REALLOCARRAY}
+#define HAVE_STRLCAT		${HAVE_STRLCAT}
+#define HAVE_STRLCPY		${HAVE_STRLCPY}
+#define HAVE_STRTONUM		${HAVE_STRTONUM}
+#define HAVE_SYS_QUEUE		${HAVE_SYS_QUEUE}
+#define HAVE_UNVEIL		${HAVE_UNVEIL}
+#define HAVE_VASPRINTF		${HAVE_VASPRINTF}
+#define HAVE___PROGNAME		${HAVE___PROGNAME}
+
+#endif
+EOF
+
+exec > config.mk
+echo "config.mk: writing..." >&2
+
+cat <<EOF
+CC =		${CC}
+CFLAGS =	${CFLAGS}
+LIBS =		${LIBS}
+LDFLAGS =	${LDFLAGS}
+YACC =		${YACC}
+
+COBJS =	${COMPATS}
+
+INSTALL=	install
+INSTALL_PROGRAM=\${INSTALL} -m 0555
+INSTALL_LIB=	\${INSTALL} -m 0444
+INSTALL_MAN=	\${INSTALL} -m 0444
+INSTALL_DATA=	\${INSTALL} -m 0444
+
+EOF
+
+for c in ${COMPATS}; do
+	src="../compat/${c%.o}.c"
+	cat <<EOF
+$c: $src
+	\${CC} \${CFLAGS} -c $src -o \$@
+EOF
+done
+
+echo "done!" >&2
+echo >&2
blob - /dev/null
blob + 7f67c56c3f92aa2c93b0deefc10c48f978e63b9f (mode 644)
--- /dev/null
+++ template/foo.tmpl
@@ -0,0 +1,3 @@
+{{define x}}
+{{ " | " }}
+{{ end }}
blob - /dev/null
blob + 98ddc5d32e22f76091257f6a124fd957744c9974 (mode 644)
--- /dev/null
+++ template/parse.y
@@ -0,0 +1,760 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@openbsd.org>
+ * Copyright (c) 2007-2016 Reyk Floeter <reyk@openbsd.org>
+ * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
+ * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
+ * Copyright (c) 2001 Markus Friedl.  All rights reserved.
+ * Copyright (c) 2001 Daniel Hartmeier.  All rights reserved.
+ * Copyright (c) 2001 Theo de Raadt.  All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+%{
+
+#include <sys/queue.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+#define YYERROR_VERBOSE 1
+
+#ifndef nitems
+#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
+#endif
+
+TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
+static struct file {
+	TAILQ_ENTRY(file)	 entry;
+	FILE			*stream;
+	char			*name;
+	size_t			 ungetpos;
+	size_t			 ungetsize;
+	unsigned char		*ungetbuf;
+	int			 eof_reached;
+	int			 lineno;
+	int			 errors;
+} *file, *topfile;
+int		 parse(FILE *, const char *);
+struct file	*pushfile(const char *, int);
+int		 popfile(void);
+int		 yyparse(void);
+int		 yylex(void);
+int		 yyerror(const char *, ...)
+    __attribute__((__format__ (printf, 1, 2)))
+    __attribute__((__nonnull__ (1)));
+int		 kw_cmp(const void *, const void *);
+int		 lookup(char *);
+int		 igetc(void);
+int		 lgetc(int);
+void		 lungetc(int);
+int		 findeol(void);
+
+void		 dbg(void);
+void		 printq(const char *);
+
+extern int	 nodebug;
+
+static FILE	*fp;
+
+static int	 block;
+static int	 in_define;
+static int	 errors;
+static int	 lastline = -1;
+
+typedef struct {
+	union {
+		char		*string;
+	} v;
+	int lineno;
+} YYSTYPE;
+
+%}
+
+%token	DEFINE ELSE END ERROR FINALLY FOR IF INCLUDE PRINTF
+%token	RENDER TQFOREACH UNSAFE URLESCAPE WHILE
+%token	<v.string>	STRING
+%type	<v.string>	string
+%type	<v.string>	stringy
+
+%%
+
+grammar		: /* empty */
+		| grammar include
+		| grammar verbatim
+		| grammar block
+		| grammar error		{ file->errors++; }
+		;
+
+include		: INCLUDE STRING {
+			struct file	*nfile;
+
+			if ((nfile = pushfile($2, 0)) == NULL) {
+				yyerror("failed to include file %s", $2);
+				free($2);
+				YYERROR;
+			}
+			free($2);
+
+			file = nfile;
+			lungetc('\n');
+		}
+		;
+
+verbatim	: '!' verbatim1 '!' {
+			if (in_define) {
+				/* TODO: check template status and exit in case */
+			}
+		}
+		;
+
+verbatim1	: /* empty */
+		| verbatim1 STRING {
+			if (*$2 != '\0') {
+				dbg();
+				fprintf(fp, "%s\n", $2);
+			}
+			free($2);
+		}
+		;
+
+verbatims	: /* empty */
+		| verbatims verbatim
+		;
+
+raw		: STRING {
+			dbg();
+			fprintf(fp, "if ((tp_ret = tp->tp_puts(tp, ");
+			printq($1);
+			fputs(")) == -1) goto err;\n", fp);
+
+			free($1);
+		}
+		;
+
+block		: define body end {
+			fputs("err:\n", fp);
+			fputs("return tp_ret;\n", fp);
+			fputs("}\n", fp);
+			in_define = 0;
+		}
+		| define body finally end {
+			fputs("return tp_ret;\n", fp);
+			fputs("}\n", fp);
+			in_define = 0;
+		}
+		;
+
+define		: '{' DEFINE string '}' {
+			in_define = 1;
+
+			dbg();
+			fprintf(fp, "int\n%s\n{\n", $3);
+			fputs("int tp_ret = 0;\n", fp);
+
+			free($3);
+		}
+		;
+
+body		: /* empty */
+		| body verbatim
+		| body raw
+		| body special
+		;
+
+special		: '{' RENDER string '}' {
+			dbg();
+			fprintf(fp, "if ((tp_ret = %s) == -1) goto err;\n",
+			    $3);
+			free($3);
+		}
+		| printf
+		| if body endif			{ fputs("}\n", fp); }
+		| loop
+		| '{' string '|' UNSAFE '}' {
+			dbg();
+			fprintf(fp,
+			    "if ((tp_ret = tp->tp_puts(tp, %s)) == -1)\n",
+			    $2);
+			fputs("goto err;\n", fp);
+			free($2);
+		}
+		| '{' string '|' URLESCAPE '}' {
+			dbg();
+			fprintf(fp,
+			    "if ((tp_ret = tp_urlescape(tp, %s)) == -1)\n",
+			    $2);
+			fputs("goto err;\n", fp);
+			free($2);
+		}
+		| '{' string '}' {
+			dbg();
+			fprintf(fp,
+			    "if ((tp_ret = tp->tp_escape(tp, %s)) == -1)\n",
+			    $2);
+			fputs("goto err;\n", fp);
+			free($2);
+		}
+		;
+
+printf		: '{' PRINTF {
+			dbg();
+			fprintf(fp, "if (asprintf(&tp->tp_tmp, ");
+		} printfargs '}' {
+			fputs(") == -1)\n", fp);
+			fputs("goto err;\n", fp);
+			fputs("if ((tp_ret = tp->tp_escape(tp, tp->tp_tmp)) "
+			    "== -1)\n", fp);
+			fputs("goto err;\n", fp);
+			fputs("free(tp->tp_tmp);\n", fp);
+			fputs("tp->tp_tmp = NULL;\n", fp);
+		}
+		;
+
+printfargs	: /* empty */
+		| printfargs STRING {
+			fprintf(fp, " %s", $2);
+			free($2);
+		}
+		;
+
+if		: '{' IF stringy '}' {
+			dbg();
+			fprintf(fp, "if (%s) {\n", $3);
+			free($3);
+		}
+		;
+
+endif		: end
+		| else body end
+		| elsif body endif
+		;
+
+elsif		: '{' ELSE IF stringy '}' {
+			dbg();
+			fprintf(fp, "} else if (%s) {\n", $4);
+			free($4);
+		}
+		;
+
+else		: '{' ELSE '}' {
+			dbg();
+			fputs("} else {\n", fp);
+		}
+		;
+
+loop		: '{' FOR stringy '}' {
+			fprintf(fp, "for (%s) {\n", $3);
+			free($3);
+		} body end {
+			fputs("}\n", fp);
+		}
+		| '{' TQFOREACH STRING STRING STRING '}' {
+			fprintf(fp, "TAILQ_FOREACH(%s, %s, %s) {\n",
+			    $3, $4, $5);
+			free($3);
+			free($4);
+			free($5);
+		} body end {
+			fputs("}\n", fp);
+		}
+		| '{' WHILE stringy '}' {
+			fprintf(fp, "while (%s) {\n", $3);
+			free($3);
+		} body end {
+			fputs("}\n", fp);
+		}
+		;
+
+end		: '{' END '}'
+		;
+
+finally		: '{' FINALLY '}' {
+			dbg();
+			fputs("err:\n", fp);
+		} verbatims
+		;
+
+string		: STRING string {
+			if (asprintf(&$$, "%s %s", $1, $2) == -1)
+				err(1, "asprintf");
+			free($1);
+			free($2);
+		}
+		| STRING
+		;
+
+stringy		: STRING
+		| STRING stringy {
+			if (asprintf(&$$, "%s %s", $1, $2) == -1)
+				err(1, "asprintf");
+			free($1);
+			free($2);
+		}
+		| '|' stringy {
+			if (asprintf(&$$, "|%s", $2) == -1)
+				err(1, "asprintf");
+			free($2);
+		}
+		;
+
+%%
+
+struct keywords {
+	const char	*k_name;
+	int		 k_val;
+};
+
+int
+yyerror(const char *fmt, ...)
+{
+	va_list	 ap;
+	char	*msg;
+
+	file->errors++;
+	va_start(ap, fmt);
+	if (vasprintf(&msg, fmt, ap) == -1)
+		err(1, "yyerror vasprintf");
+	va_end(ap);
+	fprintf(stderr, "%s:%d: %s\n", file->name, yylval.lineno, msg);
+	free(msg);
+	return (0);
+}
+
+int
+kw_cmp(const void *k, const void *e)
+{
+	return (strcmp(k, ((const struct keywords *)e)->k_name));
+}
+
+int
+lookup(char *s)
+{
+	/* this has to be sorted always */
+	static const struct keywords keywords[] = {
+		{ "define",		DEFINE },
+		{ "else",		ELSE },
+		{ "end",		END },
+		{ "finally",		FINALLY },
+		{ "for",		FOR },
+		{ "if",			IF },
+		{ "include",		INCLUDE },
+		{ "printf",		PRINTF },
+		{ "render",		RENDER },
+		{ "tailq-foreach",	TQFOREACH },
+		{ "unsafe",		UNSAFE },
+		{ "urlescape",		URLESCAPE },
+		{ "while",		WHILE },
+	};
+	const struct keywords	*p;
+
+	p = bsearch(s, keywords, nitems(keywords), sizeof(keywords[0]),
+	    kw_cmp);
+
+	if (p)
+		return (p->k_val);
+	else { fprintf(stderr, "found string >%s<\n", s);
+		return (STRING); }
+}
+
+#define START_EXPAND	1
+#define DONE_EXPAND	2
+
+static int	expanding;
+
+int
+igetc(void)
+{
+	int	c;
+
+	while (1) {
+		if (file->ungetpos > 0)
+			c = file->ungetbuf[--file->ungetpos];
+		else
+			c = getc(file->stream);
+
+		if (c == START_EXPAND)
+			expanding = 1;
+		else if (c == DONE_EXPAND)
+			expanding = 0;
+		else
+			break;
+	}
+	return (c);
+}
+
+int
+lgetc(int quotec)
+{
+	int		c;
+
+	if (quotec) {
+		if ((c = igetc()) == EOF) {
+			yyerror("reached end of filewhile parsing "
+			    "quoted string");
+			if (file == topfile || popfile() == EOF)
+				return (EOF);
+			return (quotec);
+		}
+		return (c);
+	}
+
+	c = igetc();
+#if 0
+	if (c == '\t' || c == ' ') {
+		/* Compress blanks to a sigle space. */
+		do {
+			c = getc(file->stream);
+		} while (c == '\t'  || c == ' ');
+		ungetc(c, file->stream);
+		c = ' ';
+	}
+#endif
+
+	if (c == EOF) {
+		/*
+		 * Fake EOL when hit EOF for the first time. This gets line
+		 * count rigchtif last line included file is syntactically
+		 * invalid and has no newline.
+		 */
+		if (file->eof_reached == 0) {
+			file->eof_reached = 1;
+			return ('\n');
+		}
+		while (c == EOF) {
+			if (file == topfile || popfile() == EOF)
+				return (EOF);
+			c = igetc();
+		}
+	}
+	return (c);
+}
+
+void
+lungetc(int c)
+{
+	if (c == EOF)
+		return;
+
+	if (file->ungetpos >= file->ungetsize) {
+		void *p = reallocarray(file->ungetbuf, file->ungetsize, 2);
+		if (p == NULL)
+			err(1, "reallocarray");
+		file->ungetbuf = p;
+		file->ungetsize *= 2;
+	}
+	file->ungetbuf[file->ungetpos++] = c;
+}
+
+int
+findeol(void)
+{
+	int	c;
+
+	/* skip to either EOF or the first real EOL */
+	while (1) {
+		c = lgetc(0);
+		if (c == '\n') {
+			file->lineno++;
+			break;
+		}
+		if (c == EOF)
+			break;
+	}
+	return (ERROR);
+}
+
+int
+yylex(void)
+{
+	char		 buf[8096];
+	char		*p = buf;
+	int		 c;
+	int		 token;
+	int		 starting = 0;
+	int		 ending = 0;
+	int		 quote = 0;
+
+	if (!in_define && block == 0) {
+		while ((c = lgetc(0)) != '{' && c != EOF) {
+			if (c == '\n')
+				file->lineno++;
+		}
+
+		if (c == EOF)
+			return (0);
+
+newblock:
+		c = lgetc(0);
+		if (c == '{' || c == '!') {
+			if (c == '{')
+				block = '}';
+			else
+				block = c;
+
+			int x;
+			while ((x = lgetc(0)) == ' ' || x == '\t' || x == '\n')
+				if (x == '\n')
+					file->lineno++;
+			lungetc(x);
+
+			return (c);
+		}
+		if (c == '\n')
+			file->lineno++;
+	}
+
+#if 0
+	while ((c = lgetc(0)) == ' ' || c == '\t' || c == '\n') {
+		if (c == '\n')
+			file->lineno++;
+	}
+#else
+	if ((c = lgetc(0)) == '\n')
+		file->lineno++;
+#endif
+
+	if (c == EOF) {
+		yyerror("unterminated block");
+		return (0);
+	}
+
+	yylval.lineno = file->lineno;
+
+	if (block != 0 && c == block) {
+		if ((c = lgetc(0)) == '}') {
+			if (block == '!') {
+				block = 0;
+				return ('!');
+			}
+			block = 0;
+			return ('}');
+		}
+		lungetc(c);
+		c = block;
+	}
+
+	if (in_define && block == 0) {
+		if (c == '{')
+			goto newblock;
+
+		do {
+			if (starting) {
+				if (c == '!' || c == '{') {
+					lungetc(c);
+					lungetc('{');
+					break;
+				}
+				starting = 0;
+				lungetc(c);
+				c = '{';
+			} else if (c == '{') {
+				starting = 1;
+				continue;
+			}
+
+			*p++ = c;
+			if ((size_t)(p - buf) >= sizeof(buf)) {
+				yyerror("string too long");
+				return (findeol());
+			}
+		} while ((c = lgetc(0)) != EOF && c != '\n');
+		*p = '\0';
+		if (c == EOF) {
+			yyerror("unterminated block");
+			return (0);
+		}
+		if (c == '\n')
+			file->lineno++;
+		if ((yylval.v.string = strdup(buf)) == NULL)
+			err(1, "strdup");
+		return (STRING);
+	}
+
+	if (block == '!') {
+		do {
+			if (ending) {
+				if (c == '}') {
+					lungetc(c);
+					lungetc(block);
+					break;
+				}
+				ending = 0;
+				lungetc(c);
+				c = block;
+			} else if (c == '!') {
+				ending = 1;
+				continue;
+			}
+
+			*p++ = c;
+			if ((size_t)(p - buf) >= sizeof(buf)) {
+				yyerror("line too long");
+				return (findeol());
+			}
+		} while ((c = lgetc(0)) != EOF && c != '\n');
+		*p = '\0';
+
+		if (c == EOF) {
+			yyerror("unterminated block");
+			return (0);
+		}
+		if (c == '\n')
+			file->lineno++;
+
+		if ((yylval.v.string = strdup(buf)) == NULL)
+			err(1, "strdup");
+		return (STRING);
+	}
+
+	if (c == '|')
+		return (c);
+
+	do {
+		if (!quote && isspace((unsigned char)c))
+			break;
+
+		if (c == '"')
+			quote = !quote;
+
+		if (!quote && c == '|') {
+			lungetc(c);
+			break;
+		}
+
+		if (ending) {
+			if (c == '}') {
+				lungetc(c);
+				lungetc('}');
+				break;
+			}
+			ending = 0;
+			lungetc(c);
+			c = block;
+		} else if (!quote && c == '}') {
+			ending = 1;
+			continue;
+		}
+
+		*p++ = c;
+		if ((size_t)(p - buf) >= sizeof(buf)) {
+			yyerror("string too long");
+			return (findeol());
+		}
+	} while ((c = lgetc(0)) != EOF);
+	*p = '\0';
+
+	if (c == EOF) {
+		yyerror(quote ? "unterminated quote" : "unterminated block");
+		return (0);
+	}
+	if (c ==  '\n')
+		file->lineno++;
+	if ((token = lookup(buf)) == STRING)
+		if ((yylval.v.string = strdup(buf)) == NULL)
+			err(1, "strdup");
+	return (token);
+}
+
+struct file *
+pushfile(const char *name, int secret)
+{
+	struct file	*nfile;
+
+	if ((nfile = calloc(1, sizeof(*nfile))) == NULL)
+		err(1, "calloc");
+	if ((nfile->name = strdup(name)) == NULL)
+		err(1, "strdup");
+	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
+		warn("can't open %s", nfile->name);
+		free(nfile->name);
+		free(nfile);
+		return (NULL);
+	}
+	nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0;
+	nfile->ungetsize = 16;
+	nfile->ungetbuf = malloc(nfile->ungetsize);
+	if (nfile->ungetbuf == NULL)
+		err(1, "malloc");
+	TAILQ_INSERT_TAIL(&files, nfile, entry);
+	return (nfile);
+}
+
+int
+popfile(void)
+{
+	struct file	*prev;
+
+	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
+		prev->errors += file->errors;
+
+	TAILQ_REMOVE(&files, file, entry);
+	fclose(file->stream);
+	free(file->name);
+	free(file->ungetbuf);
+	free(file);
+	file = prev;
+	return (file ? 0 : EOF);
+}
+
+int
+parse(FILE *outfile, const char *filename)
+{
+	fp = outfile;
+
+	if ((file = pushfile(filename, 0)) == 0)
+		return (-1);
+	topfile = file;
+
+	yyparse();
+	errors = file->errors;
+	popfile();
+
+	return (errors ? -1 : 0);
+}
+
+void
+dbg(void)
+{
+	if (nodebug)
+		return;
+
+	if (yylval.lineno == lastline + 1) {
+		lastline = yylval.lineno;
+		return;
+	}
+	lastline = yylval.lineno;
+
+	fprintf(fp, "#line %d ", yylval.lineno);
+	printq(file->name);
+	putc('\n', fp);
+}
+
+void
+printq(const char *str)
+{
+	putc('"', fp);
+	for (; *str; ++str) {
+		if (*str == '"')
+			putc('\\', fp);
+		putc(*str, fp);
+	}
+	putc('"', fp);
+}
blob - /dev/null
blob + e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 (mode 644)
blob - /dev/null
blob + c2d0fad50754e9ec3740571ad5e77d7dc475ab26 (mode 644)
--- /dev/null
+++ template/regress/01-noise-only.tmpl
@@ -0,0 +1,2 @@
+only
+noise
blob - /dev/null
blob + c35618a4287faab518c419c4bbbe06ecbca7f157 (mode 644)
--- /dev/null
+++ template/regress/02-only-verbatim.tmpl
@@ -0,0 +1,17 @@
+{!
+#include <stdio.h>
+
+#include "tmpl.h"
+!}
+
+noise {! /* woops */ !}
+here
+
+{!
+int
+main(void)
+{
+	puts("hello, world!");
+	return (0);
+}
+!}
blob - /dev/null
blob + 270c611ee72c567bc1b2abec4cbc345bab9f15ba (mode 644)
--- /dev/null
+++ template/regress/02.expected
@@ -0,0 +1 @@
+hello, world!
blob - /dev/null
blob + d866f69531b2f1248c5bb6940f9b8c40adf87c82 (mode 644)
--- /dev/null
+++ template/regress/03-block.tmpl
@@ -0,0 +1,22 @@
+{!
+#include <stdlib.h>
+
+#include "tmpl.h"
+!}
+
+{{ define base(struct template *tp, const char *title) }}
+{! char *foo = NULL; !}
+<!doctype html>
+<html>
+	<head>
+		<title>{{ title }}</title>
+	</head>
+	<body> {! /* TODO: frobnicate this line! */ !}
+		<h1>{{ title }}</h1>
+		{{ " | " }}
+		{{ "other stuff" }}
+	</body>
+</html>
+{{ finally }}
+{! free(foo); !}
+{{ end }}
blob - /dev/null
blob + 0f766820b9e5de1343ca8034bea3b2b0a4418221 (mode 644)
--- /dev/null
+++ template/regress/03.expected
@@ -0,0 +1,2 @@
+<!doctype html><html><head><title> *hello* </title></head><body> <h1> *hello* </h1> | other stuff</body></html>
+<!doctype html><html><head><title>&lt;hello&gt;</title></head><body> <h1>&lt;hello&gt;</h1> | other stuff</body></html>
blob - /dev/null
blob + aa8c5edc9b8934fea4803f8b465a7f00fb389e23 (mode 644)
--- /dev/null
+++ template/regress/04-flow.tmpl
@@ -0,0 +1,31 @@
+{!
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmpl.h"
+!}
+
+{{ define base(struct template *tp, const char *title) }}
+{! char *foo = NULL; !}
+<!doctype html>
+<html>
+	<head>
+		<title>{{ title }}</title>
+	</head>
+	<body>
+		<h1>{{ title }}</h1>
+		{{ if strchr(title, '*') != NULL }}
+			<p>"{{ title }}" has a '*' in it</p>
+			{{ if 1 }}
+				<p>tautology!</p>
+			{{ end }}
+		{{ else if strchr(title, '=') != NULL }}
+			<p>"{{ title }}" has a '=' in it!</p>
+		{{ else }}
+			<p>"{{ title }}" doesn't have a '*' in it</p>
+		{{ end }}
+	</body>
+</html>
+{{ finally }}
+{! free(foo); !}
+{{ end }}
blob - /dev/null
blob + 32240e27d9a6dbeeb395f7e760e2a043e29b939c (mode 644)
--- /dev/null
+++ template/regress/04.expected
@@ -0,0 +1,2 @@
+<!doctype html><html><head><title> *hello* </title></head><body><h1> *hello* </h1><p>" *hello* " has a '*' in it</p><p>tautology!</p></body></html>
+<!doctype html><html><head><title>&lt;hello&gt;</title></head><body><h1>&lt;hello&gt;</h1><p>"&lt;hello&gt;" doesn't have a '*' in it</p></body></html>
blob - /dev/null
blob + d47673dc5bf15202d96329fe8d971d117bc930eb (mode 644)
--- /dev/null
+++ template/regress/05-loop.tmpl
@@ -0,0 +1,42 @@
+{!
+#include <sys/queue.h>
+#include <string.h>
+#include "lists.h"
+#include "tmpl.h"
+
+int	list(struct template *, struct tailhead *);
+
+!}
+
+{{ define base(struct template *tp, struct tailhead *head) }}
+<!doctype html>
+<html>
+	<body>
+		{{ render list(tp, head) }}
+	</body>
+</html>
+{{ end }}
+
+{{ define list(struct template *tp, struct tailhead *head) }}
+{!
+	struct entry *np;
+	int i;
+!}
+	{{ if !TAILQ_EMPTY(head) }}
+		<p>items:</p>
+		<ul>
+			{{ tailq-foreach np head entries }}
+				<li>{{ np->text }}</li>
+			{{ end }}
+		</ul>
+	{{ else }}
+		<p>no items</p>
+	{{ end }}
+
+	<p>
+		{{ for i = 0; i < 3; ++i }}
+			hello{{ " " }}
+		{{ end }}
+		world!
+	</p>
+{{ end }}
blob - /dev/null
blob + d4c20d67eeee5e6e0217d8abd4c19f2d440ba6d9 (mode 644)
--- /dev/null
+++ template/regress/05.expected
@@ -0,0 +1,2 @@
+<!doctype html><html><body><p>items:</p><ul><li>1</li><li>2</li></ul><p>hello hello hello world!</p></body></html>
+<!doctype html><html><body><p>no items</p><p>hello hello hello world!</p></body></html>
blob - /dev/null
blob + f5450fbc2e7134e6fb5f5798e14983545f6347d4 (mode 644)
--- /dev/null
+++ template/regress/06-escape.tmpl
@@ -0,0 +1,17 @@
+{!
+#include <stdlib.h>
+
+#include "tmpl.h"
+!}
+
+{{ define base(struct template *tp, const char *title) }}
+<!doctype html>
+<html>
+	<head>
+		<title>{{ title | urlescape }}</title>
+	</head>
+	<body>
+		<h1>{{ title | unsafe }}</h1>
+	</body>
+</html>
+{{ end }}
blob - /dev/null
blob + 6a9d734b454093206236753143743d95d5e473af (mode 644)
--- /dev/null
+++ template/regress/06.expected
@@ -0,0 +1,2 @@
+<!doctype html><html><head><title>%20*hello*%20</title></head><body><h1> *hello* </h1></body></html>
+<!doctype html><html><head><title><hello></title></head><body><h1><hello></h1></body></html>
blob - /dev/null
blob + 8b48d50f1257a72de04bbd33038320dd05edca49 (mode 644)
--- /dev/null
+++ template/regress/07-printf.tmpl
@@ -0,0 +1,10 @@
+{!
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "tmpl.h"
+!}
+
+{{ define base(struct template *tp, const char *title) }}
+{{ printf "%.2s:\t%d\n", title, 42 }}
+{{ end }}
blob - /dev/null
blob + 681daf3915b708a380ddc75467b47beceff9b367 (mode 644)
--- /dev/null
+++ template/regress/07.expected
@@ -0,0 +1,4 @@
+ *:	42
+
+&lt;h:	42
+
blob - /dev/null
blob + 5b41a3b36ca0223dab174dafa40e73c572f9a203 (mode 644)
--- /dev/null
+++ template/regress/Makefile
@@ -0,0 +1,60 @@
+REGRESS_TARGETS =	00-empty \
+			01-noise-only \
+			02-only-verbatim \
+			03-block \
+			04-flow \
+			05-loop \
+			06-escape \
+			07-printf
+
+REGRESS_SETUP_ONCE =	setup-comp
+REGRESS_CLEANUP =	clean-comp
+NO_OBJ =		Yes
+
+CFLAGS +=		-I${.CURDIR}/../
+
+setup-comp:
+	cp ${.CURDIR}/../tmpl.c .
+	ln -f ${.CURDIR}/../template template || \
+		ln -f ${.CURDIR}/../obj/template template
+
+clean-comp:
+	rm template
+	rm -f t got 0*.[cdo] runbase.[do] runlist.[do] tmpl.*
+
+.SUFFIXES: .tmpl .c .o
+
+.tmpl.c:
+	./template -o $@ $?
+
+00-empty:
+	./template 00-empty.tmpl >/dev/null
+
+01-noise-only:
+	./template 01-noise-only.tmpl >/dev/null
+
+02-only-verbatim: 02-only-verbatim.o tmpl.o
+	${CC} 02-only-verbatim.o tmpl.o -o t && ./t > got
+	diff -u ${.CURDIR}/02.expected got
+
+03-block: 03-block.o runbase.o tmpl.o
+	${CC} 03-block.o runbase.o tmpl.o -o t && ./t > got
+	diff -u ${.CURDIR}/03.expected got
+
+04-flow: 04-flow.o runbase.o tmpl.o
+	${CC} 04-flow.o runbase.o tmpl.o -o t && ./t > got
+	diff -u ${.CURDIR}/04.expected got
+
+05-loop: 05-loop.o runlist.o tmpl.o
+	${CC} 05-loop.o runlist.o tmpl.o -o t && ./t > got
+	diff -u ${.CURDIR}/05.expected got
+
+06-escape: 06-escape.o runbase.o tmpl.o
+	${CC} 06-escape.o runbase.o tmpl.o -o t && ./t > got
+	diff -u ${.CURDIR}/06.expected got
+
+07-printf: 07-printf.o runbase.o tmpl.o
+	${CC} 07-printf.o runbase.o tmpl.o -o t && ./t > got
+	diff -u ${.CURDIR}/07.expected got
+
+.include <bsd.regress.mk>
blob - /dev/null
blob + 7229706ecd6bfb0bb2f38be92950b7082f7a6c41 (mode 644)
--- /dev/null
+++ template/regress/lists.h
@@ -0,0 +1,7 @@
+#include <sys/queue.h>
+
+TAILQ_HEAD(tailhead, entry);
+struct entry {
+	char	*text;
+	TAILQ_ENTRY(entry) entries;
+};
blob - /dev/null
blob + bde2b4cb20be96fc0b83abde12d5c55a2a7a4ac4 (mode 644)
--- /dev/null
+++ template/regress/runbase.c
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "tmpl.h"
+
+int	 base(struct template *, const char *title);
+
+int
+my_putc(struct template *tp, int c)
+{
+	FILE	*fp = tp->tp_arg;
+
+	if (putc(c, fp) < 0)
+		return (-1);
+
+	return (0);
+}
+
+int
+my_puts(struct template *tp, const char *s)
+{
+	FILE	*fp = tp->tp_arg;
+
+	if (fputs(s, fp) < 0)
+		return (-1);
+
+	return (0);
+}
+
+int
+main(int argc, char **argv)
+{
+	struct template *tp;
+
+	if ((tp = template(stdout, my_puts, my_putc)) == NULL)
+		err(1, "template");
+
+	if (base(tp, " *hello* ") == -1)
+		return (1);
+	puts("");
+
+	if (base(tp, "<hello>") == -1)
+		return (1);
+	puts("");
+
+	free(tp);
+	return (0);
+}
blob - /dev/null
blob + cafb14beb743d07677cd510ccfae45e5be4fd612 (mode 644)
--- /dev/null
+++ template/regress/runlist.c
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "tmpl.h"
+#include "lists.h"
+
+int	base(struct template *, struct tailhead *);
+
+int
+my_putc(struct template *tp, int c)
+{
+	FILE	*fp = tp->tp_arg;
+
+	if (putc(c, fp) < 0)
+		return (-1);
+
+	return (0);
+}
+
+int
+my_puts(struct template *tp, const char *s)
+{
+	FILE	*fp = tp->tp_arg;
+
+	if (fputs(s, fp) < 0)
+		return (-1);
+
+	return (0);
+}
+
+int
+main(int argc, char **argv)
+{
+	struct template	*tp;
+	struct tailhead	 head;
+	struct entry	*np;
+	int		 i;
+
+	if ((tp = template(stdout, my_puts, my_putc)) == NULL)
+		err(1, "template");
+
+	TAILQ_INIT(&head);
+	for (i = 0; i < 2; ++i) {
+		if ((np = calloc(1, sizeof(*np))) == NULL)
+			err(1, "calloc");
+		if (asprintf(&np->text, "%d", i+1) == -1)
+			err(1, "asprintf");
+		TAILQ_INSERT_TAIL(&head, np, entries);
+	}
+
+	if (base(tp, &head) == -1)
+		return (1);
+	puts("");
+
+	while ((np = TAILQ_FIRST(&head))) {
+		TAILQ_REMOVE(&head, np, entries);
+		free(np->text);
+		free(np);
+	}
+
+	if (base(tp, &head) == -1)
+		return (1);
+	puts("");
+
+	free(tp);
+
+	return (0);
+}
blob - /dev/null
blob + 48765fbe52f01bd15fa5126aa7f5d4029d94172e (mode 644)
--- /dev/null
+++ template/template.1
@@ -0,0 +1,85 @@
+.\" Copyright (c) 2022 Omar Polo <op@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd January 06, 2022
+.Dt TEMPLATE 1
+.Os
+.Sh NAME
+.Nm template
+.Nd templating system compiler
+.Sh SYNOPSIS
+.Nm
+.Op Fl G
+.Op Fl o Ar out
+.Op Ar
+.Sh DESCRIPTION
+.Nm
+is an utility that converts files written in the
+.Xr template 7
+format format to a set of routine writtens in the C programming
+language.
+.Nm
+converts the files given as arguments or from standard input, and
+writes to standard output.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl G
+Do not emit debug info in the generated source.
+It's disabled by default, unless
+.Nm
+is reading from standard input.
+.It Fl o Ar out
+Write output to file.
+.Ar out
+will be created or truncated if exists and will be removed if
+.Nm
+encounters any error.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr template 7
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility was written by
+.An Omar Polo Aq Mt op@openbsd.org .
+.Sh CAVEATS
+The compiler is very naive, so there are quite a few shortcomings:
+.Bl -bullet -compact
+.It
+No attempt is made to validate the C code provided inline, nor the
+validity of the arguments to many constructs.
+.It
+The generated code assumes that a variable called
+.Va tp
+of type
+.Vt struct template *
+is in scope inside each block.
+.It
+Each block may have additional variables used for the template
+generation implicitly defined: to avoid clashes, don't name variables
+or arguments with the
+.Sq tp_
+prefix.
+.It
+Blanks are, in most cases, trimmed.
+Normally this is not a problem, but a workaround is needed in case
+they need to be preserved, for e.g.:
+.Bd -literal -offset indent
+Name: {{ " " }} {{ render name_field(tp) }}
+.Ed
+.El
blob - /dev/null
blob + c344796aaae84d607d9948f2ace4f85729f3c7d8 (mode 644)
--- /dev/null
+++ template/template.7
@@ -0,0 +1,127 @@
+.\" Copyright (c) 2022 Omar Polo <op@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd January 06, 2022
+.Dt TEMPLATE 7
+.Os
+.Sh NAME
+.Nm template
+.Nd templating language
+.Sh DESCRIPTION
+.Nm
+is a language used to define programs that output data in some way.
+These programs are called
+.Dq templates .
+A
+.Nm
+file is assumed to be compiled using the
+.Xr template 1
+utility into C code, to be further compiled as part of a bigger
+application.
+The language itself is format-agnostic and can thus be used to produce
+various type of outputs.
+.Pp
+There are two special sequences:
+.Bl -tag -width 9m
+.It Cm {{ Ar ... Cm }}
+used for
+.Nm
+special syntax.
+.It Cm {! Ar ... Cm !}
+used to include literal C code.
+This is the only special syntax permitted as top-level, except for block
+definition and includes.
+.El
+.Pp
+The basic unit of a
+.Nm
+file is the block.
+Each block is turned into a C function that output its content via some
+provided functions.
+Here's an example of a block:
+.Bd -literal -offset indent
+{{ define tp_base(struct template *tp, const char *title) }}
+<!doctype html>
+<html>
+	<head>
+		<title>{{ title }}</title>
+	</head>
+	<body>
+		{{ render tp->tp_body(tp) }}
+	</body>
+</html>
+{{ end }}
+.Ed
+.Ss SPECIAL SYNTAX
+This section is a reference for all the special syntaxes supported.
+.Bl -tag -width Ds
+.It Cm {{ Ic include Ar file Cm }}
+Include additional template files.
+.It Cm {{ Ic define Ar name Ns ( Ar arguments ... ) Cm }} Ar body Cm {{ Ic end Cm }}
+Defines the block
+.Ar name
+with the given
+.Ar arguments .
+.Ar body
+will be outputted as-is via the provided functions
+.Pq i.e.\& is still escaped eventually
+and can contain all the special syntaxes documented here except
+.Ic include
+and
+.Ic define .
+.It Cm {{ Ic render Ar expression() Cm }}
+Executes
+.Ar expression()
+and terminate the template if it returns -1.
+It's used to render (call) another template.
+.It Cm {{ Ic printf Ar fmt , Ar arguments ... Cm }}
+Outputs the string that would be produced by calling
+.Xr printf 3
+with the given
+.Ar fmt
+format string and the given
+.Ar arguments .
+.It Cm {{ Ic if Ar expr Cm }} Ar ... Cm {{ Ic elseif Ar expr Cm }} Ar ... Cm {{ Ic else Cm }} Ar ... Cm {{ Ic end Cm }}
+Conditional evaluation.
+.Ic elseif
+can be provided zero or more times,
+.Ic else
+only zero or one time and always for last.
+.It Cm {{ Ic for Ar ... ; Ar ... ; Ar ... Cm  }} Ar ... Cm {{ Ic end Cm }}
+Looping construct similar to the C for loop.
+.It Cm {{ Ic tailq-foreach Ar var head fieldname Cm }} Ar .. Cm {{ Ic end Cm }}
+Looping construct similar to the queue.h macro TAILQ_FOREACH.
+.It Cm {{ Ic while Ar ... Cm  }} Ar ... Cm {{ Ic end Cm }}
+Looping construct similar to the C while loop.
+.It Cm {{ Ar expression Cm \&| Ic unsafe Cm }}
+Output
+.Ar expression
+as-is.
+.It Cm {{ Ar expression Cm \&| Ic urlescape Cm }}
+Output
+.Ar expression
+escaped in a way that can be made part of an URL.
+.It Cm {{ Ar expression Cm }}
+Output
+.Ar expression
+with the default escaping.
+.El
+.Sh SEE ALSO
+.Xr template 1
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+reference was written by
+.An Omar Polo Aq Mt op@openbsd.org .
blob - /dev/null
blob + d8f3e5383a96a5faf569ee9b566218d8f14189c1 (mode 644)
--- /dev/null
+++ template/template.c
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int	 parse(FILE *, const char *);
+
+int	 nodebug;
+
+static void __dead
+usage(void)
+{
+	fprintf(stderr, "usage: %s [file...]\n",
+	    getprogname());
+	exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+	FILE		*fp = stdout;
+	const char	*out = NULL;
+	int		 ch, i;
+
+	while ((ch = getopt(argc, argv, "Go:")) != -1) {
+		switch (ch) {
+		case 'G':
+			nodebug = 1;
+			break;
+		case 'o':
+			out = optarg;
+			break;
+		default:
+			usage();
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (out && (fp = fopen(out, "w")) == NULL)
+		err(1, "can't open %s", out);
+
+	if (out && unveil(out, "wc") == -1)
+		err(1, "unveil %s", out);
+	if (unveil("/", "r") == -1)
+		err(1, "unveil /");
+	if (pledge(out ? "stdio rpath cpath" : "stdio rpath", NULL) == -1)
+		err(1, "pledge");
+
+	if (argc == 0) {
+		nodebug = 1;
+		if (parse(fp, "/dev/stdin") == -1)
+			goto err;
+	} else {
+		for (i = 0; i < argc; ++i)
+			if (parse(fp, argv[i]) == -1)
+				goto err;
+	}
+
+	if (ferror(fp))
+		goto err;
+
+	if (fclose(fp) == -1) {
+		fp = NULL;
+		goto err;
+	}
+
+	return (0);
+
+err:
+	if (fp)
+		fclose(fp);
+	if (out && unlink(out) == -1)
+		err(1, "unlink %s", out);
+	return (1);
+}
blob - /dev/null
blob + f125c861a661eea5d4cb494eb708b2fb02003b88 (mode 644)
--- /dev/null
+++ template/template.d
@@ -0,0 +1 @@
+template.o: template.c
blob - /dev/null
blob + 18ee428516dc3b90ef925c5ac135e90278c0d5f9 (mode 644)
--- /dev/null
+++ template/tmpl.c
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "tmpl.h"
+
+int
+tp_urlescape(struct template *tp, const char *str)
+{
+	int	 r;
+	char	 tmp[4];
+
+	if (str == NULL)
+		return (0);
+
+	for (; *str; ++str) {
+		if (iscntrl((unsigned char)*str) ||
+		    isspace((unsigned char)*str) ||
+		    *str == '\'' || *str == '"' || *str == '\\') {
+			r = snprintf(tmp, sizeof(tmp), "%%%2X", *str);
+			if (r < 0  || (size_t)r >= sizeof(tmp))
+				return (0);
+			if (tp->tp_puts(tp, tmp) == -1)
+				return (-1);
+		} else {
+			if (tp->tp_putc(tp, *str) == -1)
+				return (-1);
+		}
+	}
+
+	return (0);
+}
+
+int
+tp_htmlescape(struct template *tp, const char *str)
+{
+	int r;
+
+	if (str == NULL)
+		return (0);
+
+	for (; *str; ++str) {
+		switch (*str) {
+		case '<':
+			r = tp->tp_puts(tp, "&lt;");
+			break;
+		case '>':
+			r = tp->tp_puts(tp, "&gt;");
+			break;
+		case '&':
+			r = tp->tp_puts(tp, "&amp;");
+			break;
+		case '"':
+			r = tp->tp_puts(tp, "&quot;");
+			break;
+		case '\'':
+			r = tp->tp_puts(tp, "&apos;");
+			break;
+		default:
+			r = tp->tp_putc(tp, *str);
+			break;
+		}
+
+		if (r == -1)
+			return (-1);
+	}
+
+	return (0);
+}
+
+struct template *
+template(void *arg, tmpl_puts putsfn, tmpl_putc putcfn)
+{
+	struct template *tp;
+
+	if ((tp = calloc(1, sizeof(*tp))) == NULL)
+		return (NULL);
+
+	tp->tp_arg = arg;
+	tp->tp_escape = tp_htmlescape;
+	tp->tp_puts = putsfn;
+	tp->tp_putc = putcfn;
+
+	return (tp);
+}
+
+void
+template_free(struct template *tp)
+{
+	free(tp->tp_tmp);
+	free(tp);
+}
blob - /dev/null
blob + c1e0f61eabf6578836278219e4b359dedea12681 (mode 644)
--- /dev/null
+++ template/tmpl.d
@@ -0,0 +1 @@
+template/tmpl.o: template/tmpl.c template/tmpl.h
blob - /dev/null
blob + 4c8de903c9b7957de3c6bc1ed2058fa9a5db553b (mode 644)
--- /dev/null
+++ template/tmpl.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef TMPL_H
+#define TMPL_H
+
+struct template;
+
+typedef int (*tmpl_puts)(struct template *, const char *);
+typedef int (*tmpl_putc)(struct template *, int);
+
+struct template {
+	void		*tp_arg;
+	char		*tp_tmp;
+	tmpl_puts	 tp_escape;
+	tmpl_puts	 tp_puts;
+	tmpl_putc	 tp_putc;
+};
+
+int		 tp_urlescape(struct template *, const char *);
+int		 tp_htmlescape(struct template *, const char *);
+
+struct template	*template(void *, tmpl_puts, tmpl_putc);
+void		 template_free(struct template *);
+
+#endif
blob - /dev/null
blob + 95485db80176bf6c2746a0ef6ed0107bcd87e1bc (mode 644)
--- /dev/null
+++ tests/MMD.c
@@ -0,0 +1,5 @@
+int
+main(void)
+{
+	return 0;
+}
blob - /dev/null
blob + eeea39ab2f7055479d97e4cc3295a54e92afa194 (mode 644)
--- /dev/null
+++ tests/Makefile
@@ -0,0 +1,43 @@
+DISTFILES =	Makefile \
+		MMD.c \
+		WAIT_ANY.c \
+		__progname.c \
+		accept4.c \
+		asr_run.c \
+		bufferevent_read_pressure_cb.c \
+		err.c \
+		event_asr_run.c \
+		freezero.c \
+		getdtablecount.c \
+		getdtablesize.c \
+		getexecname.c \
+		getprogname.c \
+		imsg.c \
+		libevent.c \
+		libevent2.c \
+		libsqlite3.c \
+		libtls.c \
+		pledge.c \
+		reallocarray.c \
+		recallocarray.c \
+		setgroups.c \
+		setproctitle.c \
+		setresgid.c \
+		setresuid.c \
+		strlcat.c \
+		strlcpy.c \
+		strtonum.c \
+		sys_queue.c \
+		sys_tree.c \
+		unveil.c \
+		vasprintf.c
+
+all:
+	false
+
+dist: ${DISTFILES}
+	mkdir -p ${DESTDIR}/
+	${INSTALL} -m 0644 ${DISTFILES} ${DESTDIR}/
+
+.PHONY: all dist
+include ../config.mk
blob - /dev/null
blob + 970e15419a8408b4772603b33c51259415d75d4e (mode 644)
--- /dev/null
+++ tests/WAIT_ANY.c
@@ -0,0 +1,7 @@
+#include <sys/wait.h>
+
+int
+main(void)
+{
+	return WAIT_ANY;
+}
blob - /dev/null
blob + 1964c2b48a27a9f94f4a1fa7427b63f653b7b3c2 (mode 644)
--- /dev/null
+++ tests/__progname.c
@@ -0,0 +1,10 @@
+#include <stdio.h>
+
+extern const char *__progname;
+
+int
+main(void)
+{
+	puts(__progname);
+	return 0;
+}
blob - /dev/null
blob + 3e2af5d5e26a4a2ceabfae40f10bbf66bee748bf (mode 644)
--- /dev/null
+++ tests/accept4.c
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/socket.h>
+#include <stdlib.h>
+
+int
+main(void)
+{
+	accept4(0, NULL, 0, SOCK_NONBLOCK);
+	return 0;
+}
blob - /dev/null
blob + 3478809e685017e20e2b20667fac8c9e40cf19d8 (mode 644)
--- /dev/null
+++ tests/asr_run.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <netdb.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <asr.h>
+
+int
+main(void)
+{
+	struct asr_query	*query;
+	struct asr_result	 ar;
+	struct addrinfo		 hints;
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family = AF_UNSPEC;
+	hints.ai_socktype = SOCK_STREAM;
+
+	query = getaddrinfo_async("openbsd.org", "www", &hints, NULL);
+	if (query == NULL)
+		return 1;
+
+	asr_run_sync(query, &ar);
+
+	freeaddrinfo(ar.ar_addrinfo);
+	return 0;
+}
blob - /dev/null
blob + 93eb816d939712b0bb93d05c88810f247ead41f7 (mode 644)
--- /dev/null
+++ tests/err.c
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <err.h>
+
+int
+main(void)
+{
+	warnx("%d. warnx", 1);
+	warn("%d. warn", 2);
+	err(0, "%d. err", 3);
+        return 1;
+}
blob - /dev/null
blob + 060264e7c011ca4961c63fdeda4d7af53e14cf8c (mode 644)
--- /dev/null
+++ tests/event_asr_run.c
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <netdb.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <asr.h>
+#include <event.h>
+
+int
+main(void)
+{
+	struct asr_query	*query;
+	struct addrinfo		 hints;
+
+	event_init();
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family = AF_UNSPEC;
+	hints.ai_socktype = SOCK_STREAM;
+
+	query = getaddrinfo_async("openbsd.org", "www", &hints, NULL);
+	if (query == NULL)
+		return 1;
+
+	/* just testing whether it compiles */
+	event_asr_run(query, NULL, NULL);
+
+	return 0;
+}
blob - /dev/null
blob + 1b15cbfca63d973754b5a508ee3f2dd5def3bbe9 (mode 644)
--- /dev/null
+++ tests/freezero.c
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdlib.h>
+
+int
+main(void)
+{
+	freezero(NULL, 0);
+	return 0;
+}
blob - /dev/null
blob + 67558279c11e1dd7ba8d29d10ba83b9b00e3fd68 (mode 644)
--- /dev/null
+++ tests/getdtablecount.c
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <unistd.h>
+
+int
+main(void)
+{
+	return getdtablecount() == 0;
+}
blob - /dev/null
blob + 86fdae393a79943e168e0bef67adb83e5751218c (mode 644)
--- /dev/null
+++ tests/getdtablesize.c
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <unistd.h>
+
+int
+main(void)
+{
+	return getdtablesize() == 0;
+}
blob - /dev/null
blob + 2c53cab9c23171ab5aeb4c66a5851dba8d12de51 (mode 644)
--- /dev/null
+++ tests/getexecname.c
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdlib.h>
+
+int
+main(void)
+{
+	const char *progname;
+
+	progname = getexecname();
+	return (progname == NULL);
+}
blob - /dev/null
blob + 57c0e2b059de0ac1a0662f96d0fe211a8f30e3db (mode 644)
--- /dev/null
+++ tests/getprogname.c
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdlib.h>
+
+int
+main(void)
+{
+	return getprogname() == NULL;
+}
blob - /dev/null
blob + e0b63cd9b08fac391097b56a76185ca4816924eb (mode 644)
--- /dev/null
+++ tests/libevent.c
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/time.h>
+
+#include <event.h>
+
+int
+main(void)
+{
+	event_init();
+	return 0;
+}
blob - /dev/null
blob + 8a4b11f39666df7c2f5c94447ca54b285e1d7bc2 (mode 644)
--- /dev/null
+++ tests/libsqlite3.c
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2023 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stddef.h>
+#include <sqlite3.h>
+
+int
+main(void)
+{
+	struct sqlite3	*db;
+	int err;
+
+	err = sqlite3_open_v2(":memory:", &db, SQLITE_OPEN_READONLY, NULL);
+	if (err != SQLITE_OK)
+		return (1);
+
+	err = sqlite3_close(db);
+	if (err != SQLITE_OK)
+		return (1);
+
+	return (0);
+}
blob - /dev/null
blob + 8878b01d6c97df185cfb7031e92b28545e0025a9 (mode 644)
--- /dev/null
+++ tests/pledge.c
@@ -0,0 +1,10 @@
+#include <stdlib.h>
+#include <unistd.h>
+
+int
+main(void)
+{
+	if (pledge("stdio", NULL) == -1)
+		return 1;
+	return 0;
+}
blob - /dev/null
blob + f99e685d8350c6a98901404fbc059b61607c14f9 (mode 644)
--- /dev/null
+++ tests/reallocarray.c
@@ -0,0 +1,7 @@
+#include <stdlib.h>
+
+int
+main(void)
+{
+	return !reallocarray(NULL, 2, 2);
+}
blob - /dev/null
blob + e0c60d7118f8b94719e18fa5efadb3daf9b36833 (mode 644)
--- /dev/null
+++ tests/recallocarray.c
@@ -0,0 +1,11 @@
+#include <stdlib.h>
+
+int
+main(void)
+{
+	void	*p;
+
+	if ((p = calloc(2, 2)) == NULL)
+		return 1;
+	return !recallocarray(p, 2, 3, 2);
+}
blob - /dev/null
blob + 3c972c73ae245c44960d431c192d4cc52f95994a (mode 644)
--- /dev/null
+++ tests/setgroups.c
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <grp.h>
+#include <pwd.h>
+#include <unistd.h>
+
+int
+main(void)
+{
+	struct passwd *pw;
+
+	pw = getpwnam("www");
+	if (pw == NULL)
+		return 1;
+
+	setgroups(1, &pw->pw_gid);
+	return 0;
+}
blob - /dev/null
blob + d54a63e74996e9df0d6a249669510f7cad852ecd (mode 644)
--- /dev/null
+++ tests/setproctitle.c
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * FreeBSD has setproctitle in a different header than OpenBSD.
+ */
+
+#include <sys/types.h>
+
+#include <stdlib.h>
+#include <unistd.h>
+
+int
+main(void)
+{
+	setproctitle("%s", "frobnicator");
+	return 0;
+}
blob - /dev/null
blob + 616458f82280192cdb500175616ed252dfe19928 (mode 644)
--- /dev/null
+++ tests/setresgid.c
@@ -0,0 +1,8 @@
+#include <sys/types.h>
+#include <unistd.h>
+
+int
+main(void)
+{
+	return setresgid(-1, -1, -1) == -1;
+}
blob - /dev/null
blob + 0f3f65c864ef1d839583399c33676500daf4c72a (mode 644)
--- /dev/null
+++ tests/setresuid.c
@@ -0,0 +1,8 @@
+#include <sys/types.h>
+#include <unistd.h>
+
+int
+main(void)
+{
+	return setresuid(-1, -1, -1) == -1;
+}
blob - /dev/null
blob + 0a005c57f59ab5c0b9c5cb707940cb875ce21dbb (mode 644)
--- /dev/null
+++ tests/strlcat.c
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <string.h>
+
+int
+main(void)
+{
+	char buf[3] = "a";
+	return ! (strlcat(buf, "b", sizeof(buf)) == 2 &&
+	    buf[0] == 'a' && buf[1] == 'b' && buf[2] == '\0');
+}
blob - /dev/null
blob + d1789354e72631744c3a953289693faa450da222 (mode 644)
--- /dev/null
+++ tests/strlcpy.c
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <string.h>
+
+int
+main(void)
+{
+	char buf[2] = "";
+	return ! (strlcpy(buf, "a", sizeof(buf)) == 1 &&
+	    buf[0] == 'a' && buf[1] == '\0');
+}
blob - /dev/null
blob + 6e2aad2efbc46b09473e200b9a648019bdde8554 (mode 644)
--- /dev/null
+++ tests/strtonum.c
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdlib.h>
+
+int
+main(void)
+{
+	const char *str = "42", *errstr;
+	int res;
+
+	res = strtonum(str, 1, 64, &errstr);
+	return res != 42 || errstr != NULL;
+}
blob - /dev/null
blob + 81d3c6166446ea4d4da6944141bfff27fe02d9a5 (mode 644)
--- /dev/null
+++ tests/sys_queue.c
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/queue.h>
+
+#include <stddef.h>
+
+TAILQ_HEAD(tailhead, entry) head;
+struct entry {
+	TAILQ_ENTRY(entry) entries;
+} *np, *nt;
+
+int
+main(void)
+{
+	TAILQ_INIT(&head);
+	TAILQ_FOREACH_SAFE(np, &head, entries, nt) {
+		/* nop */;
+	}
+
+	return 0;
+}
blob - /dev/null
blob + 795c578610adb6931f3ba912d4a0152822d044c2 (mode 644)
--- /dev/null
+++ tests/sys_tree.c
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/tree.h>
+
+#include <stddef.h>
+
+struct node {
+	int			id;
+	SPLAY_ENTRY(node)	nodes;
+};
+SPLAY_HEAD(node_tree, node);
+
+int
+node_cmp(struct node *a, struct node *b)
+{
+	return (a->id - b->id);
+}
+
+SPLAY_PROTOTYPE(node_tree, node, nodes, node_cmp);
+SPLAY_GENERATE(node_tree, node, nodes, node_cmp);
+
+int
+main(void)
+{
+	return 0;
+}
blob - /dev/null
blob + 0582be2a1f74f3d5d0728115dd45ac33a069ee81 (mode 644)
--- /dev/null
+++ tests/unveil.c
@@ -0,0 +1,10 @@
+#include <stdlib.h>
+#include <unistd.h>
+
+int
+main(void)
+{
+	if (unveil(".", "r") == -1)
+		return 1;
+	return 0;
+}
blob - /dev/null
blob + 2d1ac0b1d15ccd93ef5b50e92cb00d65f009f3b0 (mode 644)
--- /dev/null
+++ tests/vasprintf.c
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+static int	testfn(char **, const char*, ...);
+
+static int
+testfn(char **ret, const char *fmt, ...)
+{
+	va_list	ap;
+	int	irc;
+
+	va_start(ap, fmt);
+	irc = vasprintf(ret, fmt, ap);
+	va_end(ap);
+
+	return irc;
+}
+
+int
+main(void)
+{
+	char	*ret;
+
+	if (testfn(&ret, "%s.", "Text") != 5)
+		return 1;
+	if (strcmp(ret, "Text."))
+		return 2;
+	return 0;
+}
blob - /dev/null
blob + b7db175dfd8ea2b00dc361998c459a66ad831a2d (mode 644)
--- /dev/null
+++ ui.tmpl
@@ -0,0 +1,38 @@
+{!
+/*
+ * Copyright (c) 2023 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* #include <sys/queue.h> */
+#include <sys/tree.h>
+
+#include <event.h>
+#include <stdint.h>
+
+#include "pkg.h"
+#include "tmpl.h"
+
+!}
+
+{{ define tp_home(struct template *tp) }}
+# pkg_fcgi
+
+Welcome to pkg_fcgi, the Gemini interface for the OpenBSD ports collection!
+
+=> /search	Search for a package
+=> /all/	Browse all categories
+
+What you search will be matched against the package name (pkgstem), comment, description, and maintainer.
+{{ end }}
blob - /dev/null
blob + f05ceadf51129ffe6559594eeea12fa517db5f69 (mode 644)
--- /dev/null
+++ xmalloc.c
@@ -0,0 +1,100 @@
+/* $OpenBSD: xmalloc.c,v 1.4 2019/06/28 05:44:09 deraadt Exp $ */
+/*
+ * Author: Tatu Ylonen <ylo@cs.hut.fi>
+ * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
+ *                    All rights reserved
+ * Versions of malloc and friends that check their results, and never return
+ * failure (they call fatal if they encounter an error).
+ *
+ * As far as I am concerned, the code I have written for this software
+ * can be used freely for any purpose.  Any derived versions of this
+ * software must be clearly marked as such, and if the derived work is
+ * incompatible with the protocol description in the RFC file, it must be
+ * called by a name other than "ssh" or "Secure Shell".
+ */
+
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "log.h"
+#include "xmalloc.h"
+
+void *
+xmalloc(size_t size)
+{
+	void *ptr;
+
+	if (size == 0)
+		fatal("xmalloc: zero size");
+	ptr = malloc(size);
+	if (ptr == NULL)
+		fatal("xmalloc: allocating %zu bytes", size);
+	return ptr;
+}
+
+void *
+xcalloc(size_t nmemb, size_t size)
+{
+	void *ptr;
+
+	if (size == 0 || nmemb == 0)
+		fatal("xcalloc: zero size");
+	ptr = calloc(nmemb, size);
+	if (ptr == NULL)
+		fatal("xcalloc: allocating %zu * %zu bytes", nmemb, size);
+	return ptr;
+}
+
+void *
+xreallocarray(void *ptr, size_t nmemb, size_t size)
+{
+	void *new_ptr;
+
+	new_ptr = reallocarray(ptr, nmemb, size);
+	if (new_ptr == NULL)
+		fatal("xreallocarray: allocating %zu * %zu bytes",
+		    nmemb, size);
+	return new_ptr;
+}
+
+void *
+xrecallocarray(void *ptr, size_t oldnmemb, size_t nmemb, size_t size)
+{
+	void *new_ptr;
+
+	new_ptr = recallocarray(ptr, oldnmemb, nmemb, size);
+	if (new_ptr == NULL)
+		fatal("xrecallocarray: allocating %zu * %zu bytes",
+		    nmemb, size);
+	return new_ptr;
+}
+
+char *
+xstrdup(const char *str)
+{
+	char *cp;
+
+	if ((cp = strdup(str)) == NULL)
+		fatal("xstrdup");
+	return cp;
+}
+
+int
+xasprintf(char **ret, const char *fmt, ...)
+{
+	va_list ap;
+	int i;
+
+	va_start(ap, fmt);
+	i = vasprintf(ret, fmt, ap);
+	va_end(ap);
+
+	if (i == -1)
+		fatal("xasprintf");
+
+	return i;
+}
blob - /dev/null
blob + 5e11cb4e79d681af3c5b8787d9308302336a092f (mode 644)
--- /dev/null
+++ xmalloc.h
@@ -0,0 +1,31 @@
+/* $OpenBSD: xmalloc.h,v 1.3 2015/11/17 18:25:03 tobias Exp $ */
+
+/*
+ * Author: Tatu Ylonen <ylo@cs.hut.fi>
+ * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
+ *                    All rights reserved
+ * Created: Mon Mar 20 22:09:17 1995 ylo
+ *
+ * Versions of malloc and friends that check their results, and never return
+ * failure (they call fatal if they encounter an error).
+ *
+ * As far as I am concerned, the code I have written for this software
+ * can be used freely for any purpose.  Any derived versions of this
+ * software must be clearly marked as such, and if the derived work is
+ * incompatible with the protocol description in the RFC file, it must be
+ * called by a name other than "ssh" or "Secure Shell".
+ */
+
+#ifndef XMALLOC_H
+#define XMALLOC_H
+
+void	*xmalloc(size_t);
+void	*xcalloc(size_t, size_t);
+void	*xreallocarray(void *, size_t, size_t);
+void	*xrecallocarray(void *, size_t, size_t, size_t);
+char	*xstrdup(const char *);
+int	 xasprintf(char **, const char *, ...)
+                __attribute__((__format__ (printf, 2, 3)))
+                __attribute__((__nonnull__ (2)));
+
+#endif	/* XMALLOC_H */